mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
fix(inventory): keep vault tags when provisioning the network mode
apply_network_mode_from_env loaded the whole host_vars file through
utils.cache.yaml, set one scalar and wrote everything back. That loader's
!vault constructor returns a plain string, so every credential in the file
was re-emitted untagged and Ansible handed the raw ciphertext to the roles
instead of the secret. It only runs when the row sets `network`, which is
every multi-network round.
Run 40032069040 on the fork shows what that costs: the generated host_vars
of a failing job carries 0 `!vault` tags against 56 `$ANSIBLE_VAULT`
headers, mariadb then refuses root with 1045 on a freshly initialised
database, and the openldap bind fails with result 49.
08680e933d fixed the same class in tor_node.py, the sibling writer in this
package. This one writes through the round-trip helpers too.
The existing test did not cover the case, which is why the sibling fix did
not stop it: test_preserves_existing_vault_credentials now fails against
the previous writer with
`'!vault |' not found in "... root_password: '$ANSIBLE_VAULT;1.1;AES256`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0f27d3f8f5
commit
c88647b3a1
2 files changed
+25
-6
No files matched your search
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from utils.networks.reachability import NODE_MODES
|
||||
|
||||
from .yaml_io import dump_yaml, load_yaml
|
||||
from .ruamel_io import dump_document, load_document
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
@@ -33,9 +33,7 @@ def apply_network_mode_from_env(host_vars_file: Path) -> None:
|
||||
raise SystemExit(
|
||||
f"network={raw!r} is unknown; expected {', '.join(NODE_MODES)}"
|
||||
)
|
||||
data = load_yaml(host_vars_file) if host_vars_file.exists() else {}
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
data["NETWORK_MODE"] = raw
|
||||
dump_yaml(host_vars_file, data)
|
||||
document = load_document(host_vars_file)
|
||||
document["NETWORK_MODE"] = raw
|
||||
dump_document(host_vars_file, document)
|
||||
print(f"[INFO] network={raw!r} → NETWORK_MODE")
|
||||
@@ -4,11 +4,22 @@ import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import utils.handler.vault # noqa: F401 registers the !vault YAML constructor
|
||||
from cli.administration.inventory.provision.network_mode import (
|
||||
apply_network_mode_from_env,
|
||||
)
|
||||
from cli.administration.inventory.provision.yaml_io import load_yaml
|
||||
|
||||
_VAULT_CRED = """\
|
||||
applications:
|
||||
svc-db-mariadb:
|
||||
credentials:
|
||||
root_password: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
33323532656330393531653863306437306438353765363637326435636235396237
|
||||
3435626336306666643833633030333736316234623365610a303233663130636566
|
||||
"""
|
||||
|
||||
|
||||
class TestApplyNetworkModeFromEnv(unittest.TestCase):
|
||||
def _apply(self, network: str | None, existing: str = "") -> Path:
|
||||
@@ -27,6 +38,16 @@ class TestApplyNetworkModeFromEnv(unittest.TestCase):
|
||||
self.assertEqual(load_yaml(hv)["NETWORK_MODE"], "tor")
|
||||
self.assertEqual(load_yaml(hv)["applications"], {})
|
||||
|
||||
def test_preserves_existing_vault_credentials(self) -> None:
|
||||
"""Writing NETWORK_MODE must not strip !vault tags from credentials that
|
||||
already live in the same host_vars file — otherwise the ciphertext is
|
||||
re-emitted as an untagged plain string and every consumer receives it
|
||||
verbatim instead of the secret."""
|
||||
hv = self._apply("multi", _VAULT_CRED)
|
||||
out = hv.read_text(encoding="utf-8") # nocheck: cache-read -- tempdir
|
||||
self.assertIn("!vault |", out)
|
||||
self.assertNotIn("root_password: '$ANSIBLE_VAULT", out)
|
||||
|
||||
def test_noop_without_the_variable(self) -> None:
|
||||
self.assertFalse(self._apply(None).exists())
|
||||
|
||||
|
||||
Reference in new issue
Block a user