Skip to content

Security

Authentication model, multi-tenancy authorization, encryption, source verification, and hardening for FluxCD.

FluxCD delegates security almost entirely to Kubernetes-native mechanisms. Unlike ArgoCD, which ships its own Casbin RBAC engine and Dex SSO server, FluxCD has no built-in identity provider or authorization system. Security is enforced through Kubernetes RBAC, ServiceAccount impersonation, and controller-level flags.

Authentication

Controller-to-Cluster Authentication

Each Flux controller runs under a ServiceAccount in the flux-system namespace. The controller uses that ServiceAccount's credentials to communicate with the Kubernetes API server. There is no separate authentication layer.

  • source-controller: Authenticates to Git repositories via SSH keys, HTTPS tokens, or basic auth credentials stored as Kubernetes Secrets
  • kustomize-controller / helm-controller: Impersonates the ServiceAccount specified in .spec.serviceAccountName on each Kustomization or HelmRelease resource, inheriting that account's RBAC permissions
  • notification-controller: Uses its own ServiceAccount to post events and read Alert/Provider CRDs

Git Repository Authentication

FluxCD supports multiple authentication methods for private Git repositories:

Method Secret Fields Use Case
SSH (ed25519/rsa) identity (private key), known_hosts Most common for GitHub/GitLab private repos
HTTPS with token username, password (personal access token) Azure DevOps, GitHub App tokens
Basic auth username, password Self-hosted Git servers
OIDC (GitHub App) clientID, clientSecret, idToken Short-lived tokens from GitHub OIDC
# SSH authentication for a private GitRepository
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: private-repo
  namespace: flux-system
spec:
  interval: 5m
  url: ssh://[email protected]/org/private-repo.git
  ref:
    branch: main
  secretRef:
    name: ssh-credentials

Authorization (Multi-Tenancy and RBAC)

FluxCD implements multi-tenancy through Kubernetes RBAC and controller-level isolation flags. There is no separate RBAC engine.

ServiceAccount Impersonation

The kustomize-controller and helm-controller impersonate the ServiceAccount specified in each resource's .spec.serviceAccountName. This means:

  • Each tenant team creates a dedicated ServiceAccount in their namespace
  • The controller inherits only the permissions granted to that ServiceAccount via Role and RoleBinding
  • If no serviceAccountName is specified, the controller uses the --default-service-account flag value (set to default in multi-tenant setups)

Multi-Tenancy Lockdown

To enforce hard multi-tenancy isolation, apply these controller flags during bootstrap:

Flag Controllers Effect
--no-cross-namespace-refs=true kustomize-controller, helm-controller, notification-controller, image-reflector-controller, image-automation-controller Prevents tenants from referencing Flux resources (sources, secrets) in other namespaces
--no-remote-bases=true kustomize-controller Prevents Kustomize from fetching remote bases from arbitrary URLs
--default-service-account=default kustomize-controller, helm-controller Falls back to the default ServiceAccount in the tenant namespace when no serviceAccountName is specified
# Kustomize patches applied during flux bootstrap
patches:
  - patch: |
      - op: add
        path: /spec/template/spec/containers/0/args/-
        value: --no-cross-namespace-refs=true
    target:
      kind: Deployment
      name: "(kustomize-controller|helm-controller|notification-controller)"
  - patch: |
      - op: add
        path: /spec/template/spec/containers/0/args/-
        value: --no-remote-bases=true
    target:
      kind: Deployment
      name: "kustomize-controller"
  - patch: |
      - op: add
        path: /spec/template/spec/containers/0/args/-
        value: --default-service-account=default
    target:
      kind: Deployment
      name: "(kustomize-controller|helm-controller)"

Tenant Isolation Model

graph TB
    subgraph Flux["flux-system namespace"]
        SC["source-controller"]
        KC["kustomize-controller"]
    end

    subgraph TenantA["team-a namespace"]
        SA_A["ServiceAccount: team-a"]
        RB_A["RoleBinding"]
        GitRepoA["GitRepository"]
        KustA["Kustomization"]
    end

    subgraph TenantB["team-b namespace"]
        SA_B["ServiceAccount: team-b"]
        RB_B["RoleBinding"]
        GitRepoB["GitRepository"]
        KustB["Kustomization"]
    end

    GitRepoA --> SC
    GitRepoB --> SC
    KustA -->|"impersonates SA: team-a"| KC
    KustB -->|"impersonates SA: team-b"| KC
    RB_A -.->|"grants limited permissions"| SA_A
    RB_B -.->|"grants limited permissions"| SA_B

