#!/usr/bin/env nix-shell #!nix-shell -i python3 -p "python3.withPackages(ps: [ ps.pip ps.consul ps.ldap ps.passlib ps.requests ps.six ])" # DEPENDENCY: python-consul import consul # DEPENDENCY: python-ldap import ldap # DEPENDENCY: passlib from passlib.hash import ldap_salted_sha1 import os import sys import glob import subprocess import getpass import base64 from secrets import token_bytes """ TODO: this will be a utility to handle secrets in the Consul database for the various components of the Deuxfleurs infrastructure Functionnalities: - check that secrets are correctly configured - help user fill in secrets - create LDAP service users and fill in corresponding secrets - maybe one day: manage SSL certificates and keys It uses files placed in /secrets/* to know what secrets it should handle. These secret files contain directives for what to do about these secrets. Example directives: USER (a secret that must be filled in by the user) USER_LONG (the same, indicates that the secret fits on several lines) CMD (a secret that is generated by running this command) CMD_ONCE (same, but value is not changed when doing a regen) CONST (the secret has a constant value set here) CONST_LONG (same) SERVICE_DN (the LDAP DN of a service user) SERVICE_PASSWORD (the LDAP password for the corresponding service user) SSL_CERT (a SSL domain for the given domains) SSL_KEY (the SSL key going with corresponding certificate) RSA_PUBLIC_KEY (a public RSA key) RSA_PRIVATE_KEY (the corresponding private RSA key) """ # Parameters LDAP_URL = "ldap://localhost:1389" SERVICE_DN_SUFFIX = "ou=services,ou=users,dc=deuxfleurs,dc=fr" consul_server = consul.Consul() # ---- USER = "USER" USER_LONG = "USER_LONG" CMD = "CMD" CMD_ONCE = "CMD_ONCE" CONST = "CONST" CONST_LONG = "CONST_LONG" SERVICE_DN = "SERVICE_DN" SERVICE_PASSWORD = "SERVICE_PASSWORD" SSL_CERT = "SSL_CERT" SSL_KEY = "SSL_KEY" RSA_PUBLIC_KEY = "RSA_PUBLIC_KEY" RSA_PRIVATE_KEY = "RSA_PRIVATE_KEY" class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def read_secret(key, file_path): lines = [l.strip() for l in open(file_path, "r")] if len(lines) == 0: print(bcolors.FAIL, "ERROR:", bcolors.ENDC, "Empty file in", file_path) sys.exit(-1) l0 = lines[0].split(" ") stype = l0[0] secret = {"type": stype, "key": key} if stype in [USER, USER_LONG]: secret["desc"] = " ".join(l0[1:]) elif stype in [CMD, CMD_ONCE]: secret["cmd"] = " ".join(l0[1:]) elif stype == CONST: secret["value"] = " ".join(l0[1:]) elif stype == CONST_LONG: secret["value"] = "\n".join(lines[1:]) elif stype in [SERVICE_DN, SERVICE_PASSWORD]: secret["service"] = l0[1] if stype == SERVICE_DN: secret["service_desc"] = " ".join(l0[2:]) elif stype in [SSL_CERT, SSL_KEY]: secret["cert_name"] = l0[1] if stype == SSL_CERT: secret["cert_domains"] = l0[2:] elif stype in [RSA_PUBLIC_KEY, RSA_PRIVATE_KEY]: secret["key_name"] = l0[1] if stype == RSA_PUBLIC_KEY: secret["key_desc"] = " ".join(l0[2:]) else: print(bcolors.FAIL, "ERROR:", bcolors.ENDC, "Invalid secret type", stype, "in", file_path) sys.exit(-1) return secret def read_secrets(module_list): secrets = {} for mod in module_list: for file_path in glob.glob(mod.strip('/') + "/secrets/**", recursive=True): if os.path.isfile(file_path): key = '/'.join(file_path.split("/")[1:]) secrets[key] = read_secret(key, file_path) return secrets def convert_secrets(module_list): for mod in module_list: print("converting module: ", mod) secrets = read_secrets([mod]) with open(os.path.join(mod.strip('/'), "secrets.toml"), "w") as file: for (secret_key, secret) in secrets.items(): file.write("[secrets.\"{}\"]\n".format("/".join(secret_key.split("/")[1:]))) ty = secret["type"] if ty in [CMD, CMD_ONCE]: newsecret = { "type": "command", "rotate": ty != "CMD_ONCE", "command": secret["cmd"], } elif ty in [USER, USER_LONG]: newsecret = { "type": "user", "multiline": ty == "USER_LONG", "description": secret["desc"], } elif ty in [CONST, CONST_LONG]: newsecret = { "type": "constant", "value": secret["value"], } else: newsecret = { "type": secret["type"], } if "service_desc" in secret: newsecret["description"] = secret["service_desc"] if "key_desc" in secret: newsecret["description"] = secret["key_desc"] if "cert_name" in secret: newsecret["name"] = secret["cert_name"] if "key_name" in secret: newsecret["name"] = secret["key_name"] for k in ["service", "cert_domains"]: if k in secret: newsecret[k] = secret[k] for (k, v) in newsecret.items(): if type(v) == bool: if v: file.write("{} = true\n".format(k)) elif type(v) == str: file.write("{} = {}\n".format(k, repr(v))) else: print("invalid value: ", v) file.write("\n") # ---- MAIN ---- if __name__ == "__main__": for i, val in enumerate(sys.argv): if val == "convert": convert_secrets(sys.argv[i+1:]) break else: print("Usage:") print(" convertsecrets convert ...") # vim: set sts=4 ts=4 sw=4 tw=0 ft=python et :