Skip to content

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

Draft
tiagoek wants to merge 1 commit 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 1 commit 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 (Tiago Kochenborger + Thiago Bohn + Felipe Morais, 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.

Stacked on: #257 (feat/aicore-clear-client-secret)


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 0031):

  • Sets litellm.api_base and litellm.api_key (virtual key) globally
  • Rewrites sap/<model>litellm_proxy/<model> transparently in completion() and acompletion(), including on auth-error retry
  • No AI Core credentials written to the process environment
  • JWT never reaches the agent process — the proxy holds the OAuth client secret
# Deployer injects these; agent code unchanged
AICORE_PROXY_URL=https://litellm-proxy.cluster.svc
AICORE_PROXY_VIRTUAL_KEY=sk-virtual-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
  • 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)

The destination must be configured with OAuth2ClientCredentials and the app must have the Destination Service technical-user scope so that clientId/clientSecret/tokenServiceURL are returned by the API.

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.


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
Zero agent code changes

Tests

30 new unit tests across two new test classes in each file:

  • TestSetAICoreConfigProxyMode (7 tests) — mode detection, litellm globals, precedence, filtering still applied
  • TestSetAICoreConfigDestinationMode (9 tests) — URL extraction, credentials, resource group, error cases
  • TestCompletionProxyModeAliasing (4 tests) — model rewrite on call and retry, no-op when inactive
  • TestACompletionProxyModeAliasing (2 tests) — async path

Full suite: 163 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
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