With --no-cross-namespace-refs=true, team-a's Kustomization cannot reference team-b's GitRepository, and vice versa.

Encryption and Secret Management

SOPS Integration

FluxCD natively supports Mozilla SOPS (Secrets OPerationS) for encrypting secrets in Git. The kustomize-controller decrypts SOPS-encrypted files at reconciliation time.

Supported encryption backends:

Backend Configuration Use Case
Age age.agekey in a K8s Secret Recommended; simple, modern encryption
PGP/GPG pgp in .sops.yaml Legacy; broader key management ecosystem
AWS KMS kms in .sops.yaml AWS-native deployments
GCP KMS gcp_kms in .sops.yaml GCP-native deployments
Azure Key Vault azure_kv in .sops.yaml Azure-native deployments

The decryption key is stored as a Kubernetes Secret in the controller's namespace:

apiVersion: v1
kind: Secret
metadata:
  name: sops-age
  namespace: flux-system
stringData:
  age.agekey: |
    AGE-SECRET-KEY-1...

Configure the kustomize-controller to use the decryption key via Kustomize patches:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
  - patch: |
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: kustomize-controller
      spec:
        template:
          spec:
            containers:
              - name: manager
                env:
                  - name: SOPS_AGE_KEY_FILE
                    value: /sops-age/age.agekey
                volumeMounts:
                  - name: sops-age
                    mountPath: /sops-age
            volumes:
              - name: sops-age
                secret:
                  secretName: sops-age

Key Management

The SOPS decryption private key must be stored as a Kubernetes Secret in the cluster. Protect this Secret with RBAC (only kustomize-controller should have read access) and consider using an external secret management system to inject it.

Source Verification

FluxCD can verify the authenticity and integrity of source artifacts before reconciling them.

GPG Commit Signing Verification

For GitRepository resources, FluxCD can verify that commits are signed by a trusted GPG key:

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: verified-repo
  namespace: flux-system
spec:
  interval: 5m
  url: https://github.com/org/secure-repo
  ref:
    branch: main
  verify:
    mode: HEAD
    secretRef:
      name: gpg-public-keys

Cosign OCI Artifact Verification

For OCIRepository resources, FluxCD can verify signatures using Sigstore Cosign:

apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  name: verified-oci
  namespace: flux-system
spec:
  interval: 5m
  url: oci://ghcr.io/org/manifests
  ref:
    semver: ">=1.0.0"
  verify:
    provider: cosign
    secretRef:
      name: cosign-pub

Container Image Verification

All Flux controller images are signed using Cosign with GitHub OIDC (keyless signing). Verify at deployment time:

cosign verify ghcr.io/fluxcd/source-controller:v1.0.0 \
  --certificate-identity-regexp='^https://github\.com/fluxcd/.*$' \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com

Enforce verification at the cluster level using a Kyverno policy that rejects unsigned Flux images.

Hardening Checklist

  • Enable --no-cross-namespace-refs=true on all controllers for multi-tenancy
  • Enable --no-remote-bases=true on kustomize-controller
  • Set --default-service-account=default on kustomize-controller and helm-controller
  • Use SOPS or External Secrets Operator for secrets management (never commit plaintext)
  • Enable GPG or Cosign source verification on all GitRepository and OCIRepository resources
  • Store SOPS decryption keys in K8s Secrets with strict RBAC (only controller SA has read)
  • Apply NetworkPolicies to restrict controller egress (source-controller to Git hosts only)
  • Run controllers with SecurityContext (non-root, read-only root filesystem, drop all capabilities)
  • Verify Flux controller image signatures with Cosign or Kyverno admission policies
  • Use short-lived SSH keys or OIDC tokens for Git repository authentication
  • Keep flux-system namespace restricted; tenants should not have access to it