feat(client)!: expose Docker Hub auth as HubAuth sub-client - #618
Draft
Benehiko wants to merge 6 commits into
Draft
feat(client)!: expose Docker Hub auth as HubAuth sub-client#618Benehiko wants to merge 6 commits into
Benehiko wants to merge 6 commits into
Conversation
Add a client/dockerhub package that fetches Docker Hub access tokens from the secrets engine and decodes them into typed sessions, so consumers no longer need to know the realm layout or the JSON payload format. The package resolves the default signed-in account through the profile metadata realm (docker/auth/metadata/hub/default) or fetches a specific username directly under docker/auth/hub/, and parses the served payload into a UserSession with typed JWT claims. Claim parsing is dependency-free: numeric dates accept integer and fractional epochs and the audience accepts both string and array forms, per RFC 7519. Envelope decoding is deliberately lenient: undecodable envelopes are skipped, empty result sets map to ErrNoSession/ErrNoDefaultProfile, and usernames are parsed as strict IDs so wildcard patterns cannot be injected. Verified live against the engine embedded in Docker Desktop: default profile resolution, per-user fetch, not-found mapping, and profile metadata parsing all round-trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the dockerhub.Client wrapper type and its constructor. Consumers pass the standard secrets-engine client to package functions instead of instantiating a second client object; staging becomes a per-call option. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shorten the package, type, and function doc comments to be direct while keeping the realm layout and error semantics. Drop redundant comments from the tests, which describe themselves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…m-checked lookups Reshape client/dockerhub around a typed sub-client, kubernetes-clientset style: client.Client gains HubAuth(opts ...dockerhub.Option) returning a dockerhub.ClientAuth with ListProfiles, GetDefaultProfile, GetDefaultSession and GetSession. The package-level helpers GetDefaultProfileAccessToken/GetUserAccessToken and the SecretsGetter interface are removed. dockerhub now imports x/secrets directly so the client package can import it without a cycle. Security hardening in the lookup paths: - profile.UserID from stored metadata is parsed as an ID (wildcards rejected) and must lie inside the configured accounts realm, so a tampered default-profile payload can no longer fan out over every readable secret or point across realms/environments. - usernames containing '/' are rejected instead of silently addressing a nested key. NumericDate fixes: plain decimals parse exactly from the string (going through float64 loses sub-second precision at epoch magnitudes), out-of-range epochs error instead of implementation-defined conversion, JSON null is a no-op, and marshalling preserves fractional seconds with correct pre-epoch sign handling. ListProfiles sweeps the metadata realm, skips the default pointer entry and dedupes by user ID; the realm doc comments in client/realms and x/realms now describe the per-account entries plus the default pointer. Tests move onto x/testhelper.MockResolver for real pattern matching. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Callers that only care about having a usable session can check errors.Is(err, ErrNoSession) alone; callers that need to distinguish a missing default pointer from a missing credential can still check ErrNoDefaultProfile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
What
Adds Docker Hub authentication to the client as a typed sub-client, kubernetes-clientset style:
client.Clientgains aHubAuth()accessor returning adockerhub.ClientAuth, so consumers no longer need to know the realm layout or the JSON payload format.Callers holding a bare
secrets.Resolvercan construct the accessor directly withdockerhub.New(engine).API surface
ClientAuth— the sub-client interface:ListProfiles,GetDefaultProfile,GetDefaultSession,GetSession(username); sentinel errorsErrNoSessionandErrNoDefaultProfile(the latter wraps the former, soerrors.Is(err, ErrNoSession)covers both "no default set" and "credential missing")UserSession{AccessToken, Claims}— decoded form of adocker/auth/hub/**envelope payloadClaims— full Hub token claims (registered JWT claims + scope, app_name, uuid, source, session_id, client_id, client_name, email, username). Parsing is dependency-free:NumericDateaccepts integer and fractional epochs,Audienceaccepts string and array forms, per RFC 7519Profile— account profile metadata (docker/auth/metadata/hub/**): user_id, username, email, sign-in date, original sign-in appParseUserSession/ParseProfile— envelope-level decoders for consumers holding raw envelopesThe
dockerhubpackage now importsx/secretsdirectly (types are aliases of the client package's), which lets the client package importdockerhubwithout a cycle.Security
The lookup paths validate stored and caller-supplied identifiers before touching the engine:
profile.UserIDread from the default-profile metadata is parsed as a strict ID (wildcards rejected) and must lie inside the configured accounts realm. A tampered or corrupt metadata payload can no longer fan out over every readable secret ("user_id": "docker/**") or point across realms/environments (a production token returned as a staging session, or a non-Hub credential returned as a session)./are rejected instead of silently addressing a nested key under the realm.NumericDaterejects out-of-range epochs instead of relying on implementation-defined float-to-int conversion, treats JSONnullas a no-op, parses plain decimals exactly from the string (round-tripping through float64 loses sub-second precision at epoch magnitudes), and preserves fractional seconds when marshalling, including correct sign handling for pre-epoch dates.Design notes
ListProfilessweeps the metadata realm, skips thedefaultpointer entry (its payload duplicates the default account's own entry), and dedupes by user ID. The realm doc comments inclient/realmsandx/realmsnow describe these semantics.Testing
exp, array and stringaud, fractional epochs, claims omitted, staging realms, error mapping) and cover the new rejection paths: wildcard and cross-realmuser_id, multi-component usernames, out-of-range epochs, and theListProfilesdefault-skip and dedupe behavior.x/testhelper.MockResolver, so lookups exercise real pattern matching instead of exact-string comparison.claimsobject with empty values — a Desktop-side fix is in flight; the parser handles both shapes, andenvelope.ExpiresAtremains the reliable expiry source meanwhile.🤖 Generated with Claude Code