Env-var control of in-cluster auth mode (ExternalCommand without EAGER_API_KEY) - #1536
Open
EngHabu wants to merge 5 commits into
Open
Env-var control of in-cluster auth mode (ExternalCommand without EAGER_API_KEY)#1536EngHabu wants to merge 5 commits into
EngHabu wants to merge 5 commits into
Conversation
Every config entry doubles as an env var (`admin.command` -> FLYTE_ADMIN_COMMAND),
but `ConfigEntry` applied no transform to the value it read, so a list-typed entry
came back as the raw string. `FLYTE_ADMIN_COMMAND="mint-token --audience flyte"`
reached the external-command authenticator as a str, and
asyncio.create_subprocess_exec(*self._cmd)
spread it one argument per *character*. Same shape for the proxy command and for
scopes.
Add transforms so an env-supplied value is normalized to a list: a JSON array for
arguments that contain spaces, otherwise shlex.split for a command line and a
comma/whitespace split for a value list. YAML already yields a list and passes
through untouched, so the transforms are idempotent on the file path.
Also correct the AUTH_MODE docstring, which listed flytekit-1 spellings
('standard', 'basic', 'client_credentials') that the authenticator factory does
not match on, and omitted ExternalCommand and Passthrough entirely.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUo1yWWgg9hxxgni4hm8XR
Signed-off-by: Haytham Abuelfutuh <haytham@union.ai>
`create_remote_controller` accepted `command`, `proxy_command`, `http_proxy_url` and `client_config` and then never passed them to `ControllerClient`, so they were silently dropped on the way to `create_session_config`. In-cluster that split the two halves apart: `init_in_cluster` builds the SDK client and the controller from the same kwargs, so with `auth_type="ExternalCommand"` the client authenticated correctly while the controller got the auth mode with no command to run. That surfaced only when the controller made its first RPC, as `AuthenticationError: Command cannot be empty for command authenticator` — and the controller is what enqueues and watches child actions, so eager tasks failed at the point of spawning work rather than at init. Build the kwargs once and spread them into both the endpoint and api-key constructors, with a note pointing at `_initialize._initialize_client` so the two sets stay together. `rpc_retries` is deliberately still not forwarded: the controller has never installed the retry interceptor and adding it here would change retry behavior rather than fix a drop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUo1yWWgg9hxxgni4hm8XR Signed-off-by: Haytham Abuelfutuh <haytham@union.ai>
`init_in_cluster` resolved credentials from an explicit api_key, then a mounted
config file (UCTL_CONFIG / FLYTECTL_CONFIG), then the injected _UNION_EAGER_API_KEY
/ EAGER_API_KEY. A deployment that mints its own scoped tokens could only opt out
of the injected key by mounting a config file; there was no env-var-only route,
even though every credentials config entry already has an env var.
Read the auth entries from the environment and thread them through. A pod can now
carry:
FLYTE_ADMIN_AUTHTYPE=ExternalCommand
FLYTE_ADMIN_COMMAND="/usr/local/bin/mint-token --audience flyte"
and authenticate by running that command, with no api key issued to it at all.
Setting an auth type disables the injected-key fallback outright rather than
layering on top of it, so a cluster still injecting a key during migration cannot
have the two silently disagree. The endpoint keeps coming from the injected
_U_EP_OVERRIDE and also accepts FLYTE_ADMIN_ENDPOINT, since dropping the api key
drops the endpoint it used to decode to. FLYTE_ADMIN_PROXYCOMMAND is picked up
independently of the auth type.
An explicit api_key argument still wins, and says so in the log rather than
quietly ignoring the env vars. ExternalCommand with no command raises at init
instead of deferring to an AuthenticationError on the first RPC that names neither
env var.
The entries are read with `ConfigEntry.read()` and no config file, which consults
only the environment - this keeps the pod off `resolve_config_path`, whose
`git rev-parse` subprocess is wasted work in a task pod.
Known gap: the opt-in Rust controller (_F_USE_RUST_CONTROLLER=1) reads the injected
api key directly and does not honor these vars.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUo1yWWgg9hxxgni4hm8XR
Signed-off-by: Haytham Abuelfutuh <haytham@union.ai>
…names `admin.authType` and `admin.command` are auth settings, not "admin" ones. The "admin" in their env vars is not a chosen name at all — `get_env_name()` derives it mechanically from the yaml switch, so `admin.authType` becomes FLYTE_ADMIN_AUTHTYPE. That spelling leaks flyte-1 `flyteadmin` naming into something a customer now has to type into a pod spec, and `read_from_env`'s own docstring already flagged the scheme as provisional. Renaming outright would be worse than the wart. The scheme's one real virtue is that the env var and the config key are derivable from each other in both directions; changing two of the eleven `admin.*` entries would leave no rule, just a lookup table with exceptions. And the derived names are public surface that predates this feature — they are what the config-file path has always used. So make the preferred name explicit instead of implicit. `YamlConfigEntry` takes an `aliases` tuple of hand-written names that are checked before the derived one; `get_env_name()` reports the preferred name (used in docs and error messages) and `env_names()` gives the full precedence order. The auth entries declare FLYTE_AUTH_TYPE, FLYTE_AUTH_COMMAND and FLYTE_AUTH_PROXY_COMMAND; everything that worked before still works. When two names for one setting disagree, the higher-precedence one wins and the conflict is logged rather than silently dropped — that ambiguity is how a migration goes wrong quietly. Identical values are not reported. Scoped deliberately to the three auth entries this feature documents. FLYTE_ADMIN_ENDPOINT keeps its derived name: the endpoint is a platform setting, not an auth one. Renaming the rest of the `admin.*` family is the scheme-wide change the code comment anticipates, and wants its own discussion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUo1yWWgg9hxxgni4hm8XR Signed-off-by: Haytham Abuelfutuh <haytham@union.ai>
`make check-docstrings` rejects reStructuredText in docstrings — this repo's are Markdown — and the docstrings added by this branch used ``double backticks``. Nine were flagged across _initialize.py and _internal.py; replaced with single backticks. Fenced blocks are untouched. Scoped to the docstrings this branch introduced. Pre-existing double backticks in *comments* are left alone: check_docstring_style only inspects docstrings, so they are green on main and rewriting them would be unrelated churn here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P7wJwZqTMDxj8EkRCHkM3q Signed-off-by: Haytham Abuelfutuh <haytham@union.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A customer does not want the control plane issuing eager API keys to their task pods. They mint their own, more narrowly scoped tokens and want the SDK to authenticate with
ExternalCommand, which we already support and prototyped with them.init_in_clusterresolved credentials in this order:api_key=argumentUCTL_CONFIG/FLYTECTL_CONFIGset_UNION_EAGER_API_KEY/EAGER_API_KEYSo the only way to opt out of the injected key was to mount a config file. There was no env-var-only route — even though every credentials config entry already derives an env var (
admin.authType→FLYTE_ADMIN_AUTHTYPE).Two things were also quietly broken along the way, and both had to be fixed for the env-var route to work end to end.
What this does
A task pod can now carry:
and authenticate by running that command, with no API key issued to it at all.
FLYTE_AUTH_COMMANDtakes a shell-quoted command line, or a JSON array when an argument contains spaces. Its stdout is the access token, re-run on refresh.FLYTE_AUTH_TYPEdisables the injected-key fallback outright rather than layering on top of it, so a cluster still injecting a key mid-migration cannot have the two silently disagree._U_EP_OVERRIDE, and now also acceptsFLYTE_ADMIN_ENDPOINT— dropping the API key drops the endpoint it used to decode to.FLYTE_AUTH_PROXY_COMMANDis picked up independently of the auth type.api_key=argument still wins, and logs that it is ignoring the env vars instead of doing so quietly.ExternalCommandwith no command raises at init, rather than deferring to anAuthenticationErroron the first RPC that names neither env var.The entries are read via
ConfigEntry.read()with no config file, which consults only the environment. That deliberately keeps the pod offresolve_config_path, whosegit rev-parsesubprocess is wasted work in a task pod.Two bugs fixed on the way
FLYTE_ADMIN_COMMANDwas unusable even on the existing config-file path.ConfigEntryapplied no transform to env values, so alist-typed entry came back as the raw string andasyncio.create_subprocess_exec(*self._cmd)spread it one argument per character. Same shape for the proxy command and scopes. YAML already yields a list and is unaffected; the new transforms are idempotent on that path.create_remote_controlleracceptedcommandand threw it away. It tookcommand,proxy_command,http_proxy_urlandclient_configand never forwarded them toControllerClient. Sinceinit_in_clusterbuilds the SDK client and the controller from the same kwargs,auth_type="ExternalCommand"would have authenticated the client correctly while leaving the controller holding the auth mode with nothing to run — surfacing only asAuthenticationError: Command cannot be empty for command authenticatoron the controller's first RPC. The controller is what enqueues and watches child actions, so eager tasks would have failed at the point of spawning work rather than at init.rpc_retriesis deliberately still not forwarded there: the controller has never installed the retry interceptor, and adding it would change retry behavior rather than fix a drop.On the env var names
These names are normally derived, not chosen:
get_env_name()doesFLYTE_{SWITCH_UPPERCASED}, soadmin.authType→FLYTE_ADMIN_AUTHTYPE. That spelling leaks flyte-1flyteadminnaming into something a customer has to type into a pod spec, andread_from_env's docstring already called the scheme provisional.Renaming outright would cost more than the wart. The scheme's one real virtue is that env var and config key are derivable from each other both ways; changing 2 of the 11
admin.*entries leaves no rule, just a lookup table with exceptions. The derived names are also public surface predating this feature — they are what the config-file path has always used.So the preferred name is now explicit rather than implicit.
YamlConfigEntrytakes analiasestuple checked ahead of the derived name;get_env_name()reports the preferred one (docs, error messages) andenv_names()gives the precedence order.admin.authTypeFLYTE_AUTH_TYPEFLYTE_ADMIN_AUTHTYPEadmin.commandFLYTE_AUTH_COMMANDFLYTE_ADMIN_COMMANDadmin.proxyCommandFLYTE_AUTH_PROXY_COMMANDFLYTE_ADMIN_PROXYCOMMANDNothing that worked before stops working. When two names for one setting disagree the higher-precedence one wins and the conflict is logged rather than dropped silently — that ambiguity is how a migration goes wrong quietly; identical values are not reported.
Scoped deliberately to the three auth entries this feature documents.
FLYTE_ADMIN_ENDPOINTkeeps its derived name, since the endpoint is a platform setting rather than an auth one. Renaming the rest of theadmin.*family is the scheme-wide change the code comment anticipates and wants its own discussion.Known gap
The opt-in Rust controller (
_F_USE_RUST_CONTROLLER=1) reads the injected API key directly and does not honor these vars. Anyone on it cannot drop the key yet. Called out in theinit_in_clusterdocstring.Testing
tests/user_api/test_init_in_cluster_env_auth.pycovering parsing (shlex, JSON array, quoted args), precedence (explicit auth type beats the injected key; explicitapi_keybeats the env vars; no env vars means unchanged legacy behavior), the endpoint fallback, the loud-failure case, the controller forwarding, and the alias mechanism (preferred name wins, derived name still honored, conflicts reported, non-aliased entries unchanged).AsyncCommandAuthenticatorwhich runs the command and returns its stdout as the token.ruffandmypyclean.Note for reviewers running tests inside a Flyte task pod:
_U_EP_OVERRIDEand_UNION_EAGER_API_KEYare live in that environment and leak into several tests, including a pre-existing one intest_init_in_cluster_fallback.py. Unset them for a clean run.🤖 Generated with Claude Code
https://claude.ai/code/session_01AUo1yWWgg9hxxgni4hm8XR