Repository navigation
Conversation
Read the default Docker Hub session from the secrets engine that Docker Desktop serves on its engine socket before asking the Desktop backend API, as in docker/gordon#2510 and docker/cloud#304. The Desktop /registry/token endpoint, the token minted from the docker login access token and the forced Desktop refresh remain fallbacks, in that order. The engine only adds a source: it sits behind the docker-desktop source at the end of the environment chain, so a DOCKER_TOKEN from the environment, run secrets, env files or a credential helper (CI) still wins. Lookups are bounded to 5s on their own context, a failing engine is skipped for 30s, and a missing session is a clean miss. Platforms the SDK does not support, js/wasm included, get a stub.
A missing engine is routine on machines without Docker Desktop and stays at debug level, but other failures, such as access being denied, now log a warning. Only the first failure in a run warns, so a long-lived process doesn't repeat it after every cooldown; the next run starts once the engine answers again. Telling a missing engine apart needs the SDK's client package, so the check lives in the build-tagged files and js/wasm still builds. Also: the lookup budget and cooldown are consts, the cooldown state goes through small helpers, a canceled caller keeps the engine's error, a token refused mid-lookup is no longer returned with ok=false, and a test that replaced hubAuth now restores it itself. The GetTokenWithSource comment and the Docker authentication docs list the token sources in order.
dgageot
left a comment
There was a problem hiding this comment.
Cache hits add no socket work. On a cache miss, an absent engine adds a failed dial; a usable engine session needs two sequential RPCs instead of the previous single token request; a stalled engine can add five seconds. The deadline interaction below is a concrete regression. Reproduced the deadline, concurrent-probe, and identity cases with temporary tests; desktop race tests pass. The existing socket-error classification comment already covers the logging issue.
…fall back The engine lookup's five-second budget was derived from the caller's context, so it could only shrink: gateway model discovery gives auth and its request five seconds in all, and a stalled engine used all of it before /registry/token could run. That caller had then given up, so the cooldown was skipped and every discovery stalled again. Concurrent cache misses also each probed the engine before any failure started the cooldown. Callers now share one lookup that runs detached on its own budget, the way forced refreshes already do. Each caller waits at most half of its remaining time, capped at the budget, then falls back. The lookup always finishes, so a stalled engine starts the cooldown even when every caller left, and logs its failure once rather than once per caller. The docs no longer claim the engine never holds up model requests.
The SDK wraps every failed dial in ErrSecretsEngineNotAvailable, permission denied and timeouts included, so an engine we may not connect to was logged at debug level as if Docker Desktop were not installed. Only a missing socket or one nothing listens on is routine now; anything else gets the warning. Tests build the SDK's real error shape instead of wrapping a string. docker/secrets-engine#677 adds ErrSecretsEngineUnreachable to do this in the SDK.
GetUserInfo asked Docker Desktop's /registry/info first and only read the token's claims when that failed, while GetToken now prefers the secrets engine. Read the account from the claims of the token GetToken serves, so `debug auth`, DOCKER_USERNAME and DOCKER_EMAIL name the account requests are sent as. /registry/info remains the fallback for a token that carries no account, such as a personal access token some Docker Desktop releases serve.
The engine client read the production Docker Hub realms only, so a staging setup was served a production session or none. When DOCKER_AGENT_HUB_LOGIN_URL points the token exchange at a staging host, such as hub-stage.docker.com, read the staging realms instead. Docker Desktop doesn't say which environment it is signed in to, so a Desktop on staging still needs that variable; the godoc and docs say so.
|
Thanks both. I've pushed fixes for all the review threads, with replies inline:
On the latency summary: agreed on the numbers. On a cache miss, a usable engine session takes two sequential RPCs where there was one request before. The 30s token cache keeps misses rare, so I've left that as is. A stalled engine is now bounded by half the caller's remaining time, and it starts the cooldown. |
…tRunning Bump github.com/docker/secrets-engine/client to v0.2.0, which reports failed dials as a ConnectError carrying the reason. Match ErrSecretsEngineNotRunning instead of ENOENT and ECONNREFUSED ourselves, drop the now unused errno files, and replace the secretsEngineUnavailable helper with a per-platform errNoSecretsEngine sentinel.
affd741 to
a381ab6
Compare
A failed connection to the engine is a secrets-engine ConnectError, which knows how to recover from it, such as starting Docker Desktop. Log its Hint() alongside the error, and log through a single slog.Log call.
Shorten godocs to a single line, drop comments inside function bodies, and remove comments from the new test files.
Move the lookup, cooldown and logging state into a secretsEngine struct that owns its client and logger. Tests build their own engine with a fake client instead of swapping package globals, so the engine tests run with t.Parallel. Drop the hubAuth and hubStaging vars; GetTokenWithSource reads a lazily created default engine.
… built Call hubauth.Staging() in newSecretsEngineHubAuth instead of passing the choice through secretsEngineHubOptions. Drop the helper and its test, which only exercised the SDK's dockerhub.Staging().
Put secrets_engine.go behind the darwin/linux/windows build tag so it uses the SDK directly: ErrSecretsEngineNotRunning, ConnectError.Hint and the client constructor. This drops the errNoSecretsEngine, secretsEngineHint and newSecretsEngineHubAuth wrappers. Other platforms get a single secretsEngineToken stub that finds no token.
The default engine stored slog.Default() when it was created, so it kept logging to that logger even after the default changed, as eval's temporary swap does. Fall back to slog.Default() at log time instead; tests still pass in their own logger.
Record the last secrets engine lookup error and expose it as desktop.SecretsEngineError. debug auth prints it with the SDK's ConnectError hint, in the text output and as secrets_engine in the JSON. A secrets engine that isn't running is left out when another source supplied the token.
…ate lines debug auth printed the secrets engine's full error chain, which repeated the socket path and named the internal step that failed. Print the SDK's ConnectError reason, socket path and hint on their own lines instead, falling back to the full error when there is no known reason. The JSON output gains reason and socket and keeps the full error.
Warning
Deprecation notice: Docker Desktop's
/registry/tokenendpoint is being deprecated. It is retained here only as a fallback while the secrets-engine SDK becomes the primary source of the Docker Hub session, and will be removed in a future change.What
docker-agent now reads the default Docker Hub session from the Docker secrets engine before it asks Docker Desktop's backend API for a token. This matches docker/gordon#2510 and docker/cloud#304.
When the Docker Desktop source of the environment chain needs
DOCKER_TOKEN, it tries these in order:HubAuth().GetDefaultSession()over Docker Desktop's engine socket (api.DesktopSocketPath()). The SDK defaults to the standalone daemon socket, which Desktop doesn't serve; this is the same fix as in docker/cloud#304./registry/token(unchanged).docker loginstored (unchanged).Also:
DOCKER_AGENT_HUB_LOGIN_URLpoints the token exchange at a Docker Hub staging host (*-stage.docker.com), the engine's staging realms are read instead (dockerhub.Staging()).GetUserInfonow names the account of the token in use, read from its claims, so it matches the account requests are sent as. Docker Desktop's account is the fallback for a token without one, such as a personal access token.docker agent debug authreports the new source asdocker desktop secrets engineand, when the engine couldn't be read, says why with the SDK's hint (see below).Other credentials (CI etc.)
docker-desktopsource at the end of the environment chain. ADOCKER_TOKENfrom the environment, run secrets, an env file or a credential helper still wins, and the engine isn't asked.ErrSecretsEngineNotRunning) is logged at debug level only. Other failures (permission denied, a timeout, access denied, a session that can't be decoded) log a warning, only for the first failure in a run, so a long-running process doesn't repeat it after every cooldown.dockerhub.ErrNoSession) is a clean miss with no cooldown, so a new sign-in is picked up right away.InvalidateToken), is never served. It falls through so minting or the forced refresh can replace it. Engine tokens share the existing 30 s in-memory cache.How a failure shows up
A failed lookup never fails a request: the next source is tried. Without
--debugthe CLI discards logs, sodebug authis where a user sees why the engine was skipped. It prints the SDK'sConnectErrorreason, socket and hint, falling back to the full error when there is no known reason. An engine that isn't running is only reported when no token was found, since machines without Docker Desktop don't have one.With Docker Desktop quit and minting disabled:
When another source supplied the token, other failures are still reported. For example, when the user can't open the socket:
The JSON output adds a
secrets_engineobject that also keeps the fullerror:With
--debug, the debug log records the same failure, with the hint in ahint=field.Changes
go.mod/go.sum:github.com/docker/secrets-engine/client v0.2.0andgithub.com/docker/secrets-engine/x v0.8.3. v0.2.0 is the first release withErrSecretsEngineNotRunning(feat(client)!: report why the client could not connect to the engine secrets-engine#677), and it reports failed connections as aConnectErrorcarrying the reason, socket and a hint. The only other new module isconnectrpc.com/connect, pulled in indirectly.pkg/desktop/secrets_engine.go(darwin, linux, windows): thesecretsEnginetype, which owns the SDK client and handles the lookup, its time cap, shared lookups, the cooldown and failure logging, plusdesktop.SecretsEngineError()fordebug auth.pkg/desktop/secrets_engine_other.go: the stub everywhere else.pkg/desktop/login.go: the engine step, theSourceSecretsEnginelabel, andGetUserInfonaming the account of the token in use.pkg/hubauth/exchange.go:Staging(), which picks the engine's staging realms.cmd/root/debug_auth.go: why the engine supplied no token, in the text and JSON output.guides/secrets(Docker Authentication),features/cli(debug authsources and secrets engine fields), andconfiguration/overview(staging sessions forDOCKER_AGENT_HUB_LOGIN_URL).Testing
pkg/desktop/secrets_engine_test.goruns the realdockerhubaccessor against a fake resolver.secretsEnginewith a fake client and run witht.Parallel(). They cover: the cooldown after a missing engine, access denied or a lookup failure; no cooldown when nobody is signed in; a client that can't be created; the 5 s budget and the half-deadline rule; a lookup that outlives its callers; a canceled caller; concurrent callers sharing one lookup; and recording the last lookup error.GetTokenWithSourcecover: engine preferred; cached; signed out, then signing in; a failing engine falling back; expiring token; expired token falling through to minting; rejected token; andGetUserInfonaming the engine token's account.pkg/desktop/secrets_engine_logging_test.gochecks the log level for each kind of failure, that connection failures carry the SDK's hint, and that other failures warn once per run.cmd/root/debug_auth_test.gochecks which failuresdebug authreports, with their reason, socket and hint, and how they print.pkg/hubauth/exchange_test.gochecks staging host detection.pkg/desktoptests install a signed-out fake engine, so they never reach a developer's own Docker Desktop.go test -racepasses forpkg/desktop,pkg/environment,pkg/hubauth,pkg/sandboxandcmd/root.go run ./lint .and the tidy check pass.docker_agent_wasm_cloud.debug authwith Docker Desktop quit, as shown above.Open question
docker/cloud#304 counts engine failures as an OTel
secrets_engine_auth_failuremetric; docker/gordon#2510 doesn't. This PR logs (debug when no engine is running, a warning for the first other failure in a run) and shows the failure indebug auth. It sends no telemetry. Happy to add an event if we want that signal.