From 3f45b8d02d2bd5274c1438a5966b231b3943f2e7 Mon Sep 17 00:00:00 2001 From: Jeroen Baten Date: Mon, 13 May 2024 08:49:08 +0200 Subject: [PATCH 01/12] Adding Ansiblke linting to CI/CD --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..1f806f2 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,5 @@ +ansible-lint: + stage: linting + image: registry.gitlab.com/pipeline-components/ansible-lint:latest + script: + - ansible-lint --show-relpath playbooks/ From 6146ce6e7b9a7f234e1e19356ee0e63dd8296f49 Mon Sep 17 00:00:00 2001 From: Jeroen Baten Date: Mon, 13 May 2024 06:52:08 +0000 Subject: [PATCH 02/12] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1f806f2..41263e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,12 @@ +image: python:3-slim + +before_script: + - pip install ansible ansible-lint + - ansible-lint --version + +stages: + - ansible-lint + ansible-lint: stage: linting image: registry.gitlab.com/pipeline-components/ansible-lint:latest From aec9713544ba8c71e007c1442228e00777f7579c Mon Sep 17 00:00:00 2001 From: Jeroen Baten Date: Mon, 13 May 2024 07:00:55 +0000 Subject: [PATCH 03/12] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 41263e6..abe1180 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,14 +1,9 @@ -image: python:3-slim - -before_script: - - pip install ansible ansible-lint - - ansible-lint --version - stages: - ansible-lint ansible-lint: - stage: linting + stage: ansible-lint image: registry.gitlab.com/pipeline-components/ansible-lint:latest script: + - ansible-lint --version - ansible-lint --show-relpath playbooks/ From 8bb49c6b7338c7770188eb1cf6a0edbe97a66a87 Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Sun, 19 May 2024 11:29:50 +0200 Subject: [PATCH 04/12] Removed trailing spaces --- playbooks/install-bitwarden-part-1.yml | 100 +++++------ .../install-bitwarden-part-2-saml-sso.yml | 48 +++--- playbooks/install-cmdb-sso.yml | 86 +++++----- playbooks/install-freeipa-server.yml | 68 ++++---- playbooks/install-gitlab-sso.yml | 58 +++---- playbooks/install-jenkins-sso.yml | 86 +++++----- playbooks/install-keycloak-nginx.yml | 22 +-- playbooks/install-nextcloud-sso-snap.yml | 150 ++++++++--------- playbooks/install-nextcloud-sso.yml | 158 +++++++++--------- playbooks/install-odoo-sso.yml | 52 +++--- playbooks/install-xwiki-sso.yml | 90 +++++----- playbooks/install-zabbix-server-sso.yml | 52 +++--- 12 files changed, 485 insertions(+), 485 deletions(-) diff --git a/playbooks/install-bitwarden-part-1.yml b/playbooks/install-bitwarden-part-1.yml index a7d3961..edb19d9 100644 --- a/playbooks/install-bitwarden-part-1.yml +++ b/playbooks/install-bitwarden-part-1.yml @@ -50,14 +50,14 @@ name: "{{ item }}" state: present loop: - - apt-transport-https - - ca-certificates - - curl - - gnupg-agent + - apt-transport-https + - ca-certificates + - curl + - gnupg-agent - software-properties-common - - docker-ce - - docker-ce-cli - - containerd.io + - docker-ce + - docker-ce-cli + - containerd.io - docker-compose - python3-pexpect - unzip @@ -73,7 +73,7 @@ name: bitwarden shell: /bin/bash home: /opt/bitwarden - groups: docker + groups: docker # Make dir for SSL keys - name: Make SSL dir for keys @@ -90,7 +90,7 @@ # owner: bitwarden # group: bitwarden # mode: '0600' -# loop: +# loop: # - _wildcard.{{ domain }}.pem # - _wildcard.{{ domain }}-key.pem @@ -119,7 +119,7 @@ - name: Is /opt/bitwarden/bwdata/ already present? debug: - msg: "/opt/bitwarden/bwdata/ already present" + msg: "/opt/bitwarden/bwdata/ already present" when: bwdata_dir_stat.stat.exists - name: Download extra Bitwarden CLI tool @@ -155,74 +155,74 @@ # INSTALLATION ID: 8c7729c8-a13e-4110-ae36-ae52008c2724 # INSTALLATION KEY: oslnBjE2l0WtcEvD9VcJ # Run the install script - # _ _ _ _ - # | |__ (_) |___ ____ _ _ __ __| | ___ _ __ - # | '_ \| | __\ \ /\ / / _` | '__/ _` |/ _ \ '_ \ + # _ _ _ _ + # | |__ (_) |___ ____ _ _ __ __| | ___ _ __ + # | '_ \| | __\ \ /\ / / _` | '__/ _` |/ _ \ '_ \ # | |_) | | |_ \ V V / (_| | | | (_| | __/ | | | # |_.__/|_|\__| \_/\_/ \__,_|_| \__,_|\___|_| |_| - # + # # Open source password management solutions # Copyright 2015-2022, 8bit Solutions LLC # https://bitwarden.com, https://github.com/bitwarden - # + # # =================================================== - # + # # bitwarden.sh version 2022.8.4 # Docker version 20.10.17, build 100c701 # docker-compose version 1.25.0, build unknown - # + # # (!) Enter the domain name for your Bitwarden instance (ex. bitwarden.example.com): password.{{ domain }} - # + # # (!) Do you want to use Let's Encrypt to generate a free SSL certificate? (y/n): n - # + # # (!) Enter the database name for your Bitwarden instance (ex. vault): vault - # + # # 2022.8.4: Pulling from bitwarden/setup - # 1efc276f4ff9: Pull complete - # e5aeae5c9ad4: Pull complete - # 9d8b4edc672a: Pull complete - # 67bb3a123350: Pull complete - # 4b31f33ff8ee: Pull complete - # 8302c6d93c2f: Pull complete - # 64c1ff0e03a3: Pull complete - # 289e8b648bb1: Pull complete - # c706fe453135: Pull complete - # 6b18bfe90415: Pull complete + # 1efc276f4ff9: Pull complete + # e5aeae5c9ad4: Pull complete + # 9d8b4edc672a: Pull complete + # 67bb3a123350: Pull complete + # 4b31f33ff8ee: Pull complete + # 8302c6d93c2f: Pull complete + # 64c1ff0e03a3: Pull complete + # 289e8b648bb1: Pull complete + # c706fe453135: Pull complete + # 6b18bfe90415: Pull complete # Digest: sha256:257317606bad7b6c06755c81e4f61099b4af8b89829d7a9a2688545b92daa45f # Status: Downloaded newer image for bitwarden/setup:2022.8.4 # docker.io/bitwarden/setup:2022.8.4 - # + # # (!) Enter your installation id (get at https://bitwarden.com/host): 8c7729c8-a13e-4110-ae36-ae52008c2724 - # + # # (!) Enter your installation key: oslnBjE2l0WtcEvD9VcJ - # + # # (!) Do you have a SSL certificate to use? (y/n): y - # + # # !!!!!!!!!! NOTE !!!!!!!!!! - # Make sure 'certificate.crt' and 'private.key' are provided in the + # Make sure 'certificate.crt' and 'private.key' are provided in the # appropriate directory before running 'start' (see docs for info). - # + # # (!) Is this a trusted SSL certificate (requires ca.crt, see docs)? (y/n): y - # + # # Generating key for IdentityServer. # Generating a RSA private key # ...................................................................................++++ # .................++++ # writing new private key to 'identity.key' # ----- - # + # # Building nginx config. # Building docker environment files. # Building docker environment override files. # Building FIDO U2F app id. # Building docker-compose.yml. - # + # # Installation complete - # + # # If you need to make additional configuration changes, you can modify # the settings in `./bwdata/config.yml` and then run: # `./bitwarden.sh rebuild` or `./bitwarden.sh update` - # + # # Next steps, run: # `./bitwarden.sh start` - name: Run the install script @@ -272,8 +272,8 @@ # ./bwdata/ssl/your.domain. You may specify a different location for your # certificate files by editing the following values in ./bwdata/config.yml: # - # ssl_certificate_path: - # ssl_key_path: + # ssl_certificate_path: + # ssl_key_path: # ssl_ca_path: # # Make sure SSL filenames are correct @@ -283,7 +283,7 @@ # ssl_key_path: /etc/ssl/self/password.{{ domain }}/private.key # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to # `/etc/ssl` within the container. - # ssl_ca_path: + # ssl_ca_path: # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to - name: Copy current SSL cert to correct directory ("/bwdata/ssl/{{ ansible_fqdn }}/" become: yes @@ -325,7 +325,7 @@ msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/ca.crt" # TODO Next steps, run: - # `./bitwarden.sh start` + # `./bitwarden.sh start` # 3. Configure the Environment # Run ./bitwarden.sh start to start the Bitwarden Server. # Note: Some Bitwarden features are not configured by the bitwarden.sh installer, and must be configured in the environment file, located at ./bwdata/env/global.override.env. At a minimum, you should configure: @@ -337,7 +337,7 @@ # globalSettings__mail__smtp__password= # ... # adminSettings__admins= - + # If you need to make additional configuration changes, you can modify # the settings in `./bwdata/config.yml` and then run: # `./bitwarden.sh rebuild` or `./bitwarden.sh update` @@ -362,11 +362,11 @@ ############################################################################################ - # Also this needs to be fixed. - # DO NOT EVER USE THE PIECE OF CRAP Bitwarden Directory Connector to sync users from ldap + # Also this needs to be fixed. + # DO NOT EVER USE THE PIECE OF CRAP Bitwarden Directory Connector to sync users from ldap ############################################################################################ # SAML SSO: https://bitwarden.com/help/configure-sso-saml/ - + - debug: msg: | * Now go to URL "{{ bitwarden_server_url }}" @@ -377,7 +377,7 @@ * Click on the "View API Key" button, enter your password and copy the "client_id" . * Insert these into the next Bitwarden playbook as environment settings on the command line as follows; * $ ansible-playbook install-bitwarden-part-2-saml-sso.yml -e client_id="your-client_id" . - * Please continue to the SAML playbook for bitwarden called "install-bitwarden-part-2-saml-sso.yml". + * Please continue to the SAML playbook for bitwarden called "install-bitwarden-part-2-saml-sso.yml". * We are done here. **************************************************************************************** diff --git a/playbooks/install-bitwarden-part-2-saml-sso.yml b/playbooks/install-bitwarden-part-2-saml-sso.yml index b10947d..21f5390 100644 --- a/playbooks/install-bitwarden-part-2-saml-sso.yml +++ b/playbooks/install-bitwarden-part-2-saml-sso.yml @@ -11,7 +11,7 @@ vars: root_ca_path: /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt bitwarden_server_url: https://password.{{ domain }} - #bitwarden_client_id: "client-bitwarden-{{ ansible_fqdn }}" + #bitwarden_client_id: "client-bitwarden-{{ ansible_fqdn }}" bitwarden_client_id: "{{ bitwarden_server_url }}/sso/saml2" bitwarden_client_name: "client-bitwarden-{{ ansible_fqdn }}" keycloak_server_url: https://ad.{{ domain }} @@ -30,7 +30,7 @@ debug: msg: Do yourself a favour; export ANSIBLE_STDOUT_CALLBACK=debug - + # We need the uuid of the organization, so ask this from the command line - fail: msg: | @@ -52,9 +52,9 @@ organization_uuid: "{{ client_id[-36:] }}" ############################################################################################ - # Also this needs to be fixed. + # Also this needs to be fixed. # The installation of the Bitwarden Directory Connector to sync users from ldap to bitwarden - # DO NOT EVER USE THE PIECE OF CRAP Bitwarden Directory Connector to sync users from ldap + # DO NOT EVER USE THE PIECE OF CRAP Bitwarden Directory Connector to sync users from ldap ############################################################################################ # SAML SSO: https://bitwarden.com/help/configure-sso-saml/ # Install all needed software in one go. @@ -74,7 +74,7 @@ chdir: /tmp - # Start getting auth token + # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server uri: @@ -83,13 +83,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" @@ -98,7 +98,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -138,14 +138,14 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor @@ -154,10 +154,10 @@ url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" +# Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: yes register: idp_metadata - debug: @@ -171,13 +171,13 @@ # content: attribute # register: instance_attributes - # We are going to save the XML metadate for shell processing + # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing copy: - content: "{{ idp_metadata.content }}" + content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml - # We are going to use a shell command to retrieve the value for X509Certificate + # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate shell: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml @@ -224,7 +224,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ bitwarden_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -248,7 +248,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 @@ -294,29 +294,29 @@ # url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ bitwarden_client_id }}" # headers: # Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" +# Authorization: "Bearer {{ auth_token }}" # method: get # validate_certs: false # register: client1 # # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - + - debug: msg: | **************************************************************************************** * Here some stuff you need to do yourself * - Login to your BitWarden server and open your Organization. * - * - Open the Settings tab and enter the following unique Identifier + * - Open the Settings tab and enter the following unique Identifier * for your organization "{{ organization_id }}" * - * Once you have your Organization Identifier, you can proceed to + * Once you have your Organization Identifier, you can proceed to * enabling and configuring your integration. To enable Login with SSO - * From the Organization Vault, navigate to the Manage tab and + * From the Organization Vault, navigate to the Manage tab and * select Single Sign-On from the left-hand menu. * * - On the Single Sign-On Screen, check the Allow SSO Authentication checkbox. * - From the Type dropdown menu, select the "SAML 2.0" option. - * + * * == SAML Service Provider Configuration == * - SP Entity ID should read "https://password.{{ domain }}/sso/saml2" * - SAML 2.0 Metadata URL should start with "https://password.{{ domain }}/sso/saml2/" @@ -341,7 +341,7 @@ * Go to top menu 'Vaults' -> "{{ organization_name }}". * Click the 3 dots to the right of the organization name and click on 'Link SSO'. * - * Notes; BitWarden allows per-organization logins based on valid redirect URLs containing + * Notes; BitWarden allows per-organization logins based on valid redirect URLs containing * a specific organization uuid. This playbook allows one redirect URLs. ******************************************************************************************** diff --git a/playbooks/install-cmdb-sso.yml b/playbooks/install-cmdb-sso.yml index 45b4c05..ce7bc89 100644 --- a/playbooks/install-cmdb-sso.yml +++ b/playbooks/install-cmdb-sso.yml @@ -24,7 +24,7 @@ include_vars: encrypted-vars.yml - - name: Update repositories cache + - name: Update repositories cache apt: update_cache: yes @@ -53,7 +53,7 @@ path: /root/cmdbuild-3.4.war register: warfile_stat - + # Download cmdbuild-3.4.war via cmdbuild.org - name: Download CMDbuild war file with big timeout value (because it comes from Sourceforge...) @@ -73,8 +73,8 @@ dest: /var/lib/tomcat9/webapps/cmdbuild.war remote_src: yes mode: '0644' - - + + # add system group cmdbuild - name: Ensure group "cmdbuild" exists ansible.builtin.group: @@ -103,12 +103,12 @@ name: cmdbuild password: 'cmdbuild' #priv: "CONNECT" - + # postgresql create database cmdbuild owner cmdbuild - name: Make sure cmdbuild Postgresql database exists. become: true become_user: postgres - postgresql_db: + postgresql_db: name: cmdbuild template: 'template0' owner: cmdbuild @@ -160,16 +160,16 @@ db.username=cmdbuild db.password=cmdbuild db.admin.username=postgres - db.admin.password=postgres - + db.admin.password=postgres + # Set up nginx - name: Install nginx config file copy: src: files/cmdb_nginx.conf - dest: /etc/nginx/sites-available/default + dest: /etc/nginx/sites-available/default - - name: Make SSL dir for nginx + - name: Make SSL dir for nginx ansible.builtin.file: path: /etc/nginx/ssl state: directory @@ -182,13 +182,13 @@ owner: root group: root mode: '0600' - loop: + loop: - _wildcard.{{ domain }}.pem - _wildcard.{{ domain }}-key.pem # Restart nginx - name: Restart nginx - systemd: + systemd: name: nginx state: restarted enabled: yes @@ -201,17 +201,17 @@ # We can create a SAML client at the Keycloak side. And use REST to # configure at the CMDBuild side. - #################################################### + #################################################### # Create a SAML client at the Keycloak side. - #################################################### - + #################################################### + # Generate a key on the cmdb server - name: Generate key on cmdb server shell: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:1024 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /tmp - # Start getting auth token + # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server uri: @@ -220,13 +220,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" @@ -235,7 +235,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -275,14 +275,14 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor @@ -291,7 +291,7 @@ url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" method: get validate_certs: false - return_content: yes + return_content: yes register: idp_metadata - debug: @@ -305,13 +305,13 @@ # content: attribute # register: instance_attributes - # We are going to save the XML metadate for shell processing + # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing copy: - content: "{{ idp_metadata.content }}" + content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml - # We are going to use a shell command to retrieve the value for X509Certificate + # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate shell: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml @@ -357,7 +357,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ cmdb_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -381,13 +381,13 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - + - debug: var: deleteclient @@ -416,7 +416,7 @@ body: "{{ jsonbody }}" status_code: 201 register: createclientresult - + # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID @@ -425,10 +425,10 @@ # So far, so good # - #################################################### + #################################################### # Configure CMDBuild authentication. - #################################################### - # I tried to use the editconfig (/cmdbuild.sh restws editconfig)to set values + #################################################### + # I tried to use the editconfig (/cmdbuild.sh restws editconfig)to set values # or example:cmdbuild.sh restws setconfig org.cmdbuild.dms.enabled true # Well, cmdbuild.sh restws is not documented and tough to reverse engineer. # @@ -454,11 +454,11 @@ # This can also be done using REST calls, but you need to make the auth.conf file readable, so we do it this way. # We end now. But if we want to do rest stuff, uncomment below and poll first until online - + # The next things only works after adding ReadWritePaths=/etc/tomcat9/cmdbuild/ to /lib/systemd/system/tomcat9.service - name: Add ReadWritePaths=/etc/tomcat9/cmdbuild/ to /lib/systemd/system/tomcat9.service so we can edit the config. lineinfile: - path: /lib/systemd/system/tomcat9.service + path: /lib/systemd/system/tomcat9.service insertafter: '^ReadWritePaths=/var/log/tomcat9/.*' line: 'ReadWritePaths=/etc/tomcat9/cmdbuild/' - name: Do a systemd daemon-reload now @@ -466,7 +466,7 @@ daemon_reload: true # Restart tomcat9 - name: Restart tomcat9 - systemd: + systemd: name: tomcat9 state: restarted enabled: yes @@ -474,8 +474,8 @@ - debug: msg: "Start waiting for 443 to become available" - - wait_for: - port: 443 + - wait_for: + port: 443 delay: 20 # Measured 12 attempts, so 25 max should work most of the time. @@ -490,7 +490,7 @@ until: amiup.status == 403 retries: 25 # 25 * 5 seconds = 1hour (60*60/5) delay: 5 # Every 5 seconds - + # - fail: # msg: # "* There was an error starting the tomcat server" @@ -547,7 +547,7 @@ headers: { 'Cmdbuild-authorization': "{{ sessionid }}" } - # Now we know this is stored in auth.conf as module.saml.idp.id + # Now we know this is stored in auth.conf as module.saml.idp.id - name: Set value of org.cmdbuild.auth.module.saml.idp.id to "{{ keycloak_server_url }}" uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.id" @@ -659,7 +659,7 @@ headers: { 'Cmdbuild-authorization': "{{ sessionid }}" } body: "{{ cmdb_base_url }}" - + - name: Set value of org.cmdbuild.auth.module.saml.idp.login to "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.login" @@ -690,7 +690,7 @@ headers: { 'Cmdbuild-authorization': "{{ sessionid }}" } body: "login = auth.getNameId()" - + # Try to enable saml module - name: Set value of org.cmdbuild.auth.module.saml.enabled to "True" uri: @@ -712,7 +712,7 @@ headers: { 'Cmdbuild-authorization': "{{ sessionid }}" } body: "True" - + # Werkt goed. - name: Signal CMDBuild to reload system configuration uri: @@ -728,7 +728,7 @@ # decided to try adding saml to the login modules variable I saw there. # org.cmdbuild.auth.modules=default,saml # The SSO cannot be enabled from the UI, you can do it with the following - # command: + # command: # bash cmdbuild.sh restws -username "{{ cmdb_admin_id }}" -password "{{ cmdb_admin_pw }}" setconfig org.cmdbuild.auth.modules saml,default # (this configuration means that both saml and the default login are enabled) - name: Add SSO to internal db login method diff --git a/playbooks/install-freeipa-server.yml b/playbooks/install-freeipa-server.yml index fc76152..0945a82 100644 --- a/playbooks/install-freeipa-server.yml +++ b/playbooks/install-freeipa-server.yml @@ -57,11 +57,11 @@ regexp: '^SELINUX=' line: SELINUX=permissive - # yum -y install @idm:DL1 + # yum -y install @idm:DL1 # Using shell module because well, it just didn't work any other way - - name: Enable freeipa repo stream + - name: Enable freeipa repo stream shell: - cmd: yum -y install @idm:DL1 + cmd: yum -y install @idm:DL1 warn: false # # dnf install freeipa-server @@ -75,23 +75,23 @@ state: latest # Configure a FreeIPA server. - # The command can take command arguments or can be run in the interactive mode. - # You can get more details with man ipa-server-install. + # The command can take command arguments or can be run in the interactive mode. + # You can get more details with man ipa-server-install. # To start the interactive installation, run: # ipa-server-install - # The command will at first gather all required information and then configure all required services. + # The command will at first gather all required information and then configure all required services. - debug: msg: "To start the interactive installation, run: ipa-server-install" # Yes, we could automate this with Ansible vault and expect stuff, but we don't - + # ipa-server-install # The log file for this installation can be found in /var/log/ipaserver-install.log # ====================================================================== # This program will set up the IPA Server. # Version 4.9.6 - # + # # This includes: # * Configure a stand-alone CA (dogtag) for certificate management # * Configure the NTP client (chronyd) @@ -99,55 +99,55 @@ # * Create and configure a Kerberos Key Distribution Center (KDC) # * Configure Apache (httpd) # * Configure the KDC to enable PKINIT - # + # # To accept the default shown in brackets, press the Enter key. - # - # Do you want to configure integrated DNS (BIND)? [no]: - # + # + # Do you want to configure integrated DNS (BIND)? [no]: + # # Enter the fully qualified domain name of the computer # on which you're setting up server software. Using the form # . # Example: master.example.com. - # - # - # Server host name [ipa.{{ domain }}]: - # + # + # + # Server host name [ipa.{{ domain }}]: + # # The domain name has been determined based on the host name. - # - # Please confirm the domain name [{{ domain }}]: - # + # + # Please confirm the domain name [{{ domain }}]: + # # The kerberos protocol requires a Realm name to be defined. # This is typically the domain name converted to uppercase. - # - # Please provide a realm name [ONESTEIN.LAN]: + # + # Please provide a realm name [ONESTEIN.LAN]: # Certain directory server operations require an administrative user. # This user is referred to as the Directory Manager and has full access # to the Directory for system management tasks and will be added to the # instance of directory server created for IPA. # The password must be at least 8 characters long. - # - # Directory Manager password: - # Password (confirm): - # + # + # Directory Manager password: + # Password (confirm): + # # The IPA server requires an administrative user, named 'admin'. # This user is a regular system account used for IPA server administration. - # - # IPA admin password: - # Password (confirm): - # - # Do you want to configure chrony with NTP server or pool address? [no]: - # + # + # IPA admin password: + # Password (confirm): + # + # Do you want to configure chrony with NTP server or pool address? [no]: + # # The IPA Master Server will be configured with: # Hostname: ipa.{{ domain }} # IP address(es): 10.11.1.242 # Domain name: {{ domain }} # Realm name: ONESTEIN.LAN - # + # # The CA will be configured with: # Subject DN: CN=Certificate Authority,O=ONESTEIN.LAN # Subject base: O=ONESTEIN.LAN # Chaining: self-signed - # + # # Continue to configure the system with these values? [no]: yes # - + diff --git a/playbooks/install-gitlab-sso.yml b/playbooks/install-gitlab-sso.yml index 80d799e..57e37dd 100644 --- a/playbooks/install-gitlab-sso.yml +++ b/playbooks/install-gitlab-sso.yml @@ -47,11 +47,11 @@ - ca-certificates - curl - openssh-server - - mlocate - - openssl + - mlocate + - openssl - autopostgresqlbackup - - - name: Upload mkcert program + + - name: Upload mkcert program copy: src: files/mkcert dest: /root/mkcert @@ -78,7 +78,7 @@ - name: Update all CA certificates shell: - cmd: /usr/sbin/update-ca-certificates + cmd: /usr/sbin/update-ca-certificates # Check if our CA is already installed # Should be /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt @@ -89,16 +89,16 @@ - name: Is our root CA already present? debug: - msg: "Yes it is!" + msg: "Yes it is!" when: root_ca_stat.stat.exists # Start installation of GitLab - name: Download repo add script. get_url: - url: https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh + url: https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh dest: /tmp/script.deb.sh mode: '0700' - + - name: execute repo add script shell: cmd: /tmp/script.deb.sh @@ -123,7 +123,7 @@ lineinfile: path: /etc/gitlab/gitlab.rb regexp: ".*letsencrypt['enable'].*" - line: letsencrypt['enable'] = false + line: letsencrypt['enable'] = false state: present # 1 Create the certificate(s) @@ -155,14 +155,14 @@ mode: '0644' force: true - # Copy SSL files to gitlab /opt/gitlab/embedded/ssl/certs - # To set the location of ssl certificates create /etc/gitlab/ssl directory, - - name: Make SSL dir for GitLab + # Copy SSL files to gitlab /opt/gitlab/embedded/ssl/certs + # To set the location of ssl certificates create /etc/gitlab/ssl directory, + - name: Make SSL dir for GitLab ansible.builtin.file: path: /etc/gitlab/ssl state: directory mode: '0755' - + # place the .crt and .key files in the directory and specify the following configuration: - name: Copy SSL cert to gitlab ssl certs dir /etc/gitlab/ssl @@ -188,7 +188,7 @@ path: /etc/gitlab/gitlab.rb regexp: ".*gitlab_rails['ldap_enabled'].*" state: absent - + # gitlab_rails['ldap_servers'] = YAML.load_file('/etc/gitlab/gitlab_freeipa_settings.yml') - name: Remove if exists reference to LDAP settings file lineinfile: @@ -204,13 +204,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" @@ -219,7 +219,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -259,19 +259,19 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token ####################################################################################### # Very badly documented: The admin RESTful API has a base path /admin/realms/ ####################################################################################### - + # Retrieve current list of clients of our type # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . @@ -280,7 +280,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ gitlab_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -295,13 +295,13 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - + - debug: var: deleteclient @@ -322,7 +322,7 @@ body: "{{ jsonbody }}" status_code: 201 register: createclientresult - + # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID @@ -336,7 +336,7 @@ url: "{{ createclientresult.location }}/client-secret" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: clientsecret @@ -381,7 +381,7 @@ gitlab_rails['omniauth_providers'] = [ { name: "openid_connect", - label: "Network login", + label: "Network login", args: { name: "openid_connect", scope: ["openid","profile","email"], @@ -403,7 +403,7 @@ shell: cmd: gitlab-ctl reconfigure - - name: Start GitLab + - name: Start GitLab shell: cmd: gitlab-ctl start @@ -419,7 +419,7 @@ # Measured 12 attempts, so 25 max should work most of the time. retries: 25 delay: 10 - + - debug: msg: "Done waiting for 443" @@ -429,7 +429,7 @@ register: opensslconf - debug: - var: opensslconf + var: opensslconf - name: Post-install message IT IS IMPORTANT TO READ THIS pause: diff --git a/playbooks/install-jenkins-sso.yml b/playbooks/install-jenkins-sso.yml index 77ee420..a0e81f8 100644 --- a/playbooks/install-jenkins-sso.yml +++ b/playbooks/install-jenkins-sso.yml @@ -50,7 +50,7 @@ filename: jenkins #Update your local package index, then finally install Jenkins: - - name: Update repositories cache + - name: Update repositories cache apt: update_cache: yes @@ -68,15 +68,15 @@ - ca-certificates - curl - openssh-server - - mlocate - - openssl + - mlocate + - openssl - autopostgresqlbackup - jenkins - nginx # - name: Disable Jenkins useSecurity in /var/lib/jenkins/config.xml so we can edit the configuration at the end of this playbook. # lineinfile: -# path: /var/lib/jenkins/config.xml +# path: /var/lib/jenkins/config.xml # regexp: ".*.*" # line: ' false' @@ -102,8 +102,8 @@ name: nginx state: present - - - name: Upload mkcert program + + - name: Upload mkcert program copy: src: files/mkcert dest: /root/mkcert @@ -130,7 +130,7 @@ - name: Update all CA certificates shell: - cmd: /usr/sbin/update-ca-certificates + cmd: /usr/sbin/update-ca-certificates # Check if our CA is already installed # Should be /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt @@ -141,10 +141,10 @@ - name: Is our root CA already present? debug: - msg: "Yes it is!" + msg: "Yes it is!" when: root_ca_stat.stat.exists - + # Install nginx config file - name: Install nginx config file template: @@ -165,13 +165,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" @@ -180,7 +180,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -220,19 +220,19 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token ####################################################################################### # Very badly documented: The admin RESTful API has a base path /admin/realms/ ####################################################################################### - + # Retrieve current list of clients of our type # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . @@ -241,7 +241,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ jenkins_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -256,13 +256,13 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - + - debug: var: deleteclient @@ -315,7 +315,7 @@ # - name: What did we receive? # debug: -# var: client_representation +# var: client_representation # # Good news! Result contains location of new client id in location # # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" @@ -329,7 +329,7 @@ url: "{{ createclientresult.location }}/client-secret" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: clientsecret @@ -341,12 +341,12 @@ # We have to create 2 groups in Keycloak for Jenkins: Jenkins-admin and Jenkins-user # First retrieve current list of configured groups - - name: Retrieve current list of groups + - name: Retrieve current list of groups uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: kc_groups @@ -376,7 +376,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups?search={{ item }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false with_items: @@ -388,7 +388,7 @@ debug: var: groupinfo - # Create groups if not exist, else ignore errors + # Create groups if not exist, else ignore errors - name: Create groups Jenkins-admin and Jenkins-user or ignore errors uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups" @@ -405,12 +405,12 @@ - Jenkins-user ignore_errors: yes - - name: Retrieve current list of groups + - name: Retrieve current list of groups uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: kc_groups @@ -437,7 +437,7 @@ path: /etc/nginx/sites-enabled/default state: absent - - name: Make SSL dir for nginx + - name: Make SSL dir for nginx ansible.builtin.file: path: /etc/nginx/ssl state: directory @@ -450,11 +450,11 @@ owner: root group: root mode: '0600' - loop: + loop: - _wildcard.{{ domain }}.pem - _wildcard.{{ domain }}-key.pem -# - name: Install Nginx reverse proxy virtualhost for Keycloak +# - name: Install Nginx reverse proxy virtualhost for Keycloak # copy: # src: "files/jenkins_nginx.conf" # dest: "/etc/nginx/sites-available/jenkins" @@ -481,10 +481,10 @@ retries: 55 delay: 10 # ignore_errors: yes - + - debug: msg: "Done waiting for 443" - + # Default user: admin # Default password: $JENKINS_HOME/secrets/initialAdminPassword - name: "Retrieve initialAdminPassword from JENKINS_HOME/secrets/initialAdminPassword" @@ -493,7 +493,7 @@ register: admin_password_encoded ignore_errors: yes - - name: Decode initialAdminPassword + - name: Decode initialAdminPassword set_fact: admin_password: "{{ admin_password_encoded.content | b64decode | trim }}" ignore_errors: yes @@ -510,7 +510,7 @@ ******************************************************************************************************** * After a fresh installation Jenkins offers no way to configure SSO using Ansible or the command line. * Sure you can enable remote admin options, but you might just as well configure SSO from the GUI. - * + * * Default Administrator account name is 'admin' * The password for your installation is: "{{ admin_password }}" * @@ -520,26 +520,26 @@ * - Select the 'Install suggested plugins' button to create a default installation. * - When presented with the 'Create First Admin User' click 'Skip and continue as Admin'. * - Confirm or edit the Jenkins URL in the 'Instance Configuration' screen and click 'Save and Finish' - * - The following message will appear - * You have skipped the setup of an admin user. + * - The following message will appear + * You have skipped the setup of an admin user. * To log in, use the username: "admin" and the administrator password you used to access the setup wizard. - * - Click the 'Start using Jenkins' button. + * - Click the 'Start using Jenkins' button. * - At the moment, the file /var/lib/jenkins/secrets/initialAdminPassword still contains the admin password. * * - Time to configure SSO * - Go in the Jenkins GUI to 'Manage Jenkins' → 'Manage Plugins', url = "{{ jenkins_server_url }}/pluginManager/" * - Go to the 'available' tab ("{{ jenkins_server_url }}/pluginManager/available") and enter 'OpenId' in the search field. * - Select the checkbox of the 'OpenId Connect Authentication' plugin and click the 'Install without restart' button. - * - If you like you can verify the installation by going to the 'Installed' tab + * - If you like you can verify the installation by going to the 'Installed' tab * ("{{ jenkins_server_url }}/pluginManager/installed") * - * - Once you have installed the OpenId plugin. you need to change the authentication mode of the Jenkins server. + * - Once you have installed the OpenId plugin. you need to change the authentication mode of the Jenkins server. * - Go to "Manage Jenkins" -> "Configure Global Security" ("{{ jenkins_server_url }}/configureSecurity/" - * - Under the 'Authentication' section, Change the pull-down list of 'Security Realm' ('Beveiligingszone') + * - Under the 'Authentication' section, Change the pull-down list of 'Security Realm' ('Beveiligingszone') * section to 'Login with openid Connect' option. * - Enter in the "Client id" field the value "{{ jenkins_client_id }}" * - Enter in the "Client secret" field the value "{{ client_secret }}" - * - Select as the "Configuration mode" the option "Automatic configuration" and enter as the "Well-known configuration endpoint" + * - Select as the "Configuration mode" the option "Automatic configuration" and enter as the "Well-known configuration endpoint" * the value "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" and press the TAB-key afterwards. * - Open the "Advanced" part of this configuration section * - Enter in the "User name field name" the text 'sub' @@ -557,7 +557,7 @@ # Information about debugging Jenkins # journalctl -u jenkins.service - + # cat /var/lib/jenkins/logging-jb.properties #handlers = java.util.logging.ConsoleHandler @@ -575,10 +575,10 @@ # vim /etc/default/jenkins # add to last JENKINS: -Djava.util.logging.config.file=/var/lib/jenkins/logging-jb.properties - + # Settings found in # org.jenkinsci.plugins.KeycloakSecurityRealm.xml: "auth-server-url": "https://ad.{{ domain }}/" - # plugin OpenId Connect Authentication Version 1.8 Authentication and User Management + # plugin OpenId Connect Authentication Version 1.8 Authentication and User Management # https://ad.{{ domain }}:8443/realms/ONESTEIN.LAN/.well-known/openid-configuration diff --git a/playbooks/install-keycloak-nginx.yml b/playbooks/install-keycloak-nginx.yml index 698ff21..39a7623 100644 --- a/playbooks/install-keycloak-nginx.yml +++ b/playbooks/install-keycloak-nginx.yml @@ -1,5 +1,5 @@ --- -- name: Install central Keycloak Kerberos server on Ubuntu +- name: Install central Keycloak Kerberos server on Ubuntu hosts: grpad remote_user: root @@ -26,7 +26,7 @@ # Install all needed software in one go. # apt install -y ca-certificates curl openssh-server - - name: Install all needed packages + - name: Install all needed packages apt: name: "{{ item }}" loop: @@ -47,7 +47,7 @@ apt: name: apache2 state: absent - + - name: Set timezone to Europe/Amsterdam timezone: name: Europe/Amsterdam @@ -99,7 +99,7 @@ force: yes - - name: Unzip Keycloak distribution zip file + - name: Unzip Keycloak distribution zip file ansible.builtin.unarchive: src: "/root/keycloak-{{ keycloak_release }}.zip" dest: /opt/ @@ -119,7 +119,7 @@ - name: Setup Keycloak Postgresql database become: true become_user: postgres - postgresql_db: + postgresql_db: name: keycloak template: 'template0' owner: keycloak @@ -128,13 +128,13 @@ # Install Systemd service definition # TODO: file currently contains hard-coded password. Needs vault someday. - name: Upload Systemd service definition - template: + template: src: keycloak.service dest: /etc/systemd/system/keycloak.service owner: root group: root - - name: Force systemd to reread configs + - name: Force systemd to reread configs ansible.builtin.systemd: daemon_reload: yes @@ -157,7 +157,7 @@ file: path: "/tmp/{{ domain }}.p12" owner: 'keycloak' - + - name: "Import wildcard key for {{ domain }} into keycloak keystore file" become: true become_user: keycloak @@ -197,7 +197,7 @@ enabled: yes state: restarted - - name: Make SSL dir for nginx + - name: Make SSL dir for nginx ansible.builtin.file: path: /etc/nginx/ssl state: directory @@ -210,11 +210,11 @@ owner: root group: root mode: '0600' - loop: + loop: - _wildcard.{{ domain }}.pem - _wildcard.{{ domain }}-key.pem - - name: Install nginx reverse proxy virtualhost for Keycloak + - name: Install nginx reverse proxy virtualhost for Keycloak template: src: "keycloak_nginx.conf.j2" dest: "/etc/nginx/sites-available/keycloak" diff --git a/playbooks/install-nextcloud-sso-snap.yml b/playbooks/install-nextcloud-sso-snap.yml index 4e7177e..5e892b3 100644 --- a/playbooks/install-nextcloud-sso-snap.yml +++ b/playbooks/install-nextcloud-sso-snap.yml @@ -52,17 +52,17 @@ # We can create a SAML client at the Keycloak side. And use REST to # configure at the CMDBuild side. - #################################################### + #################################################### # Create a SAML client at the Keycloak side. - #################################################### - + #################################################### + # Generate a key on the nextcloud server - name: Generate key on nextcloud server shell: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /tmp - # Start getting auth token + # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server uri: @@ -71,13 +71,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" @@ -86,7 +86,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -126,14 +126,14 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor @@ -142,10 +142,10 @@ url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" +# Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: yes register: idp_metadata - debug: @@ -159,13 +159,13 @@ # content: attribute # register: instance_attributes - # We are going to save the XML metadate for shell processing + # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing copy: - content: "{{ idp_metadata.content }}" + content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml - # We are going to use a shell command to retrieve the value for X509Certificate + # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate shell: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml @@ -211,7 +211,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ nextcloud_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -235,13 +235,13 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - + - debug: var: deleteclient @@ -273,14 +273,14 @@ body: "{{ jsonbody }}" status_code: 201 register: createclientresult - + # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID debug: var: createclientresult.location - - name: Put nextcloud OCC commands together in one Ansbible block + - name: Put nextcloud OCC commands together in one Ansbible block # ########################################################################## # # Start of Nexctcloud OCC commands block # ########################################################################## @@ -302,18 +302,18 @@ shell: cmd: "nextcloud.occ config:system:set trusted_domains 1 --value='nc.{{ domain }}'" chdir: /snap/nextcloud/current - + - name: Enable already installed but default disabled Nextcloud External Storage support shell: cmd: nextcloud.occ app:enable files_external chdir: /snap/nextcloud/current - + - name: Retrieve list of currently installed apps shell: cmd: nextcloud.occ app:list chdir: /snap/nextcloud/current register: app_list - + - name: Install and enable all the Nextcloud apps we like shell: cmd: nextcloud.occ app:install "{{ item }}" @@ -330,8 +330,8 @@ # # Thhis could be useful: https://janikvonrotz.ch/2020/04/21/configure-saml-authentication-for-nextcloud-with-keycloack/ # - # In case you lose connection to your nextcloud account, you can try to connect login - # to http://nextcloud.my.domain/login?direct=1 to login with your admin account again. + # In case you lose connection to your nextcloud account, you can try to connect login + # to http://nextcloud.my.domain/login?direct=1 to login with your admin account again. # # Make sure some settings are in place # occ config:list @@ -343,14 +343,14 @@ # "general-require_provisioned_account": "1", # "general-allow_multiple_user_back_ends": "0" - # If 0 then autocreate users, if 1, then not... + # If 0 then autocreate users, if 1, then not... # Set this to 1 and try to solve it yourself... - name: Configure Only allow authentication if an account exists on some other backend (e.g. LDAP). shell: cmd: nextcloud.occ config:app:set user_saml general-require_provisioned_account --value="0" chdir: /snap/nextcloud/current - # Disable this setting (means set it to 1) once Keycloak connection works. + # Disable this setting (means set it to 1) once Keycloak connection works. - name: Configure Allow the use of multiple user back-ends (e.g. LDAP) shell: # Yes, I know, a zero to switch it on is weird. @@ -384,49 +384,49 @@ # Identifier of the IdP entry : https://keycloak.my.domain/auth/realms/ # URL, Target of the IdP where the SP will send the Authentication Request Message : https://keycloak.my.domain/auth/realms//protocol/saml # URL Location of the IdP where the SP will send SLO Request : https://keycloak.my.domain/auth/realms//protocol/saml - # Public X.509 certificate of the IdP : leave empty + # Public X.509 certificate of the IdP : leave empty # - # saml:config:set - # [--general-idp0_display_name GENERAL-IDP0_DISPLAY_NAME] - # [--general-uid_mapping GENERAL-UID_MAPPING] - # [--idp-entityId IDP-ENTITYID] - # [--idp-singleLogoutService.responseUrl IDP-SINGLELOGOUTSERVICE.RESPONSEURL] - # [--idp-singleLogoutService.url IDP-SINGLELOGOUTSERVICE.URL] - # [--idp-singleSignOnService.url IDP-SINGLESIGNONSERVICE.URL] - # [--idp-x509cert IDP-X509CERT] - # [--security-authnRequestsSigned SECURITY-AUTHNREQUESTSSIGNED] - # [--security-general SECURITY-GENERAL] - # [--security-logoutRequestSigned SECURITY-LOGOUTREQUESTSIGNED] - # [--security-logoutResponseSigned SECURITY-LOGOUTRESPONSESIGNED] - # [--security-lowercaseUrlencoding SECURITY-LOWERCASEURLENCODING] - # [--security-nameIdEncrypted SECURITY-NAMEIDENCRYPTED] - # [--security-offer SECURITY-OFFER] - # [--security-required SECURITY-REQUIRED] - # [--security-signatureAlgorithm SECURITY-SIGNATUREALGORITHM] - # [--security-signMetadata SECURITY-SIGNMETADATA] - # [--security-sloWebServerDecode SECURITY-SLOWEBSERVERDECODE] - # [--security-wantAssertionsEncrypted SECURITY-WANTASSERTIONSENCRYPTED] - # [--security-wantAssertionsSigned SECURITY-WANTASSERTIONSSIGNED] - # [--security-wantMessagesSigned SECURITY-WANTMESSAGESSIGNED] - # [--security-wantNameId SECURITY-WANTNAMEID] - # [--security-wantNameIdEncrypted SECURITY-WANTNAMEIDENCRYPTED] - # [--security-wantXMLValidation SECURITY-WANTXMLVALIDATION] - # [--saml-attribute-mapping-displayName_mapping SAML-ATTRIBUTE-MAPPING-DISPLAYNAME_MAPPING] - # [--saml-attribute-mapping-email_mapping SAML-ATTRIBUTE-MAPPING-EMAIL_MAPPING] - # [--saml-attribute-mapping-group_mapping SAML-ATTRIBUTE-MAPPING-GROUP_MAPPING] - # [--saml-attribute-mapping-home_mapping SAML-ATTRIBUTE-MAPPING-HOME_MAPPING] - # [--saml-attribute-mapping-quota_mapping SAML-ATTRIBUTE-MAPPING-QUOTA_MAPPING] - # [--sp-x509cert SP-X509CERT] - # [--sp-name-id-format SP-NAME-ID-FORMAT] - # [--sp-privateKey SP-PRIVATEKEY] + # saml:config:set + # [--general-idp0_display_name GENERAL-IDP0_DISPLAY_NAME] + # [--general-uid_mapping GENERAL-UID_MAPPING] + # [--idp-entityId IDP-ENTITYID] + # [--idp-singleLogoutService.responseUrl IDP-SINGLELOGOUTSERVICE.RESPONSEURL] + # [--idp-singleLogoutService.url IDP-SINGLELOGOUTSERVICE.URL] + # [--idp-singleSignOnService.url IDP-SINGLESIGNONSERVICE.URL] + # [--idp-x509cert IDP-X509CERT] + # [--security-authnRequestsSigned SECURITY-AUTHNREQUESTSSIGNED] + # [--security-general SECURITY-GENERAL] + # [--security-logoutRequestSigned SECURITY-LOGOUTREQUESTSIGNED] + # [--security-logoutResponseSigned SECURITY-LOGOUTRESPONSESIGNED] + # [--security-lowercaseUrlencoding SECURITY-LOWERCASEURLENCODING] + # [--security-nameIdEncrypted SECURITY-NAMEIDENCRYPTED] + # [--security-offer SECURITY-OFFER] + # [--security-required SECURITY-REQUIRED] + # [--security-signatureAlgorithm SECURITY-SIGNATUREALGORITHM] + # [--security-signMetadata SECURITY-SIGNMETADATA] + # [--security-sloWebServerDecode SECURITY-SLOWEBSERVERDECODE] + # [--security-wantAssertionsEncrypted SECURITY-WANTASSERTIONSENCRYPTED] + # [--security-wantAssertionsSigned SECURITY-WANTASSERTIONSSIGNED] + # [--security-wantMessagesSigned SECURITY-WANTMESSAGESSIGNED] + # [--security-wantNameId SECURITY-WANTNAMEID] + # [--security-wantNameIdEncrypted SECURITY-WANTNAMEIDENCRYPTED] + # [--security-wantXMLValidation SECURITY-WANTXMLVALIDATION] + # [--saml-attribute-mapping-displayName_mapping SAML-ATTRIBUTE-MAPPING-DISPLAYNAME_MAPPING] + # [--saml-attribute-mapping-email_mapping SAML-ATTRIBUTE-MAPPING-EMAIL_MAPPING] + # [--saml-attribute-mapping-group_mapping SAML-ATTRIBUTE-MAPPING-GROUP_MAPPING] + # [--saml-attribute-mapping-home_mapping SAML-ATTRIBUTE-MAPPING-HOME_MAPPING] + # [--saml-attribute-mapping-quota_mapping SAML-ATTRIBUTE-MAPPING-QUOTA_MAPPING] + # [--sp-x509cert SP-X509CERT] + # [--sp-name-id-format SP-NAME-ID-FORMAT] + # [--sp-privateKey SP-PRIVATEKEY] # [--output [OUTPUT]] [--] # # sudo -u www-data nextcloud.occ saml:config:get # - 1: # - general-uid_mapping: username # - general-idp0_display_name: Keycloak - # - sp-x509cert: - # - sp-privateKey: + # - sp-x509cert: + # - sp-privateKey: # - idp-entityId: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN # - idp-singleSignOnService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml # - idp-singleLogoutService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml @@ -434,7 +434,7 @@ # - general-idp0_display_name: Keycloak # PLEASE NOTE: No '--value' to set the value! - - name: Saml configuration set general-idp0_display_name + - name: Saml configuration set general-idp0_display_name shell: cmd: nextcloud.occ saml:config:set --general-idp0_display_name "Keycloak" 1 chdir: /snap/nextcloud/current @@ -456,7 +456,7 @@ # - idp-entityId: https://ad.{{ domain }}/realms/ONESTEIN.LAN # https://nextcloud.yourdomain.com/index.php/apps/user_saml/metadata ? # What is an Entity ID: https://spaces.at.internet2.edu/display/federation/saml-metadata-entityid - - name: Saml configuration set idp-entityId + - name: Saml configuration set idp-entityId shell: # cmd: nextcloud.occ saml:config:set --idp-entityId "{{ nextcloud_server_url }}/index.php/apps/user_saml/metadata" 1 cmd: nextcloud.occ saml:config:set --idp-entityId "{{ keycloak_server_url }}/realms/{{ realm }}" 1 @@ -464,14 +464,14 @@ chdir: /snap/nextcloud/current # - idp-singleSignOnService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml - # --idp-singleSignOnService.url + # --idp-singleSignOnService.url - name: Saml configuration set general-uid_mapping shell: cmd: nextcloud.occ saml:config:set --idp-singleSignOnService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 chdir: /snap/nextcloud/current # - idp-singleLogoutService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml - # --idp-singleLogoutService.url + # --idp-singleLogoutService.url - name: Saml configuration set general-uid_mapping shell: cmd: nextcloud.occ saml:config:set --idp-singleLogoutService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 @@ -508,20 +508,20 @@ # chdir: /snap/nextcloud/current # sp_certs_not_found_and_required, idp_cert_or_fingerprint_not_found_and_required - # [--sp-x509cert SP-X509CERT] - # [--sp-privateKey SP-PRIVATEKEY] - - name: Saml configuration set sp-x509cert + # [--sp-x509cert SP-X509CERT] + # [--sp-privateKey SP-PRIVATEKEY] + - name: Saml configuration set sp-x509cert shell: cmd: nextcloud.occ saml:config:set --sp-x509cert "{{ sp_crt.stdout }}" 1 chdir: /snap/nextcloud/current - - name: Saml configuration set sp-privateKey + - name: Saml configuration set sp-privateKey shell: cmd: nextcloud.occ saml:config:set --sp-privateKey "{{ sp_key.stdout }}" 1 chdir: /snap/nextcloud/current - # [--idp-x509cert IDP-X509CERT] - - name: Saml configuration set idp-x509cert + # [--idp-x509cert IDP-X509CERT] + - name: Saml configuration set idp-x509cert shell: cmd: nextcloud.occ saml:config:set --idp-x509cert "{{ idp_crt }}" 1 chdir: /snap/nextcloud/current @@ -532,7 +532,7 @@ cmd: nextcloud.occ saml:config:get chdir: /snap/nextcloud/current register: ncconfig - + ########################################################################## # Closing of Nexctcloud OCC commands block ########################################################################## @@ -549,9 +549,9 @@ - name: Post-install message IT IS IMPORTANT TO READ THIS pause: - prompt: | + prompt: | ******************************************************************************************************** - * Nextcloud is installed in /snap/nextcloud/current. + * Nextcloud is installed in /snap/nextcloud/current. * Here you can also find the database config in /snap/nextcloud/current/config/config.php * Nextcloud data is stored in /var/lib/nextcloud. * Default login after installation is admin/admin diff --git a/playbooks/install-nextcloud-sso.yml b/playbooks/install-nextcloud-sso.yml index 7c65f9e..c27442a 100644 --- a/playbooks/install-nextcloud-sso.yml +++ b/playbooks/install-nextcloud-sso.yml @@ -43,7 +43,7 @@ apt: name: "{{ item }}" loop: - - postgresql + - postgresql - autopostgresqlbackup - nginx - jq @@ -91,13 +91,13 @@ - name: Setup nextcloud Postgresql database become: true become_user: postgres - postgresql_db: + postgresql_db: name: nextcloud template: 'template0' owner: nextcloud state: present - - name: Make SSL dir for nginx + - name: Make SSL dir for nginx ansible.builtin.file: path: /etc/nginx/ssl state: directory @@ -110,7 +110,7 @@ owner: root group: root mode: '0600' - loop: + loop: - _wildcard.{{ domain }}.pem - _wildcard.{{ domain }}-key.pem @@ -198,7 +198,7 @@ name: php7.4-fpm enabled: yes state: restarted - + - name: start and enable redis systemd: name: redis @@ -217,17 +217,17 @@ # We can create a SAML client at the Keycloak side. And use REST to # configure at the CMDBuild side. - #################################################### + #################################################### # Create a SAML client at the Keycloak side. - #################################################### - + #################################################### + # Generate a key on the nextcloud server - name: Generate key on nextcloud server shell: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /tmp - # Start getting auth token + # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server uri: @@ -236,13 +236,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" @@ -251,7 +251,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -291,14 +291,14 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor @@ -307,10 +307,10 @@ url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" +# Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: yes register: idp_metadata - debug: @@ -324,13 +324,13 @@ # content: attribute # register: instance_attributes - # We are going to save the XML metadate for shell processing + # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing copy: - content: "{{ idp_metadata.content }}" + content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml - # We are going to use a shell command to retrieve the value for X509Certificate + # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate shell: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml @@ -376,7 +376,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ nextcloud_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -400,13 +400,13 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - + - debug: var: deleteclient @@ -435,7 +435,7 @@ body: "{{ jsonbody }}" status_code: 201 register: createclientresult - + # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID @@ -464,18 +464,18 @@ shell: cmd: php occ config:system:set trusted_domains 1 --value='nc.{{ domain }}' chdir: /var/www/html/nextcloud - + - name: Enable already installed but default disabled Nextcloud External Storage support shell: cmd: php occ app:enable files_external chdir: /var/www/html/nextcloud - + - name: Retrieve list of currently installed apps shell: cmd: php occ app:list chdir: /var/www/html/nextcloud register: app_list - + - name: Install and enable all the Nextcloud apps we like shell: cmd: php occ app:install "{{ item }}" @@ -492,8 +492,8 @@ # # Thhis could be useful: https://janikvonrotz.ch/2020/04/21/configure-saml-authentication-for-nextcloud-with-keycloack/ # - # In case you lose connection to your nextcloud account, you can try to connect login - # to http://nextcloud.my.domain/login?direct=1 to login with your admin account again. + # In case you lose connection to your nextcloud account, you can try to connect login + # to http://nextcloud.my.domain/login?direct=1 to login with your admin account again. # # Make sure some settings are in place # occ config:list @@ -505,13 +505,13 @@ # "general-require_provisioned_account": "1", # "general-allow_multiple_user_back_ends": "0" - # If 0 then autocreate users, if 1, then not... + # If 0 then autocreate users, if 1, then not... - name: Configure Only allow authentication if an account exists on some other backend (e.g. LDAP). shell: cmd: php occ config:app:set user_saml general-require_provisioned_account --value="0" chdir: /var/www/html/nextcloud - # Disable this setting (means set it to 1) once Keycloak connection works. + # Disable this setting (means set it to 1) once Keycloak connection works. - name: Configure Allow the use of multiple user back-ends (e.g. LDAP) shell: # Yes, I know, a zero to switch it on is weird. @@ -545,49 +545,49 @@ # Identifier of the IdP entry : https://keycloak.my.domain/auth/realms/ # URL, Target of the IdP where the SP will send the Authentication Request Message : https://keycloak.my.domain/auth/realms//protocol/saml # URL Location of the IdP where the SP will send SLO Request : https://keycloak.my.domain/auth/realms//protocol/saml - # Public X.509 certificate of the IdP : leave empty + # Public X.509 certificate of the IdP : leave empty # - # saml:config:set - # [--general-idp0_display_name GENERAL-IDP0_DISPLAY_NAME] - # [--general-uid_mapping GENERAL-UID_MAPPING] - # [--idp-entityId IDP-ENTITYID] - # [--idp-singleLogoutService.responseUrl IDP-SINGLELOGOUTSERVICE.RESPONSEURL] - # [--idp-singleLogoutService.url IDP-SINGLELOGOUTSERVICE.URL] - # [--idp-singleSignOnService.url IDP-SINGLESIGNONSERVICE.URL] - # [--idp-x509cert IDP-X509CERT] - # [--security-authnRequestsSigned SECURITY-AUTHNREQUESTSSIGNED] - # [--security-general SECURITY-GENERAL] - # [--security-logoutRequestSigned SECURITY-LOGOUTREQUESTSIGNED] - # [--security-logoutResponseSigned SECURITY-LOGOUTRESPONSESIGNED] - # [--security-lowercaseUrlencoding SECURITY-LOWERCASEURLENCODING] - # [--security-nameIdEncrypted SECURITY-NAMEIDENCRYPTED] - # [--security-offer SECURITY-OFFER] - # [--security-required SECURITY-REQUIRED] - # [--security-signatureAlgorithm SECURITY-SIGNATUREALGORITHM] - # [--security-signMetadata SECURITY-SIGNMETADATA] - # [--security-sloWebServerDecode SECURITY-SLOWEBSERVERDECODE] - # [--security-wantAssertionsEncrypted SECURITY-WANTASSERTIONSENCRYPTED] - # [--security-wantAssertionsSigned SECURITY-WANTASSERTIONSSIGNED] - # [--security-wantMessagesSigned SECURITY-WANTMESSAGESSIGNED] - # [--security-wantNameId SECURITY-WANTNAMEID] - # [--security-wantNameIdEncrypted SECURITY-WANTNAMEIDENCRYPTED] - # [--security-wantXMLValidation SECURITY-WANTXMLVALIDATION] - # [--saml-attribute-mapping-displayName_mapping SAML-ATTRIBUTE-MAPPING-DISPLAYNAME_MAPPING] - # [--saml-attribute-mapping-email_mapping SAML-ATTRIBUTE-MAPPING-EMAIL_MAPPING] - # [--saml-attribute-mapping-group_mapping SAML-ATTRIBUTE-MAPPING-GROUP_MAPPING] - # [--saml-attribute-mapping-home_mapping SAML-ATTRIBUTE-MAPPING-HOME_MAPPING] - # [--saml-attribute-mapping-quota_mapping SAML-ATTRIBUTE-MAPPING-QUOTA_MAPPING] - # [--sp-x509cert SP-X509CERT] - # [--sp-name-id-format SP-NAME-ID-FORMAT] - # [--sp-privateKey SP-PRIVATEKEY] + # saml:config:set + # [--general-idp0_display_name GENERAL-IDP0_DISPLAY_NAME] + # [--general-uid_mapping GENERAL-UID_MAPPING] + # [--idp-entityId IDP-ENTITYID] + # [--idp-singleLogoutService.responseUrl IDP-SINGLELOGOUTSERVICE.RESPONSEURL] + # [--idp-singleLogoutService.url IDP-SINGLELOGOUTSERVICE.URL] + # [--idp-singleSignOnService.url IDP-SINGLESIGNONSERVICE.URL] + # [--idp-x509cert IDP-X509CERT] + # [--security-authnRequestsSigned SECURITY-AUTHNREQUESTSSIGNED] + # [--security-general SECURITY-GENERAL] + # [--security-logoutRequestSigned SECURITY-LOGOUTREQUESTSIGNED] + # [--security-logoutResponseSigned SECURITY-LOGOUTRESPONSESIGNED] + # [--security-lowercaseUrlencoding SECURITY-LOWERCASEURLENCODING] + # [--security-nameIdEncrypted SECURITY-NAMEIDENCRYPTED] + # [--security-offer SECURITY-OFFER] + # [--security-required SECURITY-REQUIRED] + # [--security-signatureAlgorithm SECURITY-SIGNATUREALGORITHM] + # [--security-signMetadata SECURITY-SIGNMETADATA] + # [--security-sloWebServerDecode SECURITY-SLOWEBSERVERDECODE] + # [--security-wantAssertionsEncrypted SECURITY-WANTASSERTIONSENCRYPTED] + # [--security-wantAssertionsSigned SECURITY-WANTASSERTIONSSIGNED] + # [--security-wantMessagesSigned SECURITY-WANTMESSAGESSIGNED] + # [--security-wantNameId SECURITY-WANTNAMEID] + # [--security-wantNameIdEncrypted SECURITY-WANTNAMEIDENCRYPTED] + # [--security-wantXMLValidation SECURITY-WANTXMLVALIDATION] + # [--saml-attribute-mapping-displayName_mapping SAML-ATTRIBUTE-MAPPING-DISPLAYNAME_MAPPING] + # [--saml-attribute-mapping-email_mapping SAML-ATTRIBUTE-MAPPING-EMAIL_MAPPING] + # [--saml-attribute-mapping-group_mapping SAML-ATTRIBUTE-MAPPING-GROUP_MAPPING] + # [--saml-attribute-mapping-home_mapping SAML-ATTRIBUTE-MAPPING-HOME_MAPPING] + # [--saml-attribute-mapping-quota_mapping SAML-ATTRIBUTE-MAPPING-QUOTA_MAPPING] + # [--sp-x509cert SP-X509CERT] + # [--sp-name-id-format SP-NAME-ID-FORMAT] + # [--sp-privateKey SP-PRIVATEKEY] # [--output [OUTPUT]] [--] # # sudo -u www-data php occ saml:config:get # - 1: # - general-uid_mapping: username # - general-idp0_display_name: Keycloak - # - sp-x509cert: - # - sp-privateKey: + # - sp-x509cert: + # - sp-privateKey: # - idp-entityId: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN # - idp-singleSignOnService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml # - idp-singleLogoutService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml @@ -595,7 +595,7 @@ # - general-idp0_display_name: Keycloak # PLEASE NOTE: No '--value' to set the value! - - name: Saml configuration set general-idp0_display_name + - name: Saml configuration set general-idp0_display_name shell: cmd: php occ saml:config:set --general-idp0_display_name "Keycloak" 1 chdir: /var/www/html/nextcloud @@ -617,7 +617,7 @@ # - idp-entityId: https://ad.{{ domain }}/realms/ONESTEIN.LAN # https://nextcloud.yourdomain.com/index.php/apps/user_saml/metadata ? # What is an Entity ID: https://spaces.at.internet2.edu/display/federation/saml-metadata-entityid - - name: Saml configuration set idp-entityId + - name: Saml configuration set idp-entityId shell: # cmd: php occ saml:config:set --idp-entityId "{{ nextcloud_server_url }}/index.php/apps/user_saml/metadata" 1 cmd: php occ saml:config:set --idp-entityId "{{ keycloak_server_url }}/realms/{{ realm }}" 1 @@ -625,14 +625,14 @@ chdir: /var/www/html/nextcloud # - idp-singleSignOnService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml - # --idp-singleSignOnService.url + # --idp-singleSignOnService.url - name: Saml configuration set general-uid_mapping shell: cmd: php occ saml:config:set --idp-singleSignOnService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 chdir: /var/www/html/nextcloud # - idp-singleLogoutService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml - # --idp-singleLogoutService.url + # --idp-singleLogoutService.url - name: Saml configuration set general-uid_mapping shell: cmd: php occ saml:config:set --idp-singleLogoutService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 @@ -670,20 +670,20 @@ # sp_certs_not_found_and_required, idp_cert_or_fingerprint_not_found_and_required - # [--sp-x509cert SP-X509CERT] - # [--sp-privateKey SP-PRIVATEKEY] - - name: Saml configuration set sp-x509cert + # [--sp-x509cert SP-X509CERT] + # [--sp-privateKey SP-PRIVATEKEY] + - name: Saml configuration set sp-x509cert shell: cmd: php occ saml:config:set --sp-x509cert "{{ sp_crt.stdout }}" 1 chdir: /var/www/html/nextcloud - - name: Saml configuration set sp-privateKey + - name: Saml configuration set sp-privateKey shell: cmd: php occ saml:config:set --sp-privateKey "{{ sp_key.stdout }}" 1 chdir: /var/www/html/nextcloud - # [--idp-x509cert IDP-X509CERT] - - name: Saml configuration set idp-x509cert + # [--idp-x509cert IDP-X509CERT] + - name: Saml configuration set idp-x509cert shell: cmd: php occ saml:config:set --idp-x509cert "{{ idp_crt }}" 1 chdir: /var/www/html/nextcloud @@ -694,7 +694,7 @@ cmd: php occ saml:config:get chdir: /var/www/html/nextcloud register: ncconfig - + ########################################################################## # Closing of Nexctcloud OCC commands block ########################################################################## @@ -711,9 +711,9 @@ - name: Post-install message IT IS IMPORTANT TO READ THIS debug: - msg: | + msg: | ******************************************************************************************************** - * Nextcloud is installed in /var/www/html/nextcloud. + * Nextcloud is installed in /var/www/html/nextcloud. * Here you can also find the database config in /var/www/html/nextcloud/config/config.php * Nextcloud data is stored in /var/lib/nextcloud. * Default login after installation is admin/admin diff --git a/playbooks/install-odoo-sso.yml b/playbooks/install-odoo-sso.yml index f38468b..8da53f2 100644 --- a/playbooks/install-odoo-sso.yml +++ b/playbooks/install-odoo-sso.yml @@ -31,10 +31,10 @@ - name: Check if /opt/odoo/odoo-server/odoo-bin exists stat: - path: /opt/odoo/odoo-server/odoo-bin + path: /opt/odoo/odoo-server/odoo-bin register: stat_odoo_bin - - name: Fail when odoo-bin not at /opt/odoo/odoo-server/odoo-bin + - name: Fail when odoo-bin not at /opt/odoo/odoo-server/odoo-bin fail: when: stat_odoo_bin.stat.exists == false @@ -46,7 +46,7 @@ - debug: var: stat_custom_addon_dir - - name: Fail when /opt/odoo/custom/addons is not an existing directory + - name: Fail when /opt/odoo/custom/addons is not an existing directory fail: when: stat_custom_addon_dir.stat.exists == false or stat_custom_addon_dir.stat.isdir == false @@ -61,7 +61,7 @@ register: installed_odoo - name: Get the last 4 characters from version string - set_fact: + set_fact: odoo_version: "{{ installed_odoo.stdout[-4:] }}" - debug: @@ -94,8 +94,8 @@ - xmlsec1 - python3-pysaml2 - xmlstarlet - - # Original at https://orus.io/xcg/auth_saml + + # Original at https://orus.io/xcg/auth_saml # These days at https://github.com/OCA/server-auth/tree/{{ odoo_version }}/auth_saml - name: checkout OCA repo server-auth branch "{{ odoo_version }}" to temporary storage directory ansible.builtin.git: @@ -117,7 +117,7 @@ state: restarted # Copy ssl keys to fix nginx - - name: Make SSL dir for nginx + - name: Make SSL dir for nginx ansible.builtin.file: path: /etc/nginx/ssl state: directory @@ -130,7 +130,7 @@ owner: root group: root mode: '0600' - loop: + loop: - _wildcard.{{ domain }}.pem - _wildcard.{{ domain }}-key.pem @@ -144,17 +144,17 @@ # We can create a SAML client at the Keycloak side. And use REST to # configure at the CMDBuild side. - #################################################### + #################################################### # Create a SAML client at the Keycloak side. - #################################################### - + #################################################### + # Generate a key on the odoo server - name: Generate key on odoo server shell: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj "/CN={{ domain }}" chdir: /tmp - # Start getting auth token + # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server uri: @@ -163,13 +163,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" @@ -178,7 +178,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -218,14 +218,14 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.onestein.lan/realms/ONESTEIN.LAN/protocol/saml/descriptor @@ -234,10 +234,10 @@ url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" +# Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: yes register: idp_metadata - debug: @@ -251,13 +251,13 @@ # content: attribute # register: instance_attributes - # We are going to save the XML metadate for shell processing + # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing copy: - content: "{{ idp_metadata.content }}" + content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml - # We are going to use a shell command to retrieve the value for X509Certificate + # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate shell: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml @@ -323,7 +323,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ odoo_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -347,13 +347,13 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - + - debug: var: deleteclient @@ -382,7 +382,7 @@ body: "{{ jsonbody }}" status_code: 201 register: createclientresult - + # Good news! Result contains location of new client id in location # Example: "location": "https://ad.onestein.lan:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID diff --git a/playbooks/install-xwiki-sso.yml b/playbooks/install-xwiki-sso.yml index 9c2ca03..2a4c44c 100644 --- a/playbooks/install-xwiki-sso.yml +++ b/playbooks/install-xwiki-sso.yml @@ -26,7 +26,7 @@ url: https://maven.xwiki.org/xwiki-keyring.gpg dest: /usr/share/keyrings/xwiki-keyring.gpg mode: '0644' - + - name: Download Xwiki repo # sudo wget "https://maven.xwiki.org/stable/xwiki-stable.list" -P /etc/apt/sources.list.d/ # Note that there is several repositories you can choose (as alternatives to the stable one you have in the previous example): @@ -38,7 +38,7 @@ dest: /etc/apt/sources.list.d/xwiki-lts.list mode: '0644' - - name: Update repositories cache + - name: Update repositories cache apt: update_cache: yes @@ -55,8 +55,8 @@ - xwiki-tomcat9-pgsql - libreoffice - autopostgresqlbackup - - mlocate - - openssl + - mlocate + - openssl - apache2 - name: Make sure no nginx installed @@ -85,7 +85,7 @@ state: present name: rewrite - - name: Make SSL dir for apache + - name: Make SSL dir for apache ansible.builtin.file: path: /etc/apache2/ssl state: directory @@ -98,11 +98,11 @@ owner: root group: root mode: '0600' - loop: + loop: - _wildcard.{{ domain }}.pem - _wildcard.{{ domain }}-key.pem - - name: Install Apache reverse proxy virtualhost for Keycloak + - name: Install Apache reverse proxy virtualhost for Keycloak template: src: "xwiki_apache.conf" dest: "/etc/apache2/sites-available/xwiki.conf" @@ -120,7 +120,7 @@ enabled: yes state: restarted - + - name: remove existing filestore if exists file: path: /etc/tomcat9/keystore.pkcs12 @@ -138,7 +138,7 @@ chdir: /etc/tomcat9/ # Enable Tomcat9 ssl connector on 8443 - # Do not try to do this the hard way with some Ansible XML magix. It doesn't work. + # Do not try to do this the hard way with some Ansible XML magix. It doesn't work. # Simply copy from local files - name: Overwrite /etc/tomcat9/server.xml with enabled https connector definition from local file copy: @@ -215,7 +215,7 @@ regexp: ".*url.trustedDomains=.*" line: "url.trustedDomains={{ domain }}" - + #-# Allow to enable or disable checks performed on domains by taking into account the list of trusted domains. #-# Disable this property only if you experienced some issues on your wiki: some security check won't be performed when #-# this property is set to false. @@ -226,23 +226,23 @@ # Find xwiki.authentication.authclass in xwiki.cfg and comment it out with #-# in the beginning. # Add below: xwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl - name: Enable Keycloak SSO oidc auth services in Xwiki - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.cfg regexp: ".*xwiki.authentication.authclass=.*" line: "xwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl" # xwiki.home=https://MYWIKIDOMAIN/ - name: Set xwiki.home in xwiki.cfg - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.cfg regexp: ".*xwiki.home=.*" - line: "xwiki.home={{ xwiki_server_url }}" + line: "xwiki.home={{ xwiki_server_url }}" # Open xwiki.properties and adapt the following to your settings, and add this at the end of the file (Note the __XXX__ parts): # oidc.xwikiprovider=https://__YOUR-WIKI-ADDRESS__/xwiki/oidc - name: Configure oidc.xwikiprovider - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.xwikiprovider=.*" line: "oidc.xwikiprovider={{ xwiki_server_url }}/xwiki/oidc" @@ -250,7 +250,7 @@ # oidc.skipped=false - name: Configure - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.skipped=.*" line: "oidc.skipped=false" @@ -273,13 +273,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint # Curl example: curl -k GET "https://ad.onestein.lan:8443/realms/ONESTEIN.LAN/.well-known/openid-configuration" # Example output: # "issuer": "https://ad.onestein.lan:8443/realms/ONESTEIN.LAN", @@ -301,7 +301,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -323,7 +323,7 @@ # Relocated this code to here so we can generate better strings based on result of prior REST call # oidc.endpoint.authorization=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/auth - name: Configure oidc.endpoint.authorization - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.authorization=.*" line: "oidc.endpoint.authorization={{ authorization_endpoint }}" @@ -331,7 +331,7 @@ # oidc.endpoint.token=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/token - name: Configure oidc.endpoint.token - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.token=.*" line: "oidc.endpoint.token={{ token_endpoint }}" @@ -339,7 +339,7 @@ # oidc.endpoint.userinfo=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/userinfo - name: Configure oidc.endpoint.userinfo - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.userinfo=.*" line: "oidc.endpoint.userinfo={{ userinfo_endpoint }}" @@ -347,13 +347,13 @@ # oidc.scope=openid,profile,email,address - name: Configure oidc.scope - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.scope=.*" line: "oidc.scope=openid,profile,email,address" - name: Configure - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.defaultClientConfiguration=.*" line: "oidc.defaultClientConfiguration=default" @@ -361,21 +361,21 @@ # oidc.endpoint.userinfo.method=GET - name: Configure oidc.endpoint.userinfo.method - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.userinfo.method=.*" line: "oidc.endpoint.userinfo.method=GET" # oidc.user.nameFormater=${oidc.user.preferredUsername._clean._lowerCase} - name: Configure oidc.user.nameFormater - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.user.nameFormater=.*" line: "oidc.user.nameFormater=${oidc.user.preferredUsername._clean._lowerCase}" # oidc.user.subjectFormater=${oidc.user.subject} - name: Configure oidc.user.subjectFormater - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.user.subjectFormater=.*" line: "oidc.user.subjectFormater=${oidc.user.subject}" @@ -389,21 +389,21 @@ # oidc.userinfoclaims=xwiki_user_accessibility,xwiki_user_company,xwiki_user_displayHiddenDocuments,xwiki_user_editor,xwiki_user_usertype - name: Configure oidc.userinfoclaims - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.userinfoclaims=.*" line: "oidc.userinfoclaims=xwiki_user_accessibility,xwiki_user_company,xwiki_user_displayHiddenDocuments,xwiki_user_editor,xwiki_user_usertype" # # oidc.userinforefreshrate=600000 - name: Configure oidc.userinforefreshrate - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.userinforefreshrate=.*" line: "oidc.userinforefreshrate=600000" # oidc.clientid=__KEYCLOAK-CLIENT-ID__ - name: Configure oidc.clientid - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.clientid=.*" line: "oidc.clientid={{ xwiki_client_id }}" @@ -411,7 +411,7 @@ # oidc.endpoint.token.auth_method=client_secret_basic # Changed this to client_secret_post and retry - name: Configure oidc.endpoint.token.auth_method=client_secret_post - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.token.auth_method=.*" line: "oidc.endpoint.token.auth_method=client_secret_post" @@ -436,19 +436,19 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token ####################################################################################### # Very badly documented in Keycloak: The admin RESTful API has a base path /admin/realms/ ####################################################################################### - + # Retrieve current list of clients of our type # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.onestein.lan:8443/admin/realms/master/clients" | jq . @@ -457,7 +457,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ xwiki_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -472,13 +472,13 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - + - debug: var: deleteclient @@ -502,7 +502,7 @@ body: "{{ jsonbody }}" status_code: 201 register: createclientresult - + # Good news! Result contains location of new client id in location # Example: "location": "https://ad.onestein.lan:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID @@ -515,12 +515,12 @@ # url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ xwiki_client_id }}" # headers: # Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" +# Authorization: "Bearer {{ auth_token }}" # method: get # validate_certs: false # register: client1 # # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - + # After importing this to Keycloak, you have to generate a new Client-Secret and put it into xwiki.properties under oidc.secret=__KEYCLOAK-CLIENT-SECRET__. # Generate and retrieve a new Client-Secret to put into xwiki.properties under oidc.secret=__KEYCLOAK-CLIENT-SECRET__ # GET /{realm}/clients/{id}/client-secret @@ -529,7 +529,7 @@ url: "{{ createclientresult.location }}/client-secret" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: clientsecret @@ -540,9 +540,9 @@ # Aim is: In the end, Client-ID and Client-Secret have to match on Keycloak and in xwiki.properties. # oidc.secret=__KEYCLOAK-CLIENT-SECRET__ - + - name: Configure oidc.secret - lineinfile: + lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.secret=.*" line: "oidc.secret={{ client_secret }}" @@ -551,7 +551,7 @@ # Aim is: In the end, Client-ID and Client-Secret have to match on Keycloak and in xwiki.properties. # - name: Enable and start tomcat9 - service: + service: name: tomcat9 state: restarted enabled: yes @@ -559,7 +559,7 @@ - name: Post-install message IT IS IMPORTANT TO READ THIS debug: - msg: | + msg: | ******************************************************************************************************** * After a fresh Xwiki installation you need to manually install (within Xwiki!) the following extension: * OpenID Connect Authenticator 1.31 @@ -574,4 +574,4 @@ * After that ssh to the server and do a systemctl restart tomcat9 ******************************************************************************************************** - + diff --git a/playbooks/install-zabbix-server-sso.yml b/playbooks/install-zabbix-server-sso.yml index 6eabcc3..ade5396 100644 --- a/playbooks/install-zabbix-server-sso.yml +++ b/playbooks/install-zabbix-server-sso.yml @@ -52,7 +52,7 @@ apt: name: "{{ item }}" loop: - - postgresql + - postgresql - zabbix-agent - zabbix-sender - zabbix-get @@ -70,7 +70,7 @@ - libxml2-utils - xmlstarlet - # Edit file /etc/zabbix/zabbix_server.conf, DBPassword=password + # Edit file /etc/zabbix/zabbix_server.conf, DBPassword=password - name: Configure zabbix database password lineinfile: path: /etc/zabbix/zabbix_server.conf @@ -93,7 +93,7 @@ - name: Setup Zabbix Postgresql database become: true become_user: postgres - postgresql_db: + postgresql_db: name: zabbix template: 'template0' owner: zabbix @@ -108,15 +108,15 @@ become_user: zabbix shell: cmd: zcat /usr/share/doc/zabbix-sql-scripts/postgresql/create.sql.gz | psql zabbix - when: dbwork.changed - + when: dbwork.changed + - name: (Re)start Zabbix agent ansible.builtin.systemd: name: zabbix-agent enabled: yes state: restarted - - name: Make SSL dir for nginx + - name: Make SSL dir for nginx ansible.builtin.file: path: /etc/nginx/ssl state: directory @@ -129,14 +129,14 @@ owner: root group: root mode: '0600' - loop: + loop: - _wildcard.{{ domain }}.pem - _wildcard.{{ domain }}-key.pem - name: Install nginx config file copy: src: files/zabbix_nginx.conf - dest: /etc/nginx/sites-available/default + dest: /etc/nginx/sites-available/default # Docs describing a working setup. Zabbix currently only supports SAML out @@ -153,7 +153,7 @@ cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /usr/share/zabbix/conf/certs - # Start getting auth token + # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server uri: @@ -162,13 +162,13 @@ register: tokenurl - debug: - var: tokenurl.json["token-service"] + var: tokenurl.json["token-service"] - name: Store url for easier retrieval set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" - # Call info endpoint + # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" @@ -177,7 +177,7 @@ - debug: var: endpointinfo - + - name: Store authorization_endpoint for faster retrieval set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" @@ -217,14 +217,14 @@ var: authtoken - debug: - var: authtoken.json["access_token"] + var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - debug: - var: auth_token + var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate @@ -234,10 +234,10 @@ url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" +# Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: yes register: idp_metadata - debug: @@ -251,13 +251,13 @@ # content: attribute # register: instance_attributes - # We are going to save the XML metadate for shell processing + # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing copy: - content: "{{ idp_metadata.content }}" + content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml - # We are going to use a shell command to retrieve the value for X509Certificate + # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate shell: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml @@ -289,7 +289,7 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ zabbix_client_id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -344,13 +344,13 @@ url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - + - debug: var: deleteclient @@ -379,7 +379,7 @@ body: "{{ jsonbody }}" status_code: 201 register: createclientresult - + # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID @@ -401,7 +401,7 @@ - name: Post-install message IT IS IMPORTANT TO READ THIS debug: - msg: | + msg: | ********************************************************************************************************** * After a fresh installation of Zabbix you need to run the setup process. * Visit zabbix.{{ domain }} @@ -421,9 +421,9 @@ * * Configure users in Zabbix to use SAML * - Make sure every Zabbix user account is in email address format user@fqdn. - * - In Keycloak make sure every user has in the Attributes tab an attribute called 'zabbixuser' + * - In Keycloak make sure every user has in the Attributes tab an attribute called 'zabbixuser' * containing that same email address. - * + * * Now login to Zabbix not by entering userid/password. * But by clicking on the 'Sign in with Single Sign-On (SAML)' link. ********************************************************************************************************** From b875ef9c8ee60ca4cf7cb2b486b62fab6e32d633 Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Sun, 19 May 2024 16:29:35 +0200 Subject: [PATCH 05/12] Massive lint corrections to comply with recent standards. --- .../install-bitwarden-part-2-saml-sso.yml | 682 +++++++-------- playbooks/install-cmdb-sso.yml | 226 ++--- playbooks/install-freeipa-server.yml | 36 +- playbooks/install-gitlab-sso.yml | 124 +-- playbooks/install-jenkins-sso.yml | 225 ++--- playbooks/install-keycloak-nginx.yml | 87 +- playbooks/install-nextcloud-sso-snap.yml | 697 ++++++++------- playbooks/install-nextcloud-sso.yml | 797 +++++++++--------- playbooks/install-odoo-sso.yml | 166 ++-- playbooks/install-xwiki-sso.yml | 244 +++--- playbooks/install-zabbix-server-sso.yml | 211 +++-- 11 files changed, 1883 insertions(+), 1612 deletions(-) diff --git a/playbooks/install-bitwarden-part-2-saml-sso.yml b/playbooks/install-bitwarden-part-2-saml-sso.yml index 21f5390..8be9283 100644 --- a/playbooks/install-bitwarden-part-2-saml-sso.yml +++ b/playbooks/install-bitwarden-part-2-saml-sso.yml @@ -5,344 +5,368 @@ # Execute like the following line; - # ansible-playbook install-bitwarden.yml -i hosts -e client_id="organization." --vault-password-file .vault-password + # ansible-playbook install-bitwarden.yml -i hosts -e client_id="organization. \ + # " --vault-password-file .vault-password # Playbook to install bitwarden password server package on host. vars: root_ca_path: /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt bitwarden_server_url: https://password.{{ domain }} - #bitwarden_client_id: "client-bitwarden-{{ ansible_fqdn }}" + # bitwarden_client_id: "client-bitwarden-{{ ansible_fqdn }}" bitwarden_client_id: "{{ bitwarden_server_url }}/sso/saml2" bitwarden_client_name: "client-bitwarden-{{ ansible_fqdn }}" keycloak_server_url: https://ad.{{ domain }} tasks: - - name: test connection to host - ping: - - - name: Read global vars - include_vars: global-vars.yml - - - name: Read encrypted content - include_vars: encrypted-vars.yml - - - name: TIP - debug: - msg: Do yourself a favour; export ANSIBLE_STDOUT_CALLBACK=debug - - - # We need the uuid of the organization, so ask this from the command line - - fail: - msg: | - ========================================================================== - Fatal: We need the client_id from the organization API key! - Read the last part of the install-bitwarden-sso.yml playbook - and rerun while adding the correct argument as shown next - "-e client_id=organization.3f0c3962-29d3-11ed-bc12-07773c74e67b" - You can find this id as follows - Login to BitWarden, open your organization. - Go to "Settings" -> "My Organization" and click on the - "View API Key" button. Copy the information shown under "client_id" - ========================================================================== - when: client_id is not defined - - - name: We need to get the uuid part of the client_id - set_fact: - # Slide from right so we always have the UUID, even when string is not starting with "organization." - organization_uuid: "{{ client_id[-36:] }}" - - ############################################################################################ - # Also this needs to be fixed. - # The installation of the Bitwarden Directory Connector to sync users from ldap to bitwarden - # DO NOT EVER USE THE PIECE OF CRAP Bitwarden Directory Connector to sync users from ldap - ############################################################################################ - # SAML SSO: https://bitwarden.com/help/configure-sso-saml/ - # Install all needed software in one go. - - name: Install all needed packages - apt: - name: "{{ item }}" - loop: - - python3-lxml - - libxml2-utils - - xmlstarlet - - openssl - - # Generate a key on the Bitwarden server - - name: Generate key on Bitwarden server - shell: - cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' - chdir: /tmp - - - # Start getting auth token - # Retrieve token url needed. Returns JSON payload with var token-service. - - name: Retrieve token url from server - uri: - url: "{{ keycloak_server_url }}/realms/master" - validate_certs: false - register: tokenurl - - - debug: - var: tokenurl.json["token-service"] - - - name: Store url for easier retrieval - set_fact: - token_url: "{{ tokenurl.json[\"token-service\"] }}" - - # Call info endpoint - - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: - url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" - validate_certs: false - register: endpointinfo - - - debug: - var: endpointinfo - - - name: Store authorization_endpoint for faster retrieval - set_fact: - authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" - - # "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth", - - debug: - var: authorization_endpoint - - - name: Store token_endpoint for faster retrieval - set_fact: - token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - - debug: - var: token_endpoint - - - name: Store userinfo_endpoint for faster retrieval - set_fact: - userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: - var: userinfo_endpoint - - # Get authentication token from token-service url - - name: Retrieve authentication token from token-service url - uri: - url: "{{ tokenurl.json[\"token-service\"] }}/token" - method: POST - body_format: form-urlencoded - validate_certs: false - body: - realm: master - client_id: admin-cli - username: "{{ kc_adminid }}" - password: "{{ kc_adminpw }}" - grant_type: password - register: authtoken - - - debug: - var: authtoken - - - debug: - var: authtoken.json["access_token"] - - - name: Store access token into variable for easier retrieval - set_fact: - auth_token: "{{ authtoken.json[\"access_token\"] }}" - - - debug: - var: auth_token - - # Retrieve IDP metadata descriptor and copy the 509 formatted certificate - # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor - - name: Retrieve IDP metadata descriptor to use the 509 formatted certificate - uri: - url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" -# headers: -# Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" - method: get - validate_certs: false - return_content: yes - register: idp_metadata - - - debug: - var: idp_metadata - -# Please do not go down this road and stay sane... -# - xml: -# xmlstring: "{{ idp_metadata.content }}" -# xpath: /listResponse/instance -# xpath: /md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate -# content: attribute -# register: instance_attributes - - # We are going to save the XML metadate for shell processing - - name: Save IDP XML metadata to file for processing - copy: - content: "{{ idp_metadata.content }}" - dest: /tmp/idp.xml - - # We are going to use a shell command to retrieve the value for X509Certificate - - name: Run xmlstarlet to retrieve X509Certificate - shell: - cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml - register: xmlstarlet - - - debug: - var: xmlstarlet - - - name: Store output in certificate variable - set_fact: - idp_crt: "{{ xmlstarlet.stdout }}" - - - name: Remove first line from tmp files - lineinfile: - path: "/tmp/{{ item }}" - regexp: '^-----(BEGIN|END).*-----$' - state: absent - loop: - - sp.crt - - sp.key - - - name: Retrieve remote ssl cert - shell: - cmd: cat /tmp/sp.crt | tr -d '\n' - register: sp_crt - - - debug: - var: sp_crt - - - name: Retrieve remote ssl key - shell: - cmd: cat /tmp/sp.key | tr -d '\n' - register: sp_key - - - debug: - var: sp_key - - - # Retrieve current list of clients of our type - # So, this works in curl: - # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . - - name: Retrieve current list of clients and search for already existing "{{ bitwarden_client_id }} " - uri: - url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ bitwarden_client_id }}" - headers: + - name: Test connection to host + ansible.builtin.ping: + + - name: Read global vars + ansible.builtin.include_vars: global-vars.yml + + - name: Read encrypted content + ansible.builtin.include_vars: encrypted-vars.yml + + - name: TIP + ansible.builtin.debug: + msg: Do yourself a favour; export ANSIBLE_STDOUT_CALLBACK=debug + + + # We need the uuid of the organization, so ask this from the command line + - name: Fail if no uuid provided on command line + ansible.builtin.fail: + msg: | + ========================================================================== + Fatal: We need the client_id from the organization API key! + Read the last part of the install-bitwarden-sso.yml playbook + and rerun while adding the correct argument as shown next + "-e client_id=organization.3f0c3962-29d3-11ed-bc12-07773c74e67b" + You can find this id as follows + Login to BitWarden, open your organization. + Go to "Settings" -> "My Organization" and click on the + "View API Key" button. Copy the information shown under "client_id" + ========================================================================== + when: client_id is not defined + + - name: We need to get the uuid part of the client_id + ansible.builtin.set_fact: + # Slide from right so we always have the UUID, even when string is not starting with "organization." + organization_uuid: "{{ client_id[-36:] }}" + + ############################################################################################ + # Also this needs to be fixed. + # The installation of the Bitwarden Directory Connector to sync users from ldap to bitwarden + # DO NOT EVER USE THE PIECE OF CRAP Bitwarden Directory Connector to sync users from ldap + ############################################################################################ + # SAML SSO: https://bitwarden.com/help/configure-sso-saml/ + # Install all needed software in one go. + - name: Install all needed packages + ansible.builtin.apt: + name: "{{ item }}" + loop: + - python3-lxml + - libxml2-utils + - xmlstarlet + - openssl + + # Generate a key on the Bitwarden server + - name: Generate key on Bitwarden server + ansible.builtin.command: + cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' + chdir: /tmp + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + + # Start getting auth token + # Retrieve token url needed. Returns JSON payload with var token-service. + - name: Retrieve token url from server + ansible.builtin.uri: + url: "{{ keycloak_server_url }}/realms/master" + validate_certs: false + register: tokenurl + + - name: Show token-service + ansible.builtin.debug: + var: tokenurl.json["token-service"] + + - name: Store url for easier retrieval + ansible.builtin.set_fact: + token_url: "{{ tokenurl.json[\"token-service\"] }}" + + # Call info endpoint + - name: "Retrieve endpoint info for our realm {{ realm }}" + ansible.builtin.uri: + url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" + validate_certs: false + register: endpointinfo + + - name: Show endpopint info + ansible.builtin.debug: + var: endpointinfo + + - name: Store authorization_endpoint for faster retrieval + ansible.builtin.set_fact: + authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" + + # "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth", + - name: Show authorization endpoint + ansible.builtin.debug: + var: authorization_endpoint + + - name: Store token_endpoint for faster retrieval + ansible.builtin.set_fact: + token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" + + - name: Show token endpoint + ansible.builtin.debug: + var: token_endpoint + + - name: Store userinfo_endpoint for faster retrieval + ansible.builtin.set_fact: + userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" + + - name: Show userinfo endpoint + ansible.builtin.debug: + var: userinfo_endpoint + + # Get authentication token from token-service url + - name: Retrieve authentication token from token-service url + ansible.builtin.uri: + url: "{{ tokenurl.json[\"token-service\"] }}/token" + method: POST + body_format: form-urlencoded + validate_certs: false + body: + realm: master + client_id: admin-cli + username: "{{ kc_adminid }}" + password: "{{ kc_adminpw }}" + grant_type: password + register: authtoken + + - name: Show authtoken + ansible.builtin.debug: + var: authtoken + + - name: Show access_token + ansible.builtin.debug: + var: authtoken.json["access_token"] + + - name: Store access token into variable for easier retrieval + ansible.builtin.set_fact: + auth_token: "{{ authtoken.json[\"access_token\"] }}" + + - name: Show auth_token + ansible.builtin.debug: + var: auth_token + + # Retrieve IDP metadata descriptor and copy the 509 formatted certificate + # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor + - name: Retrieve IDP metadata descriptor to use the 509 formatted certificate + ansible.builtin.uri: + url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" + # headers: + # Accept: "application/json" + # Authorization: "Bearer {{ auth_token }}" + method: get + validate_certs: false + return_content: true + register: idp_metadata + + - name: Show idp metadata + ansible.builtin.debug: + var: idp_metadata + + # Please do not go down this road and stay sane... + # - xml: + # xmlstring: "{{ idp_metadata.content }}" + # xpath: /listResponse/instance + # xpath: /md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate + # content: attribute + # register: instance_attributes + + # We are going to save the XML metadate for shell processing + - name: Save IDP XML metadata to file for processing + ansible.builtin.copy: + content: "{{ idp_metadata.content }}" + dest: /tmp/idp.xml + mode: '0600' + + # We are going to use a shell command to retrieve the value for X509Certificate + - name: Run xmlstarlet to retrieve X509Certificate + ansible.builtin.command: + cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml + register: xmlstarlet + changed_when: xmlstartlet.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Show xmlstartlet + ansible.builtin.debug: + var: xmlstarlet + + - name: Store output in certificate variable + ansible.builtin.set_fact: + idp_crt: "{{ xmlstarlet.stdout }}" + + - name: Remove first line from tmp files + ansible.builtin.lineinfile: + path: "/tmp/{{ item }}" + regexp: '^-----(BEGIN|END).*-----$' + state: absent + loop: + - sp.crt + - sp.key + + - name: Retrieve remote ssl cert + ansible.builtin.command: + cmd: cat /tmp/sp.crt | tr -d '\n' + register: sp_crt + changed_when: sp_crt.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Show sp crt + ansible.builtin.debug: + var: sp_crt + + - name: Retrieve remote ssl key + ansible.builtin.command: + cmd: cat /tmp/sp.key | tr -d '\n' + register: sp_key + changed_when: sp_key.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Show sp key + ansible.builtin.debug: + var: sp_key + + + # Retrieve current list of clients of our type + # So, this works in curl: + # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . + - name: Retrieve current list of clients and search for already existing "{{ bitwarden_client_id }} " + ansible.builtin.uri: + url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ bitwarden_client_id }}" + headers: + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" + method: get + validate_certs: false + register: existingclient + + - name: Returned json + ansible.builtin.debug: + var: existingclient + + # - name: Copy currently existing client definition to backup file in JSON format. + # copy: + # content: "{{ existingclient }}" + # dest: /tmp/bitwarden-original-client-backup.json + + - name: Find ID in returned json + ansible.builtin.debug: + var: existingclient.json[0].id + + - name: If it already exists then delete client with id "{{ bitwarden_client_id }}". + # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" + ansible.builtin.uri: + url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" + headers: Accept: "application/json" Authorization: "Bearer {{ auth_token }}" - method: get - validate_certs: false - register: existingclient - - - name: Returned json - debug: - var: existingclient - -# - name: Copy currently existing client definition to backup file in JSON format. -# copy: -# content: "{{ existingclient }}" -# dest: /tmp/bitwarden-original-client-backup.json - - - name: Find ID in returned json - debug: - var: existingclient.json[0].id - - - name: Delete client id "{{ bitwarden_client_id }}" if it already exists. - # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: - url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" - headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" - method: DELETE - validate_certs: false - status_code: 204 - when: existingclient.json[0].id is defined - register: deleteclient - - - debug: - var: deleteclient - - - name: Convert Ninja template to variable - set_fact: - jsonbody: "{{ lookup('template', 'bitwarden-keycloak-saml-sso.json.j2') }}" - - - debug: - var: jsonbody - - - name: For debugging store json var in local file - copy: - content: "{{ jsonbody }}" - dest: /tmp/bitwarden-new-client-backup.json - - # Generate the json payload to upload - - name: Upload JSON template file to create new Client ID on Keycloak server - uri: - url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" - headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" - method: POST - validate_certs: false - body_format: json - body: "{{ jsonbody }}" - status_code: 201 - register: createclientresult - - - name: If all went well we now have a locaton of the newly created Client ID - debug: - var: createclientresult.location - -# # You can also search the client id using this piece of code -# - name: Search id of newly created client -# uri: -# url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ bitwarden_client_id }}" -# headers: -# Accept: "application/json" -# Authorization: "Bearer {{ auth_token }}" -# method: get -# validate_certs: false -# register: client1 -# # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - - - debug: - msg: | - **************************************************************************************** - * Here some stuff you need to do yourself - * - Login to your BitWarden server and open your Organization. - * - * - Open the Settings tab and enter the following unique Identifier - * for your organization "{{ organization_id }}" - * - * Once you have your Organization Identifier, you can proceed to - * enabling and configuring your integration. To enable Login with SSO - * From the Organization Vault, navigate to the Manage tab and - * select Single Sign-On from the left-hand menu. - * - * - On the Single Sign-On Screen, check the Allow SSO Authentication checkbox. - * - From the Type dropdown menu, select the "SAML 2.0" option. - * - * == SAML Service Provider Configuration == - * - SP Entity ID should read "https://password.{{ domain }}/sso/saml2" - * - SAML 2.0 Metadata URL should start with "https://password.{{ domain }}/sso/saml2/" - * - Assertion Consumer Service (ACS) URL should start with "https://password.{{ domain }}/sso/saml2" - * - Set Name ID Format to "Email Address" - * - Outbound Signing Algorithm should end with "rsa-sha256" - * - Signing Behaviour should be "If IdP Wants Authn Requests Signed" - * - Minimum Incoming Signing Algorithm should also end with "rsa-sha256" - * - Uncheck checkbox "Expect signed assertions" - * - Uncheck checkbox "Validate certificates" - * - * == SAML Identity Provider Configuration == - * - Set Entity ID to "{{ keycloak_server_url }}/realms/{{ realm }}" . - * - Set Binding Type to "HTTP POST". - * - Set Single Sign On Service Url to "{{ keycloak_server_url}}/realms/{{ realm }}/protocol/saml". - * - BitWarden does not yet support "Single Log Out Service URL". - * - Set the X509 Public Certificate to "{{ idp_crt }}" - * - Outbound Signing Algorithm should end with "rsa-sha256" - * - Uncheck checkbox "Allow outbound logout requests". - * - Uncheck checkbox "Sign authentication requests". - * - * Go to top menu 'Vaults' -> "{{ organization_name }}". - * Click the 3 dots to the right of the organization name and click on 'Link SSO'. - * - * Notes; BitWarden allows per-organization logins based on valid redirect URLs containing - * a specific organization uuid. This playbook allows one redirect URLs. - ******************************************************************************************** - - + method: DELETE + validate_certs: false + status_code: 204 + when: existingclient.json[0].id is defined + register: deleteclient + + - name: Show deleteclient + ansible.builtin.debug: + var: deleteclient + + - name: Convert Ninja template to variable + ansible.builtin.set_fact: + jsonbody: "{{ lookup('template', 'bitwarden-keycloak-saml-sso.json.j2') }}" + + - name: Show json body + ansible.builtin.debug: + var: jsonbody + + - name: For debugging store json var in local file + ansible.builtin.copy: + content: "{{ jsonbody }}" + dest: /tmp/bitwarden-new-client-backup.json + mode: '0600' + + # Generate the json payload to upload + - name: Upload JSON template file to create new Client ID on Keycloak server + ansible.builtin.uri: + url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" + headers: + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" + method: POST + validate_certs: false + body_format: json + body: "{{ jsonbody }}" + status_code: 201 + register: createclientresult + + - name: If all went well we now have a locaton of the newly created Client ID + ansible.builtin.debug: + var: createclientresult.location + + # # You can also search the client id using this piece of code + # - name: Search id of newly created client + # uri: + # url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ bitwarden_client_id }}" + # headers: + # Accept: "application/json" + # Authorization: "Bearer {{ auth_token }}" + # method: get + # validate_certs: false + # register: client1 + # # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" + + - name: Show post install message + ansible.builtin.debug: + msg: | + **************************************************************************************** + * Here some stuff you need to do yourself + * - Login to your BitWarden server and open your Organization. + * + * - Open the Settings tab and enter the following unique Identifier + * for your organization "{{ organization_id }}" + * + * Once you have your Organization Identifier, you can proceed to + * enabling and configuring your integration. To enable Login with SSO + * From the Organization Vault, navigate to the Manage tab and + * select Single Sign-On from the left-hand menu. + * + * - On the Single Sign-On Screen, check the Allow SSO Authentication checkbox. + * - From the Type dropdown menu, select the "SAML 2.0" option. + * + * == SAML Service Provider Configuration == + * - SP Entity ID should read "https://password.{{ domain }}/sso/saml2" + * - SAML 2.0 Metadata URL should start with "https://password.{{ domain }}/sso/saml2/" + * - Assertion Consumer Service (ACS) URL should start with "https://password.{{ domain }}/sso/saml2" + * - Set Name ID Format to "Email Address" + * - Outbound Signing Algorithm should end with "rsa-sha256" + * - Signing Behaviour should be "If IdP Wants Authn Requests Signed" + * - Minimum Incoming Signing Algorithm should also end with "rsa-sha256" + * - Uncheck checkbox "Expect signed assertions" + * - Uncheck checkbox "Validate certificates" + * + * == SAML Identity Provider Configuration == + * - Set Entity ID to "{{ keycloak_server_url }}/realms/{{ realm }}" . + * - Set Binding Type to "HTTP POST". + * - Set Single Sign On Service Url to "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml". + * - BitWarden does not yet support "Single Log Out Service URL". + * - Set the X509 Public Certificate to "{{ idp_crt }}" + * - Outbound Signing Algorithm should end with "rsa-sha256" + * - Uncheck checkbox "Allow outbound logout requests". + * - Uncheck checkbox "Sign authentication requests". + * + * Go to top menu 'Vaults' -> "{{ organization_name }}". + * Click the 3 dots to the right of the organization name and click on 'Link SSO'. + * + * Notes; BitWarden allows per-organization logins based on valid redirect URLs containing + * a specific organization uuid. This playbook allows one redirect URLs. + ******************************************************************************************** diff --git a/playbooks/install-cmdb-sso.yml b/playbooks/install-cmdb-sso.yml index ce7bc89..0aab74a 100644 --- a/playbooks/install-cmdb-sso.yml +++ b/playbooks/install-cmdb-sso.yml @@ -2,9 +2,6 @@ - name: Install CMDBuild hosts: grpcmdb remote_user: root - become: true - become_user: root - become_method: sudo # use this command line: ansible-playbook -v -i hosts install-cmdb.yml -u root # Execute ansible-playbook playbookfile.yml -i hosts --vault-password-file .vault-password @@ -14,26 +11,27 @@ cmdb_client_id: "client-cmdb" tasks: - - debug: + - name: Show tip for sensible output + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read global vars - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - name: Update repositories cache - apt: - update_cache: yes + ansible.builtin.apt: + update_cache: true - name: Set timezone to Europe/Amsterdam community.general.timezone: name: Europe/Amsterdam - name: Install needed packages - apt: + ansible.builtin.apt: name: "{{ item }}" state: present loop: @@ -49,8 +47,8 @@ - xmlstarlet - name: Check if war file already downloaded - stat: - path: /root/cmdbuild-3.4.war + ansible.builtin.stat: + path: /root/cmdbuild-3.4.war register: warfile_stat @@ -58,20 +56,20 @@ # Download cmdbuild-3.4.war via cmdbuild.org - name: Download CMDbuild war file with big timeout value (because it comes from Sourceforge...) # sudo wget https://maven.xwiki.org/xwiki-keyring.gpg -P /usr/share/keyrings/ - get_url: + ansible.builtin.get_url: url: https://downloads.sourceforge.net/project/cmdbuild/3.4/cmdbuild-3.4.1.war dest: /root/cmdbuild-3.4.1.war mode: '0644' - force: no + force: false timeout: 120 when: not warfile_stat.stat.exists # Copy war file to webapps dir - name: Install war file in tomcat webapps dir - copy: + ansible.builtin.copy: src: /root/cmdbuild-3.4.1.war dest: /var/lib/tomcat9/webapps/cmdbuild.war - remote_src: yes + remote_src: true mode: '0644' @@ -88,7 +86,7 @@ name: cmdbuild comment: cmdbuild server account home: /home/cmdbuild - create_home: no + create_home: false group: cmdbuild groups: syslog shell: /bin/bash @@ -99,28 +97,29 @@ - name: Setup cmdbuild Postgresql database user become: true become_user: postgres - postgresql_user: + community.postgresql.postgresql_user: name: cmdbuild password: 'cmdbuild' - #priv: "CONNECT" + # priv: "CONNECT" # postgresql create database cmdbuild owner cmdbuild - name: Make sure cmdbuild Postgresql database exists. become: true become_user: postgres - postgresql_db: + community.postgresql.postgresql_db: name: cmdbuild template: 'template0' owner: cmdbuild state: present register: dbwork - - debug: + - name: Show dbwork variable + community.postgresql.postgresql_userdebug: msg: "{{ dbwork }}" # Copy db init file to /tmp so user postgres can read it - name: Copy database dump file to /tmp if the database was just created. - copy: + ansible.builtin.copy: src: files/cmdbuild.dump.gz dest: /tmp/cmdbuild.dump.gz mode: '0644' @@ -132,27 +131,27 @@ - name: Initialize cmdbuild datatase if it was just created. become: true become_user: postgres - shell: + ansible.builtin.command: cmd: /usr/bin/gunzip -c /tmp/cmdbuild.dump.gz | psql postgresql://cmdbuild:cmdbuild@localhost:5432/cmdbuild when: dbwork.changed # Set default jdk to /usr/lib/jvm/java-17-openjdk-amd64/ in /etc/default/tomcat9 - name: Setup default jdk in /etc/default/tomcat9 - lineinfile: + ansible.builtin.lineinfile: path: /etc/default/tomcat9 regexp: 'JAVA_HOME=.*' line: 'JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/' # Setup cmdbuild config directory - name: Setup cmdbuild config directory - file: + ansible.builtin.file: path: /var/lib/tomcat9/conf/cmdbuild state: directory owner: tomcat group: tomcat - name: Configure database connection - blockinfile: + ansible.builtin.blockinfile: path: /var/lib/tomcat9/conf/cmdbuild/database.conf create: yes block: | @@ -165,7 +164,7 @@ # Set up nginx - name: Install nginx config file - copy: + ansible.builtin.copy: src: files/cmdb_nginx.conf dest: /etc/nginx/sites-available/default @@ -176,7 +175,7 @@ mode: '0755' - name: Copy SSL key and cert to nginx ssl dir - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/etc/nginx/ssl/{{ item }}" owner: root @@ -188,7 +187,7 @@ # Restart nginx - name: Restart nginx - systemd: + ansible.builtin.systemd: name: nginx state: restarted enabled: yes @@ -207,58 +206,65 @@ # Generate a key on the cmdb server - name: Generate key on cmdb server - shell: + ansible.builtin.command: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:1024 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /tmp # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/master" validate_certs: false register: tokenurl - - debug: + - name: Show token-service + ansible.builtin.debug: var: tokenurl.json["token-service"] - name: Store url for easier retrieval - set_fact: + ansible.builtin.set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" validate_certs: false register: endpointinfo - - debug: + - name: Show endpoint info + ansible.builtin.debug: var: endpointinfo - name: Store authorization_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" # "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth", - - debug: + - name: Show authorization endpoint + ansible.builtin.debug: var: authorization_endpoint - name: Store token_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - - debug: + + - name: Show token endpoint + ansible.builtin.debug: var: token_endpoint - name: Store userinfo_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: + + - name: Show userinfo endpoint + ansible.builtin.debug: var: userinfo_endpoint # Get authentication token from token-service url - name: Retrieve authentication token from token-service url - uri: + ansible.builtin.uri: url: "{{ tokenurl.json[\"token-service\"] }}/token" method: POST body_format: form-urlencoded @@ -271,30 +277,34 @@ grant_type: password register: authtoken - - debug: + - name: Show auth token + ansible.builtin.debug: var: authtoken - - debug: + - name: Show access_token + ansible.builtin.debug: var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval - set_fact: + ansible.builtin.set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - - debug: + - name: Show auth_token + ansible.builtin.debug: var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor - name: Retrieve IDP metadata descriptor to use the 509 formatted certificate - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" method: get validate_certs: false return_content: yes register: idp_metadata - - debug: + - name: Show idp metadata + ansible.builtin.debug: var: idp_metadata # Please do not go down this road and stay sane... @@ -307,25 +317,26 @@ # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing - copy: + ansible.builtin.copy: content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate - shell: + ansible.builtin.command: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml register: xmlstarlet - - debug: + - name: Show xmlstarlet + ansible.builtin.debug: var: xmlstarlet - name: Store output in certificate variable - set_fact: + ansible.builtin.set_fact: idp_crt: "{{ xmlstarlet.stdout }}" - name: Remove first line from tmp files - lineinfile: + ansible.builtin.lineinfile: path: "/tmp/{{ item }}" regexp: '^-----(BEGIN|END).*-----$' state: absent @@ -334,26 +345,28 @@ - sp.key - name: Retrieve remote ssl cert - shell: + ansible.builtin.command: cmd: cat /tmp/sp.crt | tr -d '\n' register: sp_crt - - debug: + - name: Show sp crt + ansible.builtin.debug: var: sp_crt - name: Retrieve remote ssl key - shell: + ansible.builtin.command: cmd: cat /tmp/sp.key | tr -d '\n' register: sp_key - - debug: + - name: Show sp key + ansible.builtin.debug: var: sp_key # Retrieve current list of clients of our type # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . - name: Retrieve current list of clients and search for already existing "{{ cmdb_client_id }} " - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ cmdb_client_id }}" headers: Accept: "application/json" @@ -363,21 +376,21 @@ register: existingclient - name: Returned json - debug: + ansible.builtin.debug: var: existingclient - name: Copy existing client to backup file. - copy: + ansible.builtin.copy: content: "{{ existingclient }}" dest: /tmp/cmdb-existing-client-backup.json - name: Find ID in returned json - debug: + ansible.builtin.debug: var: existingclient.json[0].id - name: Delete client id "{{ cmdb_client_id }}" if it already exists. # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" @@ -388,24 +401,26 @@ when: existingclient.json[0].id is defined register: deleteclient - - debug: + - name: Show deleteclient + ansible.builtin.debug: var: deleteclient - name: Convert Ninja template to variable - set_fact: + ansible.builtin.set_fact: jsonbody: "{{ lookup('template', 'cmdb-keycloak-sso.json.j2') }}" - - debug: + - name: Show json body + ansible.builtin.debug: var: jsonbody - name: For debugging store json var in local file - copy: + ansible.builtin.copy: content: "{{ jsonbody }}" dest: /tmp/jsonbody # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: Accept: "application/json" @@ -420,7 +435,7 @@ # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID - debug: + ansible.builtin.debug: var: createclientresult.location # So far, so good @@ -457,30 +472,32 @@ # The next things only works after adding ReadWritePaths=/etc/tomcat9/cmdbuild/ to /lib/systemd/system/tomcat9.service - name: Add ReadWritePaths=/etc/tomcat9/cmdbuild/ to /lib/systemd/system/tomcat9.service so we can edit the config. - lineinfile: + ansible.builtin.lineinfile: path: /lib/systemd/system/tomcat9.service insertafter: '^ReadWritePaths=/var/log/tomcat9/.*' line: 'ReadWritePaths=/etc/tomcat9/cmdbuild/' - name: Do a systemd daemon-reload now - systemd: + ansible.builtin.systemd: daemon_reload: true # Restart tomcat9 - name: Restart tomcat9 - systemd: + ansible.builtin.systemd: name: tomcat9 state: restarted enabled: yes - - debug: + - name: Start waiting + ansible.builtin.debug: msg: "Start waiting for 443 to become available" - - wait_for: + - name: waiting + ansible.builtin.wait_for: port: 443 delay: 20 # Measured 12 attempts, so 25 max should work most of the time. - name: check if "{{ cmdb_server_url }}/cmdbuild/rest/v3/sessions" is open on the host (even a 403 means it is responding). - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/rest/v3/sessions" status_code: 403 validate_certs: false @@ -496,13 +513,14 @@ # "* There was an error starting the tomcat server" # when: 'ok' not in "{{ amiup.content }}" - - debug: + - name: Done waiting + ansible.builtin.debug: msg: "Done waiting for 443" # Get authentication token from cmdb token-service url # After installation, the default login is admin/admin. - name: Retrieve authentication token from cmdb token-service url - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/sessions?scope=service&returnId=true" method: POST body_format: json @@ -513,34 +531,36 @@ register: cmdbreq1 - name: Store authentication id into sessionid variable for easier retrieval - set_fact: + ansible.builtin.set_fact: sessionid: "{{ cmdbreq1.json.data._id }}" - name: Retrieve configuration of system from cmdb rest url - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/configuration/system" method: GET validate_certs: false headers: { 'Cmdbuild-authorization': "{{ sessionid }}" } register: cmdbreq3 - - debug: + - name: Show variable cmdbreq3 + ansible.builtin.debug: var: cmdbreq3 - name: Retrieve cmdb system config (compare to previous step!) from cmdb rest url - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config" method: GET validate_certs: false headers: { 'Cmdbuild-authorization': "{{ sessionid }}", 'includeDefault': "True" } register: cmdbconf - - debug: + - name: Show cmdbconf + ansible.builtin.debug: var: cmdbconf # Werkt goed. - name: Retrieve value of org.cmdbuild.auth.module.saml.idp.id from cmdb rest url - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.id" method: GET validate_certs: false @@ -549,7 +569,7 @@ # Now we know this is stored in auth.conf as module.saml.idp.id - name: Set value of org.cmdbuild.auth.module.saml.idp.id to "{{ keycloak_server_url }}" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.id" method: PUT body_format: form-urlencoded @@ -559,7 +579,7 @@ "{{ keycloak_server_url }}" - name: Set value of org.cmdbuild.auth.module.saml.sp.id to "{{ cmdb_client_id }}" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.sp.id" method: PUT body_format: form-urlencoded @@ -570,7 +590,7 @@ - name: Set value of org.cmdbuild.auth.module.saml.idp.cert to "{{ idp_crt }}" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.cert" method: PUT body_format: form-urlencoded @@ -580,7 +600,7 @@ "{{ idp_crt }}" - name: Set value of org.cmdbuild.auth.module.saml.sp.key to "{{ sp_key.stdout }}" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.sp.key" method: PUT body_format: form-urlencoded @@ -590,7 +610,7 @@ "{{ sp_key.stdout }}" - name: Set value of org.cmdbuild.auth.module.saml.sp.cert to "{{ sp_crt.stdout }}" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.sp.cert" method: PUT body_format: form-urlencoded @@ -600,7 +620,7 @@ "{{ sp_crt.stdout }}" - name: Set value of org.cmdbuild.auth.module.saml.requireSignedAssertions to "False" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.requireSignedAssertions" method: PUT body_format: form-urlencoded @@ -610,7 +630,7 @@ "False" - name: Set value of org.cmdbuild.auth.module.saml.requireSignedMessages to "False" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.requireSignedMessages" method: PUT body_format: form-urlencoded @@ -620,7 +640,7 @@ "False" - name: Set value of org.cmdbuild.auth.module.saml.signatureAlgorithm to "RSA_SHA256" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.signatureAlgorithm" method: PUT body_format: form-urlencoded @@ -630,7 +650,7 @@ "RSA_SHA256" - name: Set value of org.cmdbuild.auth.module.saml.logout.enabled to "False" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.logout.enabled" method: PUT body_format: form-urlencoded @@ -640,7 +660,7 @@ "False" - name: Set value of org.cmdbuild.auth.module.saml.idp.logout to "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.logout" method: PUT body_format: form-urlencoded @@ -651,7 +671,7 @@ # Set sp.baseUrl - name: Set value of org.cmdbuild.auth.module.saml.sp.baseUrl to "{{ cmdb_base_url }}" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.sp.baseUrl" method: PUT body_format: form-urlencoded @@ -661,7 +681,7 @@ "{{ cmdb_base_url }}" - name: Set value of org.cmdbuild.auth.module.saml.idp.login to "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.login" method: PUT body_format: form-urlencoded @@ -671,7 +691,7 @@ "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" - name: Set value of org.cmdbuild.auth.module.saml.strict to "False" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.strict" method: PUT body_format: form-urlencoded @@ -682,7 +702,7 @@ # bash ./cmdbuild.sh restws -username admin -password admin setconfig org.cmdbuild.auth.module.saml.handlerScript "login = auth.getNameId()" - name: Set value of org.cmdbuild.auth.module.saml.handlerScript to "login = auth.getNameId()" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.handlerScript" method: PUT body_format: form-urlencoded @@ -693,7 +713,7 @@ # Try to enable saml module - name: Set value of org.cmdbuild.auth.module.saml.enabled to "True" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.enabled" method: PUT body_format: form-urlencoded @@ -704,7 +724,7 @@ # bash ./cmdbuild.sh restws -username admin -password admin setconfig org.cmdbuild.auth.case.insensitive true - name: Set value of org.cmdbuild.auth.case.insensitive to "True" - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.case.insensitive" method: PUT body_format: form-urlencoded @@ -715,14 +735,15 @@ # Werkt goed. - name: Signal CMDBuild to reload system configuration - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/reload" method: GET validate_certs: false headers: { 'Cmdbuild-authorization': "{{ sessionid }}" } register: cmdbreload - - debug: + - name: Show variable cmdbreload + ansible.builtin.debug: var: cmdbreload # decided to try adding saml to the login modules variable I saw there. @@ -732,7 +753,7 @@ # bash cmdbuild.sh restws -username "{{ cmdb_admin_id }}" -password "{{ cmdb_admin_pw }}" setconfig org.cmdbuild.auth.modules saml,default # (this configuration means that both saml and the default login are enabled) - name: Add SSO to internal db login method - uri: + ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.modules" method: PUT body_format: form-urlencoded @@ -744,10 +765,11 @@ - name: Dump current authentication config become: true become_user: tomcat - shell: + ansible.builtin.command: cmd: bash ./cmdbuild.sh restws -username admin -password admin getconfigs | grep auth chdir: /var/lib/tomcat9/webapps/cmdbuild register: authconfig - - debug: + - name: Show variable authconfig + ansible.builtin.debug: var: authconfig diff --git a/playbooks/install-freeipa-server.yml b/playbooks/install-freeipa-server.yml index 0945a82..5d9fa9d 100644 --- a/playbooks/install-freeipa-server.yml +++ b/playbooks/install-freeipa-server.yml @@ -2,9 +2,6 @@ - name: Install FreeIPA server hosts: all remote_user: root - become: true - become_user: root - become_method: sudo # use this command line: ansible-playbook -vvv --ask-pass --ask-become-pass setup-new-server.yml -i 10.1.1.158, -u jeroen @@ -12,15 +9,16 @@ ansible_python_interpreter: /usr/bin/python3 tasks: - - debug: + - name: Show tip for better output + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read global vars - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - name: Set hostname ipa.{{ domain }} ansible.builtin.hostname: @@ -29,29 +27,31 @@ # remove 127.0.0.1 ipa.{{ domain }} ipa from /etc/hosts # remove ::1 ipa.{{ domain }} ipa from /etc/hosts - name: Make sure the IPA Server hostname does not resolve to localhost on v4 and v6 - lineinfile: + ansible.builtin.lineinfile: path: /etc/hosts regexp: ipa.{{ domain }} state: absent - name: Add hostname to /etc/hosts - blockinfile: + ansible.builtin.blockinfile: path: /etc/hosts block: | - {{ item.ip }} {{ item.name }} {{item.shortname}} + {{ item.ip }} {{ item.name }} {{ item.shortname }} marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.name }}" loop: - - { name: ipa.{{ domain }}, shortname: ipa, ip: 10.11.1.242 } + - { name: "ipa.{{ domain }}", shortname: ipa, ip: 10.11.1.242 } - name: Set timezone to Europe/Amsterdam community.general.timezone: name: Europe/Amsterdam - name: Disable selinux - shell: + ansible.builtin.command: cmd: setenforce 0 + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - - name: edit selinux in /etc/selinux/config + - name: Edit selinux in /etc/selinux/config ansible.builtin.lineinfile: path: /etc/selinux/config regexp: '^SELINUX=' @@ -60,9 +60,11 @@ # yum -y install @idm:DL1 # Using shell module because well, it just didn't work any other way - name: Enable freeipa repo stream - shell: + ansible.builtin.command: cmd: yum -y install @idm:DL1 warn: false + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # # dnf install freeipa-server # Note that the installed package just contains all the bits that FreeIPA uses, it does not configure the actual server. @@ -70,9 +72,9 @@ # # dnf install freeipa-server-dns - name: Install freeipa-server - dnf: + ansible.builtin.dnf: name: freeipa-server - state: latest + state: present # Configure a FreeIPA server. # The command can take command arguments or can be run in the interactive mode. @@ -80,7 +82,8 @@ # To start the interactive installation, run: # ipa-server-install # The command will at first gather all required information and then configure all required services. - - debug: + - name: Show optional command + ansible.builtin.debug: msg: "To start the interactive installation, run: ipa-server-install" # Yes, we could automate this with Ansible vault and expect stuff, but we don't @@ -150,4 +153,3 @@ # # Continue to configure the system with these values? [no]: yes # - diff --git a/playbooks/install-gitlab-sso.yml b/playbooks/install-gitlab-sso.yml index 57e37dd..6122d53 100644 --- a/playbooks/install-gitlab-sso.yml +++ b/playbooks/install-gitlab-sso.yml @@ -2,9 +2,7 @@ - name: Install gitlab on Ubuntu 20.04 hosts: grpgitlab remote_user: root - become: true - become_user: root - become_method: sudo + # Execute ansible-playbook playbookfile.yml -i hosts --vault-password-file .vault-password @@ -18,7 +16,8 @@ realm: "{{ realm }}" tasks: - - debug: + - name: Show tip for sensible output + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read global vars @@ -28,7 +27,7 @@ include_vars: encrypted-vars.yml - name: Shutdown GitLab if already exists and ignore any errors - shell: + ansible.builtin.shell: cmd: gitlab-ctl stop ignore_errors: yes @@ -39,7 +38,7 @@ # Install all needed software in one go. # apt install -y ca-certificates curl openssh-server - name: Install GitLab and other packages - apt: + ansible.builtin.apt: name: "{{ item }}" state: present update_cache: yes @@ -52,7 +51,7 @@ - autopostgresqlbackup - name: Upload mkcert program - copy: + ansible.builtin.copy: src: files/mkcert dest: /root/mkcert owner: root @@ -61,7 +60,7 @@ force: true - name: Upload CA key and cert - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/root/{{ item }}" owner: root @@ -73,46 +72,46 @@ - rootCA-key.pem - name: Try to install my root CA on this system - shell: + ansible.builtin.shell: cmd: CAROOT=/root /root/mkcert -install - name: Update all CA certificates - shell: + ansible.builtin.shell: cmd: /usr/sbin/update-ca-certificates # Check if our CA is already installed # Should be /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt - name: Verify that our root CA is already installed - stat: + ansible.builtin.stat: path: "{{ root_ca_path }}" register: root_ca_stat - name: Is our root CA already present? - debug: + ansible.builtin.debug: msg: "Yes it is!" when: root_ca_stat.stat.exists # Start installation of GitLab - name: Download repo add script. - get_url: + ansible.builtin.get_url: url: https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh dest: /tmp/script.deb.sh mode: '0700' - name: execute repo add script - shell: + ansible.builtin.shell: cmd: /tmp/script.deb.sh ######################################################## # After this step we should have a GitLab installation # ######################################################## - name: Install gitlab-ce - apt: + ansible.builtin.apt: name: gitlab-ce # fix external_url 'http://gitlab.example.com' - name: Configure gitlab-ce url - lineinfile: + ansible.builtin.lineinfile: path: /etc/gitlab/gitlab.rb regexp: "external_url.*" line: "external_url 'https://gitlab.{{ domain }}'" @@ -120,7 +119,7 @@ # Disable GitLab LetsEncrypt support as mentioned on their page # letsencrypt['enable'] = false in /etc/gitlab/gitlab.rb - name: Make sure LetsEncrypt is disabled - lineinfile: + ansible.builtin.lineinfile: path: /etc/gitlab/gitlab.rb regexp: ".*letsencrypt['enable'].*" line: letsencrypt['enable'] = false @@ -147,7 +146,7 @@ mode: '0755' - name: Upload CA key and cert into gitlab trusted-certs store - copy: + ansible.builtin.copy: src: "files/rootCA.pem" dest: "/etc/gitlab/trusted-certs/rootCA.pem" owner: root @@ -166,7 +165,7 @@ # place the .crt and .key files in the directory and specify the following configuration: - name: Copy SSL cert to gitlab ssl certs dir /etc/gitlab/ssl - copy: + ansible.builtin.copy: src: "files/_wildcard.{{ domain }}.pem" dest: "/etc/gitlab/ssl/gitlab.{{ domain }}.crt" owner: root @@ -174,7 +173,7 @@ mode: '0600' - name: Copy SSL key to gitlab ssl certs dir /etc/gitlab/ssl - copy: + ansible.builtin.copy: src: "files/_wildcard.{{ domain }}-key.pem" dest: "/etc/gitlab/ssl/gitlab.{{ domain }}.key" owner: root @@ -184,65 +183,69 @@ # Remove old LDAP stuff # gitlab_rails['ldap_enabled'] = true - name: Remove if enable ldap setting in GitLab config file - lineinfile: + ansible.builtin.lineinfile: path: /etc/gitlab/gitlab.rb regexp: ".*gitlab_rails['ldap_enabled'].*" state: absent # gitlab_rails['ldap_servers'] = YAML.load_file('/etc/gitlab/gitlab_freeipa_settings.yml') - name: Remove if exists reference to LDAP settings file - lineinfile: + ansible.builtin.lineinfile: path: /etc/gitlab/gitlab.rb line: "gitlab_rails['ldap_servers'].*" state: absent # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/master" validate_certs: false register: tokenurl - - debug: + - name: Show token-service + ansible.builtin.debug: var: tokenurl.json["token-service"] - name: Store url for easier retrieval - set_fact: + ansible.builtin.set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" validate_certs: false register: endpointinfo - - debug: + - name: Show endpoint info + ansible.builtin.debug: var: endpointinfo - name: Store authorization_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" # "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth", - - debug: + - name: Show authorization_endpoint + ansible.builtin.debug: var: authorization_endpoint - name: Store token_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - debug: var: token_endpoint - name: Store userinfo_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: + - name: Show userinfo endpoint + ansible.builtin.debug: var: userinfo_endpoint # Get authentication token from token-service url - name: Retrieve authentication token from token-service url - uri: + ansible.builtin.uri: url: "{{ tokenurl.json[\"token-service\"] }}/token" method: POST body_format: form-urlencoded @@ -255,17 +258,20 @@ grant_type: password register: authtoken - - debug: + - name: Show current authtoken + ansible.builtin.debug: var: authtoken - - debug: + - name: show access_token + ansible.builtin.debug: var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval - set_fact: + ansible.builtin.set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - - debug: + - name: Show auth_token + ansible.builtin.debug: var: auth_token ####################################################################################### @@ -276,7 +282,7 @@ # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . - name: Retrieve current list of clients and search for already existing "{{ gitlab_client_id }} " - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ gitlab_client_id }}" headers: Accept: "application/json" @@ -286,12 +292,12 @@ register: existingclient - name: Find ID in returned json - debug: + ansible.builtin.debug: var: existingclient.json[0].id - name: Delete client id "{{ gitlab_client_id }}" if it already exists. # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" @@ -302,16 +308,17 @@ when: existingclient.json[0].id is defined register: deleteclient - - debug: + - name: Show deleteclient variable + ansible.builtin.debug: var: deleteclient - name: Convert Ninja template to variable - set_fact: + ansible.builtin.set_fact: jsonbody: "{{ lookup('template', 'gitlab-keycloak-sso.json.j2') }}" # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: Accept: "application/json" @@ -326,13 +333,13 @@ # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID - debug: + ansible.builtin.debug: var: createclientresult.location # After importing this to Keycloak, you have to generate a new Client-Secret and put it into gitabl.rb file . # GET /{realm}/clients/{id}/client-secret - name: Retrieve secret for newly created client "{{ gitlab_client_id }} " - uri: + ansible.builtin.uri: url: "{{ createclientresult.location }}/client-secret" headers: Accept: "application/json" @@ -342,12 +349,12 @@ register: clientsecret - name: Store secret for easy retrieval - set_fact: + ansible.builtin.set_fact: client_secret: "{{ clientsecret.json.value }}" # gitlab_rails['omniauth_enabled'] = true - name: Configure omniauth_enabled - lineinfile: + ansible.builtin.lineinfile: path: /etc/gitlab/gitlab.rb regexp: ".*gitlab_rails['omniauth_enabled'].*" line: "gitlab_rails['omniauth_enabled'] = true" @@ -357,7 +364,7 @@ # Automatically created upon first sign in with the LDAP integration. # Created when first signing in using an OmniAuth provider if the allow_single_sign_on setting is present. - name: Configure omniauth_allow_single_sign_on - lineinfile: + ansible.builtin.lineinfile: path: /etc/gitlab/gitlab.rb regexp: ".*gitlab_rails['omniauth_allow_single_sign_on'].*" line: "gitlab_rails['omniauth_allow_single_sign_on'] = true" @@ -365,7 +372,7 @@ # gitlab_rails['omniauth_block_auto_created_users'] = false - name: Configure omniauth_block_auto_created_users - lineinfile: + ansible.builtin.lineinfile: path: /etc/gitlab/gitlab.rb regexp: ".*gitlab_rails['omniauth_block_auto_created_users'].*" line: "gitlab_rails['omniauth_block_auto_created_users'] = true" @@ -400,18 +407,19 @@ } ] - name: Run GitLab reconfiguration script - shell: + sansible.builtin.hell: cmd: gitlab-ctl reconfigure - name: Start GitLab - shell: + ansible.builtin.shell: cmd: gitlab-ctl start - - debug: + - name: Show that we start to wait + ansible.builtin.debug: msg: "Start waiting for 443" - name: Wait for port 443 to become open on the host - uri: + ansible.builtin.uri: url: "https://gitlab.{{ domain }}/users/sign_in" status_code: 200 register: result @@ -420,19 +428,21 @@ retries: 25 delay: 10 - - debug: + - name: Shwo that we are done waiting + ansible.builtin.debug: msg: "Done waiting for 443" - name: Check openssl config - shell: + ansible.builtin.shell: cmd: /opt/gitlab/embedded/bin/openssl s_client -connect localhost:443 /dev/null - name: Download Jenkins repo key # sudo wget https://pkg.jenkins.io/debian-stable/jenkins.io.key - get_url: - url: https://pkg.jenkins.io/debian-stable/jenkins.io.key + ansible.builtin.get_url: + url: https://pkg.jenkins.io/debian-stable/jenkins.io.key dest: /usr/share/keyrings/jenkins-keyring.asc mode: '0644' - #Then add a Jenkins apt repository entry: + # Then add a Jenkins apt repository entry: - name: Download Jenkins repo info - apt_repository: + ansible.builtin.apt_repository: repo: deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/ state: present filename: jenkins - #Update your local package index, then finally install Jenkins: + # Update your local package index, then finally install Jenkins: - name: Update repositories cache - apt: - update_cache: yes + ansible.builtin.apt: + update_cache: true - name: Set timezone to Europe/Amsterdam community.general.timezone: @@ -60,7 +59,7 @@ # Install all needed software in one go. - name: Install Jenkins and other packages - apt: + ansible.builtin.apt: name: "{{ item }}" state: present loop: @@ -81,13 +80,13 @@ # line: ' false' - name: Enable and start if not running Jenkins - systemd: + ansible.builtin.systemd: name: jenkins state: started - enabled: yes + enabled: true - name: Upload vanilla Jenkins config.xml.vanilla file, just in case. - copy: + ansible.builtin.copy: src: files/jenkins_vanilla_config.xml dest: /var/lib/jenkins/config.xml.vanilla owner: jenkins @@ -98,13 +97,13 @@ # Now install Nginx - name: Install Nginx - apt: + ansible.builtin.apt: name: nginx state: present - name: Upload mkcert program - copy: + ansible.builtin.copy: src: files/mkcert dest: /root/mkcert owner: root @@ -113,7 +112,7 @@ force: true - name: Upload CA key and cert - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/root/{{ item }}" owner: root @@ -125,33 +124,39 @@ - rootCA-key.pem - name: Try to install my root CA on this system - shell: - cmd: CAROOT=/root /root/mkcert -install + ansible.builtin.command: + cmd: /root/mkcert -install + environment: + CAROOT: /root + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Update all CA certificates - shell: + ansible.builtin.command: cmd: /usr/sbin/update-ca-certificates + register: my_output2 # <- Registers the command output. + changed_when: my_output2.rc != 0 # <- Uses the return code to define when the task has changed. # Check if our CA is already installed # Should be /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt - name: Verify that our root CA is already installed - stat: + ansible.builtin.stat: path: "{{ root_ca_path }}" register: root_ca_stat - name: Is our root CA already present? - debug: + ansible.builtin.debug: msg: "Yes it is!" when: root_ca_stat.stat.exists # Install nginx config file - name: Install nginx config file - template: + ansible.builtin.template: src: templates/jenkins_nginx_sso.conf.j2 dest: /etc/nginx/sites-available/jenkins # Do not overwrite when file already exists!!! - force: no + force: false owner: root group: root mode: '0644' @@ -159,51 +164,58 @@ # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/master" validate_certs: false register: tokenurl - - debug: + - name: Show url + ansible.builtin.debug: var: tokenurl.json["token-service"] - name: Store url for easier retrieval - set_fact: + ansible.builtin.set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" validate_certs: false register: endpointinfo - - debug: + - name: Show endpointinfo + ansible.builtin.debug: var: endpointinfo - name: Store authorization_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" # "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth", - - debug: + - name: Show authorization endpoint + ansible.builtin.debug: var: authorization_endpoint - name: Store token_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - - debug: + + - name: Show token endpoint + ansible.builtin.debug: var: token_endpoint - name: Store userinfo_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: + + - name: Show userinfo endpoint + ansible.builtin.debug: var: userinfo_endpoint # Get authentication token from token-service url - name: Retrieve authentication token from token-service url - uri: + ansible.builtin.uri: url: "{{ tokenurl.json[\"token-service\"] }}/token" method: POST body_format: form-urlencoded @@ -216,17 +228,20 @@ grant_type: password register: authtoken - - debug: + - name: Show authtoken + ansible.builtin.debug: var: authtoken - - debug: + - name: Show access token + ansible.builtin.debug: var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval - set_fact: + ansible.builtin.set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - - debug: + - name: Show var auth_token + ansible.builtin.debug: var: auth_token ####################################################################################### @@ -237,37 +252,38 @@ # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . - name: Retrieve current list of clients and search for already existing "{{ jenkins_client_id }} " - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ jenkins_client_id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient - name: Find ID in returned json - debug: + ansible.builtin.debug: var: existingclient.json[0].id - - name: Delete client id "{{ jenkins_client_id }}" if it already exists. + - name: If this client id already exists then delete "{{ jenkins_client_id }}". # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - - debug: + - name: Show variable deleteclient + ansible.builtin.debug: var: deleteclient - name: Convert Ninja template to variable - set_fact: + ansible.builtin.set_fact: jsonbody: "{{ lookup('template', 'jenkins-keycloak-sso.json.j2') }}" # - debug: @@ -275,11 +291,11 @@ # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: POST validate_certs: false body_format: json @@ -292,7 +308,7 @@ # var: createclientresult - name: Get client UUID from locaton - set_fact: + ansible.builtin.set_fact: cl_uuid: "{{ createclientresult.location.split(\"/\")[-1] }}" # - name: What does the 'cl_uuid' look like? @@ -303,11 +319,11 @@ # Get representation of the client # GET /{realm}/clients/{id} - name: Download representation of client for later use - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ cl_uuid }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: GET validate_certs: false status_code: 200 @@ -325,28 +341,28 @@ # GET /{realm}/clients/{id}/client-secret - name: Retrieve secret for newly created client "{{ jenkins_client_id }} " - uri: + ansible.builtin.uri: url: "{{ createclientresult.location }}/client-secret" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: clientsecret - name: Store secret for easy retrieval - set_fact: + ansible.builtin.set_fact: client_secret: "{{ clientsecret.json.value }}" # We have to create 2 groups in Keycloak for Jenkins: Jenkins-admin and Jenkins-user # First retrieve current list of configured groups - name: Retrieve current list of groups - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: kc_groups @@ -367,16 +383,16 @@ # } # ], - name: Show currently configured groups - debug: + ansible.builtin.debug: var: kc_groups # Search groups, We store the information for later conditional adding of groups if needed - name: Search for our groups - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups?search={{ item }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false with_items: @@ -385,16 +401,16 @@ register: groupinfo - name: Find ID in returned json - debug: + ansible.builtin.debug: var: groupinfo # Create groups if not exist, else ignore errors - name: Create groups Jenkins-admin and Jenkins-user or ignore errors - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: POST validate_certs: false body_format: json @@ -403,38 +419,36 @@ with_items: - Jenkins-admin - Jenkins-user - ignore_errors: yes + ignore_errors: "{{ ansible_check_mode }}" # <- Ignores errors in check mode. - name: Retrieve current list of groups - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/groups" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: kc_groups - name: Show currently configured groups - debug: + ansible.builtin.debug: var: kc_groups - #- fail: - # Create symlink ln -s /etc/nginx/sites-available/jenkins /etc/nginx/sites-enabled/ - name: Enable virtualhost in nginx ansible.builtin.file: src: /etc/nginx/sites-available/jenkins - dest: /etc/nginx/sites-enabled/jenkins + dest: /etc/nginx/sites-enabled/jenkins owner: root group: root state: link - force: yes + force: true # Disable default configuration by removing symlink /etc/nginx/sites-enabled/default - name: Disable default nginx site config by removing a symlink ansible.builtin.file: - path: /etc/nginx/sites-enabled/default + path: /etc/nginx/sites-enabled/default state: absent - name: Make SSL dir for nginx @@ -444,7 +458,7 @@ mode: '0755' - name: Copy SSL key and cert to apache ssl dir - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/etc/nginx/ssl/{{ item }}" owner: root @@ -463,16 +477,17 @@ # mode: '0600' - name: Enable and restart always Nginx - systemd: + ansible.builtin.systemd: name: nginx state: restarted - enabled: yes + enabled: true - - debug: + - name: Show that we start to wait + ansible.builtin.debug: msg: "Start waiting for 443" - name: Wait for port 443 to become open on the host - uri: + ansible.builtin.uri: url: "https://jenkins.{{ domain }}/" status_code: 403 register: result @@ -482,29 +497,26 @@ delay: 10 # ignore_errors: yes - - debug: + - name: Show that we are done waiting + ansible.builtin.debug: msg: "Done waiting for 443" # Default user: admin # Default password: $JENKINS_HOME/secrets/initialAdminPassword - name: "Retrieve initialAdminPassword from JENKINS_HOME/secrets/initialAdminPassword" - slurp: + ansible.builtin.slurp: src: "/var/lib/jenkins/secrets/initialAdminPassword" register: admin_password_encoded - ignore_errors: yes + ignore_errors: "{{ ansible_check_mode }}" # <- Ignores errors in check mode. - name: Decode initialAdminPassword - set_fact: + ansible.builtin.set_fact: admin_password: "{{ admin_password_encoded.content | b64decode | trim }}" - ignore_errors: yes + ignore_errors: "{{ ansible_check_mode }}" # <- Ignores errors in check mode. # Create post-install message - - name: test - debug: - msg: "test" - - name: Post-install message IT IS IMPORTANT TO READ THIS - pause: + ansible.builtin.pause: seconds: 1 prompt: | ******************************************************************************************************** @@ -560,16 +572,16 @@ # cat /var/lib/jenkins/logging-jb.properties - #handlers = java.util.logging.ConsoleHandler + # handlers = java.util.logging.ConsoleHandler # ## see https://docs.oracle.com/en/java/javase/11/docs/api/java.logging/java/util/logging/SimpleFormatter.html - #java.util.logging.SimpleFormatter.format = [%1$tF %1$tT][%4$-6s][%2$s] %5$s %6$s %n + # java.util.logging.SimpleFormatter.format = [%1$tF %1$tT][%4$-6s][%2$s] %5$s %6$s %n # ## Keep this level to ALL or FINEST or it will be filtered before applying other levels - #java.util.logging.ConsoleHandler.level = ALL + # java.util.logging.ConsoleHandler.level = ALL # ## Default level - #.level= DEBUG + # .level= DEBUG # ## High verbosity for a d @@ -581,4 +593,3 @@ # plugin OpenId Connect Authentication Version 1.8 Authentication and User Management # https://ad.{{ domain }}:8443/realms/ONESTEIN.LAN/.well-known/openid-configuration - diff --git a/playbooks/install-keycloak-nginx.yml b/playbooks/install-keycloak-nginx.yml index 39a7623..bfd0834 100644 --- a/playbooks/install-keycloak-nginx.yml +++ b/playbooks/install-keycloak-nginx.yml @@ -8,17 +8,18 @@ keycloak_url: https://github.com/keycloak/keycloak/releases/download/ tasks: - - debug: + - name: Show tip for sensible output + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - name: Read global variables - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - name: Make sure hostname is in /etc/hosts - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/hosts" regexp: ".*\t{{ ansible_fqdn }}" line: "{{ ansible_facts['default_ipv4']['address'] }}\t{{ ansible_fqdn }}\t{{ ansible_fqdn }}" @@ -27,7 +28,7 @@ # Install all needed software in one go. # apt install -y ca-certificates curl openssh-server - name: Install all needed packages - apt: + ansible.builtin.apt: name: "{{ item }}" loop: - ca-certificates @@ -44,16 +45,16 @@ - nginx - name: Make sure no apache2 installed - apt: + ansible.builtin.apt: name: apache2 state: absent - name: Set timezone to Europe/Amsterdam - timezone: + community.general.timezone: name: Europe/Amsterdam - - name: "Download {{ keycloak_url }}{{ keycloak_release }}/keycloak-{{ keycloak_release }}.zip" - get_url: + - name: "Download keycloak release zip file" # {{ keycloak_url }}{{ keycloak_release }}/keycloak-{{ keycloak_release }}.zip + ansible.builtin.get_url: url: " {{ keycloak_url }}{{ keycloak_release }}/keycloak-{{ keycloak_release }}.zip" dest: "/root/keycloak-{{ keycloak_release }}.zip" @@ -74,17 +75,17 @@ name: keycloak comment: Keycloak server account home: /opt/keycloak - create_home: no + create_home: false group: keycloak groups: syslog shell: /bin/bash system: true - name: Create logfile for Keycloak - copy: + ansible.builtin.copy: content: "" dest: /var/log/keycloak.log - force: no + force: false group: syslog owner: keycloak mode: 0664 @@ -96,7 +97,7 @@ owner: keycloak group: keycloak state: link - force: yes + force: true - name: Unzip Keycloak distribution zip file @@ -105,21 +106,21 @@ dest: /opt/ owner: keycloak group: keycloak - remote_src: yes + remote_src: true # Setup Postgresql database - name: Setup Keycloak Postgresql database user become: true become_user: postgres - postgresql_user: + community.postgresql.postgresql_user: name: keycloak password: 'K3yCl0@k' - #priv: "CONNECT" + # priv: "CONNECT" - name: Setup Keycloak Postgresql database become: true become_user: postgres - postgresql_db: + community.postgresql.postgresql_db: name: keycloak template: 'template0' owner: keycloak @@ -128,18 +129,19 @@ # Install Systemd service definition # TODO: file currently contains hard-coded password. Needs vault someday. - name: Upload Systemd service definition - template: + ansible.builtin.template: src: keycloak.service dest: /etc/systemd/system/keycloak.service owner: root group: root + mode: '0644' - name: Force systemd to reread configs ansible.builtin.systemd: - daemon_reload: yes + daemon_reload: true - name: Test if keystore already exists - stat: + ansible.builtin.stat: path: /opt/keycloak/conf/server.keystore register: keystore @@ -150,49 +152,64 @@ # You will thank me later: https://coderwall.com/p/3t4xka/import-private-key-and-certificate-into-java-keystore - name: Export complete ssl chain into one file - shell: - cmd: "/usr/bin/openssl pkcs12 -export -in /root/_wildcard.{{ domain }}.pem -inkey /root/_wildcard.{{ domain }}-key.pem -chain -CAfile /root/rootCA.pem -name \"{{ domain }}\" -passout pass:secret -out /tmp/{{ domain }}.p12" + ansible.builtin.command: + cmd: "/usr/bin/openssl pkcs12 -export -in /root/_wildcard.{{ domain }}.pem \ + -inkey /root/_wildcard.{{ domain }}-key.pem -chain -CAfile /root/rootCA.pem -name \"{{ domain }}\" \ + -passout pass:secret -out /tmp/{{ domain }}.p12" + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Make sure tmp file can be read by user keycloak - file: + ansible.builtin.file: path: "/tmp/{{ domain }}.p12" owner: 'keycloak' - - name: "Import wildcard key for {{ domain }} into keycloak keystore file" + - name: "Import wildcard key into keycloak keystore file for domain {{ domain }}" become: true become_user: keycloak - shell: - cmd: "/usr/bin/keytool -importkeystore -srcstorepass secret -deststorepass secret -destkeystore server.keystore -srckeystore /tmp/{{ domain }}.p12 -srcstoretype PKCS12" + ansible.builtin.command: + cmd: "/usr/bin/keytool -importkeystore -srcstorepass secret -deststorepass secret \ + -destkeystore server.keystore -srckeystore /tmp/{{ domain }}.p12 -srcstoretype PKCS12" chdir: /opt/keycloak/conf - when: keystore.stat.exists == false + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + when: not keystore.stat.exists - name: Initial setup Keycloak run to configure connection to PostgreSQL database become: true become_user: keycloak - shell: + ansible.builtin.command: cmd: ./kc.sh build --db postgres chdir: /opt/keycloak/bin + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Normal initial startup only happens on localhost to be able to set admin userid and password # Other option is to use commandline settings for initial startup to set these credentials # We have to make a timeout on this - - name: Initial keycloak startup to set initial admin userid and password. This can take a long time because we are polling. Also, when not the first time it generates an error we will ignore. + - name: Initial keycloak startup to set initial admin userid and password. This can take a long time!" + # ...because we are polling. Also, when not the first time it generates an error we will ignore. become: true become_user: keycloak - shell: - cmd: "KEYCLOAK_ADMIN={{ kc_adminid }} KEYCLOAK_ADMIN_PASSWORD={{ kc_adminpw }} ./kc.sh start --hostname \"{{ ansible_fqdn }}\" --db-password 'K3yCl0@k' --db-username keycloak --db-url-database keycloak --https-key-store-password=secret" + ansible.builtin.command: + cmd: "./kc.sh start --hostname \"{{ ansible_fqdn }}\" \ + --db-password 'K3yCl0@k' --db-username keycloak --db-url-database keycloak --https-key-store-password=secret" chdir: /opt/keycloak/bin + environment: + KEYCLOAK_ADMIN: "{{ kc_adminid }}" + KEYCLOAK_ADMIN_PASSWORD: "{{ kc_adminpw }}" async: 180 poll: 10 register: result + changed_when: result.rc != 0 # <- Uses the return code to define when the task has changed. # IF this is not the first time we do this, the next string will appear in the output. until: result.stdout is search(".*user with username exists.*") # Since this will always timeout we will ignore the errors. - ignore_errors: yes + ignore_errors: "{{ ansible_check_mode }}" # <- Ignores errors in check mode. # Enable and start systemd keycloak service - name: Configure Keycloak systemd - ansible.builtin.systemd: + ansible.builtin.systemd_Service: name: keycloak enabled: yes state: restarted @@ -204,7 +221,7 @@ mode: '0755' - name: Copy SSL key and cert to nginx ssl dir - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/etc/nginx/ssl/{{ item }}" owner: root @@ -215,7 +232,7 @@ - _wildcard.{{ domain }}-key.pem - name: Install nginx reverse proxy virtualhost for Keycloak - template: + ansible.builtin.template: src: "keycloak_nginx.conf.j2" dest: "/etc/nginx/sites-available/keycloak" owner: root diff --git a/playbooks/install-nextcloud-sso-snap.yml b/playbooks/install-nextcloud-sso-snap.yml index 5e892b3..3e8451e 100644 --- a/playbooks/install-nextcloud-sso-snap.yml +++ b/playbooks/install-nextcloud-sso-snap.yml @@ -2,16 +2,13 @@ - name: Migrate Nextcloud SNAP! to SSO hosts: grpnc remote_user: root - become: true - become_user: root - become_method: sudo + # use this command line: ansible-playbook -v -i hosts install-nextcloud.yml -u root # Execute ansible-playbook playbookfile.yml -i hosts --vault-password-file .vault-password vars: ansible_python_interpreter: /usr/bin/python3 - supported_distros: "{{ ansible_distribution }} {{ ansible_distribution_major_version }}" nextcloud_fqdn: "nc.{{ domain }}" # Yes, this is different from the other playbooks. For a reason... # https://nc.onestein.lan/index.php/apps/user_saml/saml/metadata @@ -22,31 +19,35 @@ realm: "{{ realm }}" tasks: - - debug: + - name: Show tip for sensible output + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read global vars - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - - name: check if server is Ubuntu 20.04 - fail: msg='Unsupported platform! This playbook is for Ubuntu 20.04!' - when: supported_distros not in ["Ubuntu 20"] + - name: Check if server is Ubuntu 20.04 + ansible.builtin.fail: + msg: "'Unsupported platform! This playbook is for Ubuntu 20.04!" + when: not (ansible_distribution == 'Ubuntu' and ansible_distribution_version == '20.04') - name: Extra update apt cache ansible.builtin.apt: - update_cache: yes + update_cache: true name: xmlstarlet - name: Unpack nextcloud - snap: + community.general.snap: name: nextcloud - name: Configure Nextcloud snap to use self-signed certificates - shell: + ansible.builtin.command: cmd: nextcloud.enable-https self-signed + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Retrieve some saml info from our Keycloak server # We can create a SAML client at the Keycloak side. And use REST to @@ -58,58 +59,67 @@ # Generate a key on the nextcloud server - name: Generate key on nextcloud server - shell: + ansible.builtin.command: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /tmp + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/master" validate_certs: false register: tokenurl - - debug: + - name: Show token-service + ansible.builtin.debug: var: tokenurl.json["token-service"] - name: Store url for easier retrieval - set_fact: + ansible.builtin.set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" validate_certs: false register: endpointinfo - - debug: + - name: Show endpoint info + ansible.builtin.debug: var: endpointinfo - name: Store authorization_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" # "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth", - - debug: + - name: Show authorization_endpoint + ansible.builtin.debug: var: authorization_endpoint - name: Store token_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - - debug: + + - name: Show token endpoint + ansible.builtin.debug: var: token_endpoint - name: Store userinfo_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: + + - name: Show userinfo endpoint + ansible.builtin.debug: var: userinfo_endpoint # Get authentication token from token-service url - name: Retrieve authentication token from token-service url - uri: + ansible.builtin.uri: url: "{{ tokenurl.json[\"token-service\"] }}/token" method: POST body_format: form-urlencoded @@ -117,38 +127,42 @@ body: realm: master client_id: admin-cli - username: "{{kc_adminid }}" - password: "{{kc_adminpw }}" + username: "{{ kc_adminid }}" + password: "{{ kc_adminpw }}" grant_type: password register: authtoken - - debug: + - name: Show authtoken + ansible.builtin.debug: var: authtoken - - debug: + - name: Show access_token + ansible.builtin.debug: var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval - set_fact: + ansible.builtin.set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - - debug: + - name: Show auth_token + ansible.builtin.debug: var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor - name: Retrieve IDP metadata descriptor to use the 509 formatted certificate - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" # Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: true register: idp_metadata - - debug: + - name: Show idp metadata + ansible.builtin.debug: var: idp_metadata # Please do not go down this road and stay sane... @@ -161,25 +175,28 @@ # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing - copy: + ansible.builtin.copy: content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml + mode: '0600' # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate - shell: + ansible.builtin.command: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml register: xmlstarlet + changed_when: xmlstarlet.rc != 0 # <- Uses the return code to define when the task has changed. - - debug: + - name: Show xmlstarlet variable + ansible.builtin.debug: var: xmlstarlet - name: Store output in certificate variable - set_fact: + ansible.builtin.set_fact: idp_crt: "{{ xmlstarlet.stdout }}" - name: Remove first line from tmp files - lineinfile: + ansible.builtin.lineinfile: path: "/tmp/{{ item }}" regexp: '^-----(BEGIN|END).*-----$' state: absent @@ -188,26 +205,31 @@ - sp.key - name: Retrieve remote ssl cert - shell: + ansible.builtin.command: cmd: cat /tmp/sp.crt | tr -d '\n' register: sp_crt + changed_when: sp_cert.rc != 0 # <- Uses the return code to define when the task has changed. + - - debug: + - name: Show crt for sp + ansible.builtin.debug: var: sp_crt - name: Retrieve remote ssl key - shell: - cmd: cat /tmp/sp.key | tr -d '\n' + ansible.builtin.shell: + cmd: set -o pipefail && cat /tmp/sp.key | tr -d '\n' register: sp_key + changed_when: sp_key.rc != 0 # <- Uses the return code to define when the task has changed. - - debug: + - name: Show key for sp + ansible.builtin.debug: var: sp_key # Retrieve current list of clients of our type # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . - name: Retrieve current list of clients and search for already existing "{{ nextcloud_client_id }} " - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ nextcloud_client_id }}" headers: Accept: "application/json" @@ -217,7 +239,7 @@ register: existingclient - name: Returned json - debug: + ansible.builtin.debug: var: existingclient # - name: Copy currently existing client definition to backup file in JSON format. @@ -226,47 +248,51 @@ # dest: /tmp/nextcloud-original-client-backup.json - name: Find ID in returned json - debug: + ansible.builtin.debug: var: existingclient.json[0].id - - name: Delete client id "{{ nextcloud_client_id }}" if it already exists. + - name: If it already exists delete client id "{{ nextcloud_client_id }}" # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - - debug: + - name: Show delete client + ansible.builtin.debug: var: deleteclient - - debug: + - name: Show tip + ansible.builtin.debug: msg: "If the next step fails, run 'ansible-galaxy collection install community.general'" - name: Convert Ninja template to variable - set_fact: + ansible.builtin.set_fact: jsonbody: "{{ lookup('template', 'nextcloud-keycloak-sso.json.j2') }}" - - debug: + - name: Show variable jsonbody + ansible.builtin.debug: var: jsonbody - name: For debugging store json var in local file - copy: + ansible.builtin.copy: content: "{{ jsonbody }}" dest: /tmp/nextcloud-new-client-backup.json + mode: '0600' # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: POST validate_certs: false body_format: json @@ -277,7 +303,7 @@ # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID - debug: + ansible.builtin.debug: var: createclientresult.location - name: Put nextcloud OCC commands together in one Ansbible block @@ -286,269 +312,314 @@ # ########################################################################## block: - - name: Check if we need to run occ maintenance:install command - shell: - cmd: nextcloud.occ list - chdir: /snap/nextcloud/current - register: occ_list - - - name: Installing nextcloud from the command line if first time. - shell: - cmd: nextcloud.occ maintenance:install --database pgsql --database-name nextcloud --database-host 127.0.0.1 --database-port 5432 --database-user nextcloud --database-pass 'nextcloud' --admin-user admin --admin-pass admin --admin-email jbaten@i2rs.nl --data-dir /var/lib/nextcloud - chdir: /snap/nextcloud/current - when: '"maintenance:install" in occ_list.stdout' - - - name: "Configuring/adding trusted domain nc.{{ domain }}" - shell: - cmd: "nextcloud.occ config:system:set trusted_domains 1 --value='nc.{{ domain }}'" - chdir: /snap/nextcloud/current - - - name: Enable already installed but default disabled Nextcloud External Storage support - shell: - cmd: nextcloud.occ app:enable files_external - chdir: /snap/nextcloud/current - - - name: Retrieve list of currently installed apps - shell: - cmd: nextcloud.occ app:list - chdir: /snap/nextcloud/current - register: app_list - - - name: Install and enable all the Nextcloud apps we like - shell: - cmd: nextcloud.occ app:install "{{ item }}" - chdir: /snap/nextcloud/current - loop: - - deck - - user_saml - # When combining conditionals with a loop, the when: statement is processed separately for each item. - # This is tested and works. - when: 'item not in app_list.stdout' - - # Now start configuring Saml SSO connection to keycloak - # https://www.muehlencord.de/wordpress/2019/12/14/nextcloud-sso-using-keycloak/ - # - # Thhis could be useful: https://janikvonrotz.ch/2020/04/21/configure-saml-authentication-for-nextcloud-with-keycloack/ - # - # In case you lose connection to your nextcloud account, you can try to connect login - # to http://nextcloud.my.domain/login?direct=1 to login with your admin account again. - # - # Make sure some settings are in place - # occ config:list - # "user_saml": { - # "installed_version": "5.0.2", - # "types": "authentication", - # "enabled": "yes", - # "type": "saml", - # "general-require_provisioned_account": "1", - # "general-allow_multiple_user_back_ends": "0" - - # If 0 then autocreate users, if 1, then not... - # Set this to 1 and try to solve it yourself... - - name: Configure Only allow authentication if an account exists on some other backend (e.g. LDAP). - shell: - cmd: nextcloud.occ config:app:set user_saml general-require_provisioned_account --value="0" - chdir: /snap/nextcloud/current - - # Disable this setting (means set it to 1) once Keycloak connection works. - - name: Configure Allow the use of multiple user back-ends (e.g. LDAP) - shell: - # Yes, I know, a zero to switch it on is weird. - cmd: nextcloud.occ config:app:set user_saml general-allow_multiple_user_back_ends --value="1" - chdir: /snap/nextcloud/current - - # Add a new identity provider - - # Start to setup a new identity provider with the following settings - - # sudo -u www-data nextcloud.occ saml:config:get - - name: Dump current saml config to see changes - shell: - cmd: nextcloud.occ saml:config:get - chdir: /snap/nextcloud/current - - ########################################################################## - # After some thought I decided to claim/overwrite saml_user config set 1 ! - ########################################################################## - # This means we start with deleting entry 1 - - name: Deleting original first saml configuration entry !!! - shell: - cmd: nextcloud.occ saml:config:delete 1 - chdir: /snap/nextcloud/current - - # Attribute to map the UID to : username - # Displayname : any name you like : e.g. Keycloak - # Name id format : unspecififed - # X.509 certificate of the service provider : leave empty for the moment - # Private key of the service provider : leave also empty for now - # Identifier of the IdP entry : https://keycloak.my.domain/auth/realms/ - # URL, Target of the IdP where the SP will send the Authentication Request Message : https://keycloak.my.domain/auth/realms//protocol/saml - # URL Location of the IdP where the SP will send SLO Request : https://keycloak.my.domain/auth/realms//protocol/saml - # Public X.509 certificate of the IdP : leave empty - # - # saml:config:set - # [--general-idp0_display_name GENERAL-IDP0_DISPLAY_NAME] - # [--general-uid_mapping GENERAL-UID_MAPPING] - # [--idp-entityId IDP-ENTITYID] - # [--idp-singleLogoutService.responseUrl IDP-SINGLELOGOUTSERVICE.RESPONSEURL] - # [--idp-singleLogoutService.url IDP-SINGLELOGOUTSERVICE.URL] - # [--idp-singleSignOnService.url IDP-SINGLESIGNONSERVICE.URL] - # [--idp-x509cert IDP-X509CERT] - # [--security-authnRequestsSigned SECURITY-AUTHNREQUESTSSIGNED] - # [--security-general SECURITY-GENERAL] - # [--security-logoutRequestSigned SECURITY-LOGOUTREQUESTSIGNED] - # [--security-logoutResponseSigned SECURITY-LOGOUTRESPONSESIGNED] - # [--security-lowercaseUrlencoding SECURITY-LOWERCASEURLENCODING] - # [--security-nameIdEncrypted SECURITY-NAMEIDENCRYPTED] - # [--security-offer SECURITY-OFFER] - # [--security-required SECURITY-REQUIRED] - # [--security-signatureAlgorithm SECURITY-SIGNATUREALGORITHM] - # [--security-signMetadata SECURITY-SIGNMETADATA] - # [--security-sloWebServerDecode SECURITY-SLOWEBSERVERDECODE] - # [--security-wantAssertionsEncrypted SECURITY-WANTASSERTIONSENCRYPTED] - # [--security-wantAssertionsSigned SECURITY-WANTASSERTIONSSIGNED] - # [--security-wantMessagesSigned SECURITY-WANTMESSAGESSIGNED] - # [--security-wantNameId SECURITY-WANTNAMEID] - # [--security-wantNameIdEncrypted SECURITY-WANTNAMEIDENCRYPTED] - # [--security-wantXMLValidation SECURITY-WANTXMLVALIDATION] - # [--saml-attribute-mapping-displayName_mapping SAML-ATTRIBUTE-MAPPING-DISPLAYNAME_MAPPING] - # [--saml-attribute-mapping-email_mapping SAML-ATTRIBUTE-MAPPING-EMAIL_MAPPING] - # [--saml-attribute-mapping-group_mapping SAML-ATTRIBUTE-MAPPING-GROUP_MAPPING] - # [--saml-attribute-mapping-home_mapping SAML-ATTRIBUTE-MAPPING-HOME_MAPPING] - # [--saml-attribute-mapping-quota_mapping SAML-ATTRIBUTE-MAPPING-QUOTA_MAPPING] - # [--sp-x509cert SP-X509CERT] - # [--sp-name-id-format SP-NAME-ID-FORMAT] - # [--sp-privateKey SP-PRIVATEKEY] - # [--output [OUTPUT]] [--] - # - # sudo -u www-data nextcloud.occ saml:config:get - # - 1: - # - general-uid_mapping: username - # - general-idp0_display_name: Keycloak - # - sp-x509cert: - # - sp-privateKey: - # - idp-entityId: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN - # - idp-singleSignOnService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml - # - idp-singleLogoutService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml - # - - # - general-idp0_display_name: Keycloak - # PLEASE NOTE: No '--value' to set the value! - - name: Saml configuration set general-idp0_display_name - shell: - cmd: nextcloud.occ saml:config:set --general-idp0_display_name "Keycloak" 1 - chdir: /snap/nextcloud/current - - # - general-uid_mapping: username - # PLEASE NOTE: No '--value' to set the value! - - name: Saml configuration set general-uid_mapping - shell: - cmd: nextcloud.occ saml:config:set --general-uid_mapping "username" 1 - chdir: /snap/nextcloud/current - - # Add mapping of email adres - # saml-attribute-mapping-email_mapping: email - - name: Saml configuration set saml-attribute-mapping-email_mapping - shell: - cmd: nextcloud.occ saml:config:set --saml-attribute-mapping-email_mapping "email" 1 - chdir: /snap/nextcloud/current - - # - idp-entityId: https://ad.{{ domain }}/realms/ONESTEIN.LAN - # https://nextcloud.yourdomain.com/index.php/apps/user_saml/metadata ? - # What is an Entity ID: https://spaces.at.internet2.edu/display/federation/saml-metadata-entityid - - name: Saml configuration set idp-entityId - shell: -# cmd: nextcloud.occ saml:config:set --idp-entityId "{{ nextcloud_server_url }}/index.php/apps/user_saml/metadata" 1 - cmd: nextcloud.occ saml:config:set --idp-entityId "{{ keycloak_server_url }}/realms/{{ realm }}" 1 -# cmd: nextcloud.occ saml:config:set --idp-entityId "{{ nextcloud_client_id }}" 1 - chdir: /snap/nextcloud/current - - # - idp-singleSignOnService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml - # --idp-singleSignOnService.url - - name: Saml configuration set general-uid_mapping - shell: - cmd: nextcloud.occ saml:config:set --idp-singleSignOnService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 - chdir: /snap/nextcloud/current - - # - idp-singleLogoutService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml - # --idp-singleLogoutService.url - - name: Saml configuration set general-uid_mapping - shell: - cmd: nextcloud.occ saml:config:set --idp-singleLogoutService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 - chdir: /snap/nextcloud/current - - # - security-authnRequestsSigned: 1 - - name: Saml configuration set security-authnRequestsSigned - shell: - cmd: nextcloud.occ saml:config:set --security-authnRequestsSigned "1" 1 - chdir: /snap/nextcloud/current - - # - security-logoutRequestSigned: 1 - - name: Saml configuration set security-logoutRequestSigned - shell: - cmd: nextcloud.occ saml:config:set --security-logoutRequestSigned "1" 1 - chdir: /snap/nextcloud/current - - # - security-logoutResponseSigned: 1 - - name: Saml configuration set security-logoutResponseSigned - shell: - cmd: nextcloud.occ saml:config:set --security-logoutResponseSigned "1" 1 - chdir: /snap/nextcloud/current - - # - security-wantMessagesSigned: 1 - - name: Saml configuration set security-wantMessagesSigned - shell: - cmd: nextcloud.occ saml:config:set --security-wantMessagesSigned "1" 1 - chdir: /snap/nextcloud/current - - # - security-wantAssertionsSigned: 1 -# - name: Saml configuration set security-wantAssertionsSigned -# shell: -# cmd: nextcloud.occ saml:config:set --security-wantAssertionsSigned "1" 1 -# chdir: /snap/nextcloud/current - - # sp_certs_not_found_and_required, idp_cert_or_fingerprint_not_found_and_required - # [--sp-x509cert SP-X509CERT] - # [--sp-privateKey SP-PRIVATEKEY] - - name: Saml configuration set sp-x509cert - shell: - cmd: nextcloud.occ saml:config:set --sp-x509cert "{{ sp_crt.stdout }}" 1 - chdir: /snap/nextcloud/current - - - name: Saml configuration set sp-privateKey - shell: - cmd: nextcloud.occ saml:config:set --sp-privateKey "{{ sp_key.stdout }}" 1 - chdir: /snap/nextcloud/current - - # [--idp-x509cert IDP-X509CERT] - - name: Saml configuration set idp-x509cert - shell: - cmd: nextcloud.occ saml:config:set --idp-x509cert "{{ idp_crt }}" 1 - chdir: /snap/nextcloud/current - - - - name: Dump current saml config to see changes - shell: - cmd: nextcloud.occ saml:config:get - chdir: /snap/nextcloud/current - register: ncconfig - + - name: "Check if we need to run occ maintenance:install command" + ansible.builtin.command: + cmd: nextcloud.occ list + chdir: /snap/nextcloud/current + register: occ_list + changed_when: occ_list.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Installing nextcloud from the command line if first time. + ansible.builtin.command: + cmd: "nextcloud.occ maintenance:install --database pgsql --database-name nextcloud \ + --database-host 127.0.0.1 --database-port 5432 --database-user nextcloud \ + --database-pass 'nextcloud' --admin-user admin --admin-pass admin \ + --admin-email root@localhost.local --data-dir /var/lib/nextcloud" + chdir: /snap/nextcloud/current + when: '"maintenance:install" in occ_list.stdout' + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: "Configuring/adding trusted domain nc.{{ domain }}" + ansible.builtin.command: + cmd: "nextcloud.occ config:system:set trusted_domains 1 --value='nc.{{ domain }}'" + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Enable already installed but default disabled Nextcloud External Storage support + ansible.builtin.command: + cmd: nextcloud.occ app:enable files_external + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Retrieve list of currently installed apps + ansible.builtin.command: + cmd: nextcloud.occ app:list + chdir: /snap/nextcloud/current + register: app_list + changed_when: app_list.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Install and enable all the Nextcloud apps we like + ansible.builtin.command: + cmd: nextcloud.occ app:install "{{ item }}" + chdir: /snap/nextcloud/current + loop: + - deck + - user_saml + # When combining conditionals with a loop, the when: statement is processed separately for each item. + # This is tested and works. + when: 'item not in app_list.stdout' + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Now start configuring Saml SSO connection to keycloak + # https://www.muehlencord.de/wordpress/2019/12/14/nextcloud-sso-using-keycloak/ + # + # Thhis could be useful: https://janikvonrotz.ch/2020/04/21/configure-saml-authentication-for-nextcloud-with-keycloack/ + # + # In case you lose connection to your nextcloud account, you can try to connect login + # to http://nextcloud.my.domain/login?direct=1 to login with your admin account again. + # + # Make sure some settings are in place + # occ config:list + # "user_saml": { + # "installed_version": "5.0.2", + # "types": "authentication", + # "enabled": "yes", + # "type": "saml", + # "general-require_provisioned_account": "1", + # "general-allow_multiple_user_back_ends": "0" + + # If 0 then autocreate users, if 1, then not... + # Set this to 1 and try to solve it yourself... + - name: Configure Only allow authentication if an account exists on some other backend (e.g. LDAP). + ansible.builtin.command: + cmd: nextcloud.occ config:app:set user_saml general-require_provisioned_account --value="0" + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Disable this setting (means set it to 1) once Keycloak connection works. + - name: Configure Allow the use of multiple user back-ends (e.g. LDAP) + ansible.builtin.command: + # Yes, I know, a zero to switch it on is weird. + cmd: nextcloud.occ config:app:set user_saml general-allow_multiple_user_back_ends --value="1" + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Add a new identity provider + + # Start to setup a new identity provider with the following settings + + # sudo -u www-data nextcloud.occ saml:config:get + - name: Dump current saml config to see changes + ansible.builtin.command: + cmd: nextcloud.occ saml:config:get + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + ########################################################################## + # After some thought I decided to claim/overwrite saml_user config set 1 ! + ########################################################################## + # This means we start with deleting entry 1 + - name: Deleting original first saml configuration entry !!! + ansible.builtin.command: + cmd: nextcloud.occ saml:config:delete 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Attribute to map the UID to : username + # Displayname : any name you like : e.g. Keycloak + # Name id format : unspecififed + # X.509 certificate of the service provider : leave empty for the moment + # Private key of the service provider : leave also empty for now + # Identifier of the IdP entry : https://keycloak.my.domain/auth/realms/ + # URL, Target of the IdP where the SP will send the Authentication Request Message : https://keycloak.my.domain/auth/realms//protocol/saml + # URL Location of the IdP where the SP will send SLO Request : https://keycloak.my.domain/auth/realms//protocol/saml + # Public X.509 certificate of the IdP : leave empty + # + # saml:config:set + # [--general-idp0_display_name GENERAL-IDP0_DISPLAY_NAME] + # [--general-uid_mapping GENERAL-UID_MAPPING] + # [--idp-entityId IDP-ENTITYID] + # [--idp-singleLogoutService.responseUrl IDP-SINGLELOGOUTSERVICE.RESPONSEURL] + # [--idp-singleLogoutService.url IDP-SINGLELOGOUTSERVICE.URL] + # [--idp-singleSignOnService.url IDP-SINGLESIGNONSERVICE.URL] + # [--idp-x509cert IDP-X509CERT] + # [--security-authnRequestsSigned SECURITY-AUTHNREQUESTSSIGNED] + # [--security-general SECURITY-GENERAL] + # [--security-logoutRequestSigned SECURITY-LOGOUTREQUESTSIGNED] + # [--security-logoutResponseSigned SECURITY-LOGOUTRESPONSESIGNED] + # [--security-lowercaseUrlencoding SECURITY-LOWERCASEURLENCODING] + # [--security-nameIdEncrypted SECURITY-NAMEIDENCRYPTED] + # [--security-offer SECURITY-OFFER] + # [--security-required SECURITY-REQUIRED] + # [--security-signatureAlgorithm SECURITY-SIGNATUREALGORITHM] + # [--security-signMetadata SECURITY-SIGNMETADATA] + # [--security-sloWebServerDecode SECURITY-SLOWEBSERVERDECODE] + # [--security-wantAssertionsEncrypted SECURITY-WANTASSERTIONSENCRYPTED] + # [--security-wantAssertionsSigned SECURITY-WANTASSERTIONSSIGNED] + # [--security-wantMessagesSigned SECURITY-WANTMESSAGESSIGNED] + # [--security-wantNameId SECURITY-WANTNAMEID] + # [--security-wantNameIdEncrypted SECURITY-WANTNAMEIDENCRYPTED] + # [--security-wantXMLValidation SECURITY-WANTXMLVALIDATION] + # [--saml-attribute-mapping-displayName_mapping SAML-ATTRIBUTE-MAPPING-DISPLAYNAME_MAPPING] + # [--saml-attribute-mapping-email_mapping SAML-ATTRIBUTE-MAPPING-EMAIL_MAPPING] + # [--saml-attribute-mapping-group_mapping SAML-ATTRIBUTE-MAPPING-GROUP_MAPPING] + # [--saml-attribute-mapping-home_mapping SAML-ATTRIBUTE-MAPPING-HOME_MAPPING] + # [--saml-attribute-mapping-quota_mapping SAML-ATTRIBUTE-MAPPING-QUOTA_MAPPING] + # [--sp-x509cert SP-X509CERT] + # [--sp-name-id-format SP-NAME-ID-FORMAT] + # [--sp-privateKey SP-PRIVATEKEY] + # [--output [OUTPUT]] [--] + # + # sudo -u www-data nextcloud.occ saml:config:get + # - 1: + # - general-uid_mapping: username + # - general-idp0_display_name: Keycloak + # - sp-x509cert: + # - sp-privateKey: + # - idp-entityId: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN + # - idp-singleSignOnService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml + # - idp-singleLogoutService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml + # + + # - general-idp0_display_name: Keycloak + # PLEASE NOTE: No '--value' to set the value! + - name: Saml configuration set general-idp0_display_name + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --general-idp0_display_name "Keycloak" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - general-uid_mapping: username + # PLEASE NOTE: No '--value' to set the value! + - name: Saml configuration set general-uid_mapping + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --general-uid_mapping "username" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Add mapping of email adres + # saml-attribute-mapping-email_mapping: email + - name: Saml configuration set saml-attribute-mapping-email_mapping + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --saml-attribute-mapping-email_mapping "email" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - idp-entityId: https://ad.{{ domain }}/realms/ONESTEIN.LAN + # https://nextcloud.yourdomain.com/index.php/apps/user_saml/metadata ? + # What is an Entity ID: https://spaces.at.internet2.edu/display/federation/saml-metadata-entityid + - name: Saml configuration set idp-entityId + ansible.builtin.command: + # cmd: nextcloud.occ saml:config:set --idp-entityId "{{ nextcloud_server_url }}/index.php/apps/user_saml/metadata" 1 + cmd: nextcloud.occ saml:config:set --idp-entityId "{{ keycloak_server_url }}/realms/{{ realm }}" 1 + # cmd: nextcloud.occ saml:config:set --idp-entityId "{{ nextcloud_client_id }}" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - idp-singleSignOnService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml + # --idp-singleSignOnService.url + - name: Saml configuration set general-uid_mapping + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --idp-singleSignOnService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - idp-singleLogoutService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml + # --idp-singleLogoutService.url + - name: Saml configuration set general-uid_mapping + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --idp-singleLogoutService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-authnRequestsSigned: 1 + - name: Saml configuration set security-authnRequestsSigned + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --security-authnRequestsSigned "1" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-logoutRequestSigned: 1 + - name: Saml configuration set security-logoutRequestSigned + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --security-logoutRequestSigned "1" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-logoutResponseSigned: 1 + - name: Saml configuration set security-logoutResponseSigned + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --security-logoutResponseSigned "1" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-wantMessagesSigned: 1 + - name: Saml configuration set security-wantMessagesSigned + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --security-wantMessagesSigned "1" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-wantAssertionsSigned: 1 + # - name: Saml configuration set security-wantAssertionsSigned + # shell: + # cmd: nextcloud.occ saml:config:set --security-wantAssertionsSigned "1" 1 + # chdir: /snap/nextcloud/current + + # sp_certs_not_found_and_required, idp_cert_or_fingerprint_not_found_and_required + # [--sp-x509cert SP-X509CERT] + # [--sp-privateKey SP-PRIVATEKEY] + - name: Saml configuration set sp-x509cert + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --sp-x509cert "{{ sp_crt.stdout }}" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Saml configuration set sp-privateKey + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --sp-privateKey "{{ sp_key.stdout }}" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # [--idp-x509cert IDP-X509CERT] + - name: Saml configuration set idp-x509cert + ansible.builtin.command: + cmd: nextcloud.occ saml:config:set --idp-x509cert "{{ idp_crt }}" 1 + chdir: /snap/nextcloud/current + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + + - name: Dump current saml config to see changes + ansible.builtin.command: + cmd: nextcloud.occ saml:config:get + chdir: /snap/nextcloud/current + register: ncconfig + changed_when: ncconfig.rc != 0 # <- Uses the return code to define when the task has changed. ########################################################################## # Closing of Nexctcloud OCC commands block ########################################################################## -# become: yes -# become_user: www-data -# become_method: sudo + ########################################################################## # End of Nexctcloud OCC commands block ########################################################################## - name: Dump nextcloud config - debug: + ansible.builtin.debug: var: ncconfig - name: Post-install message IT IS IMPORTANT TO READ THIS - pause: + ansible.builtin.pause: prompt: | ******************************************************************************************************** * Nextcloud is installed in /snap/nextcloud/current. @@ -560,5 +631,3 @@ * "{{ nextcloud_server_url }}"/apps/user_saml/saml/selectUserBackEnd?redirectUrl=. * The SSO option is called 'Keycloak'. ******************************************************************************************************** - - diff --git a/playbooks/install-nextcloud-sso.yml b/playbooks/install-nextcloud-sso.yml index c27442a..81e3838 100644 --- a/playbooks/install-nextcloud-sso.yml +++ b/playbooks/install-nextcloud-sso.yml @@ -2,16 +2,13 @@ - name: Install nextcloud release 24 hosts: grpnc remote_user: root - become: true - become_user: root - become_method: sudo + # use this command line: ansible-playbook -v -i hosts install-nextcloud.yml -u root # Execute ansible-playbook playbookfile.yml -i hosts --vault-password-file .vault-password vars: ansible_python_interpreter: /usr/bin/python3 - supported_distros: "{{ ansible_distribution }} {{ ansible_distribution_major_version }}" nextcloud_fqdn: "nc.{{ domain }}" # Yes, this is different from the other playbooks. For a reason... nextcloud_client_id: "{{ nextcloud_server_url }}/apps/user_saml/saml/metadata" @@ -21,26 +18,28 @@ realm: "{{ realm }}" tasks: - - debug: + - name: Show sensible tip for output + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read global vars - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - - name: check if server is Ubuntu 20.04 - fail: msg='Unsupported platform! This playbook is for Ubuntu 20.04!' - when: supported_distros not in ["Ubuntu 20"] + - name: Check if server is Ubuntu 20.04 + ansible.builtin.fail: + msg: "'Unsupported platform! This playbook is for Ubuntu 20.04!" + when: not (ansible_distribution == 'Ubuntu' and ansible_distribution_version == '20.04') - name: Extra update apt cache ansible.builtin.apt: - update_cache: yes + update_cache: true # Install all needed software in one go. - name: Install all needed packages - apt: + ansible.builtin.apt: name: "{{ item }}" loop: - postgresql @@ -83,15 +82,15 @@ - name: Setup nextcloud Postgresql database user become: true become_user: postgres - postgresql_user: + community.postgresql.postgresql_user: name: nextcloud password: 'nextcloud' - #priv: "CONNECT" + # priv: "CONNECT" - name: Setup nextcloud Postgresql database become: true become_user: postgres - postgresql_db: + community.postgresql.postgresql_db: name: nextcloud template: 'template0' owner: nextcloud @@ -104,7 +103,7 @@ mode: '0755' - name: Copy SSL key and cert to nginx ssl dir - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/etc/nginx/ssl/{{ item }}" owner: root @@ -114,99 +113,74 @@ - _wildcard.{{ domain }}.pem - _wildcard.{{ domain }}-key.pem - - set_fact: + - name: Set fact for ssl cert + ansible.builtin.set_fact: tls_crt: "/etc/nginx/ssl/_wildcard.{{ domain }}.pem" - - set_fact: + - name: Set fact for ssl key + ansible.builtin.set_fact: tls_key: "/etc/nginx/ssl/_wildcard.{{ domain }}-key.pem" - - name: generate and copy nginx config for standalone instance - template: + - name: Generate and copy nginx config for standalone instance + ansible.builtin.template: src: nextcloud-nginx.j2 dest: /etc/nginx/sites-enabled/default - force: yes + force: true + mode: '0644' - - name: overwrite php-fpm www.conf - copy: + - name: Overwrite php-fpm www.conf + ansible.builtin.copy: src: files/nextcloud-php-fpm.conf dest: /etc/php/7.4/fpm/pool.d/www.conf + mode: '0644' - - name: overwrite php.ini - copy: + - name: Overwrite php.ini + ansible.builtin.copy: src: files/nextcloud-php.ini dest: /etc/php/7.4/fpm/php.ini + mode: '0644' - # - #- name: start and enable postgres - # systemd: - # name: postgresql - # enabled: yes - # state: started - # become: true - # - # - #- name: create database - # become: true - # become_user: postgres - # postgresql_db: - # name: "{{ postgres.database }}" - # state: present - # - # name: create database user - # become: true - # become_user: postgres - # postgresql_user: - # db: "{{ postgres.database }}" - # name: "{{ postgres.user }}" - # password: "{{ postgres.password }}" - # priv: ALL - # state: present - # - - name: download nextcloud - get_url: + - name: Download nextcloud + ansible.builtin.get_url: url: https://download.nextcloud.com/server/releases/latest-24.zip dest: "/root/latest-24.zip" + mode: '0644' - name: Unpack nextcloud - unarchive: + ansible.builtin.unarchive: src: /root/latest-24.zip dest: /var/www/html - remote_src: yes + remote_src: true + mode: '0644' - - name: change owner to www-data in nextcloud dir - file: + - name: Change owner to www-data in nextcloud dir + ansible.builtin.file: path: /var/www/html/nextcloud state: directory owner: www-data group: www-data - recurse: yes - - # - #- name: systemd reload - # systemd: - # daemon_reload: yes - # become: true - # - - name: start and enable nginx - systemd: + recurse: true + + - name: Start and enable nginx + ansible.builtin.systemd_service: name: nginx state: restarted - enabled: yes + enabled: true - - name: start and enable php-fpm - systemd: + - name: Start and enable php-fpm + ansible.builtin.systemd_service: name: php7.4-fpm - enabled: yes + enabled: true state: restarted - - name: start and enable redis - systemd: + - name: Start and enable redis + ansible.builtin.systemd_service: name: redis state: started - enabled: yes + enabled: true - name: Create nextcloud data directory /var/lib/nextcloud - file: + ansible.builtin.file: path: /var/lib/nextcloud state: directory owner: www-data @@ -223,58 +197,67 @@ # Generate a key on the nextcloud server - name: Generate key on nextcloud server - shell: + ansible.builtin.command: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /tmp + register: my_output + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/master" validate_certs: false register: tokenurl - - debug: + - name: Show token-service + ansible.builtin.debug: var: tokenurl.json["token-service"] - name: Store url for easier retrieval - set_fact: + ansible.builtin.set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" validate_certs: false register: endpointinfo - - debug: + - name: Show endpoint info + ansible.builtin.debug: var: endpointinfo - name: Store authorization_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" # "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth", - - debug: + - name: Show authorization endpoint + ansible.builtin.debug: var: authorization_endpoint - name: Store token_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - - debug: + + - name: Show token endpoint + ansible.builtin.debug: var: token_endpoint - name: Store userinfo_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: + + - name: Show userinfo endpoint + ansible.builtin.debug: var: userinfo_endpoint # Get authentication token from token-service url - name: Retrieve authentication token from token-service url - uri: + ansible.builtin.uri: url: "{{ tokenurl.json[\"token-service\"] }}/token" method: POST body_format: form-urlencoded @@ -287,33 +270,37 @@ grant_type: password register: authtoken - - debug: + - name: Show authtoken + ansible.builtin.debug: var: authtoken - - debug: + - name: Show access token + ansible.builtin.debug: var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval - set_fact: + ansible.builtin.set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - - debug: + - name: Show auth_token + ansible.builtin.debug: var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor - name: Retrieve IDP metadata descriptor to use the 509 formatted certificate - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" # Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: true register: idp_metadata - - debug: + - name: Show idp metadata + ansible.builtin.debug: var: idp_metadata # Please do not go down this road and stay sane... @@ -326,25 +313,28 @@ # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing - copy: + ansible.builtin.copy: content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml + mode: '0600' # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate - shell: + ansible.builtin.command: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml register: xmlstarlet + changed_when: xmlstarlet.rc != 0 # <- Uses the return code to define when the task has changed. - - debug: + - name: Show xmlstarlet + ansible.builtin.debug: var: xmlstarlet - name: Store output in certificate variable - set_fact: + ansible.builtin.set_fact: idp_crt: "{{ xmlstarlet.stdout }}" - name: Remove first line from tmp files - lineinfile: + ansible.builtin.lineinfile: path: "/tmp/{{ item }}" regexp: '^-----(BEGIN|END).*-----$' state: absent @@ -353,26 +343,30 @@ - sp.key - name: Retrieve remote ssl cert - shell: - cmd: cat /tmp/sp.crt | tr -d '\n' + ansible.builtin.command: + cmd: set -o pipefail && cat /tmp/sp.crt | tr -d '\n' register: sp_crt + changed_when: sp_crt.rc != 0 # <- Uses the return code to define when the task has changed. - - debug: + - name: Show sp_cert + ansible.builtin.debug: var: sp_crt - name: Retrieve remote ssl key - shell: - cmd: cat /tmp/sp.key | tr -d '\n' + ansible.builtin.command: + cmd: set -o pipefail && cat /tmp/sp.key | tr -d '\n' register: sp_key + changed_when: sp_key.rc != 0 # <- Uses the return code to define when the task has changed. - - debug: + - name: Show sp_key + ansible.builtin.debug: var: sp_key # Retrieve current list of clients of our type # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . - name: Retrieve current list of clients and search for already existing "{{ nextcloud_client_id }} " - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ nextcloud_client_id }}" headers: Accept: "application/json" @@ -382,7 +376,7 @@ register: existingclient - name: Returned json - debug: + ansible.builtin.debug: var: existingclient # - name: Copy currently existing client definition to backup file in JSON format. @@ -391,44 +385,47 @@ # dest: /tmp/nextcloud-original-client-backup.json - name: Find ID in returned json - debug: + ansible.builtin.debug: var: existingclient.json[0].id - - name: Delete client id "{{ nextcloud_client_id }}" if it already exists. + - name: If it already exists then delete client id "{{ nextcloud_client_id }}". # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - - debug: + - name: Show deleteclient + ansible.builtin.debug: var: deleteclient - name: Convert Ninja template to variable - set_fact: + ansible.builtin.set_fact: jsonbody: "{{ lookup('template', 'nextcloud-keycloak-sso.json.j2') }}" - - debug: + - name: Show jsonbody + ansible.builtin.debug: var: jsonbody - name: For debugging store json var in local file - copy: + ansible.builtin.copy: content: "{{ jsonbody }}" dest: /tmp/nextcloud-new-client-backup.json + mode: '0600' # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: POST validate_certs: false body_format: json @@ -439,278 +436,328 @@ # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID - debug: + ansible.builtin.debug: var: createclientresult.location + ########################################################################## + # Start of Nexctcloud OCC commands block + ########################################################################## - name: Put a lot of nextcloud OCC commands together in one Ansbible block because they are all become user www-data - ########################################################################## - # Start of Nexctcloud OCC commands block - ########################################################################## + become: true + become_user: www-data + become_method: ansible.builtin.sudo block: - - name: Check if we need to run occ maintenance:install command - shell: - cmd: php occ list - chdir: /var/www/html/nextcloud - register: occ_list - - - name: Installing nextcloud from the command line if first time. - shell: - cmd: php occ maintenance:install --database pgsql --database-name nextcloud --database-host 127.0.0.1 --database-port 5432 --database-user nextcloud --database-pass 'nextcloud' --admin-user admin --admin-pass admin --admin-email jbaten@i2rs.nl --data-dir /var/lib/nextcloud - chdir: /var/www/html/nextcloud - when: '"maintenance:install" in occ_list.stdout' - - - name: Configuring/adding trusted domain nc.{{ domain }} - shell: - cmd: php occ config:system:set trusted_domains 1 --value='nc.{{ domain }}' - chdir: /var/www/html/nextcloud - - - name: Enable already installed but default disabled Nextcloud External Storage support - shell: - cmd: php occ app:enable files_external - chdir: /var/www/html/nextcloud - - - name: Retrieve list of currently installed apps - shell: - cmd: php occ app:list - chdir: /var/www/html/nextcloud - register: app_list - - - name: Install and enable all the Nextcloud apps we like - shell: - cmd: php occ app:install "{{ item }}" - chdir: /var/www/html/nextcloud - loop: - - deck - - user_saml - # When combining conditionals with a loop, the when: statement is processed separately for each item. - # This is tested and works. - when: 'item not in app_list.stdout' - - # Now start configuring Saml SSO connection to keycloak - # https://www.muehlencord.de/wordpress/2019/12/14/nextcloud-sso-using-keycloak/ - # - # Thhis could be useful: https://janikvonrotz.ch/2020/04/21/configure-saml-authentication-for-nextcloud-with-keycloack/ - # - # In case you lose connection to your nextcloud account, you can try to connect login - # to http://nextcloud.my.domain/login?direct=1 to login with your admin account again. - # - # Make sure some settings are in place - # occ config:list - # "user_saml": { - # "installed_version": "5.0.2", - # "types": "authentication", - # "enabled": "yes", - # "type": "saml", - # "general-require_provisioned_account": "1", - # "general-allow_multiple_user_back_ends": "0" - - # If 0 then autocreate users, if 1, then not... - - name: Configure Only allow authentication if an account exists on some other backend (e.g. LDAP). - shell: - cmd: php occ config:app:set user_saml general-require_provisioned_account --value="0" - chdir: /var/www/html/nextcloud - - # Disable this setting (means set it to 1) once Keycloak connection works. - - name: Configure Allow the use of multiple user back-ends (e.g. LDAP) - shell: - # Yes, I know, a zero to switch it on is weird. - cmd: php occ config:app:set user_saml general-allow_multiple_user_back_ends --value="1" - chdir: /var/www/html/nextcloud - - # Add a new identity provider - - # Start to setup a new identity provider with the following settings - - # sudo -u www-data php occ saml:config:get - - name: Dump current saml config to see changes - shell: - cmd: php occ saml:config:get - chdir: /var/www/html/nextcloud - - ########################################################################## - # After some thought I decided to claim/overwrite saml_user config set 1 ! - ########################################################################## - # This means we start with deleting entry 1 - - name: Deleting original first saml configuration entry !!! - shell: - cmd: php occ saml:config:delete 1 - chdir: /var/www/html/nextcloud - - # Attribute to map the UID to : username - # Displayname : any name you like : e.g. Keycloak - # Name id format : unspecififed - # X.509 certificate of the service provider : leave empty for the moment - # Private key of the service provider : leave also empty for now - # Identifier of the IdP entry : https://keycloak.my.domain/auth/realms/ - # URL, Target of the IdP where the SP will send the Authentication Request Message : https://keycloak.my.domain/auth/realms//protocol/saml - # URL Location of the IdP where the SP will send SLO Request : https://keycloak.my.domain/auth/realms//protocol/saml - # Public X.509 certificate of the IdP : leave empty - # - # saml:config:set - # [--general-idp0_display_name GENERAL-IDP0_DISPLAY_NAME] - # [--general-uid_mapping GENERAL-UID_MAPPING] - # [--idp-entityId IDP-ENTITYID] - # [--idp-singleLogoutService.responseUrl IDP-SINGLELOGOUTSERVICE.RESPONSEURL] - # [--idp-singleLogoutService.url IDP-SINGLELOGOUTSERVICE.URL] - # [--idp-singleSignOnService.url IDP-SINGLESIGNONSERVICE.URL] - # [--idp-x509cert IDP-X509CERT] - # [--security-authnRequestsSigned SECURITY-AUTHNREQUESTSSIGNED] - # [--security-general SECURITY-GENERAL] - # [--security-logoutRequestSigned SECURITY-LOGOUTREQUESTSIGNED] - # [--security-logoutResponseSigned SECURITY-LOGOUTRESPONSESIGNED] - # [--security-lowercaseUrlencoding SECURITY-LOWERCASEURLENCODING] - # [--security-nameIdEncrypted SECURITY-NAMEIDENCRYPTED] - # [--security-offer SECURITY-OFFER] - # [--security-required SECURITY-REQUIRED] - # [--security-signatureAlgorithm SECURITY-SIGNATUREALGORITHM] - # [--security-signMetadata SECURITY-SIGNMETADATA] - # [--security-sloWebServerDecode SECURITY-SLOWEBSERVERDECODE] - # [--security-wantAssertionsEncrypted SECURITY-WANTASSERTIONSENCRYPTED] - # [--security-wantAssertionsSigned SECURITY-WANTASSERTIONSSIGNED] - # [--security-wantMessagesSigned SECURITY-WANTMESSAGESSIGNED] - # [--security-wantNameId SECURITY-WANTNAMEID] - # [--security-wantNameIdEncrypted SECURITY-WANTNAMEIDENCRYPTED] - # [--security-wantXMLValidation SECURITY-WANTXMLVALIDATION] - # [--saml-attribute-mapping-displayName_mapping SAML-ATTRIBUTE-MAPPING-DISPLAYNAME_MAPPING] - # [--saml-attribute-mapping-email_mapping SAML-ATTRIBUTE-MAPPING-EMAIL_MAPPING] - # [--saml-attribute-mapping-group_mapping SAML-ATTRIBUTE-MAPPING-GROUP_MAPPING] - # [--saml-attribute-mapping-home_mapping SAML-ATTRIBUTE-MAPPING-HOME_MAPPING] - # [--saml-attribute-mapping-quota_mapping SAML-ATTRIBUTE-MAPPING-QUOTA_MAPPING] - # [--sp-x509cert SP-X509CERT] - # [--sp-name-id-format SP-NAME-ID-FORMAT] - # [--sp-privateKey SP-PRIVATEKEY] - # [--output [OUTPUT]] [--] - # - # sudo -u www-data php occ saml:config:get - # - 1: - # - general-uid_mapping: username - # - general-idp0_display_name: Keycloak - # - sp-x509cert: - # - sp-privateKey: - # - idp-entityId: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN - # - idp-singleSignOnService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml - # - idp-singleLogoutService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml - # - - # - general-idp0_display_name: Keycloak - # PLEASE NOTE: No '--value' to set the value! - - name: Saml configuration set general-idp0_display_name - shell: - cmd: php occ saml:config:set --general-idp0_display_name "Keycloak" 1 - chdir: /var/www/html/nextcloud - - # - general-uid_mapping: username - # PLEASE NOTE: No '--value' to set the value! - - name: Saml configuration set general-uid_mapping - shell: - cmd: php occ saml:config:set --general-uid_mapping "username" 1 - chdir: /var/www/html/nextcloud - - # Add mapping of email adres - # saml-attribute-mapping-email_mapping: email - - name: Saml configuration set saml-attribute-mapping-email_mapping - shell: - cmd: php occ saml:config:set --saml-attribute-mapping-email_mapping "email" 1 - chdir: /var/www/html/nextcloud - - # - idp-entityId: https://ad.{{ domain }}/realms/ONESTEIN.LAN - # https://nextcloud.yourdomain.com/index.php/apps/user_saml/metadata ? - # What is an Entity ID: https://spaces.at.internet2.edu/display/federation/saml-metadata-entityid - - name: Saml configuration set idp-entityId - shell: -# cmd: php occ saml:config:set --idp-entityId "{{ nextcloud_server_url }}/index.php/apps/user_saml/metadata" 1 - cmd: php occ saml:config:set --idp-entityId "{{ keycloak_server_url }}/realms/{{ realm }}" 1 -# cmd: php occ saml:config:set --idp-entityId "{{ nextcloud_client_id }}" 1 - chdir: /var/www/html/nextcloud - - # - idp-singleSignOnService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml - # --idp-singleSignOnService.url - - name: Saml configuration set general-uid_mapping - shell: - cmd: php occ saml:config:set --idp-singleSignOnService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 - chdir: /var/www/html/nextcloud - - # - idp-singleLogoutService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml - # --idp-singleLogoutService.url - - name: Saml configuration set general-uid_mapping - shell: - cmd: php occ saml:config:set --idp-singleLogoutService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 - chdir: /var/www/html/nextcloud - - # - security-authnRequestsSigned: 1 - - name: Saml configuration set security-authnRequestsSigned - shell: - cmd: php occ saml:config:set --security-authnRequestsSigned "1" 1 - chdir: /var/www/html/nextcloud - - # - security-logoutRequestSigned: 1 - - name: Saml configuration set security-logoutRequestSigned - shell: - cmd: php occ saml:config:set --security-logoutRequestSigned "1" 1 - chdir: /var/www/html/nextcloud - - # - security-logoutResponseSigned: 1 - - name: Saml configuration set security-logoutResponseSigned - shell: - cmd: php occ saml:config:set --security-logoutResponseSigned "1" 1 - chdir: /var/www/html/nextcloud - - # - security-wantMessagesSigned: 1 - - name: Saml configuration set security-wantMessagesSigned - shell: - cmd: php occ saml:config:set --security-wantMessagesSigned "1" 1 - chdir: /var/www/html/nextcloud - - # - security-wantAssertionsSigned: 1 -# - name: Saml configuration set security-wantAssertionsSigned -# shell: -# cmd: php occ saml:config:set --security-wantAssertionsSigned "1" 1 -# chdir: /var/www/html/nextcloud - - - # sp_certs_not_found_and_required, idp_cert_or_fingerprint_not_found_and_required - # [--sp-x509cert SP-X509CERT] - # [--sp-privateKey SP-PRIVATEKEY] - - name: Saml configuration set sp-x509cert - shell: - cmd: php occ saml:config:set --sp-x509cert "{{ sp_crt.stdout }}" 1 - chdir: /var/www/html/nextcloud - - - name: Saml configuration set sp-privateKey - shell: - cmd: php occ saml:config:set --sp-privateKey "{{ sp_key.stdout }}" 1 - chdir: /var/www/html/nextcloud - - # [--idp-x509cert IDP-X509CERT] - - name: Saml configuration set idp-x509cert - shell: - cmd: php occ saml:config:set --idp-x509cert "{{ idp_crt }}" 1 - chdir: /var/www/html/nextcloud - - - - name: Dump current saml config to see changes - shell: - cmd: php occ saml:config:get - chdir: /var/www/html/nextcloud - register: ncconfig + - name: "Check if we need to run occ maintenance:install command" + ansible.builtin.command: + cmd: php occ list + chdir: /var/www/html/nextcloud + register: occ_list + changed_when: occ_list.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Installing nextcloud from the command line if first time. + ansible.builtin.command: + cmd: "php occ maintenance:install --database pgsql --database-name nextcloud \ + --database-host 127.0.0.1 --database-port 5432 --database-user nextcloud \ + --database-pass 'nextcloud' --admin-user admin --admin-pass admin \ + --admin-email root@localhost.local --data-dir /var/lib/nextcloud" + chdir: /var/www/html/nextcloud + when: '"maintenance:install" in occ_list.stdout' + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Configuring/adding trusted domain nc.{{ domain }} + ansible.builtin.command: + cmd: php occ config:system:set trusted_domains 1 --value='nc.{{ domain }}' + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Enable already installed but default disabled Nextcloud External Storage support + ansible.builtin.command: + cmd: php occ app:enable files_external + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Retrieve list of currently installed apps + ansible.builtin.command: + cmd: php occ app:list + chdir: /var/www/html/nextcloud + register: app_list + changed_when: app_list.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Install and enable all the Nextcloud apps we like + ansible.builtin.command: + cmd: php occ app:install "{{ item }}" + chdir: /var/www/html/nextcloud + loop: + - deck + - user_saml + # When combining conditionals with a loop, the when: statement is processed separately for each item. + # This is tested and works. + when: 'item not in app_list.stdout' + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + + # Now start configuring Saml SSO connection to keycloak + # https://www.muehlencord.de/wordpress/2019/12/14/nextcloud-sso-using-keycloak/ + # + # Thhis could be useful: https://janikvonrotz.ch/2020/04/21/configure-saml-authentication-for-nextcloud-with-keycloack/ + # + # In case you lose connection to your nextcloud account, you can try to connect login + # to http://nextcloud.my.domain/login?direct=1 to login with your admin account again. + # + # Make sure some settings are in place + # occ config:list + # "user_saml": { + # "installed_version": "5.0.2", + # "types": "authentication", + # "enabled": "yes", + # "type": "saml", + # "general-require_provisioned_account": "1", + # "general-allow_multiple_user_back_ends": "0" + + # If 0 then autocreate users, if 1, then not... + - name: Configure Only allow authentication if an account exists on some other backend (e.g. LDAP). + ansible.builtin.command: + cmd: php occ config:app:set user_saml general-require_provisioned_account --value="0" + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Disable this setting (means set it to 1) once Keycloak connection works. + - name: Configure Allow the use of multiple user back-ends (e.g. LDAP) + ansible.builtin.command: + # Yes, I know, a zero to switch it on is weird. + cmd: php occ config:app:set user_saml general-allow_multiple_user_back_ends --value="1" + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Add a new identity provider + + # Start to setup a new identity provider with the following settings + + # sudo -u www-data php occ saml:config:get + - name: Dump current saml config to see changes + ansible.builtin.command: + cmd: php occ saml:config:get + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + ########################################################################## + # After some thought I decided to claim/overwrite saml_user config set 1 ! + ########################################################################## + # This means we start with deleting entry 1 + - name: Deleting original first saml configuration entry !!! + ansible.builtin.command: + cmd: php occ saml:config:delete 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Attribute to map the UID to : username + # Displayname : any name you like : e.g. Keycloak + # Name id format : unspecififed + # X.509 certificate of the service provider : leave empty for the moment + # Private key of the service provider : leave also empty for now + # Identifier of the IdP entry : https://keycloak.my.domain/auth/realms/ + # URL, Target of the IdP where the SP will send the Authentication Request Message : https://keycloak.my.domain/auth/realms//protocol/saml + # URL Location of the IdP where the SP will send SLO Request : https://keycloak.my.domain/auth/realms//protocol/saml + # Public X.509 certificate of the IdP : leave empty + # + # saml:config:set + # [--general-idp0_display_name GENERAL-IDP0_DISPLAY_NAME] + # [--general-uid_mapping GENERAL-UID_MAPPING] + # [--idp-entityId IDP-ENTITYID] + # [--idp-singleLogoutService.responseUrl IDP-SINGLELOGOUTSERVICE.RESPONSEURL] + # [--idp-singleLogoutService.url IDP-SINGLELOGOUTSERVICE.URL] + # [--idp-singleSignOnService.url IDP-SINGLESIGNONSERVICE.URL] + # [--idp-x509cert IDP-X509CERT] + # [--security-authnRequestsSigned SECURITY-AUTHNREQUESTSSIGNED] + # [--security-general SECURITY-GENERAL] + # [--security-logoutRequestSigned SECURITY-LOGOUTREQUESTSIGNED] + # [--security-logoutResponseSigned SECURITY-LOGOUTRESPONSESIGNED] + # [--security-lowercaseUrlencoding SECURITY-LOWERCASEURLENCODING] + # [--security-nameIdEncrypted SECURITY-NAMEIDENCRYPTED] + # [--security-offer SECURITY-OFFER] + # [--security-required SECURITY-REQUIRED] + # [--security-signatureAlgorithm SECURITY-SIGNATUREALGORITHM] + # [--security-signMetadata SECURITY-SIGNMETADATA] + # [--security-sloWebServerDecode SECURITY-SLOWEBSERVERDECODE] + # [--security-wantAssertionsEncrypted SECURITY-WANTASSERTIONSENCRYPTED] + # [--security-wantAssertionsSigned SECURITY-WANTASSERTIONSSIGNED] + # [--security-wantMessagesSigned SECURITY-WANTMESSAGESSIGNED] + # [--security-wantNameId SECURITY-WANTNAMEID] + # [--security-wantNameIdEncrypted SECURITY-WANTNAMEIDENCRYPTED] + # [--security-wantXMLValidation SECURITY-WANTXMLVALIDATION] + # [--saml-attribute-mapping-displayName_mapping SAML-ATTRIBUTE-MAPPING-DISPLAYNAME_MAPPING] + # [--saml-attribute-mapping-email_mapping SAML-ATTRIBUTE-MAPPING-EMAIL_MAPPING] + # [--saml-attribute-mapping-group_mapping SAML-ATTRIBUTE-MAPPING-GROUP_MAPPING] + # [--saml-attribute-mapping-home_mapping SAML-ATTRIBUTE-MAPPING-HOME_MAPPING] + # [--saml-attribute-mapping-quota_mapping SAML-ATTRIBUTE-MAPPING-QUOTA_MAPPING] + # [--sp-x509cert SP-X509CERT] + # [--sp-name-id-format SP-NAME-ID-FORMAT] + # [--sp-privateKey SP-PRIVATEKEY] + # [--output [OUTPUT]] [--] + # + # sudo -u www-data php occ saml:config:get + # - 1: + # - general-uid_mapping: username + # - general-idp0_display_name: Keycloak + # - sp-x509cert: + # - sp-privateKey: + # - idp-entityId: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN + # - idp-singleSignOnService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml + # - idp-singleLogoutService.url: https://ad.{{ domain }}/auth/realms/ONESTEIN.LAN/protocol/saml + # + + # - general-idp0_display_name: Keycloak + # PLEASE NOTE: No '--value' to set the value! + - name: Saml configuration set general-idp0_display_name + ansible.builtin.command: + cmd: php occ saml:config:set --general-idp0_display_name "Keycloak" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - general-uid_mapping: username + # PLEASE NOTE: No '--value' to set the value! + - name: Saml configuration set general-uid_mapping + ansible.builtin.command: + cmd: php occ saml:config:set --general-uid_mapping "username" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Add mapping of email adres + # saml-attribute-mapping-email_mapping: email + - name: Saml configuration set saml-attribute-mapping-email_mapping + ansible.builtin.command: + cmd: php occ saml:config:set --saml-attribute-mapping-email_mapping "email" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - idp-entityId: https://ad.{{ domain }}/realms/ONESTEIN.LAN + # https://nextcloud.yourdomain.com/index.php/apps/user_saml/metadata ? + # What is an Entity ID: https://spaces.at.internet2.edu/display/federation/saml-metadata-entityid + - name: Saml configuration set idp-entityId + ansible.builtin.command: + # cmd: php occ saml:config:set --idp-entityId "{{ nextcloud_server_url }}/index.php/apps/user_saml/metadata" 1 + cmd: php occ saml:config:set --idp-entityId "{{ keycloak_server_url }}/realms/{{ realm }}" 1 + # cmd: php occ saml:config:set --idp-entityId "{{ nextcloud_client_id }}" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - idp-singleSignOnService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml + # --idp-singleSignOnService.url + - name: Saml configuration set general-uid_mapping + ansible.builtin.command: + cmd: php occ saml:config:set --idp-singleSignOnService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - idp-singleLogoutService.url: https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml + # --idp-singleLogoutService.url + - name: Saml configuration set general-uid_mapping + ansible.builtin.command: + cmd: php occ saml:config:set --idp-singleLogoutService.url "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-authnRequestsSigned: 1 + - name: Saml configuration set security-authnRequestsSigned + ansible.builtin.command: + cmd: php occ saml:config:set --security-authnRequestsSigned "1" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-logoutRequestSigned: 1 + - name: Saml configuration set security-logoutRequestSigned + ansible.builtin.command: + cmd: php occ saml:config:set --security-logoutRequestSigned "1" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-logoutResponseSigned: 1 + - name: Saml configuration set security-logoutResponseSigned + ansible.builtin.command: + cmd: php occ saml:config:set --security-logoutResponseSigned "1" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-wantMessagesSigned: 1 + - name: Saml configuration set security-wantMessagesSigned + ansible.builtin.command: + cmd: php occ saml:config:set --security-wantMessagesSigned "1" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - security-wantAssertionsSigned: 1 + # - name: Saml configuration set security-wantAssertionsSigned + # shell: + # cmd: php occ saml:config:set --security-wantAssertionsSigned "1" 1 + # chdir: /var/www/html/nextcloud + + + # sp_certs_not_found_and_required, idp_cert_or_fingerprint_not_found_and_required + # [--sp-x509cert SP-X509CERT] + # [--sp-privateKey SP-PRIVATEKEY] + - name: Saml configuration set sp-x509cert + ansible.builtin.command: + cmd: php occ saml:config:set --sp-x509cert "{{ sp_crt.stdout }}" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: Saml configuration set sp-privateKey + ansible.builtin.command: + cmd: php occ saml:config:set --sp-privateKey "{{ sp_key.stdout }}" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # [--idp-x509cert IDP-X509CERT] + - name: Saml configuration set idp-x509cert + ansible.builtin.command: + cmd: php occ saml:config:set --idp-x509cert "{{ idp_crt }}" 1 + chdir: /var/www/html/nextcloud + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + + - name: Dump current saml config to see changes + ansible.builtin.command: + cmd: php occ saml:config:get + chdir: /var/www/html/nextcloud + register: ncconfig + changed_when: ncconfig.rc != 0 # <- Uses the return code to define when the task has changed. ########################################################################## # Closing of Nexctcloud OCC commands block ########################################################################## - become: yes - become_user: www-data - become_method: sudo + ########################################################################## # End of Nexctcloud OCC commands block ########################################################################## - name: Dump nextcloud config - debug: + ansible.builtin.debug: var: ncconfig - name: Post-install message IT IS IMPORTANT TO READ THIS - debug: + ansible.builtin.debug: msg: | ******************************************************************************************************** * Nextcloud is installed in /var/www/html/nextcloud. @@ -722,5 +769,3 @@ * "{{ nextcloud_server_url }}"/apps/user_saml/saml/selectUserBackEnd?redirectUrl=. * The SSO option is called 'Keycloak'. ******************************************************************************************************** - - diff --git a/playbooks/install-odoo-sso.yml b/playbooks/install-odoo-sso.yml index 8da53f2..489f150 100644 --- a/playbooks/install-odoo-sso.yml +++ b/playbooks/install-odoo-sso.yml @@ -8,20 +8,21 @@ vars: ansible_python_interpreter: /usr/bin/python3 - supported_distros: "{{ ansible_distribution }} {{ ansible_distribution_major_version }}" odoo_fqdn: "odoo.{{ domain }}" tasks: - - debug: + - name: Show tip for sensible output + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - name: Read global variables - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - - debug: + - name: Show pre-install tip! + ansible.builtin.debug: msg: | ************************************************************************************* * We assume you have setup Odoo using the playbook at the following url; @@ -30,24 +31,25 @@ ************************************************************************************* - name: Check if /opt/odoo/odoo-server/odoo-bin exists - stat: + ansible.builtin.stat: path: /opt/odoo/odoo-server/odoo-bin register: stat_odoo_bin - name: Fail when odoo-bin not at /opt/odoo/odoo-server/odoo-bin - fail: + ansible.builtin.fail: when: stat_odoo_bin.stat.exists == false - name: Check if /opt/odoo/custom/addons is a directory - stat: + ansible.builtin.stat: path: /opt/odoo/custom/addons register: stat_custom_addon_dir - - debug: + - name: Show variable custom_addon_dir + ansible.builtin.debug: var: stat_custom_addon_dir - name: Fail when /opt/odoo/custom/addons is not an existing directory - fail: + ansible.builtin.fail: when: stat_custom_addon_dir.stat.exists == false or stat_custom_addon_dir.stat.isdir == false # Okay, this seems to be an Odoo install, but what version? @@ -55,24 +57,25 @@ - name: Try to determine the version of Odoo currently installed become: yes become_user: odoo - become_method: sudo - shell: + become_method: ansible.builtin.sudo + ansible.builtin.shell: cmd: /opt/odoo/odoo-server/odoo-bin --version register: installed_odoo - name: Get the last 4 characters from version string - set_fact: + ansible.builtin.set_fact: odoo_version: "{{ installed_odoo.stdout[-4:] }}" - - debug: + - name: Show odoo version + ansible.builtin.debug: var: odoo_version - - name: Set variable odoo_client_id to "client-odoo-{{ odoo_version }}-{{ ansible_fqdn }}" - set_fact: + - name: Set variable odoo_client_id to "client-odoo-$version-$ansible_fqdn" + ansible.builtin.set_fact: odoo_client_id: "client-odoo-{{ odoo_version }}-{{ ansible_fqdn }}" - - name: Set variable odoo_client_name to "client-odoo-{{ odoo_version }}-{{ ansible_fqdn }}" - set_fact: + - name: Set variable odoo_client_name to "client-odoo-$version-$ansible_fqdn" + ansible.builtin.set_fact: odoo_client_name: "client-odoo-{{ odoo_version }}-{{ ansible_fqdn }}" # Now we start doing some stuff @@ -80,11 +83,11 @@ # Installing needed tools - name: Update apt cache ansible.builtin.apt: - update_cache: yes + update_cache: true # Install all needed software in one go. - name: Install all needed packages - apt: + ansible.builtin.apt: name: "{{ item }}" loop: - autopostgresqlbackup @@ -97,22 +100,26 @@ # Original at https://orus.io/xcg/auth_saml # These days at https://github.com/OCA/server-auth/tree/{{ odoo_version }}/auth_saml - - name: checkout OCA repo server-auth branch "{{ odoo_version }}" to temporary storage directory + - name: checkout OCA repo server-auth branch to temporary storage directory for version "{{ odoo_version }}" ansible.builtin.git: repo: 'https://github.com/OCA/server-auth/' dest: /root/server-auth version: "{{ odoo_version }}" - name: Copy only auth_saml part to Odoo custom addon directory - shell: + ansible.builtin.command: cmd: cp -av /root/server-auth/auth_saml /opt/odoo/custom/addons/. + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Change ownership of copied files to odoo user and group - shell: + ansible.builtin.command: cmd: chown -R odoo:odoo /opt/odoo/custom/addons/. + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Restart Odoo server to make sure the new module is available in the app store. - systemd: + ansible.builtin.systemd_Service: name: odoo-server state: restarted @@ -124,7 +131,7 @@ mode: '0755' - name: Copy SSL key and cert to apache ssl dir - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/etc/nginx/ssl/{{ item }}" owner: root @@ -135,7 +142,7 @@ - _wildcard.{{ domain }}-key.pem - name: UFW - Allow HTTPS - ufw: + community.general.ufw: rule: allow port: "443" @@ -150,58 +157,67 @@ # Generate a key on the odoo server - name: Generate key on odoo server - shell: + ansible.builtin.command: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj "/CN={{ domain }}" chdir: /tmp + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/master" validate_certs: false register: tokenurl - - debug: + - name: Show token-service + ansible.builtin.debug: var: tokenurl.json["token-service"] - name: Store url for easier retrieval - set_fact: + ansible.builtin.set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" validate_certs: false register: endpointinfo - - debug: + - name: Show endpoint info + ansible.builtin.debug: var: endpointinfo - name: Store authorization_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" # "authorization_endpoint": "https://ad.onestein.lan/realms/ONESTEIN.LAN/protocol/openid-connect/auth", - - debug: + - name: Show authorization endpoint + ansible.builtin.debug: var: authorization_endpoint - name: Store token_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - - debug: + + - name: Show token endpoint + ansible.builtin.debug: var: token_endpoint - name: Store userinfo_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: + + - name: Show userinfo endpoint + ansible.builtin.debug: var: userinfo_endpoint # Get authentication token from token-service url - name: Retrieve authentication token from token-service url - uri: + ansible.builtin.uri: url: "{{ tokenurl.json[\"token-service\"] }}/token" method: POST body_format: form-urlencoded @@ -214,33 +230,37 @@ grant_type: password register: authtoken - - debug: + - name: Show authtoken + ansible.builtin.debug: var: authtoken - - debug: + - name: Show access_token + ansible.builtin.debug: var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval - set_fact: + ansible.builtin.set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - - debug: + - name: Show auth_token + ansible.builtin.debug: var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.onestein.lan/realms/ONESTEIN.LAN/protocol/saml/descriptor - name: Retrieve IDP metadata descriptor to use the 509 formatted certificate - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" # Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: true register: idp_metadata - - debug: + - name: Show idp metadata + ansible.builtin.debug: var: idp_metadata # Please do not go down this road and stay sane... @@ -253,21 +273,24 @@ # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing - copy: + ansible.builtin.copy: content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml + mode: '0600' # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate - shell: + ansible.builtin.shell: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml register: xmlstarlet + changed_when: xmlstarlet.rc != 0 # <- Uses the return code to define when the task has changed. - - debug: + - name: Show xmlstartlet + ansible.builtin.debug: var: xmlstarlet - name: Store output in certificate variable - set_fact: + ansible.builtin.set_fact: idp_crt: "{{ xmlstarlet.stdout }}" - name: Download SP key and cert to local system @@ -280,7 +303,7 @@ - sp.key - name: Remove first line from tmp files - lineinfile: + ansible.builtin.lineinfile: path: "/tmp/{{ item }}" regexp: '^-----(BEGIN|END).*-----$' state: absent @@ -289,29 +312,31 @@ - sp.key - name: Retrieve remote ssl cert - shell: + ansible.builtin.shell: cmd: cat /tmp/sp.crt | tr -d '\n' register: sp_crt - - debug: + - name: show sp crt + ansible.builtin.debug: var: sp_crt - name: Retrieve remote ssl key - shell: + ansible.builtin.shell: cmd: cat /tmp/sp.key | tr -d '\n' register: sp_key - - debug: + - name: Show sp key + ansible.builtin.debug: var: sp_key - name: Overwrite current nginx configuration - template: + ansible.builtin.template: src: odoo-nginx-odoo.j2 dest: /etc/nginx/sites-available/odoo force: yes - name: Restart Nginx - systemd: + ansible.builtin.systemd: name: nginx state: restarted @@ -319,7 +344,7 @@ # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.onestein.lan:8443/admin/realms/master/clients" | jq . - name: Retrieve current list of clients and search for already existing "{{ odoo_client_id }} " - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ odoo_client_id }}" headers: Accept: "application/json" @@ -329,7 +354,7 @@ register: existingclient - name: Returned json - debug: + ansible.builtin.debug: var: existingclient # - name: Copy currently existing client definition to backup file in JSON format. @@ -338,12 +363,12 @@ # dest: /tmp/odoo-original-client-backup.json - name: Find ID in returned json - debug: + ansible.builtin.debug: var: existingclient.json[0].id - name: Delete client id "{{ odoo_client_id }}" if it already exists. # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: Accept: "application/json" @@ -354,28 +379,30 @@ when: existingclient.json[0].id is defined register: deleteclient - - debug: + - name: Show deleteclient + ansible.builtin.debug: var: deleteclient - name: Convert Ninja template to variable - set_fact: + ansible.builtin.set_fact: jsonbody: "{{ lookup('template', 'odoo-keycloak-sso.json.j2') }}" - - debug: + - name: Show json body + ansible.builtin.debug: var: jsonbody - name: For debugging store json var in local file - copy: + ansible.builtin.copy: content: "{{ jsonbody }}" dest: /tmp/odoo-new-client-backup.json # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: POST validate_certs: false body_format: json @@ -386,10 +413,11 @@ # Good news! Result contains location of new client id in location # Example: "location": "https://ad.onestein.lan:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID - debug: + ansible.builtin.debug: var: createclientresult.location - - debug: + - name: IT IS IMPORTANT TO READ THIS + ansible.builtin.debug: msg: | ************************************************************************************* * Log in your Odoo server at "https://{{ ansible_fqdn }}" and install the 'auth_saml' module in the apps module. diff --git a/playbooks/install-xwiki-sso.yml b/playbooks/install-xwiki-sso.yml index 2a4c44c..fbfe18d 100644 --- a/playbooks/install-xwiki-sso.yml +++ b/playbooks/install-xwiki-sso.yml @@ -11,18 +11,19 @@ tasks: - - debug: + - name: Show sensible tip + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - name: Read global variables - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - name: Download Xwiki repo key # sudo wget https://maven.xwiki.org/xwiki-keyring.gpg -P /usr/share/keyrings/ - get_url: + ansible.builtin.get_url: url: https://maven.xwiki.org/xwiki-keyring.gpg dest: /usr/share/keyrings/xwiki-keyring.gpg mode: '0644' @@ -33,21 +34,21 @@ # https://maven.xwiki.org/releases/xwiki-releases.list: all released versions including milestones and release candidates (beta) # https://maven.xwiki.org/stable/xwiki-stable.list: all released versions excluding milestones and release candidates (stable) # https://maven.xwiki.org/lts/xwiki-lts.list: the current "long term support" branch versions as defined in Support page - get_url: + ansible.builtin.get_url: url: https://maven.xwiki.org/lts/xwiki-lts.list dest: /etc/apt/sources.list.d/xwiki-lts.list mode: '0644' - name: Update repositories cache - apt: - update_cache: yes + ansible.builtin.apt: + update_cache: true - name: Set timezone to Europe/Amsterdam - timezone: + community.general.timezone: name: "{{ timezone }}" - name: Install Xwiki and Libreoffice - apt: + ansible.builtin.apt: name: "{{ item }}" state: present loop: @@ -60,28 +61,28 @@ - apache2 - name: Make sure no nginx installed - apt: + ansible.builtin.apt: name: nginx state: absent # Configure Apache with SSL - name: Enable Apache module proxy - apache2_module: + community.general.apache2_module: state: present name: proxy - name: Enable Apache module proxy_http - apache2_module: + community.general.apache2_module: state: present name: proxy_http - name: Enable Apache module ssl - apache2_module: + community.general.apache2_module: state: present name: ssl - name: Enable Apache module rewrite - apache2_module: + community.general.apache2_module: state: present name: rewrite @@ -92,7 +93,7 @@ mode: '0755' - name: Copy SSL key and cert to apache ssl dir - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/etc/apache2/ssl/{{ item }}" owner: root @@ -103,7 +104,7 @@ - _wildcard.{{ domain }}-key.pem - name: Install Apache reverse proxy virtualhost for Keycloak - template: + ansible.builtin.template: src: "xwiki_apache.conf" dest: "/etc/apache2/sites-available/xwiki.conf" owner: root @@ -111,42 +112,50 @@ mode: '0600' - name: Enable virtualhost - shell: + ansible.builtin.command: cmd: a2ensite xwiki + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Restart Apache webserver - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: apache2 - enabled: yes + enabled: true state: restarted - - name: remove existing filestore if exists - file: + - name: Remove existing filestore if exists + ansible.builtin.file: path: /etc/tomcat9/keystore.pkcs12 state: absent # You will thank me later: https://coderwall.com/p/3t4xka/import-private-key-and-certificate-into-java-keystore # USE PASSWORD!: changeit - name: Export complete ssl chain into one file - shell: - cmd: /usr/bin/openssl pkcs12 -export -in /root/_wildcard.{{ domain }}.pem -inkey /root/_wildcard.{{ domain }}-key.pem -chain -CAfile /root/rootCA.pem -name "{{ domain }}" -passout pass:changeit -out "/root/{{ domain }}.p12" - - - name: "Import wildcard key for i{{ domain }} into keycloak keystore file" - shell: - cmd: /usr/bin/keytool -importkeystore -srcstorepass changeit -deststorepass changeit -destkeystore /etc/tomcat9/keystore.pkcs12 -srckeystore "/root/{{ domain }}.p12" -srcstoretype PKCS12 + ansible.builtin.command: + cmd: "/usr/bin/openssl pkcs12 -export -in /root/_wildcard.{{ domain }}.pem -inkey /root/_wildcard.{{ domain }}-key.pem \ + -chain -CAfile /root/rootCA.pem -name '{{ domain }}' -passout pass:changeit -out '/root/{{ domain }}.p12'" + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + - name: "Import wildcard key into keycloak keystore file for i{{ domain }}" + ansible.builtin.command: + cmd: "/usr/bin/keytool -importkeystore -srcstorepass changeit -deststorepass changeit \ + -destkeystore /etc/tomcat9/keystore.pkcs12 -srckeystore '/root/{{ domain }}.p12' -srcstoretype PKCS12" chdir: /etc/tomcat9/ + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Enable Tomcat9 ssl connector on 8443 # Do not try to do this the hard way with some Ansible XML magix. It doesn't work. # Simply copy from local files - name: Overwrite /etc/tomcat9/server.xml with enabled https connector definition from local file - copy: + ansible.builtin.copy: src: files/xwiki_tomcat9_server.xml dest: /etc/tomcat9/server.xml owner: root group: tomcat - force: yes + force: true mode: '0640' # Memory @@ -163,7 +172,7 @@ # edit /etc/default/tomcat9 file and add # JAVA_OPTS="${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom" - name: Tune Java settings in /etc/default/tomcat9 - lineinfile: + ansible.builtin.lineinfile: path: /etc/default/tomcat9 regexp: "JAVA_OPTS=.*" line: 'JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom -Xmx2048m" ' @@ -175,65 +184,65 @@ # openoffice.autoStart=true # openoffice.homePath=/usr/lib/libreoffice/ # ====== Contents config file ======= - # #-# Type of the openoffice server instance used by officeimporter component. + # # Type of the openoffice server instance used by officeimporter component. # openoffice.serverType = 0 - #-# Port numbers used for connecting to the openoffice server instance. + # Port numbers used for connecting to the openoffice server instance. # openoffice.serverPorts = 8100,8101 - #-# If the openoffice server should be started / connected upon XE start. + # If the openoffice server should be started / connected upon XE start. # openoffice.autoStart = false - #-# Path to openoffice installation (serverType:0 only). + # Path to openoffice installation (serverType:0 only). # openoffice.homePath = /opt/openoffice.org3/ - #-# Path to openoffice execution profile (serverType:0 only). + # Path to openoffice execution profile (serverType:0 only). # openoffice.profilePath = /home/user/.openoffice.org/3 - #-# Maximum number of simultaneous conversion tasks to be handled by a single openoffice process (serverType:0 only). + # Maximum number of simultaneous conversion tasks to be handled by a single openoffice process (serverType:0 only). # openoffice.maxTasksPerProcess = 50 # openoffice.taskExecutionTimeout = 60000 # ====================================== - name: Autostart Libreoffice integration into Xwiki - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: ".*openoffice.autoStart.*" line: 'openoffice.autoStart=true' - name: Config Libreoffice path for Xwiki - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: ".*openoffice.homePath.*" line: 'openoffice.homePath=/usr/lib/libreoffice/' - # #-# Define a list of trusted domains that can be used in the wiki for performing requests or redirections even if - #-# the wiki does not use it. Domains are listed without http and separated with a comma in the list. Subdomains can be - #-# specified. - #-# Example of accepted value: foo.acme.org,enterprise.org - #-# - #-# By default the list of trusted domains is empty: + # Define a list of trusted domains that can be used in the wiki for performing requests or redirections even if + # the wiki does not use it. Domains are listed without http and separated with a comma in the list. Subdomains can be + # specified. + # Example of accepted value: foo.acme.org,enterprise.org + # + # By default the list of trusted domains is empty: # url.trustedDomains= - - name: "Add url.trustedDomains={{ domain }} to /etc/xwiki/xwiki.properties" - lineinfile: + - name: "Add to /etc/xwiki/xwiki.properties url.trustedDomains={{ domain }} " + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: ".*url.trustedDomains=.*" line: "url.trustedDomains={{ domain }}" - #-# Allow to enable or disable checks performed on domains by taking into account the list of trusted domains. - #-# Disable this property only if you experienced some issues on your wiki: some security check won't be performed when - #-# this property is set to false. - #-# - #-# By default this property is set to true: + # Allow to enable or disable checks performed on domains by taking into account the list of trusted domains. + # Disable this property only if you experienced some issues on your wiki: some security check won't be performed when + # this property is set to false. + # + # By default this property is set to true: # url.trustedDomainsEnabled=true # Find xwiki.authentication.authclass in xwiki.cfg and comment it out with #-# in the beginning. # Add below: xwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl - name: Enable Keycloak SSO oidc auth services in Xwiki - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.cfg regexp: ".*xwiki.authentication.authclass=.*" line: "xwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl" # xwiki.home=https://MYWIKIDOMAIN/ - name: Set xwiki.home in xwiki.cfg - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.cfg regexp: ".*xwiki.home=.*" line: "xwiki.home={{ xwiki_server_url }}" @@ -242,7 +251,7 @@ # Open xwiki.properties and adapt the following to your settings, and add this at the end of the file (Note the __XXX__ parts): # oidc.xwikiprovider=https://__YOUR-WIKI-ADDRESS__/xwiki/oidc - name: Configure oidc.xwikiprovider - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.xwikiprovider=.*" line: "oidc.xwikiprovider={{ xwiki_server_url }}/xwiki/oidc" @@ -250,7 +259,7 @@ # oidc.skipped=false - name: Configure - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.skipped=.*" line: "oidc.skipped=false" @@ -267,16 +276,17 @@ # "account-service": "https://ad.onestein.lan:8443/realms/master/account", # "tokens-not-before": 0 - name: Retrieve token url from server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/master" validate_certs: false register: tokenurl - - debug: + - name: Show token-service + ansible.builtin.debug: var: tokenurl.json["token-service"] - name: Store url for easier retrieval - set_fact: + ansible.builtin.set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" # Call info endpoint @@ -294,88 +304,94 @@ # "check_session_iframe": "https://ad.onestein.lan:8443/realms/ONESTEIN.LAN/protocol/openid-connect/login-status-iframe.html" # ... and a lot more... - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" validate_certs: false register: endpointinfo - - debug: + - name: Show endpoint info + ansible.builtin.debug: var: endpointinfo - name: Store authorization_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" - - debug: + + - name: Show authorization endpoint + ansible.builtin.debug: var: authorization_endpoint - name: Store token_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - - debug: + + - name: Show token endpoint + ansible.builtin.debug: var: token_endpoint - name: Store userinfo_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: + + - name: Show userinfo endpoint + ansible.builtin.debug: var: userinfo_endpoint # Relocated this code to here so we can generate better strings based on result of prior REST call # oidc.endpoint.authorization=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/auth - name: Configure oidc.endpoint.authorization - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.authorization=.*" line: "oidc.endpoint.authorization={{ authorization_endpoint }}" - #line: "oidc.endpoint.authorization={{ keycloak_server_url }}/realms/{{ realm }}/protocol/openid-connect/auth" + # line: "oidc.endpoint.authorization={{ keycloak_server_url }}/realms/{{ realm }}/protocol/openid-connect/auth" # oidc.endpoint.token=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/token - name: Configure oidc.endpoint.token - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.token=.*" line: "oidc.endpoint.token={{ token_endpoint }}" - #line: "oidc.endpoint.token={{ keycloak_server_url }}/realms/{{ realm }}/protocol/openid-connect/token" + # line: "oidc.endpoint.token={{ keycloak_server_url }}/realms/{{ realm }}/protocol/openid-connect/token" # oidc.endpoint.userinfo=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/userinfo - name: Configure oidc.endpoint.userinfo - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.userinfo=.*" line: "oidc.endpoint.userinfo={{ userinfo_endpoint }}" - #line: "oidc.endpoint.userinfo={{ keycloak_server_url }}/realms/{{ realm }}/protocol/openid-connect/userinfo" + # line: "oidc.endpoint.userinfo={{ keycloak_server_url }}/realms/{{ realm }}/protocol/openid-connect/userinfo" # oidc.scope=openid,profile,email,address - name: Configure oidc.scope - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.scope=.*" line: "oidc.scope=openid,profile,email,address" - name: Configure - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.defaultClientConfiguration=.*" line: "oidc.defaultClientConfiguration=default" - # oidc.endpoint.userinfo.method=GET - name: Configure oidc.endpoint.userinfo.method - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.userinfo.method=.*" line: "oidc.endpoint.userinfo.method=GET" # oidc.user.nameFormater=${oidc.user.preferredUsername._clean._lowerCase} - name: Configure oidc.user.nameFormater - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.user.nameFormater=.*" line: "oidc.user.nameFormater=${oidc.user.preferredUsername._clean._lowerCase}" # oidc.user.subjectFormater=${oidc.user.subject} - name: Configure oidc.user.subjectFormater - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.user.subjectFormater=.*" line: "oidc.user.subjectFormater=${oidc.user.subject}" @@ -389,21 +405,21 @@ # oidc.userinfoclaims=xwiki_user_accessibility,xwiki_user_company,xwiki_user_displayHiddenDocuments,xwiki_user_editor,xwiki_user_usertype - name: Configure oidc.userinfoclaims - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.userinfoclaims=.*" line: "oidc.userinfoclaims=xwiki_user_accessibility,xwiki_user_company,xwiki_user_displayHiddenDocuments,xwiki_user_editor,xwiki_user_usertype" # # oidc.userinforefreshrate=600000 - name: Configure oidc.userinforefreshrate - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.userinforefreshrate=.*" line: "oidc.userinforefreshrate=600000" # oidc.clientid=__KEYCLOAK-CLIENT-ID__ - name: Configure oidc.clientid - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.clientid=.*" line: "oidc.clientid={{ xwiki_client_id }}" @@ -411,7 +427,7 @@ # oidc.endpoint.token.auth_method=client_secret_basic # Changed this to client_secret_post and retry - name: Configure oidc.endpoint.token.auth_method=client_secret_post - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.endpoint.token.auth_method=.*" line: "oidc.endpoint.token.auth_method=client_secret_post" @@ -419,7 +435,7 @@ # Get authentication token from token-service url - name: Retrieve authentication token from token-service url - uri: + ansible.builtin.uri: url: "{{ tokenurl.json[\"token-service\"] }}/token" method: POST body_format: form-urlencoded @@ -432,17 +448,20 @@ grant_type: password register: authtoken - - debug: + - name: Show authtoken + ansible.builtin.debug: var: authtoken - - debug: + - name: Show access-token + ansible.builtin.debug: var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval - set_fact: + ansible.builtin.set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - - debug: + - name: Show auth_token + ansible.builtin.debug: var: auth_token ####################################################################################### @@ -453,49 +472,47 @@ # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.onestein.lan:8443/admin/realms/master/clients" | jq . - name: Retrieve current list of clients and search for already existing "{{ xwiki_client_id }} " - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ xwiki_client_id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient - name: Find ID in returned json - debug: + ansible.builtin.debug: var: existingclient.json[0].id - - name: Delete client id "{{ xwiki_client_id }}" if it already exists. + - name: If it already exists then delete client id "{{ xwiki_client_id }}" . # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - - debug: + - name: Show deleteclient var + ansible.builtin.debug: var: deleteclient - name: Convert Ninja template to variable - set_fact: + ansible.builtin.set_fact: jsonbody: "{{ lookup('template', 'xwiki-keycloak-sso.json.j2') }}" -# - debug: -# var: jsonbody - # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: POST validate_certs: false body_format: json @@ -506,7 +523,7 @@ # Good news! Result contains location of new client id in location # Example: "location": "https://ad.onestein.lan:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID - debug: + ansible.builtin.debug: var: createclientresult.location # # You can also search the client id using this piece of code @@ -525,24 +542,24 @@ # Generate and retrieve a new Client-Secret to put into xwiki.properties under oidc.secret=__KEYCLOAK-CLIENT-SECRET__ # GET /{realm}/clients/{id}/client-secret - name: Retrieve secret for newly created client "{{ xwiki_client_id }} " - uri: + ansible.builtin.uri: url: "{{ createclientresult.location }}/client-secret" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: clientsecret - name: Store secret for easy retrieval - set_fact: + ansible.builtin.set_fact: client_secret: "{{ clientsecret.json.value }} " # Aim is: In the end, Client-ID and Client-Secret have to match on Keycloak and in xwiki.properties. # oidc.secret=__KEYCLOAK-CLIENT-SECRET__ - name: Configure oidc.secret - lineinfile: + ansible.builtin.lineinfile: path: /etc/xwiki/xwiki.properties regexp: "oidc.secret=.*" line: "oidc.secret={{ client_secret }}" @@ -551,14 +568,13 @@ # Aim is: In the end, Client-ID and Client-Secret have to match on Keycloak and in xwiki.properties. # - name: Enable and start tomcat9 - service: + ansible.builtin.service: name: tomcat9 state: restarted - enabled: yes - + enabled: true - name: Post-install message IT IS IMPORTANT TO READ THIS - debug: + ansible.builtin.debug: msg: | ******************************************************************************************************** * After a fresh Xwiki installation you need to manually install (within Xwiki!) the following extension: @@ -573,5 +589,3 @@ * Now click on Continue to do the actual install * After that ssh to the server and do a systemctl restart tomcat9 ******************************************************************************************************** - - diff --git a/playbooks/install-zabbix-server-sso.yml b/playbooks/install-zabbix-server-sso.yml index ade5396..97728c9 100644 --- a/playbooks/install-zabbix-server-sso.yml +++ b/playbooks/install-zabbix-server-sso.yml @@ -2,54 +2,62 @@ - name: Install Zabbix 5.4 server hosts: grpzabbixserver remote_user: root - become: true - become_user: root - become_method: sudo + # use this command line: ansible-playbook -vvv --ask-pass --ask-become-pass setup-new-server.yml -i 10.1.1.158, -u jeroen vars: ansible_python_interpreter: /usr/bin/python3 - supported_distros: "{{ ansible_distribution }} {{ ansible_distribution_major_version }}" zabbix_repo: zabbix-release_5.4-1+ubuntu20.04_all.deb keycloak_server_url: https://ad.{{ domain }} zabbix_server_url: https://zabbix.{{ domain }} zabbix_client_id: "client-zabbix-{{ ansible_fqdn }}" zabbix_client_name: "client-zabbix-{{ ansible_fqdn }}" + handlers: + - name: Initial db load + become: true + become_user: zabbix + ansible.builtin.command: + cmd: set -o pipefail && zcat /usr/share/doc/zabbix-sql-scripts/postgresql/create.sql.gz | psql zabbix + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + tasks: - - debug: + - name: Show tip for sensible output + ansible.builtin.debug: msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read global vars - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - - name: check if server is Ubuntu 20.04 - fail: msg='Unsupported platform! This playbook is for Ubuntu 20.04!' - when: supported_distros not in ["Ubuntu 20"] + - name: Check if server is Ubuntu 20.04 + ansible.builtin.fail: + msg: "'Unsupported platform! This playbook is for Ubuntu 20.04!" + when: not (ansible_distribution == 'Ubuntu' and ansible_distribution_version == '20.04') - name: Download Zabbix 5.4 repo package for Ubuntu 20.04 - get_url: + ansible.builtin.get_url: url: "https://repo.zabbix.com/zabbix/5.4/ubuntu/pool/main/z/zabbix-release/{{ zabbix_repo }}" dest: "/tmp/{{ zabbix_repo }}" mode: '0644' - when: ansible_distribution == 'Ubuntu' and ansible_distribution_version == '20.04' + when: ansible_distribution == 'Ubuntu' and ansible_distribution_version == '20.04' - - name: install 5.4 repo list - apt: + - name: Install 5.4 repo list + ansible.builtin.apt: deb: "/tmp/{{ zabbix_repo }}" - update_cache: yes + update_cache: true - name: Extra update apt cache ansible.builtin.apt: - update_cache: yes + update_cache: true # Install all needed software in one go. - name: Install all needed packages - apt: + ansible.builtin.apt: name: "{{ item }}" loop: - postgresql @@ -72,7 +80,7 @@ # Edit file /etc/zabbix/zabbix_server.conf, DBPassword=password - name: Configure zabbix database password - lineinfile: + ansible.builtin.lineinfile: path: /etc/zabbix/zabbix_server.conf regexp: '#DBPassword=' line: "DBPassword=zabbix" @@ -85,35 +93,31 @@ - name: Setup Zabbix Postgresql database user become: true become_user: postgres - postgresql_user: + community.postgresql.postgresql_user: name: zabbix password: 'zabbix' - #priv: "CONNECT" + # priv: "CONNECT" - name: Setup Zabbix Postgresql database become: true become_user: postgres - postgresql_db: + community.postgresql.postgresql_db: name: zabbix template: 'template0' owner: zabbix state: present register: dbwork + notify: + - Initial db load - - debug: + - name: Show dbwork var + ansible.builtin.debug: msg: "{{ dbwork }}" - - name: Load initial Zabbix dataset when db just created - become: true - become_user: zabbix - shell: - cmd: zcat /usr/share/doc/zabbix-sql-scripts/postgresql/create.sql.gz | psql zabbix - when: dbwork.changed - - name: (Re)start Zabbix agent - ansible.builtin.systemd: + ansible.builtin.systemd_service: name: zabbix-agent - enabled: yes + enabled: true state: restarted - name: Make SSL dir for nginx @@ -123,7 +127,7 @@ mode: '0755' - name: Copy SSL key and cert to ssl dir - copy: + ansible.builtin.copy: src: "files/{{ item }}" dest: "/etc/nginx/ssl/{{ item }}" owner: root @@ -134,9 +138,10 @@ - _wildcard.{{ domain }}-key.pem - name: Install nginx config file - copy: + ansible.builtin.copy: src: files/zabbix_nginx.conf dest: /etc/nginx/sites-available/default + mode: '0644' # Docs describing a working setup. Zabbix currently only supports SAML out @@ -149,58 +154,67 @@ # Generate a key on the Zabbix server - name: Generate key on Zabbix server - shell: + ansible.builtin.command: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:2048 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /usr/share/zabbix/conf/certs + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. - name: Retrieve token url from server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/master" validate_certs: false register: tokenurl - - debug: + - name: Show token-service + ansible.builtin.debug: var: tokenurl.json["token-service"] - name: Store url for easier retrieval - set_fact: + ansible.builtin.set_fact: token_url: "{{ tokenurl.json[\"token-service\"] }}" # Call info endpoint - name: "Retrieve endpoint info for our realm {{ realm }}" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/.well-known/openid-configuration" validate_certs: false register: endpointinfo - - debug: + - name: Show endpoint info + ansible.builtin.debug: var: endpointinfo - name: Store authorization_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: authorization_endpoint: "{{ endpointinfo.json[\"authorization_endpoint\"] }}" # "authorization_endpoint": "https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/openid-connect/auth", - - debug: + - name: Show authorization endpoint + ansible.builtin.debug: var: authorization_endpoint - name: Store token_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: token_endpoint: "{{ endpointinfo.json[\"token_endpoint\"] }}" - - debug: + + - name: Show token endpoint + ansible.builtin.debug: var: token_endpoint - name: Store userinfo_endpoint for faster retrieval - set_fact: + ansible.builtin.set_fact: userinfo_endpoint: "{{ endpointinfo.json[\"userinfo_endpoint\"] }}" - - debug: + + - name: Show userinfo endpoint + ansible.builtin.debug: var: userinfo_endpoint # Get authentication token from token-service url - name: Retrieve authentication token from token-service url - uri: + ansible.builtin.uri: url: "{{ tokenurl.json[\"token-service\"] }}/token" method: POST body_format: form-urlencoded @@ -208,39 +222,43 @@ body: realm: master client_id: admin-cli - username: "{{kc_adminid }}" - password: "{{kc_adminpw }}" + username: "{{ kc_adminid }}" + password: "{{ kc_adminpw }}" grant_type: password register: authtoken - - debug: + - name: Show var authtoken + ansible.builtin.debug: var: authtoken - - debug: + - name: Show var access_token + ansible.builtin.debug: var: authtoken.json["access_token"] - name: Store access token into variable for easier retrieval - set_fact: + ansible.builtin.set_fact: auth_token: "{{ authtoken.json[\"access_token\"] }}" - - debug: + - name: Show auth_token + ansible.builtin.debug: var: auth_token # Retrieve IDP metadata descriptor and copy the 509 formatted certificate # https://ad.{{ domain }}/realms/ONESTEIN.LAN/protocol/saml/descriptor - name: Retrieve IDP metadata descriptor to use the 509 formatted certificate - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" # headers: # Accept: "application/json" # Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false - return_content: yes + return_content: true register: idp_metadata - - debug: + - name: Show idp metadata + ansible.builtin.debug: var: idp_metadata # Please do not go down this road and stay sane... @@ -253,28 +271,32 @@ # We are going to save the XML metadate for shell processing - name: Save IDP XML metadata to file for processing - copy: + ansible.builtin.copy: content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml + mode: '0600' # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate - shell: + ansible.builtin.command: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml register: xmlstarlet + changed_when: xmlstarlet.rc != 0 # <- Uses the return code to define when the task has changed. + - - debug: + - name: Show var xmlstarlet + ansible.builtin.debug: var: xmlstarlet - name: Store output in certificate variable - set_fact: + ansible.builtin.set_fact: certificate: "{{ xmlstarlet.stdout }}" - name: Create idp.crt file - template: + ansible.builtin.template: src: templates/zabbix-sso-idp.crt.j2 dest: /usr/share/zabbix/conf/certs/idp.crt - force: yes + force: true owner: root group: root mode: '0644' @@ -285,35 +307,36 @@ # So, this works in curl: # curl -k -v -H "Accept: application/json" -H "Authorization: Bearer ${access_token}" "https://ad.{{ domain }}:8443/admin/realms/master/clients" | jq . - name: Retrieve current list of clients and search for already existing "{{ zabbix_client_id }} " - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ zabbix_client_id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient - name: Returned json - debug: + ansible.builtin.debug: var: existingclient - name: Find ID in returned json - debug: + ansible.builtin.debug: var: existingclient.json[0].id - - name: copy remote ssl files to remote /tmp - copy: - remote_src: yes + - name: Copy remote ssl files to remote /tmp + ansible.builtin.copy: + remote_src: true src: "/usr/share/zabbix/conf/certs/{{ item }}" dest: "/tmp/{{ item }}" + mode: '0644' loop: - sp.crt - sp.key # - idp.crt - name: Remove first line from tmp files - lineinfile: + ansible.builtin.lineinfile: path: "/tmp/{{ item }}" regexp: '^-----(BEGIN|END).*-----$' state: absent @@ -323,56 +346,64 @@ # - idp.crt - name: Retrieve remote ssl cert - shell: + ansible.builtin.command: cmd: cat /tmp/sp.crt | tr -d '\n' register: sp_crt + changed_when: sp_crt.rc != 0 # <- Uses the return code to define when the task has changed. - - debug: + - name: Show sp crt + ansible.builtin.debug: var: sp_crt - name: Retrieve remote ssl key - shell: + ansible.builtin.command: cmd: cat /tmp/sp.key | tr -d '\n' register: sp_key + changed_when: sp_key.rc != 0 # <- Uses the return code to define when the task has changed. + - - debug: + - name: Show sp key + ansible.builtin.debug: var: sp_key - - name: Delete client id "{{ zabbix_client_id }}" if it already exists. + - name: If it already exists then delete client id "{{ zabbix_client_id }}". # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 when: existingclient.json[0].id is defined register: deleteclient - - debug: + - name: Show deleteclient var + ansible.builtin.debug: var: deleteclient - name: Convert Ninja template to variable - set_fact: + ansible.builtin.set_fact: jsonbody: "{{ lookup('template', 'zabbix-keycloak-sso.json.j2') }}" - - debug: + - name: Show json body + ansible.builtin.debug: var: jsonbody - name: For debugging store json var in local file - copy: + ansible.builtin.copy: content: "{{ jsonbody }}" dest: /tmp/jsonbody + mode: '0600' # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server - uri: + ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: POST validate_certs: false body_format: json @@ -383,24 +414,24 @@ # Good news! Result contains location of new client id in location # Example: "location": "https://ad.{{ domain }}:8443/admin/realms/ONESTEIN.LAN/clients/e01a87cf-537c-441e-b6ee-17f4cd07d92c" - name: If all went well we now have a locaton of the newly created Client ID - debug: + ansible.builtin.debug: var: createclientresult.location - name: (Re)start Zabbix server ansible.builtin.systemd: name: zabbix-server - enabled: yes + enabled: true state: restarted - name: (Re)start Nginx server ansible.builtin.systemd: name: nginx - enabled: yes + enabled: true state: restarted - name: Post-install message IT IS IMPORTANT TO READ THIS - debug: + ansible.builtin.debug: msg: | ********************************************************************************************************** * After a fresh installation of Zabbix you need to run the setup process. @@ -427,5 +458,3 @@ * Now login to Zabbix not by entering userid/password. * But by clicking on the 'Sign in with Single Sign-On (SAML)' link. ********************************************************************************************************** - - From 14ca4b96db797640ef39c2b264d5f15cda0eb616 Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Sun, 19 May 2024 16:40:03 +0200 Subject: [PATCH 06/12] Adding two links to talk recordings. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 28c0fbf..e1ff810 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,6 @@ Jeroen "Kwoot" Baten # Media +- [Recording of my talk about this project at FOSDEM 2024](https://mirror.as35701.net/video.fosdem.org/2024/k3401/fosdem-2024-1665-making-ansible-playbooks-to-configure-single-sign-on-for-popular-open-source-applications.av1.webm) +- [Recording of my talk about this project at cfgmgmtcamp 2023](https://www.youtube.com/watch?v=LXrtg0_wMqc). - [5 minute YouTube talk at the 2022 Nextcloud conference about this project](https://www.youtube.com/watch?v=pDPKzo8Bi10). From 26578cdac912635fc1c22678f53fb89e9b61442f Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Mon, 27 May 2024 09:17:25 +0200 Subject: [PATCH 07/12] Fixing typos --- playbooks/install-cmdb-sso.yml | 2 +- playbooks/install-gitlab-sso.yml | 4 +++- playbooks/install-keycloak-nginx.yml | 2 +- playbooks/install-odoo-sso.yml | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/playbooks/install-cmdb-sso.yml b/playbooks/install-cmdb-sso.yml index 0aab74a..14a8464 100644 --- a/playbooks/install-cmdb-sso.yml +++ b/playbooks/install-cmdb-sso.yml @@ -114,7 +114,7 @@ register: dbwork - name: Show dbwork variable - community.postgresql.postgresql_userdebug: + ansible.builtin.debug: msg: "{{ dbwork }}" # Copy db init file to /tmp so user postgres can read it diff --git a/playbooks/install-gitlab-sso.yml b/playbooks/install-gitlab-sso.yml index 6122d53..aa2267c 100644 --- a/playbooks/install-gitlab-sso.yml +++ b/playbooks/install-gitlab-sso.yml @@ -407,8 +407,10 @@ } ] - name: Run GitLab reconfiguration script - sansible.builtin.hell: + sansible.builtin.command: cmd: gitlab-ctl reconfigure + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Start GitLab ansible.builtin.shell: diff --git a/playbooks/install-keycloak-nginx.yml b/playbooks/install-keycloak-nginx.yml index bfd0834..22cbd19 100644 --- a/playbooks/install-keycloak-nginx.yml +++ b/playbooks/install-keycloak-nginx.yml @@ -209,7 +209,7 @@ # Enable and start systemd keycloak service - name: Configure Keycloak systemd - ansible.builtin.systemd_Service: + ansible.builtin.systemd_service: name: keycloak enabled: yes state: restarted diff --git a/playbooks/install-odoo-sso.yml b/playbooks/install-odoo-sso.yml index 489f150..0e75107 100644 --- a/playbooks/install-odoo-sso.yml +++ b/playbooks/install-odoo-sso.yml @@ -119,7 +119,7 @@ changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Restart Odoo server to make sure the new module is available in the app store. - ansible.builtin.systemd_Service: + ansible.builtin.systemd_service: name: odoo-server state: restarted From 9153f7d1fbadd197d7c3047665dfaabd4cf4f966 Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Mon, 27 May 2024 09:18:58 +0200 Subject: [PATCH 08/12] Fix typo --- playbooks/install-gitlab-sso.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playbooks/install-gitlab-sso.yml b/playbooks/install-gitlab-sso.yml index aa2267c..aead0bd 100644 --- a/playbooks/install-gitlab-sso.yml +++ b/playbooks/install-gitlab-sso.yml @@ -407,7 +407,7 @@ } ] - name: Run GitLab reconfiguration script - sansible.builtin.command: + ansible.builtin.command: cmd: gitlab-ctl reconfigure register: my_output # <- Registers the command output. changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. From 2cf61f6d8026fe4bfa3fbba3db2d38ec33891b21 Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Mon, 27 May 2024 09:38:17 +0200 Subject: [PATCH 09/12] Changed playbook top FQ names. --- playbooks/install-bitwarden-part-1.yml | 743 +++++++++++++------------ 1 file changed, 378 insertions(+), 365 deletions(-) diff --git a/playbooks/install-bitwarden-part-1.yml b/playbooks/install-bitwarden-part-1.yml index edb19d9..a9ee33f 100644 --- a/playbooks/install-bitwarden-part-1.yml +++ b/playbooks/install-bitwarden-part-1.yml @@ -15,370 +15,383 @@ keycloak_server_url: https://ad.{{ domain }} tasks: - - name: test connection to host - ping: - - - name: Read global vars - include_vars: global-vars.yml - - - name: Read encrypted content - include_vars: encrypted-vars.yml - - - name: Set timezone to Europe/Amsterdam - timezone: - name: Europe/Amsterdam - - # Add docker key - # curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - - name: Add docker key - apt_key: - url: https://download.docker.com/linux/ubuntu/gpg - state: present - - # Add docker repo - # sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - - name: Add docker repo - shell: - cmd: add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - - # - # apt update - # sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common - # sudo apt install docker-ce docker-ce-cli containerd.io docker-compose - - name: Install needed packages - apt: - name: "{{ item }}" - state: present - loop: - - apt-transport-https - - ca-certificates - - curl - - gnupg-agent - - software-properties-common - - docker-ce - - docker-ce-cli - - containerd.io - - docker-compose - - python3-pexpect - - unzip - - - # Create a user to run Bitwarden. Use bash as the default terminal and /opt/bitwarden as the home directory. - - #$ sudo useradd -s /bin/bash -d /opt/bitwarden bitwarden - # Add user bitwarden to group docker so it can access Docker. - #$ sudo mkdir -p /opt/bitwarden - - name: Create a user to run Bitwarden. - user: - name: bitwarden - shell: /bin/bash - home: /opt/bitwarden - groups: docker - - # Make dir for SSL keys - - name: Make SSL dir for keys - become: yes - become_user: bitwarden - become_method: sudo - shell: - cmd: "mkdir -p /opt/bitwarden/bwdata/ssl/self/{{ ansible_fqdn }}" - -# - name: Copy SSL key and cert to "/etc/nginx/ssl/self/{{ ansible_fqdn }}" for later use -# copy: -# src: "files/{{ item }}" -# dest: "/etc/nginx/ssl/self/{{ ansible_fqdn }}/{{ item }}" -# owner: bitwarden -# group: bitwarden -# mode: '0600' -# loop: -# - _wildcard.{{ domain }}.pem -# - _wildcard.{{ domain }}-key.pem - - #$ sudo chown bitwarden: /opt/bitwarden - #$ sudo chmod 700 /opt/bitwarden - - #Switch to the new user. - #$ sudo su - bitwarden - #2. Install Bitwarden Server - #Download the official Bitwarden deployment script: - #$ wget -O bitwarden.sh https://go.btwrdn.co/bw-sh - #$ chmod +x bitwarden.sh - - name: Download bitwarden install script in homedir of bitwarden user - get_url: - url: https://go.btwrdn.co/bw-sh - dest: /opt/bitwarden/bitwarden.sh - owner: bitwarden - group: bitwarden - mode: '0755' - - # Check if /opt/bitwarden/bwdata/ exists - - name: Check if /opt/bitwarden/bwdata/ already exists - stat: - path: "/opt/bitwarden/bwdata/" - register: bwdata_dir_stat - - - name: Is /opt/bitwarden/bwdata/ already present? - debug: - msg: "/opt/bitwarden/bwdata/ already present" - when: bwdata_dir_stat.stat.exists - - - name: Download extra Bitwarden CLI tool - get_url: - url: "https://vault.bitwarden.com/download/?app=cli&platform=linux" - dest: /tmp/bw-cli.zip - owner: bitwarden - group: bitwarden - mode: '0644' - - - name: Unpack Bitwarden CLI tool - ansible.builtin.unarchive: - src: "/tmp/bw-cli.zip" - dest: /opt/bitwarden - owner: bitwarden - group: bitwarden - remote_src: yes - - - name: make Bitwarden CLI tool executable - file: - path: /opt/bitwarden/bw - owner: bitwarden - group: bitwarden - mode: '0755' - - - # If not present we now need to run the install script. - # $ ./bitwarden.sh install - # - # Use the form at https://bitwarden.com/host/ to request your private Installation Id and Installation Key for self-hosting Bitwarden. - # The Installation Id and Key are applied during installation of your Bitwarden instance and stored here: ./bwdata/env/global.override.env  - # You should use a unique id and key for each Bitwarden installation. - # INSTALLATION ID: 8c7729c8-a13e-4110-ae36-ae52008c2724 - # INSTALLATION KEY: oslnBjE2l0WtcEvD9VcJ - # Run the install script - # _ _ _ _ - # | |__ (_) |___ ____ _ _ __ __| | ___ _ __ - # | '_ \| | __\ \ /\ / / _` | '__/ _` |/ _ \ '_ \ - # | |_) | | |_ \ V V / (_| | | | (_| | __/ | | | - # |_.__/|_|\__| \_/\_/ \__,_|_| \__,_|\___|_| |_| - # - # Open source password management solutions - # Copyright 2015-2022, 8bit Solutions LLC - # https://bitwarden.com, https://github.com/bitwarden - # - # =================================================== - # - # bitwarden.sh version 2022.8.4 - # Docker version 20.10.17, build 100c701 - # docker-compose version 1.25.0, build unknown - # - # (!) Enter the domain name for your Bitwarden instance (ex. bitwarden.example.com): password.{{ domain }} - # - # (!) Do you want to use Let's Encrypt to generate a free SSL certificate? (y/n): n - # - # (!) Enter the database name for your Bitwarden instance (ex. vault): vault - # - # 2022.8.4: Pulling from bitwarden/setup - # 1efc276f4ff9: Pull complete - # e5aeae5c9ad4: Pull complete - # 9d8b4edc672a: Pull complete - # 67bb3a123350: Pull complete - # 4b31f33ff8ee: Pull complete - # 8302c6d93c2f: Pull complete - # 64c1ff0e03a3: Pull complete - # 289e8b648bb1: Pull complete - # c706fe453135: Pull complete - # 6b18bfe90415: Pull complete - # Digest: sha256:257317606bad7b6c06755c81e4f61099b4af8b89829d7a9a2688545b92daa45f - # Status: Downloaded newer image for bitwarden/setup:2022.8.4 - # docker.io/bitwarden/setup:2022.8.4 - # - # (!) Enter your installation id (get at https://bitwarden.com/host): 8c7729c8-a13e-4110-ae36-ae52008c2724 - # - # (!) Enter your installation key: oslnBjE2l0WtcEvD9VcJ - # - # (!) Do you have a SSL certificate to use? (y/n): y - # - # !!!!!!!!!! NOTE !!!!!!!!!! - # Make sure 'certificate.crt' and 'private.key' are provided in the - # appropriate directory before running 'start' (see docs for info). - # - # (!) Is this a trusted SSL certificate (requires ca.crt, see docs)? (y/n): y - # - # Generating key for IdentityServer. - # Generating a RSA private key - # ...................................................................................++++ - # .................++++ - # writing new private key to 'identity.key' - # ----- - # - # Building nginx config. - # Building docker environment files. - # Building docker environment override files. - # Building FIDO U2F app id. - # Building docker-compose.yml. - # - # Installation complete - # - # If you need to make additional configuration changes, you can modify - # the settings in `./bwdata/config.yml` and then run: - # `./bitwarden.sh rebuild` or `./bitwarden.sh update` - # - # Next steps, run: - # `./bitwarden.sh start` - - name: Run the install script - become: yes - become_user: bitwarden - become_method: sudo - ansible.builtin.expect: - command: /opt/bitwarden/bitwarden.sh install - chdir: /opt/bitwarden - timeout: 120 - echo: yes - responses: - "Enter the domain name for your Bitwarden instance": "{{ ansible_fqdn }}" - "Do you want to use Let's Encrypt to generate a free SSL certificate": n - "Enter the database name for your Bitwarden instance": vault - "Enter your installation id": "{{ bitwarden_installation_id }}" - "Enter your installation key": "{{ bitwarden_installation_key }}" - "Do you have a SSL certificate to use": y - "Is this a trusted SSL certificate": y - ignore_errors: yes - - # Make dir for BitWarden SSL keys - - name: Make SSL dir for keys - become: yes - become_user: bitwarden - become_method: sudo - shell: - cmd: "mkdir -p /opt/bitwarden/bwdata/ssl/self/{{ ansible_fqdn }}" - - - # Use an Existing SSL Certificate - # - # You may alternatively opt to use an existing SSL Certificate, which will - # require you to have the following files: - # - # A Server Certificate (certificate.crt) - # A Private Key (private.key) - # A CA Certificate (ca.crt) - # - # You may need to bundle your primary certificate with Intermediate CA - # certificates to prevent SSL trust errors. All certificates should be - # included in the Server Certificate file when using a CA Certificate. The - # first certificate in the file should be your Server Certificate, followed by - # any Intermediate CA certificate(s), followed by the Root CA. - # - # Under the default configuration, place your files in - # ./bwdata/ssl/your.domain. You may specify a different location for your - # certificate files by editing the following values in ./bwdata/config.yml: - # - # ssl_certificate_path: - # ssl_key_path: - # ssl_ca_path: - # - # Make sure SSL filenames are correct - # ssl_certificate_path: /etc/ssl/self/password.{{ domain }}/certificate.crt - # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to - # `/etc/ssl` within the container. - # ssl_key_path: /etc/ssl/self/password.{{ domain }}/private.key - # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to - # `/etc/ssl` within the container. - # ssl_ca_path: - # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to - - name: Copy current SSL cert to correct directory ("/bwdata/ssl/{{ ansible_fqdn }}/" - become: yes - become_user: bitwarden - become_method: sudo - copy: - src: "files/_wildcard.{{ domain }}.pem" - dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" - - - debug: - msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" - -# Bitwarden NGINX borkt hierop -# - name: Append current CA to certificate -# shell: -# cmd: cat "{{ root_ca_path }}" >> "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" - - - name: Copy current SSL key to correct directory "bwdata/ssl/{{ ansible_fqdn }}/" - become: yes - become_user: bitwarden - become_method: sudo - copy: - src: "files/_wildcard.{{ domain }}-key.pem" - dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/private.key" - - - debug: - msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/private.key" - - - name: Copy CA certificate file key to correct directory - become: yes - become_user: bitwarden - become_method: sudo - copy: - src: "{{ root_ca_path }}" - remote_src: yes - dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/ca.crt" - - - debug: - msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/ca.crt" - - # TODO Next steps, run: - # `./bitwarden.sh start` - # 3. Configure the Environment - # Run ./bitwarden.sh start to start the Bitwarden Server. - # Note: Some Bitwarden features are not configured by the bitwarden.sh installer, and must be configured in the environment file, located at ./bwdata/env/global.override.env. At a minimum, you should configure: - # ... - # globalSettings__mail__smtp__host= - # globalSettings__mail__smtp__port= - # globalSettings__mail__smtp__ssl= - # globalSettings__mail__smtp__username= - # globalSettings__mail__smtp__password= - # ... - # adminSettings__admins= - - # If you need to make additional configuration changes, you can modify - # the settings in `./bwdata/config.yml` and then run: - # `./bitwarden.sh rebuild` or `./bitwarden.sh update` - - name: Implement changes in config files - become: yes - become_user: bitwarden - become_method: sudo - shell: - chdir: /opt/bitwarden - cmd: /opt/bitwarden/bitwarden.sh rebuild - - - # Run the following command to apply your changes: - # ./bitwarden.sh restart - - name: Restart all bitwarden services - become: yes - become_user: bitwarden - become_method: sudo - shell: - chdir: /opt/bitwarden - cmd: /opt/bitwarden/bitwarden.sh restart - - - ############################################################################################ - # Also this needs to be fixed. - # DO NOT EVER USE THE PIECE OF CRAP Bitwarden Directory Connector to sync users from ldap - ############################################################################################ - # SAML SSO: https://bitwarden.com/help/configure-sso-saml/ - - - debug: - msg: | - * Now go to URL "{{ bitwarden_server_url }}" - * Create a master user account. - * Go to "Settings" and add an organization using "+New Organization" link. - * Upload the Bitwarden license file for your organization. - * Go to the "Organizations" menu and open the "Settings" menu. - * Click on the "View API Key" button, enter your password and copy the "client_id" . - * Insert these into the next Bitwarden playbook as environment settings on the command line as follows; - * $ ansible-playbook install-bitwarden-part-2-saml-sso.yml -e client_id="your-client_id" . - * Please continue to the SAML playbook for bitwarden called "install-bitwarden-part-2-saml-sso.yml". - * We are done here. - **************************************************************************************** + - name: Test connection to host + ansible.builtin.ping: + + - name: Read global vars + ansible.builtin.include_vars: global-vars.yml + + - name: Read encrypted content + ansible.builtin.include_vars: encrypted-vars.yml + + - name: Set timezone to Europe/Amsterdam + community.general.timezone: + name: Europe/Amsterdam + + # Add docker key + # curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - + - name: Add docker key + ansible.builtin.apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + + # Add docker repo + # sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" + - name: Add docker repo + ansible.builtin.command: + cmd: add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # + # apt update + # sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common + # sudo apt install docker-ce docker-ce-cli containerd.io docker-compose + - name: Install needed packages + ansible.builtin.apt: + name: "{{ item }}" + state: present + loop: + - apt-transport-https + - ca-certificates + - curl + - gnupg-agent + - software-properties-common + - docker-ce + - docker-ce-cli + - containerd.io + - docker-compose + - python3-pexpect + - unzip + + + # Create a user to run Bitwarden. Use bash as the default terminal and /opt/bitwarden as the home directory. + + # $ sudo useradd -s /bin/bash -d /opt/bitwarden bitwarden + # Add user bitwarden to group docker so it can access Docker. + # $ sudo mkdir -p /opt/bitwarden + - name: Create a user to run Bitwarden. + ansible.builtin.user: + name: bitwarden + shell: /bin/bash + home: /opt/bitwarden + groups: docker + + # Make dir for SSL keys + - name: Make SSL dir for keys + become: yes + become_user: bitwarden + become_method: sudo + ansible.builtin.command: + cmd: "mkdir -p /opt/bitwarden/bwdata/ssl/self/{{ ansible_fqdn }}" + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # - name: Copy SSL key and cert to "/etc/nginx/ssl/self/{{ ansible_fqdn }}" for later use + # copy: + # src: "files/{{ item }}" + # dest: "/etc/nginx/ssl/self/{{ ansible_fqdn }}/{{ item }}" + # owner: bitwarden + # group: bitwarden + # mode: '0600' + # loop: + # - _wildcard.{{ domain }}.pem + # - _wildcard.{{ domain }}-key.pem + + # $ sudo chown bitwarden: /opt/bitwarden + # $ sudo chmod 700 /opt/bitwarden + + # Switch to the new user. + # $ sudo su - bitwarden + # 2. Install Bitwarden Server + # Download the official Bitwarden deployment script: + # $ wget -O bitwarden.sh https://go.btwrdn.co/bw-sh + # $ chmod +x bitwarden.sh + - name: Download bitwarden install script in homedir of bitwarden user + ansible.builtin.get_url: + url: https://go.btwrdn.co/bw-sh + dest: /opt/bitwarden/bitwarden.sh + owner: bitwarden + group: bitwarden + mode: '0755' + + # Check if /opt/bitwarden/bwdata/ exists + - name: Check if /opt/bitwarden/bwdata/ already exists + ansible.builtin.stat: + path: "/opt/bitwarden/bwdata/" + register: bwdata_dir_stat + + - name: Is /opt/bitwarden/bwdata/ already present? + ansible.builtin.debug: + msg: "/opt/bitwarden/bwdata/ already present" + when: bwdata_dir_stat.stat.exists + + - name: Download extra Bitwarden CLI tool + ansible.builtin.get_url: + url: "https://vault.bitwarden.com/download/?app=cli&platform=linux" + dest: /tmp/bw-cli.zip + owner: bitwarden + group: bitwarden + mode: '0644' + + - name: Unpack Bitwarden CLI tool + ansible.builtin.unarchive: + src: "/tmp/bw-cli.zip" + dest: /opt/bitwarden + owner: bitwarden + group: bitwarden + remote_src: yes + + - name: make Bitwarden CLI tool executable + ansible.builtin.file: + path: /opt/bitwarden/bw + owner: bitwarden + group: bitwarden + mode: '0755' + + + # If not present we now need to run the install script. + # $ ./bitwarden.sh install + # + # Use the form at https://bitwarden.com/host/ to request your private Installation Id and Installation Key for self-hosting Bitwarden. + # The Installation Id and Key are applied during installation of your Bitwarden instance and stored here: ./bwdata/env/global.override.env  + # You should use a unique id and key for each Bitwarden installation. + # INSTALLATION ID: 8c7729c8-a13e-4110-ae36-ae52008c2724 + # INSTALLATION KEY: oslnBjE2l0WtcEvD9VcJ + # Run the install script + # _ _ _ _ + # | |__ (_) |___ ____ _ _ __ __| | ___ _ __ + # | '_ \| | __\ \ /\ / / _` | '__/ _` |/ _ \ '_ \ + # | |_) | | |_ \ V V / (_| | | | (_| | __/ | | | + # |_.__/|_|\__| \_/\_/ \__,_|_| \__,_|\___|_| |_| + # + # Open source password management solutions + # Copyright 2015-2022, 8bit Solutions LLC + # https://bitwarden.com, https://github.com/bitwarden + # + # =================================================== + # + # bitwarden.sh version 2022.8.4 + # Docker version 20.10.17, build 100c701 + # docker-compose version 1.25.0, build unknown + # + # (!) Enter the domain name for your Bitwarden instance (ex. bitwarden.example.com): password.{{ domain }} + # + # (!) Do you want to use Let's Encrypt to generate a free SSL certificate? (y/n): n + # + # (!) Enter the database name for your Bitwarden instance (ex. vault): vault + # + # 2022.8.4: Pulling from bitwarden/setup + # 1efc276f4ff9: Pull complete + # e5aeae5c9ad4: Pull complete + # 9d8b4edc672a: Pull complete + # 67bb3a123350: Pull complete + # 4b31f33ff8ee: Pull complete + # 8302c6d93c2f: Pull complete + # 64c1ff0e03a3: Pull complete + # 289e8b648bb1: Pull complete + # c706fe453135: Pull complete + # 6b18bfe90415: Pull complete + # Digest: sha256:257317606bad7b6c06755c81e4f61099b4af8b89829d7a9a2688545b92daa45f + # Status: Downloaded newer image for bitwarden/setup:2022.8.4 + # docker.io/bitwarden/setup:2022.8.4 + # + # (!) Enter your installation id (get at https://bitwarden.com/host): 8c7729c8-a13e-4110-ae36-ae52008c2724 + # + # (!) Enter your installation key: oslnBjE2l0WtcEvD9VcJ + # + # (!) Do you have a SSL certificate to use? (y/n): y + # + # !!!!!!!!!! NOTE !!!!!!!!!! + # Make sure 'certificate.crt' and 'private.key' are provided in the + # appropriate directory before running 'start' (see docs for info). + # + # (!) Is this a trusted SSL certificate (requires ca.crt, see docs)? (y/n): y + # + # Generating key for IdentityServer. + # Generating a RSA private key + # ...................................................................................++++ + # .................++++ + # writing new private key to 'identity.key' + # ----- + # + # Building nginx config. + # Building docker environment files. + # Building docker environment override files. + # Building FIDO U2F app id. + # Building docker-compose.yml. + # + # Installation complete + # + # If you need to make additional configuration changes, you can modify + # the settings in `./bwdata/config.yml` and then run: + # `./bitwarden.sh rebuild` or `./bitwarden.sh update` + # + # Next steps, run: + # `./bitwarden.sh start` + - name: Run the install script + become: yes + become_user: bitwarden + become_method: sudo + ansible.builtin.expect: + command: /opt/bitwarden/bitwarden.sh install + chdir: /opt/bitwarden + timeout: 120 + echo: true + responses: + "Enter the domain name for your Bitwarden instance": "{{ ansible_fqdn }}" + "Do you want to use Let's Encrypt to generate a free SSL certificate": n + "Enter the database name for your Bitwarden instance": vault + "Enter your installation id": "{{ bitwarden_installation_id }}" + "Enter your installation key": "{{ bitwarden_installation_key }}" + "Do you have a SSL certificate to use": y + "Is this a trusted SSL certificate": y + ignore_errors: true + + # Make dir for BitWarden SSL keys + - name: Make SSL dir for keys + become: true + become_user: bitwarden + become_method: sudo + ansible.builtin.command: + cmd: "mkdir -p /opt/bitwarden/bwdata/ssl/self/{{ ansible_fqdn }}" + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + # Use an Existing SSL Certificate + # + # You may alternatively opt to use an existing SSL Certificate, which will + # require you to have the following files: + # + # A Server Certificate (certificate.crt) + # A Private Key (private.key) + # A CA Certificate (ca.crt) + # + # You may need to bundle your primary certificate with Intermediate CA + # certificates to prevent SSL trust errors. All certificates should be + # included in the Server Certificate file when using a CA Certificate. The + # first certificate in the file should be your Server Certificate, followed by + # any Intermediate CA certificate(s), followed by the Root CA. + # + # Under the default configuration, place your files in + # ./bwdata/ssl/your.domain. You may specify a different location for your + # certificate files by editing the following values in ./bwdata/config.yml: + # + # ssl_certificate_path: + # ssl_key_path: + # ssl_ca_path: + # + # Make sure SSL filenames are correct + # ssl_certificate_path: /etc/ssl/self/password.{{ domain }}/certificate.crt + # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to + # `/etc/ssl` within the container. + # ssl_key_path: /etc/ssl/self/password.{{ domain }}/private.key + # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to + # `/etc/ssl` within the container. + # ssl_ca_path: + # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to + - name: Copy current SSL cert to correct directory ("/bwdata/ssl/{{ ansible_fqdn }}/" + become: yes + become_user: bitwarden + become_method: sudo + ansible.builtin.copy: + src: "files/_wildcard.{{ domain }}.pem" + dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" + + - name: Show path to certificate + ansible.builtin.debug: + msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" + + # Bitwarden NGINX borkt hierop + # - name: Append current CA to certificate + # shell: + # cmd: cat "{{ root_ca_path }}" >> "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" + + - name: Copy current SSL key to correct directory "bwdata/ssl/{{ ansible_fqdn }}/" + become: yes + become_user: bitwarden + become_method: sudo + ansible.builtin.copy: + src: "files/_wildcard.{{ domain }}-key.pem" + dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/private.key" + + - name: Show path to private key + ansible.builtin.debug: + msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/private.key" + + - name: Copy CA certificate file key to correct directory + become: yes + become_user: bitwarden + become_method: sudo + ansible.builtin.copy: + src: "{{ root_ca_path }}" + remote_src: yes + dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/ca.crt" + + - name: Show path to ca certificate + ansible.builtin.debug: + msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/ca.crt" + + # TODO Next steps, run: + # `./bitwarden.sh start` + # 3. Configure the Environment + # Run ./bitwarden.sh start to start the Bitwarden Server. + # Note: Some Bitwarden features are not configured by the bitwarden.sh installer, and must be configured in the environment file, located at ./bwdata/env/global.override.env. At a minimum, you should configure: + # ... + # globalSettings__mail__smtp__host= + # globalSettings__mail__smtp__port= + # globalSettings__mail__smtp__ssl= + # globalSettings__mail__smtp__username= + # globalSettings__mail__smtp__password= + # ... + # adminSettings__admins= + + # If you need to make additional configuration changes, you can modify + # the settings in `./bwdata/config.yml` and then run: + # `./bitwarden.sh rebuild` or `./bitwarden.sh update` + - name: Implement changes in config files + become: yes + become_user: bitwarden + become_method: sudo + ansible.builtin.command: + chdir: /opt/bitwarden + cmd: /opt/bitwarden/bitwarden.sh rebuild + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + + # Run the following command to apply your changes: + # ./bitwarden.sh restart + - name: Restart all bitwarden services + become: yes + become_user: bitwarden + become_method: sudo + shell: + chdir: /opt/bitwarden + cmd: /opt/bitwarden/bitwarden.sh restart + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + + + ############################################################################################ + # Also this needs to be fixed. + # DO NOT EVER USE THE PIECE OF CRAP Bitwarden Directory Connector to sync users from ldap + ############################################################################################ + # SAML SSO: https://bitwarden.com/help/configure-sso-saml/ + + - name: Show post-install message + ansible.builtin.debug: + msg: | + * Now go to URL "{{ bitwarden_server_url }}" + * Create a master user account. + * Go to "Settings" and add an organization using "+New Organization" link. + * Upload the Bitwarden license file for your organization. + * Go to the "Organizations" menu and open the "Settings" menu. + * Click on the "View API Key" button, enter your password and copy the "client_id" . + * Insert these into the next Bitwarden playbook as environment settings on the command line as follows; + * $ ansible-playbook install-bitwarden-part-2-saml-sso.yml -e client_id="your-client_id" . + * Please continue to the SAML playbook for bitwarden called "install-bitwarden-part-2-saml-sso.yml". + * We are done here. + **************************************************************************************** From f5c4674f61d6e209c3858d640411de63ed43e94c Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Mon, 27 May 2024 10:06:39 +0200 Subject: [PATCH 10/12] Done syntax check install-bitwarden-part-1.yml --- playbooks/install-bitwarden-part-1.yml | 50 ++++++++++++-------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/playbooks/install-bitwarden-part-1.yml b/playbooks/install-bitwarden-part-1.yml index a9ee33f..fe6d62e 100644 --- a/playbooks/install-bitwarden-part-1.yml +++ b/playbooks/install-bitwarden-part-1.yml @@ -79,9 +79,8 @@ # Make dir for SSL keys - name: Make SSL dir for keys - become: yes + become: true become_user: bitwarden - become_method: sudo ansible.builtin.command: cmd: "mkdir -p /opt/bitwarden/bwdata/ssl/self/{{ ansible_fqdn }}" register: my_output # <- Registers the command output. @@ -140,9 +139,9 @@ dest: /opt/bitwarden owner: bitwarden group: bitwarden - remote_src: yes + remote_src: true - - name: make Bitwarden CLI tool executable + - name: Make Bitwarden CLI tool executable ansible.builtin.file: path: /opt/bitwarden/bw owner: bitwarden @@ -230,9 +229,8 @@ # Next steps, run: # `./bitwarden.sh start` - name: Run the install script - become: yes + become: true become_user: bitwarden - become_method: sudo ansible.builtin.expect: command: /opt/bitwarden/bitwarden.sh install chdir: /opt/bitwarden @@ -240,19 +238,18 @@ echo: true responses: "Enter the domain name for your Bitwarden instance": "{{ ansible_fqdn }}" - "Do you want to use Let's Encrypt to generate a free SSL certificate": n + "Do you want to use Let's Encrypt to generate a free SSL certificate": n "Enter the database name for your Bitwarden instance": vault "Enter your installation id": "{{ bitwarden_installation_id }}" "Enter your installation key": "{{ bitwarden_installation_key }}" "Do you have a SSL certificate to use": y "Is this a trusted SSL certificate": y - ignore_errors: true + ignore_errors: "{{ ansible_check_mode }}" # <- Ignores errors in check mode. # Make dir for BitWarden SSL keys - name: Make SSL dir for keys become: true become_user: bitwarden - become_method: sudo ansible.builtin.command: cmd: "mkdir -p /opt/bitwarden/bwdata/ssl/self/{{ ansible_fqdn }}" register: my_output # <- Registers the command output. @@ -291,16 +288,16 @@ # ssl_ca_path: # Note: Path uses the container's ssl directory. The `./ssl` host directory is mapped to - name: Copy current SSL cert to correct directory ("/bwdata/ssl/{{ ansible_fqdn }}/" - become: yes + become: true become_user: bitwarden - become_method: sudo ansible.builtin.copy: src: "files/_wildcard.{{ domain }}.pem" dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" + mode: '0600' - name: Show path to certificate ansible.builtin.debug: - msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" + msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" # Bitwarden NGINX borkt hierop # - name: Append current CA to certificate @@ -308,35 +305,36 @@ # cmd: cat "{{ root_ca_path }}" >> "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/certificate.crt" - name: Copy current SSL key to correct directory "bwdata/ssl/{{ ansible_fqdn }}/" - become: yes + become: true become_user: bitwarden - become_method: sudo ansible.builtin.copy: src: "files/_wildcard.{{ domain }}-key.pem" dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/private.key" + mode: '0600' - name: Show path to private key ansible.builtin.debug: - msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/private.key" + msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/private.key" - name: Copy CA certificate file key to correct directory - become: yes + become: true become_user: bitwarden - become_method: sudo ansible.builtin.copy: src: "{{ root_ca_path }}" - remote_src: yes + remote_src: true dest: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/ca.crt" + mode: '0600' - name: Show path to ca certificate ansible.builtin.debug: - msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/ca.crt" + msg: "/opt/bitwarden/bwdata/ssl/{{ ansible_fqdn }}/ca.crt" # TODO Next steps, run: # `./bitwarden.sh start` # 3. Configure the Environment # Run ./bitwarden.sh start to start the Bitwarden Server. - # Note: Some Bitwarden features are not configured by the bitwarden.sh installer, and must be configured in the environment file, located at ./bwdata/env/global.override.env. At a minimum, you should configure: + # Note: Some Bitwarden features are not configured by the bitwarden.sh installer, and must be configured in the environment file, + # located at ./bwdata/env/global.override.env. At a minimum, you should configure: # ... # globalSettings__mail__smtp__host= # globalSettings__mail__smtp__port= @@ -350,23 +348,21 @@ # the settings in `./bwdata/config.yml` and then run: # `./bitwarden.sh rebuild` or `./bitwarden.sh update` - name: Implement changes in config files - become: yes + become: true become_user: bitwarden - become_method: sudo ansible.builtin.command: chdir: /opt/bitwarden cmd: /opt/bitwarden/bitwarden.sh rebuild register: my_output # <- Registers the command output. - changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Run the following command to apply your changes: # ./bitwarden.sh restart - name: Restart all bitwarden services - become: yes + become: true become_user: bitwarden - become_method: sudo - shell: + ansible.builtin.command: chdir: /opt/bitwarden cmd: /opt/bitwarden/bitwarden.sh restart register: my_output # <- Registers the command output. @@ -393,5 +389,3 @@ * Please continue to the SAML playbook for bitwarden called "install-bitwarden-part-2-saml-sso.yml". * We are done here. **************************************************************************************** - - From 442c94b3d69f893a918bc75ed13ba6c5f15eb515 Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Mon, 27 May 2024 10:23:57 +0200 Subject: [PATCH 11/12] Fixed install-cmdb --- .../install-bitwarden-part-2-saml-sso.yml | 4 +- playbooks/install-cmdb-sso.yml | 93 ++++++++++++------- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/playbooks/install-bitwarden-part-2-saml-sso.yml b/playbooks/install-bitwarden-part-2-saml-sso.yml index 8be9283..8744325 100644 --- a/playbooks/install-bitwarden-part-2-saml-sso.yml +++ b/playbooks/install-bitwarden-part-2-saml-sso.yml @@ -269,8 +269,8 @@ ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 diff --git a/playbooks/install-cmdb-sso.yml b/playbooks/install-cmdb-sso.yml index 14a8464..969af03 100644 --- a/playbooks/install-cmdb-sso.yml +++ b/playbooks/install-cmdb-sso.yml @@ -10,6 +10,17 @@ cmdb_server_url: https://cmdb.{{ domain }} cmdb_client_id: "client-cmdb" + handlers: + # Populate/initialize cmdbuild database + - name: Initial datatase load + become: true + become_user: postgres + ansible.builtin.command: + cmd: /usr/bin/gunzip -c /tmp/cmdbuild.dump.gz | psql postgresql://cmdbuild:cmdbuild@localhost:5432/cmdbuild + when: dbwork.changed + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + tasks: - name: Show tip for sensible output ansible.builtin.debug: @@ -52,7 +63,6 @@ register: warfile_stat - # Download cmdbuild-3.4.war via cmdbuild.org - name: Download CMDbuild war file with big timeout value (because it comes from Sourceforge...) # sudo wget https://maven.xwiki.org/xwiki-keyring.gpg -P /usr/share/keyrings/ @@ -102,6 +112,16 @@ password: 'cmdbuild' # priv: "CONNECT" + # Copy db init file to /tmp so user postgres can read it + - name: Copy database dump file to /tmp if the database was just created. + ansible.builtin.copy: + src: files/cmdbuild.dump.gz + dest: /tmp/cmdbuild.dump.gz + mode: '0644' + owner: postgres + group: postgres + + # postgresql create database cmdbuild owner cmdbuild - name: Make sure cmdbuild Postgresql database exists. become: true @@ -112,28 +132,13 @@ owner: cmdbuild state: present register: dbwork + notify: + - Initial datatase load - name: Show dbwork variable ansible.builtin.debug: msg: "{{ dbwork }}" - # Copy db init file to /tmp so user postgres can read it - - name: Copy database dump file to /tmp if the database was just created. - ansible.builtin.copy: - src: files/cmdbuild.dump.gz - dest: /tmp/cmdbuild.dump.gz - mode: '0644' - owner: postgres - group: postgres - when: dbwork.changed - - # Populate/initialize cmdbuild database - - name: Initialize cmdbuild datatase if it was just created. - become: true - become_user: postgres - ansible.builtin.command: - cmd: /usr/bin/gunzip -c /tmp/cmdbuild.dump.gz | psql postgresql://cmdbuild:cmdbuild@localhost:5432/cmdbuild - when: dbwork.changed # Set default jdk to /usr/lib/jvm/java-17-openjdk-amd64/ in /etc/default/tomcat9 - name: Setup default jdk in /etc/default/tomcat9 @@ -149,11 +154,13 @@ state: directory owner: tomcat group: tomcat + mode: '0755' - name: Configure database connection ansible.builtin.blockinfile: path: /var/lib/tomcat9/conf/cmdbuild/database.conf - create: yes + create: true + mode: '0600' block: | db.url=jdbc:postgresql://localhost:5432/cmdbuild db.username=cmdbuild @@ -167,6 +174,7 @@ ansible.builtin.copy: src: files/cmdb_nginx.conf dest: /etc/nginx/sites-available/default + mode: '0644' - name: Make SSL dir for nginx ansible.builtin.file: @@ -190,7 +198,7 @@ ansible.builtin.systemd: name: nginx state: restarted - enabled: yes + enabled: true # Setup SSO configuration Using SSO SAML authentication as described in @@ -209,6 +217,9 @@ ansible.builtin.command: cmd: /usr/bin/openssl req -x509 -sha256 -newkey rsa:1024 -keyout sp.key -out sp.crt -days 3650 -nodes -subj '/CN={{ domain }}' chdir: /tmp + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. + # Start getting auth token # Retrieve token url needed. Returns JSON payload with var token-service. @@ -300,7 +311,7 @@ url: "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml/descriptor" method: get validate_certs: false - return_content: yes + return_content: true register: idp_metadata - name: Show idp metadata @@ -320,12 +331,15 @@ ansible.builtin.copy: content: "{{ idp_metadata.content }}" dest: /tmp/idp.xml + mode: '0600' # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate ansible.builtin.command: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml register: xmlstarlet + changed_when: xmlstarlet.rc != 0 # <- Uses the return code to define when the task has changed. + - name: Show xmlstarlet ansible.builtin.debug: @@ -348,6 +362,8 @@ ansible.builtin.command: cmd: cat /tmp/sp.crt | tr -d '\n' register: sp_crt + changed_when: sp_crt.rc != 0 # <- Uses the return code to define when the task has changed. + - name: Show sp crt ansible.builtin.debug: @@ -357,6 +373,8 @@ ansible.builtin.command: cmd: cat /tmp/sp.key | tr -d '\n' register: sp_key + changed_when: sp_key.rc != 0 # <- Uses the return code to define when the task has changed. + - name: Show sp key ansible.builtin.debug: @@ -369,8 +387,8 @@ ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients?clientId={{ cmdb_client_id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: get validate_certs: false register: existingclient @@ -381,20 +399,21 @@ - name: Copy existing client to backup file. ansible.builtin.copy: - content: "{{ existingclient }}" - dest: /tmp/cmdb-existing-client-backup.json + content: "{{ existingclient }}" + dest: /tmp/cmdb-existing-client-backup.json + mode: '0600' - name: Find ID in returned json ansible.builtin.debug: var: existingclient.json[0].id - - name: Delete client id "{{ cmdb_client_id }}" if it already exists. + - name: If it already exists then delete client id "{{ cmdb_client_id }}". # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 @@ -417,14 +436,15 @@ ansible.builtin.copy: content: "{{ jsonbody }}" dest: /tmp/jsonbody + mode: '0600' # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" #.json[\"access_token\"] }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" # .json[\"access_token\"] }}" method: POST validate_certs: false body_format: json @@ -484,19 +504,19 @@ ansible.builtin.systemd: name: tomcat9 state: restarted - enabled: yes + enabled: true - name: Start waiting ansible.builtin.debug: msg: "Start waiting for 443 to become available" - - name: waiting + - name: Waiting ansible.builtin.wait_for: port: 443 delay: 20 # Measured 12 attempts, so 25 max should work most of the time. - - name: check if "{{ cmdb_server_url }}/cmdbuild/rest/v3/sessions" is open on the host (even a 403 means it is responding). + - name: Check if cmdb_server_url/cmdbuild/rest/v3/sessions" is open on the host (even a 403 means it is responding). ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/rest/v3/sessions" status_code: 403 @@ -551,7 +571,7 @@ url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config" method: GET validate_certs: false - headers: { 'Cmdbuild-authorization': "{{ sessionid }}", 'includeDefault': "True" } + headers: { 'Cmdbuild-authorization': "{{ sessionid }}", 'includeDefault': "True" } register: cmdbconf - name: Show cmdbconf @@ -659,7 +679,7 @@ body: "False" - - name: Set value of org.cmdbuild.auth.module.saml.idp.logout to "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" + - name: Set value of org.cmdbuild.auth.module.saml.idp.logout " ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.logout" method: PUT @@ -680,7 +700,7 @@ body: "{{ cmdb_base_url }}" - - name: Set value of org.cmdbuild.auth.module.saml.idp.login to "{{ keycloak_server_url }}/realms/{{ realm }}/protocol/saml" + - name: Set value of org.cmdbuild.auth.module.saml.idp.login" ansible.builtin.uri: url: "{{ cmdb_server_url }}/cmdbuild/services/rest/v3/system/config/org.cmdbuild.auth.module.saml.idp.login" method: PUT @@ -769,6 +789,7 @@ cmd: bash ./cmdbuild.sh restws -username admin -password admin getconfigs | grep auth chdir: /var/lib/tomcat9/webapps/cmdbuild register: authconfig + changed_when: authconfig.rc != 0 # <- Uses the return code to define when the task has changed. - name: Show variable authconfig ansible.builtin.debug: From a60f39c5df8817b129eaec188c3dafed8f2f7d5f Mon Sep 17 00:00:00 2001 From: "Jeroen Baten [NIPV]" Date: Mon, 27 May 2024 11:00:06 +0200 Subject: [PATCH 12/12] Fixing more typos... --- playbooks/install-gitlab-sso.yml | 71 +++++++++++++++++----------- playbooks/install-keycloak-nginx.yml | 17 +++---- playbooks/install-odoo-sso.yml | 44 +++++++++-------- 3 files changed, 73 insertions(+), 59 deletions(-) diff --git a/playbooks/install-gitlab-sso.yml b/playbooks/install-gitlab-sso.yml index aead0bd..241885b 100644 --- a/playbooks/install-gitlab-sso.yml +++ b/playbooks/install-gitlab-sso.yml @@ -21,15 +21,17 @@ msg: Readable debug output export ANSIBLE_STDOUT_CALLBACK=debug - name: Read global vars - include_vars: global-vars.yml + ansible.builtin.include_vars: global-vars.yml - name: Read encrypted content - include_vars: encrypted-vars.yml + ansible.builtin.include_vars: encrypted-vars.yml - name: Shutdown GitLab if already exists and ignore any errors - ansible.builtin.shell: + ansible.builtin.command: cmd: gitlab-ctl stop - ignore_errors: yes + ignore_errors: "{{ ansible_check_mode }}" # <- Ignores errors in check mode. + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Set timezone to Europe/Amsterdam community.general.timezone: @@ -41,7 +43,7 @@ ansible.builtin.apt: name: "{{ item }}" state: present - update_cache: yes + update_cache: true loop: - ca-certificates - curl @@ -72,12 +74,18 @@ - rootCA-key.pem - name: Try to install my root CA on this system - ansible.builtin.shell: - cmd: CAROOT=/root /root/mkcert -install + ansible.builtin.command: + cmd: /root/mkcert -install + environment: + CAROOT: /root + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. - name: Update all CA certificates - ansible.builtin.shell: + ansible.builtin.command: cmd: /usr/sbin/update-ca-certificates + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. # Check if our CA is already installed # Should be /usr/local/share/ca-certificates/mkcert_development_CA_62268663181785622328732999788222374785.crt @@ -98,9 +106,11 @@ dest: /tmp/script.deb.sh mode: '0700' - - name: execute repo add script - ansible.builtin.shell: + - name: Execute repo add script + ansible.builtin.command: cmd: /tmp/script.deb.sh + register: my_output # <- Registers the command output. + changed_when: my_output.rc != 0 # <- Uses the return code to define when the task has changed. ######################################################## # After this step we should have a GitLab installation # @@ -134,7 +144,7 @@ # /opt/gitlab/embedded/bin/openssl s_client -connect localhost:443 Odoo server 14.0? - name: Try to determine the version of Odoo currently installed - become: yes + become: true become_user: odoo become_method: ansible.builtin.sudo - ansible.builtin.shell: + ansible.builtin.command: cmd: /opt/odoo/odoo-server/odoo-bin --version register: installed_odoo + changed_when: installed_odoo.rc != 0 # <- Uses the return code to define when the task has changed. - name: Get the last 4 characters from version string ansible.builtin.set_fact: @@ -100,7 +101,7 @@ # Original at https://orus.io/xcg/auth_saml # These days at https://github.com/OCA/server-auth/tree/{{ odoo_version }}/auth_saml - - name: checkout OCA repo server-auth branch to temporary storage directory for version "{{ odoo_version }}" + - name: Checkout OCA repo server-auth branch to temporary storage directory for version "{{ odoo_version }}" ansible.builtin.git: repo: 'https://github.com/OCA/server-auth/' dest: /root/server-auth @@ -280,7 +281,7 @@ # We are going to use a shell command to retrieve the value for X509Certificate - name: Run xmlstarlet to retrieve X509Certificate - ansible.builtin.shell: + ansible.builtin.command: cmd: /usr/bin/xmlstarlet sel -t -v //ds:X509Certificate /tmp/idp.xml register: xmlstarlet changed_when: xmlstarlet.rc != 0 # <- Uses the return code to define when the task has changed. @@ -297,7 +298,7 @@ ansible.builtin.fetch: src: "/tmp/{{ item }}" dest: "/tmp/" - flat: yes + flat: true loop: - sp.crt - sp.key @@ -312,18 +313,20 @@ - sp.key - name: Retrieve remote ssl cert - ansible.builtin.shell: + ansible.builtin.command: cmd: cat /tmp/sp.crt | tr -d '\n' register: sp_crt + changed_when: sp_crt.rc != 0 # <- Uses the return code to define when the task has changed. - - name: show sp crt + - name: Show sp crt ansible.builtin.debug: var: sp_crt - name: Retrieve remote ssl key - ansible.builtin.shell: + ansible.builtin.command: cmd: cat /tmp/sp.key | tr -d '\n' register: sp_key + changed_when: sp_key.rc != 0 # <- Uses the return code to define when the task has changed. - name: Show sp key ansible.builtin.debug: @@ -333,7 +336,8 @@ ansible.builtin.template: src: odoo-nginx-odoo.j2 dest: /etc/nginx/sites-available/odoo - force: yes + force: true + mode: '0644' - name: Restart Nginx ansible.builtin.systemd: @@ -366,13 +370,13 @@ ansible.builtin.debug: var: existingclient.json[0].id - - name: Delete client id "{{ odoo_client_id }}" if it already exists. + - name: If it already exists delete client id "{{ odoo_client_id }}" . # Example: "id": "ba973624-2d00-488f-8d18-154224c63f8f" ansible.builtin.uri: url: "{{ keycloak_server_url }}/admin/realms/{{ realm }}/clients/{{ existingclient.json[0].id }}" headers: - Accept: "application/json" - Authorization: "Bearer {{ auth_token }}" + Accept: "application/json" + Authorization: "Bearer {{ auth_token }}" method: DELETE validate_certs: false status_code: 204 @@ -395,6 +399,7 @@ ansible.builtin.copy: content: "{{ jsonbody }}" dest: /tmp/odoo-new-client-backup.json + mode: '0600' # Generate the json payload to upload - name: Upload JSON template file to create new Client ID on Keycloak server @@ -434,9 +439,11 @@ * - In the Identity Provider field enter the value "userid" !!! * - Make sure Entity ID contains the value "{{ odoo_client_id }}" !!!! * - Click on "Upload your file" next to "Odoo Public Certificate" and upload the "sp.crt" file you will see in /tmp. - * - If, after selecting and clicking "Ok" the upload button does not change into a field with the filename in it, you have to use a different browser! + * - If, after selecting and clicking "Ok" the upload button does not change into a field with the filename in it, + * you have to use a different browser! * - Click on "Upload your file" next to "Odoo Private Key" and upload the "sp.key" file you will see in /tmp. - * - Again, if, after selecting and clicking "Ok" the upload button does not change into a field with the filename in it, you have to use a different browser! + * - Again, if, after selecting and clicking "Ok" the upload button does not change into a field with the filename in it, + * you have to use a different browser! * - For "Signature Algorythm" you select "SIG_RSA_SHA256" * - Alle checkboxen aanvinken behalve "Want Assertions Signed". * - Zoek in keycloak het userid op van een gebruiker (lijkt op "1513d874-2a7e-4f05-87a3-9f101302aa81". @@ -444,8 +451,3 @@ * - Maak gebruiker aan en open het tabblad "SAML". * - Voeg een regel toe, kies de geconfigureerde SAML backend en vul het SAML userid toe. ************************************************************************************* - - - - -