Skip to content

feat(aicore): transparent proxy routing and BTP Destination Service mode (Option 3) - #271

Draft
tiagoek wants to merge 3 commits into
feat/aicore-clear-client-secretfrom
feat/aicore-proxy-routing
Draft

feat(aicore): transparent proxy routing and BTP Destination Service mode (Option 3)#271
tiagoek wants to merge 3 commits into
feat/aicore-clear-client-secretfrom
feat/aicore-proxy-routing

Conversation

@tiagoek

@tiagoek tiagoek commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Option 3 from the AFSDK-4306 security alignment meeting (2026-08-14): "Hide the ugliness in the SDK" — agent code is identical in all environments; the deployer controls routing by choosing which env vars to inject.

Tracking: AFSDK-4414 / HASI2026203 (CVE 9.9)
Stacked on: #257 (feat/aicore-clear-client-secret) → #256 (feat/aicore-transparent-tls)

Neither proxy mode nor destination mode requires a LiteLLM upstream change. Both work with the current public litellm package.


What changed

set_aicore_config() now detects the routing mode from environment variables (priority: proxy > destination > direct):

Proxy mode — AICORE_PROXY_URL set

Routes all LiteLLM calls through an external LiteLLM proxy (e.g. ADR 0039):

  • Sets litellm.api_base and litellm.api_key (proxy master API key) globally
  • Model strings (e.g. sap/<model>) are passed verbatim — no prefix rewrite. LiteLLM routes sap/<model> through the configured api_base natively (aligned with ADR 0039)
  • No AI Core credentials written to the process environment
  • JWT never reaches the agent process — the proxy holds the OAuth client secret

Env var naming is intentionally AICORE_* (not LITELLM_*) — the SDK interface is vendor-agnostic and stays stable if the underlying proxy implementation changes.

# Deployer injects these; agent code unchanged
AICORE_PROXY_URL=https://litellm-proxy.cluster.svc
AICORE_PROXY_API_KEY=sk-master-xxx

Destination mode — AICORE_DESTINATION_NAME set

Loads AI Core credentials at startup from a named BTP Destination Service destination:

  • Uses the existing sap_cloud_sdk.destination client — no new dependencies
  • Deployer only needs to inject Destination Service binding credentials into the K8s Secret
  • AICORE_CLIENT_SECRET never needs to be in the K8s Secret — it lives in BTP Destination Service
  • Combined with _clear_client_secret() (PR feat(aicore): clear AICORE_CLIENT_SECRET after token acquisition (AFSDK-4291) #257), the secret is removed from env after the first successful LiteLLM call
  • On AuthenticationError, reload_aicore_credentials() re-fetches fresh credentials from the Destination Service
# Deployer injects these; agent code unchanged
AICORE_DESTINATION_NAME=aicore-instance
# + Destination Service service binding (clientid/clientsecret/url/uri)

End-to-end validated on Kyma managed runtime (AFSDK-4416 ✅).

Direct mode (unchanged)

Neither env var set → existing behaviour: load from mounted K8s secret or env vars. AICORE_TRANSPARENT_TLS still works within direct mode (pending LiteLLM upstream for full end-to-end; see PR #256).


Agent code — identical in all modes

# Same regardless of proxy, destination, or direct mode
set_aicore_config()
response = completion(model="sap/gpt-4o", messages=[{"role": "user", "content": "Hello"}])

Security coverage

Threat Proxy mode Destination mode Direct mode
CLIENT_SECRET in K8s Secret ✅ not needed ✅ not needed ❌ required
CLIENT_SECRET in process env ✅ never ✅ cleared after first call (#257) ✅ cleared after first call (#257)
JWT in agent process memory ✅ never (proxy holds it) ❌ agent receives JWT ❌ agent receives JWT
LiteLLM upstream change required ✅ no ✅ no ⚠️ yes (for transparent TLS)
Zero agent code changes

Tests

New unit tests:

  • TestSetAICoreConfigProxyMode (6 tests) — mode detection, litellm globals, precedence, filtering still applied
  • TestSetAICoreConfigDestinationMode (9 tests) — URL extraction, credentials, resource group, error cases

Full suite: 156 passed, 4 skipped (integration tests requiring real BTP).


Related

…ce mode

Implements Option 3 from the AFSDK-4306 security alignment meeting: SDK
absorbs all routing complexity so agent code is identical in all environments.
The deployer controls routing by choosing which env vars to inject.

Two new modes in set_aicore_config():

Proxy mode (AICORE_PROXY_URL set):
- Routes all LiteLLM calls through an external LiteLLM proxy
- Sets litellm.api_base / litellm.api_key globally
- Rewrites sap/<model> → litellm_proxy/<model> transparently in
  completion() and acompletion() wrappers (including on auth-error retry)
- No AI Core credentials written to the process environment
- JWT never reaches the agent process (proxy handles OAuth)

Destination mode (AICORE_DESTINATION_NAME set):
- Loads AI Core credentials at startup from a named BTP Destination Service
  destination via the existing sap_cloud_sdk.destination client
- Deployer only injects Destination Service binding — AI Core client_secret
  is never in the K8s Secret, only in BTP Destination Service
- Combined with _clear_client_secret() (PR #257), the secret is removed
  from env after the first successful LiteLLM call

Direct mode (neither set): existing behaviour unchanged, including
transparent TLS (AICORE_TRANSPARENT_TLS).

Adds 30 unit tests covering both new modes and all edge cases.

AFSDK-4306
… mode

Model strings (e.g. sap/<model>) are now passed verbatim to LiteLLM in all
routing modes. LiteLLM natively routes sap/<model> through the configured
litellm.api_base without a prefix rewrite. Removes _rewrite_model_for_proxy(),
_set_proxy_active(), and all associated proxy-aliasing tests (6 unit tests).

Aligns with ADR 0039 which explicitly documents the litellm_proxy/ prefix
approach as a rejected alternative.
The previous name was misleading — the SDK reads the LiteLLM proxy master
API key, not a virtual (per-user/per-team) key. AICORE_PROXY_API_KEY is
accurate for both master key and virtual key usage.

Aligned with Sam Garland (CAD) feedback on ADR 0039 review.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant