Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion exgentic_a2a_runner/authbridge/apply-pipeline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ JWT_VALIDATION_KEYCLOAK_URL="${JWT_VALIDATION_KEYCLOAK_URL:-$(_ab_base_key KEYCL
JWT_VALIDATION_KEYCLOAK_REALM="${JWT_VALIDATION_KEYCLOAK_REALM:-$(_ab_base_key KEYCLOAK_REALM)}"
export JWT_VALIDATION_ISSUER JWT_VALIDATION_KEYCLOAK_URL JWT_VALIDATION_KEYCLOAK_REALM

# --- SPARC defaults (consumed only when sparc is in the resolved set).
SPARC_REFLECTOR_ENDPOINT="${SPARC_REFLECTOR_ENDPOINT:-http://sparc-service.rossoctl-system.svc.cluster.local:8090}"
SPARC_TIMEOUT_MS="${SPARC_TIMEOUT_MS:-30000}"
export SPARC_REFLECTOR_ENDPOINT SPARC_TIMEOUT_MS

echo "Applying AuthBridge pipeline to $NAMESPACE/$AGENT_NAME"
echo " Plugins: $PIPELINE_PLUGINS"
if [ -n "${PIPELINE_OVERLAY_FILE:-}" ]; then
Expand Down Expand Up @@ -113,7 +118,7 @@ fi

# --- Pre-flight: validate plugin names in PIPELINE_PLUGINS up front so
# we fail before any kubectl call.
KNOWN_PLUGINS=(jwt-validation token-exchange token-broker a2a-parser mcp-parser inference-parser ibac)
KNOWN_PLUGINS=(jwt-validation token-exchange token-broker a2a-parser mcp-parser inference-parser sparc ibac)
VALID_POLICIES=(enforce observe off)
is_known() { local needle=$1; shift; for x in "$@"; do [[ "$x" == "$needle" ]] && return 0; done; return 1; }
for tok in $PIPELINE_PLUGINS; do
Expand Down
3 changes: 2 additions & 1 deletion exgentic_a2a_runner/authbridge/pipeline-merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"token-broker", # gate (mutex with token-exchange)
"inference-parser", # observe-only, after token gate
"mcp-parser", # observe-only, after token gate
"ibac", # last: reads parser extension slots
"sparc", # semantic pre-execution guardrail; reads parser slots
"ibac", # intent-based access control; reads parser extension slots
]

