# KubeVela Application for Fern Platform apiVersion: core.oam.dev/v1beta1 kind: Application metadata: name: fern-platform namespace: fern-platform spec: components: # PostgreSQL Database using simple deployment - name: postgres type: cloud-native-postgres properties: name: postgres namespace: fern-platform instances: 1 storageSize: 0.5Gi # Redis Cache - name: redis type: webservice properties: image: redis:7-alpine namespace: fern-platform imagePullPolicy: IfNotPresent ports: - port: 6379 expose: true resources: requests: cpu: "50m" memory: "64Mi" limits: cpu: "200m" memory: "128Mi" # Keycloak OAuth Provider - name: keycloak type: webservice properties: image: quay.io/keycloak/keycloak:23.0 namespace: fern-platform imagePullPolicy: IfNotPresent cmd: ["/opt/keycloak/bin/kc.sh", "start-dev", "--import-realm"] ports: - port: 8080 expose: true protocol: TCP env: - name: KEYCLOAK_ADMIN value: "admin" - name: KEYCLOAK_ADMIN_PASSWORD value: "admin123" - name: KC_DB value: "dev-file" - name: KC_HOSTNAME_STRICT value: "false" - name: KC_HOSTNAME_STRICT_HTTPS value: "false" - name: KC_HOSTNAME_URL value: "http://keycloak:8080" - name: KC_HOSTNAME_ADMIN_URL value: "http://keycloak:8080" - name: KC_HTTP_ENABLED value: "true" - name: KC_METRICS_ENABLED value: "true" - name: KC_HEALTH_ENABLED value: "true" - name: KC_LOG_LEVEL value: "INFO" # Mount realm configuration for import volumeMounts: configMap: - name: realm-config mountPath: /opt/keycloak/data/import cmName: keycloak-realm-config resources: requests: cpu: "200m" memory: "768Mi" limits: cpu: "500m" memory: "1Gi" livenessProbe: httpGet: path: /health/live port: 8080 initialDelaySeconds: 60 periodSeconds: 30 timeoutSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 traits: - type: gateway properties: domain: keycloak http: "/": 8080 class: traefik # Keycloak Realm Configuration - name: keycloak-realm-config type: raw properties: apiVersion: v1 kind: ConfigMap metadata: name: keycloak-realm-config namespace: fern-platform data: realm.json: | { "realm": "fern-platform", "displayName": "Fern Platform", "enabled": true, "sslRequired": "none", "registrationAllowed": false, "resetPasswordAllowed": true, "editUsernameAllowed": false, "bruteForceProtected": true, "permanentLockout": false, "maxFailureWaitSeconds": 900, "minimumQuickLoginWaitSeconds": 60, "waitIncrementSeconds": 60, "quickLoginCheckMilliSeconds": 1000, "maxDeltaTimeSeconds": 43200, "failureFactor": 30, "loginTheme": "keycloak", "adminTheme": "keycloak", "accountTheme": "keycloak", "emailTheme": "keycloak", "internationalizationEnabled": false, "defaultLocale": "en", "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "ssoSessionIdleTimeoutRememberMe": 0, "ssoSessionMaxLifespanRememberMe": 0, "offlineSessionIdleTimeout": 2592000, "offlineSessionMaxLifespanEnabled": false, "offlineSessionMaxLifespan": 5184000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300, "accessCodeLifespanLogin": 1800, "actionTokenGeneratedByAdminLifespan": 43200, "actionTokenGeneratedByUserLifespan": 300, "oauth2DeviceCodeLifespan": 600, "oauth2DevicePollingInterval": 5, "clients": [ { "clientId": "fern-platform-web", "name": "Fern Platform Web Application", "description": "Main web application client for Fern Platform", "enabled": true, "clientAuthenticatorType": "client-secret", "secret": "fern-platform-client-secret", "directAccessGrantsEnabled": true, "standardFlowEnabled": true, "implicitFlowEnabled": false, "serviceAccountsEnabled": false, "publicClient": false, "bearerOnly": false, "consentRequired": false, "frontchannelLogout": false, "protocol": "openid-connect", "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, "defaultClientScopes": [ "openid", "web-origins", "profile", "email" ], "optionalClientScopes": [ "address", "phone", "offline_access", "microprofile-jwt" ], "access": { "view": true, "configure": true, "manage": true }, "baseUrl": "http://fern-platform.local:8080", "rootUrl": "http://fern-platform.local:8080", "adminUrl": "http://fern-platform.local:8080", "redirectUris": [ "http://localhost:8080/auth/callback", "http://fern-platform.local:8080/auth/callback", "http://fern-platform.local:8080/auth/login", "http://fern-platform.local:8080/*" ], "webOrigins": [ "http://localhost:8080", "http://fern-platform.local:8080", "+" ], "attributes": { "post.logout.redirect.uris": "http://fern-platform.local:8080/auth/login", "frontchannel.logout.url": "http://fern-platform.local:8080/auth/login", "frontchannel.logout.session.required": "false", "backchannel.logout.session.required": "false", "backchannel.logout.revoke.offline.tokens": "false" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "config": { "access.token.claim": "true", "claim.name": "groups", "full.path": "false", "id.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "realm_roles", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-realm-role-mapper", "config": { "access.token.claim": "true", "claim.name": "realm_roles", "id.token.claim": "true", "userinfo.token.claim": "true", "multivalued": "true" } }, { "name": "username", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", "config": { "access.token.claim": "true", "claim.name": "preferred_username", "id.token.claim": "true", "user.attribute": "username", "userinfo.token.claim": "true" } }, { "name": "email verified", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", "config": { "access.token.claim": "true", "claim.name": "email_verified", "id.token.claim": "true", "user.attribute": "emailVerified", "userinfo.token.claim": "true" } }, { "name": "audience resolve", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "config": { "access.token.claim": "true", "included.client.audience": "fern-platform-web" } } ] } ], "clientScopes": [ { "name": "groups", "description": "User groups", "protocol": "openid-connect", "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "true", "consent.screen.text": "User groups" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "config": { "access.token.claim": "true", "claim.name": "groups", "full.path": "false", "id.token.claim": "true", "userinfo.token.claim": "true" } } ] }, { "name": "profile", "description": "OpenID Connect built-in scope: profile", "protocol": "openid-connect", "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "true", "consent.screen.text": "${profileScopeConsentText}" }, "protocolMappers": [ { "name": "profile", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "config": { "userinfo.token.claim": "true", "user.attribute": "profile", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "profile" } }, { "name": "given name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", "config": { "userinfo.token.claim": "true", "user.attribute": "firstName", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "given_name" } }, { "name": "family name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", "config": { "userinfo.token.claim": "true", "user.attribute": "lastName", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "family_name" } }, { "name": "full name", "protocol": "openid-connect", "protocolMapper": "oidc-full-name-mapper", "config": { "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "username", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", "config": { "userinfo.token.claim": "true", "user.attribute": "username", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "preferred_username" } } ] }, { "name": "email", "description": "OpenID Connect built-in scope: email", "protocol": "openid-connect", "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "true", "consent.screen.text": "${emailScopeConsentText}" }, "protocolMappers": [ { "name": "email", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", "config": { "userinfo.token.claim": "true", "user.attribute": "email", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "email" } }, { "name": "email verified", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", "config": { "userinfo.token.claim": "true", "user.attribute": "emailVerified", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "email_verified" } } ] } ], "defaultDefaultClientScopes": [ "role_list", "profile", "email", "web-origins", "acr" ], "defaultOptionalClientScopes": [ "offline_access", "address", "phone", "microprofile-jwt", "groups" ], "groups": [ { "name": "admin", "path": "/admin", "attributes": { "description": ["Platform administrators with full access"] } }, { "name": "manager", "path": "/manager", "attributes": { "description": ["Manager role - can create/edit/delete team projects"] } }, { "name": "user", "path": "/user", "attributes": { "description": ["User role - read-only access to team projects"] } }, { "name": "fern", "path": "/fern", "attributes": { "description": ["Fern team"] } }, { "name": "atmos", "path": "/atmos", "attributes": { "description": ["Atmos team"] } } ], "roles": { "realm": [ { "name": "admin", "description": "Administrative role with full platform access" }, { "name": "manager", "description": "Team manager role - can create/edit/delete team projects" }, { "name": "user", "description": "Team user role - read-only access to team projects" } ] }, "otpPolicyType": "totp", "otpPolicyAlgorithm": "HmacSHA1", "otpPolicyInitialCounter": 0, "otpPolicyDigits": 6, "otpPolicyLookAheadWindow": 1, "otpPolicyPeriod": 30, "otpSupportedApplications": [ "totpAppFreeOTPName", "totpAppGoogleName" ], "webAuthnPolicyRpEntityName": "keycloak", "webAuthnPolicySignatureAlgorithms": [ "ES256" ], "webAuthnPolicyRpId": "", "webAuthnPolicyAttestationConveyancePreference": "not specified", "webAuthnPolicyAuthenticatorAttachment": "not specified", "webAuthnPolicyRequireResidentKey": "not specified", "webAuthnPolicyUserVerificationRequirement": "not specified", "webAuthnPolicyCreateTimeout": 0, "webAuthnPolicyAvoidSameAuthenticatorRegister": false, "webAuthnPolicyAcceptableAaguids": [], "defaultGroups": [], "requiredCredentials": [ "password" ], "users": [ { "username": "admin@fern.com", "email": "admin@fern.com", "firstName": "Platform", "lastName": "Admin", "enabled": true, "emailVerified": true, "credentials": [ { "type": "password", "value": "test123", "temporary": false } ], "groups": ["/admin"], "realmRoles": ["admin"] }, { "username": "fern-manager@fern.com", "email": "fern-manager@fern.com", "firstName": "Fern", "lastName": "Manager", "enabled": true, "emailVerified": true, "credentials": [ { "type": "password", "value": "test123", "temporary": false } ], "groups": ["/fern", "/manager"], "realmRoles": ["manager"] }, { "username": "fern-user@fern.com", "email": "fern-user@fern.com", "firstName": "Fern", "lastName": "User", "enabled": true, "emailVerified": true, "credentials": [ { "type": "password", "value": "test123", "temporary": false } ], "groups": ["/fern", "/user"], "realmRoles": ["user"] }, { "username": "atmos-manager@fern.com", "email": "atmos-manager@fern.com", "firstName": "Atmos", "lastName": "Manager", "enabled": true, "emailVerified": true, "credentials": [ { "type": "password", "value": "test123", "temporary": false } ], "groups": ["/atmos", "/manager"], "realmRoles": ["manager"] }, { "username": "atmos-user@fern.com", "email": "atmos-user@fern.com", "firstName": "Atmos", "lastName": "User", "enabled": true, "emailVerified": true, "credentials": [ { "type": "password", "value": "test123", "temporary": false } ], "groups": ["/atmos", "/user"], "realmRoles": ["user"] } ] } # Fern Platform Service - name: fern-platform type: webservice dependsOn: - postgres - redis - keycloak properties: # Use ghcr.io/guidewire-oss/fern-platform:latest or docker.io/guidewireoss/fern-platform:latest image: ghcr.io/guidewire-oss/fern-platform:0.1.0 imagePullPolicy: Always namespace: fern-platform ports: - port: 8080 expose: true env: - name: LOG_LEVEL value: debug - name: REDIS_HOST value: redis - name: REDIS_PORT value: "6379" # Core Authentication Configuration - name: AUTH_ENABLED value: "true" - name: JWKS_URL value: "http://keycloak:8080/realms/fern-platform/protocol/openid-connect/certs" - name: AUTH_ISSUER value: "http://keycloak:8080/realms/fern-platform" - name: AUTH_AUDIENCE value: "fern-platform-web" # OAuth 2.0/OpenID Connect Configuration - name: OAUTH_ENABLED value: "true" - name: OAUTH_CLIENT_ID value: "fern-platform-web" - name: OAUTH_CLIENT_SECRET value: "fern-platform-client-secret" - name: OAUTH_REDIRECT_URL value: "http://fern-platform.local:8080/auth/callback" - name: OAUTH_SCOPES value: "openid,profile,email,groups" # OAuth Provider Endpoints # Auth URL must use external URL for browser redirects - name: OAUTH_AUTH_URL value: "http://keycloak:8080/realms/fern-platform/protocol/openid-connect/auth" - name: OAUTH_TOKEN_URL value: "http://keycloak:8080/realms/fern-platform/protocol/openid-connect/token" - name: OAUTH_USERINFO_URL value: "http://keycloak:8080/realms/fern-platform/protocol/openid-connect/userinfo" - name: OAUTH_JWKS_URL value: "http://keycloak:8080/realms/fern-platform/protocol/openid-connect/certs" - name: OAUTH_ISSUER_URL value: "http://keycloak:8080/realms/fern-platform" - name: OAUTH_LOGOUT_URL value: "http://keycloak:8080/realms/fern-platform/protocol/openid-connect/logout" # Debug OAuth issues - name: DEBUG_OAUTH value: "true" # Admin User and Group Configuration - name: OAUTH_ADMIN_USERS value: "admin@fern.com" - name: OAUTH_ADMIN_GROUPS value: "fern-platform-admins" # Token Claim Field Mappings - name: OAUTH_USER_ID_FIELD value: "sub" - name: OAUTH_EMAIL_FIELD value: "email" - name: OAUTH_NAME_FIELD value: "name" - name: OAUTH_GROUPS_FIELD value: "groups" - name: OAUTH_ROLES_FIELD value: "realm_roles" # Cookie Configuration for local k3d - name: COOKIE_DOMAIN value: "" # Empty for local development (use current domain) - name: COOKIE_SECURE value: "false" # HTTP for local k3d - name: TRUST_PROXY value: "false" # No proxy in local k3d resources: requests: cpu: "200m" memory: "768Mi" limits: cpu: "300m" memory: "768Mi" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 5 readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 15 periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 3 traits: - type: gateway properties: domain: fern-platform.local http: "/": 8080 class: traefik - type: service-binding properties: envMappings: DB_USER: secret: postgres-app key: username DB_PASSWORD: secret: postgres-app key: password DB_HOST: secret: postgres-app key: host DB_PORT: secret: postgres-app key: port DB_NAME: secret: postgres-app key: dbname policies: - name: debug type: debug # Override policy for environment-specific configs - name: fern-platform-config type: override properties: components: - name: postgres properties: replicas: 1 - name: redis properties: replicas: 1 - name: keycloak properties: replicas: 1 - name: fern-platform properties: replicas: 1 workflow: steps: # Step 1: Deploy PostgreSQL database first - name: deploy-database type: apply-component properties: component: postgres # Step 2: Deploy Redis cache - name: deploy-redis type: apply-component properties: component: redis # Step 3: Deploy Keycloak realm configuration - name: deploy-keycloak-config type: apply-component properties: component: keycloak-realm-config # Step 4: Deploy Keycloak OAuth provider - name: deploy-keycloak type: apply-component dependsOn: - deploy-keycloak-config properties: component: keycloak # Step 5: Wait for infrastructure to be ready - name: wait-for-infrastructure type: suspend properties: duration: 45s # Step 6: Deploy fern-platform after all infrastructure is ready - name: deploy-application type: apply-component dependsOn: - deploy-database - deploy-redis - deploy-keycloak-config - deploy-keycloak - wait-for-infrastructure properties: component: fern-platform