88
99- **Default flow** (everything except runtime). One authorizer handles
1010 every operation that does not have a specific override:
11- :class:`HeaderAuthProvider` (local credentials) or
11+ :class:`NoAuthProvider` (no credentials),
12+ :class:`HeaderAuthProvider` (local API keys), or
1213 :class:`HttpUpstreamAuthProvider` (forwards to a configurable URL).
13- - **Runtime flow.** When ``AGENT_CONTROL_RUNTIME_TOKEN_SECRET`` is
14- configured, :class:`LocalJwtVerifyProvider` is registered as the
15- override for :data:`Operation.RUNTIME_USE`; the
16- ``runtime.token_exchange`` operation continues to flow through the
17- default authorizer because the exchange itself is shaped like a
18- management call (forward credential, get grant). Without the secret,
19- no runtime override is installed.
14+ - **Runtime flow.** ``AGENT_CONTROL_RUNTIME_AUTH_MODE`` selects the
15+ override for :data:`Operation.RUNTIME_USE`: ``none`` uses
16+ :class:`NoAuthProvider`, ``api_key`` uses
17+ :class:`HeaderAuthProvider`, and ``jwt`` uses
18+ :class:`LocalJwtVerifyProvider`. When the mode is unset, startup
19+ preserves historical behavior by selecting ``jwt`` if
20+ ``AGENT_CONTROL_RUNTIME_TOKEN_SECRET`` is set, otherwise ``api_key``.
21+ The ``runtime.token_exchange`` operation continues to flow through
22+ the default authorizer because the exchange itself is shaped like a
23+ management call (forward credential, get grant).
2024"""
2125
2226from __future__ import annotations
3034 HeaderAuthProvider ,
3135 HttpUpstreamAuthProvider ,
3236 LocalJwtVerifyProvider ,
37+ NoAuthProvider ,
3338)
3439from .providers .http_upstream import HttpUpstreamConfig
3540
4348_UPSTREAM_TOKEN_HEADER_ENV = "AGENT_CONTROL_AUTH_UPSTREAM_SERVICE_TOKEN_HEADER"
4449
4550# Runtime flow.
51+ _RUNTIME_MODE_ENV = "AGENT_CONTROL_RUNTIME_AUTH_MODE"
4652_RUNTIME_TOKEN_SECRET_ENV = "AGENT_CONTROL_RUNTIME_TOKEN_SECRET"
4753_RUNTIME_TOKEN_TTL_ENV = "AGENT_CONTROL_RUNTIME_TOKEN_TTL_SECONDS"
4854_DEFAULT_RUNTIME_TOKEN_TTL_SECONDS = 300
@@ -80,15 +86,19 @@ def configure_auth_from_env() -> None:
8086
8187 Default flow:
8288
83- - ``AGENT_CONTROL_AUTH_MODE=header`` (default): :class:`HeaderAuthProvider`.
89+ - ``AGENT_CONTROL_AUTH_MODE=none``: :class:`NoAuthProvider`.
90+ - ``AGENT_CONTROL_AUTH_MODE=api_key`` (default): :class:`HeaderAuthProvider`.
91+ ``header`` remains accepted as a backwards-compatible alias.
8492 - ``AGENT_CONTROL_AUTH_MODE=http_upstream``: :class:`HttpUpstreamAuthProvider`
8593 pointed at ``AGENT_CONTROL_AUTH_UPSTREAM_URL``.
8694
8795 Runtime flow:
8896
89- - When ``AGENT_CONTROL_RUNTIME_TOKEN_SECRET`` is set, register
90- :class:`LocalJwtVerifyProvider` as an override for
91- :data:`Operation.RUNTIME_USE`.
97+ - ``AGENT_CONTROL_RUNTIME_AUTH_MODE=none``: :class:`NoAuthProvider`.
98+ - ``AGENT_CONTROL_RUNTIME_AUTH_MODE=api_key`` (default when no runtime
99+ token secret is configured): :class:`HeaderAuthProvider`.
100+ - ``AGENT_CONTROL_RUNTIME_AUTH_MODE=jwt`` (default when a runtime token
101+ secret is configured): :class:`LocalJwtVerifyProvider`.
92102
93103 Clears any previously-installed default and operation overrides
94104 before installing fresh ones, so reconfiguration cannot leave
@@ -101,27 +111,27 @@ def configure_auth_from_env() -> None:
101111 global _runtime_auth_config
102112 clear_authorizers ()
103113 _active_providers .clear ()
104- _runtime_auth_config = _load_runtime_auth_config ()
114+ runtime_mode = _resolve_runtime_mode ()
115+ _runtime_auth_config = (
116+ _load_runtime_auth_config (require_secret = True ) if runtime_mode == "jwt" else None
117+ )
105118
106119 default = _build_default_provider ()
107120 set_authorizer (default )
108121 _active_providers .append (default )
109122
110- if _runtime_auth_config is not None :
111- runtime_provider = LocalJwtVerifyProvider ( secret = _runtime_auth_config . secret )
112- set_authorizer (runtime_provider , operation = Operation . RUNTIME_USE )
113- _active_providers . append ( runtime_provider )
123+ runtime_provider = _build_runtime_provider ( runtime_mode , _runtime_auth_config )
124+ set_authorizer ( runtime_provider , operation = Operation . RUNTIME_USE )
125+ _active_providers . append (runtime_provider )
126+ if runtime_mode == "jwt" :
114127 _logger .info (
115- "Runtime auth enabled: LocalJwtVerifyProvider override installed for %s" ,
128+ "Runtime auth provider: jwt override installed for %s" ,
116129 Operation .RUNTIME_USE .value ,
117130 )
118131 else :
119- _logger .warning (
120- "Runtime auth disabled (%s not set); %s falls through to the "
121- "default authorizer, which may grant any authenticated credential. "
122- "Set the runtime token secret to bind runtime calls to a "
123- "short-lived target-scoped JWT." ,
124- _RUNTIME_TOKEN_SECRET_ENV ,
132+ _logger .info (
133+ "Runtime auth provider: %s override installed for %s" ,
134+ runtime_mode ,
125135 Operation .RUNTIME_USE .value ,
126136 )
127137
@@ -172,9 +182,12 @@ def set_runtime_auth_config(config: RuntimeAuthConfig | None) -> None:
172182
173183
174184def _build_default_provider () -> RequestAuthorizer :
175- mode = os .environ .get (_MODE_ENV , "header" ).strip ().lower ()
176- if mode == "header" :
177- _logger .info ("Default auth provider: header (local credentials)" )
185+ mode = os .environ .get (_MODE_ENV , "api_key" ).strip ().lower ()
186+ if mode in {"none" , "no_auth" }:
187+ _logger .info ("Default auth provider: none" )
188+ return NoAuthProvider ()
189+ if mode in {"api_key" , "header" }:
190+ _logger .info ("Default auth provider: api_key (local credentials)" )
178191 return HeaderAuthProvider ()
179192 if mode == "http_upstream" :
180193 url = os .environ .get (_UPSTREAM_URL_ENV )
@@ -192,19 +205,60 @@ def _build_default_provider() -> RequestAuthorizer:
192205 service_token_header = token_header ,
193206 )
194207 )
195- raise RuntimeError (f"Unknown { _MODE_ENV } ={ mode !r} ; expected 'header' or 'http_upstream'." )
208+ raise RuntimeError (
209+ f"Unknown { _MODE_ENV } ={ mode !r} ; expected 'none', 'api_key', or 'http_upstream'."
210+ )
211+
212+
213+ def _resolve_runtime_mode () -> str :
214+ raw = os .environ .get (_RUNTIME_MODE_ENV )
215+ if raw is None or not raw .strip ():
216+ return "jwt" if os .environ .get (_RUNTIME_TOKEN_SECRET_ENV ) else "api_key"
217+
218+ mode = raw .strip ().lower ()
219+ if mode in {"none" , "no_auth" }:
220+ return "none"
221+ if mode in {"api_key" , "header" }:
222+ return "api_key"
223+ if mode == "jwt" :
224+ return mode
225+ raise RuntimeError (
226+ f"Unknown { _RUNTIME_MODE_ENV } ={ mode !r} ; expected 'none', 'api_key', or 'jwt'."
227+ )
228+
229+
230+ def _build_runtime_provider (
231+ mode : str ,
232+ config : RuntimeAuthConfig | None ,
233+ ) -> RequestAuthorizer :
234+ if mode == "none" :
235+ return NoAuthProvider ()
236+ if mode == "api_key" :
237+ return HeaderAuthProvider ()
238+ if mode == "jwt" :
239+ if config is None :
240+ raise RuntimeError (f"{ _RUNTIME_MODE_ENV } =jwt but runtime auth config is missing." )
241+ return LocalJwtVerifyProvider (secret = config .secret )
242+ raise RuntimeError (
243+ f"Unknown runtime auth mode { mode !r} ; expected 'none', 'api_key', or 'jwt'."
244+ )
196245
197246
198- def _load_runtime_auth_config () -> RuntimeAuthConfig | None :
247+ def _load_runtime_auth_config (* , require_secret : bool = False ) -> RuntimeAuthConfig | None :
199248 """Parse, validate, and return the runtime-auth config from env.
200249
201- Returns ``None`` when no runtime secret is configured. Raises
202- ``RuntimeError`` when the secret is too short or the TTL is invalid
203- so misconfiguration surfaces at startup, not on the first
204- request-time mint.
250+ Returns ``None`` when no runtime secret is configured and
251+ ``require_secret`` is false. Raises ``RuntimeError`` when the
252+ secret is required, too short, or the TTL is invalid so
253+ misconfiguration surfaces at startup, not on the first request-time
254+ mint.
205255 """
206256 secret = os .environ .get (_RUNTIME_TOKEN_SECRET_ENV )
207257 if not secret :
258+ if require_secret :
259+ raise RuntimeError (
260+ f"{ _RUNTIME_MODE_ENV } =jwt requires { _RUNTIME_TOKEN_SECRET_ENV } to be set."
261+ )
208262 return None
209263 if len (secret .encode ("utf-8" )) < _RUNTIME_TOKEN_SECRET_MIN_BYTES :
210264 raise RuntimeError (
0 commit comments