Plan 0006: K8S Lab - Authenticated External Ingress
Overview
Implementation plan for hostname-based edge authentication with Traefik. This plan follows the spec at codev/specs/0006-authenticated-external-ingress.md.
Prerequisites
- Google OAuth application created in Google Cloud Console
- Authorized redirect URI:
https://auth.lab.ctoaas.co/oauth2/callback - Note the Client ID and Client Secret
- Authorized redirect URI:
Phase 1: Secret Infrastructure
Task 1.1: Create Source Secret (Manual)
# Generate a cookie secret
COOKIE_SECRET=$(openssl rand -base64 32 | tr -d '\n')
# Create the source secret in central-secret-store namespace
kubectl create secret generic google-oauth -n central-secret-store \
--from-literal=client-id=<GOOGLE_CLIENT_ID> \
--from-literal=client-secret=<GOOGLE_CLIENT_SECRET> \
--from-literal=cookie-secret=$COOKIE_SECRETTask 1.2: Create ClusterExternalSecret
File: repos/k8s-lab/components/central-secret-store/external-secrets/google-oauth.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterExternalSecret
metadata:
name: google-oauth-credentials
spec:
refreshTime: 5m
namespaceSelector:
matchLabels:
secrets/google-oauth-credentials: "true"
externalSecretSpec:
secretStoreRef:
name: central-secret-store
kind: ClusterSecretStore
target:
name: oauth2-proxy-secrets
creationPolicy: Owner
template:
metadata:
labels:
managed-by: external-secrets
source: central-secret-store
secret-type: google-oauth-credentials
data:
- secretKey: client-id
remoteRef:
key: google-oauth
property: client-id
- secretKey: client-secret
remoteRef:
key: google-oauth
property: client-secret
- secretKey: cookie-secret
remoteRef:
key: google-oauth
property: cookie-secretTask 1.3: Update central-secret-store kustomization
File: repos/k8s-lab/components/central-secret-store/kustomization.yaml
Add to resources:
- external-secrets/google-oauth.yamlPhase 2: oauth2-proxy Deployment
Task 2.1: Create oauth2-proxy Directory Structure
repos/k8s-lab/oauth2-proxy/
├── kustomization.yaml
├── oauth2-proxy-namespace.yaml
├── values.yaml
└── allowed-emails.yaml
Task 2.2: Create Namespace
File: repos/k8s-lab/oauth2-proxy/oauth2-proxy-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: oauth2-proxy
labels:
secrets/google-oauth-credentials: "true"Task 2.3: Create Allowed Emails ConfigMap
File: repos/k8s-lab/oauth2-proxy/allowed-emails.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: oauth2-proxy-allowed-emails
namespace: oauth2-proxy
data:
allowed-emails.txt: |
# Add allowed email addresses, one per line
# Example:
# user@example.comTask 2.4: Create Helm Values
File: repos/k8s-lab/oauth2-proxy/values.yaml
# oauth2-proxy configuration for Google OAuth
image:
repository: quay.io/oauth2-proxy/oauth2-proxy
tag: "v7.6.0"
config:
clientID: "" # Set via secret
clientSecret: "" # Set via secret
cookieSecret: "" # Set via secret
configFile: |-
provider = "google"
email_domains = ["*"]
authenticated_emails_file = "/etc/oauth2-proxy/allowed-emails.txt"
upstreams = ["static://200"]
http_address = "0.0.0.0:4180"
cookie_secure = true
cookie_domains = [".lab.ctoaas.co"]
whitelist_domains = [".lab.ctoaas.co"]
set_xauthrequest = true
set_authorization_header = true
pass_access_token = true
skip_provider_button = true
extraArgs:
- --reverse-proxy=true
existingSecret: oauth2-proxy-secrets
extraVolumes:
- name: allowed-emails
configMap:
name: oauth2-proxy-allowed-emails
extraVolumeMounts:
- name: allowed-emails
mountPath: /etc/oauth2-proxy
readOnly: true
ingress:
enabled: true
className: ""
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
hosts:
- host: auth.lab.ctoaas.co
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- auth.lab.ctoaas.co
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoScheduleTask 2.5: Create Kustomization
File: repos/k8s-lab/oauth2-proxy/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: oauth2-proxy
resources:
- oauth2-proxy-namespace.yaml
- allowed-emails.yaml
helmCharts:
- name: oauth2-proxy
repo: https://oauth2-proxy.github.io/manifests
version: 7.7.1
releaseName: oauth2-proxy
namespace: oauth2-proxy
valuesFile: values.yamlPhase 3: Traefik File Provider Configuration
Task 3.1: Update Traefik Values
File: repos/k8s-lab/traefik/values.yaml
Add file provider configuration via additionalConfigMaps:
additionalConfigMaps:
hostname-auth:
http:
routers:
# OAuth callback endpoint - must bypass auth (highest priority)
auth-host-noauth:
rule: "Host(`auth.lab.ctoaas.co`)"
entryPoints:
- websecure
service: noop@internal
priority: 300
tls: {}
# Internal hosts - no auth required (high priority)
internal-host-noauth:
rule: "HostRegexp(`{subdomain:.+}.lab.local.ctoaas.co`)"
entryPoints:
- websecure
service: noop@internal
priority: 200
tls: {}
# Public hosts with public=true label - require Google auth
# Only matches ingresses that have opted-in via annotation:
# traefik.ingress.kubernetes.io/router.labels: public=true
public-host-auth:
rule: >
HostRegexp(`{subdomain:.+}.lab.ctoaas.co`)
&& HeadersRegexp(`X-Forwarded-Labels`, `.*public=true.*`)
entryPoints:
- websecure
middlewares:
- google-auth
service: noop@internal
priority: 100
tls: {}
middlewares:
google-auth:
forwardAuth:
address: "http://oauth2-proxy.oauth2-proxy.svc.cluster.local:4180/oauth2/auth"
trustForwardHeader: true
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- X-Auth-Request-Access-Token
- AuthorizationPublic Opt-In: Ingresses must add the annotation to be publicly accessible:
annotations:
traefik.ingress.kubernetes.io/router.labels: public=truePhase 4: Integration
Task 4.1: Add oauth2-proxy to Components
File: repos/k8s-lab/components/kustomization.yaml
Add to resources:
- ../oauth2-proxy/Task 4.2: Sync and Verify
# Apply changes (if using direct kustomize)
cd repos/k8s-lab
kustomize build components/ | kubectl apply -f -
kustomize build traefik/ | kubectl apply -f -
# Or if using ArgoCD, sync the applications
argocd app sync k8s-lab-components
argocd app sync k8s-lab-traefikPhase 5: Validation
Task 5.1: Verify Secret Sync
# Check ClusterExternalSecret status
kubectl get clusterexternalsecrets google-oauth-credentials
# Verify secret created in oauth2-proxy namespace
kubectl get secret oauth2-proxy-secrets -n oauth2-proxyTask 5.2: Verify oauth2-proxy
# Check pod status
kubectl get pods -n oauth2-proxy
# Check logs
kubectl logs -n oauth2-proxy -l app.kubernetes.io/name=oauth2-proxy
# Test internal connectivity
kubectl run -it --rm debug --image=curlimages/curl -- \
curl -v http://oauth2-proxy.oauth2-proxy.svc.cluster.local:4180/pingTask 5.3: Verify Traefik Configuration
# Check Traefik dashboard at https://traefik.lab.local.ctoaas.co
# Verify routers:
# - auth-host-noauth@file (priority 300)
# - internal-host-noauth@file (priority 200)
# - public-host-auth@file (priority 100)
# Verify middleware:
# - google-auth@fileTask 5.4: End-to-End Testing
| Test | Command/Action | Expected Result |
|---|---|---|
| Public host with opt-in | Visit https://service.lab.ctoaas.co (Ingress has public=true) | Redirects to Google login |
| Public host without opt-in | Visit https://service.lab.ctoaas.co (Ingress lacks annotation) | No route match / not accessible |
| Auth callback | Complete Google login | Redirects back to original URL |
| Internal host bypass | Visit https://traefik.lab.local.ctoaas.co | Direct access, no auth |
| Auth host bypass | Visit https://auth.lab.ctoaas.co | oauth2-proxy responds (no redirect loop) |
| Email allowlist | Login with non-allowed email | Access denied |
| Email allowlist | Login with allowed email | Access granted |
File Summary
New Files
| File | Description |
|---|---|
repos/k8s-lab/oauth2-proxy/kustomization.yaml | Kustomize config with Helm chart |
repos/k8s-lab/oauth2-proxy/oauth2-proxy-namespace.yaml | Namespace with secret label |
repos/k8s-lab/oauth2-proxy/values.yaml | oauth2-proxy Helm values |
repos/k8s-lab/oauth2-proxy/allowed-emails.yaml | ConfigMap for email allowlist |
repos/k8s-lab/components/central-secret-store/external-secrets/google-oauth.yaml | ClusterExternalSecret |
Modified Files
| File | Change |
|---|---|
repos/k8s-lab/traefik/values.yaml | Add file provider with routers and middleware |
repos/k8s-lab/components/central-secret-store/kustomization.yaml | Add google-oauth.yaml resource |
repos/k8s-lab/components/kustomization.yaml | Add oauth2-proxy resource |
Rollback Plan
If issues occur:
- Remove file provider config from
traefik/values.yaml(restores pre-auth behavior) - Remove oauth2-proxy from
components/kustomization.yaml - Delete oauth2-proxy namespace:
kubectl delete namespace oauth2-proxy
Notes
- The
noop@internalservice is a Traefik built-in that does nothing - it allows the request to continue to normal Ingress routing after middleware processing - Router priorities ensure more specific rules (auth host, internal hosts) are evaluated before the catch-all public host rule
- The
HostRegexpsyntax changed in Traefik v3 - uses^.+\\.domain$format - Cookie domain
.lab.ctoaas.coallows session sharing across all subdomains