aboutsummaryrefslogtreecommitdiff
path: root/convertsecrets
diff options
context:
space:
mode:
Diffstat (limited to 'convertsecrets')
-rwxr-xr-xconvertsecrets215
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 :