diff --git a/keycloak/.gitignore b/keycloak/.gitignore index 26c068c..4828403 100644 --- a/keycloak/.gitignore +++ b/keycloak/.gitignore @@ -1 +1 @@ -keycloak-values.yaml +keycloak-cr.yaml diff --git a/keycloak/justfile b/keycloak/justfile index 4d8271d..cee06c1 100644 --- a/keycloak/justfile +++ b/keycloak/justfile @@ -1,10 +1,10 @@ set fallback := true -# Keycloak Helm chart info: -# helm show chart oci://registry-1.docker.io/bitnamicharts/keycloak +# Keycloak Operator info: +# https://www.keycloak.org/operator/installation export KEYCLOAK_NAMESPACE := env("KEYCLOAK_NAMESPACE", "keycloak") -export KEYCLOAK_CHART_VERSION := env("KEYCLOAK_CHART_VERSION", "25.0.2") +export KEYCLOAK_OPERATOR_VERSION := env("KEYCLOAK_OPERATOR_VERSION", "26.3.4") export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "") export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "") export K8S_OIDC_CLIENT_ID := env('K8S_OIDC_CLIENT_ID', "k8s") @@ -52,23 +52,37 @@ create-credentials: echo "Waiting for ExternalSecret to sync..." kubectl wait --for=condition=Ready externalsecret/keycloak-credentials \ -n ${KEYCLOAK_NAMESPACE} --timeout=60s + + # Create bootstrap admin secret for Keycloak Operator (username/password format) + kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found + kubectl create secret generic keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} \ + --from-literal=username="${admin_user}" \ + --from-literal=password="${password}" else - echo "External Secrets Operator not found. Creating secret directly..." - if kubectl get secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} &>/dev/null; then - kubectl delete --ignore-not-found secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} - fi + echo "External Secrets Operator not found. Creating secrets directly..." + # Delete existing secrets + kubectl delete secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found + kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found + + # Create keycloak-credentials secret (admin-user/password format for legacy scripts) kubectl create secret generic keycloak-credentials -n ${KEYCLOAK_NAMESPACE} \ --from-literal=admin-user="${admin_user}" \ --from-literal=password="${password}" + # Create bootstrap admin secret for Keycloak Operator (username/password format) + kubectl create secret generic keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} \ + --from-literal=username="${admin_user}" \ + --from-literal=password="${password}" + if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then just put-admin-credentials-to-vault "${admin_user}" "${password}" fi fi -# Delete Keycloak secret +# Delete Keycloak secrets delete-credentials: @kubectl delete secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found + @kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found @kubectl delete externalsecret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found # Create Keycloak database secret @@ -89,31 +103,51 @@ create-database-secret: delete-database-secret: @kubectl delete secret database-config -n ${KEYCLOAK_NAMESPACE} --ignore-not-found -# Install Keycloak +# Install Keycloak Operator +install-operator: + #!/bin/bash + set -euo pipefail + just create-namespace + echo "Installing Keycloak Operator CRDs..." + kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloaks.k8s.keycloak.org-v1.yml + kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml + echo "Installing Keycloak Operator in ${KEYCLOAK_NAMESPACE} namespace..." + kubectl apply -n ${KEYCLOAK_NAMESPACE} -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/kubernetes.yml + kubectl wait --for=condition=available deployment/keycloak-operator -n ${KEYCLOAK_NAMESPACE} --timeout=300s + +# Install Keycloak instance install: #!/bin/bash set -euo pipefail - # Setup vault environment once at the beginning if vault is enabled + just install-operator just create-credentials just postgres::create-db keycloak just create-database-secret + just create-namespace KEYCLOAK_ADMIN_USER=$(just admin-username) \ - gomplate -f keycloak-values.gomplate.yaml -o keycloak-values.yaml - helm upgrade --cleanup-on-fail --install \ - keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \ - --version ${KEYCLOAK_CHART_VERSION} -n ${KEYCLOAK_NAMESPACE} --wait \ - -f keycloak-values.yaml + gomplate -f keycloak-cr.gomplate.yaml -o keycloak-cr.yaml + kubectl apply -f keycloak-cr.yaml + kubectl wait --for=condition=Ready keycloak/keycloak -n ${KEYCLOAK_NAMESPACE} --timeout=600s -# Uninstall Keycloak +# Uninstall Keycloak instance uninstall delete-db='true': #!/bin/bash set -euo pipefail - helm uninstall keycloak -n ${KEYCLOAK_NAMESPACE} --ignore-not-found --wait + kubectl delete keycloak keycloak -n ${KEYCLOAK_NAMESPACE} --ignore-not-found + kubectl wait --for=delete keycloak/keycloak -n ${KEYCLOAK_NAMESPACE} --timeout=300s || true just delete-namespace if [ "{{ delete-db }}" = "true" ]; then just postgres::delete-db keycloak fi +# Uninstall Keycloak Operator +uninstall-operator: + #!/bin/bash + set -euo pipefail + kubectl delete -n ${KEYCLOAK_NAMESPACE} -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/kubernetes.yml --ignore-not-found + kubectl delete -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml --ignore-not-found + kubectl delete -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloaks.k8s.keycloak.org-v1.yml --ignore-not-found + # Create Keycloak realm create-realm create-client-for-k8s='true' access_token_lifespan='3600' refresh_token_lifespan='14400' sso_session_idle_timeout='7200': #!/bin/bash diff --git a/keycloak/keycloak-cr.gomplate.yaml b/keycloak/keycloak-cr.gomplate.yaml new file mode 100644 index 0000000..08a1577 --- /dev/null +++ b/keycloak/keycloak-cr.gomplate.yaml @@ -0,0 +1,96 @@ +apiVersion: k8s.keycloak.org/v2alpha1 +kind: Keycloak +metadata: + name: keycloak + namespace: {{ .Env.KEYCLOAK_NAMESPACE }} +spec: + instances: 1 + image: quay.io/keycloak/keycloak:26.3.4 + startOptimized: false + + # Database configuration for external PostgreSQL + db: + vendor: postgres + host: postgres-cluster-rw.postgres + port: 5432 + database: keycloak + usernameSecret: + name: database-config + key: user + passwordSecret: + name: database-config + key: password + + # Hostname configuration + hostname: + hostname: {{ .Env.KEYCLOAK_HOST }} + strict: false + strictBackchannel: false + + # HTTP configuration + http: + httpEnabled: true + httpPort: 8080 + httpsPort: 8443 + + # Proxy configuration for edge proxy + proxy: + headers: xforwarded + + # Additional options and admin configuration + additionalOptions: + - name: http-enabled + value: "true" + - name: hostname-strict + value: "false" + - name: hostname-strict-https + value: "false" + - name: proxy + value: edge + + # Bootstrap admin configuration + bootstrapAdmin: + user: + secret: keycloak-bootstrap-admin + + # Resources + resources: + requests: + memory: "1.5Gi" + cpu: "500m" + limits: + memory: "2Gi" + cpu: "1000m" + + # Ingress configuration (disabled - using separate Ingress resource) + ingress: + enabled: false + +--- +# Separate Ingress resource for custom configuration +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: keycloak-ingress + namespace: {{ .Env.KEYCLOAK_NAMESPACE }} + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/router.entrypoints: websecure +spec: + ingressClassName: traefik + tls: + - hosts: + - {{ .Env.KEYCLOAK_HOST }} + secretName: keycloak-tls + rules: + - host: {{ .Env.KEYCLOAK_HOST }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: keycloak-service + port: + number: 8080 + diff --git a/keycloak/keycloak-values.gomplate.yaml b/keycloak/keycloak-values.gomplate.yaml deleted file mode 100644 index 85b5053..0000000 --- a/keycloak/keycloak-values.gomplate.yaml +++ /dev/null @@ -1,57 +0,0 @@ -global: - security: - allowInsecureImages: true - -production: true - -# Enable HTTP for health checks in production mode -extraEnvVars: - - name: KC_HTTP_ENABLED - value: "true" - -auth: - adminUser: {{ .Env.KEYCLOAK_ADMIN_USER }} - existingSecret: keycloak-credentials - passwordSecretKey: password - -postgresql: - enabled: false - -externalDatabase: - host: postgres-cluster-rw.postgres - port: 5432 - database: keycloak - existingSecret: database-config - existingSecretUserKey: user - existingSecretPasswordKey: password - -tls: - enabled: true - autoGenerated: - enabled: true - engine: helm - -# Keycloak pod may not start with the default memory limits -resources: - limits: - memory: 2Gi - requests: - memory: 1.5Gi - -image: - registry: docker.io - repository: bitnamilegacy/keycloak - # tag: 26.0.5-debian-12-r0 - # debug: true - -# logging: -# level: DEBUG - -ingress: - enabled: true - ingressClassName: traefik - hostname: {{ .Env.KEYCLOAK_HOST }} - annotations: - kubernetes.io/ingress.class: traefik - traefik.ingress.kubernetes.io/router.entrypoints: websecure - tls: true