recovery: wire Vault passwords to existing Ananke host actions

This commit is contained in:
jenkins 2026-10-04 00:45:33 -05:00
parent 36c7e76e56
commit 886dbeee10
3 changed files with 213 additions and 1 deletions

View File

@ -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()

View File

@ -25,6 +25,58 @@ spec:
- objectName: "soteria-restic__AWS_ENDPOINTS" - objectName: "soteria-restic__AWS_ENDPOINTS"
secretPath: "kv/data/atlas/shared/soteria-restic" secretPath: "kv/data/atlas/shared/soteria-restic"
secretKey: "AWS_ENDPOINTS" 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: secretObjects:
- secretName: harbor-regcred - secretName: harbor-regcred
type: kubernetes.io/dockerconfigjson type: kubernetes.io/dockerconfigjson
@ -42,3 +94,68 @@ spec:
key: RESTIC_PASSWORD key: RESTIC_PASSWORD
- objectName: soteria-restic__AWS_ENDPOINTS - objectName: soteria-restic__AWS_ENDPOINTS
key: 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

View File

@ -14,7 +14,7 @@ spec:
labels: labels:
app: maintenance-vault-sync app: maintenance-vault-sync
annotations: annotations:
maintenance.bstein.dev/restart-at: "2026-04-13T05:57:00Z" maintenance.bstein.dev/credential-schema: "node-sudo-v1"
spec: spec:
nodeSelector: nodeSelector:
node-role.kubernetes.io/worker: "true" node-role.kubernetes.io/worker: "true"
@ -33,6 +33,12 @@ spec:
command: ["/bin/sh", "-c"] command: ["/bin/sh", "-c"]
args: args:
- "sleep infinity" - "sleep infinity"
resources:
requests:
cpu: 5m
memory: 16Mi
limits:
memory: 64Mi
volumeMounts: volumeMounts:
- name: vault-secrets - name: vault-secrets
mountPath: /vault/secrets mountPath: /vault/secrets