docs auth

This commit is contained in:
Danijel Simeunovic
2026-03-16 11:14:12 +01:00
parent d02da33700
commit ae075bbc48
4 changed files with 847 additions and 36 deletions

View File

@@ -8,6 +8,7 @@
- [Deploying Your First Application](#deploying-your-first-application)
- [Updating an Existing Application](#updating-an-existing-application)
- [Working with Secrets](#working-with-secrets)
- [Enabling Authentication for Applications](#enabling-authentication-for-applications)
- [Troubleshooting](#troubleshooting)
- [Best Practices](#best-practices)
@@ -754,6 +755,457 @@ kubectl rollout restart deployment myapp -n myapp
---
## Enabling Authentication for Applications
The cluster supports automatic authentication sidecar injection for applications via Kyverno policies. This allows you to add authentication to your applications without modifying application code.
### How It Works
When you enable authentication in your Helm values, the Kyverno policy automatically:
1. ✅ Injects an authentication sidecar container into your pod
2. ✅ Routes all incoming traffic through the auth sidecar (port 8080)
3. ✅ Validates credentials before forwarding requests to your application
4. ✅ Creates necessary secrets (if they don't exist)
5. ✅ Adds a NetworkPolicy to restrict ingress
**Architecture**:
```
Internet → Traefik → Service:8080 → Auth Sidecar:8080 → localhost → Your App:3000
├─ Validates credentials
└─ Forwards if valid
```
### Authentication Modes
Two authentication modes are supported:
1. **Token-based**: Static tokens (simple, good for service-to-service or internal apps)
2. **OIDC**: OpenID Connect (full SSO, good for user-facing apps)
---
### Token-Based Authentication
#### Step 1: Configure Helm Values
```yaml
# In helm-values/myapp/values.yaml
auth:
enabled: true
type: token # Token mode (default)
tokens:
- d4f88f6d9292c10cc3e21c4aad56d2be485db532b54fe961d738e1137d247823
- 8803f621acc3898df1d7a8f514bc3602551a0681a8f747bd4e43c3c5849d57a7
```
#### Step 2: Generate Token (if needed)
```bash
# Generate a secure random token
openssl rand -hex 32
# Or using Python
python3 -c "import secrets; print(secrets.token_hex(32))"
# Example output:
# d4f88f6d9292c10cc3e21c4aad56d2be485db532b54fe961d738e1137d247823
```
#### Step 3: Deploy Application
Commit and push your changes:
```bash
cd ~/dev/k8s/helm-prod-values
git add myapp/values.yaml
git commit -m "Enable token auth for myapp"
git push
```
ArgoCD will sync, and the Kyverno policy will:
- Inject the auth sidecar container
- Create an `auth-tokens` Secret with your tokens
- Configure the sidecar to validate against these tokens
#### Step 4: Access Application
Use your token in the `Authorization` header:
```bash
# Access application with token
curl -H "Authorization: Bearer d4f88f6d9292c10cc3e21c4aad56d2be485db532b54fe961d738e1137d247823" \
https://myapp.forteapps.net/api/data
# Without token (will be rejected)
curl https://myapp.forteapps.net/api/data
# Response: 401 Unauthorized
```
#### Advanced: Custom Secret Name
To use a different secret for tokens:
```yaml
# In Helm values
auth:
enabled: true
type: token
tokens: [] # Empty - using external secret
# Tokens will be read from custom secret
```
Then reference it via annotation (configured by Helm chart automatically):
```yaml
# Helm chart sets this annotation:
policies.forteapps.io/auth-token-secret-name: "myapp-auth-tokens"
```
Create the secret manually:
```bash
kubectl create secret generic myapp-auth-tokens \
--from-file=tokens=tokens.txt \
--namespace=myapp
```
---
### OIDC Authentication
OIDC mode integrates with identity providers like Keycloak, Okta, Auth0, Azure AD, etc.
#### Step 1: Configure Identity Provider
In your identity provider (e.g., Keycloak):
1. Create a new client (e.g., `myapp`)
2. Set redirect URI: `https://myapp.forteapps.net/auth/callback`
3. Note the **Client ID** and **Client Secret**
4. Note the **Authority URL** (e.g., `https://keycloak.forteapps.net/realms/master`)
#### Step 2: Create OIDC Secret
```bash
# Create plain secret
kubectl create secret generic auth-oidc \
--from-literal=client-secret=your-oidc-client-secret \
--from-literal=cookie-secret=$(openssl rand -hex 32) \
--namespace=myapp \
--dry-run=client -o yaml > private/myapp-auth-oidc.yaml
# Seal it
kubeseal --format=yaml \
--cert=pub-cert.pem \
--namespace=myapp \
< private/myapp-auth-oidc.yaml \
> secrets/myapp-auth-oidc-sealed.yaml
# Commit sealed secret
cd ~/dev/k8s/launchpad
git add secrets/myapp-auth-oidc-sealed.yaml
git commit -m "Add OIDC secrets for myapp"
git push
# Clean up
rm private/myapp-auth-oidc.yaml
```
#### Step 3: Configure Helm Values
```yaml
# In helm-values/myapp/values.yaml
auth:
enabled: true
type: oidc # OIDC mode
oidc:
authority: https://keycloak.forteapps.net/realms/master
clientId: myapp
scopes: "openid,profile,email"
callbackPath: /auth/callback
```
#### Step 4: Deploy Application
```bash
cd ~/dev/k8s/helm-prod-values
git add myapp/values.yaml
git commit -m "Enable OIDC auth for myapp"
git push
```
#### Step 5: Access Application
When users access `https://myapp.forteapps.net`:
1. They're redirected to the identity provider login page
2. After successful login, redirected back to `/auth/callback`
3. Session cookie is set
4. Subsequent requests are authenticated via cookie
**User flow**:
```
User → https://myapp.forteapps.net
Redirect → https://keycloak.forteapps.net/login
Login successful → Redirect with auth code
https://myapp.forteapps.net/auth/callback?code=xyz
Auth sidecar exchanges code for tokens
Sets session cookie
Redirects to application → https://myapp.forteapps.net
User sees application (authenticated)
```
---
### Authentication Configuration Reference
#### Helm Values Schema
```yaml
auth:
enabled: false # Enable/disable authentication
type: token # "token" or "oidc"
# Token mode configuration
tokens: [] # List of valid bearer tokens
# - token1
# - token2
# OIDC mode configuration
oidc:
authority: "" # OIDC provider URL (required for OIDC)
clientId: "" # OIDC client ID (required for OIDC)
scopes: "openid,profile,email" # OIDC scopes (optional)
callbackPath: /auth/callback # OAuth callback path (optional)
```
#### Annotations Set by Helm Chart
When `auth.enabled: true`, the Helm chart sets these pod annotations:
**Token mode**:
```yaml
policies.forteapps.io/auth: "true"
policies.forteapps.io/auth-type: "token"
policies.forteapps.io/auth-token-secret-name: "auth-tokens"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
```
**OIDC mode**:
```yaml
policies.forteapps.io/auth: "true"
policies.forteapps.io/auth-type: "oidc"
policies.forteapps.io/auth-oidc-authority: "https://keycloak.forteapps.net/realms/master"
policies.forteapps.io/auth-oidc-client-id: "myapp"
policies.forteapps.io/auth-oidc-scopes: "openid,profile,email"
policies.forteapps.io/auth-oidc-callback-path: "/auth/callback"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
```
#### Sidecar Configuration
The auth sidecar container:
- **Image**: `ghcr.io/snothub/stunning-memory:latest`
- **Port**: 8080
- **Resources**: 10m CPU / 32Mi memory (requests), 50m CPU / 64Mi memory (limits)
- **Health checks**: `/healthz` endpoint
- **Security**: Read-only root filesystem, no privilege escalation
#### Advanced: Custom Sidecar Image
To use a different auth sidecar image:
```yaml
# These annotations can be set in the Helm chart template if needed
policies.forteapps.io/auth-image: "your-registry/your-auth-proxy"
policies.forteapps.io/auth-image-version: "v1.2.3"
```
---
### Authentication Examples
#### Example 1: Internal API with Token Auth
```yaml
# helm-values/internal-api/values.yaml
app:
image:
repository: ghcr.io/company/internal-api
tag: v1.0.0
auth:
enabled: true
type: token
tokens:
- d4f88f6d9292c10cc3e21c4aad56d2be485db532b54fe961d738e1137d247823 # Service A
- 8803f621acc3898df1d7a8f514bc3602551a0681a8f747bd4e43c3c5849d57a7 # Service B
ingress:
enabled: true
host: internal-api.forteapps.net
```
**Usage**:
```bash
# Service A calls API
curl -H "Authorization: Bearer d4f88f..." \
https://internal-api.forteapps.net/api/endpoint
```
#### Example 2: User-Facing App with OIDC
```yaml
# helm-values/web-app/values.yaml
app:
image:
repository: ghcr.io/company/web-app
tag: v2.1.0
auth:
enabled: true
type: oidc
oidc:
authority: https://auth.company.com/realms/employees
clientId: web-app-prod
scopes: "openid,profile,email,groups"
callbackPath: /auth/callback
ingress:
enabled: true
host: web-app.forteapps.net
```
**With sealed OIDC secret**:
```bash
# Create and seal secret
kubectl create secret generic auth-oidc \
--from-literal=client-secret=super-secret-value \
--from-literal=cookie-secret=$(openssl rand -hex 32) \
--namespace=web-app \
--dry-run=client -o yaml | \
kubeseal --format=yaml --cert=pub-cert.pem --namespace=web-app \
> secrets/web-app-auth-oidc-sealed.yaml
```
#### Example 3: Disabling Authentication
```yaml
# helm-values/public-api/values.yaml
auth:
enabled: false # No authentication
ingress:
enabled: true
host: public-api.forteapps.net
```
---
### Troubleshooting Authentication
#### Issue: 401 Unauthorized (Token Mode)
**Check token validity**:
```bash
# Get auth-tokens secret
kubectl get secret auth-tokens -n myapp -o yaml
# Decode tokens
kubectl get secret auth-tokens -n myapp \
-o jsonpath='{.data.tokens}' | base64 -d
# Verify your token is in the list
```
**Test with different token**:
```bash
curl -v -H "Authorization: Bearer YOUR-TOKEN-HERE" \
https://myapp.forteapps.net/
```
#### Issue: OIDC Login Loop
**Check OIDC configuration**:
```bash
# Verify auth-oidc secret exists
kubectl get secret auth-oidc -n myapp
# Check sidecar logs
kubectl logs -n myapp <pod-name> -c authn
# Common issues:
# - Wrong authority URL
# - Wrong client ID
# - Missing client-secret in auth-oidc Secret
# - Redirect URI not configured in identity provider
```
**Verify redirect URI** in your identity provider matches:
```
https://<your-app-domain>/auth/callback
```
#### Issue: Auth Sidecar Not Injected
**Check pod annotations**:
```bash
kubectl get pod -n myapp <pod-name> -o yaml | grep policies.forteapps.io
# Should show:
# policies.forteapps.io/auth: "true"
```
**Check Kyverno policy**:
```bash
kubectl get clusterpolicy inject-auth-sidecar
kubectl describe clusterpolicy inject-auth-sidecar
```
**Check Kyverno logs**:
```bash
kubectl logs -n kyverno deployment/kyverno | grep inject-auth
```
#### Issue: Auth Sidecar Crashes
**Check sidecar logs**:
```bash
kubectl logs -n myapp <pod-name> -c authn
```
**Common causes**:
- Missing secret (auth-tokens or auth-oidc)
- Invalid OIDC configuration
- Can't reach OIDC authority URL
- Network policy blocking outbound OIDC requests
---
### Authentication Best Practices
✅ **DO**:
- Use OIDC for user-facing applications
- Use token auth for service-to-service communication
- Rotate tokens and secrets regularly
- Use strong random tokens (32+ bytes)
- Store client secrets in SealedSecrets
- Test authentication before deploying to production
- Document which tokens/users have access
❌ **DON'T**:
- Share tokens between environments
- Commit tokens to application code
- Use predictable tokens
- Reuse tokens across multiple applications
- Disable authentication on sensitive APIs
- Log tokens or secrets
---
## Troubleshooting
### Application Not Deploying

View File

@@ -496,6 +496,122 @@ data:
When a new namespace is created, Kyverno automatically copies this secret.
### Authentication Secrets
Applications using the authentication sidecar require specific secrets depending on the auth mode.
#### Token Mode Secrets
Token-based auth uses an `auth-tokens` Secret:
```bash
# Method 1: From Helm values (automatic)
# Tokens specified in values.yaml are automatically created
# Method 2: Manual creation
kubectl create secret generic auth-tokens \
--from-literal=tokens="token1
token2
token3" \
--namespace=myapp
# Method 3: From file
echo "d4f88f6d9292c10cc3e21c4aad56d2be485db532b54fe961d738e1137d247823" > tokens.txt
echo "8803f621acc3898df1d7a8f514bc3602551a0681a8f747bd4e43c3c5849d57a7" >> tokens.txt
kubectl create secret generic auth-tokens \
--from-file=tokens=tokens.txt \
--namespace=myapp
rm tokens.txt
```
#### OIDC Mode Secrets
OIDC auth requires an `auth-oidc` Secret with two keys:
```bash
# Generate secrets
CLIENT_SECRET="your-oidc-client-secret-from-provider"
COOKIE_SECRET=$(openssl rand -hex 32)
# Create plain secret
kubectl create secret generic auth-oidc \
--from-literal=client-secret=$CLIENT_SECRET \
--from-literal=cookie-secret=$COOKIE_SECRET \
--namespace=myapp \
--dry-run=client -o yaml > private/myapp-auth-oidc.yaml
# Seal it
kubeseal --format=yaml \
--cert=pub-cert.pem \
--namespace=myapp \
< private/myapp-auth-oidc.yaml \
> secrets/myapp-auth-oidc-sealed.yaml
# Apply sealed secret
kubectl apply -f secrets/myapp-auth-oidc-sealed.yaml
# Commit to Git
git add secrets/myapp-auth-oidc-sealed.yaml
git commit -m "Add OIDC secrets for myapp"
git push
# Clean up
rm private/myapp-auth-oidc.yaml
```
#### Rotating Authentication Secrets
**Token Rotation**:
```bash
# Generate new token
NEW_TOKEN=$(openssl rand -hex 32)
# Get current tokens
kubectl get secret auth-tokens -n myapp -o yaml > /tmp/tokens.yaml
# Edit tokens (add new, optionally remove old)
# Then re-seal and apply
# Restart pods to use new tokens
kubectl rollout restart deployment myapp -n myapp
```
**OIDC Secret Rotation**:
```bash
# Rotate cookie secret (safe - invalidates existing sessions)
NEW_COOKIE_SECRET=$(openssl rand -hex 32)
# Recreate secret
kubectl create secret generic auth-oidc \
--from-literal=client-secret=$CLIENT_SECRET \
--from-literal=cookie-secret=$NEW_COOKIE_SECRET \
--namespace=myapp \
--dry-run=client -o yaml | \
kubeseal --format=yaml --cert=pub-cert.pem --namespace=myapp | \
kubectl apply -f -
# Restart to pick up new secret
kubectl rollout restart deployment myapp -n myapp
```
#### Viewing Authentication Secrets
```bash
# List auth-related secrets
kubectl get secrets -n myapp | grep auth
# View token secret (tokens are in plain text in the Secret)
kubectl get secret auth-tokens -n myapp -o jsonpath='{.data.tokens}' | base64 -d
# View OIDC secret keys (values are base64 encoded)
kubectl get secret auth-oidc -n myapp -o jsonpath='{.data.client-secret}' | base64 -d
kubectl get secret auth-oidc -n myapp -o jsonpath='{.data.cookie-secret}' | base64 -d
```
**See**: [Developer Guide - Enabling Authentication](../docs/DEVELOPER-GUIDE.md#enabling-authentication-for-applications) for complete authentication setup guide.
---
## Monitoring & Alerting

View File

@@ -299,19 +299,27 @@ ingress:
clusterIssuer: letsencrypt-prod
auth:
enabled: false
type: token # Options: "token", "oidc"
oidc:
authority: ""
clientId: ""
scopes: ""
callbackPath: /auth/callback
tokens: []
# - token1
# - token2
enabled: false # Enable authentication sidecar injection
type: token # Authentication mode: "token" or "oidc"
configmap: []
# Token-based authentication configuration
tokens: [] # List of valid bearer tokens (hex strings, 32+ bytes recommended)
# - d4f88f6d9292c10cc3e21c4aad56d2be485db532b54fe961d738e1137d247823
# - 8803f621acc3898df1d7a8f514bc3602551a0681a8f747bd4e43c3c5849d57a7
# OIDC authentication configuration
oidc:
authority: "" # OIDC provider URL (e.g., https://auth.example.com/realms/master)
clientId: "" # OIDC client ID registered with provider
scopes: "openid,profile,email" # OAuth scopes (comma-separated)
callbackPath: /auth/callback # OAuth callback path (default: /auth/callback)
# Note: Client secret must be in 'auth-oidc' Secret (client-secret key)
# Cookie secret must be in 'auth-oidc' Secret (cookie-secret key)
configmap: [] # Application ConfigMap key-value pairs
# KEY: value
# DB_HOST: postgres
# DB_PORT: "5432"
```
---
@@ -818,35 +826,235 @@ spec:
**File**: `cluster-resources/policies/auth-sidecar-injector.yaml`
**Purpose**: Inject authentication sidecar based on pod annotations
**Purpose**: Automatically inject authentication sidecar into pods with authentication enabled
**Rules**: 5 rules in the policy
1. `generate-auth-tokens-secret` - Creates Secret for token mode
2. `generate-auth-oidc-secret` - Creates Secret for OIDC mode
3. `inject-sidecar-token` - Injects auth sidecar for token mode
4. `inject-sidecar-oidc` - Injects auth sidecar for OIDC mode
5. `generate-auth-network-policy` - Creates NetworkPolicy to restrict ingress
#### Trigger Annotation
```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inject-auth-sidecar
spec:
rules:
- name: inject-sidecar
match:
any:
- resources:
kinds:
- Pod
preconditions:
all:
- key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth\" || '' }}"
operator: Equals
value: "true"
mutate:
patchStrategicMerge:
spec:
containers:
- name: auth-proxy
image: oauth2-proxy/oauth2-proxy:latest
# ... additional configuration
policies.forteapps.io/auth: "true"
```
#### Authentication Modes
**Token Mode** (default):
```yaml
# Annotations
policies.forteapps.io/auth: "true"
policies.forteapps.io/auth-type: "token"
policies.forteapps.io/auth-token-secret-name: "auth-tokens"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
# Optional customization
policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory"
policies.forteapps.io/auth-image-version: "latest"
```
**OIDC Mode**:
```yaml
# Annotations (required)
policies.forteapps.io/auth: "true"
policies.forteapps.io/auth-type: "oidc"
policies.forteapps.io/auth-oidc-authority: "https://auth.example.com/realms/master"
policies.forteapps.io/auth-oidc-client-id: "myapp"
# Optional annotations
policies.forteapps.io/auth-oidc-callback-path: "/auth/callback"
policies.forteapps.io/auth-oidc-scopes: "openid,profile,email"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory"
policies.forteapps.io/auth-image-version: "latest"
```
#### Sidecar Container Specification
**Token Mode**:
```yaml
name: authn
image: ghcr.io/snothub/stunning-memory:latest
ports:
- containerPort: 8080
name: auth
protocol: TCP
env:
- name: AUTH_MODE
value: "token"
- name: AUTH_LISTEN_ADDR
value: ":8080"
- name: AUTH_UPSTREAM_URL
value: "http://localhost:3000"
- name: AUTH_TOKEN_FILE
value: "/etc/auth/tokens"
volumeMounts:
- name: auth-tokens
mountPath: /etc/auth
readOnly: true
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 50m
memory: 64Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
```
**OIDC Mode**:
```yaml
name: authn
image: ghcr.io/snothub/stunning-memory:latest
ports:
- containerPort: 8080
name: auth
protocol: TCP
env:
- name: AUTH_MODE
value: "oidc"
- name: AUTH_LISTEN_ADDR
value: ":8080"
- name: AUTH_UPSTREAM_URL
value: "http://localhost:3000"
- name: AUTH_OIDC_AUTHORITY
value: "https://auth.example.com/realms/master"
- name: AUTH_OIDC_CLIENT_ID
value: "myapp"
- name: AUTH_OIDC_CALLBACK_PATH
value: "/auth/callback"
- name: AUTH_OIDC_SCOPES
value: "openid,profile,email"
- name: AUTH_OIDC_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: auth-oidc
key: cookie-secret
- name: AUTH_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: auth-oidc
key: client-secret
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 50m
memory: 64Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
```
#### Generated Resources
**Secret (Token Mode)**:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: auth-tokens
namespace: <app-namespace>
labels:
app.kubernetes.io/managed-by: kyverno
app.kubernetes.io/created-by: inject-auth-sidecar
type: Opaque
data: {} # Populated by Helm chart
```
**Secret (OIDC Mode)**:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: auth-oidc
namespace: <app-namespace>
labels:
app.kubernetes.io/managed-by: kyverno
app.kubernetes.io/created-by: inject-auth-sidecar
type: Opaque
data:
client-secret: <base64>
cookie-secret: <base64>
```
**NetworkPolicy**:
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: <pod-name>-auth-ingress
namespace: <app-namespace>
labels:
app.kubernetes.io/managed-by: kyverno
app.kubernetes.io/created-by: inject-auth-sidecar
spec:
podSelector:
matchLabels: <pod-labels>
policyTypes:
- Ingress
ingress:
- ports:
- port: 8080
protocol: TCP
```
#### Excluded Namespaces
The policy does NOT apply to:
- `kube-system`
- `kyverno`
- `argocd`
- `cert-manager`
- `monitoring`
#### Health Checks
```yaml
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
```
#### Request Flow
```
External Request → Traefik
Service (port 8080)
Pod: Auth Sidecar (port 8080)
├─ Validate credentials
│ • Token mode: Check Bearer token
│ • OIDC mode: Validate session or redirect to IdP
Forward to Application (localhost:3000)
Application processes request
```
**See**: [Developer Guide - Enabling Authentication](DEVELOPER-GUIDE.md#enabling-authentication-for-applications) for usage examples.
---
## Configuration Reference