Injected pull secrets
This documents the in-development version. Switch to v2.3 for the latest stable release.
When the webhook rewrites a container to a registry that needs a credential, the kubelet needs
that credential to pull. An auth whose injectPullSecret is on
makes kuik provide it: the webhook adds kuik-inject-<kind>-<name> to the pod’s
imagePullSecrets, and the secret-syncer process writes that Secret in the pod’s namespace.
One Secret exists per routing resource and namespace. It is a
kubernetes.io/dockerconfigjson Secret labelled app.kubernetes.io/managed-by: kuik-secret-syncer and owned by the resource, so deleting the resource deletes its Secrets in
every namespace.
Where a Secret lands
Section titled “Where a Secret lands”rewritePolicy | The Secret is written | It holds |
|---|---|---|
Always | in every namespace the namespaceSelector selects, before any pod exists | every entry of the resource whose auth injects |
OnFailure | in a namespace once a pod there has been rewritten by the resource | the entries that served the rewrites of its live pods |
Under Always, podSelector does not narrow where the Secret lands: an absent or empty
namespaceSelector puts it in every namespace of the cluster. Set a namespaceSelector to
bound it. A resource none of whose entries injects writes no Secret at all.
Under OnFailure, the first rewritten pod of a namespace may start before its Secret exists.
The kubelet retries the pull and reads the Secret again on each attempt, so the pod starts a few
seconds later; the next pods find the Secret in place.
What a Secret holds
Section titled “What a Secret holds”Each entry gets one auths key, its registry path: the repository or repositoryGroup value
as written, or an ImageMirror’s destination.path without its trailing slash. Two entries of
one host, quay.io/acme and quay.io/other, keep their own credentials: the kubelet picks the
most specific key matching the image.
The value is the credential of the secretRef Secret that the kubelet would use for that path,
the most specific of its own auths keys. A secretRef that is missing, is not a
docker-registry Secret, or holds no credential for the path is left out, and the other entries
are still written. kuik reports it with a PullSecretInjectionFailed
event on the resource.
provider credentials are not injected yet: an entry with a provider is left out, whatever
its injectPullSecret.
Keeping it valid
Section titled “Keeping it valid”The syncer writes with a server-side apply and never reads the Secrets it wrote: it cannot read any Secret outside the cluster resource namespace. It writes again when:
- a
secretRefSecret is created or changes in the cluster resource namespace; - the resource changes, or a namespace enters or leaves its scope;
- the pods of a namespace change what an
OnFailureresource needs; - the informers resync, which restores a Secret someone else deleted.
A write that changes nothing is a no-op on the API server.
What the syncer may write
Section titled “What the syncer may write”The syncer may create and patch Secrets in every namespace, which RBAC cannot narrow further. A
ValidatingAdmissionPolicy installed by the chart bounds it at the API server. A Secret the
syncer creates or updates must:
- be of type
kubernetes.io/dockerconfigjson; - carry the label
app.kubernetes.io/managed-by: kuik-secret-syncer; - be named
kuik-inject-*in any namespace, orkuik-provider-*in the namespace kuik is installed in.
The two prefixes are reserved both ways: any other identity is refused the creation or update
of a Secret named kuik-inject-* or kuik-provider-*, in every namespace. Two updates are let
through, both leaving the type, the data, the labels and the immutability unchanged: one that
also leaves the owner references unchanged, such as a kubectl replace of every Secret to
rotate the encryption at rest, and the garbage collector removing an owner reference. Deletions
are not checked, so deleting a resource still deletes its Secrets. The policy fails closed: when
the API server cannot evaluate it, the write is refused. Since the policy sees every Secret
write in the cluster, a failure to evaluate it would refuse all of them, Helm releases
included: its match conditions limit it to the writes of the syncer and to the reserved names,
and every other Secret write skips it.
A backup tool that recreates Secrets on restore, such as Velero, is refused these names. It is
harmless, since the syncer writes them again, but the refusals show in the restore log, and
Velero marks the restore PartiallyFailed, which fires any alert on that status: exclude these
Secrets from the backup to avoid them.
When a credential is no longer needed
Section titled “When a credential is no longer needed”Under OnFailure, an entry that no live pod uses any more stays 15 minutes before it is
removed: the pods of a rollout or a scale-up during an outage come back and find it. The Secret
itself is never deleted by the syncer. With no entry left, it holds {"auths":{}}. A namespace
that leaves the scope of an Always resource gets the same empty Secret.
The syncer keeps which Secrets it wrote in memory. After a restart, it does not know a Secret nothing needs any more, so such a Secret keeps its last content:
- under
OnFailure, when the pods of its namespace all went away while the syncer was stopped, until a new rewrite lands in that namespace; - under
Always, when its namespace left the scope while the syncer was stopped, until the namespace comes back into scope.
Deleting the resource deletes those Secrets in both cases.