From bae636a391d6414b9e0ba112b9c6b97e8604ec3a Mon Sep 17 00:00:00 2001 From: HgO Date: Thu, 21 May 2020 22:31:03 +0200 Subject: [PATCH] extract mumble and acme roles into separate repos --- ansible.cfg | 17 ++ inventories/group_vars/mumble.yml | 4 +- playbooks/mumble.yml | 3 +- requirements.yml | 8 + roles/acme/defaults/main.yml | 9 - roles/acme/files/acme_renew_cert.py | 184 ------------------- roles/acme/tasks/acme.yml | 76 -------- roles/acme/tasks/challenge.yml | 91 --------- roles/acme/tasks/main.yml | 2 - roles/acme/vars/main.yml | 1 - roles/mumble/defaults/main.yml | 24 --- roles/mumble/handlers/main.yml | 21 --- roles/mumble/handlers/nginx.yml | 7 - roles/mumble/meta/main.yml | 5 - roles/mumble/tasks/main.yml | 11 -- roles/mumble/tasks/mumble_web.yml | 77 -------- roles/mumble/tasks/nginx.yml | 18 -- roles/mumble/tasks/umurmur.yml | 88 --------- roles/mumble/templates/mumble-web.js.j2 | 23 --- roles/mumble/templates/mumble-web.service.j2 | 26 --- roles/mumble/templates/nginx.conf.j2 | 65 ------- roles/mumble/templates/umurmur.conf.j2 | 72 -------- roles/mumble/templates/umurmur.service.j2 | 28 --- scripts/gopass-client.py | 97 ++++++++++ 24 files changed, 125 insertions(+), 832 deletions(-) create mode 100644 ansible.cfg create mode 100644 requirements.yml delete mode 100644 roles/acme/defaults/main.yml delete mode 100755 roles/acme/files/acme_renew_cert.py delete mode 100644 roles/acme/tasks/acme.yml delete mode 100644 roles/acme/tasks/challenge.yml delete mode 100644 roles/acme/tasks/main.yml delete mode 100644 roles/acme/vars/main.yml delete mode 100644 roles/mumble/defaults/main.yml delete mode 100644 roles/mumble/handlers/main.yml delete mode 100644 roles/mumble/handlers/nginx.yml delete mode 100644 roles/mumble/meta/main.yml delete mode 100644 roles/mumble/tasks/main.yml delete mode 100644 roles/mumble/tasks/mumble_web.yml delete mode 100644 roles/mumble/tasks/nginx.yml delete mode 100644 roles/mumble/tasks/umurmur.yml delete mode 100644 roles/mumble/templates/mumble-web.js.j2 delete mode 100644 roles/mumble/templates/mumble-web.service.j2 delete mode 100644 roles/mumble/templates/nginx.conf.j2 delete mode 100644 roles/mumble/templates/umurmur.conf.j2 delete mode 100644 roles/mumble/templates/umurmur.service.j2 create mode 100755 scripts/gopass-client.py diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..60ba267 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,17 @@ +[defaults] +vault_identity=62a40f49-7deb-45e3-8c17-639277033357 +vault_password_file=scripts/gopass-client.py +host_key_checking = False +inventory = inventories/hosts.ini +roles_path = ~/.ansible/roles:./roles:/usr/share/ansible/roles:/etc/ansible/roles + +[gopass] +key_path=ansible + +[ssh_connection] +ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null +pipelining = True +scp_if_ssh = True + +[persistent_connection] +connect_timeout = 300 \ No newline at end of file diff --git a/inventories/group_vars/mumble.yml b/inventories/group_vars/mumble.yml index de08213..679c6c3 100644 --- a/inventories/group_vars/mumble.yml +++ b/inventories/group_vars/mumble.yml @@ -4,7 +4,7 @@ umurmur_domain: mumble.parley.be umurmur_welcome_text: - Welcome to Parley Talk! - You can talk to the people in the room you joined. -- You start in the Welcome room, to join another channel right click the room name and choose "Join Channel" +- You start in the Welcome room, to join another channel double click on the room name. umurmur_admin_password: wC7yZ4vV2ocb7AkBfQ2RwuhRqYVyiwY42Rjpw3pfJ umurmur_max_users: 100 @@ -43,4 +43,4 @@ umurmur_channels: umurmur_default_channel: Welcome mumble_web_domain: talk.parley.be -mumble_web_version: c03b78d096eae69e1cec82e148f65e7a8541bd68 \ No newline at end of file +mumble_web_version: master \ No newline at end of file diff --git a/playbooks/mumble.yml b/playbooks/mumble.yml index 9751694..db29728 100644 --- a/playbooks/mumble.yml +++ b/playbooks/mumble.yml @@ -6,5 +6,4 @@ - "{{ umurmur_domain }}" - "{{ mumble_web_domain }}" roles: - - acme - - mumble \ No newline at end of file + - ppbe.mumble \ No newline at end of file diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..72efac6 --- /dev/null +++ b/requirements.yml @@ -0,0 +1,8 @@ +- name: ppbe.acme + src: https://dev.parley.be/PPBe/ansible-role-acme.git + scm: git + version: master +- name: ppbe.mumble + src: https://dev.parley.be/PPBe/ansible-role-mumble.git + scm: git + version: master \ No newline at end of file diff --git a/roles/acme/defaults/main.yml b/roles/acme/defaults/main.yml deleted file mode 100644 index d40b76e..0000000 --- a/roles/acme/defaults/main.yml +++ /dev/null @@ -1,9 +0,0 @@ -acme_directory: https://acme-v02.api.letsencrypt.org/directory -acme_config_dir: /etc/ssl -acme_keys_dir: "{{ acme_config_dir }}/private" -acme_csr_dir: "{{ acme_config_dir }}/csr" -acme_certs_dir: "{{ acme_config_dir }}/certs" -acme_accounts_dir: "{{ acme_config_dir }}/accounts" -acme_account_key: "acme_account.key" -acme_ssl_group: ssl-cert -acme_challenge_dir: /var/www/acme \ No newline at end of file diff --git a/roles/acme/files/acme_renew_cert.py b/roles/acme/files/acme_renew_cert.py deleted file mode 100755 index 4e0bb09..0000000 --- a/roles/acme/files/acme_renew_cert.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/python3 - -import logging -import os -import sys -from datetime import datetime - -import josepy as jose -from OpenSSL import crypto -from acme import client, messages, challenges, crypto_util - -SSL_CONFIG_DIR="/etc/ssl" -OPENSSL_DATE_FORMAT = '%Y%m%d%H%M%SZ' -USER_AGENT="python-acme" - -def select_http01_challenge(order): - for auth in order.authorizations: - for challenge in auth.body.challenges: - if isinstance(challenge.chall, challenges.HTTP01): - return challenge - raise Exception("HTTP-01 challenge was not offered by the CA server.") - -def perform_http01_challenge(client_acme, challenge, order, challenge_dir): - response, validation = challenge.response_and_validation(client_acme.net.key) - validation_filename, validation_content = validation.split('.') - - challenge_path = os.path.join(challenge_dir, validation_filename) - with open(challenge_path, 'w') as f_out: - f_out.write(validation_content) - - client_acme.answer_challenge(challenge, response) - - fullchain_pem = client_acme.poll_and_finalize(order).fullchain_pem - - os.remove(challenge_path) - - return fullchain_pem - -def renew_cert(domain, - account_key_path, - privkey_path, - csr_path, - certs_dir, - challenge_dir, - directory_url, - days_before_renewal, - force = False): - logging.info(f"Checking {domain} certificate's expiration date…") - now = datetime.now() - cert_expiration_date = now - - fullchain_path = os.path.join(certs_dir, "fullchain.pem") - try: - with open(fullchain_path, 'rb') as pem_in: - fullchain_pem = crypto.load_certificate(crypto.FILETYPE_PEM, pem_in.read()) - cert_expiration_date = datetime.strptime(fullchain_pem.get_notAfter().decode(), OPENSSL_DATE_FORMAT) - except IOError as e: - logging.warning(f"Unable to load {domain} certificate: {e}") - - cert_expiration_days = (cert_expiration_date - now).days - if not force and cert_expiration_days >= days_before_renewal: - logging.info(f"Certificate expires in {cert_expiration_days} days. Nothing to do.") - return - - logging.info(f"Certificate expires in {cert_expiration_days} days! Renewing certificate…") - - logging.info(f"Loading account key from {account_key_path}…") - with open(account_key_path, 'rb') as pem_in: - account_pem = crypto.load_privatekey(crypto.FILETYPE_PEM, pem_in.read()) - account_jwk = jose.JWKRSA(key=account_pem.to_cryptography_key()) - account_jwk_pub = account_jwk.public_key() - - logging.info(f"Loading {domain} CSR file from {csr_path}…") - try: - with open(csr_path, 'r') as pem_in: - csr_pem = pem_in.read() - except IOError as e: - logging.warning(f"Unable to load CSR file: {e}") - logging.info(f"Loading {domain} private key from {privkey_path}…") - with open(privkey_path, 'r') as pem_in: - privkey_pem = pem_in.read() - - csr_pem = crypto_util.make_csr(privkey_pem, [domain]) - - client_network = client.ClientNetwork(account_jwk, user_agent=USER_AGENT) - directory = messages.Directory.from_json(client_network.get(directory_url).json()) - client_acme = client.ClientV2(directory, net=client_network) - - logging.info("Registering with ACME account…") - # Here we assume we already have an account - registration_message = messages.NewRegistration( - key=account_jwk_pub, - only_return_existing=True) - registration_response = client_acme._post(directory["newAccount"], registration_message) - account = client_acme._regr_from_response(registration_response) - client_acme.net.account = account - - logging.info("Ordering ACME challenge…") - order = client_acme.new_order(csr_pem) - - logging.info("Selecting HTTP-01 ACME challenge…") - challenge = select_http01_challenge(order) - - logging.info("Performing HTTP-01 ACME challenge…") - fullchain_pem = perform_http01_challenge(client_acme, challenge, order, challenge_dir) - - logging.info(f"Writing {domain} certificates into {certs_dir}…") - certs_pem = [] - for line in fullchain_pem.split('\n'): - if 'BEGIN CERTIFICATE' in line: - cert_pem = '' - - cert_pem += line + '\n' - - if 'END CERTIFICATE' in line: - certs_pem.append(cert_pem) - - cert_path = os.path.join(certs_dir, "cert.pem") - with open(cert_path, 'w') as pem_out: - pem_out.write(certs_pem[0]) - - chain_path = os.path.join(certs_dir, "chain.pem") - with open(chain_path, 'w') as pem_out: - pem_out.write(''.join(certs_pem[1:])) - - with open(fullchain_path, 'w') as pem_out: - pem_out.write(fullchain_pem) - -if __name__ == '__main__': - import argparse - - parser = argparse.ArgumentParser( - description="Renew ACME certificates for a list of domains. This script assumes you already have an account on the CA server and a private key for each certificate.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("domains", nargs='+', - help="List of domain names for which to renew the certificate.") - parser.add_argument("--account_key", "-a", - default=os.path.join(SSL_CONFIG_DIR, "accounts", "acme_account.key"), - help="Path to the account key.") - parser.add_argument("--privkey", "-p", - default=os.path.join(SSL_CONFIG_DIR, "private", "{domain}.pem"), - help="Path to the private certificate.") - parser.add_argument("--csr", "-r", - default=os.path.join(SSL_CONFIG_DIR, "csr", "{domain}.pem"), - help="Path to the CSR file. If the file doesn't exist, it will be generated from the private key and the domain name.") - parser.add_argument("--certs", "-o", - default=os.path.join(SSL_CONFIG_DIR, "certs", "{domain}.d"), - help="Path to the certificates directory.") - parser.add_argument("--challenge", "-c", - default="/var/www/html/.well-known/acme-challenge", - help="Path to the challenge directory.") - parser.add_argument("--directory_url", "-u", - default="https://acme-v02.api.letsencrypt.org/directory", - help="Directory URL on which performing ACME challenges. Only ACME v2 is supported.") - parser.add_argument("--days", "-d", type=int, - default=30, - help="Days before attempting to renew the certificates.") - parser.add_argument("--quiet", "-q", - action="store_true", - help="Quiet mode.") - parser.add_argument("--force", "-f", - action="store_true", - help="Force certificates renewal without checking their expiration date.") - args = parser.parse_args() - - log_level = logging.WARN if args.quiet else logging.INFO - logging.basicConfig(stream=sys.stdout, level=log_level, format="%(levelname)s:%(message)s") - - for domain in args.domains: - account_key = args.account_key.format(domain=domain) - privkey = args.privkey.format(domain=domain) - csr = args.csr.format(domain=domain) - certs_dir = args.certs.format(domain=domain) - challenge_dir = args.challenge.format(domain=domain) - - renew_cert(domain, - account_key_path=account_key, - privkey_path=privkey, - csr_path=csr, - certs_dir=certs_dir, - challenge_dir=challenge_dir, - directory_url=args.directory_url, - days_before_renewal=args.days, - force=args.force) \ No newline at end of file diff --git a/roles/acme/tasks/acme.yml b/roles/acme/tasks/acme.yml deleted file mode 100644 index 1f1b137..0000000 --- a/roles/acme/tasks/acme.yml +++ /dev/null @@ -1,76 +0,0 @@ -- name: Install ACME dependencies - apt: - name: python3-acme - state: present - tags: acme_install - -- name: Create Let's Encrypt config directories - file: - path: "{{ config_dir }}" - state: directory - owner: root - group: "{{ acme_ssl_group }}" - mode: "711" - loop: - - "{{ acme_config_dir }}" - - "{{ acme_keys_dir }}" - - "{{ acme_accounts_dir }}" - - "{{ acme_csr_dir }}" - loop_control: - loop_var: config_dir - tags: acme_install - -- name: Create challenge directory - file: - path: "{{ acme_challenge_dir }}/.well-known/acme-challenge" - state: directory - owner: root - group: root - mode: "755" - tags: acme_install - -- name: Perform ACME challenge for each domain - include_tasks: - file: challenge.yml - apply: - tags: acme_challenge - loop: "{{ acme_domains | unique }}" - loop_control: - loop_var: domain_name - tags: acme_challenge - -- name: Create directory for certificate renewal tool - file: - path: /opt/acme - owner: root - group: root - mode: "755" - state: directory - tags: acme_renew - -- name: Copy script to renew ACME certificates - copy: - src: acme_renew_cert.py - dest: /opt/acme/acme_renew_cert.py - owner: root - group: root - mode: "755" - tags: acme_renew - -- name: Setup cron job for ACME certificates renewal of {{ domain_name }} - cron: - name: acme renew {{ domain_name }} cert - job: >- - sleep $((RANDOM % 3600)) && /opt/acme/acme_renew_cert.py {{ domain_name }} -q - -a {{ (acme_accounts_dir + '/' + acme_account_key) | quote }} - -p {{ acme_keys_dir | quote }}/{domain}.pem - -r {{ acme_csr_dir | quote }}/{domain}.csr - -o {{ acme_certs_dir | quote }}/{domain}.d - -c {{ acme_challenge_dir | quote }}/.well-known/acme-challenge - minute: "30" - hour: "2" - state: present - loop: "{{ acme_domains | unique }}" - loop_control: - loop_var: domain_name - tags: acme_renew \ No newline at end of file diff --git a/roles/acme/tasks/challenge.yml b/roles/acme/tasks/challenge.yml deleted file mode 100644 index 7b56783..0000000 --- a/roles/acme/tasks/challenge.yml +++ /dev/null @@ -1,91 +0,0 @@ -- name: Create {{ domain_name }} certificates directory - file: - path: "{{ acme_certs_dir }}/{{ domain_name }}.d" - state: directory - owner: root - group: "{{ acme_ssl_group }}" - mode: "755" - tags: acme_install - -- name: Generate Let's Encrypt account key - openssl_privatekey: - path: "{{ acme_accounts_dir }}/{{ acme_account_key }}" - owner: root - group: root - mode: "600" - type: RSA - size: 4096 - tags: acme_account - -- name: Generate Let's Encrypt private key for {{ domain_name }} - openssl_privatekey: - path: "{{ acme_keys_dir }}/{{ domain_name }}.pem" - owner: root - group: "{{ acme_ssl_group }}" - mode: "640" - type: RSA - size: 4096 - -- name: Generate Let's Encrypt CSR for {{ domain_name }} - openssl_csr: - path: "{{ acme_csr_dir }}/{{ domain_name }}.csr" - owner: root - group: "{{ acme_ssl_group }}" - mode: "644" - privatekey_path: "{{ acme_keys_dir }}/{{ domain_name }}.pem" - common_name: "{{ domain_name }}" - -# - name: Check if Let's Encrypt certificate already exists for {{ domain_name }} -# stat: -# path: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem" -# register: _acme_cert_file - -# - name: Check Let's Encrypt certificate expiration date for {{ domain_name }} -# openssl_certificate_info: -# path: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem" -# valid_at: -# thirty_days: "+30d" -# register: _acme_cert_validity -# when: _acme_cert_file.stat.isreg is defined and _acme_cert_file.stat.isreg - -- name: Begin Let's Encrypt challenges for {{ domain_name }} - acme_certificate: - acme_directory: "{{ acme_directory }}" - acme_version: "{{ acme_version }}" - account_key_src: "{{ acme_accounts_dir }}/{{ acme_account_key }}" - account_email: "{{ acme_email }}" - terms_agreed: yes - challenge: http-01 - csr: "{{ acme_csr_dir }}/{{ domain_name }}.csr" - dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem" - fullchain_dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/fullchain.pem" - remaining_days: 30 - register: _acme_challenge - # when: _acme_cert_validity is skipped or not _acme_cert_validity.valid_at.thirty_days - -- debug: - var: _acme_challenge - -# - name: Implement and complete Let's Encrypt challenge for {{ domain_name }} -# when: _acme_challenge is not skipped -# block: -# - name: Implement http-01 challenge files for {{ domain_name }} -# copy: -# content: "{{ _acme_challenge.challenge_data[domain_name]['http-01'].resource_value }}" -# dest: "{{ acme_challenge_dir }}/{{ _acme_challenge.challenge_data[domain_name]['http-01'].resource }}" -# owner: root -# group: root -# mode: "644" - -# - name: Complete Let's Encrypt challenges for {{ domain_name }} -# acme_certificate: -# acme_directory: "{{ acme_directory }}" -# acme_version: "{{ acme_version }}" -# account_key_src: "{{ acme_accounts_dir }}/{{ acme_account_key }}" -# account_email: "{{ acme_email }}" -# challenge: http-01 -# csr: "{{ acme_csr_dir }}/{{ domain_name }}.csr" -# dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem" -# chain_dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/chain.pem" -# fullchain_dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/fullchain.pem" -# data: "{{ _acme_challenge }}" \ No newline at end of file diff --git a/roles/acme/tasks/main.yml b/roles/acme/tasks/main.yml deleted file mode 100644 index 2b94819..0000000 --- a/roles/acme/tasks/main.yml +++ /dev/null @@ -1,2 +0,0 @@ -- import_tasks: acme.yml - tags: acme \ No newline at end of file diff --git a/roles/acme/vars/main.yml b/roles/acme/vars/main.yml deleted file mode 100644 index 8fb9bcb..0000000 --- a/roles/acme/vars/main.yml +++ /dev/null @@ -1 +0,0 @@ -acme_version: 2 \ No newline at end of file diff --git a/roles/mumble/defaults/main.yml b/roles/mumble/defaults/main.yml deleted file mode 100644 index 4af2e83..0000000 --- a/roles/mumble/defaults/main.yml +++ /dev/null @@ -1,24 +0,0 @@ -umurmur_user_password: "" -umurmur_channel_links: -- source: "{{ umurmur_default_channel }}" - destinations: >- - {{ umurmur_channels - | selectattr('parent', 'defined') - | selectattr('parent', '==', umurmur_default_channel) - | map(attribute='name') - | list - }} -umurmur_ssl_group: "{{ acme_ssl_group }}" -umurmur_certificate: "{{ acme_certs_dir }}/{{ umurmur_domain }}.d/fullchain.pem" -umurmur_private_key: "{{ acme_keys_dir }}/{{ umurmur_domain }}.pem" -umurmur_version: master -umurmur_ispublic: yes -umurmur_port: "64738" - -mumble_web_certificate: "{{ acme_certs_dir }}/{{ mumble_web_domain }}.d/fullchain.pem" -mumble_web_trusted_certificate: "{{ acme_certs_dir }}/{{ mumble_web_domain }}.d/chain.pem" -mumble_web_private_key: "{{ acme_keys_dir }}/{{ mumble_web_domain }}.pem" -mumble_web_dhparam: "/etc/nginx/ssl/dhparam.pem" -mumble_web_www_dir: /var/www/mumble-web -mumble_web_version: master -mumble_web_websockify_port: "64737" \ No newline at end of file diff --git a/roles/mumble/handlers/main.yml b/roles/mumble/handlers/main.yml deleted file mode 100644 index 8297398..0000000 --- a/roles/mumble/handlers/main.yml +++ /dev/null @@ -1,21 +0,0 @@ -- name: reload systemd - systemd: - daemon_reload: yes - register: systemd_reloaded - -- name: restart umurmur - service: - name: umurmur - state: restarted - when: - - not (umurmur_started.changed | default(false)) or (systemd_reloaded.changed | default(false)) - -- name: restart mumble-web - service: - name: mumble-web - state: restarted - when: - - not (mumble_web_started.changed | default(false)) or (systemd_reloaded.changed | default(false)) - -- name: reload nginx - include_tasks: ../handlers/nginx.yml \ No newline at end of file diff --git a/roles/mumble/handlers/nginx.yml b/roles/mumble/handlers/nginx.yml deleted file mode 100644 index b8dc381..0000000 --- a/roles/mumble/handlers/nginx.yml +++ /dev/null @@ -1,7 +0,0 @@ -- name: Validate Nginx config - command: nginx -t - -- name: Reload Nginx server - service: - name: nginx - state: reloaded \ No newline at end of file diff --git a/roles/mumble/meta/main.yml b/roles/mumble/meta/main.yml deleted file mode 100644 index ebc0e13..0000000 --- a/roles/mumble/meta/main.yml +++ /dev/null @@ -1,5 +0,0 @@ -dependencies: -- role: geerlingguy.nodejs - vars: - nodejs_install_npm_user: root - tags: [nodejs,mumble_web] \ No newline at end of file diff --git a/roles/mumble/tasks/main.yml b/roles/mumble/tasks/main.yml deleted file mode 100644 index ead630d..0000000 --- a/roles/mumble/tasks/main.yml +++ /dev/null @@ -1,11 +0,0 @@ -- name: Install and configure umurmur server - import_tasks: umurmur.yml - tags: umurmur - -- name: Install and configure mumble web client - import_tasks: mumble_web.yml - tags: mumble_web - -- name: Configure Nginx for mumble web client - import_tasks: nginx.yml - tags: nginx \ No newline at end of file diff --git a/roles/mumble/tasks/mumble_web.yml b/roles/mumble/tasks/mumble_web.yml deleted file mode 100644 index f59bba7..0000000 --- a/roles/mumble/tasks/mumble_web.yml +++ /dev/null @@ -1,77 +0,0 @@ -- name: Install websockify - apt: - name: websockify - state: present - notify: restart mumble-web - tags: mumble_web_install - -- name: Clone mumble-web git repository - git: - repo: https://github.com/Johni0702/mumble-web.git - dest: "{{ mumble_web_www_dir }}" - version: "{{ mumble_web_version }}" - tags: mumble_web_install - -- name: Change mumble-web git repository's permissions - file: - path: "{{ mumble_web_www_dir }}" - owner: root - group: www-data - mode: "755" - state: directory - tags: mumble_web_install - -- name: Build mumble-web from sources - command: npm install - args: - chdir: "{{ mumble_web_www_dir }}" - register: _mumble_web_installed - changed_when: _mumble_web_installed.stdout is regex('(added|removed) [0-9]+ package') - tags: [mumble_web_install,mumble_web_build] - -- name: Apply security suggestions - command: npm audit fix - args: - chdir: "{{ mumble_web_www_dir }}" - when: _mumble_web_installed is changed and _mumble_web_installed.stdout is search('npm audit fix') - tags: [mumble_web_install,mumble_web_build] - -- name: Build mumble-web assets - command: npm run build - args: - chdir: "{{ mumble_web_www_dir }}" - register: _mumble_web_assets_built - when: _mumble_web_installed is changed - tags: [mumble_web_install,mumble_web_build] - -- name: Copy mumble-web config file - template: - src: mumble-web.js.j2 - dest: "{{ mumble_web_www_dir }}/dist/config.local.js" - owner: root - group: www-data - mode: "640" - tags: [mumble_web_install,mumble_web_config] - -- name: Copy mumble-web systemd service - template: - src: mumble-web.service.j2 - dest: /etc/systemd/system/mumble-web.service - owner: root - group: root - mode: "644" - notify: - - reload systemd - - restart mumble-web - tags: [mumble_web_install,mumble_web_config] - -- name: Start mumble-web service - service: - name: mumble-web - state: started - enabled: yes - register: mumble_web_started - tags: mumble_web_run - -- name: Trigger mumble-web handlers - meta: flush_handlers \ No newline at end of file diff --git a/roles/mumble/tasks/nginx.yml b/roles/mumble/tasks/nginx.yml deleted file mode 100644 index 5e89113..0000000 --- a/roles/mumble/tasks/nginx.yml +++ /dev/null @@ -1,18 +0,0 @@ -- name: Copy Nginx config file - template: - src: nginx.conf.j2 - dest: /etc/nginx/sites-available/mumble.conf - owner: root - group: www-data - mode: "755" - notify: reload nginx - -- name: Enable Nginx config file - file: - src: /etc/nginx/sites-available/mumble.conf - path: /etc/nginx/sites-enabled/mumble.conf - state: link - notify: reload nginx - -- name: Trigger Nginx handlers - meta: flush_handlers \ No newline at end of file diff --git a/roles/mumble/tasks/umurmur.yml b/roles/mumble/tasks/umurmur.yml deleted file mode 100644 index 3b0e940..0000000 --- a/roles/mumble/tasks/umurmur.yml +++ /dev/null @@ -1,88 +0,0 @@ -- name: Install umurmur build dependencies - apt: - name: - - git - - build-essential - - cmake - - libconfig-dev - - libprotobuf-c-dev - - libmbedtls-dev - - ssl-cert - -- name: Clone umurmur git repository - git: - repo: https://github.com/umurmur/umurmur.git - dest: /opt/umurmur - version: "{{ umurmur_version }}" - -- name: Change umurmur git repository's permissions - file: - path: /opt/umurmur - owner: root - group: root - mode: "775" - state: directory - -- name: Create the build directory - file: - path: /opt/umurmur/build - owner: root - group: root - mode: "775" - state: directory - -- name: Generate the Makefile with cmake - shell: cd /opt/umurmur/build && cmake .. -DSSL=mbedtls - changed_when: no - -- name: Build umurmur from source - make: - chdir: /opt/umurmur/build - changed_when: _umurmur_built.stdout_lines | length > 1 - register: _umurmur_built - -- name: Install umurmur - make: - chdir: /opt/umurmur/build - target: install - changed_when: "'Installing' in _umurmur_installed.stdout" - register: _umurmur_installed - notify: restart umurmur - -- name: Copy umurmur config file - template: - src: umurmur.conf.j2 - dest: /usr/local/etc/umurmur.conf - owner: root - group: "{{ umurmur_ssl_group }}" - mode: "640" - validate: /usr/local/bin/umurmurd -t -c %s - notify: restart umurmur - tags: umurmur_config - -- name: Copy umurmur systemd service - template: - src: umurmur.service.j2 - dest: /etc/systemd/system/umurmur.service - owner: root - group: root - mode: "644" - notify: - - reload systemd - - restart umurmur - -- name: Start umurmur service - service: - name: umurmur - enabled: yes - state: started - register: umurmur_started - -- name: Trigger umurmur handlers - meta: flush_handlers - -- name: Open umurmur port with UFW - ufw: - rule: allow - port: "{{ umurmur_port }}" - when: umurmur_ispublic | bool \ No newline at end of file diff --git a/roles/mumble/templates/mumble-web.js.j2 b/roles/mumble/templates/mumble-web.js.j2 deleted file mode 100644 index b18d954..0000000 --- a/roles/mumble/templates/mumble-web.js.j2 +++ /dev/null @@ -1,23 +0,0 @@ -{{ ansible_managed | comment('c') }} -// You can overwrite the default configuration values set in [config.js] here. -// There should never be any required changes to this file and you can always -// simply copy it over when updating to a new version. - -let config = window.mumbleWebConfig // eslint-disable-line no-unused-vars - -// E.g. changing default address and theme: -// config.defaults.address = 'voice.example.com' -// config.defaults.theme = 'MetroMumbleDark - -// Which fields to show on the Connect to Server dialog -config.connectDialog.address = false -config.connectDialog.port = false -config.connectDialog.token = false -config.connectDialog.password = {{ (umurmur_user_password != '') | lower }} - -// Default values for user settings -// You can see your current value by typing `localStorage.getItem('mumble.$setting')` in the web console. -config.settings.pttKey = 'shift' - -// Default values (can be changed by passing a query parameter of the same name) -config.defaults.address = "{{ mumble_web_domain }}/mumble" \ No newline at end of file diff --git a/roles/mumble/templates/mumble-web.service.j2 b/roles/mumble/templates/mumble-web.service.j2 deleted file mode 100644 index 3dd9af4..0000000 --- a/roles/mumble/templates/mumble-web.service.j2 +++ /dev/null @@ -1,26 +0,0 @@ -[Unit] -Description=Mumble web client using websockets -After=network.target - -[Service] -Type=simple -User=nobody -Group=nogroup -Restart=on-failure -RestartSec=3 -PIDFile=/run/mumble-web.pid -ExecStart=/usr/bin/websockify --ssl-target {{ mumble_web_websockify_port }} localhost:{{ umurmur_port }} -ExecReload=/bin/kill -HUP $MAINPID -PrivateDevices=yes -PrivateTmp=yes -ProtectSystem=strict -ProtectHome=yes -ProtectControlGroups=yes -ProtectKernelModules=yes -ProtectKernelTunables=yes -LockPersonality=yes -NoNewPrivileges=yes -LimitRTPRIO=1 - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/roles/mumble/templates/nginx.conf.j2 b/roles/mumble/templates/nginx.conf.j2 deleted file mode 100644 index 976abe9..0000000 --- a/roles/mumble/templates/nginx.conf.j2 +++ /dev/null @@ -1,65 +0,0 @@ -{{ ansible_managed | comment }} -server { - listen 80; - listen [::]:80; - server_name {{ mumble_web_domain }}; - - location / { - return 301 https://$host$request_uri; - } - -{% if acme_challenge_dir is defined %} - location ^~ /.well-known/acme-challenge/ { - allow all; - root {{ acme_challenge_dir }}; - try_files $uri =404; - } -{% endif %} -} - -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - - server_name {{ mumble_web_domain }}; - - ssl_certificate {{ mumble_web_certificate }}; - ssl_certificate_key {{ mumble_web_private_key }}; - - ssl_session_timeout 1d; - ssl_session_cache shared:AnsibleSSL:10m; # about 40000 sessions - ssl_session_tickets off; - -{% if mumble_web_dhparam is defined and mumble_web_dhparam != '' %} - ssl_dhparam {{ mumble_web_dhparam }}; -{% endif %} - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; - ssl_prefer_server_ciphers off; - - add_header Strict-Transport-Security "max-age=63072000" always; - - # OCSP stapling - ssl_stapling on; - ssl_stapling_verify on; - - # Verify chain of trust of OCSP response using Root CA and Intermediate certs - ssl_trusted_certificate {{ mumble_web_trusted_certificate }}; - - location / { - root /var/www/mumble-web/dist/; - } - - location /mumble { - proxy_pass http://127.0.0.1:{{ mumble_web_websockify_port }}; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - } -} - -map $http_upgrade $connection_upgrade { - default upgrade; - '' close; -} \ No newline at end of file diff --git a/roles/mumble/templates/umurmur.conf.j2 b/roles/mumble/templates/umurmur.conf.j2 deleted file mode 100644 index eeb63fa..0000000 --- a/roles/mumble/templates/umurmur.conf.j2 +++ /dev/null @@ -1,72 +0,0 @@ -{{ ansible_managed | comment }} -max_bandwidth = 48000; -welcometext = {{ umurmur_welcome_text - if umurmur_welcome_text is string - else (umurmur_welcome_text | join('
')) - | to_json }}; -certificate = {{ umurmur_certificate | to_json }}; -private_key = {{ umurmur_private_key | to_json }}; -password = {{ umurmur_user_password | to_json }}; -{% if umurmur_admin_password is defined %} -admin_password = {{ umurmur_admin_password | to_json }}; # Set to enable admin functionality. -{% endif %} -# ban_length = 0; # Length in seconds for a ban. Default is 0. 0 = forever. -# enable_ban = false; # Default is false -# banfile = "banfile.txt"; # File to save bans to. Default is to not save bans to file. -# sync_banfile = false; # Keep banfile synced. Default is false, which means it is saved to at shutdown only. -allow_textmessage = true; # Default is true -# opus_threshold = 100; # Percentage of users supporting Opus codec for it to be chosen. Default is 100. -# show_addresses = true; # Whether to show client's IP addresses under user information -max_users = {{ umurmur_max_users }}; - -bindport = {{ umurmur_port }}; -# bindaddr = "0.0.0.0"; - -# username and groupname for privilege dropping. -# Will attempt to switch user if set. -# username = ""; -# If groupname not set the user's default login group will be used -# groupname = ""; - -# Log to file option. Default is logging to syslog. -# umurmurd will close and reopen the logfile if SIGHUP is received. -logfile = "/var/log/umurmurd.log"; - -# CA location for CA-signed certificates -# ca_path = "/path/to/ca/certificates/"; - -# Channel tree definition: -# Root channel must always be defined first. -# If a channel has a parent, the parent must be defined before the child channel(s). -channels = ( -{% for channel in umurmur_channels %} - { - name = {{ channel.name | to_json }}; - description = {{ channel.description | default('') | to_json }}; - parent = {{ channel.parent | default('') | to_json }}; - noenter = {{ channel.noenter | default(false) | string | lower }}; - silent = {{ channel.silent | default(false) | string | lower }}; -{% if channel.position is defined %} - position = {{ channel.position }}; -{% endif %} -{% if channel.password is defined %} - password = {{ channel.password | to_json }}; -{% endif %} - }{{ loop.last | ternary("", ",") }} -{% endfor %} -); -# Channel links configuration. -channel_links = ( -{% for channel in umurmur_channel_links %} -{% for destination in channel.destinations %} - { - source = {{ channel.source | to_json }}; - destination = {{ destination | to_json }}; - }{{ loop.last | ternary('', ',') }} -{% endfor %} -{% endfor %} -); - -# The channel in which users will appear in when connecting. -# Note that default channel can't have 'noenter = true' or password set -default_channel = {{ umurmur_default_channel | to_json }}; \ No newline at end of file diff --git a/roles/mumble/templates/umurmur.service.j2 b/roles/mumble/templates/umurmur.service.j2 deleted file mode 100644 index 8c8a008..0000000 --- a/roles/mumble/templates/umurmur.service.j2 +++ /dev/null @@ -1,28 +0,0 @@ -[Unit] -Description=Minimalistic Mumble server -After=network.target - -[Service] -Type=simple -User=nobody -Group={{ umurmur_ssl_group }} -Restart=on-failure -RestartSec=3 -PIDFile=/run/umurmurd.pid -ExecStartPre=/usr/local/bin/umurmurd -t -c /usr/local/etc/umurmur.conf -ExecStart=/usr/local/bin/umurmurd -d -r -c /usr/local/etc/umurmur.conf -ExecReload=/bin/kill -HUP $MAINPID -PrivateDevices=yes -PrivateTmp=yes -ProtectSystem=strict -ReadWriteDirectories=/usr/local/etc/ -ProtectHome=yes -ProtectControlGroups=yes -ProtectKernelModules=yes -ProtectKernelTunables=yes -LockPersonality=yes -NoNewPrivileges=yes -LimitRTPRIO=1 - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/scripts/gopass-client.py b/scripts/gopass-client.py new file mode 100755 index 0000000..73878ba --- /dev/null +++ b/scripts/gopass-client.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import argparse +import configparser +import logging +import os +import subprocess +import sys + +ANSIBLE_ENV_DIR = os.getenv("ANSIBLE_CONFIG") +ANSIBLE_DIR = "/etc/ansible" +CURRENT_DIR = os.getcwd() +HOME_DIR = os.getenv("HOME") + +ANSIBLE_CONFIG_SECTION = "gopass" +ANSIBLE_CONFIG_KEY = "key_path" + +class ShutdownHandler(logging.Handler): + def emit(self, record): + logging.shutdown() + sys.exit(1) + +def find_ansible_config_value(section, key): + config = configparser.ConfigParser() + + ansible_config_dirs = [CURRENT_DIR, HOME_DIR, ANSIBLE_DIR] + if ANSIBLE_ENV_DIR: + ansible_config_dirs.insert(0, ANSIBLE_ENV_DIR) + + for ansible_config_dir in ansible_config_dirs: + ansible_config_path = os.path.join(ansible_config_dir, "ansible.cfg") + config.read(ansible_config_path) + + try: + return config[section][key] + except KeyError: + logging.debug(f"Cannot find '{key}' key in '{ansible_config_path}' config file") + + ansible_config_paths = ':'.join(ansible_config_dirs) + raise RuntimeError(f"Cannot find key '{ANSIBLE_CONFIG_KEY}' in {ansible_config_paths}") + +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Get a vault password from user keyring') + + parser.add_argument('--vault-id', action='store', default='', + dest='vault_id', + help='Name of the vault secret to get from keyring') + return parser + +def main(): + logger = logging.getLogger() + logger.setLevel(logging.NOTSET) + + formatter = logging.Formatter("%(levelname)s: %(message)s") + + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.setLevel(logging.INFO) + stdout_handler.addFilter(lambda record: record.levelno <= logging.INFO) + stdout_handler.setFormatter(formatter) + logger.addHandler(stdout_handler) + + stderr_handler = logging.StreamHandler(sys.stderr) + stderr_handler.setLevel(logging.WARNING) + stderr_handler.setFormatter(formatter) + logger.addHandler(stderr_handler) + + logger.addHandler(ShutdownHandler(level=logging.ERROR)) + + try: + ansible_config_value = find_ansible_config_value(ANSIBLE_CONFIG_SECTION, ANSIBLE_CONFIG_KEY) + except RuntimeError as e: + logging.error(e) + + arg_parser = build_arg_parser() + args = arg_parser.parse_args() + + ansible_vault_id = args.vault_id + gopass_key_path = os.path.join(ansible_config_value, ansible_vault_id) + + gopass_keys_cmd = subprocess.run(["gopass", "ls", "--flat"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if gopass_keys_cmd.returncode != 0: + logging.error(gopass_keys_cmd.stderr.decode()) + gopass_keys = map(bytes.decode, gopass_keys_cmd.stdout.splitlines()) + + try: + gopass_keyname = next(key for key in gopass_keys if key.endswith(gopass_key_path)) + except StopIteration: + logging.error(f"Cannot find '{gopass_key_path}' entry in gopass") + + ansible_vault_pass_cmd = subprocess.run(["gopass", "show", "-o", gopass_keyname], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if ansible_vault_pass_cmd.returncode != 0: + logging.error(ansible_vault_pass_cmd.stderr.decode()) + print(ansible_vault_pass_cmd.stdout.decode()) + +if __name__ == '__main__': + main()