diff options
| -rwxr-xr-x | etc/bin/cert.py | 244 | ||||
| -rw-r--r-- | secrets/ca/certs/sagittarius.sorre.internal.crt | 14 | ||||
| -rw-r--r-- | secrets/ca/certs/sagittarius.sorre.internal.key | 14 | ||||
| -rw-r--r-- | secrets/ca/rootCA.key | 14 |
4 files changed, 0 insertions, 286 deletions
diff --git a/etc/bin/cert.py b/etc/bin/cert.py deleted file mode 100755 index 92baa7e..0000000 --- a/etc/bin/cert.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python3 -""" -Usage: - python create-cert.py mydomain.internal - python create-cert.py app.internal --san api.internal --san admin.internal - -The CA material is expected under secrets/ca/ relative to the repo root: - secrets/ca/ - rootCA.key # encrypted with sops - rootCA.crt # plaintext (public) - certs/ # output directory, created if missing -""" - -from __future__ import annotations - -import argparse -import shutil -import subprocess -import sys -import tempfile -from pathlib import Path -from typing import List - - -def run(cmd: List[str], **kwargs) -> subprocess.CompletedProcess: - """Execute a command, check for errors.""" - try: - return subprocess.run(cmd, check=True, text=True, capture_output=True, **kwargs) - except subprocess.CalledProcessError as e: - print(f"Command failed: {' '.join(cmd)}", file=sys.stderr) - print(e.stderr, file=sys.stderr) - raise - - -def check_dependencies() -> None: - """Verify that git, openssl, and sops are available on $PATH.""" - required = ["git", "openssl", "sops"] - missing = [tool for tool in required if shutil.which(tool) is None] - if missing: - sys.exit(f"Missing required tools: {', '.join(missing)}") - - -def get_repo_root() -> Path: - """Return the root directory of the git repository.""" - result = run(["git", "rev-parse", "--show-toplevel"]) - return Path(result.stdout.strip()) - - -def build_csr_config(domain: str, extra_sans: List[str]) -> str: - """Return an OpenSSL config for a CSR, including SANs.""" - sans = [domain, f"*.{domain}"] + extra_sans - san_entries = "\n".join(f"DNS.{i+1} = {s}" for i, s in enumerate(sans)) - return f"""\ -[ req ] -default_bits = 2048 -distinguished_name = req_distinguished_name -req_extensions = req_ext -string_mask = utf8only -prompt = no - -[ req_distinguished_name ] -organizationName = Internal -commonName = {domain} - -[ req_ext ] -subjectAltName = @alt_names - -[ alt_names ] -{san_entries} -""" - - -def build_ext_config(domain: str, extra_sans: List[str]) -> str: - """Return an OpenSSL extension config for signing a server certificate.""" - sans = [domain, f"*.{domain}"] + extra_sans - san_entries = "\n".join(f"DNS.{i+1} = {s}" for i, s in enumerate(sans)) - return f"""\ -[ v3_server ] -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer -basicConstraints = critical, CA:FALSE -keyUsage = critical, digitalSignature, keyEncipherment -extendedKeyUsage = serverAuth, clientAuth -subjectAltName = @alt_names - -[ alt_names ] -{san_entries} -""" - - -def provision_cert( - domain: str, - extra_sans: List[str], - ca_key_path: Path, - ca_crt_path: Path, - out_dir: Path, - validity_days: int = 1095, -) -> None: - """ - Generate key, CSR, sign with CA, then encrypt artifacts. - - All temporary work is done inside a temporary directory; the final - key and certificate are moved to `out_dir` only after successful - signing. Existing files are never overwritten. - """ - key_path = out_dir / f"{domain}.key" - crt_path = out_dir / f"{domain}.crt" - - out_dir.mkdir(parents=True, exist_ok=True) - - if key_path.exists(): - raise FileExistsError(f"File already exists: {key_path}") - if crt_path.exists(): - raise FileExistsError(f"File already exists: {crt_path}") - - with tempfile.TemporaryDirectory() as tmpdir: - tmp = Path(tmpdir) - key_tmp = tmp / f"{domain}.key" - crt_tmp = tmp / f"{domain}.crt" - csr_path = tmp / f"{domain}.csr" - csr_conf = tmp / "csr.cnf" - ext_conf = tmp / "ext.cnf" - - # Generate private key inside temporary directory - subprocess.run( - ["openssl", "genrsa", "-out", str(key_tmp), "2048"], - check=True, - text=True, - capture_output=True, - ) - - # CSR configuration - csr_conf.write_text(build_csr_config(domain, extra_sans)) - subprocess.run( - [ - "openssl", "req", "-new", - "-key", str(key_tmp), - "-config", str(csr_conf), - "-out", str(csr_path), - ], - check=True, - text=True, - capture_output=True, - ) - - # Signing configuration - ext_conf.write_text(build_ext_config(domain, extra_sans)) - - # Sign the CSR with the decrypted CA key - sops_decrypt = subprocess.Popen( - ["sops", "decrypt", str(ca_key_path)], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - openssl_sign = subprocess.Popen( - [ - "openssl", "x509", "-req", - "-in", str(csr_path), - "-CA", str(ca_crt_path), - "-CAkey", "/dev/stdin", - "-CAcreateserial", - "-days", str(validity_days), - "-sha256", - "-extfile", str(ext_conf), - "-extensions", "v3_server", - "-out", str(crt_tmp), - ], - stdin=sops_decrypt.stdout, - stdout=subprocess.DEVNULL, - ) - sops_decrypt.stdout.close() - sops_return = sops_decrypt.wait() - sign_return = openssl_sign.wait() - - if sops_return != 0: - raise RuntimeError("sops decrypt failed") - if sign_return != 0: - raise RuntimeError("openssl signing failed") - - # Verify the certificate chain before moving anything - subprocess.run( - ["openssl", "verify", "-CAfile", str(ca_crt_path), str(crt_tmp)], - check=True, - text=True, - capture_output=True, - ) - - # Move the final artefacts to the output directory - shutil.move(str(key_tmp), str(key_path)) - shutil.move(str(crt_tmp), str(crt_path)) - - # Encrypt both files in place (they are now in out_dir) - subprocess.run( - ["sops", "encrypt", "--in-place", str(key_path)], - check=True, - text=True, - capture_output=True, - ) - subprocess.run( - ["sops", "encrypt", "--in-place", str(crt_path)], - check=True, - text=True, - capture_output=True, - ) - - print("✔ Certificate provisioned and encrypted:") - print(f" {key_path}") - print(f" {crt_path}") - - -def main(): - parser = argparse.ArgumentParser( - description="Provision an internal TLS certificate using a sops‑encrypted Root CA." - ) - parser.add_argument("domain", help="Primary domain name (e.g., myapp.internal)") - parser.add_argument( - "--san", - action="append", - default=[], - help="Additional Subject Alternative Names (can be used multiple times)", - ) - args = parser.parse_args() - - check_dependencies() - - repo_root = get_repo_root() - ca_dir = repo_root / "secrets/ca" - ca_key_path = ca_dir / "rootCA.key" - ca_crt_path = repo_root / "etc/certs/rootCA.crt" - out_dir = ca_dir / "certs" - - if not ca_key_path.exists(): - sys.exit(f"CA private key not found at {ca_key_path}") - if not ca_crt_path.exists(): - sys.exit(f"CA certificate not found at {ca_crt_path}") - - try: - provision_cert(args.domain, args.san, ca_key_path, ca_crt_path, out_dir) - except Exception as e: - sys.exit(f"Error: {e}") - - -if __name__ == "__main__": - main() diff --git a/secrets/ca/certs/sagittarius.sorre.internal.crt b/secrets/ca/certs/sagittarius.sorre.internal.crt deleted file mode 100644 index 7cdb7f7..0000000 --- a/secrets/ca/certs/sagittarius.sorre.internal.crt +++ /dev/null @@ -1,14 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:fbT9qNSUVbdi7uit7cvZsOM3A95EYTzCCIwaovaTnkqXAB4c9lXkOIb2mNTV3nYsLuOo9qHiU0RhHlgBk5vjnDKAdxE9iKXX49jfZhTrsLoFgkb2P7CQDYpEzvyrE4+HgtWV0p8/V+TyC/O3UUP2b7fnGljiwFnsj/ttR3Z474uGQghxNT/OSpjUeWCmmlfmqRV9Y3haruycZarlIEtXO/wc93wB1X2i8RYNBzvueguZGem1QoCsSMyJJqsjJ7gEt3QerAOPNrGctc//Ctl0hJMSW6wjXzm9Ww+m7uAq8K3joe3ztuhsn3b59GDxDX7Q0npPl+bLVtqo7erA9IThvsg5vfEllLPHFknATFif75DdeT4WMIs2R20Z9d89KZ92Zp2itJHn5EQaKNevVRLAMfLGf3xotC1Ice9/rt91x77jnA7i2CyIdmo8Y9SoyRAJkkKZrJqMvl5LCYzvmkjNpxlzU/FTcGx9DU6ipQOQWlT16wmNu7rgp22B0P2osEKnQVFqwVQwbcrVLkFxZHweAJbc3XPxRowqHGjaWU9FEpwssGnhb4gb01MmPoh3IRYBTtYMGAWmBp4F4oi2m7xq9zmrEvKxVs4mW/CNLSteW6YVTU7h/Er2bTCQPvcx1RebKHaO+5pOWI2qX9bS17WdZ52nZ6FjksEW06F9ZlOsqsB7HWYQI+RgwrtUuTpceJqkhHjDTtG9DNVJXuvymeY7OOKxX8Qw3WvvwQoksFgdi+3q3dziu3mK1vaUNmbOwFrUWPqNyKF+muWCCkygyVGygw1sOFmGVkaQ6JH8YvFjgAiGY2Q1spZrRBtGaNg4Q7by+xqSZTsaKbt/njMSQSZuCj4CAKvUS51XDXtwGquNoTkX2UeF8xdYmk8QUOyL39Hc52q7m6T05kxQwSaiuilLA4kJpvb2VXwecE/3QTQBkS9GxE+HsyMFf5IHJLt4+BYqvULaq7TO2vDoK1PPuzyURrkatQeHHRCBa6NdhWZ/ppUe1KQQDO8pqLFWm6p/qCz9pFfawVxaAgznKmRQrO2/C4uWsMSijhBPcYmYIscCWhbhkjvgg1CfVbaBZvcUMvhB4JrjdYEkt0OAvBqncvr16GWaPsDJH3BPj8TiGQuisGwKp+cninlguZ+LdRNZIihPwXwkC5YA3l0W3l6hY0oY0rZLEB/443yU3pa4FXwHFn2bgfzNYcOSXef3lRsGYLhD1nLjdhcF1B7ESA7gGMTZvRUfle+g2O8eZoloq5kv1MxqIkBpW1Gdb9UgqEU2Zv8L2lLQ5JQ5aK/i02mq1/MS1HRjPcdUdQtbyaMvmZPcxMIXsw4YU459BvVoowO/vNgznF/aM8not4fSMsnG2tVAPpTm0QCx/Ff4OZgw1E0/lXPAxNrH3KRxE1bYyS6/zABqTFeBQXKK3vU9Y20peMXR2cpIiF7+EHv4pm4phP94bEOSdEpWejDeINlj/sK73IbEWwS9D+R+w2RnaBZW86UyxZVAcl99dW5k2UPsVGK5DH8c6T3fzq1KQgqsXQkFU0N+2xpAzIFu5P6m4gMmZgjSehne4ed4Zv/qCRUuACfjJmDRmaBhx3JsFOsT7LHRRvKgq0lOSdqRt7DF9ywWGUla+qw4/453CELTMqNpFLOHaMrk42Mxd1sphZBAcg7122eTZ2Lyh5ig/Y+R+/7PRCsVA5sJsFevGZSg91XTiN0vD0qSxLfGDCWS9FB2XhhPPScX0TigbHzRH++OlZEjKd6DQwkuIVkdBUglgfUX8zHIJr0NQbAMJW+ml5npgacer6TYI00YY03hSZcdyWeOl+Sjbe2EJE4MtbkcburENI/CYXBsm+xwV8o8p5h34Fe8z3U1pTC3++uxQtLIMPtSeXFkV66gjoipUxXaab1UqVW5g7Zr8UciwY4dLGBn7WYYBO7ehx2WBrAb26h2Yg2G2vJyb+5mIz/qBYwOMFWHNxPf8n4XT4PVz3f8w1ovlU+FVMlzv9tZ3QIgt73VhhdHkd/sCCOTXxiXPUE/aKQtv7EftMatYFK5Ln9SUjinDG2TnZrq/DF+eJH7j6Aq7ExXieLPOcM+tZzy655HJnGXtOkYI6CVpwqzH5lU9L1E9sRx23wpvv2V5dfZFO70fM9Oiv3bZxf361oL/UDUF7sv1gHFRwu502sEkdwjgCanRCFCYzmfNyOvRnjCbufHArgd8pWwVX5JhA/VBRI/aLl0Es3yBq3/56LAokUql3zNZKsahVKStjgVyGProZ9n/y2dSvBGKwYUHngB3BIogkLPKNyQqV1TeTUUy39UTOuREOyJ/zBrhjIHeuQEy1Or6qWkY0XPUQctQVth9brnZSBZ17Xve21AT/Ho5T6ot5rZ5uhwCaqXCclraQrPlkQ+osjQmku+IFi70m449icpsBlS1ngeF4/xYl6QMbrJIHX3ljiUJebf1RwC4F+Jf1Yye2NCeJxyHVENUl9NcocAXBovNTXp8kZL57u11Pq4nhq/wGhdVAupvv7P3APuOLMRPkTzQCioc6vvr8Ykh/g=,iv:eHaQ2ItsNFoVA0wZJdgZcteXug3iKmkmvWzRMXQQQco=,tag:Oj1hJYgbaxNzxiJuD4EClg==,type:str]", - "sops": { - "age": [ - { - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmSzhSZmNFNzRTRTB0QWE2\nVGFDdTJwS2RNOWF6bTFTUGVXZGlrVTQzTUQ0Cjl2Mzg2amhITHhqR1lnYzQ4RVBK\nM1U2bXc3SGlBZDFWOE5PVnRvZTNvRU0KLS0tIGNNQVlpNng3UjdUNHFuaWRodWN2\nem5ZVlNhRUY5VG9WK1F3SjRyNnNlQjgKqSxHlZezUcn1pnIbN2aY5V5VCMx4a9Ya\neigSPHYR9B4faso9CM5naNtPoegB9EHFp9xvX7Z8ThYjVCmXsxOG7g==\n-----END AGE ENCRYPTED FILE-----\n", - "recipient": "age1jmpdssn7555g0pht7zwqq2dvrvzxyu65w6cf2su8pp2fz0zvaykq0jgern" - } - ], - "lastmodified": "2026-06-04T19:23:13Z", - "mac": "ENC[AES256_GCM,data:j8b2BG2u72I2QkugQ1HQu1NLKgARevNVKRjSxxqN8SitheeMfQMq/5ZqEsnUl9Cec7hA3W7JqnJ4xvOaD5BcmAbPMFdggWCmCgEuGx7Ls/gK8HIMAmrpOkmEimoj0hqLboKj1i+GEiJmejCQbvcwtt8/pTu5ZtpQxJlCitcb5zE=,iv:+1vYn/9CbjK44n0NuTgKbWNUYyOpSxAJbV6+7O1RTkM=,tag:hqAA6QveXyFjZEUt8BQWQA==,type:str]", - "version": "3.13.1" - } -} diff --git a/secrets/ca/certs/sagittarius.sorre.internal.key b/secrets/ca/certs/sagittarius.sorre.internal.key deleted file mode 100644 index 45a6ba8..0000000 --- a/secrets/ca/certs/sagittarius.sorre.internal.key +++ /dev/null @@ -1,14 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data: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,iv:VkkE7vR5aZej54Bxdc5i+9ixkjDAbMfzOZ+FsiIn4J8=,tag:uhB/+oMSmVAdsodnVoH+5A==,type:str]", - "sops": { - "age": [ - { - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzYzBGaENOL09jLzg5VlpO\nZEp0UVRFN1JMNlp6QXFSNSszRGg5ak9GckI0ClBNZFVPQVl2VWZLdjdLeGJRb3I4\neXRTUk9ET0xhN3hrYnIwNWhCV3k4TGMKLS0tIDZjZ20zQWtoR1JWSWNYWmYrdkhB\nVzd4OFprOXlpekZOclhudk5lSXdxWWcKPZfm2ZcUgv9VunyhDresifXoRglxbJpl\ncKT+jtjGZx7vfpw4pUSFDYe7uQh1qhhO0irNV7PqTS2Weinas9keFA==\n-----END AGE ENCRYPTED FILE-----\n", - "recipient": "age1jmpdssn7555g0pht7zwqq2dvrvzxyu65w6cf2su8pp2fz0zvaykq0jgern" - } - ], - "lastmodified": "2026-06-04T19:23:13Z", - "mac": "ENC[AES256_GCM,data:suXkl/k+DKxIca7vfiIBaRpMwA6n4t0sRFTwZQxueaGkQX8ZWHP4474JgZoW1kKuC4lL5lG+oW76Bai5uztqK+gN7AXQ0Qkc0bcPYm//IXwLN7li+QizyslHRO/5OqYv99s4T7J+aSkyZcLFOhi1XIR9Yuh9ZT4jxAtos+8eDkk=,iv:dnFc0IqomxTTbmMNjylZafHFK3HAWmLpN8t7k0aalKM=,tag:ntjpUIA4imnIfFPeflqISg==,type:str]", - "version": "3.13.1" - } -} diff --git a/secrets/ca/rootCA.key b/secrets/ca/rootCA.key deleted file mode 100644 index f709b13..0000000 --- a/secrets/ca/rootCA.key +++ /dev/null @@ -1,14 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data: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,iv:XfWG/Ty4Gwey9WdiLbEnb+7tveKY7B4wxZvhD7pzWWM=,tag:6tFiQ2UG8LD3RZ9RkiSQ/g==,type:str]", - "sops": { - "age": [ - { - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTM2J4ZE0zcVdCRjIraDJ5\nelZJU0p4VTlxWGIzU2w0SFVQVHJ1Y0hOUVdZCnFic1pzcTN3a0FvcXlvYVFjdjhK\nVGlRc3NQbG9CWVliKzJ0SkdLanBxVXMKLS0tIGxOMlR1UG5lYXVxdWpFQ3Qxb1Uw\nYUhaQ05TRnZoNWZIZW54d1ZGcWxXVFEKU5Tx5zoqZ21fcZD/dC8J6bkj0RpBY96D\n9fnqfFLf6SRRd5MeMHCprfltwR7pzBmQHYZYfC++iSIZj9VKmxHtwg==\n-----END AGE ENCRYPTED FILE-----\n", - "recipient": "age1jmpdssn7555g0pht7zwqq2dvrvzxyu65w6cf2su8pp2fz0zvaykq0jgern" - } - ], - "lastmodified": "2026-05-31T18:54:16Z", - "mac": "ENC[AES256_GCM,data:HNbxIfOkVm+BolQhNUvQi1Itaa/Z2SVEOvQKG9ITkeL88KqqYF+GNHq4mtJjHNA0kbN06rFPO1a3p8KWePHQ8GKPnh0Suh5eEnhpU0Rw9lZCf2G64d8AVJ267v8OQjhAdPfOLYjCQqauhvEXRt+EeWSqYtTq+u2mmSwIpCtByqA=,iv:M4ndQbEaxRbvABzzu3aIK5wMp7s1W6JQdIRPRJkqNbw=,tag:tJKZZWy/KineEBfKG/Rx+g==,type:str]", - "version": "3.13.1" - } -} |
