diff --git a/.gitignore b/.gitignore index 1e9c543..cf8cd39 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ Session.vim .envrc .direnv my-secrets.yaml + +**/.helm_ls_cache/** diff --git a/charts/auth-service/Chart.yaml b/charts/auth-service/Chart.yaml index 70a078d..854eea0 100644 --- a/charts/auth-service/Chart.yaml +++ b/charts/auth-service/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: auth-service description: EQTYLab Auth Service - Authentication and authorization service for the Governance Platform type: application -version: 1.0.0 -appVersion: "1.0.0" +version: 1.0.4 +appVersion: "1.0.2" keywords: - auth - authentication diff --git a/charts/auth-service/templates/deployment.yaml b/charts/auth-service/templates/deployment.yaml index 6cd08e9..1062ccb 100644 --- a/charts/auth-service/templates/deployment.yaml +++ b/charts/auth-service/templates/deployment.yaml @@ -169,6 +169,14 @@ spec: - name: IDP_KEYCLOAK_SERVICE_ACCOUNT_CLIENT_SECRET value: {{ .Values.config.idp.keycloak.serviceAccountClientSecret | quote }} {{- end }} + {{- if .Values.config.idp.keycloak.enableUserManagement }} + - name: IDP_KEYCLOAK_ENABLE_USER_MANAGEMENT + value: {{ .Values.config.idp.keycloak.enableUserManagement | quote }} + {{- end }} + {{- if .Values.config.idp.keycloak.enableGroupSync }} + - name: IDP_KEYCLOAK_ENABLE_GROUP_SYNC + value: {{ .Values.config.idp.keycloak.enableGroupSync | quote }} + {{- end }} {{- end }} {{- if eq .Values.config.idp.provider "zitadel" }} @@ -276,7 +284,10 @@ spec: {{- if .Values.config.serviceAccounts.governanceWorker.enabled }} - name: SERVICE_ACCOUNT_DEFAULT_NAME value: {{ .Values.config.serviceAccounts.governanceWorker.name | quote }} - # Auth0 M2M credentials for governance-worker + + # M2M credentials for governance-worker + {{- if eq .Values.config.idp.provider "auth0" }} + # Auth0 service account credentials {{- if .Values.config.serviceAccounts.governanceWorker.auth0ClientId }} - name: AUTH0_GOVERNANCE_WORKER_CLIENT_ID value: {{ .Values.config.serviceAccounts.governanceWorker.auth0ClientId | quote }} @@ -302,6 +313,31 @@ spec: value: {{ .Values.config.serviceAccounts.governanceWorker.audience | quote }} {{- end }} {{- end }} + + {{- if eq .Values.config.idp.provider "keycloak" }} + # Keycloak service account credentials + {{- if .Values.config.serviceAccounts.governanceWorker.keycloakClientId }} + - name: KEYCLOAK_GOVERNANCE_WORKER_CLIENT_ID + value: {{ .Values.config.serviceAccounts.governanceWorker.keycloakClientId | quote }} + {{- else if .Values.config.serviceAccounts.existingSecret }} + - name: KEYCLOAK_GOVERNANCE_WORKER_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ include "auth-service.serviceAccountSecretName" . }} + key: {{ .Values.config.serviceAccounts.existingSecretKeys.governanceWorkerClientId | default "governance-worker-client-id" }} + {{- end }} + {{- if .Values.config.serviceAccounts.governanceWorker.keycloakClientSecret }} + - name: KEYCLOAK_GOVERNANCE_WORKER_CLIENT_SECRET + value: {{ .Values.config.serviceAccounts.governanceWorker.keycloakClientSecret | quote }} + {{- else if .Values.config.serviceAccounts.existingSecret }} + - name: KEYCLOAK_GOVERNANCE_WORKER_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "auth-service.serviceAccountSecretName" . }} + key: {{ .Values.config.serviceAccounts.existingSecretKeys.governanceWorkerClientSecret | default "governance-worker-client-secret" }} + {{- end }} + {{- end }} + {{- end }} {{- if .Values.config.serviceAccounts.existingSecret }} - name: SERVICE_ACCOUNT_ENCRYPTION_KEY valueFrom: @@ -320,6 +356,22 @@ spec: value: {{ .Values.metrics.path | quote }} {{- end }} + # Token Exchange configuration + {{- if .Values.config.tokenExchange.enabled }} + - name: AUTH_SERVICE_KEY_ID + value: {{ .Values.config.tokenExchange.keyId | quote }} + - name: AUTH_SERVICE_PRIVATE_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.config.tokenExchange.existingSecret | default (printf "%s-keys" (include "auth-service.fullname" .)) }} + key: {{ .Values.config.tokenExchange.existingSecretKeys.privateKey | default "private-key" }} + {{- end }} + + {{- if .Values.config.authUrl }} + - name: AUTH_SERVICE_URL + value: {{ .Values.config.authUrl | quote }} + {{- end }} + {{- with .Values.extraEnvVars }} {{- toYaml . | nindent 12 }} {{- end }} diff --git a/charts/auth-service/templates/hpa.yaml b/charts/auth-service/templates/hpa.yaml index 87418f9..730939e 100644 --- a/charts/auth-service/templates/hpa.yaml +++ b/charts/auth-service/templates/hpa.yaml @@ -29,4 +29,4 @@ spec: type: Utilization averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/auth-service/templates/ingress.yaml b/charts/auth-service/templates/ingress.yaml index 8f7bd2f..414a564 100644 --- a/charts/auth-service/templates/ingress.yaml +++ b/charts/auth-service/templates/ingress.yaml @@ -58,4 +58,4 @@ spec: {{- end }} {{- end }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/auth-service/templates/migration-job.yaml b/charts/auth-service/templates/migration-job.yaml index 2a0dd20..3af9e87 100644 --- a/charts/auth-service/templates/migration-job.yaml +++ b/charts/auth-service/templates/migration-job.yaml @@ -44,4 +44,4 @@ spec: value: {{ .Values.config.database.sslMode | quote }} - name: DATABASE_MIGRATIONS_PATH value: {{ .Values.config.database.migrationsPath | default "/internal/database/migrations" | quote }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/auth-service/templates/networkpolicy.yaml b/charts/auth-service/templates/networkpolicy.yaml index 2f5c99f..e9fe96d 100644 --- a/charts/auth-service/templates/networkpolicy.yaml +++ b/charts/auth-service/templates/networkpolicy.yaml @@ -20,4 +20,4 @@ spec: egress: {{- toYaml . | nindent 4 }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/auth-service/templates/pdb.yaml b/charts/auth-service/templates/pdb.yaml index 6988a6a..f09e996 100644 --- a/charts/auth-service/templates/pdb.yaml +++ b/charts/auth-service/templates/pdb.yaml @@ -15,4 +15,4 @@ spec: selector: matchLabels: {{- include "auth-service.selectorLabels" . | nindent 6 }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/auth-service/templates/secrets.yaml b/charts/auth-service/templates/secrets.yaml index 343532b..ccbd896 100644 --- a/charts/auth-service/templates/secrets.yaml +++ b/charts/auth-service/templates/secrets.yaml @@ -24,10 +24,24 @@ data: {{- end }} {{- if .Values.config.idp.keycloak.adminPassword }} admin-password: {{ .Values.config.idp.keycloak.adminPassword | b64enc | quote }} - {{- end }} - {{- end }} +{{- end }} +{{- end }} {{- end }} +{{- if .Values.config.tokenExchange.enabled }} +{{- if not .Values.config.tokenExchange.existingSecret }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "auth-service.fullname" . }}-keys + labels: + {{- include "auth-service.labels" . | nindent 4 }} +type: Opaque +data: + private-key: {{ .Values.config.tokenExchange.privateKey | b64enc | quote }} +{{- end }} +{{- end }} {{- if and (not .Values.postgresql.enabled) (not .Values.config.database.existingSecret) }} --- apiVersion: v1 diff --git a/charts/auth-service/templates/service.yaml b/charts/auth-service/templates/service.yaml index 8d5eb31..95cf6c1 100644 --- a/charts/auth-service/templates/service.yaml +++ b/charts/auth-service/templates/service.yaml @@ -22,4 +22,4 @@ spec: name: metrics {{- end }} selector: - {{- include "auth-service.selectorLabels" . | nindent 4 }} + {{- include "auth-service.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/charts/auth-service/templates/serviceaccount.yaml b/charts/auth-service/templates/serviceaccount.yaml index c418069..f44437d 100644 --- a/charts/auth-service/templates/serviceaccount.yaml +++ b/charts/auth-service/templates/serviceaccount.yaml @@ -10,4 +10,4 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} automountServiceAccountToken: {{ .Values.serviceAccount.automount }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/auth-service/templates/servicemonitor.yaml b/charts/auth-service/templates/servicemonitor.yaml index 5f9df41..813892e 100644 --- a/charts/auth-service/templates/servicemonitor.yaml +++ b/charts/auth-service/templates/servicemonitor.yaml @@ -17,4 +17,4 @@ spec: path: {{ .Values.metrics.path }} interval: {{ .Values.metrics.serviceMonitor.interval }} scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/auth-service/values-auth0-with-service-accounts.yaml b/charts/auth-service/values-auth0-with-service-accounts.yaml new file mode 100644 index 0000000..7e3f9e3 --- /dev/null +++ b/charts/auth-service/values-auth0-with-service-accounts.yaml @@ -0,0 +1,59 @@ +# Auth0-specific values with service account configuration +# This file shows how to configure the auth-service with Auth0 and service accounts + +# Auth0 configuration +config: + idp: + provider: "auth0" + issuer: "https://your-tenant.auth0.com/" + clientId: "your-client-id" + clientSecret: "your-client-secret" + redirectUri: "https://auth.yourdomain.com/callback" + + auth0: + domain: "your-tenant.auth0.com" + enableManagementAPI: true + managementClientId: "your-m2m-client-id" + managementClientSecret: "your-m2m-client-secret" + managementAudience: "https://your-tenant.auth0.com/api/v2/" + apiIdentifier: "https://api.yourdomain.com" + defaultConnection: "Username-Password-Authentication" + syncAtStartup: true + syncPageSize: 100 + + # Service Account configuration + serviceAccounts: + autoCreate: true + governanceWorker: + enabled: true + name: "governance-worker" + description: "Automated governance service worker for processing indicator evaluations" + # No organizationId - platform-wide access + scopes: + - "governance:declarations:create" + - "integrity:statements:create" + # Generate a secure encryption key: openssl rand -base64 32 + encryptionKey: "your-base64-encoded-32-byte-key" + + # Security configuration + security: + apiSecret: "your-api-secret" # Used by Auth0 actions + + # Key Vault configuration (for DID keys) + keyVault: + provider: "azure" + azure: + vaultUrl: "https://your-vault.vault.azure.net/" + tenantId: "your-azure-tenant-id" + clientId: "your-azure-client-id" + clientSecret: "your-azure-client-secret" + +# For production, use existing secrets instead of inline values +# config: +# idp: +# existingSecret: "auth0-credentials" +# serviceAccounts: +# existingSecret: "service-account-secrets" +# keyVault: +# azure: +# existingSecret: "azure-keyvault-credentials" \ No newline at end of file diff --git a/charts/auth-service/values-auth0.yaml b/charts/auth-service/values-auth0.yaml new file mode 100644 index 0000000..719c00b --- /dev/null +++ b/charts/auth-service/values-auth0.yaml @@ -0,0 +1,110 @@ +# Auth0 Provider Configuration for SaaS Deployment +# This values file configures the auth-service to use Auth0 as the identity provider + +config: + # CORS is disabled by default as ingress controller handles it + cors: + enabled: false + origins: "*" + + idp: + provider: "auth0" + issuer: "https://your-tenant.auth0.com/" + clientId: "your-auth0-client-id" + clientSecret: "your-auth0-client-secret" + + # Auth0-specific configuration + auth0: + domain: "your-tenant.auth0.com" + managementClientId: "your-management-api-client-id" + managementClientSecret: "your-management-api-client-secret" + managementAudience: "https://your-tenant.auth0.com/api/v2/" + apiIdentifier: "https://api.your-app.com" + defaultConnection: "Username-Password-Authentication" + defaultRoles: ["user"] + sendInvitationEmail: true + + # Use existing secret for production + # existingSecret: "auth-service-auth0-credentials" + # existingSecretKeys: + # clientId: "client-id" + # clientSecret: "client-secret" + # managementClientId: "mgmt-client-id" + # managementClientSecret: "mgmt-client-secret" + + # Database configuration for SaaS + database: + # Use managed database service + host: "your-rds-endpoint.amazonaws.com" + port: 5432 + name: "auth_service" + user: "auth_service_user" + sslMode: "require" + # Use existing secret for credentials + existingSecret: "auth-service-db-credentials" + existingSecretKeys: + password: "password" + + # Key Vault configuration for SaaS + keyVault: + provider: "azure" + azure: + vaultUrl: "https://your-keyvault.vault.azure.net/" + tenantId: "your-azure-tenant-id" + # Use managed identity or existing secret + existingSecret: "auth-service-keyvault-credentials" + existingSecretKeys: + clientId: "client-id" + clientSecret: "client-secret" + +# Production-ready settings for SaaS +replicaCount: 3 + +autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 80 + +resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 250m + memory: 256Mi + +# Enable metrics for monitoring +metrics: + enabled: true + port: 9090 + path: "/metrics" + +# Health checks +healthCheck: + liveness: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + readiness: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 5 + +# Ingress configuration for SaaS +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: auth.your-saas-domain.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: auth-service-tls + hosts: + - auth.your-saas-domain.com diff --git a/charts/auth-service/values-keycloak.yaml b/charts/auth-service/values-keycloak.yaml new file mode 100644 index 0000000..eecea9c --- /dev/null +++ b/charts/auth-service/values-keycloak.yaml @@ -0,0 +1,268 @@ +# Keycloak Provider Configuration for Enterprise/On-Premises Deployment +# This values file configures the auth-service to use Keycloak as the identity provider + +config: + idp: + provider: "keycloak" + + # Keycloak OIDC endpoint - this will be auto-discovered + issuer: "https://keycloak.your-domain.com/realms/governance" + + # Frontend client for user authentication + clientId: "governance-platform-frontend" + clientSecret: "" # Not needed for public client + + # Redirect URI for the auth service + redirectUri: "https://auth.your-domain.com/callback" + + # OIDC scopes + scopes: "openid,profile,email,roles" + + # Keycloak-specific configuration + keycloak: + # Keycloak base URL + realm: "governance" + adminUrl: "https://keycloak.your-domain.com" + + # Service account for admin operations (user management) + # This should be the backend client with service account enabled + serviceAccountClientId: "governance-platform-backend" + serviceAccountClientSecret: "" # Set via existingSecret + + # Feature flags + enableUserManagement: true + enableGroupSync: false # Groups managed in database, not Keycloak + + # Use existing secret for production + existingSecret: "auth-service-keycloak-credentials" + existingSecretKeys: + clientId: "frontend-client-id" + clientSecret: "frontend-client-secret" + managementClientId: "backend-client-id" + managementClientSecret: "backend-client-secret" + + # Service Account configuration for Keycloak + serviceAccounts: + autoCreate: true + governanceWorker: + enabled: true + name: "governance-worker" + description: "Automated governance service worker for processing indicator evaluations" + organizationId: "" # Platform-wide access + scopes: + - "governance:declarations:create" + - "integrity:statements:create" + # Keycloak service account credentials + keycloakClientId: "governance-worker" + keycloakClientSecret: "" # Set via existingSecret + + # Use existing secret for service account credentials + existingSecret: "governance-worker-credentials" + existingSecretKeys: + encryptionKey: "encryption-key" + governanceWorkerClientId: "governance-worker-client-id" + governanceWorkerClientSecret: "governance-worker-client-secret" + + # Database configuration for on-premises + database: + host: "postgresql.your-domain.local" + port: 5432 + name: "auth_service" + user: "auth_service_user" + password: "" # Use existingSecret + sslMode: "require" # Use SSL in production + # Connection pool settings + maxOpenConns: 25 + maxIdleConns: 5 + connMaxLifetime: "5m" + # Use existing secret for credentials + existingSecret: "auth-service-db-credentials" + existingSecretKeys: + password: "password" + + # Key Vault configuration for on-premises + keyVault: + provider: "hashicorp" + cacheTTLMinutes: 15 + hashicorp: + address: "https://vault.your-domain.local:8200" + token: "" # Use existingSecret + # Use existing secret for credentials + existingSecret: "auth-service-vault-credentials" + existingSecretKeys: + token: "token" + + # Security configuration + security: + apiSecret: "" # Use existingSecret + jwtSecret: "" # Use existingSecret + existingSecret: "auth-service-security" + existingSecretKeys: + apiSecret: "api-secret" + jwtSecret: "jwt-secret" + + # Integration URLs + integrations: + integrityServiceUrl: "http://integrity-service:8080" + governanceServiceUrl: "http://governance-service:10001" + + # CORS configuration + cors: + enabled: true + origins: "https://app.your-domain.com,https://app-staging.your-domain.com" + + # Session configuration + session: + secret: "" # Use existingSecret + duration: "24h" + existingSecret: "auth-service-session" + existingSecretKeys: + secret: "session-secret" + +# Conservative settings for on-premises deployment +replicaCount: 2 + +autoscaling: + enabled: false # Manual scaling for on-premises + +resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 200m + memory: 256Mi + +# Enable metrics for monitoring +metrics: + enabled: true + port: 9090 + path: "/metrics" + serviceMonitor: + enabled: true + interval: 30s + labels: + release: prometheus + +# Health checks +healthCheck: + liveness: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + readiness: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 5 + +# Ingress configuration for on-premises +ingress: + enabled: true + className: "nginx" + annotations: + # Use internal CA for on-premises + cert-manager.io/cluster-issuer: "internal-ca" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-buffer-size: "16k" + hosts: + - host: auth.your-domain.local + paths: + - path: / + pathType: Prefix + tls: + - secretName: auth-service-tls + hosts: + - auth.your-domain.local + +# Pod Disruption Budget +podDisruptionBudget: + enabled: true + minAvailable: 1 + +# Network Policy +networkPolicy: + enabled: true + ingress: + - from: + - namespaceSelector: + matchLabels: + name: ingress-nginx + - podSelector: + matchLabels: + app.kubernetes.io/name: governance-service + ports: + - protocol: TCP + port: 8080 + egress: + # Allow DNS + - to: + - namespaceSelector: + matchLabels: + name: kube-system + ports: + - protocol: TCP + port: 53 + - protocol: UDP + port: 53 + # Allow PostgreSQL + - to: + - podSelector: + matchLabels: + app.kubernetes.io/name: postgresql + ports: + - protocol: TCP + port: 5432 + # Allow Keycloak + - to: [] + ports: + - protocol: TCP + port: 443 + - protocol: TCP + port: 8080 + # Allow Vault + - to: + - podSelector: + matchLabels: + app.kubernetes.io/name: vault + ports: + - protocol: TCP + port: 8200 + +# Node selector for on-premises deployment +nodeSelector: + node-role.kubernetes.io/worker: "true" + +# Tolerations for on-premises deployment +tolerations: [] + +# Affinity rules for on-premises deployment +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - auth-service + topologyKey: kubernetes.io/hostname + +# Extra environment variables for Keycloak-specific settings +extraEnvVars: + - name: IDP_KEYCLOAK_ENABLE_USER_MANAGEMENT + value: "true" + - name: IDP_KEYCLOAK_ENABLE_GROUP_SYNC + value: "false" + # Keycloak-specific service account credentials + - name: KEYCLOAK_GOVERNANCE_WORKER_CLIENT_ID + valueFrom: + secretKeyRef: + name: governance-worker-credentials + key: keycloak-governance-worker-client-id + - name: KEYCLOAK_GOVERNANCE_WORKER_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: governance-worker-credentials + key: keycloak-governance-worker-client-secret diff --git a/charts/auth-service/values.yaml b/charts/auth-service/values.yaml index 3bf6ce0..b96af53 100644 --- a/charts/auth-service/values.yaml +++ b/charts/auth-service/values.yaml @@ -130,12 +130,9 @@ config: # Security configuration security: - # API Secret for Auth0 Actions / IDP webhooks - apiSecret: "" # Required - generate a strong secret - # JWT Secret for session tokens (if needed) - jwtSecret: "" # Optional - only if using local JWT generation - # Use existing secret - existingSecret: "" + apiSecret: "" # Use existingSecret + jwtSecret: "" # Use existingSecret + existingSecret: "auth-service-api-secret" existingSecretKeys: apiSecret: "api-secret" jwtSecret: "jwt-secret" @@ -319,6 +316,7 @@ config: # These are required for the service account to authenticate auth0ClientId: "" # AUTH0_GOVERNANCE_WORKER_CLIENT_ID auth0ClientSecret: "" # AUTH0_GOVERNANCE_WORKER_CLIENT_SECRET + audience: "" # AUTH0_GOVERNANCE_WORKER_AUDIENCE # Encryption key for service account secrets encryptionKey: "" # Base64 encoded 32-byte key # Use existing secret for encryption key @@ -329,6 +327,22 @@ config: governanceWorkerClientId: "governance-worker-client-id" governanceWorkerClientSecret: "governance-worker-client-secret" + # Token Exchange configuration + tokenExchange: + enabled: false + # Key identifier for the signing key + keyId: "auth-service-prod-001" + # Private key for signing tokens (PEM format) + privateKey: "" # Will be mounted from secret + # Use existing secret for private key + existingSecret: "" # e.g., "auth-service-keys" + # Keys in the existing secret + existingSecretKeys: + privateKey: "private-key" + + # Authentication Service URL + authUrl: "" # e.g., https://your-domain.com/auth + # Rate limiting rateLimit: enabled: false diff --git a/charts/governance-platform/Chart.lock b/charts/governance-platform/Chart.lock index 4a9d237..a3e3026 100644 --- a/charts/governance-platform/Chart.lock +++ b/charts/governance-platform/Chart.lock @@ -1,18 +1,18 @@ dependencies: - name: governance-service repository: file://../governance-service - version: 1.0.0 + version: 1.4.0 - name: integrity-service repository: file://../integrity-service - version: 1.0.0 + version: 1.1.5 - name: postgresql repository: https://charts.bitnami.com/bitnami version: 12.5.9 - name: governance-studio repository: file://../governance-studio - version: 1.0.0 + version: 1.2.0 - name: auth-service repository: file://../auth-service - version: 1.0.0 -digest: sha256:e93b27b40ea5bbc23aca24254dd0d3a7a3a60a9cc0709af6da0f0b0ac5968cd0 -generated: "2025-07-23T12:43:52.656755-04:00" + version: 1.0.4 +digest: sha256:6d5c684123aa1f3aefd4573800b084aaf93772c0f87691431ab2de7f2f5a81cb +generated: "2025-10-06T09:32:57.439536-04:00" diff --git a/charts/governance-platform/Chart.yaml b/charts/governance-platform/Chart.yaml index d5e18c8..1fb8fe7 100644 --- a/charts/governance-platform/Chart.yaml +++ b/charts/governance-platform/Chart.yaml @@ -2,39 +2,34 @@ apiVersion: v2 name: governance-platform description: A Helm chart for the complete Governance Studio platform. type: application -version: 1.0.0 +version: 1.2.0 # This is the version number of the application being deployed. -appVersion: "1.0.0" +appVersion: "1.1.0" dependencies: - # 1. Governance Service API - name: governance-service - version: "1.0.0" - repository: "file://../governance-service" + version: "1.4.0" + repository: "file://../governance-service" # Local file system reference condition: governance-service.enabled - # 2. Integrity Service - name: integrity-service - version: "1.0.0" - repository: "file://../integrity-service" + version: "1.1.5" + repository: "file://../integrity-service" # Local file system reference condition: integrity-service.enabled - # 3. PostgreSQL - name: postgresql version: "~12.5.5" # Bitnami chart version repository: "https://charts.bitnami.com/bitnami" alias: postgresql condition: postgresql.enabled - # 4. Governance Studio UI - name: governance-studio - version: "1.0.0" + version: "1.2.0" repository: "file://../governance-studio" condition: governance-studio.enabled - # 5. Auth Service - name: auth-service - version: "1.0.0" + version: "1.0.4" repository: "file://../auth-service" condition: auth-service.enabled diff --git a/charts/governance-platform/README.md b/charts/governance-platform/README.md new file mode 100644 index 0000000..c480ec5 --- /dev/null +++ b/charts/governance-platform/README.md @@ -0,0 +1,596 @@ +# Governance Platform Umbrella Chart + +This Helm chart deploys the complete Governance Platform, including all necessary components and dependencies. + +## Components + +The Governance Platform consists of the following components: + +1. **Compliance Service** - A compliance management system for governance processes +2. **Governance Service** - A service for validating and analyzing governance data +3. **Integrity Service** - A service for ensuring data integrity and auditability +4. **Governance UI** - A user interface for managing governance processes +5. **PostgreSQL** - Database used by all services +6. **Redis** - Cache/message broker used by Compliance Garage + +## Prerequisites + +- Kubernetes 1.21+ +- Helm 3.8+ +- Persistent volume provisioner support in the underlying infrastructure +- **Ingress Controller** (see [Ingress Requirements](#ingress-requirements) below) +- **TLS Certificate Management** (see [TLS Configuration](#tls-configuration) below) +- Secrets created in the cluster (see below) + +## Ingress Requirements + +This chart **assumes you have an ingress controller already installed** in your cluster. The chart is configured by default to use: + +- **NGINX Ingress Controller** (`global.ingress.className: "nginx"`) +- **cert-manager** for automatic TLS certificate management + +### Supported Ingress Controllers + +While the chart defaults to NGINX, you can use any ingress controller by configuring the appropriate class: + +```yaml +global: + ingress: + className: "traefik" # or "alb", "gce", etc. + annotations: + # Remove cert-manager annotations if not using cert-manager + # cert-manager.io/issuer: letsencrypt-prod +``` + +### Installing NGINX Ingress Controller (Optional) + +If you don't have an ingress controller installed, you can install NGINX Ingress Controller: + +```bash +# Install NGINX Ingress Controller +helm upgrade --install ingress-nginx ingress-nginx \ + --repo https://kubernetes.github.io/ingress-nginx \ + --namespace ingress-nginx \ + --create-namespace +``` + +## TLS Configuration + +The chart assumes you have a TLS certificate management solution. There are several options: + +### Option 1: cert-manager (Recommended) + +Install cert-manager for automatic certificate management: + +```bash +# Install cert-manager +helm install cert-manager cert-manager \ + --repo https://charts.jetstack.io \ + --namespace cert-manager \ + --create-namespace \ + --set installCRDs=true + +# Create a ClusterIssuer for Let's Encrypt +kubectl apply -f - < **IMPORTANT**: Never check your actual secrets file into version control. Add `*-secrets.yaml` to your `.gitignore` file. + +### Option 2: Manually Created Secrets + +Alternatively, you can create the secrets manually before installing the chart: + +1. Create the following secrets in your Kubernetes cluster: + + - `compliance-secrets` - Contains database credentials and API keys + - `auth0-secret` - Authentication credentials + - `blob-secret` - Cloud storage credentials + - `azure-kv-secret` - Azure Key Vault credentials (if using Azure) + - `hf-secret` - Hugging Face credentials + - `platform-encryption` - Encryption keys + - `ghcr-pull-secret` - Image pull credentials for GitHub Container Registry + +2. Set `global.secrets.createSecrets` to `false` in your values file + +3. Ensure the secret names match those specified in your values file under `global.secrets.*` + +## Installation + +### Install the chart + +#### Local Development (using charts from filesystem) + +For local development, you can use the chart directly from the repository: + +```bash +# Update dependencies from local filesystem +helm dependency update ./charts/governance-platform + +# Install using local charts +helm install governance-platform ./charts/governance-platform \ + --create-namespace \ + --namespace governance-dev \ + --values values-dev.yaml +``` + +#### Production Installation (using OCI registry) + +For production deployments, it's recommended to use the OCI registry: + +```bash +# Optional: Login to GitHub Container Registry +export CR_PAT=YOUR_GITHUB_TOKEN +echo $CR_PAT | helm registry login ghcr.io -u USERNAME --password-stdin + +# Install from OCI registry +helm install governance-platform oci://ghcr.io/eqtylab/charts/governance-platform/governance-platform \ + --version 0.1.0 \ + --create-namespace \ + --namespace governance-prod \ + --values values-prod.yaml +``` + +## Configuration + +### Global Parameters + +| Parameter | Description | Default | +| ---------------------------- | --------------------------------------------------- | -------------------------- | +| `global.environmentType` | Environment type (development, staging, production) | `"production"` | +| `global.domain` | Base domain for ingress hostnames | `"governance.example.com"` | +| `global.imagePullPolicy` | Default pull policy for images | `"IfNotPresent"` | +| `global.defaultStorageClass` | Default storage class for PVCs | `""` (use cluster default) | + +### Global Database Configuration + +| Parameter | Description | Default | +| ---------------------------- | --------------------- | ------------------------------------ | +| `global.postgresql.host` | PostgreSQL hostname | `"{{ .Release.Name }}-postgresql"` | +| `global.postgresql.port` | PostgreSQL port | `5432` | +| `global.postgresql.database` | Default database name | `"governance"` | +| `global.postgresql.username` | PostgreSQL username | `"postgres"` | +| `global.redis.host` | Redis hostname | `"{{ .Release.Name }}-redis-master"` | +| `global.redis.port` | Redis port | `6379` | + +### Global Ingress Configuration + +> **Note**: These settings assume you have an ingress controller installed. See [Ingress Requirements](#ingress-requirements) for details. + +| Parameter | Description | Default | +| ------------------------------ | ---------------------------------- | -------------------------- | +| `global.ingress.enabled` | Enable ingress for external access | `true` | +| `global.ingress.className` | Ingress controller class ⚠️ | `"nginx"` | +| `global.ingress.host` | Base domain for all services | `"governance.example.com"` | +| `global.ingress.tlsSecretName` | TLS secret name ⚠️ | `"platform-tls"` | +| `global.ingress.annotations` | Ingress annotations ⚠️ | See values.yaml | + +⚠️ **Requires external dependencies**: + +- `className`: Requires NGINX Ingress Controller (or change to your ingress controller) +- `tlsSecretName`: Requires TLS secret (manual or cert-manager managed) +- `annotations`: Default annotations assume cert-manager is installed + +### Global Resource Configuration + +| Parameter | Description | Default | +| ----------------------------------------- | ---------------------- | --------- | +| `global.defaultResources.requests.cpu` | Default CPU request | `"100m"` | +| `global.defaultResources.requests.memory` | Default memory request | `"256Mi"` | +| `global.defaultResources.limits.cpu` | Default CPU limit | `"500m"` | +| `global.defaultResources.limits.memory` | Default memory limit | `"512Mi"` | + +### Secret Management Configuration + +| Parameter | Description | Default | +| ---------------------------------------- | ---------------------------------------------- | ----------------------- | +| `global.secrets.createSecrets` | Whether to automatically create secrets | `true` | +| `global.secrets.complianceSecretName` | Name of the Compliance Garage secret | `"compliance-secrets"` | +| `global.secrets.auth0SecretName` | Name of the Auth0 credentials secret | `"auth0-secret"` | +| `global.secrets.blobStoreSecretName` | Name of the blob storage credentials secret | `"blob-secret"` | +| `global.secrets.azureKVSecretName` | Name of the Azure Key Vault credentials secret | `"azure-kv-secret"` | +| `global.secrets.huggingFaceSecretName` | Name of the Hugging Face token secret | `"hf-secret"` | +| `global.secrets.encryptionKeySecretName` | Name of the platform encryption secret | `"platform-encryption"` | +| `global.secrets.imagePullSecretName` | Name of the image pull secret | `"ghcr-pull-secret"` | +| `global.secrets.openaiSecretName` | Name of the OpenAI API secret | `"openai-secret"` | + +### Compliance Garage Configuration + +| Parameter | Description | Default | +| --------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------- | +| `compliance-garage.enabled` | Enable Compliance Garage component | `true` | +| `compliance-garage.compliance-api.replicaCount` | Number of API replicas | `1` | +| `compliance-garage.compliance-api.image.repository` | API container image repository | `"ghcr.io/eqtylab/compliance-garage/compliance-api"` | +| `compliance-garage.compliance-api.image.tag` | API container image tag | `"latest-20250506-132949"` | +| `compliance-garage.compliance-worker.replicaCount` | Number of worker replicas | `1` | +| `compliance-garage.compliance-worker.persistence.enabled` | Enable worker persistent storage | `true` | +| `compliance-garage.compliance-worker.persistence.size` | Worker storage size | `"5Gi"` | +| `compliance-garage.complianceCoordinator.replicaCount` | Number of coordinator replicas | `1` | +| `compliance-garage.complianceProcessor.replicaCount` | Number of processor replicas | `1` | +| `compliance-garage.complianceProcessor.persistence.size` | Processor storage size | `"1Gi"` | + +### Governance Service Configuration + +| Parameter | Description | Default | +| ----------------------------------------------- | ----------------------------------- | ------------------------------------------------- | +| `governance-service.enabled` | Enable Governance Service component | `true` | +| `governance-service.replicaCount` | Number of replicas | `1` | +| `governance-service.image.repository` | Container image repository | `"ghcr.io/eqtylab/governance-service"` | +| `governance-service.image.tag` | Container image tag | `"sha-fb520fe"` | +| `governance-service.ingress.enabled` | Enable ingress | `true` | +| `governance-service.externalDatabase.host` | External database host | `"governance-platform-postgresql"` | +| `governance-service.externalDatabase.database` | Database name | `"governance"` | +| `governance-service.migrations.runAtStartup` | Run migrations at startup | `true` | +| `governance-service.config.gcsBucketName` | GCS bucket for attachments | `"gov-studio-prod-attachments"` | +| `governance-service.config.auth0Domain` | Auth0 domain | `"governance-platform.us.auth0.com"` | +| `governance-service.config.integrityServiceUrl` | Integrity service URL | `"http://governance-platform-integrity-service:80"` | +| `governance-service.auth0SyncAtStartup` | Sync Auth0 users at startup | `true` | + +### Governance Studio Configuration + +| Parameter | Description | Default | +| ------------------------------------------------ | ------------------------------ | ---------------------------------------------------- | +| `governance-studio.enabled` | Enable Governance Studio component | `true` | +| `governance-studio.replicaCount` | Number of replicas | `1` | +| `governance-studio.image.repository` | Container image repository | `"ghcr.io/eqtylab/verify-frontend"` | +| `governance-studio.image.tag` | Container image tag | `"latest"` | +| `governance-studio.config.apiUrl` | Backend API URL | `"https://governance.example.com/governanceService"` | +| `governance-studio.config.auth0Domain` | Auth0 domain | `"governance-studio.us.auth0.com"` | +| `governance-studio.config.auth0ClientId` | Auth0 client ID | `"your_auth0_client_id"` | +| `governance-studio.config.environment` | Application environment | `"production"` | +| `governance-studio.config.appTitle` | Application title | `"Governance Studio"` | +| `governance-studio.config.features.compliance` | Enable compliance features | `true` | +| `governance-studio.config.features.governance` | Enable governance features | `true` | +| `governance-studio.config.features.guardian` | Enable guardian features | `true` | +| `governance-studio.config.features.lineage` | Enable lineage features | `true` | +| `governance-studio.config.branding.logoUrl` | Company logo URL | `"/vite.svg"` | +| `governance-studio.config.branding.primaryColor` | Primary brand color | `"#0f172a"` | +| `governance-studio.config.branding.companyName` | Company name | `"EQTY Lab"` | + +### Integrity Service Configuration + +| Parameter | Description | Default | +| ------------------------------------------------------ | ---------------------------------- | --------------------------------------------------- | +| `integrity-service.enabled` | Enable Integrity Service component | `true` | +| `integrity-service.deployment.replicaCount` | Number of replicas | `1` | +| `integrity-service.image.repository` | Container image repository | `"ghcr.io/eqtylab"` | +| `integrity-service.image.name` | Container image name | `"integrity-service"` | +| `integrity-service.image.tag` | Container image tag | `"61c5345"` | +| `integrity-service.env.integrityDbHost` | Database host | `"{{ .Release.Name }}-postgresql"` | +| `integrity-service.env.integrityDbName` | Database name | `"IntegrityServiceDB"` | +| `integrity-service.env.integrityAppBlobStoreType` | Blob storage type | `"azure_blob"` | +| `integrity-service.env.integrityAppBlobStoreAccount` | Azure storage account | `"govstudiostagstorageacct"` | +| `integrity-service.env.integrityAppBlobStoreContainer` | Azure blob container | `"rootstore"` | +| `integrity-service.env.integrityServiceUrl` | Service URL | `"https://governance.example.com/integrityService"` | +| `integrity-service.service.port` | Service port | `80` | +| `integrity-service.service.targetPort` | Container port | `3050` | + +### PostgreSQL Configuration + +| Parameter | Description | Default | +| -------------------------------------------------- | -------------------------- | ----------------------------------------------------- | +| `postgresql.enabled` | Enable PostgreSQL database | `true` | +| `postgresql.global.postgresql.auth.database` | Database name | `"governance"` | +| `postgresql.global.postgresql.auth.username` | Database username | `"postgres"` | +| `postgresql.global.postgresql.auth.existingSecret` | Existing secret name | `"{{ .Values.global.secrets.complianceSecretName }}"` | +| `postgresql.primary.persistence.enabled` | Enable persistence | `true` | +| `postgresql.primary.persistence.size` | Storage size | `10Gi` | +| `postgresql.primary.resources.requests.cpu` | CPU request | `500m` | +| `postgresql.primary.resources.requests.memory` | Memory request | `1Gi` | +| `postgresql.primary.resources.limits.cpu` | CPU limit | `2000m` | +| `postgresql.primary.resources.limits.memory` | Memory limit | `2Gi` | + +### Redis Configuration + +| Parameter | Description | Default | +| ---------------------------------------- | --------------------------------- | ----------------------------------------------------- | +| `redis.enabled` | Enable Redis cache/message broker | `true` | +| `redis.auth.enabled` | Enable Redis authentication | `false` | +| `redis.auth.existingSecret` | Existing secret name | `"{{ .Values.global.secrets.complianceSecretName }}"` | +| `redis.master.persistence.enabled` | Enable master persistence | `false` | +| `redis.master.persistence.size` | Master storage size | `8Gi` | +| `redis.master.resources.requests.cpu` | CPU request | `200m` | +| `redis.master.resources.requests.memory` | Memory request | `512Mi` | +| `redis.master.resources.limits.cpu` | CPU limit | `500m` | +| `redis.master.resources.limits.memory` | Memory limit | `1Gi` | +| `redis.replica.replicaCount` | Number of Redis replicas | `0` | + +### Advanced Configuration Examples + +#### Custom Domain Configuration + +```yaml +global: + domain: "governance.yourcompany.com" + ingress: + host: "governance.yourcompany.com" + tlsSecretName: "yourcompany-tls" + annotations: + cert-manager.io/issuer: "letsencrypt-prod" # Requires cert-manager +``` + +#### Alternative Ingress Controller Configuration + +```yaml +global: + ingress: + className: "traefik" # Using Traefik instead of NGINX + annotations: + traefik.ingress.kubernetes.io/router.tls: "true" + # Remove cert-manager annotations if not using cert-manager +``` + +#### Manual TLS Configuration + +```yaml +global: + ingress: + tlsSecretName: "my-custom-tls-secret" + annotations: + # Remove all cert-manager related annotations + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "64m" +``` + +#### Production Resource Configuration + +```yaml +global: + defaultResources: + requests: + cpu: "250m" + memory: "512Mi" + limits: + cpu: "1000m" + memory: "1Gi" + +postgresql: + primary: + resources: + requests: + cpu: 1000m + memory: 2Gi + limits: + cpu: 4000m + memory: 4Gi +``` + +#### High Availability Configuration + +```yaml +compliance-garage: + compliance-api: + replicaCount: 3 + compliance-worker: + replicaCount: 2 + +governance-service: + replicaCount: 2 + +governance-studio: + replicaCount: 3 + +redis: + replica: + replicaCount: 1 +``` + +#### Custom Storage Configuration + +```yaml +global: + defaultStorageClass: "fast-ssd" + +postgresql: + primary: + persistence: + size: 50Gi + storageClass: "fast-ssd" + +compliance-garage: + compliance-worker: + persistence: + size: 20Gi + storageClass: "standard" +``` + +## Environment-Specific Values Files + +This chart comes with environment-specific values files: + +- `values.yaml` - Default values +- `values-prod.yaml` - Production environment values +- `values-stag.yaml` - Staging environment values +- `values-dev.yaml` - Development environment values + +## Ingress Configuration + +This chart uses path-based routing with rewrite rules, allowing all services to be accessed through a single domain. The default configuration is: + +- Compliance Service API: `https:///complianceService/...` +- Governance Service API: `https:///governanceService/...` +- Integrity Service: `https:///integrityService/...` + +## Architecture + +The Governance Platform uses a microservices architecture with the following components: + +- **Compliance Service** - Contains API, Worker, Coordinator, and Processor services +- **Governance Service** - Provides the governance API endpoints and processing system +- **Integrity Service** - Handles data integrity and blockchain validation +- **PostgreSQL** - Shared database across services +- **Redis** - Message broker for Compliance Garage components + +## Upgrading + +To upgrade the chart to a new version: + +### Local Development + +```bash +# Update dependencies from local filesystem +helm dependency update ./charts/governance-platform + +# Upgrade using local charts +helm upgrade governance-platform ./charts/governance-platform \ + --namespace governance-dev \ + --values values-dev.yaml \ + --values my-secrets.yaml +``` + +### Staging Environment + +```bash +# Update dependencies +helm dependency update ./charts/governance-platform + +# Upgrade staging deployment +helm upgrade governance-platform ./charts/governance-platform \ + --namespace governance-stag \ + --values values-stag.yaml \ + --values my-secrets.yaml +``` + +### Production Environment + +```bash +# Upgrade using OCI registry (recommended for production) +helm upgrade governance-platform oci://ghcr.io/eqtylab/charts/governance-platform/governance-platform \ + --version 0.1.1 \ + --namespace governance-prod \ + --values values-prod.yaml \ + --values my-secrets.yaml +``` + +### Upgrade Best Practices + +1. **Backup Database**: Always backup your PostgreSQL database before upgrading +2. **Test in Staging**: Test the upgrade in a staging environment first +3. **Check Breaking Changes**: Review the CHANGELOG for any breaking changes +4. **Rolling Updates**: The chart supports rolling updates with zero downtime +5. **Version Pinning**: Use specific chart versions in production rather than latest + +## Monitoring and Health Checks + +### Health Check Endpoints + +Each service provides health check endpoints for monitoring: + +- **Compliance Service API**: `GET /health` +- **Governance Service API**: `GET /health` +- **Integrity Service API**: `GET /health` +- **Governance UI**: `GET /health` (via nginx) + +### Monitoring Commands + +```bash +# Check all pods status +kubectl get pods -n governance-prod + +# Check specific service logs +kubectl logs -f deployment/governance-platform-compliance-garage-api -n governance-prod +kubectl logs -f deployment/governance-platform-governance-service -n governance-prod +kubectl logs -f deployment/governance-platform-integrity-service -n governance-prod + +# Check ingress status +kubectl get ingress -n governance-prod +kubectl describe ingress governance-platform-ingress -n governance-prod + +# Monitor resource usage +kubectl top pods -n governance-prod +kubectl top nodes +``` + +### Database Health + +```bash +# Check PostgreSQL connection +kubectl exec -it deployment/governance-platform-postgresql -n governance-prod -- psql -U postgres -c "SELECT version();" + +# Check Redis connection +kubectl exec -it deployment/governance-platform-redis-master -n governance-prod -- redis-cli ping +``` + +## Uninstallation + +To uninstall/delete the deployment: + +```bash +helm uninstall governance-platform -n governance-prod +``` + +> **Warning**: This will delete all resources associated with the release, including persistent volumes. Ensure you have proper backups before uninstalling. diff --git a/charts/governance-platform/secrets-sample.yaml b/charts/governance-platform/secrets-sample.yaml new file mode 100644 index 0000000..57b3d64 --- /dev/null +++ b/charts/governance-platform/secrets-sample.yaml @@ -0,0 +1,111 @@ +# Sample secrets configuration file for Governance Studio +# WARNING: This is a template file - do not use these values in production +# WARNING: Do not check your actual secrets into version control + +global: + secrets: + # Enable automatic secret creation + createSecrets: true + + # Secret values for all components + values: + # Compliance Garage secrets + compliance: + # Database connection string (used by all Compliance Garage components) + # If not provided, it will be generated from the database settings + databaseUrl: "postgresql://postgres:example-password@postgresql:5432/governance" + + # Redis connection string + # If not provided, it will be generated from the Redis settings + redisUrl: "redis://redis-master:6379" + + # Gemini API key for AI features + geminiApiKey: "example-gemini-api-key" + + # Auth0 authentication + auth0: + clientId: "example-auth0-client-id" + clientSecret: "example-auth0-client-secret" + domain: "example-tenant.us.auth0.com" + + # Database credentials + database: + postgresPassword: "example-postgres-password" + redisPassword: "example-redis-password" + + # Blob storage + blobStorage: + # For GCP (needs to use the actual JSON file content) + gcsServiceAccountJson: | + { + "type": "service_account", + "project_id": "", + "private_key_id": "", + "private_key": "", + "client_id": "", + "auth_uri": "", + "token_uri": "", + "auth_provider_x509_cert_url": "", + "client_x509_cert_url": "", + "universe_domain": "" + } + + # For Azure + azureStorageAccountName: "examplestorageaccount" + azureStorageAccountKey: "example-storage-account-key" + connectionString: "DefaultEndpointsProtocol=https;AccountName=exampleaccount;AccountKey=example-key==;EndpointSuffix=core.windows.net" + emailString: "endpoint=https://example-communications.unitedstates.communication.azure.com/;accesskey=example-access-key==" + + # For AWS + awsAccessKeyId: "EXAMPLEACCESSKEY" + awsSecretAccessKey: "example-aws-secret-access-key" + + # Azure Key Vault + azureKeyVault: + clientId: "example-azure-client-id" + clientSecret: "example-azure-client-secret" + tenantId: "example-azure-tenant-id" + vaultUrl: "https://example-kv.vault.azure.net/" + + # AI/ML credentials + huggingFace: + token: "example-hugging-face-token" + + # OpenAI credentials + openai: + apiKey: "example-openai-api-key" + + # Platform encryption + encryption: + key: "example-encryption-key" # Should be a properly generated encryption key + azureKeyVaultUrl: "https://example-kv.vault.azure.net/" + azureKeyVaultClientId: "example-azure-client-id" + azureKeyVaultClientSecret: "example-azure-client-secret" + azureKeySecretName: "db-encryption-key" + azureKeyVaultTenantId: "example-azure-tenant-id" + + # Container registry credentials + imagePull: + username: "example-github-username" # GitHub username or token name + password: "example-github-token" # GitHub PAT with read:packages scope + email: "example@example.com" # Email associated with GitHub account + + # Auth Service specific secrets + authService: + # Database password for auth service (can be same as postgresPassword) + dbPassword: "example-postgres-password" + # Session secret for auth service (generate with: openssl rand -base64 32) + sessionSecret: "example-session-secret-base64-encoded-32-bytes" + # API secret for auth service (generate with: openssl rand -base64 32) + apiSecret: "example-api-secret-base64-encoded-32-bytes" + # JWT secret for auth service (generate with: openssl rand -base64 32) + jwtSecret: "example-jwt-secret-base64-encoded-32-bytes" + + # Governance Worker Service Account + governanceWorker: + # Encryption key for service accounts (generate with: openssl rand -base64 32) + encryptionKey: "example-encryption-key-base64-encoded-32-bytes" + # Auth0 M2M application client ID for governance worker + clientId: "example-m2m-client-id" + # Auth0 M2M application client secret for governance worker + clientSecret: "example-m2m-client-secret" diff --git a/charts/governance-platform/templates/_helpers.tpl b/charts/governance-platform/templates/_helpers.tpl index 1c31f21..e90237c 100644 --- a/charts/governance-platform/templates/_helpers.tpl +++ b/charts/governance-platform/templates/_helpers.tpl @@ -59,4 +59,4 @@ Create the name of the service account to use {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/governance-platform/templates/hooks/create-keycloak-org-job.yaml b/charts/governance-platform/templates/hooks/create-keycloak-org-job.yaml new file mode 100644 index 0000000..f2d06c9 --- /dev/null +++ b/charts/governance-platform/templates/hooks/create-keycloak-org-job.yaml @@ -0,0 +1,207 @@ +{{- if .Values.keycloak.createOrganization }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "governance-platform.fullname" . }}-create-keycloak-org + labels: + {{- include "governance-platform.labels" . | nindent 4 }} + app.kubernetes.io/component: keycloak-org-setup + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "20" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + backoffLimit: 3 + ttlSecondsAfterFinished: 300 + template: + metadata: + labels: + {{- include "governance-platform.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: keycloak-org-setup + spec: + restartPolicy: OnFailure + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: create-org + image: postgres:15-alpine + command: ["/bin/sh"] + args: + - -c + - | + set -e + echo "Waiting for database to be ready..." + until pg_isready -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} -p 5432 -U {{ .Values.postgresql.auth.username }}; do + echo "Database not ready yet..." + sleep 5 + done + + echo "Creating Keycloak organization..." + + # Check if organization already exists + EXISTS=$(psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -t -c "SELECT COUNT(*) FROM organization WHERE name = '{{ .Values.keycloak.realmName }}';" | tr -d ' ') + + if [ "$EXISTS" = "1" ]; then + echo "Organization '{{ .Values.keycloak.realmName }}' already exists" + # Update to ensure idp_provider is set correctly + psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -c "UPDATE organization SET idp_provider = 'keycloak', updated_at = NOW() WHERE name = '{{ .Values.keycloak.realmName }}';" + echo "Updated organization to use Keycloak IDP" + else + # Create new organization + psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -c "INSERT INTO organization (name, description, display_name, idp_provider, settings, created_at, updated_at) \ + VALUES ('{{ .Values.keycloak.realmName }}', '{{ .Values.keycloak.realmName }}', '{{ .Values.keycloak.displayName | default .Values.keycloak.realmName }}', 'keycloak', '{}', NOW(), NOW());" + echo "Created organization '{{ .Values.keycloak.realmName }}'" + fi + + # Show the organization + psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -c "SELECT id, name, display_name, idp_provider FROM organization WHERE name = '{{ .Values.keycloak.realmName }}';" + + {{- if .Values.keycloak.createPlatformAdmin }} + echo "Creating platform-admin user..." + + # Generate UUIDs + USER_ID=$(uuidgen | tr '[:upper:]' '[:lower:]') + KEYCLOAK_USER_ID="{{ .Values.keycloak.platformAdminKeycloakId | default "00000000-0000-0000-0000-000000000000" }}" + + # Check if user exists + USER_EXISTS=$(psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -t -c "SELECT COUNT(*) FROM users WHERE email = 'admin@{{ .Values.keycloak.realmName }}.local';" | tr -d ' ') + + if [ "$USER_EXISTS" = "1" ]; then + echo "Platform admin user already exists" + USER_ID=$(psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -t -c "SELECT id FROM users WHERE email = 'admin@{{ .Values.keycloak.realmName }}.local';" | tr -d ' ') + else + # Create user + psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -c "INSERT INTO users (id, idp_provider, idp_user_id, email, email_verified, display_name, given_name, family_name, active, app_metadata, created_at, updated_at, is_service_account, service_config) \ + VALUES ('$USER_ID', 'keycloak', '$KEYCLOAK_USER_ID', 'admin@{{ .Values.keycloak.realmName }}.local', true, 'Platform Admin', 'Platform', 'Admin', true, '{}', NOW(), NOW(), false, '{}');" + echo "Created platform admin user" + fi + + # Get organization ID + ORG_ID=$(psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -t -c "SELECT id FROM organization WHERE name = '{{ .Values.keycloak.realmName }}';" | tr -d ' ') + + # Check if membership exists + MEMBERSHIP_EXISTS=$(psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -t -c "SELECT COUNT(*) FROM user_organization_memberships WHERE user_id = '$USER_ID' AND organization_id = $ORG_ID;" | tr -d ' ') + + if [ "$MEMBERSHIP_EXISTS" = "1" ]; then + echo "Membership already exists, updating to ensure owner role" + psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -c "UPDATE user_organization_memberships SET roles = '{organization_owner}', status = 'active', updated_at = NOW() WHERE user_id = '$USER_ID' AND organization_id = $ORG_ID;" + else + # Create membership + MEMBERSHIP_ID=$(uuidgen | tr '[:upper:]' '[:lower:]') + psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -c "INSERT INTO user_organization_memberships (id, user_id, organization_id, roles, invited_at, joined_at, status) \ + VALUES ('$MEMBERSHIP_ID', '$USER_ID', $ORG_ID, '{organization_owner}', NOW(), NOW(), 'active');" + echo "Created organization membership" + fi + + # Show created user and membership + echo "Platform admin user:" + psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -c "SELECT id, email, display_name, idp_provider FROM users WHERE email = 'admin@{{ .Values.keycloak.realmName }}.local';" + {{- end }} + + echo "Setup complete!" + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.postgresql.auth.existingSecret | default (printf "%s-postgresql" .Release.Name) }} + key: {{ .Values.postgresql.auth.secretKeys.userPasswordKey | default "password" }} + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + # This job should run after the application starts and runs migrations + initContainers: + - name: wait-for-schema + image: postgres:15-alpine + command: ['sh', '-c'] + args: + - | + echo "Waiting for database schema to be ready..." + + # Wait for database to be reachable + until pg_isready -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} -p 5432 -U {{ .Values.postgresql.auth.username }}; do + echo "Waiting for database to be ready..." + sleep 5 + done + + # Wait for required tables to exist (migrations run on app startup) + echo "Checking for required tables..." + REQUIRED_TABLES="organization users user_organization_memberships" + MAX_ATTEMPTS=30 + ATTEMPT=1 + + while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do + ALL_EXIST=true + for TABLE in $REQUIRED_TABLES; do + EXISTS=$(psql -h {{ .Values.postgresql.host | default (printf "%s-postgresql" .Release.Name) }} \ + -U {{ .Values.postgresql.auth.username }} \ + -d {{ .Values.postgresql.auth.database }} \ + -t -c "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '$TABLE');" | tr -d ' ') + + if [ "$EXISTS" != "t" ]; then + ALL_EXIST=false + echo "Table $TABLE does not exist yet..." + break + fi + done + + if [ "$ALL_EXIST" = "true" ]; then + echo "All required tables exist!" + exit 0 + fi + + echo "Attempt $ATTEMPT/$MAX_ATTEMPTS - Waiting 10 seconds for migrations..." + sleep 10 + ATTEMPT=$((ATTEMPT + 1)) + done + + echo "Warning: Some tables may not exist, but proceeding..." + exit 0 + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.postgresql.auth.existingSecret | default (printf "%s-postgresql" .Release.Name) }} + key: {{ .Values.postgresql.auth.secretKeys.userPasswordKey | default "password" }} +{{- end }} \ No newline at end of file diff --git a/charts/governance-platform/templates/secrets/auth-service-keycloak-credentials.yaml b/charts/governance-platform/templates/secrets/auth-service-keycloak-credentials.yaml new file mode 100644 index 0000000..4e5a2bb --- /dev/null +++ b/charts/governance-platform/templates/secrets/auth-service-keycloak-credentials.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.global.secrets.createSecrets (eq (index .Values "auth-service").config.idp.provider "keycloak") }} +apiVersion: v1 +kind: Secret +metadata: + name: auth-service-keycloak-credentials + namespace: {{ .Release.Namespace }} + labels: + {{- include "governance-studio.labels" . | nindent 4 }} + app.kubernetes.io/component: auth-service + app.kubernetes.io/part-of: keycloak-integration +type: Opaque +data: + # Frontend client configuration (public client) + frontend-client-id: {{ .Values.global.secrets.values.keycloak.frontendClientId | default "governance-platform-frontend" | b64enc | quote }} + frontend-client-secret: {{ "" | b64enc | quote }} # Public client, no secret + + # Backend client configuration (confidential client) + backend-client-id: {{ .Values.global.secrets.values.keycloak.backendClientId | default "governance-platform-backend" | b64enc | quote }} + backend-client-secret: {{ required "Keycloak backend client secret is required" .Values.global.secrets.values.keycloak.backendClientSecret | b64enc | quote }} +{{- end }} diff --git a/charts/governance-platform/templates/secrets/auth-service-keys.yaml b/charts/governance-platform/templates/secrets/auth-service-keys.yaml new file mode 100644 index 0000000..f5414b1 --- /dev/null +++ b/charts/governance-platform/templates/secrets/auth-service-keys.yaml @@ -0,0 +1,14 @@ +{{- if .Values.global.secrets.createSecrets }} +{{- if .Values.global.secrets.values.authService.privateKey }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.global.secrets.authServiceKeysSecretName | default "auth-service-keys" }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "governance-studio.labels" . | nindent 4 }} +type: Opaque +data: + private-key: {{ .Values.global.secrets.values.authService.privateKey | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/charts/governance-platform/templates/secrets/governance-service-ai-secret.yaml b/charts/governance-platform/templates/secrets/governance-service-ai-secret.yaml new file mode 100644 index 0000000..6a53370 --- /dev/null +++ b/charts/governance-platform/templates/secrets/governance-service-ai-secret.yaml @@ -0,0 +1,14 @@ +{{- if .Values.global.secrets.createSecrets }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.global.secrets.governanceServiceAISecretName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "governance-studio.labels" . | nindent 4 }} +type: Opaque +data: + {{- if .Values.global.secrets.values.governanceService.aiApiKey }} + ai-api-key: {{ .Values.global.secrets.values.governanceService.aiApiKey | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/charts/governance-platform/templates/secrets/idp-secret.yaml b/charts/governance-platform/templates/secrets/idp-secret.yaml deleted file mode 100644 index 6c0e81c..0000000 --- a/charts/governance-platform/templates/secrets/idp-secret.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.global.secrets.createSecrets }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Values.global.secrets.idpSecretName }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "governance-studio.labels" . | nindent 4 }} -type: Opaque -data: - client-id: {{ .Values.global.secrets.values.idp.clientId | b64enc | quote }} - client-secret: {{ .Values.global.secrets.values.idp.clientSecret | b64enc | quote }} - {{- if eq (index .Values "auth-service").config.idp.provider "auth0" }} - {{- if .Values.global.secrets.values.idp.auth0.managementClientId }} - mgmt-client-id: {{ .Values.global.secrets.values.idp.auth0.managementClientId | b64enc | quote }} - {{- end }} - {{- if .Values.global.secrets.values.idp.auth0.managementClientSecret }} - mgmt-client-secret: {{ .Values.global.secrets.values.idp.auth0.managementClientSecret | b64enc | quote }} - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/governance-platform/values.yaml b/charts/governance-platform/values.yaml index 281d2dc..d60ba13 100644 --- a/charts/governance-platform/values.yaml +++ b/charts/governance-platform/values.yaml @@ -24,6 +24,12 @@ global: # This domain will be used by all services for external access domain: "governance.example.com" # Example: governance.yourcompany.com + # Default image pull policy for all containers + # IfNotPresent: Only pull if image doesn't exist locally (recommended for tagged releases) + # Always: Always pull the latest version (use for 'latest' tags or development) + # Never: Never pull, use local image only + imagePullPolicy: "IfNotPresent" + # --- Secrets Management --- # Centralized secret configuration for all platform components # Secrets can be created automatically by the chart or pre-created manually @@ -42,7 +48,9 @@ global: authServiceDbSecretName: "auth-service-db-credentials" # Auth service database credentials authServiceSessionSecretName: "auth-service-session-secret" # Auth service session secret authServiceApiSecretName: "auth-service-api-secret" # Auth service API secret + authServiceKeysSecretName: "auth-service-keys" # Auth service private keys for token signing governanceWorkerSecretName: "governance-worker-credentials" # Governance worker service account + governanceServiceAISecretName: "governance-service-ai-secret" # Governance service AI secret # --- Secret Creation Control --- # true: Chart will create secrets using the values below during installation @@ -63,7 +71,7 @@ global: # Gemini API key for AI features geminiApiKey: "" - # Auth0 authentication + # Auth0 authentication (only needed when using Auth0, not Keycloak) auth0: clientId: "" clientSecret: "" @@ -123,12 +131,25 @@ global: sessionSecret: "" # Session secret for auth service (generate with: openssl rand -base64 32) apiSecret: "" # API secret for auth service (generate with: openssl rand -base64 32) jwtSecret: "" # JWT secret for auth service (generate with: openssl rand -base64 32) + privateKey: "" # Private key for token signing (PEM format) - used for Keycloak token exchange # Governance Worker Service Account governanceWorker: encryptionKey: "" # Encryption key for service accounts (generate with: openssl rand -base64 32) - clientId: "" # Auth0 M2M application client ID for governance worker - clientSecret: "" # Auth0 M2M application client secret for governance worker + clientId: "" # Auth0/Keycloak application client ID for governance worker (if Keycloak, use 'governance-worker') + clientSecret: "" # Auth0/Keycloak application client secret for governance worker + + # Keycloak-specific secrets (when using Keycloak instead of Auth0) + keycloak: + # Frontend client (public client) + frontendClientId: "governance-platform-frontend" # Usually doesn't need to be changed + + # Backend client (confidential client for auth-service) + backendClientId: "governance-platform-backend" # Usually doesn't need to be changed + backendClientSecret: "" # Required: Secret for backend client (generate with: openssl rand -hex 32) + + governanceService: + aiApiKey: "" # AI API key for governance service (used for AI-based governance decisions) # --- Database Configuration --- # PostgreSQL connection settings shared across all platform services @@ -140,6 +161,14 @@ global: username: "postgres" # PostgreSQL admin user # Note: Password is retrieved from the compliance-secrets secret for security + # --- Redis Configuration --- + # Redis connection settings for caching and message queuing + # Used primarily by Compliance Garage components + redis: + host: "{{ .Release.Name }}-redis-master" # Automatically resolves to deployed Redis master service + port: 6379 # Standard Redis port + # Note: Password is retrieved from secrets if authentication is enabled + # --- Container Registry Configuration --- # Uncomment and set if using a private container registry instead of GitHub Container Registry # imageRegistry: "mycompany.registry.io/governance-platform" @@ -149,6 +178,17 @@ global: imagePullSecrets: - name: "ghcr-pull-secret" # Required for GitHub Container Registry access + # --- Default Resource Configuration --- + # Default CPU and memory limits/requests applied to all components + # Individual components can override these values in their specific configuration + defaultResources: + requests: + cpu: "100m" # Minimum CPU allocation (0.1 CPU core) + memory: "256Mi" # Minimum memory allocation + limits: + cpu: "500m" # Maximum CPU allocation (0.5 CPU core) + memory: "512Mi" # Maximum memory allocation + # --- Storage Configuration --- # Default storage class for all persistent volumes # Leave empty ("") to use the cluster's default storage class @@ -189,7 +229,7 @@ governance-service: replicaCount: 1 image: repository: "ghcr.io/eqtylab/governance-service" - tag: "latest" # Updated to match production + tag: "1.0.0" pullPolicy: "IfNotPresent" command: ["./governance-service"] args: [] @@ -206,7 +246,7 @@ governance-service: replicaCount: 1 image: repository: "ghcr.io/eqtylab/governance-worker" - tag: "latest" + tag: "1.0.0" pullPolicy: "IfNotPresent" command: ["./governance-worker"] args: [] @@ -268,6 +308,11 @@ governance-service: runAsNonRoot: true runAsUser: 1000 + # Service Account Configuration + serviceAccount: + create: false + name: "blob-secret" + # Configure external database settings externalDatabase: host: "governance-platform-postgresql" @@ -284,252 +329,176 @@ governance-service: runAtStartup: true path: "/app/migrations" + # Application configuration config: - path: "/api/v1/health" # Health check endpoint path - gcsEnabled: false - gcsBucketName: "" # Updated to match production - integrityServiceUrl: "http://governance-platform-integrity-service:80" - appEnv: "production" logLevel: "info" + appEnv: "production" - # HTTP Server Configuration - server: - readTimeout: 30 - writeTimeout: 30 - idleTimeout: 120 - # Worker-specific scheduler configuration - scheduler: - version: "1.0.0" - workerType: "general" - maxConcurrentJobs: 5 - pollInterval: 30 - heartbeatInterval: 60 - jobClaimTTL: 5 - capabilities: "evaluation, scheduling" - # API-specific scheduler fields - workerCount: 5 - retryLimit: 3 - retryDelay: 60 - # Indicator Configuration Management - indicators: - configPath: "/app/configs/indicators" - reloadInterval: 300 - validateOnLoad: true - - # For sensitive values like tokens/keys, point to keys within the pre-created K8s secrets - huggingFaceTokenSecretKeyRef: - name: "hf-secret" - key: "hugging-face-token" - credentialEncryptionKeySecretKeyRef: - name: "platform-encryption" - key: "key" - - # Auth0 credentials reference - auth0SyncAtStartup: true - auth0SyncPageSize: 100 - auth0SecretKeyRef: - name: "auth0-secret" - domainKey: "domain" - clientIdKey: "clientId" - clientSecretKey: "clientSecret" + # Storage provider configuration + storageProvider: "gcs" # Options: "gcs" or "azure" - # For GCS service account, the assurance-engine deployment mounts a secret. - serviceAccount: - create: false - name: "blob-secret" + # Google Cloud Storage Configuration + gcsBucketName: "" # Set if using GCS -# --- Component: Governance Studio UI --- -# React-based frontend application providing the user interface for the platform -# Handles authentication, data visualization, and user interactions -governance-studio: - enabled: true # Set to false to disable the frontend UI component - replicaCount: 1 - image: - repository: "ghcr.io/eqtylab/governance-studio" - tag: "main" - pullPolicy: IfNotPresent - imagePullSecrets: - - name: "ghcr-pull-secret" - config: - integrityServiceUrl: "https://governance.example.com/integrityService" - # API Configuration - apiUrl: "https://governance.example.com/governanceService" + # Azure Storage Configuration + azureStorageAccountName: "" # Azure storage account name + azureStorageAccountKey: "" # Azure storage account key (prefer using secret reference) + azureStorageConnectionString: "" # Azure storage connection string (prefer using secret reference) + azureStorageContainerName: "" # Azure storage container name + azureUseManagedIdentity: false # Use Azure managed identity for authentication - # Auth0 Configuration + # Auth0 configuration auth0Domain: "governance-studio.us.auth0.com" - auth0ClientId: "your_auth0_client_id" - auth0Audience: "https://governance.example.com/governanceService" - - # Application Settings - environment: "production" - appTitle: "Governance Studio" - appHostname: "governance.example.com" - - # Feature Flags - features: - compliance: true - governance: true - guardian: true - lineage: true - - # Branding Configuration - branding: - logoUrl: "/vite.svg" - primaryColor: "#0f172a" - companyName: "EQTY Lab" - ingress: - enabled: true - className: "nginx" - annotations: - cert-manager.io/issuer: "letsencrypt-prod" - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/rewrite-target: "/" - hosts: - - host: "governance.example.com" - paths: - - path: "/" - pathType: "Prefix" - tls: - - secretName: "platform-tls" - hosts: - - "governance.example.com" + # Service URLs + integrityServiceUrl: "http://governance-platform-integrity-service" + authServiceUrl: "http://governance-platform-auth-service:8080" - resources: - requests: - cpu: "100m" - memory: "256Mi" - limits: - cpu: "500m" - memory: "512Mi" - -# --- Component: Integrity Service --- -# Data integrity and auditability service providing blockchain integration -# Handles cryptographic proofs, audit trails, and immutable record keeping -integrity-service: - enabled: true # Set to false to disable the Integrity Service component - # Direct ingress configuration - ingress: - enabled: true - className: "nginx" - annotations: - cert-manager.io/issuer: "letsencrypt-prod" - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/rewrite-target: "/$2" - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/proxy-body-size: "0" - hosts: - - host: "governance.example.com" - paths: - - path: "/integrityService(/|$)(.*)" - pathType: "ImplementationSpecific" - tls: - - secretName: "platform-tls" - hosts: - - "governance.example.com" - deployment: - replicaCount: 1 - image: - repository: "ghcr.io/eqtylab" - name: "integrity-service" - tag: "d4e4b46" # Updated to match production - kvSecret: - enabled: true - name: "azure-kv-secret" - keys: - clientSecret: "clientSecret" - clientId: "clientId" - tenantId: "tenantId" - vaultUrl: "vaultUrl" - env: - # Database connection information - integrityDbHost: "{{ .Release.Name }}-postgresql" - integrityDbPort: "5432" - integrityDbName: "IntegrityServiceDB" - integrityDbUser: "postgres" - # Storage configuration - integrityAppBlobStoreAccount: "" # Name of your azure blob storage account - integrityAppBlobStoreContainer: "" # Name of your azure blob storage container - integrityServiceUrl: "" # Update to match production - rustEnv: "production" - rustBacktrace: "0" - # Auth configurations - integrityAppAuthType: "" # e.g. "auth0" - integrityAppAuthClientId: "" # e.g. "your-client-id" - integrityAppAuthDomain: "" # e.g. "your-auth0-domain.auth0.com" - integrityAppCustomClaimsUrl: "" # URL for custom claims - # Service configuration for integrity - service: - enabled: true - type: ClusterIP - port: 80 - targetPort: 3050 - resources: {} - # limits: - # cpu: 500m - # memory: 512Mi - # requests: - # cpu: 250m - # memory: 256Mi - nodeSelector: - enabled: false - values: - node_pool: apps + # Service account configuration + serviceAccount: + enabled: true + authServiceUrl: "http://governance-platform-auth-service:8080" -# --- Component: Authentication Service --- -auth-service: - enabled: false - image: - tag: "sha-aee2859" + # Azure Storage Secret Reference (recommended for production) + azureStorageSecretKeyRef: + name: "blob-secret" # Uses the blob storage secret from global secrets + accountNameKey: "azure-storage-account-name" + accountKeyKey: "azure-storage-account-key" + connectionStringKey: "connection-string" - imagePullSecrets: - - name: ghcr-pull-secret + # Auth0 Secret Reference (only used when not using Keycloak) + auth0SecretKeyRef: + name: "" + clientIdKey: "" + clientSecretKey: "" + + # Keycloak Configuration (when using Keycloak instead of Auth0) + keycloakSecretKeyRef: + enabled: false # Set to true when using Keycloak + existingSecret: "keycloak-worker-config" + existingSecretKeys: + clientId: "KEYCLOAK_CLIENT_ID" + realm: "KEYCLOAK_REALM" + url: "KEYCLOAK_URL" + existingClientSecretKeyRef: + name: "keycloak-worker-client" + clientSecretKey: "client-secret" + + # HuggingFace Token Secret Reference + huggingFaceTokenSecretKeyRef: + name: "hf-secret" + key: "token" - # Configure to use shared postgresql instance - postgresql: - enabled: false # Use shared postgresql instance instead of deploying own + # Credential Encryption Key Secret Reference + credentialEncryptionKeySecretKeyRef: + name: "platform-encryption" + key: "key" +auth-service: + enabled: true # Set to false to disable the Auth Service component config: - keyVault: - provider: "azure" - azure: - vaultUrl: "https://helm-umbrella-akv.vault.azure.net/" - tenantId: "6712fe8e-3505-4daf-b881-675312392087" - existingSecret: "azure-kv-secret" - database: - host: "governance-platform-postgresql" # Use shared postgresql instance - port: 5432 - name: "authservice" - user: "postgres" - existingSecret: "compliance-secrets" - existingSecretKeys: - password: "postgresPassword" + server: + port: 8080 + host: "0.0.0.0" + environment: "production" + swaggerEnabled: true # Enable swagger for non-production environments + + logging: + level: info + format: json + skipPaths: "/health,/health/live,/health/ready" idp: - provider: "" # Provider for identity provider - issuer: "" # Issuer URL for identity provider - existingSecret: "auth0-credentials" + provider: "auth0" + issuer: "https://governance-studio.us.auth0.com/" + clientId: "" # Will use secret + clientSecret: "" # Will use secret + existingSecret: "auth0-secret" skipIssuerVerification: true auth0: - domain: "" # Domain for Auth0 + domain: "governance-studio.us.auth0.com" enableManagementAPI: true # Enable Auth0 Management API - managementAudience: "" # Management API audience for Auth0 - apiIdentifier: "" # API identifier for Auth0 + managementAudience: "https://governance-studio.us.auth0.com/api/v2/" + apiIdentifier: "https://governance-studio.us.auth0.com/api/v2/" defaultConnection: "Username-Password-Authentication" defaultRoles: ["user"] sendInvitationEmail: true syncAtStartup: true syncPageSize: 100 + # Service Account configuration + serviceAccounts: + autoCreate: true + governanceWorker: + enabled: true + name: "governance-worker" + description: "Automated governance service worker for processing indicator evaluations" + # No organizationId - platform-wide access + scopes: + - "governance:declarations:create" + - "integrity:statements:create" + # Auth0 M2M credentials will be provided via secret + auth0ClientId: "" # Will use secret + auth0ClientSecret: "" # Will use secret + audience: "https://governance.eqtylab.io" # Your API audience in Auth0 + # Generate a secure encryption key: openssl rand -base64 32 + # encryptionKey: "your-base64-encoded-32-byte-key" + existingSecret: "governance-worker-credentials" + existingSecretKeys: + encryptionKey: "key" + governanceWorkerClientId: "governance-worker-client-id" + governanceWorkerClientSecret: "governance-worker-client-secret" + + database: + host: "governance-platform-postgresql" + port: 5432 + name: "governance" + user: "postgres" + sslMode: "disable" # Internal cluster communication + maxOpenConns: 25 + maxIdleConns: 5 + connMaxLifetime: "5m" + existingSecret: "auth-service-db-credentials" + # Migration settings + autoMigrate: true # Run migrations on startup + migrationsPath: "/internal/database/migrations" # Path in container + + session: + duration: "24h" + existingSecret: "auth-service-session-secret" + + cors: + enabled: false # Ingress handles CORS in k8s + origins: "*" # Not used when enabled=false + + # Security configuration + security: + # These will be provided via extraEnvVars from existing secrets + apiSecret: "" + jwtSecret: "" + # Integration with other services integrations: # Integrity Service URL (for DID key fallback) - integrityServiceUrl: "http://governance-platform-integrity" + integrityServiceUrl: "http://governance-platform-integrity-service" # Governance Service URL governanceServiceUrl: "http://governance-platform-governance-service:10001" + # Key Vault configuration (for DID keys) + keyVault: + provider: "azure" # azure, hashicorp, local + azure: + vaultUrl: "https://example-kv.vault.azure.net/" + tenantId: "example-tenant-id" + clientId: "" # Will use secret + clientSecret: "" # Will use secret + # Use existing secret for Azure credentials + existingSecret: "platform-encryption" + existingSecretKeys: + clientId: "azure-key-vault-client-id" + clientSecret: "azure-key-vault-client-secret" + ingress: enabled: true className: "nginx" @@ -538,7 +507,7 @@ auth-service: nginx.ingress.kubernetes.io/use-regex: "true" nginx.ingress.kubernetes.io/rewrite-target: "/$2" nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/proxy-body-size: "64m" hosts: - host: "governance.example.com" paths: @@ -549,6 +518,47 @@ auth-service: hosts: - "governance.example.com" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + + autoscaling: + enabled: false # Enable later if needed + minReplicas: 2 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + + postgresql: + enabled: false # Using existing PostgreSQL in cluster + + healthCheck: + liveness: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + readiness: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 5 + + metrics: + enabled: false + port: 9090 + path: /metrics + + extraEnvVars: + - name: API_SECRET + valueFrom: + secretKeyRef: + name: auth-service-api-secret + key: api-secret + - name: SWAGGER_BASE_PATH + value: "/auth" + # ============================================================================= # INFRASTRUCTURE COMPONENTS # ============================================================================= @@ -561,6 +571,7 @@ postgresql: enabled: true # Set to false to use an external PostgreSQL instance # Global PostgreSQL values global: + defaultStorageClass: "" postgresql: auth: database: "governance" @@ -569,12 +580,20 @@ postgresql: secretKeys: adminPasswordKey: "postgresPassword" userPasswordKey: "postgresPassword" + + # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # HARDCODED VALUES, USING LEGACY BITNAMI POSTGRESQL IMAGE. + # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + image: + registry: docker.io + repository: bitnamilegacy/postgresql + tag: 15.3.0-debian-11-r17 + # Primary PostgreSQL instance configuration primary: persistence: enabled: true size: 10Gi - storageClass: "default" resources: requests: cpu: 500m @@ -589,11 +608,32 @@ postgresql: CREATE DATABASE governance; GRANT ALL PRIVILEGES ON DATABASE governance TO postgres; - CREATE DATABASE compliance; - GRANT ALL PRIVILEGES ON DATABASE compliance TO postgres; - CREATE DATABASE "IntegrityServiceDB"; GRANT ALL PRIVILEGES ON DATABASE "IntegrityServiceDB" TO postgres; - CREATE DATABASE authservice; - GRANT ALL PRIVILEGES ON DATABASE authservice TO postgres; +# ============================================================================= +# KEYCLOAK INTEGRATION +# ============================================================================= +# Configuration for Keycloak identity provider integration +# This creates the necessary organization in the governance database +keycloak: + # Enable automatic organization creation for Keycloak realm + createOrganization: false # Set to true to create org on install/upgrade + + # Realm name in Keycloak (must match the organization name in database) + realmName: "governance" + + # Display name for the organization + displayName: "Governance Platform" + + # Create platform-admin user in auth service tables + createPlatformAdmin: false # Set to true to create platform admin user + + # Keycloak user ID for platform-admin (will be set by bootstrap) + # Leave empty to auto-generate, or set after running Keycloak bootstrap + platformAdminKeycloakId: "" + + # Additional organization settings (optional) + organizationSettings: + description: "Main governance organization" + settings: "{}" # JSON string for additional settings diff --git a/charts/governance-service/Chart.yaml b/charts/governance-service/Chart.yaml index 7991e68..23eba92 100644 --- a/charts/governance-service/Chart.yaml +++ b/charts/governance-service/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 appVersion: 2.0.0 description: A Helm chart for the Governance Service API service maintainers: - - email: support@eqtylab.io + - email: hello@eqtylab.io name: EQTY Lab name: governance-service type: application -version: 1.0.0 +version: 1.4.0 diff --git a/charts/governance-service/README.md b/charts/governance-service/README.md new file mode 100644 index 0000000..43325fa --- /dev/null +++ b/charts/governance-service/README.md @@ -0,0 +1,174 @@ +# Governance Service + +This Helm chart deploys the Governance Service API service, which provides assurance validation and analysis capabilities for the Governance Studio platform. + +## Overview + +The Governance Service is a core component of the Governance Studio platform that handles: + +- Assurance validation and processing +- API endpoints for assurance-related operations +- Integration with the broader governance ecosystem + +## Prerequisites + +- Kubernetes 1.21+ +- Helm 3.8+ +- PostgreSQL database (provided by umbrella chart or external) +- Persistent volume provisioner support in the underlying infrastructure + +## Installation + +### Standalone Installation + +```bash +# Install the chart +helm install governance-service ./charts/governance-service \ + --create-namespace \ + --namespace governance \ + --values values.yaml +``` + +### As Part of Governance Studio (Recommended) + +This chart is typically deployed as part of the Governance Studio umbrella chart: + +```bash +# Install complete platform +helm install governance-studio ./charts/governance-studio \ + --create-namespace \ + --namespace governance \ + --values values.yaml +``` + +## Configuration + +### Core Parameters + +| Parameter | Description | Default | +| ------------------ | -------------------------- | -------------------------------------- | +| `image.repository` | Container image repository | `"ghcr.io/eqtylab/governance-service"` | +| `image.tag` | Container image tag | `"2.0.0"` | +| `image.pullPolicy` | Image pull policy | `"IfNotPresent"` | +| `replicaCount` | Number of pod replicas | `1` | + +### Database Configuration + +| Parameter | Description | Default | +| ------------------------- | ----------------------------------------- | -------------- | +| `database.host` | Database hostname | `"postgresql"` | +| `database.port` | Database port | `5432` | +| `database.name` | Database name | `"assurance"` | +| `database.existingSecret` | Existing secret with database credentials | `""` | + +### Service Configuration + +| Parameter | Description | Default | +| -------------------- | ----------------------- | ------------- | +| `service.type` | Kubernetes service type | `"ClusterIP"` | +| `service.port` | Service port | `8080` | +| `service.targetPort` | Container target port | `8080` | + +### Ingress Configuration + +| Parameter | Description | Default | +| ------------------- | ------------------ | --------- | +| `ingress.enabled` | Enable ingress | `false` | +| `ingress.className` | Ingress class name | `"nginx"` | +| `ingress.hosts` | Ingress hostnames | `[]` | +| `ingress.tls` | TLS configuration | `[]` | + +### Resource Limits + +| Parameter | Description | Default | +| --------------------------- | -------------- | --------- | +| `resources.limits.cpu` | CPU limit | `"1000m"` | +| `resources.limits.memory` | Memory limit | `"1Gi"` | +| `resources.requests.cpu` | CPU request | `"100m"` | +| `resources.requests.memory` | Memory request | `"128Mi"` | + +### Horizontal Pod Autoscaler + +| Parameter | Description | Default | +| -------------------------------------------- | ---------------------- | ------- | +| `autoscaling.enabled` | Enable HPA | `false` | +| `autoscaling.minReplicas` | Minimum replicas | `1` | +| `autoscaling.maxReplicas` | Maximum replicas | `100` | +| `autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization | `80` | + +## Upgrading + +### Standalone Upgrade + +```bash +helm upgrade governance-service ./charts/governance-service \ + --namespace governance \ + --values values.yaml +``` + +### As Part of Governance Studio + +```bash +helm upgrade governance-studio ./charts/governance-studio \ + --namespace governance \ + --values values.yaml +``` + +## Uninstallation + +### Standalone Uninstall + +```bash +helm uninstall governance-service -n governance +``` + +### Full Platform Uninstall + +```bash +helm uninstall governance-studio -n governance +``` + +## Troubleshooting + +### Check Pod Status + +```bash +kubectl get pods -n governance -l app.kubernetes.io/name=governance-service +kubectl logs -f deployment/governance-service -n governance +``` + +### Verify Configuration + +```bash +kubectl get configmap -n governance | grep governance-service +kubectl describe deployment governance-service -n governance +``` + +### Common Issues + +1. **Database Connection**: Verify database credentials and connectivity +2. **Image Pull**: Ensure image pull secrets are configured if using private registry +3. **Resource Limits**: Check if pods are being OOMKilled due to memory limits + +## API Endpoints + +When deployed, the Governance Service provides the following API endpoints: + +- `GET /health` - Health check endpoint +- Additional endpoints as documented in the API specification + +## Integration + +The Governance Service API integrates with: + +- **PostgreSQL**: For data persistence +- **Governance UI**: Frontend interface +- **Compliance Garage**: For compliance-related assurance data +- **Integrity Service**: For data integrity validation + +## Support + +For support and documentation: + +- GitHub Repository: https://github.com/eqtylab/governance-service +- Email: support@eqtylab.io diff --git a/charts/governance-service/templates/NOTES.txt b/charts/governance-service/templates/NOTES.txt index d1590f8..ba25d48 100644 --- a/charts/governance-service/templates/NOTES.txt +++ b/charts/governance-service/templates/NOTES.txt @@ -39,9 +39,9 @@ This chart includes two services: # Check API service status kubectl get pods -l app.kubernetes.io/name={{ include "governance-service.name" . }} -n {{ .Release.Namespace }} - + # Check worker service status kubectl get pods -l app.kubernetes.io/name={{ include "governance-worker.name" . }} -n {{ .Release.Namespace }} - + # View worker logs - kubectl logs -l app.kubernetes.io/name={{ include "governance-worker.name" . }} -n {{ .Release.Namespace }} --tail=50 + kubectl logs -l app.kubernetes.io/name={{ include "governance-worker.name" . }} -n {{ .Release.Namespace }} --tail=50 \ No newline at end of file diff --git a/charts/governance-service/templates/deployment.yaml b/charts/governance-service/templates/deployment.yaml index 9340c1e..ccc1c78 100644 --- a/charts/governance-service/templates/deployment.yaml +++ b/charts/governance-service/templates/deployment.yaml @@ -64,12 +64,60 @@ spec: name: "postgresql" key: "postgres-password" {{- end }} + - name: STORAGE_PROVIDER + value: {{ .Values.config.storageProvider | quote }} + {{- if .Values.config.gcsBucketName }} - name: GCS_BUCKET_NAME value: {{ .Values.config.gcsBucketName }} + {{- end }} + {{- if or (eq .Values.config.storageProvider "gcs") (not .Values.config.storageProvider) }} - name: GCS_CREDENTIALS_FILE value: "/var/secrets/google/attachments-sa.json" + {{- end }} + {{- if or .Values.azureStorageSecretKeyRef.name .Values.config.azureStorageAccountName }} + - name: AZURE_STORAGE_ACCOUNT_NAME + {{- if .Values.azureStorageSecretKeyRef.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.azureStorageSecretKeyRef.name | quote }} + key: {{ .Values.azureStorageSecretKeyRef.accountNameKey | quote }} + {{- else }} + value: {{ .Values.config.azureStorageAccountName }} + {{- end }} + {{- end }} + {{- if or .Values.azureStorageSecretKeyRef.name .Values.config.azureStorageAccountKey }} + - name: AZURE_STORAGE_ACCOUNT_KEY + {{- if .Values.azureStorageSecretKeyRef.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.azureStorageSecretKeyRef.name | quote }} + key: {{ .Values.azureStorageSecretKeyRef.accountKeyKey | quote }} + {{- else }} + value: {{ .Values.config.azureStorageAccountKey }} + {{- end }} + {{- end }} + {{- if or .Values.azureStorageSecretKeyRef.name .Values.config.azureStorageConnectionString }} + - name: AZURE_STORAGE_CONNECTION_STRING + {{- if .Values.azureStorageSecretKeyRef.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.azureStorageSecretKeyRef.name | quote }} + key: {{ .Values.azureStorageSecretKeyRef.connectionStringKey | quote }} + {{- else }} + value: {{ .Values.config.azureStorageConnectionString }} + {{- end }} + {{- end }} + {{- if .Values.config.azureStorageContainerName }} + - name: AZURE_STORAGE_CONTAINER_NAME + value: {{ .Values.config.azureStorageContainerName }} + {{- end }} + - name: AZURE_USE_MANAGED_IDENTITY + value: {{ .Values.config.azureUseManagedIdentity | quote }} + {{- if not .Values.keycloakSecretKeyRef.enabled }} + {{- if .Values.config.auth0Domain }} - name: AUTH0_DOMAIN value: {{ .Values.config.auth0Domain }} + {{- end }} - name: AUTH0_CLIENT_ID valueFrom: secretKeyRef: @@ -94,14 +142,13 @@ spec: value: {{ .Values.auth0SyncAtStartup | quote }} - name: AUTH0_SYNC_PAGE_SIZE value: {{ .Values.auth0SyncPageSize | quote }} + {{- end }} - name: INTEGRITY_SERVICE_URL value: {{ .Values.config.integrityServiceUrl }} - - name: AUTH_SERVICE_URL - value: {{ .Values.config.authServiceUrl }} {{- if .Values.config.serviceAccount.enabled }} - - name: INTEGRITY_AUTH_SERVICE_URL + - name: AUTH_SERVICE_URL value: {{ .Values.config.serviceAccount.authServiceUrl | default .Values.config.authServiceUrl }} - - name: INTEGRITY_AUTH_SERVICE_API_KEY + - name: AUTH_SERVICE_API_KEY valueFrom: secretKeyRef: {{- if .Values.config.serviceAccount.existingSecret }} @@ -112,6 +159,40 @@ spec: key: auth-service-api-key {{- end }} {{- end }} + {{- if .Values.keycloakSecretKeyRef.enabled }} + - name: KEYCLOAK_URL + value: {{ .Values.keycloakSecretKeyRef.url | default "https://keycloak.example.com" }} + - name: KEYCLOAK_CLIENT_ID + valueFrom: + secretKeyRef: + {{- if .Values.keycloakSecretKeyRef.existingSecret }} + name: {{ .Values.keycloakSecretKeyRef.existingSecret }} + key: {{ .Values.keycloakSecretKeyRef.existingSecretKeys.clientId | default "keycloak-client-id" }} + {{- else }} + name: {{ include "governance-service.fullname" . }}-keycloak + key: keycloak-client-id + {{- end }} + - name: KEYCLOAK_CLIENT_SECRET + valueFrom: + secretKeyRef: + {{- if .Values.keycloakSecretKeyRef.existingClientSecretKeyRef }} + name: {{ .Values.keycloakSecretKeyRef.existingClientSecretKeyRef.name }} + key: {{ .Values.keycloakSecretKeyRef.existingClientSecretKeyRef.clientSecret | default "client-secret" }} + {{- else }} + name: {{ include "governance-service.fullname" . }}-keycloak + key: keycloak-client-secret + {{- end }} + - name: KEYCLOAK_REALM + valueFrom: + secretKeyRef: + {{- if .Values.keycloakSecretKeyRef.existingSecret }} + name: {{ .Values.keycloakSecretKeyRef.existingSecret }} + key: {{ .Values.keycloakSecretKeyRef.existingSecretKeys.realm | default "governance" }} + {{- else }} + name: {{ include "governance-service.fullname" . }}-keycloak + key: realm + {{- end }} + {{- end }} {{- if .Values.huggingFaceTokenSecretKeyRef }} # Get token from external secret - name: HUGGING_FACE_TOKEN @@ -135,10 +216,50 @@ spec: - name: MIGRATIONS_PATH value: {{ .Values.migrations.path | quote }} {{- end }} + + {{- if .Values.config.ai.enabled }} + - name: AI_ENABLED + value: {{ .Values.config.ai.enabled | quote }} + - name: AI_API_KEY + {{- if .Values.config.ai.secretKeyRef }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.ai.secretKeyRef.name | quote }} + key: {{ .Values.config.ai.secretKeyRef.key | quote }} + {{- else }} + value: {{ .Values.config.ai.apiKey | quote }} + {{- end }} + - name: AI_PROVIDER + value: {{ .Values.config.ai.provider | quote }} + - name: AI_MODEL + value: {{ .Values.config.ai.model | quote }} + - name: AI_TEMPERATURE + value: {{ .Values.config.ai.temperature | quote }} + - name: AI_MAX_TOKENS + value: {{ .Values.config.ai.maxTokens | quote }} + - name: AI_TIMEOUT_SECONDS + value: {{ .Values.config.ai.timeoutSeconds | quote }} + - name: AI_RETRY_ATTEMPTS + value: {{ .Values.config.ai.retryAttempts | quote }} + - name: AI_USE_V2 + value: {{ .Values.config.ai.useV2 | quote }} + {{- end }} + - name: ENABLE_OS_GUARDRAIL_EVENTS + value: {{ .Values.config.indicators.osGuardrailsEnabled | quote }} + {{- if .Values.config.indicators.osGuardrailsEnabled }} + - name: GUARDRAIL_EVENT_BATCH_SIZE + value: {{ .Values.config.indicators.osGuardrailsBatchSize | quote }} + {{- end }} + {{- if .Values.config.indicators.osGuardrailsEnabled }} + - name: GUARDRAIL_EVENT_TIMEOUT_SECONDS + value: {{ .Values.config.indicators.osGuardrailsTimeoutSeconds | quote }} + {{- end }} volumeMounts: + {{- if or (eq .Values.config.storageProvider "gcs") (not .Values.config.storageProvider) }} - name: gcp-sa-volume mountPath: /var/secrets/google readOnly: true + {{- end }} ports: - name: http containerPort: {{ .Values.service.port }} @@ -177,9 +298,11 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} volumes: + {{- if or (eq .Values.config.storageProvider "gcs") (not .Values.config.storageProvider) }} - name: gcp-sa-volume secret: secretName: {{ .Values.serviceAccount.name | quote }} items: - key: "gcs-sa.json" path: "attachments-sa.json" + {{- end }} diff --git a/charts/governance-service/templates/worker-configmap.yaml b/charts/governance-service/templates/worker-configmap.yaml index ef64a97..3ab6f73 100644 --- a/charts/governance-service/templates/worker-configmap.yaml +++ b/charts/governance-service/templates/worker-configmap.yaml @@ -1,3 +1,4 @@ +{{- if .Values.worker.enabled }} apiVersion: v1 kind: ConfigMap metadata: @@ -14,3 +15,4 @@ data: WORKER_JOB_CLAIM_TTL: "{{ .Values.config.scheduler.jobClaimTTL }}" WORKER_CAPABILITIES: "{{ .Values.config.scheduler.capabilities }}" API_BASE_URL: "http://{{ include "governance-service.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}" +{{- end }} diff --git a/charts/governance-service/templates/worker-deployment.yaml b/charts/governance-service/templates/worker-deployment.yaml index 1323aac..b709e1f 100644 --- a/charts/governance-service/templates/worker-deployment.yaml +++ b/charts/governance-service/templates/worker-deployment.yaml @@ -1,3 +1,4 @@ +{{- if .Values.worker.enabled }} apiVersion: apps/v1 kind: Deployment metadata: @@ -95,15 +96,68 @@ spec: secretKeyRef: name: {{ .Values.worker.m2mCredentials.secretName | quote }} key: {{ .Values.worker.m2mCredentials.clientSecretKey | default "client-secret" | quote }} + {{- if not .Values.keycloakSecretKeyRef.enabled }} - name: AUTH0_DOMAIN value: {{ .Values.config.auth0Domain | quote }} - name: AUTH0_AUDIENCE value: {{ .Values.worker.m2mCredentials.audience | default "https://governance.eqtylab.io" | quote }} {{- end }} + {{- end }} + - name: STORAGE_PROVIDER + value: {{ .Values.config.storageProvider | quote }} + {{- if .Values.config.gcsBucketName }} + - name: GCS_BUCKET_NAME + value: {{ .Values.config.gcsBucketName }} + {{- end }} + {{- if or (eq .Values.config.storageProvider "gcs") (not .Values.config.storageProvider) }} + - name: GCS_CREDENTIALS_FILE + value: "/var/secrets/google/attachments-sa.json" + {{- end }} + {{- if or .Values.azureStorageSecretKeyRef.name .Values.config.azureStorageAccountName }} + - name: AZURE_STORAGE_ACCOUNT_NAME + {{- if .Values.azureStorageSecretKeyRef.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.azureStorageSecretKeyRef.name | quote }} + key: {{ .Values.azureStorageSecretKeyRef.accountNameKey | quote }} + {{- else }} + value: {{ .Values.config.azureStorageAccountName }} + {{- end }} + {{- end }} + {{- if or .Values.azureStorageSecretKeyRef.name .Values.config.azureStorageAccountKey }} + - name: AZURE_STORAGE_ACCOUNT_KEY + {{- if .Values.azureStorageSecretKeyRef.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.azureStorageSecretKeyRef.name | quote }} + key: {{ .Values.azureStorageSecretKeyRef.accountKeyKey | quote }} + {{- else }} + value: {{ .Values.config.azureStorageAccountKey }} + {{- end }} + {{- end }} + {{- if or .Values.azureStorageSecretKeyRef.name .Values.config.azureStorageConnectionString }} + - name: AZURE_STORAGE_CONNECTION_STRING + {{- if .Values.azureStorageSecretKeyRef.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.azureStorageSecretKeyRef.name | quote }} + key: {{ .Values.azureStorageSecretKeyRef.connectionStringKey | quote }} + {{- else }} + value: {{ .Values.config.azureStorageConnectionString }} + {{- end }} + {{- end }} + {{- if .Values.config.azureStorageContainerName }} + - name: AZURE_STORAGE_CONTAINER_NAME + value: {{ .Values.config.azureStorageContainerName }} + {{- end }} + - name: AZURE_USE_MANAGED_IDENTITY + value: {{ .Values.config.azureUseManagedIdentity | quote }} volumeMounts: + {{- if or (eq .Values.config.storageProvider "gcs") (not .Values.config.storageProvider) }} - name: gcp-sa-volume mountPath: /var/secrets/google readOnly: true + {{- end }} resources: {{- toYaml .Values.worker.resources | nindent 12 }} {{- with .Values.nodeSelector }} @@ -119,9 +173,12 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} volumes: + {{- if or (eq .Values.config.storageProvider "gcs") (not .Values.config.storageProvider) }} - name: gcp-sa-volume secret: secretName: {{ .Values.serviceAccount.name | quote }} items: - key: "gcs-sa.json" path: "attachments-sa.json" + {{- end }} +{{- end }} diff --git a/charts/governance-service/values.yaml b/charts/governance-service/values.yaml index c0b93fd..b99ef62 100644 --- a/charts/governance-service/values.yaml +++ b/charts/governance-service/values.yaml @@ -46,6 +46,8 @@ api: # Worker service configuration - background job processor worker: + enabled: true + # Number of worker replicas to run replicaCount: 1 @@ -181,16 +183,49 @@ credentialEncryptionKeySecretKeyRef: name: "" # Secret name (e.g., "platform-encryption") key: "" # Key within secret (e.g., "key") -# Auth0 Integration Configuration +# ============================================================================= +# AUTHENTICATION PROVIDER CONFIGURATION +# ============================================================================= +# Choose between Auth0 or Keycloak as the authentication provider +# Only configure one provider - Auth0 OR Keycloak, not both + +# --- Auth0 Integration Configuration --- +# Configure these settings when using Auth0 as the identity provider +# NOTE: These settings are ignored when keycloakSecretKeyRef.enabled is true auth0SyncAtStartup: true # Sync Auth0 users at application startup auth0SyncPageSize: 100 # Number of users to sync per page from Auth0 # Reference to Kubernetes secret containing Auth0 credentials +# Required when using Auth0 (ignored when using Keycloak) auth0SecretKeyRef: name: "" # Secret name (e.g., "auth0-secret") clientIdKey: "" # Key for Auth0 client ID clientSecretKey: "" # Key for Auth0 client secret +# --- Keycloak Integration Configuration --- +# Configure these settings when using Keycloak as the identity provider +# When enabled, Auth0 configuration is ignored +keycloakSecretKeyRef: + enabled: false # Set to true to use Keycloak instead of Auth0 + existingSecret: "keycloak-worker-config" + existingSecretKeys: + clientId: "KEYCLOAK_CLIENT_ID" + realm: "KEYCLOAK_REALM" + url: "KEYCLOAK_URL" + existingClientSecretKeyRef: + name: "keycloak-worker-client" # Secret name (e.g., "keycloak-worker-client") + clientSecretKey: "client-secret" # Key for Keycloak client secret + +# Azure Storage Secret References +# Reference to Kubernetes secret containing Azure storage credentials +# When using the governance-platform umbrella chart, these values are typically +# provided through the global blob storage secret configuration +azureStorageSecretKeyRef: + name: "" # Secret name (e.g., "azure-storage-secret") + accountNameKey: "" # Key for storage account name + accountKeyKey: "" # Key for storage account key + connectionStringKey: "" # Key for connection string + # ============================================================================= # APPLICATION CONFIGURATION # ============================================================================= @@ -233,14 +268,28 @@ config: configPath: "/app/configs/indicators" # Path to indicator configuration files reloadInterval: 300 # How often to reload configs (seconds) validateOnLoad: true # Validate configurations when loading + osGuardrailsEnabled: false # Enable OS guardrails + osGuardrailsBatchSize: 100 # Batch size for OS guardrails + osGuardrailsTimeoutSeconds: 5 # Timeout for OS guardrails (seconds) # Cloud Storage Configuration + storageProvider: "gcs" # Storage provider: "gcs" or "azure" + + # Google Cloud Storage Configuration gcsBucketName: "" # Google Cloud Storage bucket name - # Auth0 Configuration (can also be provided via secrets) - auth0Domain: "" # Auth0 tenant domain - auth0ClientId: "" # Auth0 client ID - auth0ClientSecret: "" # Auth0 client secret (prefer secret reference) + # Azure Storage Configuration + azureStorageAccountName: "" # Azure storage account name + azureStorageAccountKey: "" # Azure storage account key (prefer secret reference) + azureStorageConnectionString: "" # Azure storage connection string (prefer secret reference) + azureStorageContainerName: "" # Azure storage container name + azureUseManagedIdentity: false # Use Azure managed identity for authentication + + # Auth0 Configuration (only used when not using Keycloak) + # These can also be provided via secrets using auth0SecretKeyRef + auth0Domain: "" # Auth0 tenant domain (e.g., "your-tenant.us.auth0.com") + auth0ClientId: "" # Auth0 client ID (prefer using auth0SecretKeyRef) + auth0ClientSecret: "" # Auth0 client secret (prefer using auth0SecretKeyRef) # Integration URLs integrityServiceUrl: "" # URL of the Integrity Service @@ -260,3 +309,18 @@ config: existingSecret: "" existingSecretKeys: apiKey: "auth-service-api-key" + + # AI Configuration + ai: + enabled: false + provider: "anthropic" + model: "claude-3-7-sonnet-latest" + temperature: 0.7 + maxTokens: 4000 + timeoutSeconds: 60 + retryAttempts: 3 + secretKeyRef: + name: "" # governance-service-ai-secret + key: "" # api-secret-key + apiKey: "" + useV2: true diff --git a/charts/governance-studio/Chart.yaml b/charts/governance-studio/Chart.yaml index f51664b..9521968 100644 --- a/charts/governance-studio/Chart.yaml +++ b/charts/governance-studio/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: governance-studio description: A Helm chart for deploying EQTY Lab Governance Studio frontend type: application -version: 1.0.0 +version: 1.2.0 appVersion: "1.0.0" keywords: - governance diff --git a/charts/governance-studio/README.md b/charts/governance-studio/README.md new file mode 100644 index 0000000..a385594 --- /dev/null +++ b/charts/governance-studio/README.md @@ -0,0 +1,188 @@ +# EQTY Lab Governance Studio - Customer Deployment + +This Helm chart enables customers to deploy the EQTY Lab Governance Studio frontend in their own Kubernetes environments with custom domain names and configuration. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.8+ +- Ingress controller (nginx, traefik, etc.) +- Valid TLS certificates or cert-manager for automatic certificate management + +## Quick Start + +### 1. Add the Helm Repository (if published) + +```bash +helm repo add eqtylab https://charts.eqtylab.io +helm repo update +``` + +### 2. Create Your Configuration + +Copy the example values file and customize it for your environment: + +```bash +cp customer-values-example.yaml my-values.yaml +``` + +Edit `my-values.yaml` with your specific configuration: + +- **Domain Name**: Update `config.appHostname` and `ingress.hosts` +- **API URLs**: Set `config.apiUrl` to your backend API +- **Auth0**: Configure your Auth0 tenant details +- **Branding**: Customize logos and colors for your organization +- **Features**: Enable/disable features as needed + +### 3. Deploy + +```bash +helm install governance-studio eqtylab/governance-studio \ + -f my-values.yaml \ + --namespace governance \ + --create-namespace +``` + +### 4. Access Your Deployment + +Once deployed, access your Governance Studio at your configured domain (e.g., `https://governance.your-company.com`). + +## Configuration Reference + +### Core Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `config.apiUrl` | Backend API URL | `""` | +| `config.auth0Domain` | Auth0 tenant domain | `""` | +| `config.auth0ClientId` | Auth0 application client ID | `""` | +| `config.appHostname` | Application hostname | `""` | + +### Feature Flags + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `config.features.compliance` | Enable compliance features | `true` | +| `config.features.governance` | Enable governance features | `true` | +| `config.features.guardian` | Enable guardian features | `true` | +| `config.features.lineage` | Enable lineage features | `true` | + +### Branding + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `config.branding.logoUrl` | URL to your company logo | `"/vite.svg"` | +| `config.branding.primaryColor` | Primary brand color (hex) | `"#0f172a"` | +| `config.branding.companyName` | Company name for display | `"EQTY Lab"` | + +### Infrastructure + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `replicaCount` | Number of pod replicas | `2` | +| `image.repository` | Container image repository | `""` | +| `image.tag` | Container image tag | `"latest"` | +| `ingress.enabled` | Enable ingress | `true` | +| `ingress.className` | Ingress class name | `"nginx"` | + +## Runtime Configuration + +This deployment uses runtime configuration injection, which means: + +- ✅ **Single Container Image**: One image works for all customer environments +- ✅ **No Rebuilds**: Configuration changes don't require rebuilding containers +- ✅ **Easy Updates**: Update configuration by changing Helm values and redeploying +- ✅ **Environment Flexibility**: Switch between dev/staging/prod easily + +Configuration is injected at container startup via environment variables and converted to a JavaScript configuration file that the frontend loads. + +## Upgrading + +To upgrade to a new version: + +### Standalone Deployment + +```bash +# Check current version +helm list -n governance + +# Upgrade to latest version +helm upgrade governance-studio eqtylab/governance-studio \ + -f my-values.yaml \ + --namespace governance + +# Upgrade to specific version +helm upgrade governance-studio eqtylab/governance-studio \ + --version 1.0.0 \ + -f my-values.yaml \ + --namespace governance +``` + +### As Part of Governance Studio Platform + +If deployed as part of the umbrella chart: + +```bash +# Upgrade the entire platform +helm upgrade governance-studio ./charts/governance-studio \ + --namespace governance \ + --values values.yaml \ + --values my-secrets.yaml +``` + +### Upgrade Best Practices + +1. **Test Configuration**: Verify your values file is up to date +2. **Check Breaking Changes**: Review release notes for breaking changes +3. **Rolling Updates**: The chart supports zero-downtime rolling updates +4. **Backup Configuration**: Keep a backup of your current configuration + +## Troubleshooting + +### Check Pod Status + +```bash +kubectl get pods -n governance +kubectl logs -f deployment/governance-studio -n governance +``` + +### Verify Configuration + +Check that your configuration was applied correctly: + +```bash +kubectl get configmap governance-studio-config -n governance -o yaml +``` + +### Check Ingress + +```bash +kubectl get ingress -n governance +kubectl describe ingress governance-studio -n governance +``` + +### Common Issues + +1. **404 on custom domain**: Check that your DNS points to the ingress controller +2. **TLS certificate issues**: Verify cert-manager is working or upload certificates manually +3. **Auth0 login fails**: Check that redirect URIs are configured correctly in Auth0 +4. **API connection fails**: Verify the API URL is correct and reachable from the frontend + +### Health Monitoring + +The application provides health check endpoints: + +```bash +# Check application health (if health endpoint is configured) +curl https://your-domain.com/health + +# Monitor pod resource usage +kubectl top pods -n governance -l app.kubernetes.io/name=governance-studio + +# View application logs +kubectl logs -f deployment/governance-studio -n governance --tail=100 +``` + +## Support + +For deployment support, please contact support@eqtylab.io or create an issue in the GitHub repository. \ No newline at end of file diff --git a/charts/governance-studio/customer-values-example.yaml b/charts/governance-studio/customer-values-example.yaml new file mode 100644 index 0000000..d242c79 --- /dev/null +++ b/charts/governance-studio/customer-values-example.yaml @@ -0,0 +1,93 @@ +# Example customer values.yaml for deploying Governance Studio +# Copy this file and customize it for your specific environment + +# Application configuration +config: + # Your API endpoint + apiUrl: "https://api.your-company.com" + + # Auth0 configuration for your tenant + auth0Domain: "your-company.auth0.com" + auth0ClientId: "your_auth0_client_id_here" + auth0Audience: "https://api.your-company.com" + + # Application settings + environment: "production" + appTitle: "Your Company Governance Studio" + appHostname: "governance.your-company.com" + + # Feature flags - enable/disable features as needed + features: + compliance: true + governance: true + guardian: true + lineage: false # Example: disable lineage feature + + # Customize branding for your organization + branding: + logoUrl: "/your-logo.svg" # Place your logo in public folder + primaryColor: "#1a365d" # Your brand color + companyName: "Your Company Name" + +# Container image configuration +image: + repository: "your-registry.com/governance-ui" + tag: "v1.0.0" + pullPolicy: IfNotPresent + +# Configure ingress for your domain +ingress: + enabled: true + className: "nginx" # or your ingress controller + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + cert-manager.io/cluster-issuer: "letsencrypt-prod" + # Add any additional annotations your infrastructure requires + hosts: + - host: governance.your-company.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: governance-your-company-tls + hosts: + - governance.your-company.com + +# Resource limits appropriate for your workload +resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 200m + memory: 256Mi + +# Scaling configuration +replicaCount: 3 + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + +# If you need specific node placement +nodeSelector: {} + # kubernetes.io/os: linux + # node-type: frontend + +tolerations: [] + +affinity: {} + # Example anti-affinity to spread pods across nodes: + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 100 + # podAffinityTerm: + # labelSelector: + # matchExpressions: + # - key: app.kubernetes.io/name + # operator: In + # values: + # - governance-ui + # topologyKey: kubernetes.io/hostname \ No newline at end of file diff --git a/charts/governance-studio/templates/_helpers.tpl b/charts/governance-studio/templates/_helpers.tpl index e90237c..1c31f21 100644 --- a/charts/governance-studio/templates/_helpers.tpl +++ b/charts/governance-studio/templates/_helpers.tpl @@ -59,4 +59,4 @@ Create the name of the service account to use {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/governance-studio/templates/configmap.yaml b/charts/governance-studio/templates/configmap.yaml index 87a9c80..56a806f 100644 --- a/charts/governance-studio/templates/configmap.yaml +++ b/charts/governance-studio/templates/configmap.yaml @@ -6,17 +6,38 @@ metadata: {{- include "governance-studio.labels" . | nindent 4 }} data: API_URL: {{ .Values.config.apiUrl | quote }} - AUTH0_DOMAIN: {{ .Values.config.auth0Domain | quote }} - AUTH0_CLIENT_ID: {{ .Values.config.auth0ClientId | quote }} - AUTH0_AUDIENCE: {{ .Values.config.auth0Audience | quote }} + AUTH_SERVICE_URL: {{ .Values.config.authServiceUrl | quote }} + INTEGRITY_SERVICE_URL: {{ .Values.config.integrityServiceUrl | quote }} + GARAGE_SERVICE_URL: {{ .Values.config.garageServiceUrl | quote }} + ENVIRONMENT: {{ .Values.config.environment | quote }} APP_TITLE: {{ .Values.config.appTitle | quote }} APP_HOSTNAME: {{ .Values.config.appHostname | quote }} - FEATURE_COMPLIANCE: {{ .Values.config.features.compliance | quote }} + BASE_PATH: {{ .Values.config.basePath | quote }} + FEATURE_GOVERNANCE: {{ .Values.config.features.governance | quote }} - FEATURE_GUARDIAN: {{ .Values.config.features.guardian | quote }} FEATURE_LINEAGE: {{ .Values.config.features.lineage | quote }} + FEATURE_GUARDIAN_ENABLED: {{ .Values.config.features.guardianEnabled | quote }} + FEATURE_GUARDIAN_GARAGE: {{ .Values.config.features.guardianGarage | quote }} + FEATURE_GUARDIAN_RULEBOOKS: {{ .Values.config.features.guardianRulebooks | quote }} + FEATURE_GUARDIAN_OS_FUNCTIONS: {{ .Values.config.features.guardianOsFunctions | quote }} + FEATURE_GUARDIAN_SDK_FUNCTIONS: {{ .Values.config.features.guardianSdkFunctions | quote }} + FEATURE_GUARDIAN_AGENT_MANAGEMENT: {{ .Values.config.features.agentManagement | quote }} + BRANDING_LOGO_URL: {{ .Values.config.branding.logoUrl | quote }} BRANDING_PRIMARY_COLOR: {{ .Values.config.branding.primaryColor | quote }} BRANDING_COMPANY_NAME: {{ .Values.config.branding.companyName | quote }} - INTEGRITY_SERVICE_URL: {{ .Values.config.integrityServiceUrl | quote }} + + AUTH_PROVIDER: {{ .Values.config.authProvider | quote }} + # AUTH0 Settings + {{- if eq .Values.config.authProvider "auth0" }} + AUTH0_DOMAIN: {{ .Values.config.auth0Domain | quote }} + AUTH0_CLIENT_ID: {{ .Values.config.auth0ClientId | quote }} + AUTH0_AUDIENCE: {{ .Values.config.auth0Audience | quote }} + {{- end }} + # Keycloak Settings + {{- if eq .Values.config.authProvider "keycloak" }} + KEYCLOAK_URL: {{ .Values.config.keycloakUrl | quote }} + KEYCLOAK_REALM: {{ .Values.config.keycloakRealm | quote }} + KEYCLOAK_CLIENT_ID: {{ .Values.config.keycloakClientId | quote }} + {{- end }} diff --git a/charts/governance-studio/templates/ingress.yaml b/charts/governance-studio/templates/ingress.yaml index c392fff..97e39d4 100644 --- a/charts/governance-studio/templates/ingress.yaml +++ b/charts/governance-studio/templates/ingress.yaml @@ -56,4 +56,4 @@ spec: {{- end }} {{- end }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/governance-studio/templates/service.yaml b/charts/governance-studio/templates/service.yaml index fc21ab3..e21e8d3 100644 --- a/charts/governance-studio/templates/service.yaml +++ b/charts/governance-studio/templates/service.yaml @@ -12,4 +12,4 @@ spec: protocol: TCP name: http selector: - {{- include "governance-studio.selectorLabels" . | nindent 4 }} + {{- include "governance-studio.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/charts/governance-studio/templates/serviceaccount.yaml b/charts/governance-studio/templates/serviceaccount.yaml index 5edeb54..8912784 100644 --- a/charts/governance-studio/templates/serviceaccount.yaml +++ b/charts/governance-studio/templates/serviceaccount.yaml @@ -9,4 +9,4 @@ metadata: annotations: {{- toYaml . | nindent 4 }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/governance-studio/values.yaml b/charts/governance-studio/values.yaml index baa83a0..cd479d2 100644 --- a/charts/governance-studio/values.yaml +++ b/charts/governance-studio/values.yaml @@ -46,11 +46,19 @@ fullnameOverride: "" # These values are converted to environment variables and then to a JavaScript config file config: + basePath: "/" + # --- API Configuration --- # Backend API endpoint URL - MUST be accessible from user browsers # Should match the ingress configuration of your Assurance Engine deployment apiUrl: "https://api.governance.example.com" + authServerUrl: "https://api.governance.example.com" + + integrityServiceUrl: "https://api.governance.example.com" + + authProvider: "" # auth0 or keycloak + # --- Authentication Configuration (Auth0) --- # Auth0 tenant domain (without https://) # Example: "your-tenant.us.auth0.com" or "your-tenant.eu.auth0.com" @@ -64,6 +72,10 @@ config: # Typically the same as your API URL auth0Audience: "https://api.governance.example.com" + keycloakUrl: "https://keycloak.example.com" + keycloakRealm: "example" + keycloakClientId: "YOUR_KEYCLOAK_CLIENT_ID" + # --- Application Settings --- # Deployment environment - affects logging and debugging features # Options: "development", "staging", "production" @@ -80,10 +92,14 @@ config: # Enable or disable specific application features # Useful for progressive feature rollouts or customer-specific customizations features: - compliance: true # Enable compliance management features - governance: true # Enable governance workflow features - guardian: true # Enable guardian/approval workflow features + governance: true # Enable compliance management features lineage: true # Enable data lineage tracking features + guardianEnabled: false + guardianGarage: false + guardianRulebooks: false + guardianOsFunctions: false + guardianSdkFunctions: false + agentManagement: false # --- Branding Configuration --- # Customize the application appearance for your organization @@ -123,9 +139,9 @@ ingress: className: "nginx" # Ingress controller class (nginx, traefik, etc.) # Ingress annotations for additional configuration - annotations: - nginx.ingress.kubernetes.io/rewrite-target: / # Rewrite incoming paths - cert-manager.io/cluster-issuer: "letsencrypt-prod" # Automatic TLS certificate issuer + annotations: {} + # nginx.ingress.kubernetes.io/rewrite-target: / # Rewrite incoming paths + # cert-manager.io/cluster-issuer: "letsencrypt-prod" # Automatic TLS certificate issuer # Host configuration - defines which domains will serve this application hosts: @@ -136,7 +152,7 @@ ingress: # TLS/SSL configuration tls: - - secretName: platform-tls # Kubernetes secret containing TLS certificates + - secretName: governance-tls # Kubernetes secret containing TLS certificates hosts: - governance.example.com # Domain(s) covered by the TLS certificate @@ -146,8 +162,7 @@ ingress: # CPU and memory resource limits and requests # Uncomment and adjust based on your cluster capacity and application needs -resources: - {} +resources: {} # limits: # cpu: 500m # Maximum CPU (0.5 cores) # memory: 512Mi # Maximum memory diff --git a/charts/integrity-service/Chart.yaml b/charts/integrity-service/Chart.yaml index d7719f3..53b2d2f 100644 --- a/charts/integrity-service/Chart.yaml +++ b/charts/integrity-service/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 appVersion: 2.0.0 name: integrity-service -version: 1.0.0 +version: 1.1.5 description: "Governance Studio Integrity" home: https://github.com/eqtylab/governance-studio sources: diff --git a/charts/integrity-service/README.md b/charts/integrity-service/README.md new file mode 100644 index 0000000..3cb6986 --- /dev/null +++ b/charts/integrity-service/README.md @@ -0,0 +1,184 @@ +# Integrity Service + +This Helm chart deploys the Integrity Service, which provides data integrity and auditability capabilities for the Governance Studio platform. + +## Overview + +The Integrity Service is a core component of the Governance Studio platform that handles: +- Data integrity validation and verification +- Audit trail management +- Blockchain integration for immutable records +- Cryptographic proof generation and validation + +## Prerequisites + +- Kubernetes 1.21+ +- Helm 3.8+ +- PostgreSQL database (provided by umbrella chart or external) +- Persistent volume provisioner support in the underlying infrastructure + +## Installation + +### Standalone Installation + +```bash +# Install the chart +helm install integrity ./charts/integrity \ + --create-namespace \ + --namespace governance \ + --values values.yaml +``` + +### As Part of Governance Studio (Recommended) + +This chart is typically deployed as part of the Governance Studio umbrella chart: + +```bash +# Install complete platform +helm install governance-studio ./charts/governance-studio \ + --create-namespace \ + --namespace governance \ + --values values.yaml +``` + +## Configuration + +### Core Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `image.repository` | Container image repository | `"ghcr.io/eqtylab/integrity"` | +| `image.tag` | Container image tag | `"latest"` | +| `image.pullPolicy` | Image pull policy | `"IfNotPresent"` | +| `replicaCount` | Number of pod replicas | `1` | + +### Database Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `database.host` | Database hostname | `"postgresql"` | +| `database.port` | Database port | `5432` | +| `database.name` | Database name | `"integrity"` | +| `database.existingSecret` | Existing secret with database credentials | `""` | + +### Service Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `service.type` | Kubernetes service type | `"ClusterIP"` | +| `service.port` | Service port | `8080` | +| `service.targetPort` | Container target port | `8080` | + +### Ingress Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `ingress.enabled` | Enable ingress | `false` | +| `ingress.className` | Ingress class name | `"nginx"` | +| `ingress.hosts` | Ingress hostnames | `[]` | +| `ingress.tls` | TLS configuration | `[]` | + +### Blockchain Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `blockchain.enabled` | Enable blockchain integration | `true` | +| `blockchain.network` | Blockchain network to connect to | `"ethereum"` | +| `blockchain.providerUrl` | Blockchain provider URL | `""` | +| `blockchain.contractAddress` | Smart contract address | `""` | + +### Resource Limits + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `resources.limits.cpu` | CPU limit | `"1000m"` | +| `resources.limits.memory` | Memory limit | `"1Gi"` | +| `resources.requests.cpu` | CPU request | `"100m"` | +| `resources.requests.memory` | Memory request | `"128Mi"` | + +## Upgrading + +### Standalone Upgrade + +```bash +helm upgrade integrity ./charts/integrity \ + --namespace governance \ + --values values.yaml +``` + +### As Part of Governance Studio + +```bash +helm upgrade governance-studio ./charts/governance-studio \ + --namespace governance \ + --values values.yaml +``` + +## Uninstallation + +### Standalone Uninstall + +```bash +helm uninstall integrity -n governance +``` + +### Full Platform Uninstall + +```bash +helm uninstall governance-studio -n governance +``` + +## Troubleshooting + +### Check Pod Status + +```bash +kubectl get pods -n governance -l app.kubernetes.io/name=integrity +kubectl logs -f deployment/integrity -n governance +``` + +### Verify Configuration + +```bash +kubectl get configmap -n governance | grep integrity +kubectl describe deployment integrity -n governance +``` + +### Common Issues + +1. **Database Connection**: Verify database credentials and connectivity +2. **Blockchain Connection**: Check blockchain provider URL and network connectivity +3. **Image Pull**: Ensure image pull secrets are configured if using private registry +4. **Resource Limits**: Check if pods are being OOMKilled due to memory limits + +## API Endpoints + +When deployed, the Integrity Service provides the following API endpoints: + +- `GET /health` - Health check endpoint +- `POST /api/v1/integrity/verify` - Verify data integrity +- `POST /api/v1/integrity/record` - Record data for integrity tracking +- `GET /api/v1/integrity/audit/{id}` - Get audit trail for specific record +- Additional endpoints as documented in the API specification + +## Integration + +The Integrity Service integrates with: +- **PostgreSQL**: For data persistence and audit trail storage +- **Blockchain Networks**: For immutable record keeping +- **Governance UI**: Frontend interface for integrity operations +- **Compliance Garage**: For compliance-related integrity validation +- **Assurance Engine**: For assurance data integrity verification + +## Security Features + +- **Cryptographic Hashing**: Uses industry-standard algorithms for data integrity +- **Digital Signatures**: Supports digital signature verification +- **Blockchain Anchoring**: Optionally anchors critical data to blockchain networks +- **Audit Trails**: Maintains comprehensive audit logs for all operations + +## Support + +For support and documentation: +- GitHub Repository: https://github.com/eqtylab/governance-studio +- Email: support@eqtylab.io \ No newline at end of file diff --git a/charts/integrity-service/templates/deployment.yaml b/charts/integrity-service/templates/deployment.yaml index b495144..fb783b7 100644 --- a/charts/integrity-service/templates/deployment.yaml +++ b/charts/integrity-service/templates/deployment.yaml @@ -69,10 +69,8 @@ spec: # Auth0 settings - name: INTEGRITY_APP__auth__type value: {{ .Values.env.integrityAppAuthType | quote }} - - name: INTEGRITY_APP__auth__client_id - value: {{ .Values.env.integrityAppAuthClientId | quote }} - - name: INTEGRITY_APP__auth__domain - value: {{ .Values.env.integrityAppAuthDomain | quote }} + - name: INTEGRITY_APP__auth__url + value: {{ .Values.env.authServiceUrl | quote }} - name: INTEGRITY_APP__auth__custom_claims_url value: {{ .Values.env.integrityAppCustomClaimsUrl | quote }} @@ -94,6 +92,29 @@ spec: # Azure KV settings {{- if .Values.kvSecret.enabled }} + - name: INTEGRITY_APP__auth_signer_config__type + value: "azure_key_vault" + - name: INTEGRITY_APP__auth_signer_config__client_secret + valueFrom: + secretKeyRef: + name: "{{ include "integrity.azureKvSecretName" . }}" + key: {{ .Values.kvSecret.keys.clientSecret }} + - name: INTEGRITY_APP__auth_signer_config__client_id + valueFrom: + secretKeyRef: + name: "{{ include "integrity.azureKvSecretName" . }}" + key: {{ .Values.kvSecret.keys.clientId }} + - name: INTEGRITY_APP__auth_signer_config__tenant_id + valueFrom: + secretKeyRef: + name: "{{ include "integrity.azureKvSecretName" . }}" + key: {{ .Values.kvSecret.keys.tenantId }} + - name: INTEGRITY_APP__auth_signer_config__vault_url + valueFrom: + secretKeyRef: + name: "{{ include "integrity.azureKvSecretName" . }}" + key: {{ .Values.kvSecret.keys.vaultUrl }} + - name: INTEGRITY_APP__key_vault__client_secret valueFrom: secretKeyRef: @@ -139,14 +160,6 @@ spec: value: "{{ .Release.Name }}-postgresql" - name: INTEGRITY_APP__db__user value: "postgres" - - # Auth0 settings - - name: INTEGRITY_APP__auth_signer_config__client_secret - valueFrom: - secretKeyRef: - name: "{{ include "integrity.azureKvSecretName" . }}" - key: {{ .Values.kvSecret.keys.clientSecret }} - - name: COMMAND value: "integrity-service" args: diff --git a/charts/integrity-service/templates/ingress.yaml b/charts/integrity-service/templates/ingress.yaml index 0e18dfe..b309465 100644 --- a/charts/integrity-service/templates/ingress.yaml +++ b/charts/integrity-service/templates/ingress.yaml @@ -15,7 +15,7 @@ spec: {{- if .Values.ingress.className }} ingressClassName: {{ .Values.ingress.className }} {{- end }} - + {{- if or .Values.ingress.tls .Values.ingress.tls_compat }} tls: {{- if .Values.ingress.tls }} @@ -32,7 +32,7 @@ spec: secretName: {{ .Values.ingress.tls_compat.secretName }} {{- end }} {{- end }} - + rules: {{- if .Values.ingress.hosts }} {{- range .Values.ingress.hosts }} diff --git a/charts/integrity-service/values.yaml b/charts/integrity-service/values.yaml index 4113ce5..e81b70b 100644 --- a/charts/integrity-service/values.yaml +++ b/charts/integrity-service/values.yaml @@ -92,10 +92,9 @@ env: integrityServiceUrl: "" # Public URL for this service # Auth0 Configuration - integrityAppAuthType: "auth0" - integrityAppAuthClientId: "" - integrityAppAuthDomain: "" - integrityAppCustomClaimsUrl: "" # URL for custom claims + integrityAppAuthType: "auth_service" + authServiceUrl: "http://governance-platform-auth-service:8080" + integrityAppCustomClaimsUrl: "https://governance.eqtylab.io/auth0_user_id" # URL for custom claims # ============================================================================= # SECRET CONFIGURATION diff --git a/charts/keycloak-bootstrap/Chart.yaml b/charts/keycloak-bootstrap/Chart.yaml new file mode 100644 index 0000000..a04dda5 --- /dev/null +++ b/charts/keycloak-bootstrap/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: keycloak-bootstrap +description: A Helm chart for bootstrapping Keycloak with governance platform configuration +type: application +version: 1.0.0 +appVersion: "2.0.0" + +keywords: + - keycloak + - bootstrap + - governance + - authentication + +maintainers: + - name: Governance Platform Team + +dependencies: [] diff --git a/charts/keycloak-bootstrap/README.md b/charts/keycloak-bootstrap/README.md new file mode 100644 index 0000000..ec0434c --- /dev/null +++ b/charts/keycloak-bootstrap/README.md @@ -0,0 +1,211 @@ +# Keycloak Bootstrap Helm Chart + +This Helm chart provides an automated way to bootstrap Keycloak with the necessary configuration for the Governance Platform. + +## Overview + +The bootstrap process creates: + +- A configured realm with security settings and token exchange enabled +- Client applications (frontend, backend, worker) +- Custom scopes for authorization +- Initial platform admin user +- Configuration secrets for applications + +**Note**: Groups and projects are now managed entirely within the Governance Service, not in Keycloak. This simplified approach uses token exchange flows with Auth Service for claims enrichment. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ +- A running Keycloak instance +- Admin credentials for Keycloak + +## Installation + +### Basic Installation + +```bash +helm install keycloak-bootstrap ./keycloak-bootstrap \ + --set keycloak.url=http://keycloak:8080 \ + --set keycloak.adminPasswordSecret.name=keycloak-admin \ + --set clients.backend.secretName=keycloak-backend-secret \ + --set clients.worker.secretName=keycloak-worker-secret +``` + +### Installation with Custom Values + +```bash +helm install keycloak-bootstrap ./keycloak-bootstrap -f my-values.yaml +``` + +## Configuration + +### Required Secrets + +Before installing, create the following secrets: + +```bash +# Keycloak admin password +kubectl create secret generic keycloak-admin \ + --from-literal=password=admin + +# Backend client secret +kubectl create secret generic keycloak-backend-client \ + --from-literal=client-secret=$(openssl rand -hex 32) + +# Worker client secret +kubectl create secret generic keycloak-worker-client \ + --from-literal=client-secret=$(openssl rand -hex 32) + +# Admin user password (if creating admin user) +kubectl create secret generic keycloak-admin-user \ + --from-literal=password=admin123 +``` + +### Key Configuration Options + +| Parameter | Description | Default | +| ------------------------------- | ------------------------------ | ----------------------------- | +| `bootstrap.enabled` | Enable the bootstrap job | `true` | +| `keycloak.url` | Keycloak server URL | `http://keycloak:8080` | +| `keycloak.realm.name` | Name of the realm to create | `governance` | +| `clients.frontend.redirectUris` | Frontend redirect URIs | `["http://localhost:5173/*"]` | +| `clients.backend.redirectUris` | Backend redirect URIs | `["http://localhost:8000/*"]` | +| `users.admin.enabled` | Create admin user | `true` | +| `output.createSecrets` | Create K8s secrets with config | `true` | + +See `values.yaml` for all available options. + +## Usage Examples + +### Development Environment + +```yaml +# values-dev.yaml +keycloak: + url: http://keycloak:8080 + realm: + name: governance-dev + +clients: + frontend: + redirectUris: + - "http://localhost:5173/*" + webOrigins: + - "http://localhost:5173" + backend: + redirectUris: + - "http://localhost:8000/*" + webOrigins: + - "http://localhost:8000" + +users: + admin: + enabled: true + testUsers: + enabled: true +``` + +### Production Environment + +```yaml +# values-prod.yaml +keycloak: + url: https://auth.example.com + realm: + name: governance + sslRequired: "all" + +clients: + frontend: + redirectUris: + - "https://app.example.com/*" + webOrigins: + - "https://app.example.com" + backend: + redirectUris: + - "https://api.example.com/*" + webOrigins: + - "https://api.example.com" + +users: + admin: + enabled: true + temporaryPassword: true + testUsers: + enabled: false + +bootstrap: + resources: + limits: + cpu: 1000m + memory: 512Mi +``` + +## Troubleshooting + +### Check Job Status + +```bash +kubectl get jobs -l app.kubernetes.io/name=keycloak-bootstrap +``` + +### View Logs + +```bash +kubectl logs -l app.kubernetes.io/name=keycloak-bootstrap +``` + +### Common Issues + +1. **Job fails with authentication error** + - Verify Keycloak admin credentials + - Check if Keycloak is accessible from the pod + +2. **Realm already exists** + - The job is idempotent and will skip existing resources + - Delete the realm manually if you need a fresh start + +3. **Timeout waiting for Keycloak** + - Increase `bootstrap.wait.maxAttempts` + - Check Keycloak pod status + +## Integration with Applications + +After successful bootstrap, use the created secrets in your applications: + +```yaml +# Example auth-service deployment +env: + - name: KEYCLOAK_URL + valueFrom: + secretKeyRef: + name: keycloak-backend-config + key: KEYCLOAK_URL + - name: KEYCLOAK_REALM + valueFrom: + secretKeyRef: + name: keycloak-backend-config + key: KEYCLOAK_REALM + - name: KEYCLOAK_CLIENT_ID + valueFrom: + secretKeyRef: + name: keycloak-backend-config + key: KEYCLOAK_CLIENT_ID + - name: KEYCLOAK_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: keycloak-backend-secret + key: client-secret +``` + +## Cleanup + +To remove the bootstrap job and its resources: + +```bash +helm uninstall keycloak-bootstrap +``` + +Note: This will not remove the created Keycloak resources (realm, clients, users). diff --git a/charts/keycloak-bootstrap/templates/NOTES.txt b/charts/keycloak-bootstrap/templates/NOTES.txt new file mode 100644 index 0000000..4d9d7b1 --- /dev/null +++ b/charts/keycloak-bootstrap/templates/NOTES.txt @@ -0,0 +1,34 @@ +{{- if .Values.bootstrap.enabled }} +Keycloak Bootstrap Job has been created! + +The bootstrap job will: +1. Wait for Keycloak to be ready at {{ .Values.keycloak.url }} +2. Create the realm: {{ .Values.keycloak.realm.name }} +3. Configure clients: + - Frontend: {{ .Values.clients.frontend.clientId }} + - Backend: {{ .Values.clients.backend.clientId }} + - Worker: {{ .Values.clients.worker.clientId }} +4. Create custom scopes for governance platform +{{- if .Values.users.admin.enabled }} +5. Create admin user: {{ .Values.users.admin.username }} +{{- end }} + +To check the job status: + kubectl get jobs -l "app.kubernetes.io/name={{ include "keycloak-bootstrap.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" + +To view the job logs: + kubectl logs -l "app.kubernetes.io/name={{ include "keycloak-bootstrap.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" + +{{- if .Values.output.createSecrets }} + +Configuration secrets have been created: +- Frontend: {{ .Values.output.secrets.frontend }} +- Backend: {{ .Values.output.secrets.backend }} +- Worker: {{ .Values.output.secrets.worker }} + +You can use these secrets in your application deployments. +{{- end }} + +{{- else }} +Keycloak Bootstrap is disabled. To enable it, set bootstrap.enabled=true +{{- end }} diff --git a/charts/keycloak-bootstrap/templates/_helpers.tpl b/charts/keycloak-bootstrap/templates/_helpers.tpl new file mode 100644 index 0000000..82e1c9e --- /dev/null +++ b/charts/keycloak-bootstrap/templates/_helpers.tpl @@ -0,0 +1,49 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "keycloak-bootstrap.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "keycloak-bootstrap.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "keycloak-bootstrap.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "keycloak-bootstrap.labels" -}} +helm.sh/chart: {{ include "keycloak-bootstrap.chart" . }} +{{ include "keycloak-bootstrap.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "keycloak-bootstrap.selectorLabels" -}} +app.kubernetes.io/name: {{ include "keycloak-bootstrap.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/charts/keycloak-bootstrap/templates/configmap.yaml b/charts/keycloak-bootstrap/templates/configmap.yaml new file mode 100644 index 0000000..f21409f --- /dev/null +++ b/charts/keycloak-bootstrap/templates/configmap.yaml @@ -0,0 +1,358 @@ +{{- if .Values.bootstrap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "keycloak-bootstrap.fullname" . }}-scripts + labels: + {{- include "keycloak-bootstrap.labels" . | nindent 4 }} +data: + bootstrap.sh: | + #!/bin/sh + set -e + + # Colors for output + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + NC='\033[0m' # No Color + + # Configuration from environment + KEYCLOAK_URL="${KEYCLOAK_URL}" + KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN_USERNAME}" + KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD}" + REALM_NAME="{{ .Values.keycloak.realm.name }}" + + echo "Starting Keycloak bootstrap process..." + echo "Keycloak URL: ${KEYCLOAK_URL}" + echo "Realm: ${REALM_NAME}" + + # Function to get admin token + get_admin_token() { + echo "Getting admin token..." + TOKEN=$(curl -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=${KEYCLOAK_ADMIN}" \ + -d "password=${KEYCLOAK_ADMIN_PASSWORD}" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" | jq -r '.access_token') + + if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then + echo "Failed to get admin token" + return 1 + fi + echo "Successfully obtained admin token" + return 0 + } + + # Function to check if realm exists + check_realm_exists() { + echo "Checking if realm ${REALM_NAME} exists..." + RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer ${TOKEN}" \ + "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}") + + if [ "$RESPONSE" = "200" ]; then + echo "Realm ${REALM_NAME} already exists" + return 0 + else + echo "Realm ${REALM_NAME} does not exist" + return 1 + fi + } + + # Function to create realm + create_realm() { + echo "Creating realm ${REALM_NAME}..." + REALM_CONFIG=$(cat < /output/.env.keycloak </dev/null) +if [ "$KEYCLOAK_READY" != "True" ]; then + echo -e "${RED}Error: Keycloak pod is not ready${NC}" + kubectl get pod -l app=keycloak -n "$NAMESPACE" + exit 1 +fi +echo -e "${GREEN}✓ Keycloak is running${NC}" + +# Check required secrets +MISSING_SECRETS=() +for secret in keycloak-admin keycloak-backend-client keycloak-worker-client keycloak-admin-user; do + if ! kubectl get secret "$secret" -n "$NAMESPACE" &>/dev/null; then + MISSING_SECRETS+=("$secret") + fi +done + +if [ ${#MISSING_SECRETS[@]} -gt 0 ]; then + echo -e "${RED}Error: Missing required secrets:${NC}" + printf '%s\n' "${MISSING_SECRETS[@]}" + echo "" + echo "Create them with:" + echo " kubectl create secret generic keycloak-admin --from-literal=password= -n $NAMESPACE" + echo " kubectl create secret generic keycloak-backend-client --from-literal=client-secret= -n $NAMESPACE" + echo " kubectl create secret generic keycloak-worker-client --from-literal=client-secret= -n $NAMESPACE" + echo " kubectl create secret generic keycloak-admin-user --from-literal=password= -n $NAMESPACE" + exit 1 +fi +echo -e "${GREEN}✓ All required secrets exist${NC}" + +# Clean up any existing bootstrap jobs +echo "" +echo -e "${YELLOW}Cleaning up any existing bootstrap jobs...${NC}" +kubectl delete job -l app.kubernetes.io/instance="$BOOTSTRAP_RELEASE" -n "$NAMESPACE" --ignore-not-found + +# Run the bootstrap +echo "" +echo -e "${YELLOW}Running Keycloak bootstrap...${NC}" + +# Install the bootstrap chart with values +helm upgrade --install "$BOOTSTRAP_RELEASE" "$CHART_DIR" \ + -n "$NAMESPACE" \ + -f "$SCRIPT_DIR/values-bootstrap-keycloak.yaml" \ + --wait \ + --timeout 10m + +# Get the job name +JOB_NAME=$(kubectl get job -l app.kubernetes.io/instance="$BOOTSTRAP_RELEASE" -n "$NAMESPACE" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + +if [ -z "$JOB_NAME" ]; then + echo -e "${RED}Error: Bootstrap job not found${NC}" + exit 1 +fi + +echo "Bootstrap job: $JOB_NAME" + +# Monitor job completion +echo "" +echo -e "${YELLOW}Monitoring bootstrap job...${NC}" + +# Wait for job to complete +TIMEOUT=300 +ELAPSED=0 +while [ $ELAPSED -lt $TIMEOUT ]; do + JOB_STATUS=$(kubectl get job "$JOB_NAME" -n "$NAMESPACE" -o jsonpath='{.status.conditions[?(@.type=="Complete")].status}' 2>/dev/null) + JOB_FAILED=$(kubectl get job "$JOB_NAME" -n "$NAMESPACE" -o jsonpath='{.status.conditions[?(@.type=="Failed")].status}' 2>/dev/null) + + if [ "$JOB_STATUS" = "True" ]; then + echo -e "${GREEN}✓ Bootstrap completed successfully${NC}" + break + elif [ "$JOB_FAILED" = "True" ]; then + echo -e "${RED}Bootstrap job failed${NC}" + echo "Job logs:" + kubectl logs job/"$JOB_NAME" -n "$NAMESPACE" --tail=50 + exit 1 + fi + + echo -n "." + sleep 5 + ELAPSED=$((ELAPSED + 5)) +done + +if [ $ELAPSED -ge $TIMEOUT ]; then + echo -e "${RED}Bootstrap job timed out${NC}" + kubectl logs job/"$JOB_NAME" -n "$NAMESPACE" --tail=50 + exit 1 +fi + +# Show logs +echo "" +echo -e "${YELLOW}Bootstrap logs (last 20 lines):${NC}" +kubectl logs job/"$JOB_NAME" -n "$NAMESPACE" --tail=20 + +# Display results +echo "" +echo -e "${BLUE}Bootstrap Complete!${NC}" +echo -e "${BLUE}=================${NC}" +echo "" +echo "Keycloak URLs:" +echo "- Admin Console: https://DOMAIN/keycloak/admin" +echo "- Governance Realm: https://DOMAIN/keycloak/admin/governance/console" +echo "" + +# Show credentials +if kubectl get secret keycloak-admin -n "$NAMESPACE" &>/dev/null; then + ADMIN_PASSWORD=$(kubectl get secret --namespace "$NAMESPACE" keycloak-admin -o jsonpath="{.data.password}" | base64 -d) + echo "Keycloak Admin (master realm):" + echo " Username: admin" + echo " Password: $ADMIN_PASSWORD" + echo "" +fi + +if kubectl get secret keycloak-admin-user -n "$NAMESPACE" &>/dev/null; then + PLATFORM_ADMIN_PASSWORD=$(kubectl get secret --namespace "$NAMESPACE" keycloak-admin-user -o jsonpath="{.data.password}" | base64 -d) + echo "Platform Admin (governance realm):" + echo " Username: platform-admin" + echo " Password: $PLATFORM_ADMIN_PASSWORD" + echo "" +fi + +echo "OAuth Clients:" +echo "- Frontend: governance-platform-frontend (public client)" +echo "- Backend: governance-platform-backend (confidential with service account)" +echo "- Worker: governance-worker (service account only)" +echo "" +echo "Token Exchange: Enabled for Auth Service integration" + +# Cleanup +echo "" +echo -e "${YELLOW}Cleaning up completed job...${NC}" +kubectl delete job "$JOB_NAME" -n "$NAMESPACE" --ignore-not-found + +echo "" +echo -e "${GREEN}Bootstrap process completed!${NC}" diff --git a/scripts/keycloak/create-keycloak-organization.sh b/scripts/keycloak/create-keycloak-organization.sh new file mode 100755 index 0000000..7668b41 --- /dev/null +++ b/scripts/keycloak/create-keycloak-organization.sh @@ -0,0 +1,341 @@ +#!/bin/bash + +# Script to create organization in governance service database for Keycloak realm +# This should be run AFTER the governance platform is deployed + +set -e + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Default values +DEFAULT_NAMESPACE="governance" +DEFAULT_REALM="governance" +DEFAULT_DB_NAME="governance" + +# Function to print usage +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -n, --namespace Kubernetes namespace (default: $DEFAULT_NAMESPACE)" + echo " -r, --realm Keycloak realm name (default: $DEFAULT_REALM)" + echo " -d, --database Database name (default: $DEFAULT_DB_NAME)" + echo " -p, --pod Specific pod name (optional, will auto-detect)" + echo " -h, --help Show this help message" + echo "" + echo "Example:" + echo " $0 --namespace governance-stag --realm governance" +} + +# Parse arguments +NAMESPACE=$DEFAULT_NAMESPACE +REALM_NAME=$DEFAULT_REALM +DB_NAME=$DEFAULT_DB_NAME +POD_NAME="" + +while [[ $# -gt 0 ]]; do + case $1 in + -n | --namespace) + NAMESPACE="$2" + shift 2 + ;; + -r | --realm) + REALM_NAME="$2" + shift 2 + ;; + -d | --database) + DB_NAME="$2" + shift 2 + ;; + -p | --pod) + POD_NAME="$2" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" + usage + exit 1 + ;; + esac +done + +echo -e "${BLUE}Creating Keycloak Organization in Governance Database${NC}" +echo -e "${BLUE}==================================================${NC}" +echo "" +echo "Namespace: $NAMESPACE" +echo "Realm/Organization: $REALM_NAME" +echo "Database: $DB_NAME" +echo "" + +# Function to get PostgreSQL password +get_postgres_password() { + # Try common secret names + local password="" + + # Try compliance-secrets first + password=$(kubectl get secret -n "$NAMESPACE" compliance-secrets -o jsonpath="{.data.password}" 2>/dev/null | base64 -d) + + if [ -z "$password" ]; then + # Try generic postgresql secret + password=$(kubectl get secret -n "$NAMESPACE" postgresql -o jsonpath="{.data.postgres-password}" 2>/dev/null | base64 -d) + fi + + if [ -z "$password" ]; then + # Try with different keys + password=$(kubectl get secret -n "$NAMESPACE" compliance-secrets -o jsonpath="{.data.postgres-password}" 2>/dev/null | base64 -d) + fi + + if [ -z "$password" ]; then + # List available secrets for debugging + echo -e "${RED}Error: Could not find PostgreSQL password${NC}" + echo "Available PostgreSQL secrets:" + kubectl get secrets -n "$NAMESPACE" | grep -E "(postgres|sql)" + return 1 + fi + + echo "$password" +} + +# Function to find governance database pod +find_db_pod() { + echo -e "${YELLOW}Finding governance database pod...${NC}" + + if [ -n "$POD_NAME" ]; then + echo "Using specified pod: $POD_NAME" + return + fi + + # Try to find the governance PostgreSQL pod + POD_NAME=$(kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=postgresql,app.kubernetes.io/instance=governance-platform" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + # Try alternative label + POD_NAME=$(kubectl get pods -n "$NAMESPACE" -l "app=postgresql" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + fi + + if [ -z "$POD_NAME" ]; then + echo -e "${RED}Error: Could not find PostgreSQL pod${NC}" + echo "Please specify pod name with -p option" + kubectl get pods -n "$NAMESPACE" + exit 1 + fi + + echo "Found pod: $POD_NAME" +} + +# Function to check if organization exists +check_org_exists() { + echo -e "${YELLOW}Checking if organization already exists...${NC}" + + RESULT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -tA -c "SELECT COUNT(*) FROM organization WHERE name = '$REALM_NAME';" 2>/dev/null) + + if [ "$RESULT" = "1" ]; then + echo -e "${GREEN}Organization '$REALM_NAME' already exists${NC}" + + # Show existing organization + echo "" + echo "Existing organization details:" + kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "SELECT id, name, display_name, idp_provider, created_at FROM organization WHERE name = '$REALM_NAME';" + return 0 + else + return 1 + fi +} + +# Function to create organization +create_organization() { + echo -e "${YELLOW}Creating organization '$REALM_NAME'...${NC}" + + # Prepare SQL statement + SQL="INSERT INTO organization (name, description, display_name, idp_provider, settings, created_at, updated_at) VALUES ('$REALM_NAME', '$REALM_NAME', '${REALM_NAME^}', 'keycloak', '{}', NOW(), NOW()) RETURNING id, name, display_name, idp_provider;" + + # Execute SQL + RESULT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "$SQL" 2>&1) + + if [ $? -eq 0 ]; then + echo -e "${GREEN}Organization created successfully!${NC}" + echo "" + echo "$RESULT" + else + echo -e "${RED}Failed to create organization${NC}" + echo "$RESULT" + exit 1 + fi +} + +# Function to verify tables exist +verify_tables() { + echo -e "${YELLOW}Verifying database tables exist...${NC}" + + # Check for all required tables + local required_tables=("organization" "users" "user_organization_memberships") + local all_exist=true + local missing_tables=() + + for table in "${required_tables[@]}"; do + local count=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \ + env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -tA -c \ + "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '$table';" 2>/dev/null) + + if [ "$count" != "1" ]; then + all_exist=false + missing_tables+=("$table") + fi + done + + if [ "$all_exist" = true ]; then + echo -e "${GREEN}✓ All required tables exist${NC}" + return 0 + else + echo -e "${RED}Error: Required tables are missing:${NC}" + printf ' - %s\n' "${missing_tables[@]}" + echo "" + echo "This usually means:" + echo "1. The governance platform is not deployed yet, OR" + echo "2. The application hasn't started and run migrations yet" + echo "" + echo "Please ensure:" + echo "1. Governance platform is deployed" + echo "2. Wait for the governance-service to be running" + echo "3. Try again in a minute" + + # Show existing tables for debugging + echo "" + echo "Existing tables in database:" + kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "\dt" 2>/dev/null || echo "Could not list tables" + + exit 1 + fi +} + +# Main execution +main() { + # Find database pod + find_db_pod + + # Get PostgreSQL password + echo -e "${YELLOW}Getting database credentials...${NC}" + PG_PASSWORD=$(get_postgres_password) + if [ -z "$PG_PASSWORD" ]; then + exit 1 + fi + export PG_PASSWORD # Make it available to all functions + + # Verify tables exist + verify_tables + + # Check if organization exists + if check_org_exists; then + read -p "Organization already exists. Do you want to update it? (y/n) [n]: " UPDATE_ORG + UPDATE_ORG=${UPDATE_ORG:-n} + + if [[ "$UPDATE_ORG" =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Updating organization...${NC}" + UPDATE_SQL="UPDATE organization SET idp_provider = 'keycloak', updated_at = NOW() WHERE name = '$REALM_NAME' RETURNING id, name, display_name, idp_provider;" + kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "$UPDATE_SQL" + echo -e "${GREEN}Organization updated${NC}" + fi + else + # Create organization + create_organization + fi + + # Create platform-admin user if requested + echo "" + read -p "Create platform-admin user in auth service? (y/n) [y]: " CREATE_USER + CREATE_USER=${CREATE_USER:-y} + + if [[ "$CREATE_USER" =~ ^[Yy]$ ]]; then + create_platform_admin_user + fi + + echo "" + echo -e "${GREEN}Done!${NC}" + echo "" + echo "Next steps:" + echo "1. Run Keycloak bootstrap to create the '$REALM_NAME' realm" + echo "2. Users authenticated through Keycloak will be associated with this organization" +} + +# Function to create platform-admin user +create_platform_admin_user() { + echo -e "${YELLOW}Creating platform-admin user in auth service...${NC}" + + # Get Keycloak admin user ID from bootstrap (we'll use a placeholder for now) + local KEYCLOAK_USER_ID="${KEYCLOAK_USER_ID:-$(uuidgen | tr '[:upper:]' '[:lower:]')}" + local USER_ID=$(uuidgen | tr '[:upper:]' '[:lower:]') + + # Check if user already exists + local USER_EXISTS=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -tA -c "SELECT COUNT(*) FROM users WHERE email = 'admin@$REALM_NAME.local';" 2>/dev/null) + + if [ "$USER_EXISTS" = "1" ]; then + echo -e "${YELLOW}Platform admin user already exists${NC}" + # Get existing user ID + USER_ID=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -tA -c "SELECT id FROM users WHERE email = 'admin@$REALM_NAME.local';" 2>/dev/null | tr -d ' ') + else + # Create user + USER_SQL="INSERT INTO users (id, idp_provider, idp_user_id, email, email_verified, display_name, given_name, family_name, active, app_metadata, created_at, updated_at, is_service_account, service_config) VALUES ('$USER_ID', 'keycloak', '$KEYCLOAK_USER_ID', 'admin@$REALM_NAME.local', true, 'Platform Admin', 'Platform', 'Admin', true, '{}', NOW(), NOW(), false, '{}') RETURNING id;" + + RESULT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "$USER_SQL" 2>&1) + + if [ $? -eq 0 ]; then + echo -e "${GREEN}Platform admin user created successfully${NC}" + else + echo -e "${RED}Failed to create platform admin user${NC}" + echo "$RESULT" + return 1 + fi + fi + + # Get organization ID + local ORG_ID=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -tA -c "SELECT id FROM organization WHERE name = '$REALM_NAME';" 2>/dev/null | tr -d ' ') + + if [ -z "$ORG_ID" ]; then + echo -e "${RED}Error: Organization not found${NC}" + return 1 + fi + + # Check if membership already exists + local MEMBERSHIP_EXISTS=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -tA -c "SELECT COUNT(*) FROM user_organization_memberships WHERE user_id = '$USER_ID' AND organization_id = $ORG_ID;" 2>/dev/null) + + if [ "$MEMBERSHIP_EXISTS" = "1" ]; then + echo -e "${YELLOW}Organization membership already exists${NC}" + # Update to ensure owner role + UPDATE_SQL="UPDATE user_organization_memberships SET roles = '{organization_owner}', status = 'active' WHERE user_id = '$USER_ID' AND organization_id = $ORG_ID;" + kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "$UPDATE_SQL" + else + # Create membership + MEMBERSHIP_ID=$(uuidgen | tr '[:upper:]' '[:lower:]') + MEMBERSHIP_SQL="INSERT INTO user_organization_memberships (id, user_id, organization_id, roles, invited_at, joined_at, status) VALUES ('$MEMBERSHIP_ID', '$USER_ID', $ORG_ID, '{organization_owner}', NOW(), NOW(), 'active');" + + kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "$MEMBERSHIP_SQL" + + if [ $? -eq 0 ]; then + echo -e "${GREEN}Organization membership created successfully${NC}" + else + echo -e "${RED}Failed to create organization membership${NC}" + return 1 + fi + fi + + # Show the created user and membership + echo "" + echo "Platform admin user:" + kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "SELECT id, email, display_name, idp_provider FROM users WHERE email = 'admin@$REALM_NAME.local';" + + echo "" + echo "Organization membership:" + kubectl exec -n "$NAMESPACE" "$POD_NAME" -- env PGPASSWORD="$PG_PASSWORD" psql -U postgres -d "$DB_NAME" -c "SELECT m.*, o.name as org_name FROM user_organization_memberships m JOIN organization o ON m.organization_id = o.id WHERE m.user_id = '$USER_ID';" +} + +# Run main function +main diff --git a/scripts/keycloak/post-install-keycloak-setup.sh b/scripts/keycloak/post-install-keycloak-setup.sh new file mode 100755 index 0000000..51155ff --- /dev/null +++ b/scripts/keycloak/post-install-keycloak-setup.sh @@ -0,0 +1,533 @@ +#!/bin/bash + +# Post-install script for Keycloak database setup +# Run this AFTER: +# 1. Keycloak is deployed +# 2. Keycloak bootstrap is complete (realm, clients, users created) +# 3. Governance platform is deployed +# +# NOTE: Database migrations run automatically when the governance-service starts, +# so this script waits for the service to be running and verifies the schema exists. +# +# This script will: +# - Wait for database schema to be ready (migrations complete) +# - Create organization in governance database +# - Create platform-admin user in auth service tables +# - Set up organization membership with owner role +# - Verify the integration + +set -e + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Default values +NAMESPACE="governance" +REALM_NAME="governance" +ENVIRONMENT="dev" + +# Function to print usage +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -n, --namespace Kubernetes namespace (default: $NAMESPACE)" + echo " -r, --realm Keycloak realm name (default: $REALM_NAME)" + echo " -e, --env Environment: dev|stag|prod (default: $ENVIRONMENT)" + echo " -h, --help Show this help message" + echo "" + echo "Example:" + echo " $0 --namespace governance-stag --realm governance --env stag" +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + -n | --namespace) + NAMESPACE="$2" + shift 2 + ;; + -r | --realm) + REALM_NAME="$2" + shift 2 + ;; + -e | --env) + ENVIRONMENT="$2" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" + usage + exit 1 + ;; + esac +done + +echo -e "${BLUE}Post-Install Keycloak Database Setup${NC}" +echo -e "${BLUE}===================================${NC}" +echo "" +echo "Namespace: $NAMESPACE" +echo "Environment: $ENVIRONMENT" +echo "Realm/Organization: $REALM_NAME" +echo "" + +# Function to wait for governance platform +wait_for_platform() { + echo -e "${YELLOW}Waiting for governance platform components...${NC}" + + # Check for governance service deployment + echo "Checking for governance service deployment..." + if ! kubectl get deployment -l app.kubernetes.io/name=governance-service -n "$NAMESPACE" &>/dev/null; then + # Try alternative names + if ! kubectl get deployment governance-platform-governance-service -n "$NAMESPACE" &>/dev/null; then + echo -e "${RED}Error: Governance service deployment not found${NC}" + echo "Available deployments:" + kubectl get deployments -n "$NAMESPACE" + return 1 + fi + fi + + # Wait for database to be running + echo "Checking database pod..." + local db_ready=false + for i in {1..30}; do + if kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=postgresql" -o jsonpath='{.items[0].status.phase}' 2>/dev/null | grep -q "Running"; then + db_ready=true + break + fi + echo -n "." + sleep 5 + done + + if [ "$db_ready" = true ]; then + echo -e "${GREEN}✓ Database pod is running${NC}" + else + echo -e "${RED}Database pod not ready after 150 seconds${NC}" + return 1 + fi + + # Check if database is accepting connections + echo "Checking database connectivity..." + local db_pod=$(kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=postgresql" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -n "$db_pod" ]; then + for i in {1..10}; do + if kubectl exec -n "$NAMESPACE" "$db_pod" -- pg_isready -h localhost -U postgres &>/dev/null; then + echo -e "${GREEN}✓ Database is accepting connections${NC}" + return 0 + fi + echo -n "." + sleep 3 + done + fi + + echo -e "${YELLOW}Platform components are starting up${NC}" +} + +# Function to get PostgreSQL password +get_postgres_password() { + # Try common secret names + local password="" + + # Try compliance-secrets first + password=$(kubectl get secret -n "$NAMESPACE" compliance-secrets -o jsonpath="{.data.password}" 2>/dev/null | base64 -d) + + if [ -z "$password" ]; then + # Try generic postgresql secret + password=$(kubectl get secret -n "$NAMESPACE" postgresql -o jsonpath="{.data.postgres-password}" 2>/dev/null | base64 -d) + fi + + if [ -z "$password" ]; then + # List available secrets for debugging + echo -e "${YELLOW}Could not find PostgreSQL password. Available secrets:${NC}" + kubectl get secrets -n "$NAMESPACE" | grep -E "(postgres|sql)" + return 1 + fi + + echo "$password" +} + +# Function to verify database schema exists +verify_database_schema() { + local db_pod=$1 + local pg_password=$2 + + # Check for required tables + local required_tables=("organization" "users" "user_organization_memberships") + local all_exist=true + + for table in "${required_tables[@]}"; do + # Use COUNT for more reliable results with password + local count=$(kubectl exec -n "$NAMESPACE" "$db_pod" -- \ + env PGPASSWORD="$pg_password" psql -U postgres -d governance -tA -c \ + "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '$table';" 2>/dev/null) + + # Check if count is 1 (table exists) or 0 (doesn't exist) + if [ "$count" = "1" ]; then + echo -e "${GREEN} ✓ Table '$table' exists${NC}" + else + echo -e "${RED} Table '$table' does not exist yet (count: ${count:-unknown})${NC}" + all_exist=false + fi + done + + if [ "$all_exist" = true ]; then + return 0 + else + return 1 + fi +} + +# Function to ensure database is ready (migrations run on app startup) +ensure_database_ready() { + echo -e "${YELLOW}Ensuring database is ready...${NC}" + + # 1. Wait for governance service to be running + echo "Waiting for governance service to be available..." + kubectl wait --for=condition=available --timeout=300s \ + deployment/governance-platform-governance-service \ + -n "$NAMESPACE" 2>/dev/null || true + + # 2. Give the app time to run migrations on startup + echo "Waiting for application to initialize and run migrations..." + sleep 30 + + # 3. Get database pod + local db_pod=$(kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=postgresql" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$db_pod" ]; then + echo -e "${RED}Error: Database pod not found${NC}" + return 1 + fi + + # 4. Get PostgreSQL password + echo "Getting database credentials..." + local pg_password=$(get_postgres_password) + if [ -z "$pg_password" ]; then + echo -e "${RED}Error: Could not get PostgreSQL password${NC}" + return 1 + fi + + # 5. Verify schema with retries + echo "Verifying database schema..." + local retries=5 + for i in $(seq 1 $retries); do + if verify_database_schema "$db_pod" "$pg_password"; then + echo -e "${GREEN}Database schema is ready${NC}" + return 0 + fi + + if [ $i -lt $retries ]; then + echo "Retry $i/$retries - waiting 30 seconds for migrations to complete..." + sleep 30 + fi + done + + # 6. Final check - if critical tables exist, proceed with warning + local org_count=$(kubectl exec -n "$NAMESPACE" "$db_pod" -- \ + env PGPASSWORD="$pg_password" psql -U postgres -d governance -tA -c \ + "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'organization';" 2>/dev/null) + + if [ "$org_count" = "1" ]; then + echo -e "${YELLOW}Proceeding - organization table exists${NC}" + return 0 + else + echo -e "${RED}Warning: Database may not be fully initialized${NC}" + return 1 + fi +} + +# Function to create organization +create_organization() { + echo -e "${YELLOW}Creating organization in database...${NC}" + + # Run the organization creation script + "$SCRIPT_DIR/create-keycloak-organization.sh" \ + --namespace "$NAMESPACE" \ + --realm "$REALM_NAME" \ + --database "governance" +} + +# Function to get platform-admin Keycloak ID +get_platform_admin_keycloak_id() { + echo -e "${YELLOW}Getting platform-admin user ID from Keycloak...${NC}" + + # Get external Keycloak URL + local keycloak_url="" + local ingress_host=$(kubectl get ingress -n "$NAMESPACE" -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null) + + if [ -n "$ingress_host" ]; then + keycloak_url="https://$ingress_host/keycloak" + else + echo -e "${YELLOW}No ingress found, trying port-forward method${NC}" + # Start port-forward in background + kubectl port-forward -n "$NAMESPACE" svc/keycloak 8080:80 &>/dev/null & + local pf_pid=$! + sleep 3 + keycloak_url="http://localhost:8080/keycloak" + fi + + # Get admin password + local admin_pass=$(kubectl get secret keycloak-admin -n "$NAMESPACE" -o jsonpath='{.data.password}' | base64 -d) + + if [ -z "$admin_pass" ]; then + echo -e "${YELLOW}Could not get admin password, using placeholder ID${NC}" + [ -n "$pf_pid" ] && kill $pf_pid 2>/dev/null + echo "00000000-0000-0000-0000-000000000000" + return + fi + + # Get admin token from master realm + echo "Getting admin token..." + local token_response=$(curl -sk -X POST "$keycloak_url/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=admin" \ + -d "password=$admin_pass" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" 2>/dev/null) + + local token=$(echo "$token_response" | jq -r '.access_token // empty') + + if [ -z "$token" ]; then + echo -e "${YELLOW}Could not get Keycloak token, using placeholder ID${NC}" + [ -n "$pf_pid" ] && kill $pf_pid 2>/dev/null + echo "00000000-0000-0000-0000-000000000000" + return + fi + + # Get platform-admin user from realm + echo "Looking up platform-admin user in $REALM_NAME realm..." + local user_data=$(curl -sk -H "Authorization: Bearer $token" \ + "$keycloak_url/admin/realms/$REALM_NAME/users?username=platform-admin&exact=true" 2>/dev/null) + + local user_id=$(echo "$user_data" | jq -r '.[0].id // empty') + + # Clean up port-forward if we started it + [ -n "$pf_pid" ] && kill $pf_pid 2>/dev/null + + if [ -n "$user_id" ]; then + echo -e "${GREEN}Found platform-admin user ID: $user_id${NC}" + echo "$user_id" + else + echo -e "${YELLOW}Platform-admin user not found in Keycloak, using placeholder${NC}" + echo "00000000-0000-0000-0000-000000000000" + fi +} + +# Function to verify integration +verify_integration() { + echo -e "${YELLOW}Verifying Keycloak integration...${NC}" + + # Check Keycloak via external URL + echo "Checking Keycloak realm..." + + # Get external Keycloak URL + local keycloak_url="" + local ingress_host=$(kubectl get ingress -n "$NAMESPACE" -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null) + + if [ -n "$ingress_host" ]; then + keycloak_url="https://$ingress_host/keycloak" + else + echo -e "${YELLOW}No ingress found, trying port-forward method${NC}" + # Start port-forward in background + kubectl port-forward -n "$NAMESPACE" svc/keycloak 8080:80 &>/dev/null & + local pf_pid=$! + sleep 3 + keycloak_url="http://localhost:8080/keycloak" + fi + + # Get admin password + local admin_pass=$(kubectl get secret keycloak-admin -n "$NAMESPACE" -o jsonpath='{.data.password}' | base64 -d) + + if [ -n "$admin_pass" ]; then + # Get admin token + local token_response=$(curl -sk -X POST "$keycloak_url/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=admin" \ + -d "password=$admin_pass" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" 2>/dev/null) + + local token=$(echo "$token_response" | jq -r '.access_token // empty') + + if [ -n "$token" ]; then + # Check if realm exists + local realm_check=$(curl -sk -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer $token" \ + "$keycloak_url/admin/realms/$REALM_NAME") + + if [ "$realm_check" = "200" ]; then + echo -e "${GREEN}✓ Keycloak realm '$REALM_NAME' exists${NC}" + + # Check platform-admin user + local user_data=$(curl -sk -H "Authorization: Bearer $token" \ + "$keycloak_url/admin/realms/$REALM_NAME/users?username=platform-admin&exact=true" 2>/dev/null) + + local user_count=$(echo "$user_data" | jq '. | length // 0') + + if [ "$user_count" -gt 0 ]; then + echo -e "${GREEN}✓ Platform-admin user exists in Keycloak${NC}" + else + echo -e "${RED}✗ Platform-admin user not found in Keycloak${NC}" + fi + else + echo -e "${RED}✗ Keycloak realm '$REALM_NAME' not found${NC}" + fi + else + echo -e "${YELLOW}Could not verify Keycloak - unable to get token${NC}" + fi + else + echo -e "${YELLOW}Could not verify Keycloak - no admin password found${NC}" + fi + + # Clean up port-forward if we started it + [ -n "$pf_pid" ] && kill $pf_pid 2>/dev/null + + # Check organization in database + echo "Checking organization in database..." + local db_pod=$(kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=postgresql" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -n "$db_pod" ]; then + # Get PostgreSQL password + local pg_password=$(get_postgres_password) + + if [ -n "$pg_password" ]; then + local org_count=$(kubectl exec -n "$NAMESPACE" "$db_pod" -- \ + env PGPASSWORD="$pg_password" psql -U postgres -d governance -tA -c "SELECT COUNT(*) FROM organization WHERE name = '$REALM_NAME' AND idp_provider = 'keycloak';" 2>/dev/null) + + if [ "$org_count" = "1" ]; then + echo -e "${GREEN}✓ Organization '$REALM_NAME' exists with idp_provider=keycloak${NC}" + else + echo -e "${RED}✗ Organization not found or incorrect idp_provider (count: ${org_count:-unknown})${NC}" + fi + + # Check platform-admin user in auth service + local user_count=$(kubectl exec -n "$NAMESPACE" "$db_pod" -- \ + env PGPASSWORD="$pg_password" psql -U postgres -d governance -tA -c "SELECT COUNT(*) FROM users WHERE email = 'admin@$REALM_NAME.local' AND idp_provider = 'keycloak';" 2>/dev/null) + + if [ "$user_count" = "1" ]; then + echo -e "${GREEN}✓ Platform-admin user exists in auth service${NC}" + + # Check membership + local membership_count=$(kubectl exec -n "$NAMESPACE" "$db_pod" -- \ + env PGPASSWORD="$pg_password" psql -U postgres -d governance -tA -c "SELECT COUNT(*) FROM user_organization_memberships uom JOIN users u ON uom.user_id = u.id WHERE u.email = 'admin@$REALM_NAME.local' AND 'organization_owner' = ANY(uom.roles);" 2>/dev/null) + + if [ "$membership_count" = "1" ]; then + echo -e "${GREEN}✓ Platform-admin has organization_owner role${NC}" + else + echo -e "${RED}✗ Platform-admin missing organization_owner role (count: ${membership_count:-unknown})${NC}" + fi + else + echo -e "${RED}✗ Platform-admin user not found in auth service (count: ${user_count:-unknown})${NC}" + fi + else + echo -e "${YELLOW}Could not verify database - unable to get PostgreSQL password${NC}" + fi + else + echo -e "${YELLOW}Could not find database pod${NC}" + fi +} + +# Function to show summary +show_summary() { + echo "" + echo -e "${BLUE}Setup Summary${NC}" + echo -e "${BLUE}=============${NC}" + echo "" + + echo "Database Setup Completed:" + echo " - Organization: $REALM_NAME (idp_provider=keycloak)" + echo " - Platform Admin: admin@$REALM_NAME.local" + echo " - Role: organization_owner" + + # Get Keycloak URL + local ingress_host=$(kubectl get ingress -n "$NAMESPACE" -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null) + + echo "" + echo "Keycloak Information:" + if [ -n "$ingress_host" ]; then + echo " - Admin Console: https://$ingress_host/keycloak/admin" + echo " - Realm: https://$ingress_host/keycloak/admin/$REALM_NAME/console" + else + echo " - Use port-forward: kubectl port-forward -n $NAMESPACE svc/keycloak 8080:80" + echo " - Then access: http://localhost:8080/keycloak/admin" + fi + + echo "" + echo "Next Steps:" + echo " 1. Test login with platform-admin user" + echo " 2. Verify token exchange with auth service" + echo " 3. Check that users can access the governance platform" + + echo "" + echo "To sync Keycloak user IDs (if needed):" + echo " $SCRIPT_DIR/sync-keycloak-user-ids.sh --namespace $NAMESPACE --realm $REALM_NAME" +} + +# Main execution +main() { + echo "This script sets up database entries after Keycloak bootstrap" + echo "" + echo -e "${YELLOW}Prerequisites:${NC}" + echo " - Keycloak must be deployed and running" + echo " - Keycloak bootstrap must be complete (realm, clients, users created)" + echo " - Governance platform must be deployed" + echo "" + + # Step 1: Wait for platform + wait_for_platform + + # Step 2: Ensure database is ready (migrations run on startup) + ensure_database_ready + + # Step 3: Create organization + echo "" + read -p "Create organization in database? (y/n) [y]: " CREATE_ORG + CREATE_ORG=${CREATE_ORG:-y} + + if [[ "$CREATE_ORG" =~ ^[Yy]$ ]]; then + create_organization + fi + + # Step 4: Create platform-admin user in database + echo "" + read -p "Create platform-admin user in auth service? (y/n) [y]: " CREATE_ADMIN + CREATE_ADMIN=${CREATE_ADMIN:-y} + + if [[ "$CREATE_ADMIN" =~ ^[Yy]$ ]]; then + # Get Keycloak user ID from existing platform-admin in Keycloak + export KEYCLOAK_USER_ID=$(get_platform_admin_keycloak_id) + + # Run the organization script with user creation + "$SCRIPT_DIR/create-keycloak-organization.sh" \ + --namespace "$NAMESPACE" \ + --realm "$REALM_NAME" \ + --database "governance" <<<"n +y" # Answer n to create org (already done), y to create user + fi + + # Step 5: Verify integration + echo "" + verify_integration + + # Step 6: Show summary + show_summary + + echo "" + echo -e "${GREEN}Post-install setup complete!${NC}" +} + +# Run main function +main diff --git a/scripts/keycloak/values-bootstrap-keycloak.yaml b/scripts/keycloak/values-bootstrap-keycloak.yaml new file mode 100644 index 0000000..997a285 --- /dev/null +++ b/scripts/keycloak/values-bootstrap-keycloak.yaml @@ -0,0 +1,161 @@ +# Simplified Keycloak Bootstrap values for staging environment +# This configures the bootstrap job without SPI mappers or group management +# Groups/projects are now managed in the governance service + +# Bootstrap job configuration +bootstrap: + enabled: true + + # Use an image that already has curl and jq + image: + repository: dwdraju/alpine-curl-jq + tag: "latest" + pullPolicy: IfNotPresent + + # Job settings + backoffLimit: 3 + ttlSecondsAfterFinished: 300 + activeDeadlineSeconds: 600 + + # Disable the built-in wait since it checks the wrong port + wait: + enabled: false + + # Resources + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + +# Keycloak connection +keycloak: + # Internal service URL + url: "http://keycloak:8080/keycloak" + adminUsername: "admin" + adminPasswordSecret: + name: "keycloak-admin" + key: "password" + + # Realm configuration + realm: + name: "governance" + displayName: "Governance Platform" + loginWithEmailAllowed: true + registrationAllowed: false + resetPasswordAllowed: true + rememberMe: true + verifyEmail: false + sslRequired: "external" + bruteForceProtected: true + + # Token configuration + tokens: + accessTokenLifespan: 300 # 5 minutes + ssoSessionIdleTimeout: 1800 # 30 minutes + ssoSessionMaxLifespan: 36000 # 10 hours + +# Client configuration +clients: + # Frontend client (public) + frontend: + clientId: "governance-platform-frontend" + name: "Governance Platform Frontend" + description: "Frontend application for Governance Platform" + publicClient: true + redirectUris: + - "https://CHANGE_ME_DOMAIN_HERE/*" + - "http://localhost:5173/*" + webOrigins: + - "https://CHANGE_ME_DOMAIN_HERE" + - "http://localhost:5173" + defaultScopes: + - "openid" + - "profile" + - "email" + - "roles" + optionalScopes: + - "offline_access" + + # Backend client (confidential with service account) + backend: + clientId: "governance-platform-backend" + name: "Governance Platform Backend" + description: "Backend service for Governance Platform" + publicClient: false + serviceAccountsEnabled: true + secretName: "keycloak-backend-client" + secretKey: "client-secret" + redirectUris: + - "https://CHANGE_ME_DOMAIN_HERE/auth/*" + webOrigins: + - "https://CHANGE_ME_DOMAIN_HERE" + defaultScopes: + - "openid" + - "profile" + - "email" + - "roles" + + # Worker client (service account only) + worker: + clientId: "governance-worker" + name: "Governance Worker" + description: "Worker service for Governance Platform" + publicClient: false + serviceAccountsEnabled: true + secretName: "keycloak-worker-client" + secretKey: "client-secret" + defaultScopes: + - "openid" + - "profile" + - "email" + - "roles" + +# Custom scopes for authorization +scopes: + - name: "governance:declarations:create" + description: "Create governance declarations" + - name: "integrity:statements:create" + description: "Create integrity statements" + - name: "read:organizations" + description: "Read access to organizations" + - name: "write:organizations" + description: "Write access to organizations" + - name: "read:projects" + description: "Read access to projects" + - name: "write:projects" + description: "Write access to projects" + - name: "read:evaluations" + description: "Read access to evaluations" + - name: "write:evaluations" + description: "Write access to evaluations" + +# Initial users +users: + # Platform admin user + admin: + enabled: true + username: "platform-admin" + email: "admin@governance.eqtylab.io" + firstName: "Platform" + lastName: "Admin" + emailVerified: true + temporaryPassword: false + secretName: "keycloak-admin-user" + secretKey: "password" + + # Test users - disabled by default + testUsers: + enabled: false + users: [] + +# Output configuration +output: + generateEnvFile: true + createSecrets: true + secrets: + frontend: "keycloak-frontend-config" + backend: "keycloak-backend-config" + worker: "keycloak-worker-config"