Argo CD can use GitLab as an authentication provider, so users log in with their GitLab accounts instead of local Argo CD users. That gives you centralized identity, cleaner access control, and one less password database waiting to become a security incident with a logo.
The setup uses OIDC through Dex, stores the GitLab OAuth credentials as encrypted Kubernetes secrets with SOPS and age, and lets Argo CD decrypt them with KSOPS during reconciliation.
How authentication works
Argo CD does not talk to GitLab directly for user login. It uses Dex as an identity broker.
The flow looks like this:
User -> Argo CD -> Dex -> GitLab -> Dex -> Argo CD -> RBAC
The short version:
- The user opens Argo CD.
- Argo CD redirects the user to Dex.
- Dex redirects the user to GitLab.
- GitLab authenticates the user and returns OIDC claims.
- Argo CD reads those claims and applies RBAC rules.
Authentication tells Argo CD who the user is. RBAC tells Argo CD what damage they are allowed to do. Ideally, not much.
Create a GitLab OAuth application
First, create an OAuth application in GitLab.
In GitLab, go to your user, group, or admin application settings and create a new application with these values:
Name: Argo CD
Redirect URI: https://argocd.example.com/api/dex/callback
Scopes: openid, read_user
GitLab gives you two important values:
Application ID
Secret
In the Argo CD Dex config, these become:
dex.gitlab.clientID: "<gitlab-application-id>"
dex.gitlab.clientSecret: "<gitlab-application-secret>"
dex.gitlab.redirectURI: "https://argocd.example.com/api/dex/callback"
Do not commit the secret in plain YAML. That is not GitOps. That is just leaving your keys under the doormat and calling it automation.
Encrypt the Argo CD secret with SOPS and age
Use SOPS with age to encrypt the Kubernetes secret before committing it.
Generate an age key if you do not have one yet:
age-keygen -o age.key
Store the public key in .sops.yaml:
creation_rules:
- encrypted_regex: '^(data|stringData)$'
age: age1examplepublickeyreplacewithyourrealpublickey
Create argocd-secret.yml:
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
namespace: argocd
type: Opaque
stringData:
dex.gitlab.clientID: "<gitlab-application-id>"
dex.gitlab.clientSecret: "<gitlab-application-secret>"
dex.gitlab.redirectURI: "https://argocd.example.com/api/dex/callback"
Encrypt it:
sops --encrypt argocd-secret.yml > argocd-secret.sops.yml
Then remove the unencrypted file:
rm argocd-secret.yml
Yes, really remove it. Git has a long memory and an even longer blame history.
Add KSOPS to the Argo CD repo server
Argo CD does not decrypt SOPS files by default. To make this work with Kustomize, install the KSOPS plugin in the Argo CD repo server.
The plugin lives here:
https://github.com/viaduct-ai/kustomize-sops
If you deploy Argo CD with Helm, add this to your values:
repoServer:
volumeMounts:
- name: custom-tools
mountPath: /usr/local/bin/kustomize
subPath: kustomize
- name: custom-tools
mountPath: /usr/local/bin/ksops
subPath: ksops
volumes:
- name: custom-tools
emptyDir: {}
initContainers:
- name: install-ksops
image: viaductoss/ksops:v4.5.1
command:
[
"/usr/local/bin/ksops",
"install",
"--with-kustomize",
"/custom-tools",
]
volumeMounts:
- mountPath: /custom-tools
name: custom-tools
This installs ksops and a Kustomize binary with plugin support into the repo server container.
Provide the age private key to Argo CD
The repo server needs the age private key to decrypt the SOPS file.
Create a Kubernetes secret from your age key:
kubectl -n argocd create secret generic sops-age-key \
--from-literal=key="$(cat age.key)"
Expose it to the repo server as SOPS_AGE_KEY:
repoServer:
env:
- name: SOPS_AGE_KEY
valueFrom:
secretKeyRef:
name: sops-age-key
key: key
volumeMounts:
- name: custom-tools
mountPath: /usr/local/bin/kustomize
subPath: kustomize
- name: custom-tools
mountPath: /usr/local/bin/ksops
subPath: ksops
volumes:
- name: custom-tools
emptyDir: {}
Protect this key properly. Anyone with the private key can decrypt the secrets. Kubernetes secrets are base64 encoded, not magically protected by tiny YAML elves.
Create the KSOPS generator
Create secret-generator.yml:
apiVersion: viaduct.ai/v1
kind: ksops
metadata:
name: argocd-secret-generator
annotations:
config.kubernetes.io/function: |
exec:
path: ksops
files:
- argocd-secret.sops.yml
This tells Kustomize to call ksops and decrypt argocd-secret.sops.yml during the build.
Reference the generator in Kustomize
In kustomization.yml, add the generator:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
generators:
- secret-generator.yml
When Argo CD renders this application, Kustomize runs the KSOPS generator and produces the decrypted argocd-secret resource.
Configure Dex in Argo CD
Now configure Argo CD to use GitLab through Dex.
In Helm values, configure argocd-cm like this:
configs:
cm:
kustomize.buildOptions: "--enable-alpha-plugins --enable-exec"
dex.config: |
connectors:
- type: gitlab
id: gitlab
name: GitLab
config:
baseURL: https://gitlab.example.com
clientID: $dex.gitlab.clientID
clientSecret: $dex.gitlab.clientSecret
The $dex.gitlab.* values are loaded from argocd-secret. That is why the secret names must match exactly.
The Kustomize build options matter:
kustomize.buildOptions: "--enable-alpha-plugins --enable-exec"
This allows Kustomize to execute the KSOPS plugin.
Do not enable exec plugins for repositories you do not trust. Otherwise, you have invented remote command execution with extra YAML. Security teams love that. Briefly.