feat(cc-catalog-svc): SourceAuth/GitAuth dockerconfigjson_env mode#122
Merged
Conversation
Operators on K8s already maintain a `kubernetes.io/dockerconfigjson`
Secret for kubelet image pulls. Maintaining a parallel Opaque Secret
with the same user+password just to feed cc-catalog-svc is redundant
and a drift hazard. The destination side (`JFrogAuth.docker_config_env`)
already accepts a Docker config.json via crane's DOCKER_CONFIG; this
extends the same pattern to the source/git side which goes through
httpx and git subprocesses directly.
Schema (config.py)
------------------
- Add `dockerconfigjson_env: Optional[str]` to SourceAuth + GitAuth
- Validator: AT MOST ONE of {token_env, user_env+pass_env,
dockerconfigjson_env}. Existing "set both halves of basic together"
rule preserved.
Implementation
--------------
- New module `app/auth_dockerconfigjson.py` with two helpers:
* `resolve_basic_pair(file_path, target_host)` — direct file lookup
* `resolve_basic_pair_from_env(env_var_name, ...)` — env-var indirection
Handles both `auth` (base64 user:password) and explicit
`username`/`password` encodings, with `auth` winning (Docker CLI
behavior). Exact-match host lookup only; K8s dockerconfigjson
Secrets are typically single-host so wildcard matching is YAGNI.
- `OCISource._resolve_auth_header` derives the target host from
`cc["image_registry"]` via the existing `_split_registry_url`
helper, then delegates to the lookup. Mode returned is "basic" so
the bearer-realm dance behavior is identical to explicit Basic creds.
- `_git_auth_args` extended with optional `upstream_url` kwarg;
callers in `sync_one_repo` forward the URL for clone + fetch.
Local-only operations (`git rev-parse`) omit the URL safely.
Soft-fallback policy
--------------------
Every "can't resolve" path falls back to anonymous instead of raising:
- env var unset/empty → anonymous (warns at DEBUG)
- file missing / unparseable JSON → anonymous (warns at WARNING)
- host not in `auths` map → anonymous (DEBUG)
- malformed `auth` field → anonymous (WARNING)
- missing/unparseable image_registry → anonymous (WARNING)
This matches the existing `token_env`-empty / `user_env`-empty
behavior: partial misconfig against public sources keeps working
instead of hard-failing the whole poll.
Tests
-----
- `TestDockerconfigjsonResolveBasicPair` (13 cases): both encodings,
base64-wins-over-username/password, missing host, missing file,
empty env var name, malformed JSON, malformed base64, auth
without colon, empty user/password, env-var indirection wrapper.
- `TestDockerconfigjsonValidators` (5 cases): SourceAuth + GitAuth
mutual-exclusivity with token/basic modes.
- `TestOCISourceDockerconfigjsonAuth` (6 cases): _resolve_auth_header
happy path, host miss, env unset, missing image_registry, malformed
image_registry, end-to-end via respx.
- `TestGitAuthArgsDockerconfigjson` (5 cases): _git_auth_args happy
path, unknown host, missing upstream_url, regression guards for
token_env and user_env+pass_env paths when upstream_url is supplied.
- Updated two existing test stubs (`fake_run` lambdas in
`test_git_mirror.py`) to accept the new `upstream_url` kwarg, and
one assertion message that switched from "EITHER..." to "AT MOST ONE".
Verification:
pytest -q → 140 passed (was 137 before, +13 new auth cases minus
duplicates absorbed by existing classes)
ruff check app/ tests/ → All checks passed
black . → formatted
Example config
--------------
`config-examples/config.jfrog-remote-dockerconfigjson.yaml` — mirror
of the existing JFrog Remote example with auth swapped to
`dockerconfigjson_env`, plus inline deployment-manifest comments
showing the K8s Secret mount.
Co-authored-by: Cursor <cursoragent@cursor.com>
cc-catalog-svc imageTag:
|
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.
Why
Operators running cc-catalog-svc on Kubernetes already maintain a
kubernetes.io/dockerconfigjsonSecret for kubelet image pulls against their internal mirror (JFrog Remote, Harbor proxy, etc.). Today the catalog needs a second Opaque Secret with the same credentials inJCR_USER/JCR_PASSkeys just soos.environ.get()can find them. Two Secrets carrying identical credentials is a drift hazard and avoidable operator friction.The destination side already solved this via
JFrogAuth.docker_config_env(passed tocranethroughDOCKER_CONFIG). The source side and git-mirror side never picked up the pattern because they go throughhttpxandgitsubprocesses directly.What
New
dockerconfigjson_env: Optional[str]field onSourceAuthandGitAuth. Names an env var whose value is the path to a Dockerconfig.jsonfile. Catalog opens the file, looks up the source'simage_registryhost (or the gitupstream_urlhost) in the file'sauthsmap, and uses HTTP Basic on the resolved credentials.Deployment side: mount your existing
kubernetes.io/dockerconfigjsonSecret as a file and point the env var at it. No second Secret needed.Scope
app/config.py— schema + validator (AT MOST ONE of three modes)app/auth_dockerconfigjson.py— new shared helper (handles bothauthbase64 andusername/passwordencodings)app/sources/oci.py—_resolve_auth_headerdelegates to the helper when the new field is set; mode is"basic"so the existing bearer-realm dance behavior is unchangedapp/services/git_mirror.py—_git_auth_argsextended with optionalupstream_urlkwarg; callers insync_one_repoforward the URL for clone + fetch; local-only ops (git rev-parse) omit it safelytests/test_auth.py— +29 cases across 4 new classes covering happy path, all soft-fallback edges, schema validators, and end-to-end OCI behavior via respxtests/test_git_mirror.py— two existingfake_runstubs updated to accept the newupstream_urlkwarg, plus one assertion-message tweakconfig-examples/config.jfrog-remote-dockerconfigjson.yaml— worked example with inline K8s Secret-mount commentsSoft-fallback policy
Every "can't resolve" path returns to anonymous instead of raising, matching the existing behavior when
token_envis set but the env var is empty:The catalog stays useful when a Secret covers some hosts but not others (a public ghcr source coexisting with a private JFrog source, etc.).
Test plan
pytest -q— 140 passed (was 137; +13 new cases net of those absorbed)ruff check app/ tests/— cleanblack .— appliedkubernetes.io/dockerconfigjsonSecret (no parallel Opaque Secret)Companion chart change
runwhen/rwlight-helmis getting a parallel chart knob (ccCatalog.auth.dockerconfigjsonSecret) that mounts an existing pull-secret at a deterministic path and sets the env var. That chart PR will land after this is reviewed + tagged.Made with Cursor