Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,4 @@ local-env-teardown: ## Tear down the local Kind cluster

# Include build configuration files
-include build/*.mk
-include build/openshift/*.mk
185 changes: 185 additions & 0 deletions build/openshift/keycloak-acm.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Keycloak ACM Integration for OpenShift
#
# This file contains targets for setting up Keycloak with V1 token exchange
# for ACM multi-cluster environments on OpenShift.
#
# Prerequisites:
# - OpenShift 4.19+ or 4.20+ cluster
# - ACM installed
# - Cluster-admin access
#
# Initial Setup (Hub Only):
# make keycloak-acm-setup-hub # Deploy Keycloak and configure hub realm
# make keycloak-acm-generate-toml # Generate MCP server configuration
#
# Environment Variables:
# HUB_KUBECONFIG - Path to hub cluster kubeconfig (default: $KUBECONFIG)
# KEYCLOAK_URL - Keycloak URL (auto-detected from route if not set)
# ADMIN_USER - Keycloak admin username (default: admin)
# ADMIN_PASSWORD - Keycloak admin password (default: admin)

##@ Keycloak ACM Integration

.PHONY: keycloak-acm-setup-hub
keycloak-acm-setup-hub: ## Deploy Keycloak on OpenShift with V1 token exchange for ACM hub
@echo "==========================================="
@echo "Keycloak ACM Hub Setup"
@echo "==========================================="
@echo ""
@echo "This will:"
@echo " 1. Enable TechPreviewNoUpgrade feature gate (if needed)"
@echo " 2. Deploy Keycloak with V1 token exchange features"
@echo " 3. Create hub realm with mcp user and clients"
@echo " 4. Configure same-realm token exchange"
@echo " 5. Fix CA trust for cross-realm token exchange"
@echo " 6. Create RBAC for mcp user"
@echo " 7. Save configuration to .keycloak-config/"
@echo ""
@bash ./hack/keycloak-acm/setup-hub.sh
@echo ""
@echo "✅ Hub Keycloak setup complete!"
@echo ""
@echo "Configuration saved to: .keycloak-config/hub-config.env"
@echo ""
@echo "Next steps:"
@echo " 1. Run: make keycloak-acm-generate-toml"
@echo " 2. Start MCP server with: ./kubernetes-mcp-server --config acm-kubeconfig.toml"

.PHONY: keycloak-acm-generate-toml
keycloak-acm-generate-toml: ## Generate _output/acm-kubeconfig.toml from saved Keycloak configuration
@echo "==========================================="
@echo "Generating MCP Server Configuration"
@echo "==========================================="
@echo ""
@bash ./hack/keycloak-acm/generate-toml.sh
@echo ""
@echo "Next: Start MCP server with: ./kubernetes-mcp-server --port 8080 --config _output/acm-kubeconfig.toml"

.PHONY: keycloak-acm-register-managed-cluster
keycloak-acm-register-managed-cluster: ## Register managed cluster with ACM and configure OIDC (requires: CLUSTER_NAME, MANAGED_KUBECONFIG)
@if [ -z "$(CLUSTER_NAME)" ]; then \
echo "Error: CLUSTER_NAME is required"; \
echo "Usage: make keycloak-acm-register-managed-cluster CLUSTER_NAME=my-cluster MANAGED_KUBECONFIG=/path/to/kubeconfig"; \
exit 1; \
fi
@if [ -z "$(MANAGED_KUBECONFIG)" ]; then \
echo "Error: MANAGED_KUBECONFIG is required"; \
echo "Usage: make keycloak-acm-register-managed-cluster CLUSTER_NAME=my-cluster MANAGED_KUBECONFIG=/path/to/kubeconfig"; \
exit 1; \
fi
@if [ ! -f "$(MANAGED_KUBECONFIG)" ]; then \
echo "Error: Kubeconfig file not found: $(MANAGED_KUBECONFIG)"; \
exit 1; \
fi
@echo "==========================================="
@echo "Managed Cluster Setup: $(CLUSTER_NAME)"
@echo "==========================================="
@echo ""
@echo "This will:"
@echo " 1. Create ACM ManagedCluster resource"
@echo " 2. Apply ACM import manifests (starts cluster-proxy agents)"
@echo " 3. Create managed cluster realm in Keycloak"
@echo " 4. Configure cross-realm token exchange"
@echo " 5. Enable TechPreviewNoUpgrade on managed cluster"
@echo " 6. Configure OIDC authentication"
@echo " 7. Create RBAC for service-account-mcp-server"
@echo ""
@echo "⏳ Total time: ~25-30 minutes (rollouts happen in background)"
@echo ""
@HUB_KUBECONFIG="$${HUB_KUBECONFIG:-$$KUBECONFIG}" && \
if [ -z "$$HUB_KUBECONFIG" ]; then \
echo "Error: HUB_KUBECONFIG not set and KUBECONFIG is empty"; \
echo "Either set KUBECONFIG to hub cluster or pass HUB_KUBECONFIG=..."; \
exit 1; \
fi && \
CLUSTER_NAME="$(CLUSTER_NAME)" \
HUB_KUBECONFIG="$$HUB_KUBECONFIG" \
MANAGED_KUBECONFIG="$(MANAGED_KUBECONFIG)" \
bash ./hack/keycloak-acm/register-managed-cluster.sh

.PHONY: keycloak-acm-apply-import-manifests
keycloak-acm-apply-import-manifests: ## [Optional] Re-apply ACM import manifests to managed cluster (already included in registration)
@if [ -z "$(CLUSTER_NAME)" ]; then \
echo "Error: CLUSTER_NAME is required"; \
echo "Usage: make keycloak-acm-apply-import-manifests CLUSTER_NAME=my-cluster MANAGED_KUBECONFIG=/path/to/kubeconfig"; \
exit 1; \
fi
@if [ -z "$(MANAGED_KUBECONFIG)" ]; then \
echo "Error: MANAGED_KUBECONFIG is required"; \
echo "Usage: make keycloak-acm-apply-import-manifests CLUSTER_NAME=my-cluster MANAGED_KUBECONFIG=/path/to/kubeconfig"; \
exit 1; \
fi
@if [ ! -f "$(MANAGED_KUBECONFIG)" ]; then \
echo "Error: Kubeconfig file not found: $(MANAGED_KUBECONFIG)"; \
exit 1; \
fi
@HUB_KUBECONFIG="$${HUB_KUBECONFIG:-$$KUBECONFIG}" && \
if [ -z "$$HUB_KUBECONFIG" ]; then \
echo "Error: HUB_KUBECONFIG not set and KUBECONFIG is empty"; \
echo "Either set KUBECONFIG to hub cluster or pass HUB_KUBECONFIG=..."; \
exit 1; \
fi && \
CLUSTER_NAME="$(CLUSTER_NAME)" \
HUB_KUBECONFIG="$$HUB_KUBECONFIG" \
MANAGED_KUBECONFIG="$(MANAGED_KUBECONFIG)" \
bash ./hack/keycloak-acm/apply-import-manifests.sh

.PHONY: keycloak-acm-status
keycloak-acm-status: ## Show Keycloak ACM configuration status
@echo "==========================================="
@echo "Keycloak ACM Configuration Status"
@echo "==========================================="
@echo ""
@if [ -f .keycloak-config/hub-config.env ]; then \
source .keycloak-config/hub-config.env; \
POD_STATUS=$$(kubectl get pods -n keycloak -l app=keycloak -o jsonpath='{.items[0].metadata.name} ({.items[0].status.phase})' 2>/dev/null || echo "No pod found"); \
echo "Pod: $$POD_STATUS"; \
echo ""; \
KEYCLOAK_ROUTE=$$(kubectl get route keycloak -n keycloak -o jsonpath='{.spec.host}' 2>/dev/null); \
if [ -n "$$KEYCLOAK_ROUTE" ]; then \
echo "Route: https://$$KEYCLOAK_ROUTE"; \
else \
echo "Route: $$KEYCLOAK_URL"; \
fi; \
echo ""; \
echo "Admin Console:"; \
echo " URL: $$KEYCLOAK_URL/admin"; \
echo " Username: $$ADMIN_USER"; \
echo " Password: $$ADMIN_PASSWORD"; \
echo ""; \
echo "Hub Realm: $$HUB_REALM"; \
echo " MCP User: $$MCP_USERNAME"; \
echo " Client ID: $$CLIENT_ID"; \
echo ""; \
echo "OIDC Endpoints ($$HUB_REALM realm):"; \
echo " Discovery: $$KEYCLOAK_URL/realms/$$HUB_REALM/.well-known/openid-configuration"; \
echo " Token: $$KEYCLOAK_URL/realms/$$HUB_REALM/protocol/openid-connect/token"; \
echo " Authorize: $$KEYCLOAK_URL/realms/$$HUB_REALM/protocol/openid-connect/auth"; \
echo ""; \
else \
echo "❌ Hub configuration not found"; \
echo " Run: make keycloak-acm-setup-hub"; \
fi
@echo ""
@if [ -f .keycloak-config/clusters ]; then \
echo "Managed Clusters:"; \
for cluster_env in .keycloak-config/clusters/*.env; do \
if [ -f "$$cluster_env" ]; then \
source "$$cluster_env"; \
echo " - $$CLUSTER_NAME (realm: $$MANAGED_REALM)"; \
fi; \
done; \
echo ""; \
fi
@if [ -f _output/acm-kubeconfig.toml ]; then \
echo "✅ MCP configuration: _output/acm-kubeconfig.toml"; \
echo ""; \
echo "Configured clusters in TOML:"; \
grep '^\[cluster_provider_configs.acm-kubeconfig.clusters' _output/acm-kubeconfig.toml 2>/dev/null | \
sed 's/\[cluster_provider_configs.acm-kubeconfig.clusters."\(.*\)"\]/ - \1/' || echo " (none)"; \
else \
echo "❌ MCP configuration not found"; \
echo " Run: make keycloak-acm-generate-toml"; \
fi
@echo ""
@echo "==========================================="
195 changes: 195 additions & 0 deletions dev/config/openshift/keycloak/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# ACM Keycloak Declarative Configuration

This directory contains declarative JSON configuration files for setting up Keycloak for ACM (Advanced Cluster Management) multi-realm token exchange.

## Architecture

- **Hub Realm**: Central realm where users authenticate
- **Managed Cluster Realms**: One realm per managed cluster
- **Token Exchange**: V1 token exchange using `subject_issuer` parameter
- Same-realm: `mcp-sts` → `mcp-server` within hub realm
- Cross-realm: Hub realm token → Managed cluster realm token

## Directory Structure

```
dev/acm/config/keycloak/
├── realm/
│ ├── hub-realm-create.json # Hub realm configuration
│ └── managed-realm-create.json # Template for managed cluster realms
├── clients/
│ ├── mcp-server.json # OAuth client (confidential)
│ ├── mcp-client.json # Browser OAuth client (public)
│ └── mcp-sts.json # STS client for token exchange
├── client-scopes/
│ ├── openid.json # OpenID Connect scope
│ └── mcp-server.json # MCP audience scope
├── mappers/
│ ├── mcp-server-audience-mapper.json # Adds mcp-server to aud claim
│ └── sub-claim-mapper.json # Maps user ID to sub claim
├── users/
│ └── mcp.json # Test user (mcp/mcp)
└── identity-providers/
└── hub-realm-idp-template.json # IDP config for cross-realm trust
```

## Configuration Files

### Hub Realm (`realm/hub-realm-create.json`)

- Realm name: `hub`
- User registration: disabled
- Password reset: enabled
- Brute force protection: enabled
- Token lifespans configured for security

### Clients

#### `mcp-server` (Confidential Client)
- Used by MCP server for OAuth authentication
- Direct access grants enabled (password flow)
- Service accounts enabled
- Default scopes: `openid`, `profile`, `email`, `mcp-server`

#### `mcp-client` (Public Client)
- Used by browser-based tools (e.g., MCP Inspector)
- PKCE enabled for security
- Authorization code flow only
- No service accounts

#### `mcp-sts` (STS Client)
- Used for token exchange operations
- Service accounts only (no user login)
- No redirect URIs (not for browser flows)

### Client Scopes

#### `openid`
- Standard OpenID Connect scope
- Provides basic user claims (sub, iss, aud, exp, iat)

#### `mcp-server`
- Custom audience scope
- Adds `mcp-server` to the `aud` claim in access tokens
- Required for token validation

### Protocol Mappers

#### `mcp-server-audience`
- Type: `oidc-audience-mapper`
- Adds `mcp-server` to the audience claim
- Applied to `mcp-server` client scope

#### `sub`
- Type: `oidc-sub-mapper`
- Maps user ID to `sub` claim
- Used for federated identity linking

### Users

#### `mcp` User
- Username: `mcp`
- Password: `mcp`
- Email: `mcp@example.com`
- Full name: MCP User
- Used for testing and development

### Identity Provider

#### Hub Realm IDP Template
- Provider: `oidc` (generic OIDC, not keycloak-oidc)
- Trust email: enabled
- Store token: disabled
- Sync mode: IMPORT (create local users)
- Signature validation: enabled via JWKS URL

## Variable Substitution

JSON templates use `${VARIABLE_NAME}` placeholders that are replaced at runtime:

- `${KEYCLOAK_URL}`: Base Keycloak URL (e.g., `https://keycloak-keycloak.apps.example.com`)
- `${HUB_CLIENT_SECRET}`: Secret for mcp-server client in hub realm
- `${MANAGED_REALM}`: Name of managed cluster realm (e.g., `managed-cluster-one`)

## Usage

These JSON files are applied via the Keycloak Admin REST API using the setup scripts:

1. **Hub Setup**: `hack/acm/acm-keycloak-setup-hub-declarative.sh`
- Creates hub realm
- Creates clients (mcp-server, mcp-client, mcp-sts)
- Creates client scopes (openid, mcp-server)
- Adds protocol mappers
- Creates test user
- Configures same-realm token exchange permissions

2. **Managed Cluster Registration**: `hack/acm/acm-register-managed-cluster-declarative.sh`
- Creates managed cluster realm
- Registers identity provider (hub realm)
- Creates federated user link
- Configures cross-realm token exchange permissions

## Token Exchange Configuration

### Same-Realm Token Exchange (Hub)

Allows `mcp-sts` client to exchange tokens for `mcp-server` audience within the hub realm.

**Steps** (applied by setup script):
1. Enable management permissions on `mcp-server` client
2. Get token-exchange permission ID
3. Create client policy allowing `mcp-sts`
4. Link policy to token-exchange permission

**Test Command**:
```bash
source .keycloak-config/hub-config.env
./hack/acm/test-same-realm-token-exchange.sh
```

### Cross-Realm Token Exchange (Hub → Managed)

Allows exchanging hub realm token for managed cluster realm token.

**Steps** (applied by setup script):
1. Create identity provider in managed realm pointing to hub realm
2. Create federated identity link (hub user → managed user via `sub` claim)
3. Enable fine-grained permissions on IDP
4. Create client policy allowing hub realm's `mcp-sts`
5. Link policy to token-exchange permission on IDP

**Test Command**:
```bash
source .keycloak-config/hub-config.env
source .keycloak-config/clusters/managed-cluster-one.env
./hack/acm/test-cross-realm-token-exchange.sh
```

## Keycloak Admin API Endpoints

Configuration is applied using these endpoints:

- **Realm**: `POST /admin/realms`
- **Clients**: `POST /admin/realms/{realm}/clients`
- **Client Scopes**: `POST /admin/realms/{realm}/client-scopes`
- **Protocol Mappers**: `POST /admin/realms/{realm}/client-scopes/{scope-id}/protocol-mappers/models`
- **Users**: `POST /admin/realms/{realm}/users`
- **Identity Providers**: `POST /admin/realms/{realm}/identity-provider/instances`
- **Client Permissions**: `PUT /admin/realms/{realm}/clients/{client-id}/management/permissions`
- **Authorization Policies**: `POST /admin/realms/{realm}/clients/{client-id}/authz/resource-server/policy/client`

## Benefits of Declarative Approach

1. **Version Control**: Configuration as code
2. **Repeatability**: Same configuration every time
3. **Testability**: Easy to test in different environments
4. **Documentation**: Self-documenting via JSON structure
5. **Validation**: JSON schema validation possible
6. **Idempotency**: Can reapply without side effects
7. **Debugging**: Easy to compare configurations

## References

- Keycloak Admin REST API: https://www.keycloak.org/docs-api/26.0/rest-api/index.html
- Token Exchange: https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange
- Identity Brokering: https://www.keycloak.org/docs/latest/server_admin/#_identity_broker
9 changes: 9 additions & 0 deletions dev/config/openshift/keycloak/client-scopes/mcp-server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "mcp-server",
"description": "MCP Server audience scope",
"protocol": "openid-connect",
"attributes": {
"display.on.consent.screen": "false",
"include.in.token.scope": "true"
}
}
Loading