Connect an AuthentikInstance
Point the operator at a real Authentik server.
Every other CRD in this operator references an AuthentikInstance by
name (spec.instanceRef, or implicitly for cluster-scoped kinds — see
the allow-list guide and the
CRD reference). AuthentikInstance
itself has no corresponding object on the Authentik side — it just
describes how to reach one.
1. Create a Secret with the API token
Generate an API token in Authentik (Admin interface → Directory → Tokens, or via a service account), then:
kubectl create secret generic authentik-api-token \
--namespace weebo-authentik \
--from-literal=token=<your-api-token>2. Create the AuthentikInstance CR
apiVersion: authentik.weebo.io/v1alpha1
kind: AuthentikInstance
metadata:
name: main
spec:
url: https://login.example.com
tokenSecretRef:
name: authentik-api-token
namespace: weebo-authentik
key: tokenAuthentikInstance is cluster-scoped, so its name (main above) is
what every AuthentikApplication's spec.instanceRef points back to.
spec.url is the web base URL, not the /api/v3 REST endpoint: the
operator appends /api/v3 itself for API calls, and uses the bare URL as the
root of the per-application OIDC issuer it writes into an oauth2
application's AUTHENTIK_URL (<url>/application/o/<slug>/). A URL that
already ends in /api/v3 is accepted and trimmed back to the web base rather
than taken literally.
TLS
The operator verifies Authentik's certificate against the platform trust
store (plus SSL_CERT_FILE/SSL_CERT_DIR, if you'd rather inject the CA
into the pod's own trust store). When Authentik is served by a private CA
— a cluster-internal PKI, cert-manager's own CA issuer — point
spec.tls.caSecretRef at a Kubernetes Secret holding that CA's PEM
bundle instead:
apiVersion: authentik.weebo.io/v1alpha1
kind: AuthentikInstance
metadata:
name: main
spec:
url: https://login.example.com
tokenSecretRef:
name: authentik-api-token
namespace: weebo-authentik
key: token
tls:
caSecretRef:
name: authentik-ca
namespace: weebo-authentik
key: ca.crt # must hold a PEM bundleThe operator reads the Secret via the API and hands the PEM to the HTTP
client it talks to this instance with — no volume mount, which matters
because the chart exposes podAnnotations and extraEnv but no
extraVolumes. The CA is added on top of the platform trust store
rather than replacing it, so one operator can serve a private-CA instance
and a publicly-signed one at once. A single certificate and a concatenated
chain are both accepted.
The Secret is re-read on every reconcile, so rotating the CA takes effect
without restarting the operator. A configured-but-unreadable caSecretRef
(missing Secret, missing key, or contents with no PEM certificate in them)
is a hard error, never a silent fall-back to the platform roots.
spec.tls.insecureSkipVerify: true turns certificate verification off
entirely. It exists for throwaway/self-signed instances; prefer
caSecretRef, which keeps verification on.
This block covers the Authentik API connection only. Vault has its own CA setting — they are routinely signed by different CAs — see Trusting a private Vault CA below.
Secret storage backend
AuthentikApplication's oauth2 credentials (AUTHENTIK_CLIENT_ID/
AUTHENTIK_CLIENT_SECRET/AUTHENTIK_URL) are written to a Kubernetes
Secret by default — one per AuthentikApplication CR, same name/
namespace. To write them to Vault instead, set spec.secretStore on
the AuthentikInstance:
apiVersion: authentik.weebo.io/v1alpha1
kind: AuthentikInstance
metadata:
name: main
spec:
url: https://login.example.com
tokenSecretRef:
name: authentik-api-token
namespace: weebo-authentik
key: token
secretStore:
backend: vault
vault:
address: https://vault.example.com:8200
mount: secret
pathPrefix: weebo-authentik # optional, this is the default
kubernetesAuthRole: weebo-authentik-operator
kubernetesAuthMount: kubernetes # optional, this is the defaultThe operator authenticates to Vault using its own ServiceAccount JWT
against Vault's Kubernetes auth backend (kubernetesAuthRole must
already be configured Vault-side to trust that ServiceAccount) — no
static Vault token to provision or rotate. Credentials land at
<mount>/data/<pathPrefix>/<namespace>/<name> (KV v2), one secret per
AuthentikApplication CR, same naming convention as the Kubernetes
backend. An empty pathPrefix (pathPrefix: "") drops the segment:
<mount>/data/<namespace>/<name>, where a Vault policy templated on the
namespace already reads.
The write is conditional: the operator reads the path first and only
issues a KV v2 set when the stored document differs from what it would
write. This matters because set always creates a new version, even for
an identical document, while the reconciler rewrites credentials on every
pass (a synced object requeues every 300s) — writing unconditionally would
churn a new Vault version per application every five minutes and roll the
KV version history over continuously. Drift is still corrected: a path
edited by hand no longer matches and is rewritten on the next pass. The
Kubernetes backend needs no equivalent, since re-applying an identical
Secret is already a no-op at the API server.
Trusting a private Vault CA
If Vault (or OpenBao) presents a TLS certificate signed by a private CA,
point secretStore.vault.caSecretRef at a Kubernetes Secret holding
the CA's PEM bundle. The operator reads it via the API and hands it to
its Vault client — no volume mount or VAULT_CACERT env wiring needed:
secretStore:
backend: vault
vault:
address: https://openbao.example.com:8200
mount: secret
kubernetesAuthRole: weebo-authentik-operator
caSecretRef:
name: openbao-tls
namespace: weebo-authentik
key: ca.crt # must hold a PEM bundleWhen caSecretRef is unset, the system trust store is used. A
configured-but-unreadable caSecretRef (missing Secret or key) is a hard
reconcile error, never a silent fall-back to the system roots. The
operator's ClusterRole already grants get on Secrets cluster-wide, so
the CA Secret may live in any namespace.
Per-application targets and fan-out
The secretStore above defines the default destination for every
application on the instance: a target named after the application, in
its namespace. An individual AuthentikApplication can override that by
listing explicit spec.secretTargets. When one or more targets are set,
the credentials are written to every one of them; Kubernetes and
Vault targets may be mixed, so a single application can populate two
Vault paths and a Kubernetes Secret at once:
apiVersion: authentik.weebo.io/v1alpha1
kind: AuthentikApplication
metadata:
name: frontend
namespace: team-a
spec:
instanceRef:
name: main
name: Frontend
slug: frontend
provider:
kind: oauth2
oauth2:
authorizationFlow: default-authorization-flow
invalidationFlow: default-invalidation-flow
secretTargets:
- backend: vault
name: frontend # default path: <pathPrefix>/team-a/frontend
- backend: vault
name: frontend-shared
path: weebo-authentik/team-a/shared/frontend # explicit KV path
- name: frontend-oauth # backend defaults to kubernetesNotes:
- Every target has a
name: theSecretname for Kubernetes, the last segment of the default<pathPrefix>/<namespace>/<name>path for Vault (whenpathis unset).backenddefaults tokubernetes. - A target stays in the application's namespace (
namespace, when set, must be the application's own), and an explicit Vaultpathmust stay under<pathPrefix>/<namespace>/— a team can't write over another team's credentials — unless the instance'spathGrantsopen it (see below). A path out of reach is refused at admission (kubectl applyfails withSecretStoreError) and again at every reconcile. - A
vaulttarget reuses the connection/auth config from the instance'ssecretStore.vault(address, mount, Kubernetes-auth role) — it only chooses where under that mount to write. The instance must therefore carry asecretStore.vaultblock for Vault targets to work, even if the instance's defaultbackendiskubernetes; otherwise the application reconcile errors. Vault is only logged in to when a target needs it. - The operator records which application owns each value (label
app.kubernetes.io/managed-by: weebo-authentikand annotationauthentik.weebo.io/owneron Secrets,ownercustom metadata in Vault) and never overwrites nor deletes a value owned by someone else: such a target fails the reconcile withSecretStoreError. Secrets written by earlier releases are adopted. secretTargetsis only meaningful forprovider.kind: oauth2— proxy providers carry no client secret, so it is ignored for them.
Writing outside the namespace subtree
When an admin declares every application in one namespace (say auth)
and each credential must land where its consumer already reads it
(argocd/auth, batlehub/auth…), the AuthentikInstance opens those
paths with secretStore.vault.pathGrants:
secretStore:
vault:
address: https://vault.example.com:8200
mount: secret
kubernetesAuthRole: weebo-authentik-operator
pathGrants:
- namespaces: [auth] # names, `*`, or a prefix glob like `team-*`
paths:
- argocd/auth
- batlehub/auth
- eclipse-che/authAn application in a covered namespace may then use those paths as
explicit paths. Patterns are compared segment by segment: a literal
segment matches itself, + exactly one segment (+/auth covers
batlehub/auth, not a/b/auth), and a last * one or more
(batlehub/*). Every pattern holds at least one literal segment, and the
CRD refuses .., empty segments and a * before the last segment. A
grant lists at most 64 paths, an instance at most 32 grants.
Before granting:
- A grant opens its paths to every subject allowed to create an
AuthentikApplicationin a covered namespace. A grant onteam-*opens them to every team. It adds to RBAC andAuthentikNamespacePolicy, it doesn't replace them. - Ownership still applies: a path already owned by another CR is
never overwritten. But a value with no owner yet (written by Terraform,
by hand) is taken over by its first writer. Prefer exact paths over
+/*wherever unmanaged values live. - Vault stays the hard bound. Grants only stop the operator from
writing what its role could write; restrict the Vault policy of
kubernetesAuthRoleto the same paths. - Removing a grant leaves the values in place, and the applications
that wrote there fail with
SecretStoreError— deleting them too — until the grant is back or theirpathchanges.
AuthentikLocalInstances have no Vault connection, hence no grants.
A namespace's own instance
An AuthentikLocalInstance is an AuthentikInstance owned by a namespace:
its token (and TLS CA) live in a Secret of that namespace, and only the
AuthentikApplications of that namespace use it, unless its sharedWith
lists other namespaces (namespaces, namespaceSelector) or accepts
cluster-scoped objects. Applications bound to it are not governed by the
AuthentikNamespacePolicy — the namespace's own token bounds what they
can do — and write their credentials as Kubernetes Secrets only (Vault
targets need an AuthentikInstance: the operator's Vault identity is
shared by every namespace).
apiVersion: v1
kind: Secret
metadata:
name: authentik-api-token
namespace: team-a
labels:
authentik.weebo.io/instance-token: "true"
stringData:
token: <API token>
---
apiVersion: authentik.weebo.io/v1alpha1
kind: AuthentikLocalInstance
metadata:
name: main
namespace: team-a
spec:
url: https://login.team-a.example.com
tokenSecretRef:
name: authentik-api-token
key: tokenThe token Secret must carry the label
authentik.weebo.io/instance-token: "true", or the instance and its
applications fail with InstanceTokenNotAllowed. The operator reads
Secrets with its own rights and sends the token to spec.url: without
that consent, anyone allowed to create an AuthentikLocalInstance could
send any Secret of the namespace to a server of their choosing. An
AuthentikInstance, created by cluster admins, needs no label.
Upgrading from a release before 0.17: label every Secret an
AuthentikLocalInstance names before upgrading
(kubectl label secret -n team-a authentik-api-token authentik.weebo.io/instance-token=true).
spec.instanceRef finds the instance by name: an operator watching the
whole cluster tries the AuthentikInstance first, then the
AuthentikLocalInstance of the application's namespace; one limited to a
namespace (watch.namespace) only uses its local instances. kind
(Instance or LocalInstance) and namespace (another namespace's
local instance) pin one. Once resolved, the instance is recorded in
status.instance and kept: a cluster AuthentikInstance created later
under the same name doesn't take the application over
(InstanceBindingChanged until kind says which).
spec:
instanceRef:
name: main
kind: LocalInstanceUpgrading from a release where instanceRef was a bare name: stop the
operator, apply the new CRDs, run weebo-authentik-operator migrate-instance-ref (--dry-run first), start the operator — and
rewrite instanceRef: main as instanceRef: {name: main} in your
manifests.
Multiple instances
Only AuthentikApplication carries an explicit instanceRef.
AuthentikGroup/AuthentikUser/AuthentikOutpost/AuthentikBrand
have no such field — the operator resolves them against the single
AuthentikInstance CR in the cluster, and errors
(AmbiguousDefaultInstance) if there's more than one. Running against
more than one real Authentik instance at a time isn't supported yet for
those kinds.