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
|
# https://www.keycloak.org/operator/installation
|
||||||
|
|
||||||
export KEYCLOAK_NAMESPACE := env("KEYCLOAK_NAMESPACE", "keycloak")
|
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_REALM := env("KEYCLOAK_REALM", "")
|
||||||
export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "")
|
export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "")
|
||||||
export K8S_OIDC_CLIENT_ID := env('K8S_OIDC_CLIENT_ID', "k8s")
|
export K8S_OIDC_CLIENT_ID := env('K8S_OIDC_CLIENT_ID', "k8s")
|
||||||
@@ -108,6 +108,12 @@ install-operator:
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
just create-namespace
|
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..."
|
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/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
|
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 }}
|
namespace: {{ .Env.KEYCLOAK_NAMESPACE }}
|
||||||
spec:
|
spec:
|
||||||
instances: 1
|
instances: 1
|
||||||
image: quay.io/keycloak/keycloak:26.3.4
|
image: quay.io/keycloak/keycloak:26.4
|
||||||
startOptimized: false
|
startOptimized: false
|
||||||
|
|
||||||
# Database configuration for external PostgreSQL
|
# Database configuration for external PostgreSQL
|
||||||
@@ -37,34 +37,64 @@ spec:
|
|||||||
proxy:
|
proxy:
|
||||||
headers: xforwarded
|
headers: xforwarded
|
||||||
|
|
||||||
# Additional options and admin configuration
|
# http-enabled and hostname-strict are configured via http.httpEnabled and hostname.strict
|
||||||
additionalOptions:
|
additionalOptions:
|
||||||
- name: http-enabled
|
|
||||||
value: "true"
|
|
||||||
- name: hostname-strict
|
|
||||||
value: "false"
|
|
||||||
- name: hostname-strict-https
|
|
||||||
value: "false"
|
|
||||||
- name: proxy
|
|
||||||
value: edge
|
|
||||||
- name: metrics-enabled
|
- name: metrics-enabled
|
||||||
value: "true"
|
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
|
# Bootstrap admin configuration
|
||||||
bootstrapAdmin:
|
bootstrapAdmin:
|
||||||
user:
|
user:
|
||||||
secret: keycloak-bootstrap-admin
|
secret: keycloak-bootstrap-admin
|
||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
|
# Increased memory limit to 3Gi for Keycloak 26.4 build process
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: "1.5Gi"
|
memory: "2Gi"
|
||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
limits:
|
limits:
|
||||||
memory: "2Gi"
|
memory: "3Gi"
|
||||||
cpu: "1000m"
|
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:
|
ingress:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
@@ -95,4 +125,3 @@ spec:
|
|||||||
name: keycloak-service
|
name: keycloak-service
|
||||||
port:
|
port:
|
||||||
number: 8080
|
number: 8080
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user