From 53352252da787ff09d7246311fd53564a0feba3d Mon Sep 17 00:00:00 2001 From: Sven Ketelsen Date: Mon, 19 Apr 2021 21:04:34 +0200 Subject: [PATCH] feat: added connect/keycloak setup --- .gitignore | 2 + README.md | 8 + group_vars/all/plain.yml | 3 - group_vars/stage_dev/plain.yml | 16 ++ host_vars/dev-connect-01.yml | 7 + host_vars/dev-connect-02.yml | 7 + host_vars/dev-connect-03.yml | 7 + host_vars/dev-connect-04.yml | 7 + host_vars/dev-elastic-stack-01.yml | 3 +- host_vars/dev-elastic-stack-02.yml | 3 +- host_vars/dev-elastic-stack-03.yml | 3 +- host_vars/dev-keycloak-01.yml | 162 +++++++++++++++++ provisioning.yml | 2 +- roles/common/tasks/main.yml | 23 +-- roles/connect/defaults/main.yml | 8 +- roles/connect/tasks/main.yml | 23 +-- roles/docker-registry/defaults/main.yml | 137 -------------- roles/docker-registry/tasks/main.yml | 171 ------------------ roles/filebeat/defaults/main.yaml | 30 +++ .../handlers/main.yml | 0 .../meta/main.yml | 0 roles/filebeat/tasks/main.yaml | 63 +++++++ roles/hcloud/tasks/configure-firewall.yml | 68 +++++++ roles/hcloud/tasks/main.yml | 70 +------ .../hcloud/templates/firewall-default.json.j2 | 25 +-- .../hcloud/templates/firewall-kibana.json.j2 | 22 +++ .../templates/firewall-monitoring.json.j2 | 21 +++ roles/keycloak/defaults/main.yml | 104 +++++++++++ .../vars => keycloak/handlers}/main.yml | 0 roles/keycloak/meta/main.yml | 1 + roles/keycloak/tasks/configure_client.yml | 21 +++ roles/keycloak/tasks/configure_realm.yml | 119 ++++++++++++ .../configure_user_storage_provider_ldap.yml | 107 +++++++++++ roles/keycloak/tasks/create_realm_users.yml | 61 +++++++ roles/keycloak/tasks/main.yml | 160 ++++++++++++++++ .../keycloak-realm-create-client.json.j2 | 63 +++++++ .../keycloak-realm-create-user.json.j2 | 12 ++ .../templates/keycloak-realm-create.json.j2 | 133 ++++++++++++++ roles/keycloak/vars/main.yml | 1 + roles/node-exporter/defaults/main.yml | 2 +- roles/node-exporter/tasks/main.yml | 26 +-- roles/prometheus/tasks/main.yml | 32 +--- roles/traefik/tasks/main.yml | 32 ++-- setup.yml | 36 ++++ smardigo.yml | 35 +++- stage-dev | 5 + templates/docker-registry/registry/config.yml | 12 -- templates/docker-registry/registry/init | 7 - templates/docker-registry/secrets/.gitignore | 1 - templates/filebeat/certs/ca/ca.crt | 20 ++ templates/filebeat/certs/filebeat.crt | 21 +++ templates/filebeat/certs/filebeat.key | 27 +++ templates/filebeat/config/filebeat.yml.j2 | 28 +++ .../resources/css/smardigo-account.css | 39 ++++ .../account/resources/img/favicon.ico | Bin 0 -> 152126 bytes .../account/resources/img/smardigo-logo.png | Bin 0 -> 8143 bytes .../smardigo-theme/account/theme.properties | 4 + .../admin/resources/css/smardigo-styles.css | 17 ++ .../admin/resources/img/favicon.ico | Bin 0 -> 152126 bytes .../admin/resources/img/smardigo-logo.png | Bin 0 -> 8143 bytes .../smardigo-theme/admin/theme.properties | 4 + .../login/resources/css/smardigo-login.css | 59 ++++++ .../login/resources/img/favicon.ico | Bin 0 -> 152126 bytes .../login/resources/img/smardigo-logo.png | Bin 0 -> 35880 bytes .../smardigo-theme/login/theme.properties | 4 + .../config/prometheus/prometheus.yml.j2 | 4 +- 66 files changed, 1569 insertions(+), 519 deletions(-) create mode 100644 host_vars/dev-connect-01.yml create mode 100644 host_vars/dev-connect-02.yml create mode 100644 host_vars/dev-connect-03.yml create mode 100644 host_vars/dev-connect-04.yml create mode 100644 host_vars/dev-keycloak-01.yml delete mode 100644 roles/docker-registry/defaults/main.yml delete mode 100644 roles/docker-registry/tasks/main.yml create mode 100644 roles/filebeat/defaults/main.yaml rename roles/{docker-registry => filebeat}/handlers/main.yml (100%) rename roles/{docker-registry => filebeat}/meta/main.yml (100%) create mode 100644 roles/filebeat/tasks/main.yaml create mode 100644 roles/hcloud/tasks/configure-firewall.yml create mode 100644 roles/hcloud/templates/firewall-kibana.json.j2 create mode 100644 roles/hcloud/templates/firewall-monitoring.json.j2 create mode 100644 roles/keycloak/defaults/main.yml rename roles/{docker-registry/vars => keycloak/handlers}/main.yml (100%) create mode 100644 roles/keycloak/meta/main.yml create mode 100644 roles/keycloak/tasks/configure_client.yml create mode 100644 roles/keycloak/tasks/configure_realm.yml create mode 100644 roles/keycloak/tasks/configure_user_storage_provider_ldap.yml create mode 100644 roles/keycloak/tasks/create_realm_users.yml create mode 100644 roles/keycloak/tasks/main.yml create mode 100644 roles/keycloak/templates/keycloak-realm-create-client.json.j2 create mode 100644 roles/keycloak/templates/keycloak-realm-create-user.json.j2 create mode 100644 roles/keycloak/templates/keycloak-realm-create.json.j2 create mode 100644 roles/keycloak/vars/main.yml delete mode 100644 templates/docker-registry/registry/config.yml delete mode 100644 templates/docker-registry/registry/init delete mode 100644 templates/docker-registry/secrets/.gitignore create mode 100644 templates/filebeat/certs/ca/ca.crt create mode 100644 templates/filebeat/certs/filebeat.crt create mode 100644 templates/filebeat/certs/filebeat.key create mode 100644 templates/filebeat/config/filebeat.yml.j2 create mode 100644 templates/keycloak/smardigo-theme/account/resources/css/smardigo-account.css create mode 100644 templates/keycloak/smardigo-theme/account/resources/img/favicon.ico create mode 100644 templates/keycloak/smardigo-theme/account/resources/img/smardigo-logo.png create mode 100644 templates/keycloak/smardigo-theme/account/theme.properties create mode 100644 templates/keycloak/smardigo-theme/admin/resources/css/smardigo-styles.css create mode 100644 templates/keycloak/smardigo-theme/admin/resources/img/favicon.ico create mode 100644 templates/keycloak/smardigo-theme/admin/resources/img/smardigo-logo.png create mode 100644 templates/keycloak/smardigo-theme/admin/theme.properties create mode 100644 templates/keycloak/smardigo-theme/login/resources/css/smardigo-login.css create mode 100644 templates/keycloak/smardigo-theme/login/resources/img/favicon.ico create mode 100644 templates/keycloak/smardigo-theme/login/resources/img/smardigo-logo.png create mode 100644 templates/keycloak/smardigo-theme/login/theme.properties diff --git a/.gitignore b/.gitignore index 0dee4c6..ac6569e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .project /vault-pass + +image.tar.gz \ No newline at end of file diff --git a/README.md b/README.md index 9be9500..b8e90b2 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,11 @@ Create/Start servers for stage-dev # TODO 212.121.131.106 - Siemansdamm - IPFire + +Keykloak + Read Flow for Docker + Get ID by 'docker auth' + Update Client 'docker-registry' + Download Installation +Docker-Registry + Use Installation from Keycloak Client 'docker-registry' \ No newline at end of file diff --git a/group_vars/all/plain.yml b/group_vars/all/plain.yml index 5ff6857..5b59325 100644 --- a/group_vars/all/plain.yml +++ b/group_vars/all/plain.yml @@ -7,10 +7,7 @@ domain: smardigo.digital use_ssl: true http_s: "http{{ use_ssl | ternary('s', '', omit) }}" -service_prefix: '' -service_suffix: '' service_name: "{{ inventory_hostname }}" - stage_server_name: "{{ inventory_hostname }}" stage_server_hostname: "{{ inventory_hostname }}" stage_server_url_host: "{{ stage_server_name }}.{{ domain }}" diff --git a/group_vars/stage_dev/plain.yml b/group_vars/stage_dev/plain.yml index d6d5614..528e8db 100644 --- a/group_vars/stage_dev/plain.yml +++ b/group_vars/stage_dev/plain.yml @@ -3,3 +3,19 @@ stage: "dev" alertmanager_channel_smardigo: "#monitoring-qa" + +# TODO read configuration with hetzner rest api +filebeat_extra_hosts: [ + { + hostname: logstash-dev-elastic-stack-01, + ip: 10.0.0.2, + }, + { + hostname: logstash-dev-elastic-stack-02, + ip: 10.0.0.3 + }, + { + hostname: logstash-dev-elastic-stack-03, + ip: 10.0.0.4, + }, +] \ No newline at end of file diff --git a/host_vars/dev-connect-01.yml b/host_vars/dev-connect-01.yml new file mode 100644 index 0000000..cd15e8e --- /dev/null +++ b/host_vars/dev-connect-01.yml @@ -0,0 +1,7 @@ +--- + +connect_auth_module: oidc +connect_oidc_client_id: connect-01 +connect_oidc_client_secret: 9e234965-1041-4653-8a0e-db964c04bc26 +connect_oidc_registration_id: connect-01 +connect_oidc_issuer_uri: https://dev-keycloak-01.smardigo.digital/auth/realms/smardigo-01 diff --git a/host_vars/dev-connect-02.yml b/host_vars/dev-connect-02.yml new file mode 100644 index 0000000..0451be7 --- /dev/null +++ b/host_vars/dev-connect-02.yml @@ -0,0 +1,7 @@ +--- + +connect_auth_module: oidc +connect_oidc_client_id: connect-02 +connect_oidc_client_secret: 9e234965-1041-4653-8a0e-db964c04bc26 +connect_oidc_registration_id: connect-02 +connect_oidc_issuer_uri: https://dev-keycloak-01.smardigo.digital/auth/realms/smardigo-01 diff --git a/host_vars/dev-connect-03.yml b/host_vars/dev-connect-03.yml new file mode 100644 index 0000000..6084a68 --- /dev/null +++ b/host_vars/dev-connect-03.yml @@ -0,0 +1,7 @@ +--- + +connect_auth_module: oidc +connect_oidc_client_id: connect-03 +connect_oidc_client_secret: 9e234965-1041-4653-8a0e-db964c04bc26 +connect_oidc_registration_id: connect-03 +connect_oidc_issuer_uri: https://dev-keycloak-01.smardigo.digital/auth/realms/smardigo-01 diff --git a/host_vars/dev-connect-04.yml b/host_vars/dev-connect-04.yml new file mode 100644 index 0000000..4c6e314 --- /dev/null +++ b/host_vars/dev-connect-04.yml @@ -0,0 +1,7 @@ +--- + +connect_auth_module: oidc +connect_oidc_client_id: connect-04 +connect_oidc_client_secret: 9e234965-1041-4653-8a0e-db964c04bc26 +connect_oidc_registration_id: connect-04 +connect_oidc_issuer_uri: https://dev-keycloak-01.smardigo.digital/auth/realms/smardigo-01 diff --git a/host_vars/dev-elastic-stack-01.yml b/host_vars/dev-elastic-stack-01.yml index 995d718..ed63c6c 100644 --- a/host_vars/dev-elastic-stack-01.yml +++ b/host_vars/dev-elastic-stack-01.yml @@ -1,11 +1,12 @@ --- -hetzner_server_type: cx21 +hetzner_server_type: cx31 hetzner_ssh_keys: - stefan@curow.de - sven.ketelsen@arxes-tolina.de smardigo_plattform_users: + - 'elastic' - 'stefan.curow' - 'sven.ketelsen' \ No newline at end of file diff --git a/host_vars/dev-elastic-stack-02.yml b/host_vars/dev-elastic-stack-02.yml index 349668c..f52ff36 100644 --- a/host_vars/dev-elastic-stack-02.yml +++ b/host_vars/dev-elastic-stack-02.yml @@ -1,11 +1,12 @@ --- -hetzner_server_type: cx21 +hetzner_server_type: cx31 hetzner_ssh_keys: - stefan@curow.de - sven.ketelsen@arxes-tolina.de smardigo_plattform_users: + - 'elastic' - 'stefan.curow' - 'sven.ketelsen' diff --git a/host_vars/dev-elastic-stack-03.yml b/host_vars/dev-elastic-stack-03.yml index 349668c..f52ff36 100644 --- a/host_vars/dev-elastic-stack-03.yml +++ b/host_vars/dev-elastic-stack-03.yml @@ -1,11 +1,12 @@ --- -hetzner_server_type: cx21 +hetzner_server_type: cx31 hetzner_ssh_keys: - stefan@curow.de - sven.ketelsen@arxes-tolina.de smardigo_plattform_users: + - 'elastic' - 'stefan.curow' - 'sven.ketelsen' diff --git a/host_vars/dev-keycloak-01.yml b/host_vars/dev-keycloak-01.yml new file mode 100644 index 0000000..bb9d578 --- /dev/null +++ b/host_vars/dev-keycloak-01.yml @@ -0,0 +1,162 @@ +--- + +keycloak: { + realms: [ + { + name: 'smardigo-01', + display_name: 'smardigo-01', + users: [ + { + "username": "connect-admin", + "password": "connect-admin", + } + ], + clients: [ + { + clientId: 'connect-01', + name: 'connect-01', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-01.smardigo.digital/*", + "http://dev-connect-01.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-01.smardigo.digital", + ]', + }, + { + clientId: 'connect-02', + name: 'connect-02', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-02.smardigo.digital/*", + "http://dev-connect-02.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-02.smardigo.digital", + ]', + }, + { + clientId: 'connect-03', + name: 'connect-03', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-03.smardigo.digital/*", + "http://dev-connect-03.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-03.smardigo.digital", + ]', + }, + { + clientId: 'connect-04', + name: 'connect-04', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-04.smardigo.digital/*", + "http://dev-connect-04.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-04.smardigo.digital", + ]', + }, + { + clientId: 'connect-05', + name: 'connect-05', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-05.smardigo.digital/*", + "http://dev-connect-05.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-05.smardigo.digital", + ]', + }, + { + clientId: 'connect-06', + name: 'connect-06', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-06.smardigo.digital/*", + "http://dev-connect-06.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-06.smardigo.digital", + ]', + }, + { + clientId: 'connect-07', + name: 'connect-07', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-07.smardigo.digital/*", + "http://dev-connect-07.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-07.smardigo.digital", + ]', + }, + { + clientId: 'connect-08', + name: 'connect-08', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-08.smardigo.digital/*", + "http://dev-connect-08.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-08.smardigo.digital", + ]', + }, + { + clientId: 'connect-09', + name: 'connect-09', + admin_url: '', + root_url: '', + redirect_uris: ' + [ + "https://dev-connect-09.smardigo.digital/*", + "http://dev-connect-09.smardigo.digital/*", + ]', + secret: '9e234965-1041-4653-8a0e-db964c04bc26', + web_origins: ' + [ + "https://dev-connect-09.smardigo.digital", + ]', + } + ] + } + ] +} \ No newline at end of file diff --git a/provisioning.yml b/provisioning.yml index 6a6e925..6c1169f 100644 --- a/provisioning.yml +++ b/provisioning.yml @@ -2,7 +2,7 @@ - name: 'apply setup to {{ host | default("all") }}' hosts: '{{ host | default("all") }}' - serial: "{{ serial_number | default(5) }}" + serial: "{{ serial_number | default(1) }}" gather_facts: no become: no diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index 476e685..fef7f10 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -20,27 +20,6 @@ when: - send_status_messages -- name: Gather current server infos - hcloud_server_info: - api_token: "{{ hetzner_authentication_token }}" - register: hetzner_server_infos - delegate_to: 127.0.0.1 - become: false - -- name: Save current server infos as variable (fact) - set_fact: - hetzner_server_infos_json: "{{ hetzner_server_infos.hcloud_server_info }}" - delegate_to: 127.0.0.1 - become: false - -- name: Read ip for {{ inventory_hostname }} - set_fact: - stage_server_ip: "{{ item.ipv4_address }}" - when: item.name == inventory_hostname - with_items: "{{ hetzner_server_infos_json }}" - delegate_to: 127.0.0.1 - become: false - - name: 'Insert/Update ssh config in ~/.ssh/config' blockinfile: marker: '# {mark} managed by ansible (ssh config for {{ inventory_hostname }})' @@ -100,7 +79,7 @@ state: present exclusive: true key: "{{ lookup('file', '{{ inventory_dir }}/keys/{{ item }}/id_rsa.pub') }}" - loop: '{{ smardigo_plattform_users }}' + loop: '{{ smardigo_plattform_users | difference(["elastic"]) }}' tags: - users diff --git a/roles/connect/defaults/main.yml b/roles/connect/defaults/main.yml index e4af2c7..3ced9de 100644 --- a/roles/connect/defaults/main.yml +++ b/roles/connect/defaults/main.yml @@ -1,6 +1,6 @@ --- -connect_image_name: docker.arxes-tolina.de/smardigo/connect-whitelabel-app +connect_image_name: docker.dev-at.de/smardigo/connect-whitelabel-app connect_version: '7.1.0-SNAPSHOT' connect_admin_username: "connect-admin" @@ -20,14 +20,14 @@ connect_postgres_id: "{{ service_name }}-postgres-connect" connect_labels: [ '"traefik.enable=true"', '"traefik.http.routers.{{ connect_id }}.service={{ connect_id }}"', - '"traefik.http.routers.{{ connect_id }}.rule=Host(`{{ connect_id }}.{{ domain }}`)"', + '"traefik.http.routers.{{ connect_id }}.rule=Host(`{{ stage_server_url_host }}`)"', '"traefik.http.routers.{{ connect_id }}.entrypoints=websecure"', '"traefik.http.routers.{{ connect_id }}.tls=true"', '"traefik.http.routers.{{ connect_id }}.tls.certresolver=letsencrypt"', '"traefik.http.services.{{ connect_id }}.loadbalancer.server.port={{ service_port }}"', '"traefik.http.routers.{{ connect_id }}-admin.service={{ connect_id }}-admin"', - '"traefik.http.routers.{{ connect_id }}-admin.rule=Host(`{{ connect_id }}.{{ domain }}`)"', + '"traefik.http.routers.{{ connect_id }}-admin.rule=Host(`{{ stage_server_url_host }}`)"', '"traefik.http.routers.{{ connect_id }}-admin.entrypoints=admin-service"', '"traefik.http.routers.{{ connect_id }}-admin.tls=true"', '"traefik.http.routers.{{ connect_id }}-admin.tls.certresolver=letsencrypt"', @@ -38,7 +38,7 @@ connect_labels: [ '"traefik.http.services.{{ connect_id }}-admin.loadbalancer.server.port={{ management_port }}"', '"traefik.http.routers.{{ connect_id }}-monitor.service={{ service_name }}-node-exporter"', - '"traefik.http.routers.{{ connect_id }}-monitor.rule=Host(`{{ connect_id }}.{{ domain }}`)"', + '"traefik.http.routers.{{ connect_id }}-monitor.rule=Host(`{{ stage_server_url_host }}`)"', '"traefik.http.routers.{{ connect_id }}-monitor.entrypoints=admin-system"', '"traefik.http.routers.{{ connect_id }}-monitor.tls=true"', '"traefik.http.routers.{{ connect_id }}-monitor.tls.certresolver=letsencrypt"', diff --git a/roles/connect/tasks/main.yml b/roles/connect/tasks/main.yml index 44ff830..6adb1c1 100644 --- a/roles/connect/tasks/main.yml +++ b/roles/connect/tasks/main.yml @@ -18,34 +18,13 @@ when: - send_status_messages -- name: Gather current server infos - hcloud_server_info: - api_token: "{{ hetzner_authentication_token }}" - register: hetzner_server_infos - delegate_to: 127.0.0.1 - become: false - -- name: Save current server infos as variable (fact) - set_fact: - hetzner_server_infos_json: "{{ hetzner_server_infos.hcloud_server_info }}" - delegate_to: 127.0.0.1 - become: false - -- name: Read ip for {{ inventory_hostname }} - set_fact: - stage_server_ip: "{{ item.ipv4_address }}" - when: item.name == inventory_hostname - with_items: "{{ hetzner_server_infos_json }}" - delegate_to: 127.0.0.1 - become: false - - name: "Setup DNS configuration for {{ service_name }} connect" include_role: name: _digitalocean tasks_from: domain vars: record_data: "{{ stage_server_ip }}" - record_name: "{{ service_name }}-connect" + record_name: "{{ service_name }}" - name: "Check if {{ service_name }}/docker-compose.yml exists" stat: diff --git a/roles/docker-registry/defaults/main.yml b/roles/docker-registry/defaults/main.yml deleted file mode 100644 index f18f0f2..0000000 --- a/roles/docker-registry/defaults/main.yml +++ /dev/null @@ -1,137 +0,0 @@ ---- - -docker_registry_id: "{{ service_name }}-registry" - -docker_registry_image_name: "library/registry" -docker_registry_image_version: "2.7" - - - -docker_portus_secret_key_base: docker-portus-secret-key-base -docker_portus_password: docker-portus-admin - - -docker_postgres_portus_image_name: "postgres" -docker_postgres_portus_image_version: "12" - -docker_portus_postgres_database: docker-portus-postgres -docker_portus_postgres_username: docker-portus-postgres-admin -docker_portus_postgres_password: docker-portus-postgres-admin - -docker_registry_docker: { - networks: [ - { - name: front-tier, - external: true, - }, - { - name: back-tier, - external: true, - }, - ], - volumes: [ - { - name: "{{ service_name }}-registry-data", - }, - { - name: "{{ service_name }}-postgres-portus-data" - } - ], - services: [ - { - name: "{{ service_name }}-portus", - image_name: "opensuse/portus", - image_version: "2.4", - environment: [ - "PORTUS_MACHINE_FQDN_VALUE: \"{{ stage_server_url_host }}\"", - "PORTUS_DB_HOST: \"{{ service_name }}-postgres-portus\"", - "PORTUS_DB_DATABASE: \"{{ docker_portus_postgres_database }}\"", - "PORTUS_DB_USERNAME: \"{{ docker_portus_postgres_username }}\"", - "PORTUS_DB_PASSWORD: \"{{ docker_portus_postgres_password }}\"", - "PORTUS_DB_POOL: \"5\"", - "PORTUS_SECRET_KEY_BASE: \"{{ docker_portus_secret_key_base }}\"", - "PORTUS_KEY_PATH: \"/certificates/portus.key\"", - "PORTUS_PASSWORD: \"{{ docker_portus_password }}\"", - "PORTUS_PUMA_TLS_KEY: \"/certificates/portus.key\"", - "PORTUS_PUMA_TLS_CERT: \"/certificates/portus.crt\"", - "RAILS_SERVE_STATIC_FILES: \"true\"", - ], - volumes: [ - '"{{ service_name }}-postgres-portus-data:/var/lib/postgresql/data"', - ], - networks: [ - '"front-tier"', - '"back-tier"', - ] - }, - { - name: "{{ service_name }}-portus-background", - image_name: "opensuse/portus", - image_version: "2.4", - environment: [ - "CCONFIG_PREFIX: \"PORTUS\"", - "PORTUS_MACHINE_FQDN_VALUE: \"{{ stage_server_url_host }}\"", - "PORTUS_DB_HOST: \"{{ service_name }}-postgres-portus\"", - "PORTUS_DB_DATABASE: \"{{ docker_portus_postgres_database }}\"", - "PORTUS_DB_USERNAME: \"{{ docker_portus_postgres_username }}\"", - "PORTUS_DB_PASSWORD: \"{{ docker_portus_postgres_password }}\"", - "PORTUS_DB_POOL: \"5\"", - "PORTUS_SECRET_KEY_BASE: \"{{ docker_portus_secret_key_base }}\"", - "PORTUS_KEY_PATH: \"/certificates/portus.key\"", - "PORTUS_PASSWORD: \"{{ docker_portus_password }}\"", - "PORTUS_BACKGROUND: \"true\"", - ], - volumes: [ - '"./secrets:/certificates:ro"', - ], - networks: [ - '"back-tier"', - ] - }, - { - name: "{{ service_name }}-postgres-portus", - image_name: "{{ docker_postgres_portus_image_name }}", - image_version: "{{ docker_postgres_portus_image_version }}", - environment: [ - 'POSTGRES_DB: "{{ docker_portus_postgres_database }}"', - 'POSTGRES_USER: "{{ docker_portus_postgres_username }}"', - 'POSTGRES_PASSWORD: "{{ docker_portus_postgres_password }}"', - ], - volumes: [ - '"{{ service_name }}-postgres-portus-data:/var/lib/postgresql/data"', - ], - networks: [ - '"back-tier"', - ], - ports: "{{ docker_registry_postgres_ports | default([]) }}", - }, - { - name: "{{ service_name }}-registry", - image_name: "{{ docker_registry_image_name }}", - image_version: "{{ docker_registry_image_version }}", - command: [ - '"/bin/sh"', - '"/etc/docker/registry/init"', - ], - environment: [ - "REGISTRY_HTTP_SECRET: \"3a025df1-c7df-4c63-9ec4-103ffe3bde42\"", - "REGISTRY_AUTH_TOKEN_REALM: \"{{ stage_server_url }}/v2/token\"", - "REGISTRY_AUTH_TOKEN_SERVICE: \"{{ stage_server_url_host }}\"", - "REGISTRY_AUTH_TOKEN_ISSUER: \"{{ stage_server_url_host }}\"", - "REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE: \"/secrets/portus.crt\"", - "REGISTRY_HTTP_TLS_CERTIFICATE: \"/secrets/portus.crt\"", - "REGISTRY_HTTP_TLS_KEY: \"/secrets/portus.key\"", - ], - volumes: [ - '"{{ service_name }}-registry-data:/var/lib/registry"', - '"./secrets:/secrets:ro"', - '"./registry/init:/etc/docker/registry/init:ro"', - '"./registry/config.yml:/etc/docker/registry/config.yml:ro"', - ], - networks: [ - '"front-tier"' - ], - ports: "{{ docker_registry_ports | default([]) }}", - } - ] -} diff --git a/roles/docker-registry/tasks/main.yml b/roles/docker-registry/tasks/main.yml deleted file mode 100644 index d44ea52..0000000 --- a/roles/docker-registry/tasks/main.yml +++ /dev/null @@ -1,171 +0,0 @@ ---- - -### tags: -### update_deployment - -- name: "Send mattermost message" - uri: - url: "{{ mattermost_hook_smardigo }}" - method: POST - body: "{{ lookup('template','mattermost-deploy-start.json.j2') }}" - body_format: json - headers: - Content-Type: "application/json" - delegate_to: 127.0.0.1 - become: false - when: - - send_status_messages - -- name: Gather current server infos - hcloud_server_info: - api_token: "{{ hetzner_authentication_token }}" - register: hetzner_server_infos - delegate_to: 127.0.0.1 - become: false - -- name: Save current server infos as variable (fact) - set_fact: - hetzner_server_infos_json: "{{ hetzner_server_infos.hcloud_server_info }}" - delegate_to: 127.0.0.1 - become: false - -- name: Read ip for {{ inventory_hostname }} - set_fact: - stage_server_ip: "{{ item.ipv4_address }}" - when: item.name == inventory_hostname - with_items: "{{ hetzner_server_infos_json }}" - delegate_to: 127.0.0.1 - become: false - -- name: "Setup DNS configuration for {{ service_name }}" - include_role: - name: _digitalocean - tasks_from: domain - vars: - record_data: "{{ stage_server_ip }}" - record_name: "{{ service_name }}" - -- name: "Setup public DNS configuration for {{ service_name }}" - include_role: - name: _digitalocean - tasks_from: domain - vars: - record_data: "{{ item.ip }}" - record_name: "{{ item.name }}" - loop: "{{ docker_registry_public_dns_entries }}" - when: docker_registry_public_dns_entries is defined - -- name: "Check docker networks" - include_role: - name: _docker - tasks_from: networks - -- name: "Check if {{ service_name }}/docker-compose.yml exists" - stat: - path: '{{ service_base_path }}/{{ service_name }}/docker-compose.yml' - register: check_docker_compose_file - tags: - - update_deployment - -- name: "Stop {{ service_name }}" - shell: docker-compose down - args: - chdir: '{{ service_base_path }}/{{ service_name }}' - when: check_docker_compose_file.stat.exists - ignore_errors: yes - tags: - - update_deployment - -- name: "Deploy service configuration for {{ service_name }}" - include_role: - name: _deploy - tasks_from: configs - vars: - current_config: "docker-registry" - current_base_path: "{{ service_base_path }}" - current_destination: "{{ service_name }}" - current_owner: "{{ docker_owner }}" - current_group: "{{ docker_group }}" - current_docker: "{{ docker_registry_docker }}" - -- name: "Update {{ service_name }}" - shell: docker-compose pull - args: - chdir: '{{ service_base_path }}/{{ service_name }}' - tags: - - update_deployment - -- name: "Start {{ service_name }}" - shell: docker-compose up -d - args: - chdir: '{{ service_base_path }}/{{ service_name }}' - tags: - - update_deployment - -- name: "Update landing page entries for {{ service_name }}" - include_role: - name: _deploy - tasks_from: caddy_landing_page - vars: - current_services: [ - { - current_name: "{{ service_name }}", - current_url: "{{ http_s }}://{{ service_url }}", - current_version: "{{ docker_registry_image_version }}", - current_date: "{{ ansible_date_time.iso8601 }}", - management: "{{ http_s }}://{{ service_url }}:{{ monitor_port_service }}/management", - }, - ] - tags: - - update_deployment - -- name: "Update landing page with public entries {{ service_name }}" - include_role: - name: _deploy - tasks_from: caddy_landing_page - vars: - current_services: [ - { - current_name: "{{ item.name }}", - current_url: "{{ http_s }}://{{ item.name }}.{{ domain }}", - current_version: "{{ docker_registry_image_version }}", - current_date: "{{ ansible_date_time.iso8601 }}", - management: "{{ http_s }}://{{ service_url }}:{{ monitor_port_service }}/management", - }, - ] - loop: "{{ docker_registry_public_dns_entries }}" - when: docker_registry_public_dns_entries is defined - tags: - - update_deployment - -- name: "Update landing page with extra entries for {{ service_name }}" - include_role: - name: _deploy - tasks_from: caddy_landing_page - vars: - current_services: [ - { - current_name: "{{ item.name }}", - current_url: "{{ item.domain }}", - current_version: "{{ docker_registry_image_version }}", - current_date: "{{ ansible_date_time.iso8601 }}", - management: "{{ http_s }}://{{ service_url }}:{{ monitor_port_service }}/management", - }, - ] - loop: "{{ docker_registry_extra_domain_entries }}" - when: docker_registry_extra_domain_entries is defined - tags: - - update_deployment - -- name: "Send mattermost messsge" - uri: - url: "{{ mattermost_hook_smardigo }}" - method: POST - body: "{{ lookup('template','mattermost-deploy-end.json.j2') }}" - body_format: json - headers: - Content-Type: "application/json" - delegate_to: 127.0.0.1 - become: false - when: - - send_status_messages diff --git a/roles/filebeat/defaults/main.yaml b/roles/filebeat/defaults/main.yaml new file mode 100644 index 0000000..a28d5f4 --- /dev/null +++ b/roles/filebeat/defaults/main.yaml @@ -0,0 +1,30 @@ +--- + +logstash_hostname: "logstash-dev-elastic-stack-01" + +filebeat_image_name: "docker.elastic.co/beats/filebeat" +filebeat_image_version: "7.12.0" + +filebeat_id: "{{ service_name }}-filebeat" + +filebeat_docker: { + services: [ + { + name: "{{ filebeat_id }}", + image_name: "{{ filebeat_image_name }}", + image_version: "{{ filebeat_image_version }}", + user: root, + environment: [ + "node.name: \"{{ filebeat_id }}\"", + ], + volumes: [ + '"./config/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro"', + '"/var/lib/docker/containers/:/var/lib/docker/containers/:ro"', + '"/var/run/docker.sock:/var/run/docker.sock:ro"', + '"/var/log/:/var/log/:ro"', + '"./certs:/usr/share/filebeat/config/certificates:ro"', + ], + extra_hosts: "{{ filebeat_extra_hosts | default([]) }}", + }, + ], +} \ No newline at end of file diff --git a/roles/docker-registry/handlers/main.yml b/roles/filebeat/handlers/main.yml similarity index 100% rename from roles/docker-registry/handlers/main.yml rename to roles/filebeat/handlers/main.yml diff --git a/roles/docker-registry/meta/main.yml b/roles/filebeat/meta/main.yml similarity index 100% rename from roles/docker-registry/meta/main.yml rename to roles/filebeat/meta/main.yml diff --git a/roles/filebeat/tasks/main.yaml b/roles/filebeat/tasks/main.yaml new file mode 100644 index 0000000..9f42148 --- /dev/null +++ b/roles/filebeat/tasks/main.yaml @@ -0,0 +1,63 @@ +--- + +- name: "Send mattermost messsge" + uri: + url: "{{ mattermost_hook_smardigo }}" + method: POST + body: "{{ lookup('template','mattermost-deploy-start.json.j2') }}" + body_format: json + headers: + Content-Type: "application/json" + delegate_to: 127.0.0.1 + become: false + when: + - send_status_messages + +- name: "Check if {{ role_name }}/docker-compose.yml exists" + stat: + path: '{{ service_base_path }}/{{ role_name }}/docker-compose.yml' + register: check_docker_compose_file + +- name: "Stop {{ role_name }}" + shell: docker-compose down + args: + chdir: '{{ service_base_path }}/{{ role_name }}' + when: check_docker_compose_file.stat.exists + ignore_errors: yes + +- name: "Deploy service configuration for {{ role_name }}" + include_role: + name: _deploy + tasks_from: configs + vars: + current_config: "filebeat" + current_base_path: "{{ service_base_path }}" + current_destination: "filebeat" + current_owner: "{{ docker_owner }}" + current_group: "{{ docker_group }}" + current_docker: "{{ filebeat_docker }}" + +- name: "Update {{ role_name }}" + shell: docker-compose pull + args: + chdir: '{{ service_base_path }}/{{ role_name }}' + tags: + - update_deployment + +- name: "Start {{ role_name }}" + shell: docker-compose up -d + args: + chdir: '{{ service_base_path }}/{{ role_name }}' + +- name: "Send mattermost messsge" + uri: + url: "{{ mattermost_hook_smardigo }}" + method: POST + body: "{{ lookup('template','mattermost-deploy-end.json.j2') }}" + body_format: json + headers: + Content-Type: "application/json" + delegate_to: 127.0.0.1 + become: false + when: + - send_status_messages diff --git a/roles/hcloud/tasks/configure-firewall.yml b/roles/hcloud/tasks/configure-firewall.yml new file mode 100644 index 0000000..03b69ea --- /dev/null +++ b/roles/hcloud/tasks/configure-firewall.yml @@ -0,0 +1,68 @@ +--- + +### tags: + +- name: Get all Firewalls from Hetzner + uri: + url: "https://api.hetzner.cloud/v1/firewalls" + headers: + accept: application/json + authorization: Bearer {{ hetzner_authentication_token }} + return_content: yes + register: hetzner_firewalls_response + delegate_to: 127.0.0.1 + run_once: true + +- name: Save firewall entries as variable (fact) + set_fact: + hetzner_firewalls_response_json: "{{ hetzner_firewalls_response.json }}" + run_once: true + +- name: Parse firewall entries + set_fact: + firewall_records: "{{ hetzner_firewalls_response_json.firewalls | json_query(jmesquery) }}" + vars: + jmesquery: '[*].{id: id, name: name}' + run_once: true + +- name: Print firewall entries + debug: + msg: "{{ firewall_records }}" + run_once: true + +- name: Read firewall entry for {{ current_firewall_name }} + set_fact: + firewall_record: "{{ firewall_records | selectattr('name', 'equalto', current_firewall_name) | list | first | default({'name': '-', 'id': '-'}) }}" + +- name: Print firewall entry for {{ current_firewall_name }} + debug: + msg: "{{ firewall_record }}" + +- name: Save firewall entry {{ current_firewall_name }} + uri: + method: POST + url: "https://api.hetzner.cloud/v1/firewalls" + body_format: json + body: "{{ lookup('template','firewall-{{ current_firewall_name }}.json.j2') }}" + headers: + accept: application/json + authorization: Bearer {{ hetzner_authentication_token }} + return_content: yes + status_code: 201 + when: firewall_records | selectattr("name", "equalto", current_firewall_name) | list | length == 0 + delegate_to: 127.0.0.1 + +# TODO port changes are not written corectly +- name: Update firewall entry {{ current_firewall_name }} + uri: + method: PUT + url: "https://api.hetzner.cloud/v1/firewalls/{{ firewall_record.id }}" + body_format: json + body: "{{ lookup('template','firewall-{{ current_firewall_name }}.json.j2') }}" + headers: + accept: application/json + authorization: Bearer {{ hetzner_authentication_token }} + return_content: yes + status_code: 200 + when: firewall_records | selectattr("name", "equalto", current_firewall_name) | list | length == 1 + delegate_to: 127.0.0.1 diff --git a/roles/hcloud/tasks/main.yml b/roles/hcloud/tasks/main.yml index 260762b..bc961bc 100644 --- a/roles/hcloud/tasks/main.yml +++ b/roles/hcloud/tasks/main.yml @@ -2,57 +2,16 @@ ### tags: -- name: Get all Firewalls from Hetzner - uri: - url: "https://api.hetzner.cloud/v1/firewalls" - headers: - accept: application/json - authorization: Bearer {{ hetzner_authentication_token }} - return_content: yes - register: hetzner_firewalls_response - delegate_to: 127.0.0.1 - -- name: Save firewall entries as variable (fact) - set_fact: - hetzner_firewalls_response_json: "{{ hetzner_firewalls_response.json }}" - -- name: Parse firewall entry for default - set_fact: - firewall_record: "{{ hetzner_firewalls_response_json.firewalls | json_query(jmesquery) | first | default({'name': '-', 'id': '-'}) }}" +- name: "Setup firewall" + include_tasks: configure-firewall.yml vars: - jmesquery: '[*].{id: id, name: name}' - -- name: Print firewall entry for default - debug: - msg: "{{ firewall_record }}" - -- name: Save firewall entry default - uri: - method: POST - url: "https://api.hetzner.cloud/v1/firewalls" - body_format: json - body: "{{ lookup('template','firewall-default.json.j2') }}" - headers: - accept: application/json - authorization: Bearer {{ hetzner_authentication_token }} - return_content: yes - status_code: 201 - when: firewall_record.id == '-' - delegate_to: 127.0.0.1 - -- name: Update firewall entry default - uri: - method: PUT - url: "https://api.hetzner.cloud/v1/firewalls/{{ firewall_record.id }}" - body_format: json - body: "{{ lookup('template','firewall-default.json.j2') }}" - headers: - accept: application/json - authorization: Bearer {{ hetzner_authentication_token }} - return_content: yes - status_code: 200 - when: firewall_record.id != '-' - delegate_to: 127.0.0.1 + current_firewall_name: '{{ current_firewall }}' + with_items: + - 'default' + - 'kibana' + - 'monitoring' + loop_control: + loop_var: current_firewall - name: Create new server {{ inventory_hostname }} hetzner.hcloud.hcloud_server: @@ -65,17 +24,6 @@ state: present delegate_to: 127.0.0.1 -- name: Gather current server infos - hcloud_server_info: - api_token: "{{ hetzner_authentication_token }}" - register: hetzner_server_infos - delegate_to: 127.0.0.1 - -- name: Save current server infos as variable (fact) - set_fact: - hetzner_server_infos_json: "{{ hetzner_server_infos.hcloud_server_info }}" - delegate_to: 127.0.0.1 - - name: Read ip for {{ inventory_hostname }} set_fact: stage_server_ip: "{{ item.ipv4_address }}" diff --git a/roles/hcloud/templates/firewall-default.json.j2 b/roles/hcloud/templates/firewall-default.json.j2 index 53ab8b8..0a33e5c 100644 --- a/roles/hcloud/templates/firewall-default.json.j2 +++ b/roles/hcloud/templates/firewall-default.json.j2 @@ -19,8 +19,10 @@ "protocol": "tcp", "port": "22", "source_ips": [ + "149.233.6.129/32", "212.121.131.106/32", - "5.9.148.23/32" + "5.9.148.23/32", + "87.150.34.206/32" ], "destination_ips": [ ] @@ -30,8 +32,10 @@ "protocol": "tcp", "port": "80", "source_ips": [ - "0.0.0.0/0", - "::/0" + "149.233.6.129/32", + "212.121.131.106/32", + "5.9.148.23/32", + "87.150.34.206/32" ], "destination_ips": [ ] @@ -41,19 +45,10 @@ "protocol": "tcp", "port": "443", "source_ips": [ - "0.0.0.0/0", - "::/0" - ], - "destination_ips": [ - ] - }, - { - "direction": "in", - "protocol": "tcp", - "port": "9080-9085", - "source_ips": [ + "149.233.6.129/32", "212.121.131.106/32", - "5.9.148.23/32" + "5.9.148.23/32", + "87.150.34.206/32" ], "destination_ips": [ ] diff --git a/roles/hcloud/templates/firewall-kibana.json.j2 b/roles/hcloud/templates/firewall-kibana.json.j2 new file mode 100644 index 0000000..401005b --- /dev/null +++ b/roles/hcloud/templates/firewall-kibana.json.j2 @@ -0,0 +1,22 @@ +{ + "name": "kibana", + "labels": { + }, + "rules": [ + { + "direction": "in", + "protocol": "tcp", + "port": "5601", + "source_ips": [ + "149.233.6.129/32", + "212.121.131.106/32", + "5.9.148.23/32", + "87.150.34.206/32" + ], + "destination_ips": [ + ] + } + ], + "applied_to": [ + ] +} diff --git a/roles/hcloud/templates/firewall-monitoring.json.j2 b/roles/hcloud/templates/firewall-monitoring.json.j2 new file mode 100644 index 0000000..2041770 --- /dev/null +++ b/roles/hcloud/templates/firewall-monitoring.json.j2 @@ -0,0 +1,21 @@ +{ + "name": "monitoring", + "labels": { + }, + "rules": [ + { + "direction": "in", + "protocol": "tcp", + "port": "9080-9085", + "source_ips": [ + "212.121.131.106/32", + "87.150.34.206/32", + "94.130.97.253/32" + ], + "destination_ips": [ + ] + } + ], + "applied_to": [ + ] +} diff --git a/roles/keycloak/defaults/main.yml b/roles/keycloak/defaults/main.yml new file mode 100644 index 0000000..a7cb886 --- /dev/null +++ b/roles/keycloak/defaults/main.yml @@ -0,0 +1,104 @@ +--- + +# TODO doesn't bind to local port (currently used by setup keycloak with ansible) +service_port_keycloak_external: "8110" + +keycloak_version: "12.0.4" +keycloak_admin_username: "keycloak-admin" +keycloak_admin_password: "keycloak-admin" + +keycloak_postgres_version: "12" +keycloak_postgres_database: "keycloak-postgres" +keycloak_postgres_admin_username: "keycloak-postgres-admin" +keycloak_postgres_admin_password: "keycloak-postgres-admin" + +keycloak_id: "{{ service_name }}-keycloak" +keycloak_postgres_id: "{{ service_name }}-postgres-keycloak" + +keycloak_labels: [ + '"traefik.enable=true"', + '"traefik.http.routers.{{ keycloak_id }}.service={{ keycloak_id }}"', + '"traefik.http.routers.{{ keycloak_id }}.rule=Host(`{{ stage_server_url_host }}`)"', + '"traefik.http.routers.{{ keycloak_id }}.entrypoints=websecure"', + '"traefik.http.routers.{{ keycloak_id }}.tls=true"', + '"traefik.http.routers.{{ keycloak_id }}.tls.certresolver=letsencrypt"', + '"traefik.http.services.{{ keycloak_id }}.loadbalancer.server.port={{ service_port }}"', + + '"traefik.http.routers.{{ keycloak_id }}-monitor.service={{ service_name }}-node-exporter"', + '"traefik.http.routers.{{ keycloak_id }}-monitor.rule=Host(`{{ stage_server_url_host }}`)"', + '"traefik.http.routers.{{ keycloak_id }}-monitor.entrypoints=admin-system"', + '"traefik.http.routers.{{ keycloak_id }}-monitor.tls=true"', + '"traefik.http.routers.{{ keycloak_id }}-monitor.tls.certresolver=letsencrypt"', +] + +keycloak_docker: { + networks: [ + { + name: back-tier, + external: true, + }, + { + name: front-tier, + external: true, + }, + ], + volumes: [ + { + name: "{{ keycloak_postgres_id }}-data" + } + ], + services: [ + { + name: "{{ keycloak_id }}", + image_name: "jboss/keycloak", + image_version: "{{ keycloak_version }}", + labels: "{{ keycloak_labels + ( keycloak_labels_additional | default([])) }}", + environment: [ + "PROXY_ADDRESS_FORWARDING: \"true\"", + + "KEYCLOAK_USER: \"{{ keycloak_admin_username }}\"", + "KEYCLOAK_PASSWORD: \"{{ keycloak_admin_password }}\"", + + "DB_VENDOR: postgres", + "DB_DATABASE: \"{{ keycloak_postgres_database }}\"", + "DB_USER: \"{{ keycloak_postgres_admin_username }}\"", + "DB_PASSWORD: \"{{ keycloak_postgres_admin_password }}\"", + "DB_ADDR: \"{{ keycloak_postgres_id }}\"", + + "JAVA_OPTS_APPEND: \"-Dkeycloak.profile.feature.docker=enabled\"", + ], + volumes: [ + '"./eden-theme:/opt/jboss/keycloak/themes/eden-theme:ro"', + '"./smardigo-theme:/opt/jboss/keycloak/themes/smardigo-theme:ro"', + ], + networks: [ + '"back-tier"', + '"front-tier"', + ], + ports: [ + { + external: "{{ service_port_keycloak_external }}", + internal: "{{ service_port_keycloak }}", + }, + ], + extra_hosts: "{{ connect_extra_hosts | default([]) }}", + }, + { + name: "{{ keycloak_postgres_id }}", + image_name: "postgres", + image_version: "{{ keycloak_postgres_version }}", + environment: [ + 'POSTGRES_DB: "{{ keycloak_postgres_database }}"', + 'POSTGRES_USER: "{{ keycloak_postgres_admin_username }}"', + 'POSTGRES_PASSWORD: "{{ keycloak_postgres_admin_password }}"', + ], + volumes: [ + '"{{ keycloak_postgres_id }}-data:/var/lib/postgresql/data"', + ], + networks: [ + '"back-tier"', + ], + ports: "{{ keycloak_postgres_ports | default([]) }}", + }, + ], +} \ No newline at end of file diff --git a/roles/docker-registry/vars/main.yml b/roles/keycloak/handlers/main.yml similarity index 100% rename from roles/docker-registry/vars/main.yml rename to roles/keycloak/handlers/main.yml diff --git a/roles/keycloak/meta/main.yml b/roles/keycloak/meta/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/keycloak/meta/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/keycloak/tasks/configure_client.yml b/roles/keycloak/tasks/configure_client.yml new file mode 100644 index 0000000..6acecf6 --- /dev/null +++ b/roles/keycloak/tasks/configure_client.yml @@ -0,0 +1,21 @@ +--- + +#- name: Print client {{ client_id }} for realm {{ realm_name }} +# debug: +# msg: "{{ lookup('template','keycloak-realm-create-client.json.j2') }}" +# when: realm_client_ids | selectattr('clientId', 'equalto', client_id) | list | length == 0 +# tags: +# - update_realms + +- name: Create client {{ client_id }} for realm {{ realm_name }} + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms/{{ realm_name }}/clients + method: POST + body_format: json + body: "{{ lookup('template','keycloak-realm-create-client.json.j2') }}" + headers: + Authorization: "Bearer {{ access_token}} " + status_code: [201] + when: realm_client_ids | selectattr('clientId', 'equalto', client_id) | list | length == 0 + tags: + - update_realms diff --git a/roles/keycloak/tasks/configure_realm.yml b/roles/keycloak/tasks/configure_realm.yml new file mode 100644 index 0000000..75812de --- /dev/null +++ b/roles/keycloak/tasks/configure_realm.yml @@ -0,0 +1,119 @@ +--- + +- name: Read realms + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms + method: GET + headers: + Authorization: "Bearer {{ access_token}} " + status_code: [200] + register: realms + tags: + - update_realms + +#- name: Print realms +# debug: +# msg: "{{ realms }}" +# tags: +# - update_realms + +- name: Save realms as variable (fact) + set_fact: + realms_json: "{{ realms.json }}" + tags: + - update_realms + +- name: Read realm ids + set_fact: + realm_ids: "{{ realms_json | json_query(jmesquery) }}" + vars: + jmesquery: '[*].id' + tags: + - update_realms + +- name: Create realm {{ current_realm_name }} + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms + method: POST + body_format: json + body: "{{ lookup('template','keycloak-realm-create.json.j2') }}" + headers: + Authorization: "Bearer {{ access_token}} " + status_code: [201] + when: current_realm_name not in realm_ids + tags: + - update_realms + +- name: Read clients from realm {{ current_realm_name }} + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms/{{ current_realm_name }}/clients + method: GET + headers: + Authorization: "Bearer {{ access_token}} " + status_code: [200] + register: realm_clients + tags: + - update_realms + +#- name: Print clients from realm {{ current_realm_name }} +# debug: +# msg: "{{ realm_clients }}" +# tags: +# - update_realms + +- name: Save clients from realm as variable (fact) + set_fact: + realm_clients_json: "{{ realm_clients.json }}" + tags: + - update_realms + +- name: Save client ids from realm {{ current_realm_name }} + set_fact: + realm_client_ids: "{{ realm_clients_json | json_query(jmesquery) }}" + vars: + jmesquery: '[*].{id: id, clientId: clientId}' + tags: + - update_realms + +- name: Print client ids + debug: + msg: "{{ realm_client_ids }}" + tags: + - update_realms + +- name: Create clients from realm {{ current_realm_name }} + include_tasks: configure_client.yml + vars: + realm_name: '{{ current_realm_name }}' + client_id: '{{ client.clientId }}' + client_name: '{{ client.name }}' + admin_url: '{{ client.admin_url }}' + root_url: '{{ client.root_url }}' + redirect_uris: '{{ client.redirect_uris }}' + secret: '{{ client.secret }}' + web_origins: '{{ client.web_origins }}' + access_token: '{{ keycloak_authentication.json.access_token }}' + with_items: "{{ current_realm_clients }}" + loop_control: + loop_var: client + tags: + - update_realms + +- name: Create realm {{ current_realm_name }} LDAP user storage provider + include_tasks: configure_user_storage_provider_ldap.yml + vars: + realm: '{{ current_realm_name }}' + provider_name: '{{ provider.name }}' + usersDn: '{{ provider.usersDn }}' + ldap_username: '{{ provider.username }}' + ldap_password: '{{ provider.password }}' + ldap_connection_url: '{{ provider.connection_url }}' + ldap_username_attribute: '{{ provider.username_attribute }}' + custom_user_search_filter: '{{ provider.custom_user_search_filter }}' + search_scope: '{{ provider.search_scope }}' + access_token: '{{ keycloak_authentication.json.access_token }}' + with_items: "{{ current_realm_ldaps }}" + loop_control: + loop_var: provider + tags: + - update_realms diff --git a/roles/keycloak/tasks/configure_user_storage_provider_ldap.yml b/roles/keycloak/tasks/configure_user_storage_provider_ldap.yml new file mode 100644 index 0000000..3492d27 --- /dev/null +++ b/roles/keycloak/tasks/configure_user_storage_provider_ldap.yml @@ -0,0 +1,107 @@ +- name: Create ldap user storage provider in realm {{ realm }} + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms/{{ realm }}/components + method: POST + body_format: json + body: '{ + "name": "{{ provider_name }}", + "providerId": "ldap", + "providerType": "org.keycloak.storage.UserStorageProvider", + "parentId": "{{ realm }}", + "config": { + "allowKerberosAuthentication": ["false"], + "authType": ["simple"], + "batchSizeForSync": ["1000"], + "bindCredential": ["{{ ldap_password }}"], + "bindDn": ["{{ ldap_username }}"], + "cachePolicy": ["DEFAULT"], + "changedSyncPeriod": ["86400"], + "connectionPooling": ["true"], + "connectionUrl": ["{{ ldap_connection_url }}"], + "customUserSearchFilter": ["{{ custom_user_search_filter }}"], + "debug": ["false"], + "editMode": ["READ_ONLY"], + "enabled": ["true"], + "fullSyncPeriod": ["604800"], + "importEnabled": ["true"], + "pagination": ["true"], + "priority": ["0"], + "rdnLDAPAttribute": ["cn"], + "searchScope": ["{{ search_scope }}"], + "syncRegistrations": ["false"], + "trustEmail": ["false"], + "useKerberosForPasswordAuthentication": ["false"], + "usernameLDAPAttribute": ["{{ ldap_username_attribute }}"], + "userObjectClasses": ["person, organizationalPerson, user"], + "usersDn": ["{{ usersDn }}"], + "useTruststoreSpi": ["ldapsOnly"], + "uuidLDAPAttribute": ["objectGUID"], + "validatePasswordPolicy": ["false"], + "vendor": ["ad"] + } + }' + status_code: [201] + headers: + Authorization: "Bearer {{ access_token }}" + register: response + tags: + - update_realms + +- name: Get id of created user storage provider + uri: + url: "{{ response.location }}" + method: GET + headers: + Authorization: "Bearer {{ access_token }}" + register: response + tags: + - update_realms + +- name: Create user attribute mapper for firstName + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms/{{ realm }}/components + method: POST + body_format: json + body: '{ + "name": "first name", + "providerId": "user-attribute-ldap-mapper", + "providerType": "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "parentId": "{{ response.json.id }}", + "config": { + "ldap.attribute": ["givenName"], + "is.mandatory.in.ldap": ["false"], + "is.binary.attribute": ["false"], + "read.only": ["true"], + "always.read.value.from.ldap": ["false"], + "user.model.attribute": ["firstName"] + } + }' + headers: + Authorization: "Bearer {{ access_token }}" + status_code: [201] + tags: + - update_realms + +- name: Create user role mappers + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms/{{ realm }}/components + method: POST + body_format: json + body: '{ + "name": "{{ role.name }}", + "providerId": "hardcoded-ldap-role-mapper", + "providerType": "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "config": { + "role": ["{{ role.role_id }}"], + }, + "parentId": "{{ response.json.id }}", + }' + headers: + Authorization: "Bearer {{ access_token }}" + status_code: [201] + when: hardcoded_user_roles is defined + with_items: "{{ hardcoded_user_roles }}" + loop_control: + loop_var: role + tags: + - update_realms \ No newline at end of file diff --git a/roles/keycloak/tasks/create_realm_users.yml b/roles/keycloak/tasks/create_realm_users.yml new file mode 100644 index 0000000..8a2d5cd --- /dev/null +++ b/roles/keycloak/tasks/create_realm_users.yml @@ -0,0 +1,61 @@ +--- + +- name: Read users of realm {{ current_realm_name }} + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms/{{ current_realm_name }}/users + method: GET + headers: + Authorization: "Bearer {{ access_token}} " + status_code: [200] + register: realm_users + tags: + - create_users + - update_realms + +#- name: Print realm users +# debug: +# msg: "{{ realm_users }}" +# tags: +# - create_users +# - update_realms + +- name: Save realm users as variable (fact) + set_fact: + realm_users_json: "{{ realm_users.json }}" + tags: + - create_users + - update_realms + +- name: Read realm user ids + set_fact: + realm_user_usernames: "{{ realm_users_json | json_query(jmesquery) }}" + vars: + jmesquery: '[*].username' + tags: + - create_users + - update_realms + +#- name: Print realm usernames +# debug: +# msg: "{{ realm_user_usernames }}" +# tags: +# - create_users +# - update_realms + +- name: "Create users for realm {{ current_realm_name }}" + uri: + url: http://localhost:{{ service_port_keycloak_external }}/auth/admin/realms/{{ current_realm_name }}/users + method: POST + body_format: json + body: "{{ lookup('template','keycloak-realm-create-user.json.j2') }}" + headers: + Content-Type: "application/json" + Authorization: "Bearer {{ access_token }}" + status_code: [201] + with_items: "{{ current_realm_users }}" + when: current_realm_user.username not in realm_user_usernames + loop_control: + loop_var: current_realm_user + tags: + - create_users + - update_realms diff --git a/roles/keycloak/tasks/main.yml b/roles/keycloak/tasks/main.yml new file mode 100644 index 0000000..7b9e675 --- /dev/null +++ b/roles/keycloak/tasks/main.yml @@ -0,0 +1,160 @@ +--- + +### tags: +### create_users +### update_realms +### update_deployment + +- name: "Send mattermost messsge" + uri: + url: "{{ mattermost_hook_smardigo }}" + method: POST + body: "{{ lookup('template','mattermost-deploy-start.json.j2') }}" + body_format: json + headers: + Content-Type: "application/json" + delegate_to: 127.0.0.1 + become: false + when: + - send_status_messages + +- name: "Setup DNS configuration for {{ service_name }}" + include_role: + name: _digitalocean + tasks_from: domain + vars: + record_data: "{{ stage_server_ip }}" + record_name: "{{ service_name }}" + +- name: "Check if {{ service_name }}/docker-compose.yml exists" + stat: + path: '{{ service_base_path }}/{{ service_name }}/docker-compose.yml' + register: check_docker_compose_file + tags: + - update_deployment + +- name: "Stop {{ service_name }}" + shell: docker-compose down + args: + chdir: '{{ service_base_path }}/{{ service_name }}' + when: check_docker_compose_file.stat.exists + ignore_errors: yes + tags: + - update_deployment + +- name: "Deploy service configuration for {{ service_name }}" + include_role: + name: _deploy + tasks_from: configs + vars: + current_config: "keycloak" + current_base_path: "{{ service_base_path }}" + current_destination: "{{ service_name }}" + current_owner: "{{ docker_owner }}" + current_group: "{{ docker_group }}" + current_docker: "{{ keycloak_docker }}" + +- name: "Update {{ service_name }}" + shell: docker-compose pull + args: + chdir: '{{ service_base_path }}/{{ service_name }}' + tags: + - update_deployment + +- name: "Start {{ service_name }}" + shell: docker-compose up -d + args: + chdir: '{{ service_base_path }}/{{ service_name }}' + tags: + - update_deployment + +- name: "Update landing page for {{ service_name }}" + include_role: + name: _deploy + tasks_from: caddy_landing_page + vars: + current_services: [ + { + current_name: "{{ service_name }}", + current_url: "{{ http_s }}://{{ keycloak_id }}.{{ domain }}", + current_version: "{{ keycloak_version }}", + current_date: "{{ ansible_date_time.iso8601 }}", + }, + ] + tags: + - update_deployment + +- name: "Wait for {{ service_port_keycloak_external }}" + wait_for: + port: '{{ service_port_keycloak_external }}' + delay: 60 + +- name: "Authenticate with Keycloak server" + uri: + url: "http://localhost:{{ service_port_keycloak_external }}/auth/realms/master/protocol/openid-connect/token" + method: POST + body_format: form-urlencoded + body: 'username={{ keycloak_admin_username }}&password={{ keycloak_admin_password }}&client_id=admin-cli&grant_type=password' + retries: 5 + delay: 5 + register: keycloak_authentication + tags: + - create_users + - update_realms + +- name: "Create user storage provider in master realm" + include_tasks: configure_user_storage_provider_ldap.yml + vars: + access_token: "{{ keycloak_authentication.json.access_token }}" + realm: master + provider_name: '{{ item.name }}' + ldap_username: '{{ item.username }}' + ldap_password: '{{ item.password }}' + ldap_connection_url: '{{ item.connection_url }}' + ldap_username_attribute: '{{ item.username_attribute }}' + usersDn: '{{ item.usersDn }}' + custom_user_search_filter: '{{ item.custom_user_search_filter }}' + search_scope: '{{ item.search_scope }}' + hardcoded_user_roles: '{{ item.hardcoded_user_roles }}' + with_items: "{{ keycloak.master.ldap | default([]) }}" + when: keycloak.master is defined + tags: + - update_realms + +- name: "Setup realms" + include_tasks: configure_realm.yml + vars: + current_realm_name: '{{ current_realm.name }}' + current_realm_display_name: '{{ current_realm.display_name }}' + current_realm_clients: '{{ current_realm.clients | default([]) }}' + current_realm_ldaps: '{{ current_realm.ldaps | default([]) }}' + access_token: "{{ keycloak_authentication.json.access_token }}" + with_items: "{{ keycloak.realms }}" + loop_control: + loop_var: current_realm + tags: + - update_realms + +- name: "Create realm users" + include_tasks: create_realm_users.yml + vars: + current_realm_name: "{{ item.name }}" + current_realm_users: "{{ item.users | default([]) }}" + access_token: "{{ keycloak_authentication.json.access_token }}" + with_items: "{{ keycloak.realms }}" + tags: + - create_users + - update_realms + +- name: "Send mattermost messsge" + uri: + url: "{{ mattermost_hook_smardigo }}" + method: POST + body: "{{ lookup('template','mattermost-deploy-end.json.j2') }}" + body_format: json + headers: + Content-Type: "application/json" + delegate_to: 127.0.0.1 + become: false + when: + - send_status_messages \ No newline at end of file diff --git a/roles/keycloak/templates/keycloak-realm-create-client.json.j2 b/roles/keycloak/templates/keycloak-realm-create-client.json.j2 new file mode 100644 index 0000000..b815645 --- /dev/null +++ b/roles/keycloak/templates/keycloak-realm-create-client.json.j2 @@ -0,0 +1,63 @@ +{ + "adminUrl": "{{ admin_url }}", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "authorizationServicesEnabled": true, + "bearerOnly": false, + "clientAuthenticatorType": "client-secret", + "clientId": "{{ client_id }}", + "consentRequired": false, + "defaultClientScopes": [ + "role_list", + "profile", + "roles", + "email" + ], + "directAccessGrantsEnabled": true, + "enabled": true, + "frontchannelLogout": false, + "fullScopeAllowed": true, + "implicitFlowEnabled": false, + "name": "{{ client_name }}", + "nodeReRegistrationTimeout": -1, + "notBefore": 0, + "optionalClientScopes": [], + "protocol" : "{{ protocol | default('openid-connect') }}", + "protocolMappers": [ + { + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "username", + "claim.name": "sub", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ], + "publicClient": false, + "redirectUris": {{ redirect_uris }}, + "rootUrl": "{{ root_url }}", + "secret": "{{ secret }}", + "serviceAccountsEnabled": true, + "standardFlowEnabled": true, + "surrogateAuthRequired": false, + "webOrigins": {{ web_origins }} +} \ No newline at end of file diff --git a/roles/keycloak/templates/keycloak-realm-create-user.json.j2 b/roles/keycloak/templates/keycloak-realm-create-user.json.j2 new file mode 100644 index 0000000..74009a3 --- /dev/null +++ b/roles/keycloak/templates/keycloak-realm-create-user.json.j2 @@ -0,0 +1,12 @@ +{ + "username": "{{ current_realm_user.username }}", + "firstName": "{{ current_realm_user.firstName | default('') }}", + "lastName": "{{ current_realm_user.lastName | default('') }}", + "email": "{{ current_realm_user.email | default('') }}", + "enabled": true, + "credentials" : [{ + "type": "password", + "value": "{{ current_realm_user.password }}", + "temporary": false + }] +} \ No newline at end of file diff --git a/roles/keycloak/templates/keycloak-realm-create.json.j2 b/roles/keycloak/templates/keycloak-realm-create.json.j2 new file mode 100644 index 0000000..1aa800e --- /dev/null +++ b/roles/keycloak/templates/keycloak-realm-create.json.j2 @@ -0,0 +1,133 @@ +{ + "id": "{{ current_realm_name }}", + "realm": "{{ current_realm_name }}", + "displayName": "{{ current_realm_display_name }}", + "displayNameHtml": "", + "notBefore": 0, + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 60, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "enabled": true, + "sslRequired": "none", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": false, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "defaultRoles": [ + "uma_authorization", + "offline_access" + ], + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [ + ], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [ + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": { + }, + "loginTheme": "smardigo-theme", + "accountTheme": "smardigo-theme", + "adminTheme": "smardigo-theme", + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [ + ], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [ + ], + "identityProviderMappers": [ + ], + "internationalizationEnabled": true, + "supportedLocales": [ + "de", + "en" + ], + "defaultLocale": "de", + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0" + }, + "userManagedAccessAllowed": false +} diff --git a/roles/keycloak/vars/main.yml b/roles/keycloak/vars/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/keycloak/vars/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/node-exporter/defaults/main.yml b/roles/node-exporter/defaults/main.yml index 56dd6f4..011fb62 100644 --- a/roles/node-exporter/defaults/main.yml +++ b/roles/node-exporter/defaults/main.yml @@ -49,7 +49,7 @@ node_exporter_docker: { labels: [ '"traefik.enable=true"', '"traefik.http.routers.{{ node_exporter_id }}.service={{ node_exporter_id }}"', - '"traefik.http.routers.{{ node_exporter_id }}.rule=Host(`{{ node_exporter_id }}.{{ domain }}`)"', + '"traefik.http.routers.{{ node_exporter_id }}.rule=Host(`{{ service_name }}.{{ domain }}`)"', '"traefik.http.routers.{{ node_exporter_id }}.entrypoints=admin-system"', '"traefik.http.routers.{{ node_exporter_id }}.tls=true"', '"traefik.http.routers.{{ node_exporter_id }}.tls.certresolver=letsencrypt"', diff --git a/roles/node-exporter/tasks/main.yml b/roles/node-exporter/tasks/main.yml index 18a53ca..fc15709 100644 --- a/roles/node-exporter/tasks/main.yml +++ b/roles/node-exporter/tasks/main.yml @@ -13,24 +13,19 @@ when: - send_status_messages -- name: "Check docker networks" - include_role: - name: _docker - tasks_from: networks - -- name: "Check if node-exporter/docker-compose.yml exists" +- name: "Check if {{ role_name }}/docker-compose.yml exists" stat: - path: '{{ service_base_path }}/node-exporter/docker-compose.yml' + path: '{{ service_base_path }}/{{ role_name }}/docker-compose.yml' register: check_docker_compose_file -- name: "Stop node-exporter" +- name: "Stop {{ role_name }}" shell: docker-compose down args: - chdir: '{{ service_base_path }}/node-exporter' + chdir: '{{ service_base_path }}/{{ role_name }}' when: check_docker_compose_file.stat.exists ignore_errors: yes -- name: "Deploy service configuration for node-exporter" +- name: "Deploy service configuration for {{ role_name }}" include_role: name: _deploy tasks_from: configs @@ -42,10 +37,17 @@ current_group: "{{ docker_group }}" current_docker: "{{ node_exporter_docker }}" -- name: "Start node-exporter" +- name: "Update {{ role_name }}" + shell: docker-compose pull + args: + chdir: '{{ service_base_path }}/{{ role_name }}' + tags: + - update_deployment + +- name: "Start {{ role_name }}" shell: docker-compose up -d args: - chdir: '{{ service_base_path }}/node-exporter' + chdir: '{{ service_base_path }}/{{ role_name }}' - name: "Send mattermost messsge" uri: diff --git a/roles/prometheus/tasks/main.yml b/roles/prometheus/tasks/main.yml index 505a2c7..29a091d 100644 --- a/roles/prometheus/tasks/main.yml +++ b/roles/prometheus/tasks/main.yml @@ -16,27 +16,6 @@ when: - send_status_messages -- name: Gather current server infos - hcloud_server_info: - api_token: "{{ hetzner_authentication_token }}" - register: hetzner_server_infos - delegate_to: 127.0.0.1 - become: false - -- name: Save current server infos as variable (fact) - set_fact: - hetzner_server_infos_json: "{{ hetzner_server_infos.hcloud_server_info }}" - delegate_to: 127.0.0.1 - become: false - -- name: Read ip for {{ inventory_hostname }} - set_fact: - stage_server_ip: "{{ item.ipv4_address }}" - when: item.name == inventory_hostname - with_items: "{{ hetzner_server_infos_json }}" - delegate_to: 127.0.0.1 - become: false - - name: "Setup DNS configuration for {{ service_name }} prometheus" include_role: name: _digitalocean @@ -61,11 +40,6 @@ record_data: "{{ stage_server_ip }}" record_name: "{{ service_name }}-alertmanager" -- name: "Check docker networks" - include_role: - name: _docker - tasks_from: networks - - name: "Check if {{ service_name }}/docker-compose.yml exists" stat: path: '{{ service_base_path }}/{{ service_name }}/docker-compose.yml' @@ -110,19 +84,19 @@ vars: current_services: [ { - current_name: "{{ service_prefix }}prometheus", + current_name: "prometheus", current_url: "{{ http_s}}://{{ service_name }}-prometheus.{{ domain }}", current_version: "{{ prometheus_version }}", current_date: "{{ ansible_date_time.iso8601 }}", }, { - current_name: "{{ service_prefix }}grafana", + current_name: "grafana", current_url: "{{ http_s }}://{{ service_name }}-grafana.{{ domain }}", current_version: "{{ grafana_version }}", current_date: "{{ ansible_date_time.iso8601 }}", }, { - current_name: "{{ service_prefix }}alertmanager", + current_name: "alertmanager", current_url: "{{ http_s }}://{{ service_name }}-alertmanager.{{ domain }}", current_version: "{{ alertmanager_version }}", current_date: "{{ ansible_date_time.iso8601 }}", diff --git a/roles/traefik/tasks/main.yml b/roles/traefik/tasks/main.yml index b47974f..20a4dd1 100644 --- a/roles/traefik/tasks/main.yml +++ b/roles/traefik/tasks/main.yml @@ -13,24 +13,19 @@ when: - send_status_messages -- name: "Check docker networks" - include_role: - name: _docker - tasks_from: networks - -- name: "Check if traefik/docker-compose.yml exists" +- name: "Check if {{ role_name }}/docker-compose.yml exists" stat: - path: '{{ service_base_path }}/traefik/docker-compose.yml' + path: '{{ service_base_path }}/{{ role_name }}/docker-compose.yml' register: check_docker_compose_file -- name: "Stop traefik" +- name: "Stop {{ role_name }}" shell: docker-compose down args: - chdir: '{{ service_base_path }}/traefik' + chdir: '{{ service_base_path }}/{{ role_name }}' when: check_docker_compose_file.stat.exists ignore_errors: yes -- name: "Deploy service configuration for traefik" +- name: "Deploy service configuration for {{ role_name }}" include_role: name: _deploy tasks_from: configs @@ -45,7 +40,7 @@ - name: "Ensure acme.json exists" copy: content: "" - dest: '{{ service_base_path }}/traefik/acme.json' + dest: '{{ service_base_path }}/{{ role_name }}/acme.json' force: no owner: "{{ docker_owner }}" group: "{{ docker_group }}" @@ -58,24 +53,31 @@ vars: current_services: [] -- name: "Update landing page for traefik" +- name: "Update landing page for {{ role_name }}" include_role: name: _deploy tasks_from: caddy_landing_page vars: current_services: [ { - current_name: "traefik", + current_name: "{{ role_name }}", current_url: "{{ http_s }}://{{ stage_server_url_host }}:{{ admin_port_traefik }}", current_version: "{{ traefik_image_version }}", current_date: "{{ ansible_date_time.iso8601 }}", }, ] -- name: "Start traefik" +- name: "Update {{ role_name }}" + shell: docker-compose pull + args: + chdir: '{{ service_base_path }}/{{ role_name }}' + tags: + - update_deployment + +- name: "Start {{ role_name }}" shell: docker-compose up -d args: - chdir: '{{ service_base_path }}/traefik' + chdir: '{{ service_base_path }}/{{ role_name }}' - name: "Send mattermost messsge" uri: diff --git a/setup.yml b/setup.yml index d35cb70..17d737f 100644 --- a/setup.yml +++ b/setup.yml @@ -29,6 +29,37 @@ tags: - install + - name: "Gather current server infos" + hcloud_server_info: + api_token: "{{ hetzner_authentication_token }}" + register: hetzner_server_infos + delegate_to: 127.0.0.1 + become: false + + - name: "Set current server infos as fact: hetzner_server_infos_json" + set_fact: + hetzner_server_infos_json: "{{ hetzner_server_infos.hcloud_server_info }}" + delegate_to: 127.0.0.1 + become: false + + - name: "Read ip address for {{ inventory_hostname }}" + set_fact: + stage_server_ip: "{{ item.ipv4_address }}" + when: item.name == inventory_hostname + with_items: "{{ hetzner_server_infos_json }}" + delegate_to: 127.0.0.1 + become: false + + - name: Print the gathered infos + debug: + var: stage_server_ip + delegate_to: 127.0.0.1 + + - name: "Check docker networks" + include_role: + name: _docker + tasks_from: networks + roles: - role: ansible-role-docker vars: @@ -40,6 +71,11 @@ tags: - common + - role: filebeat + when: filebeat_enabled | default(True) + tags: + - filebeat + - role: node-exporter when: node_exporter_enabled | default(True) tags: diff --git a/smardigo.yml b/smardigo.yml index e190a0e..3113375 100644 --- a/smardigo.yml +++ b/smardigo.yml @@ -1,7 +1,7 @@ --- - name: 'apply setup to {{ host | default("all") }}' hosts: '{{ host | default("all") }}' - serial: "{{ serial_number|default(1) }}" + serial: "{{ serial_number | default(5) }}" become: yes pre_tasks: @@ -12,8 +12,41 @@ - ansible_version.minor >= 10 msg: "The ansible version has to be at least ({{ ansible_version.full }})" + - name: "Gather current server infos" + hcloud_server_info: + api_token: "{{ hetzner_authentication_token }}" + register: hetzner_server_infos + delegate_to: 127.0.0.1 + become: false + + - name: "Set current server infos as fact: hetzner_server_infos_json" + set_fact: + hetzner_server_infos_json: "{{ hetzner_server_infos.hcloud_server_info }}" + delegate_to: 127.0.0.1 + become: false + + - name: "Read ip address for {{ inventory_hostname }}" + set_fact: + stage_server_ip: "{{ item.ipv4_address }}" + when: item.name == inventory_hostname + with_items: "{{ hetzner_server_infos_json }}" + delegate_to: 127.0.0.1 + become: false + + - name: Print the gathered infos + debug: + var: stage_server_ip + delegate_to: 127.0.0.1 + + - name: "Check docker networks" + include_role: + name: _docker + tasks_from: networks + roles: - role: connect when: "'connect' in group_names" + - role: keycloak + when: "'keycloak' in group_names" - role: prometheus when: "'prometheus' in group_names" diff --git a/stage-dev b/stage-dev index 3333477..ba359b9 100644 --- a/stage-dev +++ b/stage-dev @@ -2,6 +2,7 @@ dev-connect-01 dev-connect-02 dev-connect-03 +dev-connect-04 [docker_registry] dev-docker-registry-01 @@ -11,6 +12,9 @@ dev-elastic-stack-01 dev-elastic-stack-02 dev-elastic-stack-03 +[keycloak] +dev-keycloak-01 + [prometheus] dev-prometheus-01 @@ -18,6 +22,7 @@ dev-prometheus-01 connect docker_registry elastic +keycloak prometheus [all:children] diff --git a/templates/docker-registry/registry/config.yml b/templates/docker-registry/registry/config.yml deleted file mode 100644 index f840f3d..0000000 --- a/templates/docker-registry/registry/config.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 0.1 - -storage: - filesystem: - rootdirectory: /var/lib/registry - delete: - enabled: true - -http: - addr: 0.0.0.0:5000 - debug: - addr: 0.0.0.0:5001 diff --git a/templates/docker-registry/registry/init b/templates/docker-registry/registry/init deleted file mode 100644 index ef43eba..0000000 --- a/templates/docker-registry/registry/init +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -x - -cp /secrets/portus.crt /usr/local/share/ca-certificates -update-ca-certificates -registry serve /etc/docker/registry/config.yml \ No newline at end of file diff --git a/templates/docker-registry/secrets/.gitignore b/templates/docker-registry/secrets/.gitignore deleted file mode 100644 index 612227a..0000000 --- a/templates/docker-registry/secrets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -portus.* \ No newline at end of file diff --git a/templates/filebeat/certs/ca/ca.crt b/templates/filebeat/certs/ca/ca.crt new file mode 100644 index 0000000..0bc137e --- /dev/null +++ b/templates/filebeat/certs/ca/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIVAO1gvUalebylIyFuIAZC6bfhz04QMA0GCSqGSIb3DQEB +CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu +ZXJhdGVkIENBMB4XDTIxMDQxODExMDkwOFoXDTIyMDQxODExMDkwOFowNDEyMDAG +A1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCLcbwtcUwHBNBOlLoZA+lH +xMoOrrySQNRRyLw/hV+KpW1YncCgVq3dGEOjOC3lS1B55+sZfjEn7EKfDtrZN6Pf +0Ot22/GV3r+fJi72njBfay1Cep8OCJxNOx9i0N3XO2GN6IYPMEpkqFj8nySpAgh3 +70hILu3QMov2I2rWXMzE3yV6Pi7OQ151Fa8vZ1HTXkpjO7Rxyt36cXLB7slj6Uxo +72cO0WphRV6e24Fx5iRLlAs7WdXDOSUXZfIFBiZGYvuZIgbAw9M9ZR5536eXBFuQ +MuwLiP5g+D5GZbal5enRUShBknRP9Xvnxv7OOnPhMXVHMTsM9feqxVzmhRPp4XBz +AgMBAAGjUzBRMB0GA1UdDgQWBBRJ5gyop7tp96EV6O/FHIY2P3T7pzAfBgNVHSME +GDAWgBRJ5gyop7tp96EV6O/FHIY2P3T7pzAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBCwUAA4IBAQBEgehvsAW5r1/nogmIhhRVl5rZcy9mnbxsy/9udU1zBTEe +ZhgCCqOx6xffXUWSvVXw3BUUizCvB5nSHCYBt3H2f8sdPXO54b5mcld/2n/D39yw +HSODGmgkbEVjXK1Qx4xYDRHJnOuyExWQ1D7Y7HocgtIRySFdG/h7en5SM2ooJ7fa +pPtCp8f1tHHuKCjKhgC/+wlvEZFHOWcu6Hyh1FtWHwD3uu9Tj3VRKMvW0u+KQ4mC +aNEuHUEKzgwXRZvBG8Y5k35bFf9EVulTsD2fOTMWrD9CEdctQIfQnn1Oy3s43x39 +94DgEx78H/5fGkUDjqljXp1RBDeNJV7+tssRMISL +-----END CERTIFICATE----- diff --git a/templates/filebeat/certs/filebeat.crt b/templates/filebeat/certs/filebeat.crt new file mode 100644 index 0000000..8b32bea --- /dev/null +++ b/templates/filebeat/certs/filebeat.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbjCCAlagAwIBAgIUCPrH9Oej8C0/4mg1Tum4iAzkHSMwDQYJKoZIhvcNAQEL +BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l +cmF0ZWQgQ0EwHhcNMjEwNDE4MTEwOTExWhcNMjIwNDE4MTEwOTExWjAoMSYwJAYD +VQQDEx1maWxlYmVhdC1kZXYtZWxhc3RpYy1zdGFjay0wMTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALdenPTRywIofEt8gAlpc1KwyzINsUE3TpBGYBS2 +oNAJdPC5kxSvsxclaATrAWZREPHaBiwlwgN5ApsAJzjC5NQDUrGP3ZR9Ij4Rkm5m +2yX32aWm2PurEOjhX6LPquCfadfzCctNFF1CEL/LzWenzUPN1gWSfHTIk3RGJMBt +BCb8y80RDFbFD7js7k87/zSYMlFocA/XTLWs8CTG7i71rxFVAc+9V7PWziUyIZ4j +Wa+cNDwrjmhscrA6IYf367wb+PUwcQJOVC5+NKJrJUCh91hYPn8Z34RGePuIOAjw +ITl13KrIK651pl1hear4SabGpFDX7uZwhfzMj31aJmqMQx0CAwEAAaOBgzCBgDAd +BgNVHQ4EFgQUiBlwII0trXd0F2tfRcHjcjgkZAAwHwYDVR0jBBgwFoAUSeYMqKe7 +afehFejvxRyGNj90+6cwMwYDVR0RBCwwKoIJbG9jYWxob3N0gh1maWxlYmVhdC1k +ZXYtZWxhc3RpYy1zdGFjay0wMTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IB +AQBogbmwhFmJVu9Sr9E9dhsj622aVvg3MQvp1tkvZ1S+ATXELwzvoKStmlvSUWdD +4KB4oAgK/b6a01WrJC1vF3RPHMF7JGmfGRqeJyYtk4uGfWWshnex+Ub0ooffd6l4 +I+sGFwiGuqHuekp5w0VEdgtRrrCaWXoHahIxiSdhcqaiRlS0TI8LOkjkTa4Y26am +aNg4PrP2dupJGGC94gEdomzaDw63tJsD3kSGuG3YVHMDmdySJv+ivJoudPsY+zva +dfwAVmpWVFdfd3L3twf7Mge68Zfcf8gIqxRTwr5LWfj//cu4ZbyiEe6gqgZs9Z2r +V/aVoiOdEjhuEaQh+m3sNkfq +-----END CERTIFICATE----- diff --git a/templates/filebeat/certs/filebeat.key b/templates/filebeat/certs/filebeat.key new file mode 100644 index 0000000..4d5f98b --- /dev/null +++ b/templates/filebeat/certs/filebeat.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAt16c9NHLAih8S3yACWlzUrDLMg2xQTdOkEZgFLag0Al08LmT +FK+zFyVoBOsBZlEQ8doGLCXCA3kCmwAnOMLk1ANSsY/dlH0iPhGSbmbbJffZpabY ++6sQ6OFfos+q4J9p1/MJy00UXUIQv8vNZ6fNQ83WBZJ8dMiTdEYkwG0EJvzLzREM +VsUPuOzuTzv/NJgyUWhwD9dMtazwJMbuLvWvEVUBz71Xs9bOJTIhniNZr5w0PCuO +aGxysDohh/frvBv49TBxAk5ULn40omslQKH3WFg+fxnfhEZ4+4g4CPAhOXXcqsgr +rnWmXWF5qvhJpsakUNfu5nCF/MyPfVomaoxDHQIDAQABAoIBAE9iMmj6efyRMl4r +o/JvKHHf/9fHfblSDD0Beo79EVl+/pVIgZgvCEU4+HNIme6FoeRSEuIB5qBCPxKD +WneESDRQy/f65F5oXe6pBM+uz6j8R8kjFkS9pjBrgU+mv79GxDetC8xrrilBdKbT +wDTjvEViUwlOhXq5arynsTls+KM3ihJfKjQh8ahal4+n/GN3R+S/F3eegKEEJq/k +vqmIPcPgHRoUb00s+zrNbiltLVlR/rU5/2vMudOrATdz0sf8DWssWBKMKv/7VYRS +GAykRuvGVtHS7UAA2zkoslSjPW6GjdxfZPRDo51VHFW1IK4PugY9OGVYR8+pwUX9 +aqyPQQECgYEA3MA8xQXqrL2tZNSNr5jSUiv5CkWbbkIkS+WYPVj7JduCYXcs1s74 +9AIDtmNOA5pPzHwjMtabU84BzjNOT6QDvdlkbVe9zgvq9svU9lNz2c3Q4MpbCT/i +lFhZjKe0cROSAvYk3hgUNE4DD4MymT2Q2/3sFAmbyTfPUr5bu86zonECgYEA1KZR +JsRrBuPk+7CbN8rytR/ZnT0h/SI/aev+4crDkwa0soQ4yaQK/9r91Jeq8HY/Tbwg +27c+LP33ms5/3gkpwkeh+VqeZ8fIbXJTqZ1FCadtUgLCq8vutanlaE8Z0K/enRS0 +Capexp1ZDtp8eSAeyos7RPxehqHSUMeF+MHMKW0CgYBbkKGkV7/vxv2VRVU/8PPM +gdDbIeRG58iGcsWjLLWADn0WUIiY0WESVYOUs7w4YlmXSCaRf9MN//VfwohJII8s +wG+Xqz1fqjHcDNBZHGSBg42QsF7yhz1EqyD55tZB0QxPjinctcArsfAzDwh957ue +hMTXyuSDolKsz6jdTe/VAQKBgF3mLxFqTERXn4ZQPsoNMM0wCjy3gOmxFMVl8z+q +9F9Y57OoVRcc+8ps3gbhDhdub5eYyf2bVbYyUwKlyqq16x2h2fEsxaPYATXq9OyB +yLlxmAFNvL51p6vKIMXFoAWZkzhTqwhVldIoKuo3Kh2mRFJ11q8orWjPzfnjkNH+ +aXOlAoGAaYv+Ft7GeR8mcZ2IIOivpVDQaYqgSN+uRA9yWd/ozvMtvN/sGkI48gco +ZlD7rsbZVhHLXTA+zbWU5G3SuBhJfwuiNWhHtuDRVqBefaXBaQr8GHxBxw1XcyxU +b+wzpN3zsSXMTW6+xrgw2I1eM07Ncn80EMfAjjUXFSLgoLVRDKk= +-----END RSA PRIVATE KEY----- diff --git a/templates/filebeat/config/filebeat.yml.j2 b/templates/filebeat/config/filebeat.yml.j2 new file mode 100644 index 0000000..fd68fb4 --- /dev/null +++ b/templates/filebeat/config/filebeat.yml.j2 @@ -0,0 +1,28 @@ +filebeat.inputs: +- type: container + paths: + -/var/lib/docker/containers/*/*.log + +filebeat.autodiscover: + providers: + - type: docker + hints.enabled: true + templates: + - condition: + contains: + docker.container.image: smardigo + config: + - type: container + paths: + - /var/lib/docker/containers/${data.docker.container.id}/*.log + multiline.pattern: '^{|^[0-9]{4}-[0-9]{2}-[0-9]{2}' + multiline.negate: true + multiline.match: after + +output.logstash: + hosts: ["{{ logstash_hostname }}:5044"] + ssl: + certificate_authorities: + - /usr/share/filebeat/config/certificates/ca/ca.crt + certificate: /usr/share/filebeat/config/certificates/filebeat.crt + key: /usr/share/filebeat/config/certificates/filebeat.key diff --git a/templates/keycloak/smardigo-theme/account/resources/css/smardigo-account.css b/templates/keycloak/smardigo-theme/account/resources/css/smardigo-account.css new file mode 100644 index 0000000..84e4e99 --- /dev/null +++ b/templates/keycloak/smardigo-theme/account/resources/css/smardigo-account.css @@ -0,0 +1,39 @@ +.navbar-title { + background-color: #ffffff; + background-image: url('../img/smardigo-logo.png'); + height: 30px; + width: 171px; +} + +.navbar-pf { + border-top-color: #ffffff; + border-bottom-color: #b02d3f; + background: #ffffff; + border-bottom-width: 3px; +} + +.navbar-pf .navbar-header { + border-color: #b02d3f; +} + +.bs-sidebar ul li.active a { + color: #ffffff; + border-color: #b02d3f; + background-color: #b02d3f; +} + +.btn-primary { + background-color: #b02d3f; + border-color: #b02d3f; + background-image: linear-gradient(to bottom,#b02d3f 0,#b02d3f 100%); + background-repeat: repeat-x; +} + +.btn-primary.active, .btn-primary:active, .btn-primary:focus, .btn-primary:hover, .open .dropdown-toggle.btn-primary { + background-color: #b02d3f; + border-color: #b02d3f; +} + +.navbar-pf .navbar-utility > li > a { + color: #000000 !important; +} diff --git a/templates/keycloak/smardigo-theme/account/resources/img/favicon.ico b/templates/keycloak/smardigo-theme/account/resources/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0333bfb498a4bd745e7d11ddbe6c39f60a97bfe1 GIT binary patch literal 152126 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