CHAIN_FOR: dict[str, str] = {p: "inbound" for p in INBOUND_ORDER}
Expand Down
14 changes: 14 additions & 0 deletions exgentic_a2a_runner/authbridge/plugins/sparc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPARC plugin fragment.
#
# ${VAR} placeholders are expanded by apply-pipeline.sh (envsubst) before
# the merge runs. SPARC_REFLECTOR_ENDPOINT defaults to the in-cluster
# sparc-service deployed by kagenti-extensions.
name: sparc
config:
reflector_endpoint: "${SPARC_REFLECTOR_ENDPOINT}"
enforcement: "mcp"
track: "fast_track"
on_reject_action: "observe"
deny_score_threshold: 0
fail_policy: "open"
timeout_ms: ${SPARC_TIMEOUT_MS}
4 changes: 2 additions & 2 deletions exgentic_a2a_runner/authbridge/presets/full.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# full preset.
# Everything on: auth + token exchange + parsers + IBAC.
# Everything on: auth + token exchange + parsers + SPARC + IBAC.
inbound: [a2a-parser, jwt-validation]
outbound: [token-exchange, inference-parser, mcp-parser, ibac]
outbound: [token-exchange, inference-parser, mcp-parser, sparc, ibac]
6 changes: 6 additions & 0 deletions exgentic_a2a_runner/authbridge/presets/sparc-only.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# sparc-only preset.
# SPARC guardrails without inbound auth (e.g. an upstream gateway has already
# terminated auth, or the runner calls the agent directly without a JWT).
# Outbound SPARC needs the parsers to populate extension slots.
inbound: [a2a-parser]
outbound: [inference-parser, mcp-parser, sparc]
51 changes: 42 additions & 9 deletions exgentic_a2a_runner/deploy-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,14 @@ while [[ $# -gt 0 ]]; do
echo " --in-cluster Running as a Kubernetes Job inside the cluster"
echo ""
echo "AuthBridge plugin pipeline (see AUTHBRIDGE_PIPELINE_SPEC.md):"
echo " --plugin-preset PRESET Named bundle: auth-only | ibac-only | full"
echo " --plugin-preset PRESET Named bundle: auth-only | ibac-only | sparc-only | full"
echo " --plugin NAME[:POLICY] Enable plugin; POLICY ∈ {enforce(default), observe, off}; repeatable"
echo " --no-plugin NAME Shorthand for --plugin NAME:off; repeatable"
echo " --plugin-config-file PATH Flat-map YAML overlay merged after selectors"
echo " -h, --help Show this help message"
echo ""
echo "Plugin names: jwt-validation, token-exchange, token-broker, a2a-parser,"
echo " mcp-parser, inference-parser, ibac"
echo " mcp-parser, inference-parser, sparc, ibac"
echo ""
echo "Plugin tunables (consumed when the named plugin is in the active set):"
echo " IBAC_JUDGE_ENDPOINT Judge LLM base URL (default: http://host.docker.internal:11434)"
Expand Down Expand Up @@ -556,6 +556,12 @@ ENV_VARS_WITH_CONFIG=$(echo "$ENV_VARS_WITH_CONFIG" | jq ". + [{\"name\": \"EXGE
echo "Adding LITELLM_LOCAL_MODEL_COST_MAP=True"
ENV_VARS_WITH_CONFIG=$(echo "$ENV_VARS_WITH_CONFIG" | jq ". + [{\"name\": \"LITELLM_LOCAL_MODEL_COST_MAP\", \"value\": \"True\"}]")

# Disable LiteLLM response caching. The exgentic default is litellm_caching=True.
# Cached runs complete in ~3.6s with no real LLM/tool calls, so inference-parser
# never captures the conversation context and SPARC/IBAC never fire.
echo "Adding EXGENTIC_LITELLM_CACHING=false"
ENV_VARS_WITH_CONFIG=$(echo "$ENV_VARS_WITH_CONFIG" | jq ". + [{\"name\": \"EXGENTIC_LITELLM_CACHING\", \"value\": \"false\"}]")

echo "✓ Environment variables prepared for deployment"
echo ""

Expand Down Expand Up @@ -590,7 +596,7 @@ import os, sys
import yaml

KNOWN = ["jwt-validation", "token-exchange", "token-broker",
"a2a-parser", "mcp-parser", "inference-parser", "ibac"]
"a2a-parser", "mcp-parser", "inference-parser", "sparc", "ibac"]
VALID = ("enforce", "observe", "off")
CHAIN = {p: ("inbound" if p in ("a2a-parser", "jwt-validation") else "outbound")
for p in KNOWN}
Expand All @@ -605,7 +611,7 @@ if preset:
pdir = os.environ.get("PRESETS_DIR", "")
pfile = os.path.join(pdir, f"{preset}.yaml")
if not os.path.isfile(pfile):
die(f"unknown preset '{preset}' (looked for {pfile}); available: auth-only, ibac-only, full")
die(f"unknown preset '{preset}' (looked for {pfile}); available: auth-only, ibac-only, sparc-only, full")
with open(pfile) as f:
d = yaml.safe_load(f) or {}
for chain in ("inbound", "outbound"):
Expand Down Expand Up @@ -905,12 +911,27 @@ fi

echo ""

# Step 11.4: Apply the AuthBridge plugin pipeline overlay (if any
# plugin selector was supplied). The operator base config enables every
# supported plugin; this overlay sets on_error: off on the ones we
# didn't select and tunes config on the ones we did.
# Step 11.4: Enable TLS bridge on AgentRuntime. We set this on the CR so the
# operator provisions the cert-manager CA secret. The actual tls_bridge config
# is injected directly into the ConfigMap in Step 11.5 to avoid race conditions
# with the operator's reconciliation loop.
if [ "$AUTHBRIDGE_ENABLED" = "true" ]; then
echo "Step 11.4: Enabling TLS bridge on AgentRuntime..."
if kubectl patch agentruntime -n "$NAMESPACE" "$AGENT_NAME" --type=merge -p '{"spec":{"tlsBridgeMode":"enabled"}}' 2>/dev/null; then
echo " ✓ tlsBridgeMode=enabled (CA will be provisioned by cert-manager)"
else
echo " Warning: Could not patch AgentRuntime tlsBridgeMode (CR may not exist yet)"
fi
echo ""
fi

# Step 11.5: Apply the AuthBridge plugin pipeline overlay AFTER the TLS bridge
# rollout has settled. The operator has now written its base ConfigMap; this
# overlay patches the plugin selection on top without triggering another rollout.
# Also injects tls_bridge config directly into the ConfigMap — the operator does
# not reliably write this even when tlsBridgeMode=enabled is set on AgentRuntime.
if [ "$AUTHBRIDGE_ENABLED" = "true" ]; then
echo "Step 11.4: Applying AuthBridge pipeline overlay..."
echo "Step 11.5: Applying AuthBridge pipeline overlay..."
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ ! -x "$SCRIPT_DIR/authbridge/apply-pipeline.sh" ]; then
echo "Error: $SCRIPT_DIR/authbridge/apply-pipeline.sh not found or not executable"
Expand All @@ -929,6 +950,18 @@ if [ "$AUTHBRIDGE_ENABLED" = "true" ]; then
TOKEN_BROKER_URL="${TOKEN_BROKER_URL:-}" \
TOKEN_BROKER_AUDIENCE="${TOKEN_BROKER_AUDIENCE:-}" \
"$SCRIPT_DIR/authbridge/apply-pipeline.sh"

# Inject tls_bridge section directly into the ConfigMap. The authbridge
# filesystem-watch reloader picks this up without a pod restart.
echo " Injecting tls_bridge into authbridge ConfigMap..."
CM_NAME="authbridge-config-$AGENT_NAME"
CURRENT_CONFIG=$(kubectl -n "$NAMESPACE" get configmap "$CM_NAME" -o jsonpath='{.data.config\.yaml}')
NEW_CONFIG=$(printf '%s\ntls_bridge:\n ca_dir: /etc/authbridge/tls-bridge-ca\n mode: enabled\n' "$CURRENT_CONFIG")
TMP_TLS=$(mktemp)
printf '%s' "$NEW_CONFIG" >"$TMP_TLS"
kubectl -n "$NAMESPACE" create configmap "$CM_NAME" --from-file=config.yaml="$TMP_TLS" --dry-run=client -o yaml | kubectl apply -f - >/dev/null
rm -f "$TMP_TLS"
echo " ✓ tls_bridge injected into $CM_NAME"
echo ""
fi

Expand Down
2 changes: 1 addition & 1 deletion exgentic_a2a_runner/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dependencies = [
"opentelemetry-exporter-otlp>=1.20.0",
"opentelemetry-instrumentation-requests>=0.41b0",
"opentelemetry-instrumentation-httpx>=0.41b0",
"pyyaml>=6.0",
"pyyaml>=6.0.3",
]

[project.optional-dependencies]
Expand Down
3 changes: 3 additions & 0 deletions exgentic_a2a_runner/sparc-deny.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
sparc:
on_reject_action: "deny"
skip_tools: ["message"]
2 changes: 2 additions & 0 deletions exgentic_a2a_runner/sparc-observe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sparc:
on_reject_action: "observe"
2 changes: 1 addition & 1 deletion exgentic_a2a_runner/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.