chore(keycloak): upgrade and set pod security standards
This commit is contained in:
336
keycloak/README.md
Normal file
336
keycloak/README.md
Normal file
@@ -0,0 +1,336 @@
|
||||
# Keycloak
|
||||
|
||||
Identity and Access Management (IAM) solution for Kubernetes:
|
||||
|
||||
- **Keycloak Operator**: Manages Keycloak instances via CRDs
|
||||
- **Keycloak**: Open Source Identity and Access Management
|
||||
- **PostgreSQL Integration**: Uses CloudNativePG cluster for persistence
|
||||
- **OIDC Provider**: Centralized authentication for all services
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Kubernetes cluster (k3s)
|
||||
- PostgreSQL (CloudNativePG)
|
||||
- External Secrets Operator (optional, for Vault integration)
|
||||
- Vault (optional, for credential storage)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
just keycloak::install
|
||||
```
|
||||
|
||||
You will be prompted for:
|
||||
|
||||
1. **Keycloak admin username**: Default is `admin`
|
||||
2. **Keycloak admin password**: Auto-generated if not provided
|
||||
3. **Keycloak host (FQDN)**: e.g., `auth.example.com`
|
||||
|
||||
### What Gets Installed
|
||||
|
||||
1. Keycloak Operator and CRDs
|
||||
2. Keycloak instance with PostgreSQL backend
|
||||
3. Ingress for external access
|
||||
4. Admin credentials stored in Kubernetes Secret (and optionally Vault)
|
||||
|
||||
The stack uses the official [Keycloak Operator](https://www.keycloak.org/operator/installation).
|
||||
|
||||
## Pod Security Standards
|
||||
|
||||
The keycloak namespace uses **baseline** Pod Security Standard enforcement.
|
||||
|
||||
```bash
|
||||
pod-security.kubernetes.io/enforce=baseline
|
||||
```
|
||||
|
||||
### Why Baseline Instead of Restricted?
|
||||
|
||||
The **Keycloak Operator** (provided by upstream) does not meet the `restricted` Pod Security Standard requirements:
|
||||
|
||||
- Missing `allowPrivilegeEscalation: false`
|
||||
- Missing `securityContext.capabilities.drop: [ALL]`
|
||||
- Missing `runAsNonRoot: true`
|
||||
- Missing `seccompProfile`
|
||||
|
||||
Since the Operator is deployed via official manifests from GitHub, we cannot modify its security context without maintaining a custom fork.
|
||||
|
||||
### Security Measures
|
||||
|
||||
While using baseline enforcement at the namespace level, the **Keycloak application Pod** applies restricted-level security contexts via `unsupported.podTemplate`:
|
||||
|
||||
**Pod Security Context**:
|
||||
|
||||
- `runAsNonRoot: true`
|
||||
- `runAsUser: 1000`
|
||||
- `runAsGroup: 1000`
|
||||
- `fsGroup: 1000`
|
||||
- `seccompProfile.type: RuntimeDefault`
|
||||
|
||||
**Container Security Context**:
|
||||
|
||||
- `allowPrivilegeEscalation: false`
|
||||
- `capabilities.drop: [ALL]`
|
||||
- `runAsNonRoot: true`
|
||||
- `seccompProfile.type: RuntimeDefault`
|
||||
|
||||
**Note**: `readOnlyRootFilesystem: false` is required because Keycloak needs to write configuration files and cache data.
|
||||
|
||||
### Baseline vs Restricted
|
||||
|
||||
**Baseline** still provides strong security:
|
||||
|
||||
- Prohibits privileged containers
|
||||
- Prohibits `hostNetwork`, `hostPID`, `hostIPC`
|
||||
- Prohibits `hostPath` volumes
|
||||
- Restricts dangerous capabilities
|
||||
|
||||
The primary difference is that baseline does not enforce seccomp profiles and capability drops, which the Operator lacks but the Keycloak Pod implements.
|
||||
|
||||
## Access
|
||||
|
||||
Access Keycloak at `https://your-keycloak-host/`
|
||||
|
||||
**Admin Credentials**:
|
||||
|
||||
- Username: Retrieved via `just keycloak::admin-username`
|
||||
- Password: Retrieved via `just keycloak::admin-password`
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables (set in `.env.local` or override):
|
||||
|
||||
```bash
|
||||
KEYCLOAK_NAMESPACE=keycloak # Kubernetes namespace
|
||||
KEYCLOAK_OPERATOR_VERSION=26.4.5 # Keycloak Operator version
|
||||
KEYCLOAK_REALM= # Default realm name
|
||||
KEYCLOAK_HOST= # Keycloak FQDN
|
||||
KEYCLOAK_ADMIN_USER= # Admin username
|
||||
KEYCLOAK_ADMIN_PASSWORD= # Admin password
|
||||
```
|
||||
|
||||
## Realm Management
|
||||
|
||||
### Create Realm
|
||||
|
||||
```bash
|
||||
just keycloak::create-realm
|
||||
```
|
||||
|
||||
You will be prompted for the realm name. This creates a new realm for your applications.
|
||||
|
||||
### Delete Realm
|
||||
|
||||
```bash
|
||||
just keycloak::delete-realm <realm-name>
|
||||
```
|
||||
|
||||
## User Management
|
||||
|
||||
### Create User
|
||||
|
||||
```bash
|
||||
just keycloak::create-user
|
||||
```
|
||||
|
||||
Interactive prompts for:
|
||||
|
||||
- Username
|
||||
- Email
|
||||
- First name
|
||||
- Last name
|
||||
- Password
|
||||
- Realm
|
||||
|
||||
### Delete User
|
||||
|
||||
```bash
|
||||
just keycloak::delete-user <username>
|
||||
```
|
||||
|
||||
### Add User to Group
|
||||
|
||||
```bash
|
||||
just keycloak::add-user-to-group <username> <group>
|
||||
```
|
||||
|
||||
### List Users
|
||||
|
||||
```bash
|
||||
just keycloak::list-users
|
||||
```
|
||||
|
||||
## Client Management
|
||||
|
||||
### Create OIDC Client
|
||||
|
||||
```bash
|
||||
just keycloak::create-client realm=<realm> client_id=<client-id> redirect_url=<url> client_secret=<secret>
|
||||
```
|
||||
|
||||
This creates a confidential OIDC client with the specified settings.
|
||||
|
||||
**For public clients** (e.g., browser-based apps):
|
||||
|
||||
```bash
|
||||
just keycloak::create-public-client realm=<realm> client_id=<client-id> redirect_url=<url>
|
||||
```
|
||||
|
||||
### Delete Client
|
||||
|
||||
```bash
|
||||
just keycloak::delete-client <realm> <client-id>
|
||||
```
|
||||
|
||||
### List Clients
|
||||
|
||||
```bash
|
||||
just keycloak::list-clients
|
||||
```
|
||||
|
||||
## Group Management
|
||||
|
||||
### Create Group
|
||||
|
||||
```bash
|
||||
just keycloak::create-group <group-name>
|
||||
```
|
||||
|
||||
### Delete Group
|
||||
|
||||
```bash
|
||||
just keycloak::delete-group <group-name>
|
||||
```
|
||||
|
||||
### List Groups
|
||||
|
||||
```bash
|
||||
just keycloak::list-groups
|
||||
```
|
||||
|
||||
## Common Integration Patterns
|
||||
|
||||
### OIDC Authentication for Web Applications
|
||||
|
||||
1. **Create OIDC client**:
|
||||
|
||||
```bash
|
||||
just keycloak::create-client \
|
||||
realm=myrealm \
|
||||
client_id=myapp \
|
||||
redirect_url=https://myapp.example.com/callback \
|
||||
client_secret=$(just utils::random-password)
|
||||
```
|
||||
|
||||
2. **Configure your application**:
|
||||
- OIDC Discovery URL: `https://your-keycloak-host/realms/myrealm`
|
||||
- Client ID: `myapp`
|
||||
- Client Secret: (from step 1)
|
||||
- Redirect URI: `https://myapp.example.com/callback`
|
||||
|
||||
3. **Create user groups for authorization**:
|
||||
|
||||
```bash
|
||||
just keycloak::create-group myapp-admins
|
||||
just keycloak::create-group myapp-users
|
||||
```
|
||||
|
||||
4. **Add users to groups**:
|
||||
|
||||
```bash
|
||||
just keycloak::add-user-to-group alice myapp-admins
|
||||
just keycloak::add-user-to-group bob myapp-users
|
||||
```
|
||||
|
||||
### JWT Token Validation
|
||||
|
||||
Applications can validate JWT tokens issued by Keycloak using the public keys from:
|
||||
|
||||
```
|
||||
https://your-keycloak-host/realms/myrealm/protocol/openid-connect/certs
|
||||
```
|
||||
|
||||
### Kubernetes OIDC Authentication
|
||||
|
||||
To enable OIDC authentication for Kubernetes API:
|
||||
|
||||
```bash
|
||||
just k8s::setup-oidc-auth
|
||||
```
|
||||
|
||||
This configures k3s to use Keycloak for user authentication.
|
||||
|
||||
## Monitoring
|
||||
|
||||
Enable Prometheus monitoring for Keycloak:
|
||||
|
||||
```bash
|
||||
just keycloak::enable-monitoring
|
||||
```
|
||||
|
||||
This creates a ServiceMonitor that scrapes metrics from Keycloak's management port (9000).
|
||||
|
||||
Metrics are automatically renamed from `vendor_*` to `keycloak_*` for better discoverability.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Check Keycloak Pod Status
|
||||
|
||||
```bash
|
||||
kubectl get pods -n keycloak
|
||||
```
|
||||
|
||||
### View Keycloak Logs
|
||||
|
||||
```bash
|
||||
kubectl logs -n keycloak keycloak-0
|
||||
```
|
||||
|
||||
### Check Database Connection
|
||||
|
||||
Verify PostgreSQL connection:
|
||||
|
||||
```bash
|
||||
kubectl get secret database-config -n keycloak -o yaml
|
||||
```
|
||||
|
||||
### Reset Admin Password
|
||||
|
||||
```bash
|
||||
# Delete existing credentials
|
||||
kubectl delete secret keycloak-credentials -n keycloak
|
||||
kubectl delete secret keycloak-bootstrap-admin -n keycloak
|
||||
|
||||
# Recreate with new password
|
||||
just keycloak::create-credentials
|
||||
```
|
||||
|
||||
## Management
|
||||
|
||||
### Uninstall Keycloak Instance
|
||||
|
||||
```bash
|
||||
just keycloak::uninstall
|
||||
```
|
||||
|
||||
This removes the Keycloak instance but keeps the Operator installed.
|
||||
|
||||
To keep the database:
|
||||
|
||||
```bash
|
||||
just keycloak::uninstall delete-db=false
|
||||
```
|
||||
|
||||
### Uninstall Keycloak Operator
|
||||
|
||||
```bash
|
||||
just keycloak::uninstall-operator
|
||||
```
|
||||
|
||||
This removes the Operator and all CRDs.
|
||||
|
||||
## References
|
||||
|
||||
- [Keycloak Documentation](https://www.keycloak.org/documentation)
|
||||
- [Keycloak Operator](https://www.keycloak.org/operator/installation)
|
||||
- [Keycloak Admin REST API](https://www.keycloak.org/docs-api/latest/rest-api/)
|
||||
- [OpenID Connect](https://openid.net/connect/)
|
||||
@@ -4,7 +4,7 @@ set fallback := true
|
||||
# https://www.keycloak.org/operator/installation
|
||||
|
||||
export KEYCLOAK_NAMESPACE := env("KEYCLOAK_NAMESPACE", "keycloak")
|
||||
export KEYCLOAK_OPERATOR_VERSION := env("KEYCLOAK_OPERATOR_VERSION", "26.3.4")
|
||||
export KEYCLOAK_OPERATOR_VERSION := env("KEYCLOAK_OPERATOR_VERSION", "26.4.5")
|
||||
export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "")
|
||||
export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "")
|
||||
export K8S_OIDC_CLIENT_ID := env('K8S_OIDC_CLIENT_ID', "k8s")
|
||||
@@ -108,6 +108,12 @@ install-operator:
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
just create-namespace
|
||||
|
||||
# Using 'baseline' instead of 'restricted' because Keycloak Operator does not meet
|
||||
# restricted requirements
|
||||
kubectl label namespace ${KEYCLOAK_NAMESPACE} \
|
||||
pod-security.kubernetes.io/enforce=baseline --overwrite
|
||||
|
||||
echo "Installing Keycloak Operator CRDs..."
|
||||
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
|
||||
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
|
||||
|
||||
@@ -5,7 +5,7 @@ metadata:
|
||||
namespace: {{ .Env.KEYCLOAK_NAMESPACE }}
|
||||
spec:
|
||||
instances: 1
|
||||
image: quay.io/keycloak/keycloak:26.3.4
|
||||
image: quay.io/keycloak/keycloak:26.4
|
||||
startOptimized: false
|
||||
|
||||
# Database configuration for external PostgreSQL
|
||||
@@ -37,34 +37,64 @@ spec:
|
||||
proxy:
|
||||
headers: xforwarded
|
||||
|
||||
# Additional options and admin configuration
|
||||
# http-enabled and hostname-strict are configured via http.httpEnabled and hostname.strict
|
||||
additionalOptions:
|
||||
- name: http-enabled
|
||||
value: "true"
|
||||
- name: hostname-strict
|
||||
value: "false"
|
||||
- name: hostname-strict-https
|
||||
value: "false"
|
||||
- name: proxy
|
||||
value: edge
|
||||
- name: metrics-enabled
|
||||
value: "true"
|
||||
|
||||
# Keycloak takes ~20 seconds to start, so we configure probes accordingly
|
||||
# Note: Keycloak Operator v2alpha1 only supports periodSeconds and failureThreshold
|
||||
startupProbe:
|
||||
periodSeconds: 10
|
||||
failureThreshold: 20
|
||||
|
||||
livenessProbe:
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
|
||||
readinessProbe:
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
# Bootstrap admin configuration
|
||||
bootstrapAdmin:
|
||||
user:
|
||||
secret: keycloak-bootstrap-admin
|
||||
|
||||
# Resources
|
||||
# Increased memory limit to 3Gi for Keycloak 26.4 build process
|
||||
resources:
|
||||
requests:
|
||||
memory: "1.5Gi"
|
||||
memory: "2Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
memory: "3Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
# Ingress configuration (disabled - using separate Ingress resource)
|
||||
unsupported:
|
||||
podTemplate:
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: keycloak
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
@@ -95,4 +125,3 @@ spec:
|
||||
name: keycloak-service
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
|
||||
Reference in New Issue
Block a user