diff options
Diffstat (limited to 'convertsecrets')
-rwxr-xr-x | convertsecrets | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/convertsecrets b/convertsecrets new file mode 100755 index 0000000..e8e54f4 --- /dev/null +++ b/convertsecrets @@ -0,0 +1,215 @@ +#!/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 <module_name>/secrets/* to know what secrets +it should handle. These secret files contain directives for what to do +about these secrets. + +Example directives: + +USER <description> +(a secret that must be filled in by the user) + +USER_LONG <description> +(the same, indicates that the secret fits on several lines) + +CMD <command> +(a secret that is generated by running this command) + +CMD_ONCE <command> +(same, but value is not changed when doing a regen) + +CONST <constant value> +(the secret has a constant value set here) + +CONST_LONG +<constant value, several lines> +(same) + +SERVICE_DN <service name> <service description> +(the LDAP DN of a service user) + +SERVICE_PASSWORD <service name> +(the LDAP password for the corresponding service user) + +SSL_CERT <cert name> <list of domains> +(a SSL domain for the given domains) + +SSL_KEY <cert name> +(the SSL key going with corresponding certificate) + +RSA_PUBLIC_KEY <key name> <key description> +(a public RSA key) + +RSA_PRIVATE_KEY <key name> +(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 <module name>...") + + +# vim: set sts=4 ts=4 sw=4 tw=0 ft=python et : |