Secure Argo Workflows with Pomerium
What this guide does
Argo Workflows is a container-native workflow engine for orchestrating parallel jobs on Kubernetes. Its web UI and API have no opinionated SSO of their own beyond delegating to an OIDC provider or the Kubernetes API. This guide puts Argo behind the Pomerium Ingress Controller so Pomerium handles single sign-on and authorization at the edge, and the Argo Server only ever sees requests from users your policy already allowed.
When to use this guide
Use it when you run Argo Workflows on Kubernetes and want one front door that authenticates users against your existing identity provider and enforces who can reach the Argo UI and API. This is a good fit when you would rather not wire Argo's own SSO or hand out Kubernetes bearer tokens to every user. If you only need private network access to Argo without browser SSO, a plain TCP route is simpler.
Prerequisites
- A Kubernetes cluster and
kubectlconfigured to reach it. - Helm v3.
- The Pomerium Ingress Controller installed in the cluster, with global configuration applied so Pomerium can sign users in through your identity provider. If you are new to Pomerium on Kubernetes, start with the Kubernetes Quickstart.
- A domain you control for the Argo route (this guide uses
argo.yourdomain.com) with DNS pointing at thepomerium-proxyload balancer. - A way to issue TLS certificates for that host, such as cert-manager.
Pomerium works with any OIDC provider. To run your own instead of a hosted identity provider (IdP), follow Keycloak + Pomerium and set the matching identityProvider values in the Pomerium global configuration.
Install Argo Workflows
Install Argo with the official Helm chart. Run the Argo Server in server auth mode so that Pomerium is the single gatekeeper and the Server authenticates to Kubernetes with its own service account instead of asking each user for a bearer token:
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
kubectl create namespace argo
helm install argo argo/argo-workflows \
--namespace argo \
--set server.authModes='{server}'
Setting server.authModes={server} runs the Argo Server with --auth-mode=server, so the later notes about that flag refer to this same setting.
The chart creates a Service named argo-argo-workflows-server (the pattern is <release>-<chart>-server) that listens on port 2746 over plain HTTP. Confirm it before wiring up the Ingress:
kubectl --namespace argo get svc argo-argo-workflows-server
You can sanity-check the UI by port-forwarding directly, but only do this from a trusted machine since server auth mode does not prompt for a login on its own:
kubectl --namespace argo port-forward svc/argo-argo-workflows-server 2746:2746
Then close the port-forward. From here on, all access goes through Pomerium.
Configure the Pomerium route
Create an Ingress resource that the Pomerium Ingress Controller will turn into a route. It points argo.yourdomain.com at the Argo Server, restricts access with a Pomerium policy, and forwards identity headers so Argo's logs can attribute requests to a user:
# Routes argo.yourdomain.com through the Pomerium Ingress Controller to the
# Argo Workflows Server. Replace argo.yourdomain.com with a domain you control
# and you@example.com with the user(s) who should have access.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argo
namespace: argo
annotations:
# Forward the Pomerium identity headers (e.g. X-Pomerium-Claim-Email) to the
# Argo Server so downstream logs and audit trails can attribute the user.
ingress.pomerium.io/pass_identity_headers: 'true'
# Only allow the listed user(s). Swap in a group or domain match as needed.
ingress.pomerium.io/policy: |
- allow:
or:
- email:
is: you@example.com
spec:
ingressClassName: pomerium
rules:
- host: argo.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
# Service created by the argo-workflows Helm chart with release
# name "argo": <release>-<chart>-server. Run
# `kubectl -n argo get svc` to confirm the name in your cluster.
name: argo-argo-workflows-server
port:
number: 2746
tls:
- hosts:
- argo.yourdomain.com
secretName: argo-yourdomain-com-tls
Replace argo.yourdomain.com with your domain and you@example.com with the user(s) who should have access. The tls block names the secret your certificate solution will populate; with cert-manager you would add a cert-manager.io/cluster-issuer annotation so the certificate is issued automatically. To allow a whole team, replace the email matcher with a group or domain claim; see Policy.
Validate it against your cluster's API, then apply it:
kubectl apply -f argo-ingress.yaml --dry-run=server
kubectl apply -f argo-ingress.yaml
The Pomerium Ingress Controller reconciles the resource and serves the route. You can watch it pick up the Ingress with kubectl --namespace argo describe ingress argo.
Verify the setup
- The route requires authentication. In a fresh browser, open
https://argo.yourdomain.com. Pomerium should redirect you to sign in, not drop you straight into Argo. - An allowed user gets in. Sign in as the user your policy allows. Pomerium redirects you back and the Argo Workflows dashboard loads.
- A disallowed user is blocked. Sign in as a user the policy does not list and confirm Pomerium returns a 403 instead of the Argo UI.
Common failure modes
- 404 or no route from Pomerium. The Ingress Controller did not adopt the resource. Confirm
spec.ingressClassNameispomeriumand check the controller logs withkubectl --namespace pomerium logs deploy/pomerium. - 502 / upstream connection errors. The backend service name or port is wrong. Run
kubectl --namespace argo get svcand make sure thebackend.service.nameandport.numbermatch the Argo Server service (argo-argo-workflows-server:2746by default). - Certificate warnings or a redirect loop. DNS for
argo.yourdomain.comis not pointing at thepomerium-proxyload balancer, or theargo-yourdomain-com-tlssecret has not been issued yet. Verify the DNS record and that cert-manager (or your equivalent) populated the secret. - Argo UI loads without a login prompt when reached directly. That is expected with
--auth-mode=server: Argo itself does not authenticate, so it must only be reachable through Pomerium. See Security considerations.
Security considerations
- Do not expose the Argo Server directly. With
--auth-mode=server, the Argo UI and API trust whoever can reach them. Keep theargo-argo-workflows-serverservice as aClusterIP(the chart default) so the only ingress path is through Pomerium, and avoidLoadBalancer/NodePortservices or unscoped port-forwards in shared environments. - Scope the policy to the people who should run workflows. The example allows a single email. Tighten it to the right group or domain, since anyone the policy allows can submit and manage workflows that run with the Argo Server's Kubernetes permissions.
- Prefer
serverauth mode overclientfor this pattern.clientmode would require each user to present a Kubernetes bearer token, which defeats the point of fronting Argo with Pomerium. If you do need per-user Kubernetes RBAC, combine Pomerium with Argo SSO RBAC instead of header trust.