From 886dbeee10f0d9c12199bbc8a09eee5ea23faba5 Mon Sep 17 00:00:00 2001 From: jenkins Date: Sun, 4 Oct 2026 00:45:33 -0500 Subject: [PATCH] recovery: wire Vault passwords to existing Ananke host actions --- scripts/configure_ananke_node_access.py | 89 +++++++++++++ .../bootstrap/secretproviderclass.yaml | 117 ++++++++++++++++++ .../bootstrap/vault-sync-deployment.yaml | 8 +- 3 files changed, 213 insertions(+), 1 deletion(-) create mode 100755 scripts/configure_ananke_node_access.py diff --git a/scripts/configure_ananke_node_access.py b/scripts/configure_ananke_node_access.py new file mode 100755 index 00000000..1085e1af --- /dev/null +++ b/scripts/configure_ananke_node_access.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +"""Enable Ananke's existing Vault-synchronized sudo path without replacing config.""" +import argparse +import os +from pathlib import Path +import re +import shutil +import stat +import subprocess +import tempfile + +import yaml + + +def updated_config(source): + """Preserve unrelated text/settings; change only credential lookup and Titan-24 user.""" + config = yaml.safe_load(source) + desired = { + "host_sudo_secret_namespace": "maintenance", + "host_sudo_secret_name_template": "ananke-sudo-{node}", + "host_sudo_secret_password_key": "password", + } + for key in desired: + source = re.sub(r"^ " + key + r":.*\n", "", source, flags=re.M) + stanza = "".join(" " + key + ": '" + value + "'\n" for key, value in desired.items()) + if source.count("\nstartup:\n") != 1 or source.count("\nssh_node_users:\n") != 1: + raise ValueError("unexpected_config_structure") + source = source.replace("\nstartup:\n", "\nstartup:\n" + stanza, 1) + start = source.index("\nssh_node_users:\n") + end_match = re.search(r"\n[^ #\n][^\n]*:", source[start + 1:]) + if end_match is None: + raise ValueError("missing_inventory_boundary") + end = start + 1 + end_match.start() + users = source[start:end] + if not re.search(r"^ titan-24:", users, flags=re.M): + users += "\n titan-24: tethys" + else: + users = re.sub(r"^ titan-24:.*", " titan-24: tethys", users, flags=re.M) + source = source[:start] + users + source[end:] + expected = config.copy() + expected["startup"] = dict(config.get("startup", {}), **desired) + expected["ssh_node_users"] = dict(config.get("ssh_node_users", {}), **{"titan-24": "tethys"}) + if yaml.safe_load(source) != expected: + raise ValueError("unrelated_configuration_change") + return source + + +def main(): + """Validate staged configuration with Ananke, then retain a root-only rollback.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--config", type=Path, default=Path("/etc/ananke/ananke.yaml")) + parser.add_argument("--apply", action="store_true") + args = parser.parse_args() + if os.geteuid() != 0: + raise SystemExit("Run as root") + source = args.config.read_text() + original_stat = args.config.stat() + revised = updated_config(source) + if revised == source: + print("Node-access configuration is current") + return + if not args.apply: + print("Node-access configuration needs updating; use --apply") + return + fd, name = tempfile.mkstemp(prefix=".node-access-", dir=args.config.parent) + staged = Path(name) + try: + with os.fdopen(fd, "w") as stream: + stream.write(revised) + checked = subprocess.run(["/usr/local/bin/ananke", "status", "--config", name], + capture_output=True, timeout=45) + if checked.returncode: + raise SystemExit("Ananke rejected staged configuration; original retained") + backup = Path("/var/lib/atlas-maintenance/ananke-access-before-20261004") + backup.mkdir(parents=True, exist_ok=True, mode=0o700) + target = backup / "ananke.yaml" + if not target.exists(): + shutil.copy2(args.config, target) + target.chmod(0o600) + os.chown(staged, original_stat.st_uid, original_stat.st_gid) + staged.chmod(stat.S_IMODE(original_stat.st_mode)) + staged.replace(args.config) + print("Updated node access; restart Ananke after verifying synchronized secrets") + finally: + staged.unlink(missing_ok=True) + + +if __name__ == "__main__": + main() diff --git a/services/maintenance/bootstrap/secretproviderclass.yaml b/services/maintenance/bootstrap/secretproviderclass.yaml index 3dafec61..afc75c15 100644 --- a/services/maintenance/bootstrap/secretproviderclass.yaml +++ b/services/maintenance/bootstrap/secretproviderclass.yaml @@ -25,6 +25,58 @@ spec: - objectName: "soteria-restic__AWS_ENDPOINTS" secretPath: "kv/data/atlas/shared/soteria-restic" secretKey: "AWS_ENDPOINTS" + - objectName: "ananke-sudo-titan-04" + secretPath: "kv/data/atlas/nodes/titan-04" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-07" + secretPath: "kv/data/atlas/nodes/titan-07" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-08" + secretPath: "kv/data/atlas/nodes/titan-08" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-11" + secretPath: "kv/data/atlas/nodes/titan-11" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-12" + secretPath: "kv/data/atlas/nodes/titan-12" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-13" + secretPath: "kv/data/atlas/nodes/titan-13" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-14" + secretPath: "kv/data/atlas/nodes/titan-14" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-15" + secretPath: "kv/data/atlas/nodes/titan-15" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-17" + secretPath: "kv/data/atlas/nodes/titan-17" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-18" + secretPath: "kv/data/atlas/nodes/titan-18" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-19" + secretPath: "kv/data/atlas/nodes/titan-19" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-20" + secretPath: "kv/data/atlas/nodes/titan-20" + secretKey: "atlas_password" + filePermission: 256 + - objectName: "ananke-sudo-titan-21" + secretPath: "kv/data/atlas/nodes/titan-21" + secretKey: "atlas_password" + filePermission: 256 secretObjects: - secretName: harbor-regcred type: kubernetes.io/dockerconfigjson @@ -42,3 +94,68 @@ spec: key: RESTIC_PASSWORD - objectName: soteria-restic__AWS_ENDPOINTS key: AWS_ENDPOINTS + - secretName: ananke-sudo-titan-04 + type: Opaque + data: + - objectName: ananke-sudo-titan-04 + key: password + - secretName: ananke-sudo-titan-07 + type: Opaque + data: + - objectName: ananke-sudo-titan-07 + key: password + - secretName: ananke-sudo-titan-08 + type: Opaque + data: + - objectName: ananke-sudo-titan-08 + key: password + - secretName: ananke-sudo-titan-11 + type: Opaque + data: + - objectName: ananke-sudo-titan-11 + key: password + - secretName: ananke-sudo-titan-12 + type: Opaque + data: + - objectName: ananke-sudo-titan-12 + key: password + - secretName: ananke-sudo-titan-13 + type: Opaque + data: + - objectName: ananke-sudo-titan-13 + key: password + - secretName: ananke-sudo-titan-14 + type: Opaque + data: + - objectName: ananke-sudo-titan-14 + key: password + - secretName: ananke-sudo-titan-15 + type: Opaque + data: + - objectName: ananke-sudo-titan-15 + key: password + - secretName: ananke-sudo-titan-17 + type: Opaque + data: + - objectName: ananke-sudo-titan-17 + key: password + - secretName: ananke-sudo-titan-18 + type: Opaque + data: + - objectName: ananke-sudo-titan-18 + key: password + - secretName: ananke-sudo-titan-19 + type: Opaque + data: + - objectName: ananke-sudo-titan-19 + key: password + - secretName: ananke-sudo-titan-20 + type: Opaque + data: + - objectName: ananke-sudo-titan-20 + key: password + - secretName: ananke-sudo-titan-21 + type: Opaque + data: + - objectName: ananke-sudo-titan-21 + key: password diff --git a/services/maintenance/bootstrap/vault-sync-deployment.yaml b/services/maintenance/bootstrap/vault-sync-deployment.yaml index 3c451bf5..b63f30f5 100644 --- a/services/maintenance/bootstrap/vault-sync-deployment.yaml +++ b/services/maintenance/bootstrap/vault-sync-deployment.yaml @@ -14,7 +14,7 @@ spec: labels: app: maintenance-vault-sync annotations: - maintenance.bstein.dev/restart-at: "2026-04-13T05:57:00Z" + maintenance.bstein.dev/credential-schema: "node-sudo-v1" spec: nodeSelector: node-role.kubernetes.io/worker: "true" @@ -33,6 +33,12 @@ spec: command: ["/bin/sh", "-c"] args: - "sleep infinity" + resources: + requests: + cpu: 5m + memory: 16Mi + limits: + memory: 64Mi volumeMounts: - name: vault-secrets mountPath: /vault/secrets