From cc5ef01e7fd5fa23e957e59d8549d734eb087174 Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Sat, 1 Sep 2018 08:21:54 +0100 Subject: [PATCH] Support includes in passwords.yml Distribute a default passwords.yml file for users to include. Mirrors the setup used for services.yml. --- passwords.yml.default | 43 ++++++++++++++++ playbooks/init-credentials.yml | 4 ++ scripts/pwgen.py | 89 +++++++++++++++++++++++++--------- 3 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 passwords.yml.default diff --git a/passwords.yml.default b/passwords.yml.default new file mode 100644 index 00000000..01645e0a --- /dev/null +++ b/passwords.yml.default @@ -0,0 +1,43 @@ +- name: sso_session_auth_secret + description: sso-server cookie auth key + type: binary + length: 64 +- name: sso_session_enc_secret + description: sso-server cookie encryption key + type: binary + length: 16 +- name: sso_csrf_secret + description: sso-server cookie-based CSRF secret + type: binary + length: 64 +- name: sso_device_manager_auth_secret + description: sso-server cookie-based device manager secret + type: binary + length: 64 + +- name: ssoproxy_session_auth_key + description: sso-proxy cookie authentication key + type: binary + length: 64 +- name: ssoproxy_session_enc_key + description: sso-proxy cookie encryption key + type: binary + length: 32 + +- name: ldap_root_password + description: LDAP cn=manager password +- name: ldap_replica_password + description: LDAP cn=replica password +- name: ldap_authserver_password + description: LDAP cn=authserver password +- name: ldap_keystore_password + description: LDAP cn=keystore password +- name: ldap_accountserver_password + description: LDAP cn=accountserver password + +- name: grafana_session_secret + description: session secret for Grafana + length: 32 + +- name: acme_tsig_key + type: tsig diff --git a/playbooks/init-credentials.yml b/playbooks/init-credentials.yml index f4d5ab01..a7b005d6 100644 --- a/playbooks/init-credentials.yml +++ b/playbooks/init-credentials.yml @@ -27,6 +27,10 @@ # First of all, generate secrets from the passwords.yml file. - name: Initialize secrets local_action: command ../scripts/pwgen.py --vars "{{ credentials_dir }}/secrets.yml" "{{ passwords_file }}" + register: pwgen_result + changed_when: "pwgen_result.rc == 1" + failed_when: "pwgen_result.rc > 1" + - name: Link secrets.yml from the vars directory file: src: "{{ credentials_dir }}/secrets.yml" diff --git a/scripts/pwgen.py b/scripts/pwgen.py index d7052e5c..95c71e5b 100755 --- a/scripts/pwgen.py +++ b/scripts/pwgen.py @@ -6,18 +6,49 @@ from __future__ import print_function +import argparse import base64 -import sys -import optparse import os import random import shutil import subprocess import string +import sys import tempfile import yaml +# Possible exit codes for this program. +EXIT_NOTHING_TO_DO = 0 +EXIT_CHANGED = 1 +EXIT_ERROR = 2 + + +# Returns the absolute path to a file. If the given path is relative, +# it will be evaluated based on the given path_reference. +def _abspath(path, relative_to='/'): + if path.startswith('/'): + return path + return os.path.abspath(os.path.join(os.path.dirname(relative_to), path)) + + +# A version of yaml.safe_load that supports the special 'include' +# top-level attribute and recursively merges included files. +def _read_yaml(path): + with open(path) as fd: + data = yaml.safe_load(fd) + if not isinstance(data, list): + raise Exception('data in %s is not a list' % (path,)) + # Find elements that include other files. + out = [] + for entry in data: + if 'include' in entry: + out.extend(_read_yaml(_abspath(entry['include'], path))) + else: + out.append(entry) + return out + + def decrypt(src): return subprocess.check_output( ['ansible-vault', 'decrypt', '--output=-', src]) @@ -99,38 +130,48 @@ def generate_password(entry): def main(): - parser = optparse.OptionParser(usage='%prog <password file>') - parser.add_option('--vars', metavar='FILE', dest='vars', - default='vars/passwords', - help='Output vars file') - opts, args = parser.parse_args() - if len(args) != 1: - parser.error('Not enough arguments') + parser = argparse.ArgumentParser(description=''' +Autogenerate secrets for use with Ansible. + +Secrets are encrypted with Ansible Vault, so the +ANSIBLE_VAULT_PASSWORD_FILE environment variable must be defined. +''') + parser.add_argument( + '--vars', metavar='FILE', dest='vars_file', + help='Output vars file') + parser.add_argument( + 'password_file', + help='Secrets metadata') + args = parser.parse_args() if not os.getenv('ANSIBLE_VAULT_PASSWORD_FILE'): - print("You need to set ANSIBLE_VAULT_PASSWORD_FILE", file=sys.stderr) - sys.exit(2) + raise Exception("You need to set ANSIBLE_VAULT_PASSWORD_FILE") - password_file = args[0] - vars_file = opts.vars passwords = {} - if os.path.exists(vars_file): - passwords.update(yaml.load(decrypt(vars_file))) + if os.path.exists(args.vars_file): + passwords.update(yaml.safe_load(decrypt(args.vars_file))) changed = False - with open(password_file) as fd: - for entry in yaml.load(fd): - name = entry['name'] - if name not in passwords: - print("Generating password for '%s'" % name, file=sys.stderr) - passwords[name] = generate_password(entry) - changed = True + for entry in _read_yaml(args.password_file): + name = entry['name'] + if name not in passwords: + print("Generating password for '%s'" % name, file=sys.stderr) + passwords[name] = generate_password(entry) + changed = True if changed: - encrypt(yaml.dump(passwords), vars_file) + encrypt(yaml.dump(passwords), args.vars_file) + return EXIT_CHANGED + + return EXIT_NOTHING_TO_DO if __name__ == '__main__': - main() + try: + sys.exit(main()) + except Exception as e: + print("Error: %s" % str(e), file=sys.stderr) + sys.exit(EXIT_ERROR) + -- GitLab