recovery: wire Vault passwords to existing Ananke host actions
This commit is contained in:
parent
36c7e76e56
commit
886dbeee10
89
scripts/configure_ananke_node_access.py
Executable file
89
scripts/configure_ananke_node_access.py
Executable 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()
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user