O8 Cluster Identity Management (CIM) with Keycloak
Overview
Orchestr8's Cluster Identity Management provides enterprise customers with secure, browser-based access to their private data and resources in their own VPCs, using Keycloak as the central identity broker.
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Customer Browser │
└────────────────────────┬────────────────────────────────────────────┘
│ HTTPS
▼
┌─────────────────────────────────────────────────────────────────────┐
│ O8 Platform (Public) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Istio Ingress Gateway │ │
│ │ (TLS Termination, WAF, DDoS) │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────────┐ │
│ │ OAuth2 Proxy │ │
│ │ (Session Management, PKCE Flow) │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────────┐ │
│ │ Keycloak │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Identity Providers │ │ │
│ │ │ • SAML 2.0 (Okta, Azure AD, OneLogin) │ │ │
│ │ │ • OIDC (Google, GitHub, GitLab) │ │ │
│ │ │ • LDAP/AD (Enterprise Directory) │ │ │
│ │ │ • WebAuthn/FIDO2 (Passwordless) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Customer Realms │ │ │
│ │ │ • customer-a.example.com │ │ │
│ │ │ • customer-b.example.com │ │ │
│ │ │ • Multi-tenant isolation │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────────┐ │
│ │ O8 Identity Bridge Service │ │
│ │ (Token Exchange, VPC Connection Manager) │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
└─────────────────────────┼───────────────────────────────────────────┘
│ Secure Tunnel (WireGuard/Tailscale)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Customer VPC (Private) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ O8 Edge Connector │ │
│ │ (Runs in customer's Kubernetes) │ │
│ │ • Token Validation │ │
│ │ • RBAC Enforcement │ │
│ │ • Audit Logging │ │
│ │ • Secret Injection │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────────┐ │
│ │ Customer Private Resources │ │
│ │ • Databases (RDS, CloudSQL, Azure DB) │ │
│ │ • Object Storage (S3, GCS, Blob) │ │
│ │ • Kubernetes Clusters │ │
│ │ • Internal APIs │ │
│ │ • Analytics Platforms │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Implementation
1. Keycloak Realm Configuration
# orchestr8-cli/src/orchestr8_orchestrator/commands/cim.py
"""Cluster Identity Management commands for O8"""
import typer
from typing import Optional
from keycloak import KeycloakAdmin, KeycloakOpenIDConnection
from ..core.config import Config
cim = typer.Typer(help="Cluster Identity Management with Keycloak")
class KeycloakCIM:
"""Manage customer identity and access through Keycloak"""
def __init__(self, config: Config):
self.config = config
self.keycloak_connection = KeycloakOpenIDConnection(
server_url=config.keycloak_url,
username=config.keycloak_admin,
password=config.keycloak_password,
realm_name="master",
verify=True
)
self.keycloak_admin = KeycloakAdmin(connection=self.keycloak_connection)
def create_customer_realm(self, customer_id: str, domain: str):
"""Create isolated realm for customer"""
realm_config = {
"id": f"customer-{customer_id}",
"realm": f"customer-{customer_id}",
"displayName": f"{domain} Portal",
"enabled": True,
"sslRequired": "all",
"registrationAllowed": False,
"loginWithEmailAllowed": True,
"duplicateEmailsAllowed": False,
"resetPasswordAllowed": True,
"editUsernameAllowed": False,
"bruteForceProtected": True,
"permanentLockout": False,
"maxFailureWaitSeconds": 900,
"minimumQuickLoginWaitSeconds": 60,
"waitIncrementSeconds": 60,
"quickLoginCheckMilliSeconds": 1000,
"maxDeltaTimeSeconds": 43200,
"failureFactor": 5,
"defaultSignatureAlgorithm": "RS256",
"offlineSessionMaxLifespanEnabled": False,
"offlineSessionMaxLifespan": 5184000,
"clientSessionIdleTimeout": 1800,
"clientSessionMaxLifespan": 36000,
"accessTokenLifespan": 300,
"accessCodeLifespan": 60,
"accessCodeLifespanUserAction": 300,
"actionTokenGeneratedByAdminLifespan": 43200,
"actionTokenGeneratedByUserLifespan": 300,
"oauth2DeviceCodeLifespan": 600,
"oauth2DevicePollingInterval": 5,
"attributes": {
"customerDomain": domain,
"vpcEndpoint": f"https://vpc-{customer_id}.o8.internal",
"dataResidency": "us-east-1",
"complianceLevel": "SOC2-TYPE2"
}
}
self.keycloak_admin.create_realm(realm_config)
# Configure identity providers
self._setup_identity_providers(f"customer-{customer_id}")
# Create default roles
self._create_default_roles(f"customer-{customer_id}")
# Setup client for VPC access
self._create_vpc_client(f"customer-{customer_id}", customer_id)
def _create_vpc_client(self, realm: str, customer_id: str):
"""Create OAuth2 client for VPC access"""
self.keycloak_admin.realm_name = realm
client_config = {
"clientId": f"vpc-access-{customer_id}",
"name": "VPC Access Portal",
"description": "Secure access to private VPC resources",
"rootUrl": f"https://{customer_id}.o8.platform.io",
"adminUrl": f"https://{customer_id}.o8.platform.io",
"baseUrl": "/",
"surrogateAuthRequired": False,
"enabled": True,
"alwaysDisplayInConsole": False,
"clientAuthenticatorType": "client-secret",
"secret": self._generate_client_secret(),
"redirectUris": [
f"https://{customer_id}.o8.platform.io/*",
"http://localhost:3000/*" # For development
],
"webOrigins": [
f"https://{customer_id}.o8.platform.io",
"http://localhost:3000"
],
"notBefore": 0,
"bearerOnly": False,
"consentRequired": False,
"standardFlowEnabled": True,
"implicitFlowEnabled": False,
"directAccessGrantsEnabled": True,
"serviceAccountsEnabled": True,
"publicClient": False,
"frontchannelLogout": True,
"protocol": "openid-connect",
"attributes": {
"saml.assertion.signature": "false",
"saml.multivalued.roles": "false",
"saml.force.post.binding": "false",
"saml.encrypt": "false",
"oauth2.device.authorization.grant.enabled": "false",
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"backchannel.logout.revoke.offline.tokens": "false",
"display.on.consent.screen": "false",
"oauth2.device.polling.interval": "5",
"use.refresh.tokens": "true",
"token.endpoint.auth.signing.alg": "RS256",
"pkce.code.challenge.method": "S256" # Enforce PKCE
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": False,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"name": "vpc-access-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": False,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "vpcAccess",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "vpc_access",
"jsonType.label": "JSON",
"multivalued": "true"
}
},
{
"name": "customer-id",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": False,
"config": {
"claim.name": "customer_id",
"claim.value": customer_id,
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true"
}
}
],
"defaultClientScopes": [
"web-origins",
"profile",
"roles",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}
self.keycloak_admin.create_client(client_config)
@cim.command()
def setup_customer(
customer_id: str = typer.Argument(..., help="Unique customer identifier"),
domain: str = typer.Option(..., help="Customer domain (e.g., customer.com)"),
identity_provider: str = typer.Option("oidc", help="Identity provider type"),
vpc_regions: str = typer.Option("us-east-1", help="Comma-separated VPC regions")
):
"""Setup cluster identity management for a customer"""
config = Config()
cim_manager = KeycloakCIM(config)
# Create customer realm
cim_manager.create_customer_realm(customer_id, domain)
# Deploy edge connector to customer VPC
deploy_edge_connector(customer_id, vpc_regions.split(","))
console.print(f"[green]✓ Customer {customer_id} setup complete[/green]")
console.print(f"\nAccess URL: https://{customer_id}.o8.platform.io")
console.print(f"Admin Console: https://keycloak.o8.platform.io/admin/{customer_id}")
2. Edge Connector for Customer VPC
# edge-connector/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: orchestr8-edge-connector
namespace: orchestr8-system
spec:
replicas: 2
selector:
matchLabels:
app: orchestr8-edge-connector
template:
metadata:
labels:
app: orchestr8-edge-connector
spec:
serviceAccountName: orchestr8-edge-connector
containers:
- name: connector
image: orchestr8/orchestr8-edge-connector:latest
env:
- name: CUSTOMER_ID
value: "{{ customer_id }}"
- name: KEYCLOAK_URL
value: "https://keycloak.o8.platform.io"
- name: KEYCLOAK_REALM
value: "customer-{{ customer_id }}"
- name: VPC_RESOURCES
value: |
databases:
- type: postgresql
host: prod-db.c9xkjd.us-east-1.rds.amazonaws.com
port: 5432
ssl: required
storage:
- type: s3
bucket: customer-private-data
region: us-east-1
kubernetes:
- cluster: prod-eks
endpoint: https://k8s.internal.vpc
ports:
- containerPort: 8443
name: https
- containerPort: 9090
name: metrics
volumeMounts:
- name: tls-certs
mountPath: /etc/tls
readOnly: true
livenessProbe:
httpGet:
path: /healthz
port: 8443
scheme: HTTPS
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: tls-certs
secret:
secretName: edge-connector-tls
---
apiVersion: v1
kind: Service
metadata:
name: orchestr8-edge-connector
namespace: orchestr8-system
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8443
protocol: TCP
name: https
selector:
app: orchestr8-edge-connector
3. Browser-Based Access Portal
// portal/src/VPCAccessPortal.tsx
import React, { useEffect, useState } from 'react';
import { useKeycloak } from '@react-keycloak/web';
interface VPCResource {
type: 'database' | 'storage' | 'kubernetes' | 'api';
name: string;
endpoint: string;
accessible: boolean;
}
export const VPCAccessPortal: React.FC = () => {
const { keycloak, initialized } = useKeycloak();
const [resources, setResources] = useState<VPCResource[]>([]);
const [selectedResource, setSelectedResource] = useState<VPCResource | null>(null);
useEffect(() => {
if (initialized && keycloak.authenticated) {
fetchVPCResources();
}
}, [initialized, keycloak.authenticated]);
const fetchVPCResources = async () => {
const token = keycloak.token;
const customerId = keycloak.tokenParsed?.customer_id;
const response = await fetch(`/api/vpc/${customerId}/resources`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
const data = await response.json();
setResources(data.resources);
};
const accessResource = async (resource: VPCResource) => {
// Generate temporary access credentials
const response = await fetch(`/api/vpc/access`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${keycloak.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
resourceId: resource.name,
duration: 3600 // 1 hour
})
});
const { accessUrl, credentials } = await response.json();
// Open secure terminal or data browser
if (resource.type === 'database') {
openDatabaseBrowser(accessUrl, credentials);
} else if (resource.type === 'kubernetes') {
openKubernetesConsole(accessUrl, credentials);
}
};
return (
<div className="vpc-portal">
<h1>Private VPC Resources</h1>
<div className="resource-grid">
{resources.map(resource => (
<div key={resource.name} className="resource-card">
<h3>{resource.name}</h3>
<p>Type: {resource.type}</p>
<p>Status: {resource.accessible ? '🟢 Available' : '🔴 Unavailable'}</p>
<button
onClick={() => accessResource(resource)}
disabled={!resource.accessible}
>
Access Resource
</button>
</div>
))}
</div>
{selectedResource && (
<SecureResourceViewer
resource={selectedResource}
token={keycloak.token}
/>
)}
</div>
);
};
4. Security Features
# security-policies.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: vpc-access-policy
namespace: orchestr8-system
spec:
selector:
matchLabels:
app: orchestr8-edge-connector
rules:
- from:
- source:
requestPrincipals: ["cluster.local/ns/orchestr8-system/sa/oauth2-proxy"]
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/vpc/*"]
when:
- key: request.auth.claims[customer_id]
values: ["*"]
- key: request.auth.claims[vpc_access]
values: ["true"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vpc-access-isolation
namespace: orchestr8-system
spec:
podSelector:
matchLabels:
app: orchestr8-edge-connector
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: istio-system
ports:
- protocol: TCP
port: 8443
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
- to:
- podSelector:
matchLabels:
app: customer-database
ports:
- protocol: TCP
port: 5432
Benefits
- Zero Trust Security: Every access is authenticated and authorized
- Browser-Based: No VPN client needed, works from anywhere
- Multi-Tenant: Complete isolation between customers
- Compliance Ready: Full audit trails, data residency control
- SSO Integration: Works with existing enterprise identity providers
- Fine-Grained Access: Role-based access to specific resources
- Temporary Credentials: Time-limited access with automatic revocation
- End-to-End Encryption: TLS from browser to private resources
Customer Onboarding Flow
# 1. Register customer
o8 cim register --customer acme-corp --domain acme.com
# 2. Configure identity provider
o8 cim configure-idp --customer acme-corp --type saml --metadata https://acme.okta.com/metadata
# 3. Setup VPC connection
o8 cim connect-vpc --customer acme-corp --vpc-id vpc-12345 --region us-east-1
# 4. Define access policies
o8 cim set-policy --customer acme-corp --resource database --roles "developer,analyst"
# 5. Generate access portal
o8 cim generate-portal --customer acme-corp --url https://acme.o8.platform.io
This provides enterprise customers with secure, convenient access to their private resources without exposing them to the public internet!