ServiceAccount annotations
Flux controllers read authentication and mutual TLS settings from annotations
on Kubernetes ServiceAccount objects. The annotations are placed on the
ServiceAccount selected by the Flux object, for example .spec.serviceAccountName,
.spec.decryption.serviceAccountName, or .data.serviceAccountName in a
.spec.kubeConfig.configMapRef ConfigMap. Depending on the feature, this can
be an object-level ServiceAccount or the controller’s own ServiceAccount.
The credential.auth.fluxcd.io/provider annotation selects how Flux acquires
the credential (kubernetes or spiffe), while the service provider selected
by the Flux object’s .spec.provider field, or by .data.provider in a
.spec.kubeConfig.configMapRef ConfigMap, determines what the credential is
used for (aws, azure, gcp, dockerhub, or generic). The two are
independent: for example, an OCIRepository with .spec.provider: aws can use
either credential provider. For APIs without a provider field, such as SOPS
decryption, the service provider is inferred from the encrypted data.
Note: The
x509value forcredential.auth.fluxcd.io/type, thespiffe.mtls.fluxcd.io/server-idsannotation, and thedockerhubservice provider will be implemented in Flux 2.11 (Q1 2027). They are documented here to describe the API surface they will enable.
Controller-level setup
Before using the features defined by the ServiceAccount annotations, ensure that the controller is properly configured. The next sections describe the setup required for each feature.
Kubernetes TokenRequest API
The kubernetes credential provider requires the controller to have access to the
Kubernetes TokenRequest API to acquire JWTs for the ServiceAccount specified by
the Flux object. The required permission is the create verb on the
serviceaccounts/token resource granted by a ClusterRoleBinding. Example:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flux-object-level-workload-identity
rules:
- apiGroups:
- ""
resources:
- serviceaccounts/token
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flux-object-level-workload-identity
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flux-object-level-workload-identity
subjects:
- kind: ServiceAccount
name: source-controller
namespace: flux-system
SPIFFE
The spiffe credential provider and the spiffe.mtls.fluxcd.io annotations will
require the controller to be configured with the SPIFFE Workload API endpoint via
the environment variable SPIFFE_ENDPOINT_SOCKET, the SPIFFE Broker API endpoint
via the flag --spiffe-broker-endpoint, and at least one authorized Broker SPIFFE
ID via the repeatable flag
--spiffe-broker-id. The Workload
API provides the controller’s X509-SVID used to authenticate to the Broker API.
The SPIFFE Workload API socket can be mounted in the Flux controllers using the
SPIFFE CSI Driver. The controller patch
should look like this (make sure to replace the <...> placeholders with your
actual values):
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gotk-components.yaml
- gotk-sync.yaml
patches:
- target:
kind: Deployment
name: "(source-controller|kustomize-controller|helm-controller|notification-controller|image-reflector-controller|image-automation-controller)"
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --spiffe-broker-endpoint=dns:///<svc name>.<svc namespace>.svc.cluster.local:443
- op: add
path: /spec/template/spec/containers/0/args/-
value: --spiffe-broker-id=spiffe://<trust domain>/<id path>
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: SPIFFE_ENDPOINT_SOCKET
value: unix:///spiffe-workload-api/<socket path>
- op: add
path: /spec/template/spec/volumes/-
value:
name: spiffe-workload-api
csi:
driver: csi.spiffe.io
readOnly: true
- op: add
path: /spec/template/spec/containers/0/volumeMounts/-
value:
name: spiffe-workload-api
mountPath: /spiffe-workload-api
readOnly: true
Authentication annotations
The following sections describe the annotations used for authentication.
Credential annotations
The credential.auth.fluxcd.io annotations configure how Flux acquires a
credential for the object. The provider and type annotations must always be
used together. When any *.auth.fluxcd.io annotation is present, the provider
and type annotations become mandatory as well.
credential.auth.fluxcd.io/provider
The credential provider that issues the credential.
kubernetes: Flux acquires a JWT using the Kubernetes TokenRequest API. The identity associated with the credential is the Kubernetes ServiceAccount username,system:serviceaccount:<namespace>:<name>.spiffe: Flux acquires an SVID from the SPIFFE Broker API. The identity associated with the credential is a SPIFFE ID. The SPIFFE runtime, not Flux, decides which SPIFFE ID is issued for the Flux object.
The kubernetes provider supports only the jwt credential type.
credential.auth.fluxcd.io/type
The type of credential to acquire.
jwt: Acquire a JSON Web Token.x509: Acquire an X.509 certificate. This value will be implemented in Flux 2.11 (Q1 2027). It will be supported by theawsandgcpservice providers only, and essentially enables AWS IAM Roles Anywhere and GCP Workload Identity Federation with X.509 certificates.
The kubernetes credential provider and the azure service provider support
only the jwt credential type.
credential.auth.fluxcd.io/expiration
An optional Go duration string specifying the requested expiration time of the
credential, for example 5m or 1h. It is supported only by the kubernetes
credential provider and controls the lifetime of the initial credential before
any exchange performed by the service provider. If omitted, the Kubernetes API
server default is used.
credential.auth.fluxcd.io/audiences
An optional list of audiences for the jwt credential type. The value is split
on commas and line breaks, whitespace is trimmed, and empty entries are ignored.
This annotation is rejected for the dockerhub service provider, which only
supports the fixed audience https://identity.docker.com, and for the GKE
built-in workload identity provider, which does not support custom audiences.
Set gcp.auth.fluxcd.io/workload-identity-provider when a custom audience is
required.
When omitted, the service provider determines the default audience. Cloud
providers have provider-specific defaults, while the generic provider uses
the address of the target service, such as an OCI registry host or a remote
Kubernetes API server.
credential.auth.fluxcd.io/username
An optional username to use when authenticating to the service. It is valid
only for the OCI APIs (OCIRepository and ImageRepository) with the
generic and dockerhub service providers. It is optional for generic and
mandatory for dockerhub.
For the generic service provider, the username controls how the credential is
presented to the registry:
- When
usernameis set, Flux uses HTTP Basic authentication, with this value as the username and the acquired JWT or JWT-SVID as the password. - When
usernameis omitted, Flux sends the credential as a Bearer token in theAuthorizationheader.
Docker Hub requires a username for its OIDC connection exchange and only
accepts the audience https://identity.docker.com.
Transit engine annotations
The transit-engine.auth.fluxcd.io annotations configure authentication to an
OpenBao or Vault transit engine for SOPS decryption. They are read from the
ServiceAccount referenced by .spec.decryption.serviceAccountName and must be
used together. As with the other Flux-defined authentication annotations, using
them also requires credential.auth.fluxcd.io/provider and
credential.auth.fluxcd.io/type on the same ServiceAccount.
transit-engine.auth.fluxcd.io/login-path
The HTTP path in the transit engine where the login request must be sent, for
example auth/jwt/login.
transit-engine.auth.fluxcd.io/role
The transit engine role to use when authenticating. The role defines the rules for matching credentials and the permissions granted to the authenticated entity.
Service provider annotations
The {provider}.auth.fluxcd.io annotations configure provider-specific
settings.
aws.auth.fluxcd.io/sts-endpoint
An optional AWS STS endpoint for the ServiceAccount. This annotation makes the
STS endpoint configurable for every AWS integration. It cannot be combined with
the legacy Bucket.spec.sts.provider: aws configuration; when opting into
object-level workload identity, set the STS endpoint with this annotation
instead of Bucket.spec.sts.endpoint.
gcp.auth.fluxcd.io/workload-identity-provider
The fully qualified GCP Workload Identity Provider resource name, for example:
projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
It is required outside GKE when using the gcp service provider, and for any
GCP integration using the spiffe credential provider. On GKE, the built-in
workload identity provider is used when this annotation is omitted.
dockerhub.auth.fluxcd.io/oidc-connection-id
The Docker Hub OIDC connection ID. This annotation is required for the
dockerhub service provider. Docker Hub only accepts the audience
https://identity.docker.com, so credential.auth.fluxcd.io/audiences cannot
be used with it.
Note: The
dockerhubservice provider will be implemented in Flux 2.11 (Q1 2027).
External cloud provider annotations
Flux also supports the ServiceAccount annotations defined by the managed Kubernetes providers. These annotations are not Flux-specific, but Flux reads them for a consistent workload identity experience across managed and self-hosted clusters. They can be used on an object-level ServiceAccount or on the controller ServiceAccount for controller-level workload identity.
eks.amazonaws.com/role-arn
The ARN of the AWS IAM Role that the ServiceAccount assumes. This annotation is
required when using the aws service provider with object-level workload
identity.
azure.workload.identity/tenant-id
The Microsoft Entra tenant ID. This annotation is required when using the
azure service provider with object-level workload identity.
azure.workload.identity/client-id
The Microsoft Entra client ID of the managed identity. This annotation is
required when using the azure service provider with object-level workload
identity.
iam.gke.io/gcp-service-account
The email address of the GCP Service Account to impersonate. It is optional. When omitted, the subject under the GCP Workload Identity Pool is the IAM Principal to which GCP IAM Roles should be granted. See the GCP docs for more details.
Mutual TLS annotations
The following sections describe the annotations used for mutual TLS (mTLS).
SPIFFE mTLS annotations
The spiffe.mtls.fluxcd.io annotations specify the configuration for
mTLS provided by X509-SVIDs when Flux connects to an external service
described in a Flux objects.
spiffe.mtls.fluxcd.io/server-ids
An optional list of server SPIFFE IDs to authorize when connecting to the service associated with the Flux object. This opts the connection into mTLS backed by SPIFFE. The value is split on commas and line breaks, whitespace is trimmed, and empty entries are ignored.
The annotation will be implemented in Flux 2.11 (Q1 2027). It will enable
SPIFFE mTLS for self-hosted infrastructure, including the generic service provider
in most Flux CRDs and GitHub Enterprise Server through the github provider in
GitRepository and the GitHub provider types in the notification-controller
Provider CRD. The public aws, azure, gcp, dockerhub, and GitHub.com
services do not support it.
Each entry uses the SPIFFE ID format.
The mTLS annotations are independent of credential acquisition. On their own,
they do not require credential.auth.fluxcd.io/provider or
credential.auth.fluxcd.io/type. If any authentication annotation is present
alongside them, however, the credential provider and type must also be set.
The annotation is rejected when .spec.certSecretRef is set, or when
certificates or CAs come from .spec.secretRef.
SPIFFE ID format
The --spiffe-broker-id controller flag and the
spiffe.mtls.fluxcd.io/server-ids annotation accept the same SPIFFE ID format.
The flag can be repeated, while the annotation value is split on commas and
line breaks.
Each value can be one of the following:
spiffe://<trust domain>to authorize any SPIFFE ID in the trust domain.spiffe://<trust domain>/<path>to authorize an exact SPIFFE ID.spiffe://<trust domain>/<path>/to authorize a SPIFFE ID prefix. The trailing slash identifies a prefix.spiffe://self,spiffe://self/<path>, orspiffe://self/<path>/to use theselftrust domain, defined below.
For --spiffe-broker-id, self refers to the trust domain of the controller’s
own X509-SVID. For spiffe.mtls.fluxcd.io/server-ids, self refers to the
trust domain of the Flux object’s X509-SVID, acquired through the SPIFFE Broker
API.
Migration notes
The annotation-based API cannot be combined with the following legacy configuration:
- A
kubeConfigConfigMap with anaudienceskey. Usecredential.auth.fluxcd.io/audiencesinstead. - The
--sops-vault-configmapcontroller flag. Usetransit-engine.auth.fluxcd.io/login-pathandtransit-engine.auth.fluxcd.io/roleinstead. - A
Bucketwith.spec.sts.provider: aws. Useaws.auth.fluxcd.io/sts-endpointinstead.
Examples
Object-level Kubernetes JWT for an OCI registry:
apiVersion: v1
kind: ServiceAccount
metadata:
name: tenant-a
namespace: apps
annotations:
credential.auth.fluxcd.io/provider: kubernetes
credential.auth.fluxcd.io/type: jwt
credential.auth.fluxcd.io/audiences: registry.example.com
SPIFFE JWT-SVID with SOPS authentication to an OpenBao transit engine:
apiVersion: v1
kind: ServiceAccount
metadata:
name: sops-decrypter
namespace: apps
annotations:
credential.auth.fluxcd.io/provider: spiffe
credential.auth.fluxcd.io/type: jwt
credential.auth.fluxcd.io/audiences: https://openbao.example.com
transit-engine.auth.fluxcd.io/login-path: auth/jwt/login
transit-engine.auth.fluxcd.io/role: sops-spiffe
SPIFFE JWT for a remote Kubernetes cluster:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: remote-apps
namespace: apps
spec:
# ... other fields omitted for brevity
kubeConfig:
configMapRef:
name: remote-cluster
---
apiVersion: v1
kind: ConfigMap
metadata:
name: remote-cluster
namespace: apps
data:
provider: generic
serviceAccountName: remote-cluster-auth
address: https://remote-cluster.example.com:6443
ca.crt: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: remote-cluster-auth
namespace: apps
annotations:
credential.auth.fluxcd.io/provider: spiffe
credential.auth.fluxcd.io/type: jwt
credential.auth.fluxcd.io/audiences: https://remote-cluster.example.com:6443
SPIFFE X.509 credentials for AWS IAM Roles Anywhere and GCP Workload Identity Federation (planned for Flux 2.11):
apiVersion: v1
kind: ServiceAccount
metadata:
name: aws-tenant
namespace: apps
annotations:
credential.auth.fluxcd.io/provider: spiffe
credential.auth.fluxcd.io/type: x509
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/tenant-a
apiVersion: v1
kind: ServiceAccount
metadata:
name: gcp-tenant
namespace: apps
annotations:
credential.auth.fluxcd.io/provider: spiffe
credential.auth.fluxcd.io/type: x509
gcp.auth.fluxcd.io/workload-identity-provider: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
Docker Hub OIDC connection (planned for Flux 2.11):
apiVersion: v1
kind: ServiceAccount
metadata:
name: dockerhub-tenant
namespace: apps
annotations:
credential.auth.fluxcd.io/provider: spiffe
credential.auth.fluxcd.io/type: jwt
credential.auth.fluxcd.io/username: tenant-a
dockerhub.auth.fluxcd.io/oidc-connection-id: OIDC_CONNECTION_ID
SPIFFE mTLS to a self-hosted service (planned for Flux 2.11):
apiVersion: v1
kind: ServiceAccount
metadata:
name: tenant-a
namespace: apps
annotations:
spiffe.mtls.fluxcd.io/server-ids: spiffe://example.org/some-service