aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xetc/bin/cert.py244
-rw-r--r--secrets/ca/certs/sagittarius.sorre.internal.crt14
-rw-r--r--secrets/ca/certs/sagittarius.sorre.internal.key14
-rw-r--r--secrets/ca/rootCA.key14
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: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,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:cbdZZc/KUDyzN++aQl3RcKL/B+PE3gNLFXZdAlH4oaUCC0obrJaVJiBzFCwdGS9ax+NV13yRGjKotluhAj2IX58zdsOUc8h3NKrY0MUQsIwXM83OnDH4taefRAS0At/G4sWG4dHAsJPEA4/lTvQq9CkAMO3M18+6CrF1jO6+pX5BlUYLfuki6hKET0ogiljzeqz6xGNNiTs14L4dGIyl0B1EYDw1yFw7PCABM8sSHNL3/2snSJPyGo6REID7JFBwt1L2rszPsnc1AVHynAqn0hMQRfOJurlEKbMRryjZCiisG06mKEyMrHLfIO/uE+M2neJnL74yog0GpHeqEheKlAnFCIKSsjyzxQBlRA4wTPoZzpnRGl/SzTX4K4GrQWnzTyv8aiAHpqGYEqWfUwPmwI3XQPOd9NCydxbDbrG+1aPvMmBQUEbzZm2qhcw+ObPPj37kHE8hqqdCkEz053d9grHOQQS7qKkqpCH1rrhBl1J8VOfKEZNE9eIpu0/7DIHhRch1RYyRcP3/aRCU0sD8Jx2Lzo5oRphWKgVOt/4wxbJzfzTK0zHF6RB8XPezYXfA95Ov8/1VINGNaFIqdcCCOl6TJUD2chBH6XSROp2ZD0S4ZgHxSDqqG8wb8TlvCoziuawde+se1ECH639kUbBS/nlaWixeEupZnEnOV2lPcJkQ7aJ1ffokyIjjkKzQ2aNBf5HClLUEd6SlNxASbwk24rhLvhewdL2BmizspK1GfsMhgAB6C5iwmGyVDd37YJ/xfPNkaZTQopXJxwup5xNOBDyOyMqkrL6Vbz5XqV70wnBBSUMJSRm5QecW8mJrxtZJUafgY9uEXYXg4/dAZNVdMiBbs4k7r7vQWGUtC5HaDK579HRnQr9UL5333HqtN5hEbCyVhghggoLVddQjtL6iELT29O+pERu5yePEsnrCp+GqdUjrdW+kbuOglWCY8CwAlck6D5pxpLo8uIyjtlg22Ol/qks4VQ/tSt9CcbWHciSP16R1ruNcrxXLWWTcuHAMr3yKBZKM2cJ03XuCR+gcnqiin5ylGwWXlUYgFJ1zy9Vs8Uv2bebKuCYArt686aRvJaTo7tzRgPA3r2TgdmsLmKTRkuR2qGf5QBNKC34fZp/HwRGssbGMvX3++AzLR09AvKeqagCjJNfIPs9eWLJ1Xk3DTA6H8eBG0NFxeb8qjNTqw5UxnpPuyeWIH9XtaaPOopk6K0UVS9qV20hKOVPQXlfOoXuKuJaUswupT5GZ/LcKMSC8SS2d5Ff8gDOW5prIGYCSfCkzCw1j5UDo/Iwpz/YXTTi3n+ou+HoTaCm35kQG1heGORnyM5tC0D3xzT3gwFoO93wVpEjCVSkIbhuvquznYyovuyiqR+BGUHufKwvs8UKuzb21fZA+VLLzr1JWg5Dkpma6q6jBPgNH/IEGZQncsm33wSu+TruZGqf8XfEUj47LDORjLTtHATuKTiIS8kRVvCGzHhnOfc550XCuKKXA0MyNOIyxrSg3ZYNyA22WCO+XwD2D6rup98Pwh6Zq3QTd6kz2lZaSr4xnJVz5/3RHdoxC2lRm4FXh+RebqCvxxWjHKRzna1PKZswydueXGsx20bojRm+pXUF5cE79W+3rJAxz2OH7YVQ3vex3YeFmTvDE0REXqwGgH0YQqaZxMsqU8NLklJ1fKJI/iTLHHOfPOPQV9/7xOdPhVXO1BWlcbHv1iup3Wh1nBLoBiY2bcpkrdZuZkvyzsHV4WGWIMzmpmIOmVuB1Cqmp1ObCNSIh2kDnuaGBh4wUNYqwzXR6wLpFKi8FbTAbk0wppRms4x/mHgG4cpk0DEQMIFhiv6NEX9Sjsyg3a4dcxiWPoMDCdrXp6A3Digy+w1rM52iAoO9Di1RByP40sDap/EaxDx6bOLQhOa0w4TtHUhiwRGovOBdbeULhhaxU55BzPz5u16ujQ6/TA41Q//EuhUzhHKAidHRTcuN8VKSKoS9yPPZ7nRQoaKjmaqiiviGZYt7REFGqG7xUq8t5BPSJGLC5eKbFVxdrvwjM/0LTgks22A4XFGpOGIUAr7AKFKsgzrR108nRtoYij9g9sGRiQWczjcI2eiaGcEKsDIGcOFtnbv+tyeilqAkdFi8fFcCB7NnH2bNpaSnb9kPrnEBNXQEf9YeO7rYxiQ6YH0MOgckK8wLQjV117Dc/M8KHKb9c7T3XrhZ3d/t0d0dnWOt0tbzeXV6nHYiKkwLSGQN7BKV/VB8j6jpZdZX4q8r/JMPTu4gXvNJPyNmQs7IP,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"
- }
-}