Flux Web UI SSO with Dex
Flux Operator supports Single Sign-On (SSO) for the Web UI using Dex as the identity provider. Dex is an open-source OIDC provider that can federate multiple identity sources such as GitHub, GitLab, Google, Microsoft, OpenShift and many others. The complete list of supported connectors can be found in the Dex documentation.
Client ID and Secret
First, create a Kubernetes Secret to store the client ID and client secret that Flux Operator
will use to authenticate with Dex. We’ll use flux-web as the client ID and
generate a random client secret using openssl:
kubectl create secret generic flux-web-client \
--from-literal=client-id=flux-web \
--from-literal=client-secret=$(openssl rand -hex 32) \
-n flux-system
Note that in a production setup, you should either store the Kubernetes Secret encrypted in Git for Flux to sync it, or use an external secret management solution with external-secrets to inject the secret into the cluster.
Flux Operator Configuration
Assuming that you have already deployed the Flux Operator using the Helm chart, you can enable SSO for the Web UI by updating the ResourceSet as follows:
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
name: flux-operator
namespace: flux-system
spec:
inputs:
- domain: "<your-domain>"
ingressClass: "<your-ingress-class>"
resources:
- apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: << inputs.provider.name >>
namespace: << inputs.provider.namespace >>
spec:
interval: 30m
url: oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator
layerSelector:
mediaType: "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
operation: copy
ref:
semver: '*'
- apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: << inputs.provider.name >>
namespace: << inputs.provider.namespace >>
spec:
interval: 30m
releaseName: << inputs.provider.name >>
serviceAccountName: << inputs.provider.name >>
chartRef:
kind: OCIRepository
name: << inputs.provider.name >>
values:
web:
config:
baseURL: "https://flux.<< inputs.domain >>"
authentication:
type: OAuth2
oauth2:
provider: OIDC
issuerURL: "https://dex.<< inputs.domain >>"
ingress:
enabled: true
className: << inputs.ingressClass >>
hosts:
- host: flux.<< inputs.domain >>
paths:
- path: /
pathType: Prefix
tls:
- secretName: cluster-tls
hosts:
- "*.<< inputs.domain >>"
valuesFrom:
- kind: Secret
name: flux-web-client
valuesKey: client-id
targetPath: web.config.authentication.oauth2.clientID
- kind: Secret
name: flux-web-client
valuesKey: client-secret
targetPath: web.config.authentication.oauth2.clientSecret
Make sure to replace the inputs with your actual domain name and Ingress class name. A similar configuration can be applied if you’re exposing the Web UI using Gateway API.
Note that we assume that a wildcard TLS certificate for your domain has been provisioned
in the flux-system namespace with the name cluster-tls. You should adjust the configuration
according to your TLS setup.
Dex Configuration with Static Users
To deploy Dex as a standalone OIDC provider with a static user:
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
name: dex
namespace: flux-system
annotations:
fluxcd.controlplane.io/reconcileEvery: "30m"
fluxcd.controlplane.io/reconcileTimeout: "5m"
spec:
wait: true
inputs:
- domain: "<your-domain>"
ingressClass: "<your-ingress-class>"
resources:
- apiVersion: v1
kind: Namespace
metadata:
name: dex
- apiVersion: v1
kind: Secret
metadata:
name: flux-web-client
namespace: dex
annotations:
fluxcd.controlplane.io/copyFrom: "flux-system/flux-web-client"
- apiVersion: v1
kind: Secret
metadata:
name: cluster-tls
namespace: dex
annotations:
fluxcd.controlplane.io/copyFrom: "flux-system/cluster-tls"
- apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: dex
namespace: dex
spec:
interval: 1h
url: https://charts.dexidp.io
- apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: dex
namespace: dex
spec:
chart:
spec:
chart: dex
version: "*"
sourceRef:
kind: HelmRepository
name: dex
interval: 1h
releaseName: dex
values:
config:
issuer: "https://dex.<< inputs.domain >>"
storage:
type: memory
staticClients:
- id: flux-web
redirectURIs:
- "https://flux.<< inputs.domain >>/oauth2/callback"
enablePasswordDB: true
staticPasswords:
- email: "admin@<< inputs.domain >>"
# bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
hash: "$2y$10$KR7JHCQ1BxNAKBOR/ixKqevGKtvtZnpgwvV/jF80eN5zLHVHx24E2"
username: "admin"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
ingress:
enabled: true
className: "<< inputs.ingressClass >>"
hosts:
- host: "dex.<< inputs.domain >>"
paths:
- path: /
pathType: ImplementationSpecific
tls:
- secretName: cluster-tls
hosts:
- "*.<< inputs.domain >>"
valuesFrom:
- kind: Secret
name: flux-web-client
valuesKey: client-id
targetPath: config.staticClients[0].name
- kind: Secret
name: flux-web-client
valuesKey: client-secret
targetPath: config.staticClients[0].secret
Make sure to replace the inputs with your actual domain name and Ingress class name.
After creating the ResourceSet, Dex will be available at https://dex.<your-domain> and configured
with a local OIDC provider with a single user (admin@<your-domain> / password).
Users RBAC Configuration
We can now create a ClusterRole and ClusterRoleBinding to grant the admin@<your-domain> user
read-only access to all resources in the cluster:
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
name: flux-web-users
namespace: flux-system
spec:
inputs:
- domain: "<your-domain>"
username: "admin"
resources:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flux-web-view
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch"]
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flux-web-<< inputs.username >>
subjects:
- kind: User
name: << inputs.username >>@<< inputs.domain >>
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: flux-web-view
apiGroup: rbac.authorization.k8s.io
Make sure to replace the inputs with your actual domain name.
Apply the ResourceSet and log in to the Flux Web UI using the Dex identity provider
with the admin@<your-domain> user. You should have access in the UI to view all resources in the cluster.
Dex Configuration with GitHub
To deploy Dex with a GitHub connector, you need to create a GitHub OAuth App in your GitHub organization.
Set the callback URL to https://dex.<your-domain>/callback and note down the client ID and client secret.
Create a Kubernetes Secret to store the GitHub OAuth App credentials:
kubectl create secret generic dex-github-client \
--from-literal=client-id=<your-github-client-id> \
--from-literal=client-secret=<your-github-client-secret> \
-n flux-system
Deploy Dex with the GitHub connector:
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
name: dex
namespace: flux-system
annotations:
fluxcd.controlplane.io/reconcileEvery: "30m"
fluxcd.controlplane.io/reconcileTimeout: "5m"
spec:
wait: true
inputs:
- domain: "<your-domain>"
ingressClass: "<your-ingress-class>"
org: "<your-github-org>"
resources:
- apiVersion: v1
kind: Namespace
metadata:
name: dex
- apiVersion: v1
kind: Secret
metadata:
name: dex-github-client
namespace: dex
annotations:
fluxcd.controlplane.io/copyFrom: "flux-system/dex-github-client"
- apiVersion: v1
kind: Secret
metadata:
name: flux-web-client
namespace: dex
annotations:
fluxcd.controlplane.io/copyFrom: "flux-system/flux-web-client"
- apiVersion: v1
kind: Secret
metadata:
name: cluster-tls
namespace: dex
annotations:
fluxcd.controlplane.io/copyFrom: "flux-system/cluster-tls"
- apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: dex
namespace: dex
spec:
interval: 1h
url: https://charts.dexidp.io
- apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: dex
namespace: dex
spec:
chart:
spec:
chart: dex
version: "*"
sourceRef:
kind: HelmRepository
name: dex
interval: 1h
releaseName: dex
values:
config:
issuer: "https://dex.<< inputs.domain >>"
storage:
type: memory
staticClients:
- id: flux-web
redirectURIs:
- "https://flux.<< inputs.domain >>/oauth2/callback"
connectors:
- type: github
id: github
name: GitHub
config:
redirectURI: "https://dex.<< inputs.domain >>/callback"
orgs:
- name: "<< inputs.org >>"
teamNameField: slug
ingress:
enabled: true
className: "<< inputs.ingressClass >>"
hosts:
- host: "dex.<< inputs.domain >>"
paths:
- path: /
pathType: ImplementationSpecific
tls:
- secretName: cluster-tls
hosts:
- "*.<< inputs.domain >>"
valuesFrom:
- kind: Secret
name: flux-web-client
valuesKey: client-id
targetPath: config.staticClients[0].name
- kind: Secret
name: flux-web-client
valuesKey: client-secret
targetPath: config.staticClients[0].secret
- kind: Secret
name: dex-github-client
valuesKey: client-id
targetPath: config.connectors[0].config.clientID
- kind: Secret
name: dex-github-client
valuesKey: client-secret
targetPath: config.connectors[0].config.clientSecret
Make sure to set the inputs to your actual domain name, Ingress class, and GitHub organization.
It is recommended to configure Dex with persistent storage to avoid losing user sessions on restarts. On production systems, consider decreasing the tokens expiry which defaults to 24 hours, as the ID token holds the session RBAC permissions.
Group RBAC Configuration
Assuming that your GitHub organization has a team named admins, you can create a ClusterRoleBinding
to grant cluster wide access to members of that team:
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
name: flux-web-admins
namespace: flux-system
spec:
inputs:
- org: "<your-github-org>"
team: "admin"
resources:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flux-web-<< inputs.team >>
subjects:
- kind: Group
name: "<< inputs.org >>:<< inputs.team >>"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
Assuming that your GitHub organization has dev teams that should have access to resources in their respective namespaces:
apiVersion: fluxcd.controlplane.io/v1
kind: ResourceSet
metadata:
name: flux-web-devs
namespace: flux-system
spec:
inputs:
- org: "<your-github-org>"
team: "dev-team-1"
namespace: "apps-1"
- org: "<your-github-org>"
team: "dev-team-2"
namespace: "apps-2"
resources:
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: flux-web-<< inputs.team >>
namespace: "<< inputs.namespace >>"
subjects:
- kind: Group
name: "<< inputs.org >>:<< inputs.team >>"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
The above example assumes that you have two dev teams (dev-team-1 and dev-team-2)
that should have access to resources in the apps-1 and apps-2 namespaces respectively.
Further Reading
Dex supports a wide range of identity providers and connectors, to use GitLab, Google, Microsoft or others, refer to the Dex documentation.