extract mumble and acme roles into separate repos
parent
476746f999
commit
bae636a391
|
@ -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
|
|
@ -4,7 +4,7 @@ umurmur_domain: mumble.parley.be
|
||||||
umurmur_welcome_text:
|
umurmur_welcome_text:
|
||||||
- Welcome to Parley Talk!
|
- Welcome to Parley Talk!
|
||||||
- You can talk to the people in the room you joined.
|
- 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_admin_password: wC7yZ4vV2ocb7AkBfQ2RwuhRqYVyiwY42Rjpw3pfJ
|
||||||
umurmur_max_users: 100
|
umurmur_max_users: 100
|
||||||
|
|
||||||
|
@ -43,4 +43,4 @@ umurmur_channels:
|
||||||
umurmur_default_channel: Welcome
|
umurmur_default_channel: Welcome
|
||||||
|
|
||||||
mumble_web_domain: talk.parley.be
|
mumble_web_domain: talk.parley.be
|
||||||
mumble_web_version: c03b78d096eae69e1cec82e148f65e7a8541bd68
|
mumble_web_version: master
|
|
@ -6,5 +6,4 @@
|
||||||
- "{{ umurmur_domain }}"
|
- "{{ umurmur_domain }}"
|
||||||
- "{{ mumble_web_domain }}"
|
- "{{ mumble_web_domain }}"
|
||||||
roles:
|
roles:
|
||||||
- acme
|
- ppbe.mumble
|
||||||
- mumble
|
|
|
@ -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
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -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
|
|
|
@ -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 }}"
|
|
|
@ -1,2 +0,0 @@
|
||||||
- import_tasks: acme.yml
|
|
||||||
tags: acme
|
|
|
@ -1 +0,0 @@
|
||||||
acme_version: 2
|
|
|
@ -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"
|
|
|
@ -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
|
|
|
@ -1,7 +0,0 @@
|
||||||
- name: Validate Nginx config
|
|
||||||
command: nginx -t
|
|
||||||
|
|
||||||
- name: Reload Nginx server
|
|
||||||
service:
|
|
||||||
name: nginx
|
|
||||||
state: reloaded
|
|
|
@ -1,5 +0,0 @@
|
||||||
dependencies:
|
|
||||||
- role: geerlingguy.nodejs
|
|
||||||
vars:
|
|
||||||
nodejs_install_npm_user: root
|
|
||||||
tags: [nodejs,mumble_web]
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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"
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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('<br />'))
|
|
||||||
| 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 }};
|
|
|
@ -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
|
|
|
@ -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()
|
Loading…
Reference in New Issue