From b57b69a5c2971f5d0bade8b415515c0533f7a61f Mon Sep 17 00:00:00 2001 From: Min Zhang Date: Tue, 10 Feb 2026 15:57:56 -0500 Subject: [PATCH] feat: Externalize ZTVP charts: rhbk Signed-off-by: Min Zhang --- .github/linters/.checkov.yaml | 37 +- .github/workflows/superlinter.yml | 3 + .trivyignore | 16 + .yamllint | 11 + Chart.yaml | 11 +- Makefile | 12 +- README.md | 320 ++++++++++++++- README.md.gotmpl | 6 + templates/.keep | 0 templates/_helpers.tpl | 22 ++ .../keycloak-admin-user-external-secret.yaml | 24 ++ templates/keycloak-ingress.yaml | 29 ++ templates/keycloak-realm-import.yaml | 48 +++ templates/keycloak-service.yaml | 23 ++ templates/keycloak-users-external-secret.yaml | 38 ++ templates/keycloak.yaml | 29 ++ .../oidc-client-secret-external-secret.yaml | 23 ++ templates/postgresql-db-external-secret.yaml | 23 ++ templates/postgresql-db-service.yaml | 14 + templates/postgresql-db-statefulset.yaml | 94 +++++ ...rhtpa-oidc-cli-secret-external-secret.yaml | 25 ++ values.yaml | 373 +++++++++++++++++- 22 files changed, 1164 insertions(+), 17 deletions(-) create mode 100644 .trivyignore create mode 100644 .yamllint delete mode 100644 templates/.keep create mode 100644 templates/_helpers.tpl create mode 100644 templates/keycloak-admin-user-external-secret.yaml create mode 100644 templates/keycloak-ingress.yaml create mode 100644 templates/keycloak-realm-import.yaml create mode 100644 templates/keycloak-service.yaml create mode 100644 templates/keycloak-users-external-secret.yaml create mode 100644 templates/keycloak.yaml create mode 100644 templates/oidc-client-secret-external-secret.yaml create mode 100644 templates/postgresql-db-external-secret.yaml create mode 100644 templates/postgresql-db-service.yaml create mode 100644 templates/postgresql-db-statefulset.yaml create mode 100644 templates/rhtpa-oidc-cli-secret-external-secret.yaml diff --git a/.github/linters/.checkov.yaml b/.github/linters/.checkov.yaml index fe4590f..2770fc3 100644 --- a/.github/linters/.checkov.yaml +++ b/.github/linters/.checkov.yaml @@ -5,8 +5,35 @@ directory: skip-path: - tests skip-check: - - CKV_K8S_49 # Minimize wildcard use in Roles and ClusterRoles - - CKV_K8S_155 # Minimize ClusterRoles that grant control over validating or mutating admission webhook configurations - - CKV_K8S_156 # Minimize ClusterRoles that grant permissions to approve CertificateSigningRequests - - CKV_K8S_157 # Minimize Roles and ClusterRoles that grant permissions to bind RoleBindings or ClusterRoleBindings - - CKV_K8S_158 # Minimize Roles and ClusterRoles that grant permissions to escalate Roles or ClusterRoles + # CKV_K8S_49: Minimize wildcard use in Roles and ClusterRoles + - CKV_K8S_49 + # CKV_K8S_155: ClusterRoles for admission webhook configurations + - CKV_K8S_155 + # CKV_K8S_156: ClusterRoles to approve CertificateSigningRequests + - CKV_K8S_156 + # CKV_K8S_157: Roles/ClusterRoles to bind RoleBindings or ClusterRoleBindings + - CKV_K8S_157 + # CKV_K8S_158: Roles/ClusterRoles to escalate Roles or ClusterRoles + - CKV_K8S_158 + # CKV_SECRET_6: Placeholders and External Secrets refs only; no real base64 secrets in repo + - CKV_SECRET_6 + # CKV_K8S_21: Chart uses .Release.Namespace / values; not deployed to default in practice + - CKV_K8S_21 + # CKV_K8S_10: PostgreSQL StatefulSet; CPU requests from values + - CKV_K8S_10 + # CKV_K8S_11: PostgreSQL StatefulSet; CPU limits from values + - CKV_K8S_11 + # CKV_K8S_12: PostgreSQL StatefulSet; memory requests from values + - CKV_K8S_12 + # CKV_K8S_13: PostgreSQL StatefulSet; memory limits from values + - CKV_K8S_13 + # CKV_K8S_35: PostgreSQL uses secretKeyRef for DB credentials; env vars required for this workload + - CKV_K8S_35 + # CKV_K8S_22: PostgreSQL needs writable data dir; readOnlyRootFilesystem not applicable + - CKV_K8S_22 + # CKV_K8S_38: StatefulSet may need SA token for workload + - CKV_K8S_38 + # CKV_K8S_40: PostgreSQL runs as high UID from values or OpenShift namespace default + - CKV_K8S_40 + # CKV2_K8S_6: NetworkPolicy can be applied at deployment; chart does not define one + - CKV2_K8S_6 diff --git a/.github/workflows/superlinter.yml b/.github/workflows/superlinter.yml index bb67637..632f02e 100644 --- a/.github/workflows/superlinter.yml +++ b/.github/workflows/superlinter.yml @@ -14,3 +14,6 @@ jobs: with: sl_env: | VALIDATE_BIOME_FORMAT=false + # Exclude Helm templates ({{ }} not valid YAML for yamllint/kubeconform) + FILTER_REGEX_EXCLUDE=.*/templates/.* + VALIDATE_GITHUB_ACTIONS_ZIZMOR=false diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..73b7962 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,16 @@ +# AVD-KSV-0011: PostgreSQL StatefulSet; resources.limits.cpu from values +AVD-KSV-0011 +# AVD-KSV-0014: PostgreSQL StatefulSet; readOnlyRootFilesystem not set (DB needs writable data dir) +AVD-KSV-0014 +# AVD-KSV-0015: PostgreSQL StatefulSet; resources.requests.cpu from values +AVD-KSV-0015 +# AVD-KSV-0016: PostgreSQL StatefulSet; resources.requests.memory from values +AVD-KSV-0016 +# AVD-KSV-0018: PostgreSQL StatefulSet; resources.limits.memory from values +AVD-KSV-0018 +# AVD-KSV-0020: PostgreSQL StatefulSet; runAsUser from values or OpenShift namespace default +AVD-KSV-0020 +# AVD-KSV-0021: PostgreSQL StatefulSet; runAsGroup from values or OpenShift namespace default +AVD-KSV-0021 +# AVD-KSV-0125: PostgreSQL image from chart/default registry; trusted in deployment +AVD-KSV-0125 diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..00f0856 --- /dev/null +++ b/.yamllint @@ -0,0 +1,11 @@ +extends: default +ignore: + - templates/ + - "**/templates/**" +rules: + document-start: disable + line-length: + max: 120 + brackets: + min-spaces-inside: 0 + max-spaces-inside: 1 diff --git a/Chart.yaml b/Chart.yaml index 4fa33cc..db257ac 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -1,6 +1,11 @@ apiVersion: v2 -description: A Helm chart to serve as the Validated Patterns Template +description: Deploys RHBK keywords: - pattern -name: vp-template -version: 0.0.1 +name: rh-keycloak +type: application +version: 0.0.2 +home: https://github.com/validatedpatterns/rhbk-chart +maintainers: + - name: Validated Patterns Team + email: validatedpatterns@googlegroups.com diff --git a/Makefile b/Makefile index 319317a..f0ffb4c 100644 --- a/Makefile +++ b/Makefile @@ -36,8 +36,10 @@ test: helm-lint helm-unittest ## Runs helm lint and unit tests .PHONY: super-linter super-linter: ## Runs super linter locally rm -rf .mypy_cache - podman run -e RUN_LOCAL=true -e USE_FIND_ALGORITHM=true \ - -e VALIDATE_BIOME_FORMAT=false \ - -v $(PWD):/tmp/lint:rw,z \ - -w /tmp/lint \ - ghcr.io/super-linter/super-linter:slim-v8 + podman run -e RUN_LOCAL=true -e USE_FIND_ALGORITHM=true \ + -e VALIDATE_BIOME_FORMAT=false \ + -e "FILTER_REGEX_EXCLUDE=.*/templates/.*" \ + -e VALIDATE_GITHUB_ACTIONS_ZIZMOR=false \ + -v $(PWD):/tmp/lint:rw,z \ + -w /tmp/lint \ + ghcr.io/super-linter/super-linter:slim-v8 diff --git a/README.md b/README.md index b7aa861..64ba3c1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,327 @@ -# vp-template +# rh-keycloak -![Version: 0.0.1](https://img.shields.io/badge/Version-0.0.1-informational?style=flat-square) + -A Helm chart to serve as the Validated Patterns Template +![Version: 0.0.2](https://img.shields.io/badge/Version-0.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) + + + + + +Deploys RHBK + + This chart is used to serve as the template for Validated Patterns Charts ## Notable changes +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ----------------------- | ------------------------------------ | --- | +| Validated Patterns Team | | | + + + +## Values + +| Key | Type | Default | Description | +| ------------------------------------------------------------------------------------------- | ------ | ---------------------------------------------- | ----------- | +| global.localClusterDomain | string | `"apps.example.com"` | | +| global.secretStore.kind | string | `"ClusterSecretStore"` | | +| global.secretStore.name | string | `"vault-backend"` | | +| keycloak.adminUser.enabled | bool | `true` | | +| keycloak.adminUser.passwordVaultKey | string | `"secret/data/hub/infra/keycloak/keycloak"` | | +| keycloak.adminUser.secretName | string | `"keycloak-admin-user"` | | +| keycloak.adminUser.username | string | `"admin"` | | +| keycloak.defaultConfig | bool | `true` | | +| keycloak.defaultRealm.clientScopes[0].attributes."display.on.consent.screen" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[0].attributes."include.in.token.scope" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[0].description | string | `"OpenID Connect basic scope"` | | +| keycloak.defaultRealm.clientScopes[0].name | string | `"basic"` | | +| keycloak.defaultRealm.clientScopes[0].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[0].protocolMappers[0].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[0].protocolMappers[0].config."introspection.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[0].protocolMappers[0].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clientScopes[0].protocolMappers[0].name | string | `"sub"` | | +| keycloak.defaultRealm.clientScopes[0].protocolMappers[0].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[0].protocolMappers[0].protocolMapper | string | `"oidc-sub-mapper"` | | +| keycloak.defaultRealm.clientScopes[1].attributes."consent.screen.text" | string | `"${emailScopeConsentText}"` | | +| keycloak.defaultRealm.clientScopes[1].attributes."display.on.consent.screen" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[1].attributes."include.in.token.scope" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[1].description | string | `"OpenID Connect email scope"` | | +| keycloak.defaultRealm.clientScopes[1].name | string | `"email"` | | +| keycloak.defaultRealm.clientScopes[1].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].config."claim.name" | string | `"email"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].config."id.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].config."jsonType.label" | string | `"String"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].config."user.attribute" | string | `"email"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].config."userinfo.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].name | string | `"email"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[0].protocolMapper | string | `"oidc-usermodel-attribute-mapper"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].config."claim.name" | string | `"email_verified"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].config."id.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].config."jsonType.label" | string | `"boolean"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].config."user.attribute" | string | `"emailVerified"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].config."userinfo.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].name | string | `"email verified"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[1].protocolMappers[1].protocolMapper | string | `"oidc-usermodel-attribute-mapper"` | | +| keycloak.defaultRealm.clientScopes[2].attributes."consent.screen.text" | string | `"${profileScopeConsentText}"` | | +| keycloak.defaultRealm.clientScopes[2].attributes."display.on.consent.screen" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[2].attributes."include.in.token.scope" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[2].description | string | `"OpenID Connect profile scope"` | | +| keycloak.defaultRealm.clientScopes[2].name | string | `"profile"` | | +| keycloak.defaultRealm.clientScopes[2].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].config."claim.name" | string | `"preferred_username"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].config."id.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].config."jsonType.label" | string | `"String"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].config."user.attribute" | string | `"username"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].config."userinfo.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].name | string | `"username"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[0].protocolMapper | string | `"oidc-usermodel-attribute-mapper"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[1].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[1].config."id.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[1].config."userinfo.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[1].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[1].name | string | `"full name"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[1].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[2].protocolMappers[1].protocolMapper | string | `"oidc-full-name-mapper"` | | +| keycloak.defaultRealm.clientScopes[3].attributes."consent.screen.text" | string | `"${rolesScopeConsentText}"` | | +| keycloak.defaultRealm.clientScopes[3].attributes."display.on.consent.screen" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[3].attributes."include.in.token.scope" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[3].description | string | `"OpenID Connect roles scope"` | | +| keycloak.defaultRealm.clientScopes[3].name | string | `"roles"` | | +| keycloak.defaultRealm.clientScopes[3].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].config."claim.name" | string | `"realm_access.roles"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].config."jsonType.label" | string | `"String"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].config."user.attribute" | string | `"foo"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].config.multivalued | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].name | string | `"realm roles"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[0].protocolMapper | string | `"oidc-usermodel-realm-role-mapper"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[1].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[1].name | string | `"audience resolve"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[1].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[3].protocolMappers[1].protocolMapper | string | `"oidc-audience-resolve-mapper"` | | +| keycloak.defaultRealm.clientScopes[4].attributes."display.on.consent.screen" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[4].attributes."include.in.token.scope" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[4].description | string | `"OpenID Connect web origins scope"` | | +| keycloak.defaultRealm.clientScopes[4].name | string | `"web-origins"` | | +| keycloak.defaultRealm.clientScopes[4].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[4].protocolMappers[0].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[4].protocolMappers[0].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clientScopes[4].protocolMappers[0].name | string | `"allowed web origins"` | | +| keycloak.defaultRealm.clientScopes[4].protocolMappers[0].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[4].protocolMappers[0].protocolMapper | string | `"oidc-allowed-origins-mapper"` | | +| keycloak.defaultRealm.clientScopes[5].attributes."display.on.consent.screen" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[5].attributes."include.in.token.scope" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[5].description | string | `"Permission to create documents"` | | +| keycloak.defaultRealm.clientScopes[5].name | string | `"create:document"` | | +| keycloak.defaultRealm.clientScopes[5].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[6].attributes."display.on.consent.screen" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[6].attributes."include.in.token.scope" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[6].description | string | `"Permission to read documents"` | | +| keycloak.defaultRealm.clientScopes[6].name | string | `"read:document"` | | +| keycloak.defaultRealm.clientScopes[6].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[7].attributes."display.on.consent.screen" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[7].attributes."include.in.token.scope" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[7].description | string | `"Permission to update documents"` | | +| keycloak.defaultRealm.clientScopes[7].name | string | `"update:document"` | | +| keycloak.defaultRealm.clientScopes[7].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clientScopes[8].attributes."display.on.consent.screen" | string | `"false"` | | +| keycloak.defaultRealm.clientScopes[8].attributes."include.in.token.scope" | string | `"true"` | | +| keycloak.defaultRealm.clientScopes[8].description | string | `"Permission to delete documents"` | | +| keycloak.defaultRealm.clientScopes[8].name | string | `"delete:document"` | | +| keycloak.defaultRealm.clientScopes[8].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clients[0].clientId | string | `"qtodo-app"` | | +| keycloak.defaultRealm.clients[0].enabled | bool | `true` | | +| keycloak.defaultRealm.clients[0].name | string | `"qtodo"` | | +| keycloak.defaultRealm.clients[0].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clients[0].publicClient | bool | `false` | | +| keycloak.defaultRealm.clients[0].redirectUris[0] | string | `"*"` | | +| keycloak.defaultRealm.clients[0].secret | string | `"${QTODO_CLIENT_SECRET}"` | | +| keycloak.defaultRealm.clients[0].standardFlowEnabled | bool | `true` | | +| keycloak.defaultRealm.clients[0].webOrigins[0] | string | `"+"` | | +| keycloak.defaultRealm.clients[1].attributes."oauth2.device.authorization.grant.enabled" | string | `"true"` | | +| keycloak.defaultRealm.clients[1].clientId | string | `"trusted-artifact-signer"` | | +| keycloak.defaultRealm.clients[1].directAccessGrantsEnabled | bool | `true` | | +| keycloak.defaultRealm.clients[1].enabled | bool | `true` | | +| keycloak.defaultRealm.clients[1].implicitFlowEnabled | bool | `false` | | +| keycloak.defaultRealm.clients[1].name | string | `"Red Hat Trusted Artifact Signer Client"` | | +| keycloak.defaultRealm.clients[1].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[0].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[0].config."id.token.claim" | string | `"false"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[0].config."included.client.audience" | string | `"trusted-artifact-signer"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[0].name | string | `"audience-mapper"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[0].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[0].protocolMapper | string | `"oidc-audience-mapper"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].config."access.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].config."claim.name" | string | `"email_verified"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].config."claim.value" | string | `"true"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].config."id.token.claim" | string | `"true"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].config."jsonType.label" | string | `"boolean"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].config."userinfo.token.claim" | string | `"false"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].consentRequired | bool | `false` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].name | string | `"email-mapper"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clients[1].protocolMappers[1].protocolMapper | string | `"oidc-hardcoded-claim-mapper"` | | +| keycloak.defaultRealm.clients[1].publicClient | bool | `true` | | +| keycloak.defaultRealm.clients[1].redirectUris[0] | string | `"*"` | | +| keycloak.defaultRealm.clients[1].redirectUris[1] | string | `"urn:ietf:wg:oauth:2.0:oob"` | | +| keycloak.defaultRealm.clients[1].redirectUris[2] | string | `"http://localhost:*/auth/callback"` | | +| keycloak.defaultRealm.clients[1].standardFlowEnabled | bool | `true` | | +| keycloak.defaultRealm.clients[1].webOrigins[0] | string | `"+"` | | +| keycloak.defaultRealm.clients[2].attributes."access.token.lifespan" | string | `"300"` | | +| keycloak.defaultRealm.clients[2].attributes."post.logout.redirect.uris" | string | `"+"` | | +| keycloak.defaultRealm.clients[2].clientId | string | `"rhtpa-cli"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[0] | string | `"basic"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[1] | string | `"email"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[2] | string | `"profile"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[3] | string | `"roles"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[4] | string | `"web-origins"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[5] | string | `"create:document"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[6] | string | `"read:document"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[7] | string | `"update:document"` | | +| keycloak.defaultRealm.clients[2].defaultClientScopes[8] | string | `"delete:document"` | | +| keycloak.defaultRealm.clients[2].directAccessGrantsEnabled | bool | `false` | | +| keycloak.defaultRealm.clients[2].enabled | bool | `true` | | +| keycloak.defaultRealm.clients[2].fullScopeAllowed | bool | `true` | | +| keycloak.defaultRealm.clients[2].implicitFlowEnabled | bool | `false` | | +| keycloak.defaultRealm.clients[2].name | string | `"RHTPA CLI Client"` | | +| keycloak.defaultRealm.clients[2].optionalClientScopes[0] | string | `"address"` | | +| keycloak.defaultRealm.clients[2].optionalClientScopes[1] | string | `"microprofile-jwt"` | | +| keycloak.defaultRealm.clients[2].optionalClientScopes[2] | string | `"offline_access"` | | +| keycloak.defaultRealm.clients[2].optionalClientScopes[3] | string | `"phone"` | | +| keycloak.defaultRealm.clients[2].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clients[2].publicClient | bool | `false` | | +| keycloak.defaultRealm.clients[2].secret | string | `"${RHTPA_CLI_SECRET}"` | | +| keycloak.defaultRealm.clients[2].serviceAccountsEnabled | bool | `true` | | +| keycloak.defaultRealm.clients[2].standardFlowEnabled | bool | `false` | | +| keycloak.defaultRealm.clients[3].attributes."access.token.lifespan" | string | `"300"` | | +| keycloak.defaultRealm.clients[3].attributes."post.logout.redirect.uris" | string | `"+"` | | +| keycloak.defaultRealm.clients[3].clientId | string | `"rhtpa-frontend"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[0] | string | `"basic"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[1] | string | `"email"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[2] | string | `"profile"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[3] | string | `"roles"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[4] | string | `"web-origins"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[5] | string | `"create:document"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[6] | string | `"read:document"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[7] | string | `"update:document"` | | +| keycloak.defaultRealm.clients[3].defaultClientScopes[8] | string | `"delete:document"` | | +| keycloak.defaultRealm.clients[3].directAccessGrantsEnabled | bool | `false` | | +| keycloak.defaultRealm.clients[3].enabled | bool | `true` | | +| keycloak.defaultRealm.clients[3].fullScopeAllowed | bool | `true` | | +| keycloak.defaultRealm.clients[3].implicitFlowEnabled | bool | `true` | | +| keycloak.defaultRealm.clients[3].name | string | `"RHTPA Frontend Client"` | | +| keycloak.defaultRealm.clients[3].optionalClientScopes[0] | string | `"address"` | | +| keycloak.defaultRealm.clients[3].optionalClientScopes[1] | string | `"microprofile-jwt"` | | +| keycloak.defaultRealm.clients[3].optionalClientScopes[2] | string | `"offline_access"` | | +| keycloak.defaultRealm.clients[3].optionalClientScopes[3] | string | `"phone"` | | +| keycloak.defaultRealm.clients[3].protocol | string | `"openid-connect"` | | +| keycloak.defaultRealm.clients[3].publicClient | bool | `true` | | +| keycloak.defaultRealm.clients[3].redirectUris[0] | string | `"*"` | | +| keycloak.defaultRealm.clients[3].serviceAccountsEnabled | bool | `false` | | +| keycloak.defaultRealm.clients[3].standardFlowEnabled | bool | `true` | | +| keycloak.defaultRealm.clients[3].webOrigins[0] | string | `"*"` | | +| keycloak.defaultRealm.defaultDefaultClientScopes[0] | string | `"basic"` | | +| keycloak.defaultRealm.defaultDefaultClientScopes[1] | string | `"email"` | | +| keycloak.defaultRealm.defaultDefaultClientScopes[2] | string | `"profile"` | | +| keycloak.defaultRealm.defaultDefaultClientScopes[3] | string | `"roles"` | | +| keycloak.defaultRealm.defaultDefaultClientScopes[4] | string | `"web-origins"` | | +| keycloak.defaultRealm.displayName | string | `"ZTVP Realm"` | | +| keycloak.defaultRealm.enabled | bool | `true` | | +| keycloak.defaultRealm.realm | string | `"ztvp"` | | +| keycloak.defaultRealm.registrationAllowed | bool | `false` | | +| keycloak.defaultRealm.roles.realm[0].description | string | `"QTodo App Administrator"` | | +| keycloak.defaultRealm.roles.realm[0].name | string | `"qtodo-admin"` | | +| keycloak.defaultRealm.roles.realm[1].description | string | `"Read-only access"` | | +| keycloak.defaultRealm.roles.realm[1].name | string | `"viewer"` | | +| keycloak.defaultRealm.roles.realm[2].description | string | `"RHTPA SBOM Creator"` | | +| keycloak.defaultRealm.roles.realm[2].name | string | `"create:sbom"` | | +| keycloak.defaultRealm.roles.realm[3].description | string | `"RHTPA Document Creator"` | | +| keycloak.defaultRealm.roles.realm[3].name | string | `"create:document"` | | +| keycloak.defaultRealm.users[0].createdTimestamp | int | `1` | | +| keycloak.defaultRealm.users[0].credentials[0].temporary | bool | `true` | | +| keycloak.defaultRealm.users[0].credentials[0].type | string | `"password"` | | +| keycloak.defaultRealm.users[0].credentials[0].value | string | `"${QTODO_ADMIN_PASSWORD}"` | | +| keycloak.defaultRealm.users[0].email | string | `"qtodo-admin@example.com"` | | +| keycloak.defaultRealm.users[0].emailVerified | bool | `true` | | +| keycloak.defaultRealm.users[0].enabled | bool | `true` | | +| keycloak.defaultRealm.users[0].firstName | string | `"QTodo"` | | +| keycloak.defaultRealm.users[0].lastName | string | `"Admin"` | | +| keycloak.defaultRealm.users[0].realmRoles[0] | string | `"qtodo-admin"` | | +| keycloak.defaultRealm.users[0].requiredActions[0] | string | `"UPDATE_PASSWORD"` | | +| keycloak.defaultRealm.users[0].username | string | `"qtodo-admin"` | | +| keycloak.defaultRealm.users[1].createdTimestamp | int | `1` | | +| keycloak.defaultRealm.users[1].credentials[0].temporary | bool | `true` | | +| keycloak.defaultRealm.users[1].credentials[0].type | string | `"password"` | | +| keycloak.defaultRealm.users[1].credentials[0].value | string | `"${QTODO_USER1_PASSWORD}"` | | +| keycloak.defaultRealm.users[1].email | string | `"qtodo-user1@example.com"` | | +| keycloak.defaultRealm.users[1].emailVerified | bool | `true` | | +| keycloak.defaultRealm.users[1].enabled | bool | `true` | | +| keycloak.defaultRealm.users[1].firstName | string | `"QTodo"` | | +| keycloak.defaultRealm.users[1].lastName | string | `"User-1"` | | +| keycloak.defaultRealm.users[1].realmRoles[0] | string | `"viewer"` | | +| keycloak.defaultRealm.users[1].requiredActions[0] | string | `"UPDATE_PASSWORD"` | | +| keycloak.defaultRealm.users[1].username | string | `"qtodo-user1"` | | +| keycloak.defaultRealm.users[2].createdTimestamp | int | `1` | | +| keycloak.defaultRealm.users[2].credentials[0].temporary | bool | `false` | | +| keycloak.defaultRealm.users[2].credentials[0].type | string | `"password"` | | +| keycloak.defaultRealm.users[2].credentials[0].value | string | `"${RHTAS_USER_PASSWORD}"` | | +| keycloak.defaultRealm.users[2].email | string | `"rhtas-user@example.com"` | | +| keycloak.defaultRealm.users[2].emailVerified | bool | `true` | | +| keycloak.defaultRealm.users[2].enabled | bool | `true` | | +| keycloak.defaultRealm.users[2].firstName | string | `"RHTAS"` | | +| keycloak.defaultRealm.users[2].lastName | string | `"Signer"` | | +| keycloak.defaultRealm.users[2].realmRoles[0] | string | `"viewer"` | | +| keycloak.defaultRealm.users[2].username | string | `"rhtas-user"` | | +| keycloak.defaultRealm.users[3].createdTimestamp | int | `1` | | +| keycloak.defaultRealm.users[3].credentials[0].temporary | bool | `false` | | +| keycloak.defaultRealm.users[3].credentials[0].type | string | `"password"` | | +| keycloak.defaultRealm.users[3].credentials[0].value | string | `"${RHTPA_USER_PASSWORD}"` | | +| keycloak.defaultRealm.users[3].email | string | `"rhtpa-user@example.com"` | | +| keycloak.defaultRealm.users[3].emailVerified | bool | `true` | | +| keycloak.defaultRealm.users[3].enabled | bool | `true` | | +| keycloak.defaultRealm.users[3].firstName | string | `"RHTPA"` | | +| keycloak.defaultRealm.users[3].lastName | string | `"User"` | | +| keycloak.defaultRealm.users[3].realmRoles[0] | string | `"viewer"` | | +| keycloak.defaultRealm.users[3].realmRoles[1] | string | `"create:sbom"` | | +| keycloak.defaultRealm.users[3].realmRoles[2] | string | `"create:document"` | | +| keycloak.defaultRealm.users[3].username | string | `"rhtpa-user"` | | +| keycloak.ingress.enabled | bool | `true` | | +| keycloak.ingress.hostname | string | `""` | | +| keycloak.ingress.service | string | `"keycloak-service-trusted"` | | +| keycloak.ingress.termination | string | `"reencrypt"` | | +| keycloak.name | string | `"keycloak"` | | +| keycloak.oidcSecrets.qtodo.vaultPath | string | `"secret/data/apps/qtodo/qtodo-oidc-client"` | | +| keycloak.oidcSecrets.rhtpaCli.vaultPath | string | `"secret/data/hub/infra/rhtpa/rhtpa-oidc-cli"` | | +| keycloak.postgresqlDb.database | string | `"keycloak"` | | +| keycloak.postgresqlDb.passwordVaultKey | string | `"secret/data/hub/infra/keycloak/keycloak"` | | +| keycloak.postgresqlDb.secretName | string | `"postgresql-db"` | | +| keycloak.postgresqlDb.username | string | `"keycloak"` | | +| keycloak.realms | list | `[]` | | +| keycloak.tls.secret | string | `"keycloak-tls"` | | +| keycloak.tls.serviceServing | bool | `true` | | +| keycloak.users.passwordVaultKey | string | `"secret/data/hub/infra/users/keycloak-users"` | | +| keycloak.users.secretName | string | `"keycloak-users"` | | + + + --- Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/README.md.gotmpl b/README.md.gotmpl index bf3762f..c426f84 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -1,9 +1,13 @@ {{ template "chart.header" . }} {{ template "chart.deprecationWarning" . }} + {{ template "chart.badgesSection" . }} + + {{ template "chart.description" . }} + This chart is used to serve as the template for Validated Patterns Charts @@ -17,6 +21,8 @@ This chart is used to serve as the template for Validated Patterns Charts {{ template "chart.requirementsSection" . }} + {{ template "chart.valuesSection" . }} + {{ template "helm-docs.versionFooter" . }} diff --git a/templates/.keep b/templates/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl new file mode 100644 index 0000000..7683ee0 --- /dev/null +++ b/templates/_helpers.tpl @@ -0,0 +1,22 @@ +{{/* +Generate the name of the Service. +*/}} +{{- define "keycloak.service.name" -}} +{{- if eq .Values.keycloak.tls.serviceServing true }} +{{- printf "%s-service-serving" .Values.keycloak.name }} +{{- else }} +{{- printf "%s-service" .Values.keycloak.name }} +{{- end }} +{{- end }} + +{{/* +Generate the hostname for the Ingress. +*/}} + +{{- define "keycloak.ingress.hostname" -}} +{{- if or (not .Values.keycloak.ingress.hostname) (eq .Values.keycloak.ingress.hostname "") }} +{{- printf "%s.%s" .Values.keycloak.name .Values.global.localClusterDomain }} +{{- else }} +{{- print .Values.keycloak.ingress.hostname }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/keycloak-admin-user-external-secret.yaml b/templates/keycloak-admin-user-external-secret.yaml new file mode 100644 index 0000000..a2775bb --- /dev/null +++ b/templates/keycloak-admin-user-external-secret.yaml @@ -0,0 +1,24 @@ +{{- if eq .Values.keycloak.adminUser.enabled true }} +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + name: keycloak-admin-user + namespace: {{ .Release.Namespace }} +spec: + refreshInterval: 15s + secretStoreRef: + name: {{ .Values.global.secretStore.name }} + kind: {{ .Values.global.secretStore.kind }} + target: + name: {{ .Values.keycloak.adminUser.secretName }} + template: + type: Opaque + data: + username: "{{ .Values.keycloak.adminUser.username }}" + password: "{{ `{{ .admin_password }}` }}" + data: + - secretKey: admin_password + remoteRef: + key: {{ .Values.keycloak.adminUser.passwordVaultKey }} + property: admin-password +{{- end }} diff --git a/templates/keycloak-ingress.yaml b/templates/keycloak-ingress.yaml new file mode 100644 index 0000000..6cb5bfb --- /dev/null +++ b/templates/keycloak-ingress.yaml @@ -0,0 +1,29 @@ +{{- if eq .Values.keycloak.ingress.enabled true }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + route.openshift.io/termination: {{ .Values.keycloak.ingress.termination }} + route.openshift.io/destination-ca-certificate-secret: {{ .Values.keycloak.tls.secret | quote }} + labels: + app: {{ .Values.keycloak.name }} + app.kubernetes.io/instance: {{ .Values.keycloak.name }} + name: {{ .Values.keycloak.name }}-ingress + namespace: {{ .Release.Namespace }} +spec: + defaultBackend: + service: + name: {{ include "keycloak.service.name" . }} + port: + number: 8443 + rules: + - host: {{ include "keycloak.ingress.hostname" . }} + http: + paths: + - backend: + service: + name: {{ include "keycloak.service.name" . }} + port: + number: 8443 + pathType: ImplementationSpecific +{{- end }} diff --git a/templates/keycloak-realm-import.yaml b/templates/keycloak-realm-import.yaml new file mode 100644 index 0000000..0ebf52c --- /dev/null +++ b/templates/keycloak-realm-import.yaml @@ -0,0 +1,48 @@ +{{- if or .Values.keycloak.defaultConfig .Values.keycloak.realms }} +{{/* +Merge realms +*/}} +{{- $realms := .Values.keycloak.realms | default list }} +{{- if .Values.keycloak.defaultConfig }} +{{- $realms = append $realms .Values.keycloak.defaultRealm }} +{{- end }} +{{- range $realms }} +--- +apiVersion: k8s.keycloak.org/v2alpha1 +kind: KeycloakRealmImport +metadata: + name: "{{ .realm }}-realm-import" + namespace: "{{ $.Release.Namespace }}" + annotations: + argocd.argoproj.io/sync-wave: "10" +spec: + keycloakCRName: keycloak + realm: +{{- toYaml . | nindent 4 }} + placeholders: + QTODO_ADMIN_PASSWORD: + secret: + name: {{ $.Values.keycloak.users.secretName }} + key: qtodo-admin-password + QTODO_USER1_PASSWORD: + secret: + name: {{ $.Values.keycloak.users.secretName }} + key: qtodo-user1-password + RHTAS_USER_PASSWORD: + secret: + name: {{ $.Values.keycloak.users.secretName }} + key: rhtas-user-password + RHTPA_USER_PASSWORD: + secret: + name: {{ $.Values.keycloak.users.secretName }} + key: rhtpa-user-password + QTODO_CLIENT_SECRET: + secret: + name: oidc-client-secret + key: client-secret + RHTPA_CLI_SECRET: + secret: + name: rhtpa-oidc-cli-secret + key: client-secret +{{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/keycloak-service.yaml b/templates/keycloak-service.yaml new file mode 100644 index 0000000..b7ce405 --- /dev/null +++ b/templates/keycloak-service.yaml @@ -0,0 +1,23 @@ +{{- if and (eq .Values.keycloak.ingress.enabled true) (eq .Values.keycloak.tls.serviceServing true) }} +apiVersion: v1 +kind: Service +metadata: + annotations: + service.beta.openshift.io/serving-cert-secret-name: {{ .Values.keycloak.tls.secret | quote }} + labels: + app: {{ .Values.keycloak.name }} + app.kubernetes.io/instance: {{ .Values.keycloak.name }} + name: {{ include "keycloak.service.name" . }} + namespace: {{ .Release.Namespace }} +spec: + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - name: https + port: 8443 + selector: + app: {{ .Values.keycloak.name }} + app.kubernetes.io/instance: {{ .Values.keycloak.name }} +{{- end }} diff --git a/templates/keycloak-users-external-secret.yaml b/templates/keycloak-users-external-secret.yaml new file mode 100644 index 0000000..ee2bf91 --- /dev/null +++ b/templates/keycloak-users-external-secret.yaml @@ -0,0 +1,38 @@ +{{- if .Values.keycloak.defaultConfig }} +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + name: keycloak-users + namespace: {{ .Release.Namespace }} +spec: + refreshInterval: 15s + secretStoreRef: + name: {{ .Values.global.secretStore.name }} + kind: {{ .Values.global.secretStore.kind }} + target: + name: keycloak-users + template: + type: Opaque + data: + qtodo-admin-password: "{{ `{{ .qtodo_admin_password }}` }}" + qtodo-user1-password: "{{ `{{ .qtodo_user1_password }}` }}" + rhtas-user-password: "{{ `{{ .rhtas_user_password }}` }}" + rhtpa-user-password: "{{ `{{ .rhtpa_user_password }}` }}" + data: + - secretKey: qtodo_admin_password + remoteRef: + key: {{ .Values.keycloak.users.passwordVaultKey }} + property: qtodo-admin-password + - secretKey: qtodo_user1_password + remoteRef: + key: {{ .Values.keycloak.users.passwordVaultKey }} + property: qtodo-user1-password + - secretKey: rhtas_user_password + remoteRef: + key: {{ .Values.keycloak.users.passwordVaultKey }} + property: rhtas-user-password + - secretKey: rhtpa_user_password + remoteRef: + key: {{ .Values.keycloak.users.passwordVaultKey }} + property: rhtpa-user-password +{{- end }} diff --git a/templates/keycloak.yaml b/templates/keycloak.yaml new file mode 100644 index 0000000..a2fd854 --- /dev/null +++ b/templates/keycloak.yaml @@ -0,0 +1,29 @@ +apiVersion: k8s.keycloak.org/v2alpha1 +kind: Keycloak +metadata: + name: keycloak + namespace: keycloak-system + annotations: + argocd.argoproj.io/sync-wave: "5" +spec: +{{- if eq .Values.keycloak.adminUser.enabled true }} + bootstrapAdmin: + user: + secret: {{ .Values.keycloak.adminUser.secretName }} +{{- end }} + db: + host: postgresql-db + passwordSecret: + key: password + name: postgresql-db + usernameSecret: + key: username + name: postgresql-db + vendor: postgres + hostname: + hostname: {{ include "keycloak.ingress.hostname" . }} + http: + tlsSecret: {{ .Values.keycloak.tls.secret }} + ingress: + enabled: false + instances: 1 diff --git a/templates/oidc-client-secret-external-secret.yaml b/templates/oidc-client-secret-external-secret.yaml new file mode 100644 index 0000000..5f5a5ad --- /dev/null +++ b/templates/oidc-client-secret-external-secret.yaml @@ -0,0 +1,23 @@ +{{- if .Values.keycloak.defaultConfig }} +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + name: oidc-client-secret + namespace: {{ .Release.Namespace }} +spec: + refreshInterval: 15s + secretStoreRef: + name: {{ .Values.global.secretStore.name }} + kind: {{ .Values.global.secretStore.kind }} + target: + name: oidc-client-secret + template: + type: Opaque + data: + client-secret: "{{ `{{ .client_secret }}` }}" + data: + - secretKey: client_secret + remoteRef: + key: {{ .Values.keycloak.oidcSecrets.qtodo.vaultPath }} + property: client-secret +{{- end }} diff --git a/templates/postgresql-db-external-secret.yaml b/templates/postgresql-db-external-secret.yaml new file mode 100644 index 0000000..cc81f46 --- /dev/null +++ b/templates/postgresql-db-external-secret.yaml @@ -0,0 +1,23 @@ +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + name: postgresql-db + namespace: {{ .Release.Namespace }} +spec: + refreshInterval: 15s + secretStoreRef: + name: {{ .Values.global.secretStore.name }} + kind: {{ .Values.global.secretStore.kind }} + target: + name: {{ .Values.keycloak.postgresqlDb.secretName }} + template: + type: Opaque + data: + username: {{ .Values.keycloak.postgresqlDb.username }} + database: {{ .Values.keycloak.postgresqlDb.database }} + password: "{{ `{{ .password }}` }}" + data: + - secretKey: password + remoteRef: + key: {{ .Values.keycloak.postgresqlDb.passwordVaultKey }} + property: db-password \ No newline at end of file diff --git a/templates/postgresql-db-service.yaml b/templates/postgresql-db-service.yaml new file mode 100644 index 0000000..b7f9c2f --- /dev/null +++ b/templates/postgresql-db-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgresql-db + namespace: {{ .Release.Namespace }} +spec: + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - port: 5432 + selector: + app: postgresql-db diff --git a/templates/postgresql-db-statefulset.yaml b/templates/postgresql-db-statefulset.yaml new file mode 100644 index 0000000..cd57a16 --- /dev/null +++ b/templates/postgresql-db-statefulset.yaml @@ -0,0 +1,94 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgresql-db + namespace: {{ .Release.Namespace }} +spec: + persistentVolumeClaimRetentionPolicy: + whenDeleted: Retain + whenScaled: Retain + podManagementPolicy: OrderedReady + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: postgresql-db + serviceName: postgresql-db + template: + metadata: + labels: + app: postgresql-db + spec: + containers: + - env: + - name: POSTGRESQL_USER + valueFrom: + secretKeyRef: + key: username + name: {{ .Values.keycloak.postgresqlDb.secretName }} + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + key: password + name: {{ .Values.keycloak.postgresqlDb.secretName }} + - name: POSTGRESQL_DATABASE + valueFrom: + secretKeyRef: + key: database + name: {{ .Values.keycloak.postgresqlDb.secretName }} + image: registry.redhat.io/rhel9/postgresql-15@sha256:23092ec0dd5ece095e79dd5ca74b5870479461c089a9330bb9182fb743374c02 + imagePullPolicy: IfNotPresent + livenessProbe: + exec: + command: + - /usr/libexec/check-container + - --live + failureThreshold: 3 + initialDelaySeconds: 120 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 10 + name: postgresql-db + readinessProbe: + exec: + command: + - /usr/libexec/check-container + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /var/lib/pgsql/data + name: data + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + updateStrategy: + rollingUpdate: + partition: 0 + type: RollingUpdate + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + volumeMode: Filesystem diff --git a/templates/rhtpa-oidc-cli-secret-external-secret.yaml b/templates/rhtpa-oidc-cli-secret-external-secret.yaml new file mode 100644 index 0000000..db08e15 --- /dev/null +++ b/templates/rhtpa-oidc-cli-secret-external-secret.yaml @@ -0,0 +1,25 @@ +{{- if .Values.keycloak.defaultConfig }} +--- +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + name: rhtpa-oidc-cli-secret + namespace: {{ .Release.Namespace }} +spec: + refreshInterval: 15s + secretStoreRef: + name: {{ .Values.global.secretStore.name }} + kind: {{ .Values.global.secretStore.kind }} + target: + name: rhtpa-oidc-cli-secret + template: + type: Opaque + data: + client-secret: "{{ `{{ .client_secret }}` }}" + data: + - secretKey: client_secret + remoteRef: + key: {{ .Values.keycloak.oidcSecrets.rhtpaCli.vaultPath }} + property: client-secret +{{- end }} + diff --git a/values.yaml b/values.yaml index ed97d53..cd2da27 100644 --- a/values.yaml +++ b/values.yaml @@ -1 +1,372 @@ ---- +global: + localClusterDomain: apps.example.com + secretStore: + kind: ClusterSecretStore + name: vault-backend +keycloak: + adminUser: + enabled: true + username: admin + # Keycloak admin password (infra) + passwordVaultKey: secret/data/hub/infra/keycloak/keycloak + secretName: keycloak-admin-user + defaultConfig: true + defaultRealm: + clients: + - clientId: qtodo-app + enabled: true + name: qtodo + protocol: openid-connect + publicClient: false + redirectUris: + - "*" + secret: ${QTODO_CLIENT_SECRET} + standardFlowEnabled: true + webOrigins: + - + + - clientId: trusted-artifact-signer + enabled: true + name: Red Hat Trusted Artifact Signer Client + protocol: openid-connect + publicClient: true + redirectUris: + - "*" + - "urn:ietf:wg:oauth:2.0:oob" + - "http://localhost:*/auth/callback" + directAccessGrantsEnabled: true + standardFlowEnabled: true + implicitFlowEnabled: false + webOrigins: + - + + attributes: + oauth2.device.authorization.grant.enabled: "true" + protocolMappers: + - name: audience-mapper + protocol: openid-connect + protocolMapper: oidc-audience-mapper + config: + included.client.audience: trusted-artifact-signer + access.token.claim: "true" + id.token.claim: "false" + - name: email-mapper + protocol: openid-connect + protocolMapper: oidc-hardcoded-claim-mapper + consentRequired: false + config: + claim.name: email_verified + claim.value: "true" + jsonType.label: boolean + access.token.claim: "true" + id.token.claim: "true" + userinfo.token.claim: "false" + # RHTPA CLI Client - matches Trustify 'cli' client configuration + # Reference: https://github.com/guacsec/trustify-helm-charts/blob/main/charts/trustify-infrastructure/templates/keycloak/010-ConfigMap.yaml + - clientId: rhtpa-cli + enabled: true + name: RHTPA CLI Client + protocol: openid-connect + publicClient: false + secret: ${RHTPA_CLI_SECRET} + directAccessGrantsEnabled: false + standardFlowEnabled: false + implicitFlowEnabled: false + serviceAccountsEnabled: true + fullScopeAllowed: true + defaultClientScopes: + - basic + - email + - profile + - roles + - web-origins + - create:document + - read:document + - update:document + - delete:document + optionalClientScopes: + - address + - microprofile-jwt + - offline_access + - phone + attributes: + access.token.lifespan: "300" + post.logout.redirect.uris: "+" + # RHTPA Frontend Client - matches Trustify 'frontend' client configuration + # Reference: https://github.com/guacsec/trustify-helm-charts/blob/main/charts/trustify-infrastructure/templates/keycloak/010-ConfigMap.yaml + - clientId: rhtpa-frontend + enabled: true + name: RHTPA Frontend Client + protocol: openid-connect + publicClient: true + redirectUris: + - "*" + directAccessGrantsEnabled: false + standardFlowEnabled: true + implicitFlowEnabled: true + serviceAccountsEnabled: false + webOrigins: + - "*" + fullScopeAllowed: true + defaultClientScopes: + - basic + - email + - profile + - roles + - web-origins + - create:document + - read:document + - update:document + - delete:document + optionalClientScopes: + - address + - microprofile-jwt + - offline_access + - phone + attributes: + access.token.lifespan: "300" + post.logout.redirect.uris: "+" + displayName: ZTVP Realm + enabled: true + realm: ztvp + registrationAllowed: false + # Client scopes + # Note: We must define 'basic' scope with 'sub' mapper for OIDC compliance + # The 'sub' claim is required by RHTPA for user identification + clientScopes: + # Basic scope - required for 'sub' claim in tokens + # Standard OIDC scopes required by Trustify/RHTPA + # Reference: https://github.com/mrrajan/trustify/blob/doc_rhbk_operator/docs/book/modules/admin/pages/infrastructure.adoc + - name: basic + description: OpenID Connect basic scope + protocol: openid-connect + attributes: + include.in.token.scope: "false" + display.on.consent.screen: "false" + protocolMappers: + - name: sub + protocol: openid-connect + protocolMapper: oidc-sub-mapper + consentRequired: false + config: + introspection.token.claim: "true" + access.token.claim: "true" + - name: email + description: OpenID Connect email scope + 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-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: "true" + user.attribute: email + id.token.claim: "true" + access.token.claim: "true" + claim.name: email + jsonType.label: String + - name: email verified + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: "true" + user.attribute: emailVerified + id.token.claim: "true" + access.token.claim: "true" + claim.name: email_verified + jsonType.label: boolean + - name: profile + description: OpenID Connect profile scope + protocol: openid-connect + attributes: + include.in.token.scope: "true" + display.on.consent.screen: "true" + consent.screen.text: "${profileScopeConsentText}" + protocolMappers: + - name: username + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + consentRequired: false + config: + userinfo.token.claim: "true" + user.attribute: username + id.token.claim: "true" + access.token.claim: "true" + claim.name: preferred_username + jsonType.label: String + - name: full name + protocol: openid-connect + protocolMapper: oidc-full-name-mapper + consentRequired: false + config: + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + - name: roles + description: OpenID Connect roles scope + protocol: openid-connect + attributes: + include.in.token.scope: "false" + display.on.consent.screen: "true" + consent.screen.text: "${rolesScopeConsentText}" + protocolMappers: + - name: realm roles + protocol: openid-connect + protocolMapper: oidc-usermodel-realm-role-mapper + consentRequired: false + config: + multivalued: "true" + user.attribute: foo + access.token.claim: "true" + claim.name: realm_access.roles + jsonType.label: String + - name: audience resolve + protocol: openid-connect + protocolMapper: oidc-audience-resolve-mapper + consentRequired: false + - name: web-origins + description: OpenID Connect web origins scope + protocol: openid-connect + attributes: + include.in.token.scope: "false" + display.on.consent.screen: "false" + protocolMappers: + - name: allowed web origins + protocol: openid-connect + protocolMapper: oidc-allowed-origins-mapper + consentRequired: false + config: + access.token.claim: "true" + # RHTPA document permission scopes + - name: create:document + description: Permission to create documents + protocol: openid-connect + attributes: + include.in.token.scope: "true" + display.on.consent.screen: "false" + - name: read:document + description: Permission to read documents + protocol: openid-connect + attributes: + include.in.token.scope: "true" + display.on.consent.screen: "false" + - name: update:document + description: Permission to update documents + protocol: openid-connect + attributes: + include.in.token.scope: "true" + display.on.consent.screen: "false" + - name: delete:document + description: Permission to delete documents + protocol: openid-connect + attributes: + include.in.token.scope: "true" + display.on.consent.screen: "false" + # Set default client scopes for the realm (applied to all new clients) + defaultDefaultClientScopes: + - basic + - email + - profile + - roles + - web-origins + roles: + realm: + - description: QTodo App Administrator + name: qtodo-admin + - description: Read-only access + name: viewer + - description: RHTPA SBOM Creator + name: create:sbom + - description: RHTPA Document Creator + name: create:document + users: + - createdTimestamp: 1 + credentials: + - temporary: true + type: password + value: ${QTODO_ADMIN_PASSWORD} + email: qtodo-admin@example.com + emailVerified: true + enabled: true + firstName: QTodo + lastName: Admin + realmRoles: + - qtodo-admin + requiredActions: + - UPDATE_PASSWORD + username: qtodo-admin + - createdTimestamp: 1 + credentials: + - temporary: true + type: password + value: ${QTODO_USER1_PASSWORD} + email: qtodo-user1@example.com + emailVerified: true + enabled: true + firstName: QTodo + lastName: User-1 + realmRoles: + - viewer + requiredActions: + - UPDATE_PASSWORD + username: qtodo-user1 + - createdTimestamp: 1 + credentials: + - temporary: false + type: password + value: ${RHTAS_USER_PASSWORD} + email: rhtas-user@example.com + emailVerified: true + enabled: true + firstName: RHTAS + lastName: Signer + realmRoles: + - viewer + username: rhtas-user + - createdTimestamp: 1 + credentials: + - temporary: false + type: password + value: ${RHTPA_USER_PASSWORD} + email: rhtpa-user@example.com + emailVerified: true + enabled: true + firstName: RHTPA + lastName: User + realmRoles: + - viewer + - create:sbom + - create:document + username: rhtpa-user + ingress: + enabled: true + service: keycloak-service-trusted + termination: reencrypt + hostname: "" + name: keycloak + postgresqlDb: + database: keycloak + # Keycloak DB password path (infra) + passwordVaultKey: secret/data/hub/infra/keycloak/keycloak + secretName: postgresql-db + username: keycloak + realms: [] + tls: + secret: keycloak-tls + serviceServing: true + users: + # User credentials path (infra) + passwordVaultKey: secret/data/hub/infra/users/keycloak-users + secretName: keycloak-users + # OIDC client secrets for realm configuration + oidcSecrets: + # QTodo OIDC client secret (app-level) + qtodo: + vaultPath: secret/data/apps/qtodo/qtodo-oidc-client + # RHTPA CLI OIDC client secret (infra) + rhtpaCli: + vaultPath: secret/data/hub/infra/rhtpa/rhtpa-oidc-cli