Give zot a push credential, and keep every read open #70
No reviewers
Labels
No labels
blocked
bug
duplicate
effort
high
effort
low
effort
medium
effort
minimal
enhancement
help wanted
invalid
pr
feedback
pr
reviewing
question
status
backlog
status
document
status
done
status
implement
status
pending
status
planning
status
reviewing
waiting
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
tepichord/milner!70
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/zot-htpasswd-auth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Description
The registry had no
http.authat all, so every push was anonymous — includinga pull request's own pipeline, which runs
privileged. That matters herebecause the cluster pulls
milner/woodpecker-secret-extension:0.2.4unpinned,with the extension's AppRole behind it: an anonymous push endpoint is a path to
replacing an image a pod then runs.
zot reads htpasswd from a file and cannot be handed a password, while a
pipeline cannot log in with a hash — so there is one password in two
representations, and they have to be written together or they drift into a 401
nobody can explain later:
secret/ci/zot→ZOT_TOKENsecret/zot/htpasswd→HTPASSWDVaultStaticSecretzot/zot-htpasswdset-credential.shis the one operation that writes both: it derives the hashfrom the cleartext it read or minted, so there is no second rotation path. The
hash reaches the pod as a Secret the chart mounts whole at
/secret, which isthe only way to get it there without putting it in git (
mountSecret: truewould render it from
secretFiles.htpasswd).What the access policy says
Reads stay anonymous. That is not a detail of the config but the shape of it:
zot consults
defaultPolicyonly for a named caller andanonymousPolicyonly for an anonymous one (
isPermitted,pkg/api/authz.go), and every pullerof this registry is anonymous — the secret extension,
oz, the preview router,and the kubelet. So
**is read-only for anonymous callers, and everydefaultPolicymirrors itsanonymousPolicy, because a logged-in client is notan anonymous one: without the mirror,
podman loginfollowed by a pull ofmilner/…would turn a working pull into a 401.Two repositories keep anonymous write, and they are different cases:
preview/**is permanent. A per-PR push cannot be given a credential at all —the pipeline runs the PR's own workflow file and the extension refuses every
pull event — and those images are what the l3 preview runs.
milner/**is meant to be deleted. The main halves ofbuild-ozandbuild-routerpushmilner/ozandmilner/routerunattended, and cannot begiven a credential yet: the extension's AppRole is granted
secret/forgejo/tokenand no other path. Once theci/grant and a rule forthis repository exist, those steps take
--credsand this entry loses its twopolicies — in that order, because the credential has to be in place before the
exception can go.
annia-ciis the only user in the password file, so the credential is scoped bywho holds it rather than by a prefix allowlist that every new publisher would
have to remember to extend.
Order
zot reads the file at start-up and exits 1 without it (
Error: open /secret/HTPASSWD: no such file or directory). The chart rolls the pod when theconfig changes and the secrets layer is applied after infrastructure, so a pod
that restarts into this config retries until VSO has synced the Secret — a
window with an end because the Vault side (both entries, plus the
zot-readpolicy and
zotrole from §4c-ter) was done first, and a CrashLoopBackOff onthe registry every pull goes through when it was not.
Changes
infrastructure/zot/set-credential.sh—provision(default),--rotate,verify. No value in argv, nothing echoed, the root-only half printed for anoperator session unless
ROOT_VAULT_TOKENis set.infrastructure/zot/values.yaml—http.auth.htpasswd+ the access-controltree, with the two traps (auth alone authorizes nothing; the two policies are
not alternatives) written down where they bite.
secrets/vaultauth-zot.yaml,secrets/zot-htpasswd.yaml— the namespace'sVaultAuth and the Secret the chart mounts. No
rolloutRestartTargets: zotwatches the file and its parent directory, so a rotation is picked up in
place.
OPERATIONS.md— thezot-readpolicy andzotrole in §4c-ter, and apodman loginin §9k step 3's hand-run push.infrastructure/zot/README.md— the policy table, the credential, theordering, and the evidence table.
tests/test_gitops.py— anonymous reads stay,**stays read-only foranonymous callers, and the file the config names is the one VSO writes.
Evidence
zot verifyaccepts this exactconfig.json; on a throwaway v2.1.21 with it andthe hash Vault holds: anonymous
GET /v2/200,GET /v2/_catalog200,POST /v2/milner/…202,POST /v2/preview/…202,POST /v2/annia/…401;annia-ciGET /v2/_catalog200 andPOST202 onannia,milner,library; a wrong password 401. The log saysloaded htpasswd file users: 1and
access control is being enabled.pytest tests/ -q: 206 passed.47edfd48ab881f121c95881f121c95c29e777622