
Argo CD GitLab Authentication: A Complete GitOps-Friendly Setup

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.
Configure Argo CD RBAC
Authentication is not authorization. After GitLab login works, configure Argo CD RBAC.
For simple user mapping by email:
helm: valuesObject: configs: rbac: policy.csv: | g, alice@example.com, role:admin g, bob@example.com, role:readonly scopes: "[email,groups]"
This maps:
alice@example.com -> role:admin bob@example.com -> role:readonly
For larger teams, prefer GitLab groups instead of individual users. User by user RBAC starts cute and ends as spreadsheet archaeology.
Apply the configuration
If Argo CD is managed by another Argo CD application, commit the files and let Argo CD sync them.
If you are applying locally for a test, run:
kustomize build --enable-alpha-plugins --enable-exec . | kubectl apply -f -
Then restart the Argo CD components if needed:
kubectl -n argocd rollout restart deployment argocd-server kubectl -n argocd rollout restart deployment argocd-dex-server kubectl -n argocd rollout restart deployment argocd-repo-server
Verify the setup
Check that the secret exists:
kubectl -n argocd get secret argocd-secret
Check the Dex logs:
kubectl -n argocd logs deploy/argocd-dex-server
Check the Argo CD server logs:
kubectl -n argocd logs deploy/argocd-server
Check the repo server if decryption fails:
kubectl -n argocd logs deploy/argocd-repo-server
Open Argo CD in the browser:
https://argocd.example.com
You should see a GitLab login option. After login, the user should land in Argo CD with the expected role.
Troubleshoot common failures
If GitLab returns a redirect error, check the redirect URI.
It must match exactly:
https://argocd.example.com/api/dex/callback
If login works but RBAC does not, check which claim Argo CD receives. The email in policy.csv must match the user claim.
If the secret does not render, check the repo server pod:
kubectl -n argocd describe pod -l app.kubernetes.io/name=argocd-repo-server
Common causes:
- KSOPS is not installed in the repo server.
-enable-alpha-plugins --enable-execis missing.- The age private key is not available in the repo server environment.
SOPS_AGE_KEYis missing or points to the wrong Kubernetes secret key.- The encrypted file name does not match
secret-generator.yml. - The secret keys do not match
$dex.gitlab.clientID,$dex.gitlab.clientSecret, or$dex.gitlab.redirectURI.
You can test decryption locally if you have the age key:
SOPS_AGE_KEY="$(cat age.key)" sops --decrypt argocd-secret.sops.yml
Security notes
Keep the boring security parts boring. Boring is good. Boring means nobody is explaining an outage in a meeting with too many directors.
- Encrypt the GitLab OAuth secret with SOPS.
- Protect the age private key.
- Limit who can change Argo CD applications that use Kustomize exec plugins.
- Use GitLab groups for larger teams.
- Rotate the GitLab OAuth secret periodically.
- Audit
role:adminassignments. - Avoid giving admin access to users because they asked nicely in Slack.
Final result
With this setup, GitLab becomes the login provider for Argo CD, secrets stay encrypted in Git, and Argo CD RBAC controls what authenticated users can do.
The moving parts are not complicated:
GitLab OAuth app SOPS encrypted argocd-secret KSOPS in repo server Dex GitLab connector Argo CD RBAC policy
That is a reasonable amount of YAML for centralized authentication. Not small, obviously. This is Kubernetes. We measure configuration in geological layers.




