feat(auth): decouple auth pipeline from harness-specific Go code (Phases 1-2)#516
Conversation
Implement Phases 1 and 2 of the auth pipeline decoupling: Phase 1 - Fix hydration ordering: - Add harness-config hydration to the env-gather pre-check path in handlers.go, before extractRequiredEnvKeys runs - Hub-managed harness-configs are now downloaded (with 10s timeout and graceful fallback) so config-driven auth metadata is available when the broker determines which env vars to request from the CLI - extractRequiredEnvKeys falls back to the hydrated path when on-disk search doesn't find the config Phase 2 - Generic env var passthrough in AuthConfig: - Add EnvVars map[string]string to api.AuthConfig for config-driven auth env vars - Modify GatherAuthWithEnv to accept optional *config.HarnessAuthMetadata and populate EnvVars from declared required_env keys - Modify ContainerScriptHarness.ResolveAuth and Generic.ResolveAuth to forward auth.EnvVars into the container environment - Make isAuthEnvKey config-aware via optional extraAuthKeys parameter - Update filterResolvedSecretsForResolvedAuth to recognize config-driven auth keys This is additive — existing hardcoded paths continue working for all built-in harnesses. The copilot harness's COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN now flows through the auth pipeline via config alone, without any copilot-specific Go code.
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request introduces config-driven auth metadata handling, allowing harness configurations to declare required environment variables that flow through the auth pipeline without hardcoded Go fields. The review feedback highlights a correctness issue where template-bundled harness configs fail to resolve because templatePaths is omitted during directory resolution. Additionally, the feedback suggests refactoring redundant directory resolutions and replacing variadic parameters (extraAuthKeys and hydratedHarnessConfigPath) with standard map and string parameters to avoid unnecessary slice allocations and adhere to idiomatic Go patterns.
| if harnessConfigName != "" { | ||
| if hcDir, err := resolveHarnessConfigDir(ctx, harnessConfigName, projectDir); err == nil && hcDir.Config.Auth != nil { | ||
| authMeta = hcDir.Config.Auth | ||
| } |
There was a problem hiding this comment.
In Start(), resolveHarnessConfigDir is called without templatePaths. However, earlier in Start() (around line 243), it is called with templatePaths... because harness configs can be bundled inside templates (per §3.4 of agnostic-template-design). By omitting templatePaths here, any template-bundled harness configs will fail to resolve, leaving authMeta as nil.\n\nAdditionally, resolving the harness config directory twice from the filesystem/network is inefficient. Consider promoting hcDir (or authMeta) to the outer scope of Start() where it is first resolved, and reusing it here to avoid both the correctness bug and the redundant resolution.
| } | ||
|
|
||
| func isAuthEnvKey(key string) bool { | ||
| func isAuthEnvKey(key string, extraAuthKeys ...map[string]struct{}) bool { |
There was a problem hiding this comment.
Using a variadic parameter extraAuthKeys ...map[string]struct{} causes Go to allocate a new slice on every call to isAuthEnvKey when arguments are passed. Since isAuthEnvKey is called inside hot loops (e.g., when filtering environment variables and secrets), this can lead to unnecessary allocations and garbage collection pressure.\n\nSince we only ever pass a single configKeys map, we should change the parameter to a regular map[string]struct{} (which can be nil).
| func isAuthEnvKey(key string, extraAuthKeys ...map[string]struct{}) bool { | |
| func isAuthEnvKey(key string, extraAuthKeys map[string]struct{}) bool { |
| for _, extra := range extraAuthKeys { | ||
| if _, ok := extra[key]; ok { | ||
| return true | ||
| } | ||
| } | ||
| return false |
| for _, key := range builtins { | ||
| if !isAuthEnvKey(key) { | ||
| t.Errorf("isAuthEnvKey(%q) = false, want true", key) | ||
| } | ||
| } | ||
| if isAuthEnvKey("RANDOM_ENV_VAR") { | ||
| t.Error("isAuthEnvKey(RANDOM_ENV_VAR) = true, want false") | ||
| } |
There was a problem hiding this comment.
| // config directory that supplements the on-disk search. This allows env-gather | ||
| // to see auth metadata from hub-managed harness-configs that haven't been | ||
| // downloaded to the standard on-disk locations yet. | ||
| func (s *Server) extractRequiredEnvKeys(req CreateAgentRequest, hydratedHarnessConfigPath ...string) ([]string, map[string]api.SecretKeyInfo) { |
There was a problem hiding this comment.
Using a variadic parameter hydratedHarnessConfigPath ...string to represent an optional single string is a minor anti-pattern in Go. It is more idiomatic and type-safe to pass a regular string parameter (which can be empty "").
| func (s *Server) extractRequiredEnvKeys(req CreateAgentRequest, hydratedHarnessConfigPath ...string) ([]string, map[string]api.SecretKeyInfo) { | |
| func (s *Server) extractRequiredEnvKeys(req CreateAgentRequest, hydratedHarnessConfigPath string) ([]string, map[string]api.SecretKeyInfo) { |
|
|
||
| // Fall back to hydrated hub-managed harness-config when on-disk | ||
| // search didn't find the config (or didn't populate auth metadata). | ||
| if harnessType == "" && len(hydratedHarnessConfigPath) > 0 && hydratedHarnessConfigPath[0] != "" { |
- Fix gofmt: remove extra blank line in auth_test.go - Fix duplicate config resolution: reuse harness config dir already resolved during image resolution instead of re-resolving without templatePaths - Fix nil safety: add nil check on hcDir in hydrated config fallback in handlers.go
The SCION_TEST_UNSET_TOKEN key doesn't exist in the test environment, so there's nothing to unset. The assertion already verifies that a nonexistent key doesn't appear in EnvVars.
Summary
Implements Phases 1 and 2 of issue 311 — decouples the auth pipeline from harness-specific Go code and fixes hydration ordering.
Phase 1: Fix hydration ordering
extractRequiredEnvKeysin the env-gather (HTTP 202) pathextractRequiredEnvKeysaccepts an optional hydrated path to supplement on-disk searchPhase 2: Generic env var passthrough
EnvVars map[string]stringtoAuthConfigfor config-driven auth env varsGatherAuthWithEnvaccepts*config.HarnessAuthMetadataand populatesEnvVarsfrom declaredrequired_envkeysResolveAuth(bothContainerScriptHarnessandGeneric) forwardsauth.EnvVarsinto container env (hardcoded fields take precedence)isAuthEnvKeymade config-aware via variadicextraAuthKeysparameterfilterResolvedSecretsForResolvedAuthandisAuthCandidateSecretextended with config-driven key awarenessWhat this enables
New harnesses (e.g. GitHub Copilot CLI) can declare auth requirements in their
config.yamlauth:block and have tokens flow through the pipeline without any harness-specific Go code:What this does NOT change
Files changed (8 files, +403/-22)
pkg/api/types.goEnvVars map[string]stringtoAuthConfigpkg/harness/auth.goGatherAuthWithEnvaccepts auth metadata;gatherConfigEnvVarshelperpkg/harness/auth_test.gopkg/harness/container_script_harness.goResolveAuthforwardsauth.EnvVarspkg/harness/generic.goResolveAuthforwardsauth.EnvVarspkg/agent/run.goisAuthEnvKey,configAuthEnvKeySet, auth metadata resolutionpkg/agent/run_test.gopkg/runtimebroker/handlers.goextractRequiredEnvKeysaccepts hydrated pathTest plan
pkg/harness/...tests pass (0.156s) — 6 new config-driven auth testspkg/agent/...tests pass (4.870s) — 4 new testspkg/runtimebroker/...tests pass (2.556s)pkg/api/...tests pass (0.009s)go build ./...cleanCloses issue 311 (Phases 1-2)