From 448c055693c7e4a02feb647afd53171e2c93f2f3 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Fri, 25 Apr 2025 19:58:08 -0400 Subject: [PATCH 01/49] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20refactor(main.tf)?= =?UTF-8?q?:=20native-ized=20scripts=20for=20tf=20execution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cz.yaml | 11 +- .pre-commit-config.yaml | 2 +- README.md | 11 +- config_job.tf | 102 ++++++++++++ keycloak-db-values.yaml | 18 ++- keycloak-server-values.yaml | 17 +- main.tf | 304 +++++++++++++++++++----------------- 7 files changed, 297 insertions(+), 168 deletions(-) create mode 100644 config_job.tf diff --git a/.cz.yaml b/.cz.yaml index 012238e..e152a43 100644 --- a/.cz.yaml +++ b/.cz.yaml @@ -1,10 +1,11 @@ --- commitizen: - name: cz_conventional_commits - tag_format: v$version - version: 0.1.0 - version_files: - - version.tf:module_version bump_message: "bump: version $current_version → $new_version" changelog_incremental: true + major_version_zero: true + name: cz_gitmoji + tag_format: $version update_changelog_on_bump: true + version_files: + - version.tf:module_version + version_provider: scm diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7ef969f..eb969a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,7 +47,7 @@ repos: # Terraform Hooks - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.98.0 # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases + rev: v1.99.0 # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases hooks: - id: terraform_fmt args: diff --git a/README.md b/README.md index a133e48..fec37e4 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ Common issues and solutions: | Name | Version | |------|---------| -| [aws](#provider\_aws) | 5.95.0 | -| [aws.eecr](#provider\_aws.eecr) | 5.95.0 | +| [aws](#provider\_aws) | 5.96.0 | +| [aws.eecr](#provider\_aws.eecr) | 5.96.0 | | [helm](#provider\_helm) | 2.17.0 | | [kubernetes](#provider\_kubernetes) | 2.36.0 | | [null](#provider\_null) | 3.2.4 | @@ -89,9 +89,6 @@ Common issues and solutions: |------|--------|---------| | [images](#module\_images) | git::https://github.e.it.census.gov/terraform-modules/aws-ecr-copy-images.git/ | tf-upgrade | | [ingress\_resources](#module\_ingress\_resources) | git::https://github.e.it.census.gov/SCT-Engineering/tfmod-istio-service-ingress.git | n/a | -| [keycloak\_db\_config](#module\_keycloak\_db\_config) | git::https://github.e.it.census.gov/SCT-Engineering/tfmod-config-job.git//config-job | n/a | -| [post\_install](#module\_post\_install) | git::https://github.e.it.census.gov/SCT-Engineering/tfmod-config-job.git//config-job | n/a | -| [pre\_install](#module\_pre\_install) | git::https://github.e.it.census.gov/SCT-Engineering/tfmod-config-job.git//config-job | n/a | | [service\_account](#module\_service\_account) | git::https://github.e.it.census.gov/SCT-Engineering/tfmod-config-job.git//service-account | n/a | ## Resources @@ -100,12 +97,14 @@ Common issues and solutions: |------|------| | [helm_release.keycloak](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | | [helm_release.keycloak-db](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [kubernetes_job.keycloak_config](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/job) | resource | | [kubernetes_namespace.keycloak](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | +| [kubernetes_secret.keycloak_secrets](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | | [null_resource.git_version](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [random_password.passwords](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | | [random_uuid.user_secret](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/uuid) | resource | | [aws_ecr_authorization_token.ecr_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecr_authorization_token) | data source | | [aws_ecr_authorization_token.token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecr_authorization_token) | data source | -| [kubernetes_secret.db-secrets](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/data-sources/secret) | data source | ## Inputs diff --git a/config_job.tf b/config_job.tf new file mode 100644 index 0000000..c408864 --- /dev/null +++ b/config_job.tf @@ -0,0 +1,102 @@ +locals { + config_job_script = <<-CONFIG + #!/bin/bash + set -e + + # Error handling + function handle_error { + echo "Error occurred in Keycloak configuration at line $1" + exit 1 + } + trap 'handle_error $LINENO' ERR + + # Setup variables + rootUrl="" + redirectUrl="https://*" + + function wait_for_keycloak { + local max_attempts=30 + local attempt=1 + + while [ $attempt -le $max_attempts ]; do + echo "Healthcheck attempt $attempt of $max_attempts" + if curl -s -o /dev/null -w %%{http_code} http://keycloak-keycloakx-http:${local.public_port_number}/auth/health/ready | grep -q "200"; then + echo "Keycloak is ready" + return 0 + else + echo "Keycloak not ready, waiting..." + sleep 10 + attempt=$((attempt+1)) + fi + done + + echo "Keycloak failed to become ready after $max_attempts attempts" + return 1 + } + + # Login function with retry logic + function login_with_retry { + local max_attempts=5 + local attempt=1 + + while [ $attempt -le $max_attempts ]; do + echo "Login attempt $attempt of $max_attempts" + if login; then + echo "Login successful" + return 0 + else + echo "Login failed, waiting before retry" + sleep 10 + attempt=$((attempt+1)) + fi + done + + echo "Failed to login after $max_attempts attempts" + return 1 + } + + # Wait for Keycloak to be ready before proceeding + echo "Waiting for Keycloak to be available..." + wait_for_keycloak || { + echo "Failed to connect to Keycloak" + exit 1 + } + + # Step 1: Login and create realm + echo "Step 1: Creating realm" + login_with_retry && create_realm ${var.default_realm} + + # Step 2: Create admin user + echo "Step 2: Creating admin user" + create_admin_user ${var.default_realm} "$(kc_user)" "$(kc_passwd)" "${var.admin_email}" + + # Step 3: Create roles + echo "Step 3: Creating roles" + create_realm_role ${var.default_realm} admin "Admin role for ${var.default_realm}" && \ + create_realm_role ${var.default_realm} user "User role for ${var.default_realm}" + + # Step 4: Assign roles + echo "Step 4: Assigning roles" + add_realm_role_to_user ${var.default_realm} user user + + # Step 5: Create client + echo "Step 5: Creating client" + create_client "${var.default_realm}" "${local.user_client_id}" \ + "${random_uuid.user_secret.result}" "$rootUrl" "" \ + "[ \"$redirectUrl\" ]" "" 1 + + # Step 6: Add mapper + echo "Step 6: Adding mapper" + add_mapper_to_client "${var.default_realm}" \ + "$(get_client_id "${var.default_realm}" "${local.user_client_id}")" \ + "realm roles" "openid-connect" "oidc-usermodel-realm-role-mapper" \ + "{ \"multivalued\": \"true\", \"userinfo.token.claim\": \"true\", \"user.attribute\": \"foo\", \"id.token.claim\": \"true\", \"access.token.claim\": \"true\", \"claim.name\": \"realm_access.roles\", \"jsonType.label\": \"String\" }" + + # Step 7: Restart server + echo "Step 7: Restarting server" + restart_server + + echo "Keycloak configuration completed successfully" +CONFIG + +} diff --git a/keycloak-db-values.yaml b/keycloak-db-values.yaml index 2aa7507..6326bbb 100644 --- a/keycloak-db-values.yaml +++ b/keycloak-db-values.yaml @@ -5,15 +5,17 @@ global: postgresql: architecture: replication auth: - database: keycloak - existingSecret: keycloak-db-creds + database: ${database_name} + existingSecret: ${secret_name} secretKeys: - adminPasswordKey: password - userPasswordKey: postgres-password - replicationPasswordKey: replication-password + adminPasswordKey: ${database_password_key} + userPasswordKey: ${postgres_password_key} + replicationPasswordKey: ${replication_password_key} image: - registry: + registry: ${image_registry} + repository: ${image_repository} + tag: ${image_tag} volumePermissions: image: @@ -23,7 +25,9 @@ volumePermissions: metrics: enabled: true image: - registry: + registry: ${metrics_registry} + repository: ${metrics_repository} + tag: ${metrics_tag} primary: resources: diff --git a/keycloak-server-values.yaml b/keycloak-server-values.yaml index 3614a0c..55971b2 100644 --- a/keycloak-server-values.yaml +++ b/keycloak-server-values.yaml @@ -1,9 +1,13 @@ # See https://www.keycloak.org/server/configuration # See https://www.keycloak.org/server/all-config -replicas: 3 +image: + repository: "${image_registry}/${image_repository}" + tag: "${image_tag}" skipInitContainers: true +autoscaling: + enabled: ${autoscaling} command: - "/opt/keycloak/bin/kc.sh" @@ -20,12 +24,12 @@ extraEnv: | - name: KC_BOOTSTRAP_ADMIN_USERNAME valueFrom: secretKeyRef: - name: keycloak-user-creds + name: ${keycloak_user_creds} key: username - name: KC_BOOTSTRAP_ADMIN_PASSWORD valueFrom: secretKeyRef: - name: keycloak-user-creds + name: ${keycloak_user_creds} key: password - name: JAVA_OPTS_APPEND value: >- @@ -34,6 +38,7 @@ extraEnv: | -Djava.awt.headless=true -Djgroups.dns.query={{ include "keycloak.fullname" . }}-headless +# Requires utilities # dbchecker: # enabled: true resources: @@ -48,10 +53,10 @@ database: vendor: postgres hostname: ${database_hostname} port: 5432 - username: ${database_username} + username: ${database_user} # password: dbpassword - existingSecret: keycloak-db-creds - existingSecretKey: password + existingSecret: ${secret_name} + existingSecretKey: ${database_password_key} database: ${database_name} http: diff --git a/main.tf b/main.tf index 56c4fae..277144d 100644 --- a/main.tf +++ b/main.tf @@ -1,6 +1,3 @@ -# main.tf -resource "random_uuid" "user_secret" {} - locals { public_port_number = "80" @@ -8,56 +5,77 @@ locals { user_client_id = "sso.user" user_secret = "user-sso" + k8s_secrets = { + "keycloak-db-creds" = { + data = { + username = "keycloak" + password = random_password.passwords["db"].result + "postgres-password" = random_password.passwords["postgres"].result + "replication-password" = random_password.passwords["replication"].result + } + }, + "keycloak-user-creds" = { + data = { + username = "user" + password = random_password.passwords["keycloak"].result + } + }, + (local.user_secret) = { + data = { + client_id = local.user_client_id + client_secret = random_uuid.user_secret.result + } + } + } + required_passwords = { + db = { + description = "Database user password" + length = 32 + }, + postgres = { + description = "Postgres admin password" + length = 32 + }, + replication = { + description = "Database replication password" + length = 32 + }, + keycloak = { + description = "Keycloak user password" + length = 24 + } + } + + # db_config_job_script = < /tmp/db_config.sql < /tmp/db_config.sql < Date: Mon, 28 Apr 2025 12:01:15 -0400 Subject: [PATCH 02/49] add db config to config-job.tf --- README.md | 3 +- config_job.tf | 83 ++++++++++++++++++++++++++++++++++++++++++++++++--- main.tf | 6 ++-- variables.tf | 8 +---- 4 files changed, 84 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fec37e4..4d793c8 100644 --- a/README.md +++ b/README.md @@ -117,10 +117,9 @@ Common issues and solutions: | [default\_realm](#input\_default\_realm) | The default realm to use to configure the environment. | `string` | `"cluster-user"` | no | | [eecr\_info](#input\_eecr\_info) | Enterprise ECR source information |
object({
account_id = string
alias = string
profile = string
region = string
})
|
{
"account_id": "269222635945",
"alias": "lab-gov-shared-nonprod",
"profile": "269222635945-lab-gov-shared-nonprod",
"region": "us-gov-east-1"
}
| no | | [keycloak\_chart\_version](#input\_keycloak\_chart\_version) | The version of the helm chart to install | `string` | `"7.0.1"` | no | -| [keycloak\_hostname](#input\_keycloak\_hostname) | The hostname used to access the keycloak service. | `string` | `"keycloak"` | no | -| [keycloak\_namespace](#input\_keycloak\_namespace) | The namespace which will be created and into which keycloak will be deployed. | `string` | `"keycloak"` | no | | [keycloak\_tag](#input\_keycloak\_tag) | The image tag associated with the keycloak\_chart\_version | `string` | `"26.0.7"` | no | | [keycloak\_user](#input\_keycloak\_user) | The username for the database. | `string` | `"keycloak"` | no | +| [namespace](#input\_namespace) | The namespace which will be created and into which keycloak will be deployed. | `string` | `"keycloak"` | no | | [postgres\_exporter\_tag](#input\_postgres\_exporter\_tag) | The tag of the postgres exporter image to use. | `string` | `"17.4.0"` | no | | [postgresql\_chart\_version](#input\_postgresql\_chart\_version) | The helm chart version of postgresql to use. | `string` | `"17.4.0"` | no | | [postgresql\_tag](#input\_postgresql\_tag) | The tag of the postgresql image to use. | `string` | `"17.4.0"` | no | diff --git a/config_job.tf b/config_job.tf index c408864..35a3a25 100644 --- a/config_job.tf +++ b/config_job.tf @@ -5,7 +5,7 @@ locals { # Error handling function handle_error { - echo "Error occurred in Keycloak configuration at line $1" + echo "Error occurred in configuration at line $1" exit 1 } trap 'handle_error $LINENO' ERR @@ -13,6 +13,65 @@ locals { # Setup variables rootUrl="" redirectUrl="https://*" + DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" + + # Function to retrieve password from secret + function get_db_password { + secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.password}" | base64 --decode) + echo "$secret_value" + } + + function get_postgres_password { + secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode) + echo "$secret_value" + } + + # Database configuration function + function configure_database { + echo "Starting DB Config Job" + # Create SQL file with commands to set up keycloak user + cat > /tmp/db_config.sql <<-EOF + CREATE ROLE keycloak LOGIN PASSWORD '$(get_db_password)'; + ALTER DATABASE keycloak OWNER TO keycloak; + COMMIT; + EOF + + # Copy SQL file to PostgreSQL pod + echo "Copying SQL file to PostgreSQL pod..." + kubectl cp -n ${kubernetes_namespace.keycloak.metadata[0].name} /tmp/db_config.sql keycloak-db-postgresql-0:/tmp/db_config.sql + + # Execute SQL commands + echo "Executing SQL commands to configure database..." + kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ + /opt/bitnami/postgresql/bin/psql \ + postgresql://postgres:$(get_postgres_password)@keycloak-db-postgresql.${kubernetes_namespace.keycloak.metadata[0].name}.svc.cluster.local:5432/keycloak \ + -f /tmp/db_config.sql + + echo "Database configuration completed" + } + + # Wait for PostgreSQL to be ready + function wait_for_postgresql { + local max_attempts=30 + local attempt=1 + + echo "Waiting for PostgreSQL to be ready..." + + while [ $attempt -le $max_attempts ]; do + echo "PostgreSQL check attempt $attempt of $max_attempts" + if kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- pg_isready -h localhost -U postgres > /dev/null 2>&1; then + echo "PostgreSQL is ready" + return 0 + else + echo "PostgreSQL not ready, waiting..." + sleep 10 + attempt=$((attempt+1)) + fi + done + + echo "PostgreSQL failed to become ready after $max_attempts attempts" + return 1 + } function wait_for_keycloak { local max_attempts=30 @@ -55,13 +114,29 @@ locals { return 1 } - # Wait for Keycloak to be ready before proceeding - echo "Waiting for Keycloak to be available..." + # Start configuration process + echo "Starting configuration process" + + # Wait for PostgreSQL to be ready first + wait_for_postgresql || { + echo "Failed waiting for PostgreSQL" + exit 1 + } + + # Configure the database + echo "Configuring database for Keycloak" + configure_database + + # Restart Keycloak to pick up new database configuration + echo "Restarting Keycloak to apply database changes" + kubectl rollout restart deployment -n ${kubernetes_namespace.keycloak.metadata[0].name} keycloak-keycloakx + + # Wait for Keycloak to be ready with new configuration + echo "Waiting for Keycloak to be available after restart..." wait_for_keycloak || { echo "Failed to connect to Keycloak" exit 1 } - # Step 1: Login and create realm echo "Step 1: Creating realm" login_with_retry && create_realm ${var.default_realm} diff --git a/main.tf b/main.tf index 277144d..fc00cc6 100644 --- a/main.tf +++ b/main.tf @@ -56,7 +56,7 @@ locals { # do_kubectl_cp /tmp/db_config.sql keycloak-db-postgresql-0 /tmp/db_config.sql # do_kubectl exec -n keycloak -t keycloak-db-postgresql-0 -c postgresql -- \ # /opt/bitnami/postgresql/bin/psql \ - # postgresql://postgres:${data.kubernetes_secret.db-secrets.data.password}@keycloak-db-postgresql.${var.keycloak_namespace}.svc.cluster.local:5432/keycloak \ + # postgresql://postgres:${data.kubernetes_secret.db-secrets.data.password}@keycloak-db-postgresql.${var.namespace}.svc.cluster.local:5432/keycloak \ # -f /tmp/db_config.sql # echo "End DB Config Job" # CONFIG @@ -78,7 +78,7 @@ resource "random_uuid" "user_secret" {} resource "kubernetes_namespace" "keycloak" { metadata { - name = var.keycloak_namespace + name = var.namespace labels = { istio-injection = "enabled" } @@ -158,7 +158,7 @@ resource "helm_release" "keycloak" { database_name = "keycloak" database_password_key = "password" database_user = "keycloak" - hostname = format("https://%v.%v", var.keycloak_hostname, var.cluster_domain) + hostname = format("https://%v.%v", var.service_name, var.cluster_domain) image_registry = module.images.images[local.keycloak_key].dest_registry image_repository = module.images.images[local.keycloak_key].dest_repository image_tag = module.images.images[local.keycloak_key].tag diff --git a/variables.tf b/variables.tf index 20ade53..0403679 100644 --- a/variables.tf +++ b/variables.tf @@ -49,13 +49,7 @@ variable "keycloak_chart_version" { default = "7.0.1" } -variable "keycloak_hostname" { - description = "The hostname used to access the keycloak service." - type = string - default = "keycloak" -} - -variable "keycloak_namespace" { +variable "namespace" { description = "The namespace which will be created and into which keycloak will be deployed." type = string default = "keycloak" From 0c24e8127f52a65ada029509784d90ae128fa764 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 12:27:21 -0400 Subject: [PATCH 03/49] update config in server values to match --- .../postgresql/keycloak-server-values.yaml | 6 ++++-- main.tf | 14 -------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/charts/keycloakx/examples/postgresql/keycloak-server-values.yaml b/charts/keycloakx/examples/postgresql/keycloak-server-values.yaml index 3b03e6a..98f9f1f 100644 --- a/charts/keycloakx/examples/postgresql/keycloak-server-values.yaml +++ b/charts/keycloakx/examples/postgresql/keycloak-server-values.yaml @@ -33,8 +33,10 @@ database: vendor: postgres hostname: keycloak-db-postgresql port: 5432 - username: dbusername - password: dbpassword + username: keycloak + password: keycloak + existingSecret: ${secret_name} + existingSecretKey: ${database_password_key} database: keycloak secrets: diff --git a/main.tf b/main.tf index fc00cc6..80385fb 100644 --- a/main.tf +++ b/main.tf @@ -46,20 +46,6 @@ locals { } } - # db_config_job_script = < /tmp/db_config.sql < Date: Mon, 28 Apr 2025 12:33:08 -0400 Subject: [PATCH 04/49] =?UTF-8?q?=F0=9F=90=9B=20fix(outputs.tf):=20update?= =?UTF-8?q?=20namespace=20refs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- outputs.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/outputs.tf b/outputs.tf index 1b617ad..d022197 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,9 +1,9 @@ output "admin_internal_endpoint" { description = "internal endpoint for admin" value = { - hostname = format("%v.%v.svc.cluster.local", var.service_name, var.keycloak_namespace) + hostname = format("%v.%v.svc.cluster.local", var.service_name, var.namespace) port_number = local.admin_internal_port_number - url = format("http://%v.%v.svc.cluster.local:%v", var.service_name, var.keycloak_namespace, local.admin_internal_port_number) + url = format("http://%v.%v.svc.cluster.local:%v", var.service_name, var.namespace, local.admin_internal_port_number) } } @@ -49,7 +49,7 @@ output "client_secret" { output "namespace" { description = "The namespace in which keycloak gets installed in." - value = var.keycloak_namespace + value = var.namespace } ################################################################################ From ad51a170a6db754a058d1a92497884aacd3d8d23 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 12:47:55 -0400 Subject: [PATCH 05/49] =?UTF-8?q?=F0=9F=94=A7=20config(keycloak):=20set=20?= =?UTF-8?q?replicas=20to=201=20then=20update=20replicas=20after=20setup=20?= =?UTF-8?q?is=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + keycloak-server-values.yaml | 2 ++ main.tf | 10 ++++++++++ 3 files changed, 13 insertions(+) diff --git a/README.md b/README.md index 4d793c8..c38cd7d 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ Common issues and solutions: | [kubernetes_namespace.keycloak](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | | [kubernetes_secret.keycloak_secrets](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | | [null_resource.git_version](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [null_resource.scale_keycloak](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | | [random_password.passwords](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | | [random_uuid.user_secret](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/uuid) | resource | | [aws_ecr_authorization_token.ecr_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecr_authorization_token) | data source | diff --git a/keycloak-server-values.yaml b/keycloak-server-values.yaml index 55971b2..20f1b0c 100644 --- a/keycloak-server-values.yaml +++ b/keycloak-server-values.yaml @@ -8,6 +8,8 @@ image: skipInitContainers: true autoscaling: enabled: ${autoscaling} + minReplicas: 1 + maxReplicas: 1 command: - "/opt/keycloak/bin/kc.sh" diff --git a/main.tf b/main.tf index 80385fb..255d086 100644 --- a/main.tf +++ b/main.tf @@ -209,3 +209,13 @@ module "ingress_resources" { service_namespace = kubernetes_namespace.keycloak.metadata[0].name service_port = local.public_port_number } + +resource "null_resource" "scale_keycloak" { + depends_on = [module.ingress_resources] + + provisioner "local-exec" { + command = <<-EOT + kubectl patch deployment keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} -p '{"spec": {"autoscaling": {"MinReplicas": 3, "MaxReplicas": 10}}}' + EOT + } +} From 8a930f3e41562bfba10d8bfa1a2dd44e77542d92 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 13:00:07 -0400 Subject: [PATCH 06/49] =?UTF-8?q?=F0=9F=90=9B=20fix(config):=20move=20auth?= =?UTF-8?q?=20to=20top=20level=20to=20match=20chart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- keycloak-db-values.yaml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/keycloak-db-values.yaml b/keycloak-db-values.yaml index 6326bbb..fa218f6 100644 --- a/keycloak-db-values.yaml +++ b/keycloak-db-values.yaml @@ -2,15 +2,18 @@ global: security: allowInsecureImages: true - postgresql: - architecture: replication - auth: - database: ${database_name} - existingSecret: ${secret_name} - secretKeys: - adminPasswordKey: ${database_password_key} - userPasswordKey: ${postgres_password_key} - replicationPasswordKey: ${replication_password_key} + +architecture: standalone +auth: + database: ${database_name} + existingSecret: ${secret_name} + secretKeys: + adminPasswordKey: ${database_password_key} + userPasswordKey: ${postgres_password_key} + replicationPasswordKey: ${replication_password_key} + +replication: + applicationName: ${database_name} image: registry: ${image_registry} From 06df16d618f7e0e44ae42559e853a88088fffa04 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 13:11:19 -0400 Subject: [PATCH 07/49] static config --- keycloak-db-values.yaml | 14 +++++++++----- keycloak-server-values.yaml | 7 ++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/keycloak-db-values.yaml b/keycloak-db-values.yaml index fa218f6..6c4bc08 100644 --- a/keycloak-db-values.yaml +++ b/keycloak-db-values.yaml @@ -6,11 +6,15 @@ global: architecture: standalone auth: database: ${database_name} - existingSecret: ${secret_name} - secretKeys: - adminPasswordKey: ${database_password_key} - userPasswordKey: ${postgres_password_key} - replicationPasswordKey: ${replication_password_key} + postgresPassword: keycloakadmin + username: keycloak + password: keycloak + + # existingSecret: ${secret_name} + # secretKeys: + # adminPasswordKey: ${database_password_key} + # userPasswordKey: ${postgres_password_key} + # replicationPasswordKey: ${replication_password_key} replication: applicationName: ${database_name} diff --git a/keycloak-server-values.yaml b/keycloak-server-values.yaml index 20f1b0c..c87ba3c 100644 --- a/keycloak-server-values.yaml +++ b/keycloak-server-values.yaml @@ -55,10 +55,11 @@ database: vendor: postgres hostname: ${database_hostname} port: 5432 - username: ${database_user} + username: keycloak + password: keycloak # password: dbpassword - existingSecret: ${secret_name} - existingSecretKey: ${database_password_key} + # existingSecret: ${secret_name} + # existingSecretKey: ${database_password_key} database: ${database_name} http: From 8620a62be1d6d301cc1f73cc6698e632ab416415 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 13:24:15 -0400 Subject: [PATCH 08/49] Fix heredoc string --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index 35a3a25..2b854f9 100644 --- a/config_job.tf +++ b/config_job.tf @@ -34,7 +34,7 @@ locals { CREATE ROLE keycloak LOGIN PASSWORD '$(get_db_password)'; ALTER DATABASE keycloak OWNER TO keycloak; COMMIT; - EOF +EOF # Copy SQL file to PostgreSQL pod echo "Copying SQL file to PostgreSQL pod..." From 97966433075bd1e0a64cb8546335dd9043c02987 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 13:38:33 -0400 Subject: [PATCH 09/49] change config user --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index 2b854f9..8b89aba 100644 --- a/config_job.tf +++ b/config_job.tf @@ -42,7 +42,7 @@ EOF # Execute SQL commands echo "Executing SQL commands to configure database..." - kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ + kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c keycloak -- \ /opt/bitnami/postgresql/bin/psql \ postgresql://postgres:$(get_postgres_password)@keycloak-db-postgresql.${kubernetes_namespace.keycloak.metadata[0].name}.svc.cluster.local:5432/keycloak \ -f /tmp/db_config.sql From dc85b73c19aa7545951324c6aa7d2bc8e6730bc3 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 14:19:46 -0400 Subject: [PATCH 10/49] use secrets again --- config_job.tf | 2 +- keycloak-db-values.yaml | 17 ++++++++--------- keycloak-server-values.yaml | 18 +++++++++++++----- main.tf | 2 ++ 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/config_job.tf b/config_job.tf index 8b89aba..2b854f9 100644 --- a/config_job.tf +++ b/config_job.tf @@ -42,7 +42,7 @@ EOF # Execute SQL commands echo "Executing SQL commands to configure database..." - kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c keycloak -- \ + kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ /opt/bitnami/postgresql/bin/psql \ postgresql://postgres:$(get_postgres_password)@keycloak-db-postgresql.${kubernetes_namespace.keycloak.metadata[0].name}.svc.cluster.local:5432/keycloak \ -f /tmp/db_config.sql diff --git a/keycloak-db-values.yaml b/keycloak-db-values.yaml index 6c4bc08..d393214 100644 --- a/keycloak-db-values.yaml +++ b/keycloak-db-values.yaml @@ -6,15 +6,14 @@ global: architecture: standalone auth: database: ${database_name} - postgresPassword: keycloakadmin - username: keycloak - password: keycloak - - # existingSecret: ${secret_name} - # secretKeys: - # adminPasswordKey: ${database_password_key} - # userPasswordKey: ${postgres_password_key} - # replicationPasswordKey: ${replication_password_key} + # postgresPassword: keycloakadmin + # username: keycloak + # password: keycloak + existingSecret: ${secret_name} + secretKeys: + adminPasswordKey: ${database_password_key} + userPasswordKey: ${postgres_password_key} + replicationPasswordKey: ${replication_password_key} replication: applicationName: ${database_name} diff --git a/keycloak-server-values.yaml b/keycloak-server-values.yaml index c87ba3c..1fe17a9 100644 --- a/keycloak-server-values.yaml +++ b/keycloak-server-values.yaml @@ -41,8 +41,11 @@ extraEnv: | -Djgroups.dns.query={{ include "keycloak.fullname" . }}-headless # Requires utilities -# dbchecker: -# enabled: true +dbchecker: + enabled: true + image: + repository: ${db_checker_image_repo} + tag: ${db_checker_tag} resources: requests: cpu: 500m @@ -55,12 +58,17 @@ database: vendor: postgres hostname: ${database_hostname} port: 5432 - username: keycloak - password: keycloak + database: ${database_name} + existingSecret: ${secret_name} + secretKeys: + adminPasswordKey: ${database_password_key} + userPasswordKey: ${postgres_password_key} + replicationPasswordKey: ${replication_password_key} + # username: keycloak + # password: keycloak # password: dbpassword # existingSecret: ${secret_name} # existingSecretKey: ${database_password_key} - database: ${database_name} http: relativePath: "/" diff --git a/main.tf b/main.tf index 255d086..5b0c657 100644 --- a/main.tf +++ b/main.tf @@ -144,6 +144,8 @@ resource "helm_release" "keycloak" { database_name = "keycloak" database_password_key = "password" database_user = "keycloak" + db_checker_image_repo = format("%v/%v", module.images.images[local.utilities_key].dest_registry, module.images.images[local.utilities_key].dest_repository) + db_checker_tag = module.images.images[local.utilities_key].tag hostname = format("https://%v.%v", var.service_name, var.cluster_domain) image_registry = module.images.images[local.keycloak_key].dest_registry image_repository = module.images.images[local.keycloak_key].dest_repository From 2ae8bd21b5a35845cbb361946f6903e1e01c4c2b Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 14:22:47 -0400 Subject: [PATCH 11/49] pass keys --- main.tf | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/main.tf b/main.tf index 5b0c657..1e26a0d 100644 --- a/main.tf +++ b/main.tf @@ -139,19 +139,21 @@ resource "helm_release" "keycloak" { wait = true values = [templatefile("${path.module}/keycloak-server-values.yaml", { - autoscaling = true - database_hostname = format("%v-%v", helm_release.keycloak-db.name, helm_release.keycloak-db.chart) - database_name = "keycloak" - database_password_key = "password" - database_user = "keycloak" - db_checker_image_repo = format("%v/%v", module.images.images[local.utilities_key].dest_registry, module.images.images[local.utilities_key].dest_repository) - db_checker_tag = module.images.images[local.utilities_key].tag - hostname = format("https://%v.%v", var.service_name, var.cluster_domain) - image_registry = module.images.images[local.keycloak_key].dest_registry - image_repository = module.images.images[local.keycloak_key].dest_repository - image_tag = module.images.images[local.keycloak_key].tag - keycloak_user_creds = kubernetes_secret.keycloak_secrets["keycloak-user-creds"].metadata[0].name - secret_name = kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name + autoscaling = true + database_hostname = format("%v-%v", helm_release.keycloak-db.name, helm_release.keycloak-db.chart) + database_name = "keycloak" + database_password_key = "password" + database_user = "keycloak" + db_checker_image_repo = format("%v/%v", module.images.images[local.utilities_key].dest_registry, module.images.images[local.utilities_key].dest_repository) + db_checker_tag = module.images.images[local.utilities_key].tag + hostname = format("https://%v.%v", var.service_name, var.cluster_domain) + image_registry = module.images.images[local.keycloak_key].dest_registry + image_repository = module.images.images[local.keycloak_key].dest_repository + image_tag = module.images.images[local.keycloak_key].tag + keycloak_user_creds = kubernetes_secret.keycloak_secrets["keycloak-user-creds"].metadata[0].name + postgres_password_key = "postgres-password" + replication_password_key = "replication-password" + secret_name = kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name })] } From 5484accc685e2de6edb513a38dfb9c27cf4dc91e Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 14:42:35 -0400 Subject: [PATCH 12/49] add auth username --- keycloak-db-values.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/keycloak-db-values.yaml b/keycloak-db-values.yaml index d393214..550eca5 100644 --- a/keycloak-db-values.yaml +++ b/keycloak-db-values.yaml @@ -6,6 +6,7 @@ global: architecture: standalone auth: database: ${database_name} + username: ${database_user} # postgresPassword: keycloakadmin # username: keycloak # password: keycloak From 1fa438860a6e2d3b300d0e16b4ae55f0ace14c87 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 15:08:52 -0400 Subject: [PATCH 13/49] try not to config db twice --- config_job.tf | 84 +++++++++++++++++---------------------------------- main.tf | 10 +++--- 2 files changed, 31 insertions(+), 63 deletions(-) diff --git a/config_job.tf b/config_job.tf index 2b854f9..1cba995 100644 --- a/config_job.tf +++ b/config_job.tf @@ -1,53 +1,30 @@ locals { config_job_script = <<-CONFIG - #!/bin/bash - set -e - - # Error handling - function handle_error { - echo "Error occurred in configuration at line $1" - exit 1 - } - trap 'handle_error $LINENO' ERR - - # Setup variables - rootUrl="" - redirectUrl="https://*" - DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" - - # Function to retrieve password from secret - function get_db_password { - secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.password}" | base64 --decode) - echo "$secret_value" - } - - function get_postgres_password { - secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode) - echo "$secret_value" - } - - # Database configuration function - function configure_database { - echo "Starting DB Config Job" - # Create SQL file with commands to set up keycloak user - cat > /tmp/db_config.sql <<-EOF - CREATE ROLE keycloak LOGIN PASSWORD '$(get_db_password)'; - ALTER DATABASE keycloak OWNER TO keycloak; - COMMIT; -EOF - - # Copy SQL file to PostgreSQL pod - echo "Copying SQL file to PostgreSQL pod..." - kubectl cp -n ${kubernetes_namespace.keycloak.metadata[0].name} /tmp/db_config.sql keycloak-db-postgresql-0:/tmp/db_config.sql - - # Execute SQL commands - echo "Executing SQL commands to configure database..." - kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /opt/bitnami/postgresql/bin/psql \ - postgresql://postgres:$(get_postgres_password)@keycloak-db-postgresql.${kubernetes_namespace.keycloak.metadata[0].name}.svc.cluster.local:5432/keycloak \ - -f /tmp/db_config.sql - - echo "Database configuration completed" + #!/bin/bash + set -e + + # Error handling + function handle_error { + echo "Error occurred in configuration at line $1" + exit 1 + } + trap 'handle_error $LINENO' ERR + + # Setup variables + rootUrl="" + redirectUrl="https://*" + DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" + + # Function to retrieve password from secret + function get_db_password { + secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.password}" | base64 --decode) + echo "$secret_value" + } + + function get_postgres_password { + secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode) + echo "$secret_value" + } } # Wait for PostgreSQL to be ready @@ -123,20 +100,13 @@ EOF exit 1 } - # Configure the database - echo "Configuring database for Keycloak" - configure_database - - # Restart Keycloak to pick up new database configuration - echo "Restarting Keycloak to apply database changes" - kubectl rollout restart deployment -n ${kubernetes_namespace.keycloak.metadata[0].name} keycloak-keycloakx - # Wait for Keycloak to be ready with new configuration - echo "Waiting for Keycloak to be available after restart..." + echo "Waiting for Keycloak to be available..." wait_for_keycloak || { echo "Failed to connect to Keycloak" exit 1 } + # Step 1: Login and create realm echo "Step 1: Creating realm" login_with_retry && create_realm ${var.default_realm} diff --git a/main.tf b/main.tf index 1e26a0d..3539c8d 100644 --- a/main.tf +++ b/main.tf @@ -1,10 +1,8 @@ locals { - public_port_number = "80" - admin_internal_port_number = "9000" - - user_client_id = "sso.user" - user_secret = "user-sso" + public_port_number = "80" + user_client_id = "sso.user" + user_secret = "user-sso" k8s_secrets = { "keycloak-db-creds" = { data = { @@ -112,7 +110,7 @@ resource "helm_release" "keycloak-db" { values = [ templatefile("${path.module}/keycloak-db-values.yaml", { database_name = "keycloak" - database_password_key = "password" + database_password_key = "postgres-password" database_user = "keycloak" image_registry = module.images.images[local.postgresql_key].dest_registry image_repository = module.images.images[local.postgresql_key].dest_repository From 29ea8e4188fe5e03451e9726df127f17f0693965 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 15:12:38 -0400 Subject: [PATCH 14/49] password --- keycloak-db-values.yaml | 2 +- main.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/keycloak-db-values.yaml b/keycloak-db-values.yaml index 550eca5..2ecaab6 100644 --- a/keycloak-db-values.yaml +++ b/keycloak-db-values.yaml @@ -13,8 +13,8 @@ auth: existingSecret: ${secret_name} secretKeys: adminPasswordKey: ${database_password_key} - userPasswordKey: ${postgres_password_key} replicationPasswordKey: ${replication_password_key} + userPasswordKey: ${postgres_password_key} replication: applicationName: ${database_name} diff --git a/main.tf b/main.tf index 3539c8d..41ae281 100644 --- a/main.tf +++ b/main.tf @@ -110,7 +110,7 @@ resource "helm_release" "keycloak-db" { values = [ templatefile("${path.module}/keycloak-db-values.yaml", { database_name = "keycloak" - database_password_key = "postgres-password" + database_password_key = "password" database_user = "keycloak" image_registry = module.images.images[local.postgresql_key].dest_registry image_repository = module.images.images[local.postgresql_key].dest_repository From f47fb297dac25bc1571c2c4a8f7c46a245836652 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 15:19:53 -0400 Subject: [PATCH 15/49] update chart values --- keycloak-db-values.yaml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/keycloak-db-values.yaml b/keycloak-db-values.yaml index 2ecaab6..59f270e 100644 --- a/keycloak-db-values.yaml +++ b/keycloak-db-values.yaml @@ -2,19 +2,18 @@ global: security: allowInsecureImages: true + postgresql: + auth: + postgresPassword: ${postgres_password_key} + username: ${database_user} + database: ${database_name} + existingSecret: ${secret_name} + secretKeys: + adminPasswordKey: ${postgres_password_key} + replicationPasswordKey: ${replication_password_key} + userPasswordKey: ${database_password_key} architecture: standalone -auth: - database: ${database_name} - username: ${database_user} - # postgresPassword: keycloakadmin - # username: keycloak - # password: keycloak - existingSecret: ${secret_name} - secretKeys: - adminPasswordKey: ${database_password_key} - replicationPasswordKey: ${replication_password_key} - userPasswordKey: ${postgres_password_key} replication: applicationName: ${database_name} From c9e54aa4ff7b280e3ea170798130e7c4fd42e4e3 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 15:28:02 -0400 Subject: [PATCH 16/49] fix syntax in config_job.tf --- config_job.tf | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/config_job.tf b/config_job.tf index 1cba995..e2f7c42 100644 --- a/config_job.tf +++ b/config_job.tf @@ -15,18 +15,6 @@ locals { redirectUrl="https://*" DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" - # Function to retrieve password from secret - function get_db_password { - secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.password}" | base64 --decode) - echo "$secret_value" - } - - function get_postgres_password { - secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode) - echo "$secret_value" - } - } - # Wait for PostgreSQL to be ready function wait_for_postgresql { local max_attempts=30 From 783474d79a63b5392ba978af536533b787975d7c Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 15:34:22 -0400 Subject: [PATCH 17/49] fix metrics permissions --- config_job.tf | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/config_job.tf b/config_job.tf index e2f7c42..3a374e2 100644 --- a/config_job.tf +++ b/config_job.tf @@ -15,6 +15,39 @@ locals { redirectUrl="https://*" DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" + # Function to retrieve PostgreSQL password from secret + function get_postgres_password { + secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode) + echo "$secret_value" + } + + # Function to execute SQL commands as postgres user + function execute_sql_as_postgres { + kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ + /opt/bitnami/postgresql/bin/psql \ + -U postgres \ + -c "$1" + } + + # Fix metrics exporter permissions + function fix_metrics_permissions { + echo "Setting up permissions for metrics exporter..." + + # Grant permissions needed by the exporter + SQL_GRANT="CREATE OR REPLACE FUNCTION grant_metrics_permissions() RETURNS void AS \$\$ + BEGIN + GRANT pg_monitor TO postgres; + GRANT SELECT ON pg_stat_database TO postgres; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO postgres; + GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO postgres; + END; + \$\$ LANGUAGE plpgsql; + SELECT grant_metrics_permissions();" + + execute_sql_as_postgres "$SQL_GRANT" + echo "Metrics permissions granted" + } + # Wait for PostgreSQL to be ready function wait_for_postgresql { local max_attempts=30 From 7839b1b6b876458bcde6df0868ac9166076cc556 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 15:43:59 -0400 Subject: [PATCH 18/49] try admin port --- config_job.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config_job.tf b/config_job.tf index 3a374e2..503293f 100644 --- a/config_job.tf +++ b/config_job.tf @@ -77,12 +77,12 @@ locals { while [ $attempt -le $max_attempts ]; do echo "Healthcheck attempt $attempt of $max_attempts" - if curl -s -o /dev/null -w %%{http_code} http://keycloak-keycloakx-http:${local.public_port_number}/auth/health/ready | grep -q "200"; then + if curl -s -o /dev/null -w %%{http_code} http://keycloak-keycloakx-http:${local.admin_internal_port_number}/auth/health/ready | grep -q "200"; then echo "Keycloak is ready" return 0 else echo "Keycloak not ready, waiting..." - sleep 10 + sleep 1 attempt=$((attempt+1)) fi done From 9cc241be0b75b626a5cd64c9e98e0d62faa111b8 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 15:45:08 -0400 Subject: [PATCH 19/49] exit codes --- config_job.tf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config_job.tf b/config_job.tf index 503293f..a3cdd05 100644 --- a/config_job.tf +++ b/config_job.tf @@ -88,7 +88,7 @@ locals { done echo "Keycloak failed to become ready after $max_attempts attempts" - return 1 + exit 1 } # Login function with retry logic @@ -109,7 +109,7 @@ locals { done echo "Failed to login after $max_attempts attempts" - return 1 + exit 1 } # Start configuration process @@ -163,6 +163,7 @@ locals { restart_server echo "Keycloak configuration completed successfully" + exit 0 CONFIG } From 03a91432e5fb0d7da15965784a25db65024e8178 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 15:53:33 -0400 Subject: [PATCH 20/49] healcheck --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index a3cdd05..2f762f4 100644 --- a/config_job.tf +++ b/config_job.tf @@ -77,7 +77,7 @@ locals { while [ $attempt -le $max_attempts ]; do echo "Healthcheck attempt $attempt of $max_attempts" - if curl -s -o /dev/null -w %%{http_code} http://keycloak-keycloakx-http:${local.admin_internal_port_number}/auth/health/ready | grep -q "200"; then + if curl -s -o /dev/null -w %%{http_code} http://keycloak-keycloakx-0:${local.public_port_number}/auth/health/ready | grep -q "200"; then echo "Keycloak is ready" return 0 else From 956b7a5072efe9f833df82a57e40850ffe94c684 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 16:00:07 -0400 Subject: [PATCH 21/49] remove keycloak healthcheck which requires ingress to exist --- config_job.tf | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/config_job.tf b/config_job.tf index 2f762f4..3c00f2e 100644 --- a/config_job.tf +++ b/config_job.tf @@ -71,26 +71,6 @@ locals { return 1 } - function wait_for_keycloak { - local max_attempts=30 - local attempt=1 - - while [ $attempt -le $max_attempts ]; do - echo "Healthcheck attempt $attempt of $max_attempts" - if curl -s -o /dev/null -w %%{http_code} http://keycloak-keycloakx-0:${local.public_port_number}/auth/health/ready | grep -q "200"; then - echo "Keycloak is ready" - return 0 - else - echo "Keycloak not ready, waiting..." - sleep 1 - attempt=$((attempt+1)) - fi - done - - echo "Keycloak failed to become ready after $max_attempts attempts" - exit 1 - } - # Login function with retry logic function login_with_retry { local max_attempts=5 @@ -121,13 +101,6 @@ locals { exit 1 } - # Wait for Keycloak to be ready with new configuration - echo "Waiting for Keycloak to be available..." - wait_for_keycloak || { - echo "Failed to connect to Keycloak" - exit 1 - } - # Step 1: Login and create realm echo "Step 1: Creating realm" login_with_retry && create_realm ${var.default_realm} From cb32ed59c630731ab9a6f4a020665d2c5df2311b Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 16:13:48 -0400 Subject: [PATCH 22/49] remove login_with_retry --- config_job.tf | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/config_job.tf b/config_job.tf index 3c00f2e..6a2f136 100644 --- a/config_job.tf +++ b/config_job.tf @@ -21,14 +21,6 @@ locals { echo "$secret_value" } - # Function to execute SQL commands as postgres user - function execute_sql_as_postgres { - kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /opt/bitnami/postgresql/bin/psql \ - -U postgres \ - -c "$1" - } - # Fix metrics exporter permissions function fix_metrics_permissions { echo "Setting up permissions for metrics exporter..." @@ -71,27 +63,6 @@ locals { return 1 } - # Login function with retry logic - function login_with_retry { - local max_attempts=5 - local attempt=1 - - while [ $attempt -le $max_attempts ]; do - echo "Login attempt $attempt of $max_attempts" - if login; then - echo "Login successful" - return 0 - else - echo "Login failed, waiting before retry" - sleep 10 - attempt=$((attempt+1)) - fi - done - - echo "Failed to login after $max_attempts attempts" - exit 1 - } - # Start configuration process echo "Starting configuration process" @@ -103,7 +74,7 @@ locals { # Step 1: Login and create realm echo "Step 1: Creating realm" - login_with_retry && create_realm ${var.default_realm} + create_realm ${var.default_realm} # Step 2: Create admin user echo "Step 2: Creating admin user" From f70a7105e6084a6ccb6f78c3cda217f0d72d9fa2 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 16:37:03 -0400 Subject: [PATCH 23/49] add functions change order --- config_job.tf | 465 ++++++++++++++++++++++++++++++++++++++++---------- main.tf | 6 +- 2 files changed, 376 insertions(+), 95 deletions(-) diff --git a/config_job.tf b/config_job.tf index 6a2f136..e2b6cee 100644 --- a/config_job.tf +++ b/config_job.tf @@ -13,101 +13,382 @@ locals { # Setup variables rootUrl="" redirectUrl="https://*" + KC_URL="http://keycloak-keycloakx-http:${local.public_port_number}" DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" + USER_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-user-creds"].metadata[0].name}" - # Function to retrieve PostgreSQL password from secret - function get_postgres_password { - secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode) - echo "$secret_value" - } - - # Fix metrics exporter permissions - function fix_metrics_permissions { - echo "Setting up permissions for metrics exporter..." - - # Grant permissions needed by the exporter - SQL_GRANT="CREATE OR REPLACE FUNCTION grant_metrics_permissions() RETURNS void AS \$\$ - BEGIN - GRANT pg_monitor TO postgres; - GRANT SELECT ON pg_stat_database TO postgres; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO postgres; - GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO postgres; - END; - \$\$ LANGUAGE plpgsql; - SELECT grant_metrics_permissions();" - - execute_sql_as_postgres "$SQL_GRANT" - echo "Metrics permissions granted" - } - - # Wait for PostgreSQL to be ready - function wait_for_postgresql { - local max_attempts=30 - local attempt=1 - - echo "Waiting for PostgreSQL to be ready..." - - while [ $attempt -le $max_attempts ]; do - echo "PostgreSQL check attempt $attempt of $max_attempts" - if kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- pg_isready -h localhost -U postgres > /dev/null 2>&1; then - echo "PostgreSQL is ready" + # Function to retrieve PostgreSQL password from secret + function get_postgres_password { + secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode) + echo "$secret_value" + } + + # Function to retrieve Keycloak user credentials + function kc_user { + secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $USER_SECRET -o jsonpath="{.data.username}" | base64 --decode) + echo "$secret_value" + } + + function kc_passwd { + secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $USER_SECRET -o jsonpath="{.data.password}" | base64 --decode) + echo "$secret_value" + } + + # Function to execute SQL commands as postgres user + function execute_sql_as_postgres { + echo "Executing SQL: $1" + kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ + /opt/bitnami/postgresql/bin/psql \ + -U postgres \ + -c "$1" + } + + # Fix metrics exporter permissions + function fix_metrics_permissions { + echo "Setting up permissions for metrics exporter..." + + # Grant permissions needed by the exporter + SQL_GRANT="CREATE OR REPLACE FUNCTION grant_metrics_permissions() RETURNS void AS \$\$ + BEGIN + GRANT pg_monitor TO postgres; + GRANT SELECT ON pg_stat_database TO postgres; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO postgres; + GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO postgres; + END; + \$\$ LANGUAGE plpgsql; + SELECT grant_metrics_permissions();" + + execute_sql_as_postgres "$SQL_GRANT" + echo "Metrics permissions granted" + } + + # Wait for PostgreSQL to be ready + function wait_for_postgresql { + local max_attempts=30 + local attempt=1 + + echo "Waiting for PostgreSQL to be ready..." + + while [ $attempt -le $max_attempts ]; do + echo "PostgreSQL check attempt $attempt of $max_attempts" + if kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- pg_isready -h localhost -U postgres > /dev/null 2>&1; then + echo "PostgreSQL is ready" + return 0 + else + echo "PostgreSQL not ready, waiting..." + sleep 10 + attempt=$((attempt+1)) + fi + done + + echo "PostgreSQL failed to become ready after $max_attempts attempts" + return 1 + } + + # Wait for Keycloak to be ready + function wait_for_keycloak { + local max_attempts=30 + local attempt=1 + + echo "Waiting for Keycloak to be ready..." + + while [ $attempt -le $max_attempts ]; do + echo "Keycloak check attempt $attempt of $max_attempts" + if curl -s -o /dev/null -w %%{http_code} $KC_URL/auth/health/ready | grep -q "200"; then + echo "Keycloak is ready" + return 0 + else + echo "Keycloak not ready, waiting..." + sleep 10 + attempt=$((attempt+1)) + fi + done + + echo "Keycloak failed to become ready after $max_attempts attempts" + return 1 + } + + # Function to login to Keycloak admin console + function login { + local token=$(curl -s -X POST "$KC_URL/auth/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=$(kc_user)" \ + -d "password=$(kc_passwd)" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" | jq -r '.access_token') + + if [ "$token" == "null" ] || [ -z "$token" ]; then + echo "Failed to get access token" + return 1 + fi + + # Store token for subsequent calls + KC_TOKEN=$token + return 0 + } + + # Create a new realm + function create_realm { + local realm_name=$1 + echo "Creating realm: $realm_name" + + # Check if realm exists + local status=$(curl -s -o /dev/null -w %%{http_code} \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$realm_name") + + if [ "$status" == "200" ]; then + echo "Realm $realm_name already exists" return 0 - else - echo "PostgreSQL not ready, waiting..." - sleep 10 - attempt=$((attempt+1)) fi - done - - echo "PostgreSQL failed to become ready after $max_attempts attempts" - return 1 - } - - # Start configuration process - echo "Starting configuration process" - - # Wait for PostgreSQL to be ready first - wait_for_postgresql || { - echo "Failed waiting for PostgreSQL" - exit 1 - } - - # Step 1: Login and create realm - echo "Step 1: Creating realm" - create_realm ${var.default_realm} - - # Step 2: Create admin user - echo "Step 2: Creating admin user" - create_admin_user ${var.default_realm} "$(kc_user)" "$(kc_passwd)" "${var.admin_email}" - - # Step 3: Create roles - echo "Step 3: Creating roles" - create_realm_role ${var.default_realm} admin "Admin role for ${var.default_realm}" && \ - create_realm_role ${var.default_realm} user "User role for ${var.default_realm}" - - # Step 4: Assign roles - echo "Step 4: Assigning roles" - add_realm_role_to_user ${var.default_realm} user user - - # Step 5: Create client - echo "Step 5: Creating client" - create_client "${var.default_realm}" "${local.user_client_id}" \ - "${random_uuid.user_secret.result}" "$rootUrl" "" \ - "[ \"$redirectUrl\" ]" "" 1 - - # Step 6: Add mapper - echo "Step 6: Adding mapper" - add_mapper_to_client "${var.default_realm}" \ - "$(get_client_id "${var.default_realm}" "${local.user_client_id}")" \ - "realm roles" "openid-connect" "oidc-usermodel-realm-role-mapper" \ - "{ \"multivalued\": \"true\", \"userinfo.token.claim\": \"true\", \"user.attribute\": \"foo\", \"id.token.claim\": \"true\", \"access.token.claim\": \"true\", \"claim.name\": \"realm_access.roles\", \"jsonType.label\": \"String\" }" - - # Step 7: Restart server - echo "Step 7: Restarting server" - restart_server - - echo "Keycloak configuration completed successfully" - exit 0 -CONFIG + # Create realm + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"realm\":\"$realm_name\",\"enabled\":true}" \ + "$KC_URL/auth/admin/realms" + + echo "Realm $realm_name created" + } + + # Create admin user in realm + function create_admin_user { + local realm=$1 + local username=$2 + local password=$3 + local email=$4 + + echo "Creating admin user: $username in realm $realm" + + # Check if user exists + local user_id=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$realm/users?username=$username" | jq -r '.[0].id') + + if [ "$user_id" != "null" ] && [ -n "$user_id" ]; then + echo "User $username already exists with ID $user_id" + return 0 + fi + + # Create user + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$username\",\"enabled\":true,\"emailVerified\":true,\"email\":\"$email\"}" \ + "$KC_URL/auth/admin/realms/$realm/users" + + # Get user ID + user_id=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$realm/users?username=$username" | jq -r '.[0].id') + + # Set password + curl -s -X PUT \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"type\":\"password\",\"value\":\"$password\",\"temporary\":false}" \ + "$KC_URL/auth/admin/realms/$realm/users/$user_id/reset-password" + + echo "User $username created and password set" + } + + # Create a realm role + function create_realm_role { + local realm=$1 + local role_name=$2 + local description=$3 + + echo "Creating realm role: $role_name in realm $realm" + + # Check if role exists + local status=$(curl -s -o /dev/null -w %%{http_code} \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$realm/roles/$role_name") + + if [ "$status" == "200" ]; then + echo "Role $role_name already exists" + return 0 + fi + + # Create role + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"$role_name\",\"description\":\"$description\"}" \ + "$KC_URL/auth/admin/realms/$realm/roles" + + echo "Role $role_name created" + } + + # Add realm role to user + function add_realm_role_to_user { + local realm=$1 + local role_name=$2 + local username=$3 + + echo "Adding role $role_name to user $username in realm $realm" + + # Get user ID + local user_id=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$realm/users?username=$username" | jq -r '.[0].id') + + # Get role + local role=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$realm/roles/$role_name") + + # Add role to user + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "[$role]" \ + "$KC_URL/auth/admin/realms/$realm/users/$user_id/role-mappings/realm" + + echo "Role $role_name added to user $username" + } + + # Create a client + function create_client { + local realm=$1 + local client_id=$2 + local client_secret=$3 + local root_url=$4 + local base_url=$5 + local redirect_uris=$6 + local web_origins=$7 + local direct_access_grants=$8 + + echo "Creating client: $client_id in realm $realm" + + # Check if client exists + local clients=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id") + + if [ "$(echo $clients | jq -r '. | length')" -gt 0 ]; then + echo "Client $client_id already exists" + return 0 + fi + + # Create client + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"clientId\":\"$client_id\",\"secret\":\"$client_secret\",\"rootUrl\":\"$root_url\",\"baseUrl\":\"$base_url\",\"redirectUris\":$redirect_uris,\"webOrigins\":\"$web_origins\",\"directAccessGrantsEnabled\":$direct_access_grants,\"protocol\":\"openid-connect\",\"publicClient\":false}" \ + "$KC_URL/auth/admin/realms/$realm/clients" + + echo "Client $client_id created" + } + + # Get client ID by client_id + function get_client_id { + local realm=$1 + local client_id=$2 + + local id=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id" | jq -r '.[0].id') + + echo $id + } + + # Add protocol mapper to client + function add_mapper_to_client { + local realm=$1 + local client_id=$2 + local name=$3 + local protocol=$4 + local protocolMapper=$5 + local config=$6 + + echo "Adding mapper $name to client $client_id" + + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"$name\",\"protocol\":\"$protocol\",\"protocolMapper\":\"$protocolMapper\",\"config\":$config}" \ + "$KC_URL/auth/admin/realms/$realm/clients/$client_id/protocol-mappers/models" + + echo "Mapper $name added to client $client_id" + } + + # Restart Keycloak server + function restart_server { + echo "Restarting Keycloak server..." + kubectl rollout restart deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} + + # Wait for restart to complete + kubectl rollout status deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} + + # Scale up to 3 replicas + echo "Scaling Keycloak to 3 replicas..." + kubectl scale --replicas=3 deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} + + echo "Keycloak restarted and scaled" + } + + # Start configuration process + echo "Starting configuration process" + + # Wait for PostgreSQL to be ready first + wait_for_postgresql || { + echo "Failed waiting for PostgreSQL" + exit 1 + } + + # Fix PostgreSQL permissions for metrics + fix_metrics_permissions + + # Wait for Keycloak to be ready + wait_for_keycloak || { + echo "Failed waiting for Keycloak" + exit 1 + } + + # Login to Keycloak + echo "Logging in to Keycloak" + login || { + echo "Failed to login to Keycloak" + exit 1 + } + + # Step 1: Login and create realm + echo "Step 1: Creating realm" + create_realm ${var.default_realm} + + # Step 2: Create admin user + echo "Step 2: Creating admin user" + create_admin_user ${var.default_realm} "$(kc_user)" "$(kc_passwd)" "${var.admin_email}" + + # Step 3: Create roles + echo "Step 3: Creating roles" + create_realm_role ${var.default_realm} admin "Admin role for ${var.default_realm}" && \ + create_realm_role ${var.default_realm} user "User role for ${var.default_realm}" + + # Step 4: Assign roles + echo "Step 4: Assigning roles" + add_realm_role_to_user ${var.default_realm} user user + + # Step 5: Create client + echo "Step 5: Creating client" + create_client "${var.default_realm}" "${local.user_client_id}" \ + "${random_uuid.user_secret.result}" "$rootUrl" "" \ + "[ \"$redirectUrl\" ]" "" 1 + + # Step 6: Add mapper + echo "Step 6: Adding mapper" + add_mapper_to_client "${var.default_realm}" \ + "$(get_client_id "${var.default_realm}" "${local.user_client_id}")" \ + "realm roles" "openid-connect" "oidc-usermodel-realm-role-mapper" \ + "{ \"multivalued\": \"true\", \"userinfo.token.claim\": \"true\", \"user.attribute\": \"foo\", \"id.token.claim\": \"true\", \"access.token.claim\": \"true\", \"claim.name\": \"realm_access.roles\", \"jsonType.label\": \"String\" }" + + # Step 7: Restart server + echo "Step 7: Restarting server" + restart_server + + echo "Keycloak configuration completed successfully" + exit 0 +CONFIG } diff --git a/main.tf b/main.tf index 41ae281..262f87d 100644 --- a/main.tf +++ b/main.tf @@ -157,7 +157,7 @@ resource "helm_release" "keycloak" { } resource "kubernetes_job" "keycloak_config" { - depends_on = [helm_release.keycloak] + depends_on = [module.ingress_resources] metadata { name = "keycloak-config-job" @@ -201,7 +201,7 @@ resource "kubernetes_job" "keycloak_config" { } module "ingress_resources" { - depends_on = [kubernetes_job.keycloak_config] + depends_on = [helm_release.keycloak] # tflint-ignore: terraform_module_pinned_source source = "git::https://github.e.it.census.gov/SCT-Engineering/tfmod-istio-service-ingress.git" public_hostname = var.service_name @@ -213,7 +213,7 @@ module "ingress_resources" { } resource "null_resource" "scale_keycloak" { - depends_on = [module.ingress_resources] + depends_on = [kubernetes_job.keycloak_config] provisioner "local-exec" { command = <<-EOT From 2faab130bfc0eef6efd23781555436bc2211e070 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 16:54:06 -0400 Subject: [PATCH 24/49] update execute sql func --- config_job.tf | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/config_job.tf b/config_job.tf index e2b6cee..d31e3c1 100644 --- a/config_job.tf +++ b/config_job.tf @@ -38,9 +38,7 @@ locals { function execute_sql_as_postgres { echo "Executing SQL: $1" kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /opt/bitnami/postgresql/bin/psql \ - -U postgres \ - -c "$1" + psql PGPASSWORD=$(get_postgres_password) -U postgres -c "$1" } # Fix metrics exporter permissions @@ -319,14 +317,7 @@ locals { echo "Restarting Keycloak server..." kubectl rollout restart deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} - # Wait for restart to complete - kubectl rollout status deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} - - # Scale up to 3 replicas - echo "Scaling Keycloak to 3 replicas..." - kubectl scale --replicas=3 deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} - - echo "Keycloak restarted and scaled" + echo "Keycloak restarted" } # Start configuration process From 469c72dc9230be5c9ae0bac11e0125c8623cbf24 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 17:02:47 -0400 Subject: [PATCH 25/49] update password usage for psql --- config_job.tf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index d31e3c1..586b3f1 100644 --- a/config_job.tf +++ b/config_job.tf @@ -37,8 +37,10 @@ locals { # Function to execute SQL commands as postgres user function execute_sql_as_postgres { echo "Executing SQL: $1" + local pg_password=$(get_postgres_password) kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - psql PGPASSWORD=$(get_postgres_password) -U postgres -c "$1" + PGPASSWORD='$pg_password' + psql -U postgres -c "$1" } # Fix metrics exporter permissions From 70748d29d5c7430089df38b9d45f3e07dd3fe4b5 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 17:11:02 -0400 Subject: [PATCH 26/49] use export --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index 586b3f1..82b52b0 100644 --- a/config_job.tf +++ b/config_job.tf @@ -39,7 +39,7 @@ locals { echo "Executing SQL: $1" local pg_password=$(get_postgres_password) kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - PGPASSWORD='$pg_password' + export PGPASSWORD='$pg_password'; \ psql -U postgres -c "$1" } From bf176c80b815d304e2aa6376c6df7b99dae931be Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 18:20:11 -0400 Subject: [PATCH 27/49] fix sql --- config_job.tf | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/config_job.tf b/config_job.tf index 82b52b0..35b2448 100644 --- a/config_job.tf +++ b/config_job.tf @@ -39,8 +39,7 @@ locals { echo "Executing SQL: $1" local pg_password=$(get_postgres_password) kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - export PGPASSWORD='$pg_password'; \ - psql -U postgres -c "$1" + PGPASSWORD=$pg_password psql -U postgres -c "$1" } # Fix metrics exporter permissions @@ -50,10 +49,10 @@ locals { # Grant permissions needed by the exporter SQL_GRANT="CREATE OR REPLACE FUNCTION grant_metrics_permissions() RETURNS void AS \$\$ BEGIN - GRANT pg_monitor TO postgres; - GRANT SELECT ON pg_stat_database TO postgres; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO postgres; - GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO postgres; + GRANT pg_monitor TO keycloak; + GRANT SELECT ON pg_stat_database TO keycloak; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO keycloak; + GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO keycloak; END; \$\$ LANGUAGE plpgsql; SELECT grant_metrics_permissions();" From 60695243e9b895932dd922a5ac833bec0c51c042 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 18:27:39 -0400 Subject: [PATCH 28/49] use bash --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index 35b2448..9b33152 100644 --- a/config_job.tf +++ b/config_job.tf @@ -39,7 +39,7 @@ locals { echo "Executing SQL: $1" local pg_password=$(get_postgres_password) kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - PGPASSWORD=$pg_password psql -U postgres -c "$1" + /bin/bash -c "PGPASSWORD=$pg_password psql -U postgres -c $1" } # Fix metrics exporter permissions From 4d3f559f8c6ec8b4e48e7b2d8eff9f33119b4992 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 18:33:54 -0400 Subject: [PATCH 29/49] globbing --- config_job.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config_job.tf b/config_job.tf index 9b33152..0924aac 100644 --- a/config_job.tf +++ b/config_job.tf @@ -37,9 +37,9 @@ locals { # Function to execute SQL commands as postgres user function execute_sql_as_postgres { echo "Executing SQL: $1" - local pg_password=$(get_postgres_password) + local pg_password="$(get_postgres_password)" kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /bin/bash -c "PGPASSWORD=$pg_password psql -U postgres -c $1" + /bin/bash -c export PGPASSWORD="${pg_password}"; psql -U postgres -c "$1" } # Fix metrics exporter permissions From cc548f09d21bf95e1d7580a3e45d93d7534adad6 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 18:42:01 -0400 Subject: [PATCH 30/49] fix globbing and use a HEREDOC --- config_job.tf | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/config_job.tf b/config_job.tf index 0924aac..e95207a 100644 --- a/config_job.tf +++ b/config_job.tf @@ -39,7 +39,7 @@ locals { echo "Executing SQL: $1" local pg_password="$(get_postgres_password)" kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /bin/bash -c export PGPASSWORD="${pg_password}"; psql -U postgres -c "$1" + /bin/bash -c export PGPASSWORD="$pg_password" ; psql -U postgres -c "$1" } # Fix metrics exporter permissions @@ -47,16 +47,17 @@ locals { echo "Setting up permissions for metrics exporter..." # Grant permissions needed by the exporter - SQL_GRANT="CREATE OR REPLACE FUNCTION grant_metrics_permissions() RETURNS void AS \$\$ + SQL_GRANT=<<-GRANT + CREATE OR REPLACE FUNCTION grant_metrics_permissions() RETURNS void AS $$ BEGIN GRANT pg_monitor TO keycloak; GRANT SELECT ON pg_stat_database TO keycloak; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO keycloak; GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO keycloak; END; - \$\$ LANGUAGE plpgsql; - SELECT grant_metrics_permissions();" - + $$ LANGUAGE plpgsql; + SELECT grant_metrics_permissions(); +GRANT execute_sql_as_postgres "$SQL_GRANT" echo "Metrics permissions granted" } From 920dcd39e769e72a9fddc36d900f56a4a35ab39e Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 18:48:18 -0400 Subject: [PATCH 31/49] try not to launch new shell --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index e95207a..ab7210a 100644 --- a/config_job.tf +++ b/config_job.tf @@ -39,7 +39,7 @@ locals { echo "Executing SQL: $1" local pg_password="$(get_postgres_password)" kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /bin/bash -c export PGPASSWORD="$pg_password" ; psql -U postgres -c "$1" + decalre -x PGPASSWORD="$pg_password"; psql -U postgres -c "$1" } # Fix metrics exporter permissions From 23b4c2a551435dc5e2b8fda3412321667fd788b5 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 18:51:24 -0400 Subject: [PATCH 32/49] typo --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index ab7210a..aa3ea7f 100644 --- a/config_job.tf +++ b/config_job.tf @@ -39,7 +39,7 @@ locals { echo "Executing SQL: $1" local pg_password="$(get_postgres_password)" kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - decalre -x PGPASSWORD="$pg_password"; psql -U postgres -c "$1" + declare -x PGPASSWORD="$pg_password"; psql -U postgres -c "$1" } # Fix metrics exporter permissions From 224eb2d9682ffe8e34d90ced1eed8398ecfae59b Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:00:45 -0400 Subject: [PATCH 33/49] syntax --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index aa3ea7f..bebe4cd 100644 --- a/config_job.tf +++ b/config_job.tf @@ -39,7 +39,7 @@ locals { echo "Executing SQL: $1" local pg_password="$(get_postgres_password)" kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - declare -x PGPASSWORD="$pg_password"; psql -U postgres -c "$1" + /bin/bash -c PGPASSWORD="$pg_password" psql -U postgres -c "$1" } # Fix metrics exporter permissions From f49a33e3943fd97fb6b5d9ffa27cc756499fab07 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:03:14 -0400 Subject: [PATCH 34/49] remove parens from password gen --- main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 262f87d..7b62c71 100644 --- a/main.tf +++ b/main.tf @@ -51,7 +51,7 @@ resource "random_password" "passwords" { length = each.value.length special = true - override_special = "!#$%&*()-_=+[]{}<>:?" + override_special = "!#$%&*-_=+[]<>:?" min_lower = 2 min_upper = 2 min_numeric = 2 From 1bc63bdea5348b348097a6e95069286084adfc74 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:05:21 -0400 Subject: [PATCH 35/49] remove quotes --- config_job.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_job.tf b/config_job.tf index bebe4cd..e7f0752 100644 --- a/config_job.tf +++ b/config_job.tf @@ -39,7 +39,7 @@ locals { echo "Executing SQL: $1" local pg_password="$(get_postgres_password)" kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /bin/bash -c PGPASSWORD="$pg_password" psql -U postgres -c "$1" + /bin/bash -c PGPASSWORD=$pg_password psql -U postgres -c "$1" } # Fix metrics exporter permissions From 2e90c72377b93c7d623cb203b593a511815c246e Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:07:30 -0400 Subject: [PATCH 36/49] try ref existing var --- config_job.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config_job.tf b/config_job.tf index e7f0752..7cc6465 100644 --- a/config_job.tf +++ b/config_job.tf @@ -37,9 +37,8 @@ locals { # Function to execute SQL commands as postgres user function execute_sql_as_postgres { echo "Executing SQL: $1" - local pg_password="$(get_postgres_password)" kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /bin/bash -c PGPASSWORD=$pg_password psql -U postgres -c "$1" + /bin/bash -c PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD psql -U postgres -c "$1" } # Fix metrics exporter permissions From bb7ad9af29682ea621c2c2da61562ac44522847e Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:18:26 -0400 Subject: [PATCH 37/49] assume keycloak is available --- config_job.tf | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/config_job.tf b/config_job.tf index 7cc6465..adcedcd 100644 --- a/config_job.tf +++ b/config_job.tf @@ -84,29 +84,6 @@ GRANT return 1 } - # Wait for Keycloak to be ready - function wait_for_keycloak { - local max_attempts=30 - local attempt=1 - - echo "Waiting for Keycloak to be ready..." - - while [ $attempt -le $max_attempts ]; do - echo "Keycloak check attempt $attempt of $max_attempts" - if curl -s -o /dev/null -w %%{http_code} $KC_URL/auth/health/ready | grep -q "200"; then - echo "Keycloak is ready" - return 0 - else - echo "Keycloak not ready, waiting..." - sleep 10 - attempt=$((attempt+1)) - fi - done - - echo "Keycloak failed to become ready after $max_attempts attempts" - return 1 - } - # Function to login to Keycloak admin console function login { local token=$(curl -s -X POST "$KC_URL/auth/realms/master/protocol/openid-connect/token" \ @@ -332,11 +309,6 @@ GRANT # Fix PostgreSQL permissions for metrics fix_metrics_permissions - - # Wait for Keycloak to be ready - wait_for_keycloak || { - echo "Failed waiting for Keycloak" - exit 1 } # Login to Keycloak From daff6adf23a7de4c53ddeba19f3608a186a9d2b2 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:22:45 -0400 Subject: [PATCH 38/49] cleanup --- config_job.tf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config_job.tf b/config_job.tf index adcedcd..6141c69 100644 --- a/config_job.tf +++ b/config_job.tf @@ -84,6 +84,9 @@ GRANT return 1 } + function wait_for_keycloak { + + # Function to login to Keycloak admin console function login { local token=$(curl -s -X POST "$KC_URL/auth/realms/master/protocol/openid-connect/token" \ @@ -308,8 +311,7 @@ GRANT } # Fix PostgreSQL permissions for metrics - fix_metrics_permissions - } + fix_metrics_permissionsgst # Login to Keycloak echo "Logging in to Keycloak" From ca9b417fb871c76d9d4c8defc554b47420a8cba0 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:39:53 -0400 Subject: [PATCH 39/49] fix syntax --- config_job.tf | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/config_job.tf b/config_job.tf index 6141c69..b10f7e2 100644 --- a/config_job.tf +++ b/config_job.tf @@ -84,9 +84,6 @@ GRANT return 1 } - function wait_for_keycloak { - - # Function to login to Keycloak admin console function login { local token=$(curl -s -X POST "$KC_URL/auth/realms/master/protocol/openid-connect/token" \ @@ -311,7 +308,7 @@ GRANT } # Fix PostgreSQL permissions for metrics - fix_metrics_permissionsgst + fix_metrics_permissions # Login to Keycloak echo "Logging in to Keycloak" @@ -355,6 +352,6 @@ GRANT restart_server echo "Keycloak configuration completed successfully" - exit 0 + CONFIG } From 66a3e2f6807556867db762716aa4ffba59769ca5 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:45:04 -0400 Subject: [PATCH 40/49] remove failed idea --- config_job.tf | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/config_job.tf b/config_job.tf index b10f7e2..63b9f36 100644 --- a/config_job.tf +++ b/config_job.tf @@ -84,25 +84,6 @@ GRANT return 1 } - # Function to login to Keycloak admin console - function login { - local token=$(curl -s -X POST "$KC_URL/auth/realms/master/protocol/openid-connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "username=$(kc_user)" \ - -d "password=$(kc_passwd)" \ - -d "grant_type=password" \ - -d "client_id=admin-cli" | jq -r '.access_token') - - if [ "$token" == "null" ] || [ -z "$token" ]; then - echo "Failed to get access token" - return 1 - fi - - # Store token for subsequent calls - KC_TOKEN=$token - return 0 - } - # Create a new realm function create_realm { local realm_name=$1 @@ -310,13 +291,6 @@ GRANT # Fix PostgreSQL permissions for metrics fix_metrics_permissions - # Login to Keycloak - echo "Logging in to Keycloak" - login || { - echo "Failed to login to Keycloak" - exit 1 - } - # Step 1: Login and create realm echo "Step 1: Creating realm" create_realm ${var.default_realm} From e7c74fa9e1f50d74109763ff633b259b7525a64f Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 19:55:54 -0400 Subject: [PATCH 41/49] remove jq pieces --- config_job.tf | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/config_job.tf b/config_job.tf index 63b9f36..d103214 100644 --- a/config_job.tf +++ b/config_job.tf @@ -121,7 +121,7 @@ GRANT # Check if user exists local user_id=$(curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username" | jq -r '.[0].id') + "$KC_URL/auth/admin/realms/$realm/users?username=$username") if [ "$user_id" != "null" ] && [ -n "$user_id" ]; then echo "User $username already exists with ID $user_id" @@ -138,7 +138,7 @@ GRANT # Get user ID user_id=$(curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username" | jq -r '.[0].id') + "$KC_URL/auth/admin/realms/$realm/users?username=$username") # Set password curl -s -X PUT \ @@ -189,7 +189,7 @@ GRANT # Get user ID local user_id=$(curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username" | jq -r '.[0].id') + "$KC_URL/auth/admin/realms/$realm/users?username=$username") # Get role local role=$(curl -s \ @@ -224,11 +224,6 @@ GRANT -H "Authorization: Bearer $KC_TOKEN" \ "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id") - if [ "$(echo $clients | jq -r '. | length')" -gt 0 ]; then - echo "Client $client_id already exists" - return 0 - fi - # Create client curl -s -X POST \ -H "Authorization: Bearer $KC_TOKEN" \ @@ -246,7 +241,7 @@ GRANT local id=$(curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id" | jq -r '.[0].id') + "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id") echo $id } From 883eec2c3f58432826b28e84b4edc3767b7c2d3c Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 20:22:24 -0400 Subject: [PATCH 42/49] remove jq --- config_job.tf | 51 ++++++++++++++------------------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/config_job.tf b/config_job.tf index d103214..03396ef 100644 --- a/config_job.tf +++ b/config_job.tf @@ -94,11 +94,6 @@ GRANT -H "Authorization: Bearer $KC_TOKEN" \ "$KC_URL/auth/admin/realms/$realm_name") - if [ "$status" == "200" ]; then - echo "Realm $realm_name already exists" - return 0 - fi - # Create realm curl -s -X POST \ -H "Authorization: Bearer $KC_TOKEN" \ @@ -119,14 +114,9 @@ GRANT echo "Creating admin user: $username in realm $realm" # Check if user exists - local user_id=$(curl -s \ + curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username") - - if [ "$user_id" != "null" ] && [ -n "$user_id" ]; then - echo "User $username already exists with ID $user_id" - return 0 - fi + "$KC_URL/auth/admin/realms/$realm/users?username=$username" # Create user curl -s -X POST \ @@ -136,9 +126,9 @@ GRANT "$KC_URL/auth/admin/realms/$realm/users" # Get user ID - user_id=$(curl -s \ + curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username") + "$KC_URL/auth/admin/realms/$realm/users?username=$username" # Set password curl -s -X PUT \ @@ -159,14 +149,9 @@ GRANT echo "Creating realm role: $role_name in realm $realm" # Check if role exists - local status=$(curl -s -o /dev/null -w %%{http_code} \ + curl -s -o /dev/null -w %%{http_code} \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/roles/$role_name") - - if [ "$status" == "200" ]; then - echo "Role $role_name already exists" - return 0 - fi + "$KC_URL/auth/admin/realms/$realm/roles/$role_name" # Create role curl -s -X POST \ @@ -187,14 +172,14 @@ GRANT echo "Adding role $role_name to user $username in realm $realm" # Get user ID - local user_id=$(curl -s \ + curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username") + "$KC_URL/auth/admin/realms/$realm/users?username=$username" # Get role - local role=$(curl -s \ + curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/roles/$role_name") + "$KC_URL/auth/admin/realms/$realm/roles/$role_name" # Add role to user curl -s -X POST \ @@ -220,9 +205,9 @@ GRANT echo "Creating client: $client_id in realm $realm" # Check if client exists - local clients=$(curl -s \ + curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id") + "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id" # Create client curl -s -X POST \ @@ -239,9 +224,9 @@ GRANT local realm=$1 local client_id=$2 - local id=$(curl -s \ + curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id") + "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id" echo $id } @@ -266,14 +251,6 @@ GRANT echo "Mapper $name added to client $client_id" } - # Restart Keycloak server - function restart_server { - echo "Restarting Keycloak server..." - kubectl rollout restart deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} - - echo "Keycloak restarted" - } - # Start configuration process echo "Starting configuration process" From 5d8954d375c351ca25e4f531c8d6f910b99fbea6 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 20:30:12 -0400 Subject: [PATCH 43/49] simplify more --- config_job.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config_job.tf b/config_job.tf index 03396ef..05b8474 100644 --- a/config_job.tf +++ b/config_job.tf @@ -90,9 +90,9 @@ GRANT echo "Creating realm: $realm_name" # Check if realm exists - local status=$(curl -s -o /dev/null -w %%{http_code} \ + curl -s -o /dev/null -w %%{http_code} \ -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm_name") + "$KC_URL/auth/admin/realms/$realm_name" # Create realm curl -s -X POST \ From 092527f65f09e375cd6ad8661694104fcc7e2637 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 20:48:40 -0400 Subject: [PATCH 44/49] more cleanup --- config_job.tf | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/config_job.tf b/config_job.tf index 05b8474..327088a 100644 --- a/config_job.tf +++ b/config_job.tf @@ -89,11 +89,6 @@ GRANT local realm_name=$1 echo "Creating realm: $realm_name" - # Check if realm exists - curl -s -o /dev/null -w %%{http_code} \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm_name" - # Create realm curl -s -X POST \ -H "Authorization: Bearer $KC_TOKEN" \ @@ -113,11 +108,6 @@ GRANT echo "Creating admin user: $username in realm $realm" - # Check if user exists - curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username" - # Create user curl -s -X POST \ -H "Authorization: Bearer $KC_TOKEN" \ @@ -227,8 +217,6 @@ GRANT curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id" - - echo $id } # Add protocol mapper to client From 08b1073f7f5b4b18d04b785ce92d3c316811200c Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 20:55:35 -0400 Subject: [PATCH 45/49] return codes --- config_job.tf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config_job.tf b/config_job.tf index 327088a..e4f1f8f 100644 --- a/config_job.tf +++ b/config_job.tf @@ -59,6 +59,7 @@ locals { GRANT execute_sql_as_postgres "$SQL_GRANT" echo "Metrics permissions granted" + return 0 } # Wait for PostgreSQL to be ready @@ -97,6 +98,7 @@ GRANT "$KC_URL/auth/admin/realms" echo "Realm $realm_name created" + return 0 } # Create admin user in realm @@ -128,6 +130,7 @@ GRANT "$KC_URL/auth/admin/realms/$realm/users/$user_id/reset-password" echo "User $username created and password set" + return 0 } # Create a realm role @@ -151,6 +154,7 @@ GRANT "$KC_URL/auth/admin/realms/$realm/roles" echo "Role $role_name created" + return 0 } # Add realm role to user @@ -179,6 +183,7 @@ GRANT "$KC_URL/auth/admin/realms/$realm/users/$user_id/role-mappings/realm" echo "Role $role_name added to user $username" + return 0 } # Create a client @@ -207,6 +212,7 @@ GRANT "$KC_URL/auth/admin/realms/$realm/clients" echo "Client $client_id created" + return 0 } # Get client ID by client_id @@ -217,6 +223,7 @@ GRANT curl -s \ -H "Authorization: Bearer $KC_TOKEN" \ "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id" + return 0 } # Add protocol mapper to client @@ -237,6 +244,7 @@ GRANT "$KC_URL/auth/admin/realms/$realm/clients/$client_id/protocol-mappers/models" echo "Mapper $name added to client $client_id" + return 0 } # Start configuration process @@ -286,6 +294,7 @@ GRANT restart_server echo "Keycloak configuration completed successfully" + return 0 CONFIG } From 83184cb1b0d0a8224f66d198062991c353107e04 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 21:07:01 -0400 Subject: [PATCH 46/49] add restart_server func again --- config_job.tf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config_job.tf b/config_job.tf index e4f1f8f..e2dadd8 100644 --- a/config_job.tf +++ b/config_job.tf @@ -247,6 +247,13 @@ GRANT return 0 } + # Restart Keycloak server + function restart_server { + echo "Restarting Keycloak server..." + kubectl rollout restart deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} + echo "Keycloak restarted and scaled" + } + # Start configuration process echo "Starting configuration process" From c301b1e8249288408250c0ce7ab446c4adb0eb06 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 21:12:20 -0400 Subject: [PATCH 47/49] remove restart --- config_job.tf | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/config_job.tf b/config_job.tf index e2dadd8..b173e0d 100644 --- a/config_job.tf +++ b/config_job.tf @@ -247,13 +247,6 @@ GRANT return 0 } - # Restart Keycloak server - function restart_server { - echo "Restarting Keycloak server..." - kubectl rollout restart deployment/keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} - echo "Keycloak restarted and scaled" - } - # Start configuration process echo "Starting configuration process" @@ -296,10 +289,6 @@ GRANT "realm roles" "openid-connect" "oidc-usermodel-realm-role-mapper" \ "{ \"multivalued\": \"true\", \"userinfo.token.claim\": \"true\", \"user.attribute\": \"foo\", \"id.token.claim\": \"true\", \"access.token.claim\": \"true\", \"claim.name\": \"realm_access.roles\", \"jsonType.label\": \"String\" }" - # Step 7: Restart server - echo "Step 7: Restarting server" - restart_server - echo "Keycloak configuration completed successfully" return 0 From eaa00b7ce0e72b366625da0cb0ca18fa59fa2684 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 22:02:53 -0400 Subject: [PATCH 48/49] refactor script --- README.md | 1 - config_job.tf | 638 ++++++++++++++++++++++++++++---------------------- main.tf | 18 +- 3 files changed, 366 insertions(+), 291 deletions(-) diff --git a/README.md b/README.md index c38cd7d..4d793c8 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,6 @@ Common issues and solutions: | [kubernetes_namespace.keycloak](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | | [kubernetes_secret.keycloak_secrets](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | | [null_resource.git_version](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | -| [null_resource.scale_keycloak](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | | [random_password.passwords](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | | [random_uuid.user_secret](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/uuid) | resource | | [aws_ecr_authorization_token.ecr_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecr_authorization_token) | data source | diff --git a/config_job.tf b/config_job.tf index b173e0d..c8eff5e 100644 --- a/config_job.tf +++ b/config_job.tf @@ -1,296 +1,372 @@ locals { config_job_script = <<-CONFIG - #!/bin/bash - set -e - - # Error handling - function handle_error { - echo "Error occurred in configuration at line $1" - exit 1 - } - trap 'handle_error $LINENO' ERR - - # Setup variables - rootUrl="" - redirectUrl="https://*" - KC_URL="http://keycloak-keycloakx-http:${local.public_port_number}" - DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" - USER_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-user-creds"].metadata[0].name}" - - # Function to retrieve PostgreSQL password from secret - function get_postgres_password { - secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode) - echo "$secret_value" - } - - # Function to retrieve Keycloak user credentials - function kc_user { - secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $USER_SECRET -o jsonpath="{.data.username}" | base64 --decode) - echo "$secret_value" - } - - function kc_passwd { - secret_value=$(kubectl get secret -n ${kubernetes_namespace.keycloak.metadata[0].name} $USER_SECRET -o jsonpath="{.data.password}" | base64 --decode) - echo "$secret_value" - } - - # Function to execute SQL commands as postgres user - function execute_sql_as_postgres { - echo "Executing SQL: $1" - kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- \ - /bin/bash -c PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD psql -U postgres -c "$1" - } - - # Fix metrics exporter permissions - function fix_metrics_permissions { - echo "Setting up permissions for metrics exporter..." - - # Grant permissions needed by the exporter - SQL_GRANT=<<-GRANT - CREATE OR REPLACE FUNCTION grant_metrics_permissions() RETURNS void AS $$ - BEGIN - GRANT pg_monitor TO keycloak; - GRANT SELECT ON pg_stat_database TO keycloak; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO keycloak; - GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO keycloak; - END; - $$ LANGUAGE plpgsql; - SELECT grant_metrics_permissions(); -GRANT - execute_sql_as_postgres "$SQL_GRANT" - echo "Metrics permissions granted" - return 0 - } - - # Wait for PostgreSQL to be ready - function wait_for_postgresql { - local max_attempts=30 - local attempt=1 - - echo "Waiting for PostgreSQL to be ready..." - - while [ $attempt -le $max_attempts ]; do - echo "PostgreSQL check attempt $attempt of $max_attempts" - if kubectl exec -n ${kubernetes_namespace.keycloak.metadata[0].name} -t keycloak-db-postgresql-0 -c postgresql -- pg_isready -h localhost -U postgres > /dev/null 2>&1; then - echo "PostgreSQL is ready" - return 0 - else - echo "PostgreSQL not ready, waiting..." - sleep 10 - attempt=$((attempt+1)) - fi - done - - echo "PostgreSQL failed to become ready after $max_attempts attempts" - return 1 - } - - # Create a new realm - function create_realm { - local realm_name=$1 - echo "Creating realm: $realm_name" - - # Create realm - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"realm\":\"$realm_name\",\"enabled\":true}" \ - "$KC_URL/auth/admin/realms" - - echo "Realm $realm_name created" - return 0 - } - - # Create admin user in realm - function create_admin_user { - local realm=$1 - local username=$2 - local password=$3 - local email=$4 - - echo "Creating admin user: $username in realm $realm" - - # Create user - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"username\":\"$username\",\"enabled\":true,\"emailVerified\":true,\"email\":\"$email\"}" \ - "$KC_URL/auth/admin/realms/$realm/users" - - # Get user ID - curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username" - - # Set password - curl -s -X PUT \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"type\":\"password\",\"value\":\"$password\",\"temporary\":false}" \ - "$KC_URL/auth/admin/realms/$realm/users/$user_id/reset-password" - - echo "User $username created and password set" - return 0 - } +#!/bin/bash +set -e - # Create a realm role - function create_realm_role { - local realm=$1 - local role_name=$2 - local description=$3 +# Error handling +function handle_error { + echo "Error occurred in configuration at line $1" + exit 1 +} +trap 'handle_error $LINENO' ERR + +# Setup variables +ROOT_URL="" +REDIRECT_URL="https://*" +KC_URL="http://keycloak-keycloakx-http:${local.public_port_number}" +DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" +USER_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-user-creds"].metadata[0].name}" +NAMESPACE="${kubernetes_namespace.keycloak.metadata[0].name}" +REALM="${var.default_realm}" +KC_TOKEN="" + +# Helper to get PostgreSQL password +function get_postgres_password { + kubectl get secret -n $NAMESPACE $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode +} - echo "Creating realm role: $role_name in realm $realm" +# Helper to get Keycloak credentials +function kc_user { + kubectl get secret -n $NAMESPACE $USER_SECRET -o jsonpath="{.data.username}" | base64 --decode +} - # Check if role exists - curl -s -o /dev/null -w %%{http_code} \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/roles/$role_name" +function kc_passwd { + kubectl get secret -n $NAMESPACE $USER_SECRET -o jsonpath="{.data.password}" | base64 --decode +} - # Create role - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"name\":\"$role_name\",\"description\":\"$description\"}" \ - "$KC_URL/auth/admin/realms/$realm/roles" +# Execute SQL with proper password handling +function execute_sql_as_postgres { + local pg_password=$(get_postgres_password) + echo "Executing SQL command..." + kubectl exec -n $NAMESPACE -t keycloak-db-postgresql-0 -c postgresql -- \ + bash -c "PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD psql -U postgres -c \"$1\"" +} - echo "Role $role_name created" - return 0 - } - - # Add realm role to user - function add_realm_role_to_user { - local realm=$1 - local role_name=$2 - local username=$3 - - echo "Adding role $role_name to user $username in realm $realm" - - # Get user ID - curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/users?username=$username" - - # Get role - curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/roles/$role_name" - - # Add role to user - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "[$role]" \ - "$KC_URL/auth/admin/realms/$realm/users/$user_id/role-mappings/realm" - - echo "Role $role_name added to user $username" - return 0 - } - - # Create a client - function create_client { - local realm=$1 - local client_id=$2 - local client_secret=$3 - local root_url=$4 - local base_url=$5 - local redirect_uris=$6 - local web_origins=$7 - local direct_access_grants=$8 - - echo "Creating client: $client_id in realm $realm" - - # Check if client exists - curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id" - - # Create client - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"clientId\":\"$client_id\",\"secret\":\"$client_secret\",\"rootUrl\":\"$root_url\",\"baseUrl\":\"$base_url\",\"redirectUris\":$redirect_uris,\"webOrigins\":\"$web_origins\",\"directAccessGrantsEnabled\":$direct_access_grants,\"protocol\":\"openid-connect\",\"publicClient\":false}" \ - "$KC_URL/auth/admin/realms/$realm/clients" - - echo "Client $client_id created" - return 0 - } +# Fix metrics permissions +function fix_metrics_permissions { + echo "Setting up permissions for metrics exporter..." + + execute_sql_as_postgres " + DO \$\$ + BEGIN + GRANT pg_monitor TO keycloak; + GRANT SELECT ON pg_stat_database TO keycloak; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO keycloak; + GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO keycloak; + END; + \$\$; + " + + echo "Metrics permissions granted" +} - # Get client ID by client_id - function get_client_id { - local realm=$1 - local client_id=$2 +# Check if PostgreSQL is ready +function wait_for_postgresql { + echo "Waiting for PostgreSQL to be ready..." - curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$realm/clients?clientId=$client_id" + for i in {1..30}; do + echo "PostgreSQL check attempt $i of 30" + if kubectl exec -n $NAMESPACE -t keycloak-db-postgresql-0 -c postgresql -- pg_isready -h localhost -U postgres > /dev/null 2>&1; then + echo "PostgreSQL is ready" return 0 - } - - # Add protocol mapper to client - function add_mapper_to_client { - local realm=$1 - local client_id=$2 - local name=$3 - local protocol=$4 - local protocolMapper=$5 - local config=$6 - - echo "Adding mapper $name to client $client_id" - - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"name\":\"$name\",\"protocol\":\"$protocol\",\"protocolMapper\":\"$protocolMapper\",\"config\":$config}" \ - "$KC_URL/auth/admin/realms/$realm/clients/$client_id/protocol-mappers/models" - - echo "Mapper $name added to client $client_id" + fi + sleep 10 + done + + echo "PostgreSQL failed to become ready" + return 1 +} + +# Check if Keycloak is ready +function wait_for_keycloak { + echo "Waiting for Keycloak to be ready..." + + for i in {1..30}; do + echo "Keycloak check attempt $i of 30" + # if curl -s -o /dev/null -w %%{http_code} $KC_URL/auth/health/ready | grep -q "200"; then + echo "Keycloak is ready" return 0 - } - - # Start configuration process - echo "Starting configuration process" - - # Wait for PostgreSQL to be ready first - wait_for_postgresql || { - echo "Failed waiting for PostgreSQL" - exit 1 - } - - # Fix PostgreSQL permissions for metrics - fix_metrics_permissions - - # Step 1: Login and create realm - echo "Step 1: Creating realm" - create_realm ${var.default_realm} - - # Step 2: Create admin user - echo "Step 2: Creating admin user" - create_admin_user ${var.default_realm} "$(kc_user)" "$(kc_passwd)" "${var.admin_email}" - - # Step 3: Create roles - echo "Step 3: Creating roles" - create_realm_role ${var.default_realm} admin "Admin role for ${var.default_realm}" && \ - create_realm_role ${var.default_realm} user "User role for ${var.default_realm}" - - # Step 4: Assign roles - echo "Step 4: Assigning roles" - add_realm_role_to_user ${var.default_realm} user user - - # Step 5: Create client - echo "Step 5: Creating client" - create_client "${var.default_realm}" "${local.user_client_id}" \ - "${random_uuid.user_secret.result}" "$rootUrl" "" \ - "[ \"$redirectUrl\" ]" "" 1 - - # Step 6: Add mapper - echo "Step 6: Adding mapper" - add_mapper_to_client "${var.default_realm}" \ - "$(get_client_id "${var.default_realm}" "${local.user_client_id}")" \ - "realm roles" "openid-connect" "oidc-usermodel-realm-role-mapper" \ - "{ \"multivalued\": \"true\", \"userinfo.token.claim\": \"true\", \"user.attribute\": \"foo\", \"id.token.claim\": \"true\", \"access.token.claim\": \"true\", \"claim.name\": \"realm_access.roles\", \"jsonType.label\": \"String\" }" - - echo "Keycloak configuration completed successfully" + # fi + sleep 10 + done + + echo "Keycloak failed to become ready" + return 1 +} + +# Login to Keycloak admin console +function login_keycloak { + echo "Logging in to Keycloak..." + + local response=$(curl -s -X POST "$KC_URL/auth/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=$(kc_user)" \ + -d "password=$(kc_passwd)" \ + -d "grant_type=password" \ + -d "client_id=admin-cli") + + KC_TOKEN=$(echo $response | jq -r '.access_token') + + if [[ "$KC_TOKEN" == "null" || -z "$KC_TOKEN" ]]; then + echo "Failed to login to Keycloak: $response" + return 1 + fi + + echo "Login successful" + return 0 +} + +# Create a realm with error handling +function create_realm { + echo "Creating realm: $REALM" + + # Check if realm exists + local status=$(curl -s -o /dev/null -w %%{http_code} \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM") + + if [ "$status" == "200" ]; then + echo "Realm $REALM already exists" return 0 + fi + + # Create realm + local response=$(curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"realm\":\"$REALM\",\"enabled\":true}" \ + "$KC_URL/auth/admin/realms") + + if [[ $? -ne 0 ]]; then + echo "Failed to create realm: $response" + return 1 + fi + + echo "Realm $REALM created successfully" + return 0 +} + +# Create admin user with proper error handling +function create_admin_user { + local username=$(kc_user) + local password=$(kc_passwd) + local email="${var.admin_email}" + + echo "Creating admin user: $username in realm $REALM" + + # Check if user exists + local user_id=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/users?username=$username" | jq -r '.[0].id') + + if [[ "$user_id" != "null" && -n "$user_id" ]]; then + echo "User $username already exists with ID $user_id" + else + # Create user + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$username\",\"enabled\":true,\"emailVerified\":true,\"email\":\"$email\"}" \ + "$KC_URL/auth/admin/realms/$REALM/users" + + # Get user ID again + user_id=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/users?username=$username" | jq -r '.[0].id') + fi + + # Set password + curl -s -X PUT \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"type\":\"password\",\"value\":\"$password\",\"temporary\":false}" \ + "$KC_URL/auth/admin/realms/$REALM/users/$user_id/reset-password" + + echo "User $username configured successfully" +} + +# Create roles with error handling +function create_roles { + # Create admin role + echo "Creating 'admin' role" + local status=$(curl -s -o /dev/null -w %%{http_code} \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/roles/admin") + + if [ "$status" != "200" ]; then + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"admin\",\"description\":\"Admin role for $REALM\"}" \ + "$KC_URL/auth/admin/realms/$REALM/roles" + fi + + # Create user role + echo "Creating 'user' role" + status=$(curl -s -o /dev/null -w %%{http_code} \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/roles/user") + + if [ "$status" != "200" ]; then + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"user\",\"description\":\"User role for $REALM\"}" \ + "$KC_URL/auth/admin/realms/$REALM/roles" + fi + + echo "Roles created successfully" +} + +# Assign role to user +function assign_role_to_user { + local role_name="user" + local username="user" + + echo "Assigning '$role_name' role to $username" + + # Get user ID + local user_id=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/users?username=$username" | jq -r '.[0].id') + + if [[ -z "$user_id" || "$user_id" == "null" ]]; then + echo "User $username not found" + return 1 + fi + + # Get role representation + local role=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/roles/$role_name") + + # Add role to user + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "[$role]" \ + "$KC_URL/auth/admin/realms/$REALM/users/$user_id/role-mappings/realm" + + echo "Role $role_name assigned to $username" +} + +# Create client +function create_client { + local client_id="${local.user_client_id}" + local client_secret="${random_uuid.user_secret.result}" + + echo "Creating client: $client_id in realm $REALM" + + # Check if client exists + local status=$(curl -s -o /dev/null -w %%{http_code} \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/clients?clientId=$client_id") + + if [[ "$status" == "200" ]]; then + echo "Client $client_id may already exist, checking..." + local exists=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/clients?clientId=$client_id" | jq length) + + if [[ "$exists" -gt 0 ]]; then + echo "Client $client_id already exists" + return 0 + fi + fi + + # Create client + local response=$(curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"clientId\":\"$client_id\", + \"secret\":\"$client_secret\", + \"redirectUris\":[\"$REDIRECT_URL\"], + \"directAccessGrantsEnabled\":true, + \"protocol\":\"openid-connect\", + \"publicClient\":false + }" \ + "$KC_URL/auth/admin/realms/$REALM/clients") + + echo "Client $client_id created" +} + +# Add mapper to client +function add_client_mapper { + local client_id="${local.user_client_id}" + + echo "Adding realm roles mapper to client $client_id" + + # Get internal client ID + local id=$(curl -s \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$KC_URL/auth/admin/realms/$REALM/clients?clientId=$client_id" | jq -r '.[0].id') + + if [[ -z "$id" || "$id" == "null" ]]; then + echo "Client $client_id not found" + return 1 + fi + + # Add mapper + curl -s -X POST \ + -H "Authorization: Bearer $KC_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\":\"realm roles\", + \"protocol\":\"openid-connect\", + \"protocolMapper\":\"oidc-usermodel-realm-role-mapper\", + \"config\":{ + \"multivalued\":\"true\", + \"userinfo.token.claim\":\"true\", + \"id.token.claim\":\"true\", + \"access.token.claim\":\"true\", + \"claim.name\":\"realm_access.roles\", + \"jsonType.label\":\"String\" + } + }" \ + "$KC_URL/auth/admin/realms/$REALM/clients/$id/protocol-mappers/models" + + echo "Mapper added to client $client_id" +} + +# Scale Keycloak deployment +function scale_keycloak { + echo "Scaling Keycloak deployment to 3 replicas..." + # kubectl scale --replicas=3 deployment/keycloak-keycloakx -n $NAMESPACE + + # Wait for scaling to complete + # kubectl rollout status deployment/keycloak-keycloakx -n $NAMESPACE + echo "Keycloak scaled successfully" +} + +# Main execution starts here +echo "Starting Keycloak configuration process" + +# Step 1: Wait for PostgreSQL and fix permissions +wait_for_postgresql || { echo "PostgreSQL readiness check failed"; exit 1; } +fix_metrics_permissions + +# Step 2: Wait for Keycloak to be ready +wait_for_keycloak || { echo "Keycloak readiness check failed"; exit 1; } + +# Step 3: Login to Keycloak +login_keycloak || { echo "Keycloak login failed"; exit 1; } + +# Step 4: Create and configure realm +create_realm +create_admin_user +create_roles +assign_role_to_user + +# Step 5: Setup client +create_client +add_client_mapper + +# Step 6: Scale Keycloak for production use +scale_keycloak + +echo "Keycloak configuration completed successfully" +exit 0 CONFIG } diff --git a/main.tf b/main.tf index 7b62c71..4eecf1f 100644 --- a/main.tf +++ b/main.tf @@ -212,12 +212,12 @@ module "ingress_resources" { service_port = local.public_port_number } -resource "null_resource" "scale_keycloak" { - depends_on = [kubernetes_job.keycloak_config] - - provisioner "local-exec" { - command = <<-EOT - kubectl patch deployment keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} -p '{"spec": {"autoscaling": {"MinReplicas": 3, "MaxReplicas": 10}}}' - EOT - } -} +# resource "null_resource" "scale_keycloak" { +# depends_on = [kubernetes_job.keycloak_config] + +# provisioner "local-exec" { +# command = <<-EOT +# kubectl patch deployment keycloak-keycloakx -n ${kubernetes_namespace.keycloak.metadata[0].name} -p '{"spec": {"autoscaling": {"MinReplicas": 3, "MaxReplicas": 10}}}' +# EOT +# } +# } From a1e8dcaad112efa06f39df52b083019d72eee5b4 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Mon, 28 Apr 2025 22:15:41 -0400 Subject: [PATCH 49/49] revert --- README.md | 2 +- config_job.tf | 389 +++----------------------------------------------- main.tf | 63 ++------ 3 files changed, 32 insertions(+), 422 deletions(-) diff --git a/README.md b/README.md index 4d793c8..a366d18 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ Common issues and solutions: |------|--------|---------| | [images](#module\_images) | git::https://github.e.it.census.gov/terraform-modules/aws-ecr-copy-images.git/ | tf-upgrade | | [ingress\_resources](#module\_ingress\_resources) | git::https://github.e.it.census.gov/SCT-Engineering/tfmod-istio-service-ingress.git | n/a | +| [post\_install](#module\_post\_install) | git::https://github.e.it.census.gov/SCT-Engineering/tfmod-config-job.git//config-job | n/a | | [service\_account](#module\_service\_account) | git::https://github.e.it.census.gov/SCT-Engineering/tfmod-config-job.git//service-account | n/a | ## Resources @@ -97,7 +98,6 @@ Common issues and solutions: |------|------| | [helm_release.keycloak](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | | [helm_release.keycloak-db](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | -| [kubernetes_job.keycloak_config](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/job) | resource | | [kubernetes_namespace.keycloak](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | | [kubernetes_secret.keycloak_secrets](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | | [null_resource.git_version](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | diff --git a/config_job.tf b/config_job.tf index c8eff5e..2b5d677 100644 --- a/config_job.tf +++ b/config_job.tf @@ -1,372 +1,23 @@ locals { - config_job_script = <<-CONFIG -#!/bin/bash -set -e - -# Error handling -function handle_error { - echo "Error occurred in configuration at line $1" - exit 1 -} -trap 'handle_error $LINENO' ERR - -# Setup variables -ROOT_URL="" -REDIRECT_URL="https://*" -KC_URL="http://keycloak-keycloakx-http:${local.public_port_number}" -DB_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-db-creds"].metadata[0].name}" -USER_SECRET="${kubernetes_secret.keycloak_secrets["keycloak-user-creds"].metadata[0].name}" -NAMESPACE="${kubernetes_namespace.keycloak.metadata[0].name}" -REALM="${var.default_realm}" -KC_TOKEN="" - -# Helper to get PostgreSQL password -function get_postgres_password { - kubectl get secret -n $NAMESPACE $DB_SECRET -o jsonpath="{.data.postgres-password}" | base64 --decode -} - -# Helper to get Keycloak credentials -function kc_user { - kubectl get secret -n $NAMESPACE $USER_SECRET -o jsonpath="{.data.username}" | base64 --decode -} - -function kc_passwd { - kubectl get secret -n $NAMESPACE $USER_SECRET -o jsonpath="{.data.password}" | base64 --decode -} - -# Execute SQL with proper password handling -function execute_sql_as_postgres { - local pg_password=$(get_postgres_password) - echo "Executing SQL command..." - kubectl exec -n $NAMESPACE -t keycloak-db-postgresql-0 -c postgresql -- \ - bash -c "PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD psql -U postgres -c \"$1\"" -} - -# Fix metrics permissions -function fix_metrics_permissions { - echo "Setting up permissions for metrics exporter..." - - execute_sql_as_postgres " - DO \$\$ - BEGIN - GRANT pg_monitor TO keycloak; - GRANT SELECT ON pg_stat_database TO keycloak; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO keycloak; - GRANT EXECUTE ON FUNCTION pg_ls_waldir(text) TO keycloak; - END; - \$\$; - " - - echo "Metrics permissions granted" -} - -# Check if PostgreSQL is ready -function wait_for_postgresql { - echo "Waiting for PostgreSQL to be ready..." - - for i in {1..30}; do - echo "PostgreSQL check attempt $i of 30" - if kubectl exec -n $NAMESPACE -t keycloak-db-postgresql-0 -c postgresql -- pg_isready -h localhost -U postgres > /dev/null 2>&1; then - echo "PostgreSQL is ready" - return 0 - fi - sleep 10 - done - - echo "PostgreSQL failed to become ready" - return 1 -} - -# Check if Keycloak is ready -function wait_for_keycloak { - echo "Waiting for Keycloak to be ready..." - - for i in {1..30}; do - echo "Keycloak check attempt $i of 30" - # if curl -s -o /dev/null -w %%{http_code} $KC_URL/auth/health/ready | grep -q "200"; then - echo "Keycloak is ready" - return 0 - # fi - sleep 10 - done - - echo "Keycloak failed to become ready" - return 1 -} - -# Login to Keycloak admin console -function login_keycloak { - echo "Logging in to Keycloak..." - - local response=$(curl -s -X POST "$KC_URL/auth/realms/master/protocol/openid-connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "username=$(kc_user)" \ - -d "password=$(kc_passwd)" \ - -d "grant_type=password" \ - -d "client_id=admin-cli") - - KC_TOKEN=$(echo $response | jq -r '.access_token') - - if [[ "$KC_TOKEN" == "null" || -z "$KC_TOKEN" ]]; then - echo "Failed to login to Keycloak: $response" - return 1 - fi - - echo "Login successful" - return 0 -} - -# Create a realm with error handling -function create_realm { - echo "Creating realm: $REALM" - - # Check if realm exists - local status=$(curl -s -o /dev/null -w %%{http_code} \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM") - - if [ "$status" == "200" ]; then - echo "Realm $REALM already exists" - return 0 - fi - - # Create realm - local response=$(curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"realm\":\"$REALM\",\"enabled\":true}" \ - "$KC_URL/auth/admin/realms") - - if [[ $? -ne 0 ]]; then - echo "Failed to create realm: $response" - return 1 - fi - - echo "Realm $REALM created successfully" - return 0 -} - -# Create admin user with proper error handling -function create_admin_user { - local username=$(kc_user) - local password=$(kc_passwd) - local email="${var.admin_email}" - - echo "Creating admin user: $username in realm $REALM" - - # Check if user exists - local user_id=$(curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/users?username=$username" | jq -r '.[0].id') - - if [[ "$user_id" != "null" && -n "$user_id" ]]; then - echo "User $username already exists with ID $user_id" - else - # Create user - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"username\":\"$username\",\"enabled\":true,\"emailVerified\":true,\"email\":\"$email\"}" \ - "$KC_URL/auth/admin/realms/$REALM/users" - - # Get user ID again - user_id=$(curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/users?username=$username" | jq -r '.[0].id') - fi - - # Set password - curl -s -X PUT \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"type\":\"password\",\"value\":\"$password\",\"temporary\":false}" \ - "$KC_URL/auth/admin/realms/$REALM/users/$user_id/reset-password" - - echo "User $username configured successfully" -} - -# Create roles with error handling -function create_roles { - # Create admin role - echo "Creating 'admin' role" - local status=$(curl -s -o /dev/null -w %%{http_code} \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/roles/admin") - - if [ "$status" != "200" ]; then - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"name\":\"admin\",\"description\":\"Admin role for $REALM\"}" \ - "$KC_URL/auth/admin/realms/$REALM/roles" - fi - - # Create user role - echo "Creating 'user' role" - status=$(curl -s -o /dev/null -w %%{http_code} \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/roles/user") - - if [ "$status" != "200" ]; then - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"name\":\"user\",\"description\":\"User role for $REALM\"}" \ - "$KC_URL/auth/admin/realms/$REALM/roles" - fi - - echo "Roles created successfully" -} - -# Assign role to user -function assign_role_to_user { - local role_name="user" - local username="user" - - echo "Assigning '$role_name' role to $username" - - # Get user ID - local user_id=$(curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/users?username=$username" | jq -r '.[0].id') - - if [[ -z "$user_id" || "$user_id" == "null" ]]; then - echo "User $username not found" - return 1 - fi - - # Get role representation - local role=$(curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/roles/$role_name") - - # Add role to user - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "[$role]" \ - "$KC_URL/auth/admin/realms/$REALM/users/$user_id/role-mappings/realm" - - echo "Role $role_name assigned to $username" -} - -# Create client -function create_client { - local client_id="${local.user_client_id}" - local client_secret="${random_uuid.user_secret.result}" - - echo "Creating client: $client_id in realm $REALM" - - # Check if client exists - local status=$(curl -s -o /dev/null -w %%{http_code} \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/clients?clientId=$client_id") - - if [[ "$status" == "200" ]]; then - echo "Client $client_id may already exist, checking..." - local exists=$(curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/clients?clientId=$client_id" | jq length) - - if [[ "$exists" -gt 0 ]]; then - echo "Client $client_id already exists" - return 0 - fi - fi - - # Create client - local response=$(curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"clientId\":\"$client_id\", - \"secret\":\"$client_secret\", - \"redirectUris\":[\"$REDIRECT_URL\"], - \"directAccessGrantsEnabled\":true, - \"protocol\":\"openid-connect\", - \"publicClient\":false - }" \ - "$KC_URL/auth/admin/realms/$REALM/clients") - - echo "Client $client_id created" -} - -# Add mapper to client -function add_client_mapper { - local client_id="${local.user_client_id}" - - echo "Adding realm roles mapper to client $client_id" - - # Get internal client ID - local id=$(curl -s \ - -H "Authorization: Bearer $KC_TOKEN" \ - "$KC_URL/auth/admin/realms/$REALM/clients?clientId=$client_id" | jq -r '.[0].id') - - if [[ -z "$id" || "$id" == "null" ]]; then - echo "Client $client_id not found" - return 1 - fi - - # Add mapper - curl -s -X POST \ - -H "Authorization: Bearer $KC_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"name\":\"realm roles\", - \"protocol\":\"openid-connect\", - \"protocolMapper\":\"oidc-usermodel-realm-role-mapper\", - \"config\":{ - \"multivalued\":\"true\", - \"userinfo.token.claim\":\"true\", - \"id.token.claim\":\"true\", - \"access.token.claim\":\"true\", - \"claim.name\":\"realm_access.roles\", - \"jsonType.label\":\"String\" - } - }" \ - "$KC_URL/auth/admin/realms/$REALM/clients/$id/protocol-mappers/models" - - echo "Mapper added to client $client_id" -} - -# Scale Keycloak deployment -function scale_keycloak { - echo "Scaling Keycloak deployment to 3 replicas..." - # kubectl scale --replicas=3 deployment/keycloak-keycloakx -n $NAMESPACE - - # Wait for scaling to complete - # kubectl rollout status deployment/keycloak-keycloakx -n $NAMESPACE - echo "Keycloak scaled successfully" -} - -# Main execution starts here -echo "Starting Keycloak configuration process" - -# Step 1: Wait for PostgreSQL and fix permissions -wait_for_postgresql || { echo "PostgreSQL readiness check failed"; exit 1; } -fix_metrics_permissions - -# Step 2: Wait for Keycloak to be ready -wait_for_keycloak || { echo "Keycloak readiness check failed"; exit 1; } - -# Step 3: Login to Keycloak -login_keycloak || { echo "Keycloak login failed"; exit 1; } - -# Step 4: Create and configure realm -create_realm -create_admin_user -create_roles -assign_role_to_user - -# Step 5: Setup client -create_client -add_client_mapper - -# Step 6: Scale Keycloak for production use -scale_keycloak - -echo "Keycloak configuration completed successfully" -exit 0 - + config_job_script = <