From 3ac1b27b4cda6e5dd458063effefbb840c70edcc Mon Sep 17 00:00:00 2001 From: ace Date: Mon, 6 May 2024 03:00:10 +0300 Subject: [PATCH] add keycloak --- .../ghp/sample/group_vars/k8s/keycloak.yaml | 46 +++++++++ playbooks/ghp/keycloak.yaml | 5 + playbooks/ghp/shared-infra.yaml | 12 ++- roles/keycloak/defaults/main.yaml | 63 ++++++++++++ roles/keycloak/tasks/main.yaml | 96 +++++++++++++++++++ roles/keycloak/tasks/secrets.yaml | 25 +++++ 6 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 inventory/ghp/sample/group_vars/k8s/keycloak.yaml create mode 100644 playbooks/ghp/keycloak.yaml create mode 100644 roles/keycloak/defaults/main.yaml create mode 100644 roles/keycloak/tasks/main.yaml create mode 100644 roles/keycloak/tasks/secrets.yaml diff --git a/inventory/ghp/sample/group_vars/k8s/keycloak.yaml b/inventory/ghp/sample/group_vars/k8s/keycloak.yaml new file mode 100644 index 0000000..9258562 --- /dev/null +++ b/inventory/ghp/sample/group_vars/k8s/keycloak.yaml @@ -0,0 +1,46 @@ +keycloak_values: {} + +keycloak_realms: {} +# - id: myrealm +# realm: myrealm + +keycloak_clients: {} +# - client_id: gitea +# realm: myrealm +# public_client: true +# - client_id: gitea +# realm: myrealm +# public_client: false + +keycloak_clients_default_protocol_mappings: {} +# - config: +# access.token.claim: true +# claim.name: "groups" +# id.token.claim: true +# jsonType.label: String +# user.attribute: groups +# userinfo.token.claim: true +# name: groups +# protocol: openid-connect +# protocolMapper: oidc-usermodel-attribute-mapper + +keycloak_groups: {} +# - name: admins +# realm: myrealm +# - name: devops +# realm: myrealm + +keycloak_users: {} +# - username: John Doe +# realm: myrealm +# firstName: John +# lastName: Doe +# credentials: +# - type: password +# value: my_very_strong_password +# temporary: true +# groups: +# - name: admins +# state: present +# - name: devops +# state: present diff --git a/playbooks/ghp/keycloak.yaml b/playbooks/ghp/keycloak.yaml new file mode 100644 index 0000000..dc69aa7 --- /dev/null +++ b/playbooks/ghp/keycloak.yaml @@ -0,0 +1,5 @@ +--- +- hosts: k8s + connection: local + roles: + - keycloak diff --git a/playbooks/ghp/shared-infra.yaml b/playbooks/ghp/shared-infra.yaml index 0cf2d3d..f1abb5e 100644 --- a/playbooks/ghp/shared-infra.yaml +++ b/playbooks/ghp/shared-infra.yaml @@ -3,19 +3,25 @@ connection: local tasks: - name: Deploy PostgreSQL - import_role: + import_role: name: postgres when: postgres_enabled | default(true) tags: postgres - name: Deploy OpenLDAP - import_role: + import_role: name: openldap when: openldap_enabled | default(true) tags: openldap - name: Deploy MinIO - import_role: + import_role: name: minio when: minio_enabled | default(true) tags: minio + + - name: Deploy Keycloak + import_role: + name: keycloak + when: keycloak_enabled | default(false) + tags: keycloak diff --git a/roles/keycloak/defaults/main.yaml b/roles/keycloak/defaults/main.yaml new file mode 100644 index 0000000..d7c5fd7 --- /dev/null +++ b/roles/keycloak/defaults/main.yaml @@ -0,0 +1,63 @@ +keycloak_enabled: true +keycloak_publish: false +keycloak_console_publish: false +keycloak_use_external_db: true +keycloak_chart_ref: "codecentric/keycloakx" +keycloak_short_name: "keycloak" +keycloak_console_short_name: "console" +keycloak_default_values: + command: + - /opt/keycloak/bin/kc.sh + - start + - --http-enabled=true + - --http-port=8080 + - --hostname={{ keycloak_short_name }}.{{ domain }} + - --hostname-strict=false + - --hostname-strict-https=false + database: + database: "keycloak" + hostname: "{{ postgres_db_team | default(namespace) }}-postgres.{{ postgres_db_namespace | default(namespace) }}" + username: "{{ keycloak_db_username | default(omit) }}" + password: "{{ keycloak_db_password | default(omit) }}" + port: 5432 + vendor: postgres + extraEnv: | + - name: KEYCLOAK_ADMIN + value: admin + - name: KEYCLOAK_ADMIN_PASSWORD + value: {{ keycloak_admin_password }} + - name: JAVA_OPTS_APPEND + value: >- + -Djgroups.dns.query={{ keycloak_short_name }}-keycloakx-headless + ingress: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + enabled: true + ingressClassName: "{{ external_ingress_class if minio_publish else internal_ingress_class }}" + rules: + - host: "{{ keycloak_short_name }}.{{ domain }}" + paths: + - path: /auth/ + pathType: Prefix + servicePort: http + tls: + - hosts: + - "{{ keycloak_short_name }}.{{ domain }}" + secretName: "{{ keycloak_short_name }}.{{ domain }}-tls" + +keycloak_realms: {} +keycloak_clients: {} +keycloak_clients_default_protocol_mappings: {} +# - config: +# access.token.claim: true +# claim.name: "groups" +# id.token.claim: true +# jsonType.label: String +# user.attribute: groups +# userinfo.token.claim: true +# name: groups +# protocol: openid-connect +# protocolMapper: oidc-usermodel-attribute-mapper + +keycloak_users: {} +keycloak_groups: {} diff --git a/roles/keycloak/tasks/main.yaml b/roles/keycloak/tasks/main.yaml new file mode 100644 index 0000000..3120705 --- /dev/null +++ b/roles/keycloak/tasks/main.yaml @@ -0,0 +1,96 @@ +- name: Import secret.yaml to obtain secrets + include_tasks: secrets.yaml + when: + - keycloak_use_external_db + - postgres_enabled is defined and postgres_enabled + +- set_fact: + keycloak_combined_values: "{{ keycloak_default_values | combine(keycloak_values, recursive=true) }}" + +- name: Deploy Keycloak + kubernetes.core.helm: + release_namespace: "{{ keycloak_namespace | default(namespace) }}" + release_name: "{{ keycloak_name | default('keycloak') }}" + chart_ref: "{{ keycloak_chart_ref }}" + chart_version: "{{ keycloak_version | default(omit) }}" + release_values: "{{ keycloak_combined_values | from_yaml }}" + +- name: Wait Keycloak until HTTP status is 200 + uri: + url: "https://{{ keycloak_short_name }}.{{ domain }}/auth" + return_content: yes + validate_certs: no + status_code: + - 200 + until: uri_output.status == 200 + retries: 24 # Retries for 24 * 5 seconds = 120 seconds = 2 minutes + delay: 5 # Every 5 seconds + register: uri_output + +- name: Create or update Keycloak client, authentication with credentials + community.general.keycloak_client: + client_id: admin-cli + auth_keycloak_url: "https://{{ keycloak_short_name }}.{{ domain }}/auth" + auth_realm: master + auth_username: admin + auth_password: "{{ keycloak_admin_password }}" + state: present +- name: Create or update Keycloak realms + community.general.keycloak_realm: + auth_client_id: admin-cli + auth_keycloak_url: "https://{{ keycloak_short_name }}.{{ domain }}/auth" + auth_realm: master + auth_username: admin + auth_password: "{{ keycloak_admin_password }}" + id: "{{ item.id }}" + realm: "{{ item.realm }}" + state: "{{ item.state | default('present') }}" + enabled: "{{ item.enabled | default(true) }}" + loop: "{{ keycloak_realms }}" +- name: Create or update Keycloak clients + community.general.keycloak_client: + auth_client_id: admin-cli + auth_keycloak_url: "https://{{ keycloak_short_name }}.{{ domain }}/auth" + auth_realm: master + auth_username: admin + auth_password: "{{ keycloak_admin_password }}" + client_id: "{{ item.client_id + '-public' if item.public_client else item.client_id }}" + realm: "{{ item.realm }}" + name: "{{ \"'${client_\" + item.client_id + \"'\" if item.public_client else \"'${client_\" + item.client_id + \"_public'\" }}" + protocol: openid-connect + public_client: "{{ item.public_client | default(false) }}" + standard_flow_enabled: "{{ item.standard_flow_enabled | default(true) }}" + implicit_flow_enabled: "{{ item.implicit_flow_enabled | default(true) }}" + direct_access_grants_enabled: "{{ item.direct_access_grants_enabled | default(true) }}" + state: "{{ item.state | default('present') }}" + protocol_mappers: "{{ keycloak_clients_default_protocol_mappings }}" + loop: "{{ keycloak_clients }}" +- name: Create Keycloak groups + community.general.keycloak_group: + auth_client_id: admin-cli + auth_keycloak_url: "https://{{ keycloak_short_name }}.{{ domain }}/auth" + auth_realm: master + auth_username: admin + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ item.realm }}" + name: "{{ item.name }}" + state: "{{ item.state | default('present') }}" + loop: "{{ keycloak_groups }}" +- name: Create Keycloak users + community.general.keycloak_user: + auth_client_id: admin-cli + auth_keycloak_url: "https://{{ keycloak_short_name }}.{{ domain }}/auth" + auth_realm: master + auth_username: admin + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ item.realm }}" + state: "{{ item.state | default('present') }}" + username: "{{ item.username }}" + firstName: "{{ item.firstName }}" + lastName: "{{ item.lastName }}" + email: "{{ item.email | default( item.username + '@' + domain) }}" + enabled: "{{ item.enabled | default(true) }}" + emailVerified: "{{ item.emailVerified | default(true) }}" + credentials: "{{ item.credentials }}" + groups: "{{ item.groups }}" + loop: "{{ keycloak_users }}" diff --git a/roles/keycloak/tasks/secrets.yaml b/roles/keycloak/tasks/secrets.yaml new file mode 100644 index 0000000..578808a --- /dev/null +++ b/roles/keycloak/tasks/secrets.yaml @@ -0,0 +1,25 @@ +- block: + - name: Set DB namespace for secret lookup + set_fact: + db_namespace: "{{ keycloak_db_namespace | default(postgres_db_namespace) | default(postgres_namespace) | default(postgres_operator_namespace) | default(namespace) }}" + + - name: Set DB secret name for lookup + set_fact: + db_secret_name: "keycloak.{{ postgres_db_team | default(namespace) }}-postgres.credentials.postgresql.acid.zalan.do" + + - name: Lookup Keycloak DB secret + set_fact: + keycloak_db_secret: "{{ lookup('k8s', kind='Secret', namespace=db_namespace, resource_name=db_secret_name) }}" + + - debug: + msg: "{{ keycloak_db_secret }}" + verbosity: 2 + + - name: Set Keycloak DB username + set_fact: + keycloak_db_username: "{{ keycloak_db_secret.data.username | b64decode }}" + + - name: Set Keycloak DB password + set_fact: + keycloak_db_password: "{{ keycloak_db_secret.data.password | b64decode }}" +