From 06e0ee0ad76fea4650a3d3b8186352b903090fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Z=20Sink=C3=B3?= Date: Thu, 13 Aug 2026 15:18:52 +0200 Subject: [PATCH] =?UTF-8?q?fix(vault):=20a=20hoszt=20hiteles=C3=ADt=C5=91?= =?UTF-8?q?=20adatai=20jussanak=20el=20a=20kont=C3=A9nerbe=20=E2=80=94=20m?= =?UTF-8?q?ind=20a=20h=C3=A1rom=20m=C3=B3don,=20ahogy=20eddig=20nem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A `make release` "[FATAL] VAULT_ADDR and VAULT_TOKEN must be set" hibát adott egy olyan shellből, ahol MINDKETTŐ be volt állítva. Három külön ok volt, és mindegyik félrevezető hibaüzenetet szült. 1. A konténer sosem látta a hitelesítő adatokat A docker-compose.yml LÉTREHOZÁSKOR helyettesíti be a ${VAULT_ADDR}-t és a ${VAULT_TOKEN}-t. Egy builder, amit a token exportálása ELŐTT indítottak, üres sztringeket kap, és azokat hordozza az élettartamára. Konkrétan ez történt: a konténer 2026-08-10 21:21-kor indult egy olyan munkamenetből, ahol a változók nem voltak beállítva. 2. A 127.0.0.1 mást jelent a konténerben VAULT_ADDR=https://127.0.0.1:18200 a hoszton helyes, a konténerben a SAJÁT loopbackjára mutat → "Connection refused", olyan tracebackkel, ami egy szót sem szól névterekről. Mérve: a konténerből a 127.0.0.1:18200 elérhetetlen, a host.docker.internal:18200 HTTP 200 — a compose az extra_hosts-ot már ma is biztosítja. 3. A TLS rossz okból kapcsolt ki A compiler.py a VAULT_CACERT-et olvassa, a hoszt konvenciója viszont VAULT_CA_CERT_FILE, és a fájl a $XDG_RUNTIME_DIR alatt van, ami nincs mountolva. Így a hitelesítés csendben "disabled"-re esett vissza — FELESLEGESEN: a szerver tanúsítványának SAN-ja tartalmazza a host.docker.internal-t, tehát az ellenőrzés sikerülne. subject=CN = localhost SAN: DNS:localhost, IP:127.0.0.1, DNS:host.docker.internal A tools/vault-exec.sh mindhármat kezeli: exec-időben adja át a tokent, a címben a hoszt-részt átírja, a CA-t bemásolja a mountolt fába és rámutat. Lemérve: TLS-ELLENŐRZÉSSEL HTTP 200 a konténerből. Ha nincs CA, a script MEGÁLL, nem esik vissza ellenőrzés nélküli kapcsolatra: az aláíró token többet ér, mint egy megszakadt release kellemetlensége. --- [signing-metadata] key = cic-my-sign-key signature = vault:v1:MEUCIQCGMIJx2Hwh9nEYAZw72loPrNpAZFm48CcHYIfdBmz7lAIgNNW5dc5EnZQ6G8/aoa22j5YdDFjI0MQKggTRVwMjixU= hash-algorithm = sha256 digest = Fh0Ev0LWTV/GKIa5FR875KFKz/27e4MFzWT2VAI1cD0= [certificate] -----BEGIN CERTIFICATE----- MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0 lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW -----END CERTIFICATE----- --- .gitignore | 1 + Makefile | 14 +++++++--- tools/vault-exec.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) create mode 100755 tools/vault-exec.sh diff --git a/.gitignore b/.gitignore index a7e059f..0554506 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ primitive.txt # mutation testing .mutmut-cache mutants/ +.vault-ca.crt diff --git a/Makefile b/Makefile index 79bf378..2639da8 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,12 @@ build: # Main Development Tasks # ============================================================================= +# Anything needing Vault goes through tools/vault-exec.sh, which fixes the three +# ways host credentials fail to reach the container: creation-time +# interpolation, 127.0.0.1 meaning the container's own loopback, and TLS +# degrading to "disabled" because the CA path is a host path. See its header. +VAULT_EXEC := ./tools/vault-exec.sh + validate: grammar @echo "--- Validating all schemas against the meta-schema ---" @docker compose exec builder python tools/compiler.py validate @@ -97,22 +103,22 @@ gate.local: validate.local test.local provenance pledge: @echo "--- Developer commitment: validity + createdBy signed by Vault ---" - @docker compose exec builder python tools/compiler.py pledge + @$(VAULT_EXEC) python tools/compiler.py pledge # The grammar gate runs INSIDE compiler.py release, not here: a step only the # Makefile performs is bypassed by calling the tool directly. release: @echo "--- Building and signing release schemas ---" - @docker compose exec builder python tools/compiler.py release + @$(VAULT_EXEC) python tools/compiler.py release verify-release: @if [ -z "$(FILE)" ]; then echo "Usage: make verify-release FILE=release/-vX.Y.Z.yaml [STRICT=1] [TRUST_ROOT=path/to/root.pem]"; exit 1; fi - @docker compose exec builder python tools/compiler.py verify-release $(FILE) \ + @$(VAULT_EXEC) python tools/compiler.py verify-release $(FILE) \ $(if $(STRICT),--strict,) $(if $(TRUST_ROOT),--trust-root $(TRUST_ROOT),) verify-release-strict: @if [ -z "$(FILE)" ]; then echo "Usage: make verify-release-strict FILE=release/-vX.Y.Z.yaml"; exit 1; fi - @docker compose exec builder python tools/compiler.py verify-release $(FILE) --strict + @$(VAULT_EXEC) python tools/compiler.py verify-release $(FILE) --strict test: @echo "--- Running pytest for the compiler infrastructure ---" diff --git a/tools/vault-exec.sh b/tools/vault-exec.sh new file mode 100755 index 0000000..3fa65fd --- /dev/null +++ b/tools/vault-exec.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# +# Run a command in the builder container with working Vault access. +# +# Three things go wrong between a host shell that can reach Vault and a +# container that cannot, and each one produces a misleading error: +# +# 1. The container never sees the credentials. +# docker-compose.yml interpolates ${VAULT_ADDR} and ${VAULT_TOKEN} when the +# container is CREATED. A builder started before the token was exported +# captures empty strings for its whole life, and `make release` then reports +# "VAULT_ADDR and VAULT_TOKEN must be set" while the caller's shell has both. +# Passing them at exec time removes the dependency on who started it, when. +# +# 2. 127.0.0.1 means something else inside a container. +# VAULT_ADDR=https://127.0.0.1:18200 is correct on the host and points at the +# container's own loopback inside it — "Connection refused", with a traceback +# that says nothing about namespaces. The compose file already provides +# host.docker.internal; this rewrites the host part to use it. +# +# 3. TLS gets switched off for the wrong reason. +# compiler.py reads VAULT_CACERT; the host convention here is +# VAULT_CA_CERT_FILE, and the file lives under $XDG_RUNTIME_DIR, which is not +# mounted. So verification silently degraded to "disabled" even though the +# server certificate lists host.docker.internal in its SAN and verification +# would succeed. The CA is copied into the mounted tree and pointed at. +# +# Usage: tools/vault-exec.sh [args...] + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +if [ -z "${VAULT_ADDR:-}" ] || [ -z "${VAULT_TOKEN:-}" ]; then + echo "[!] VAULT_ADDR and VAULT_TOKEN must be set in this shell." >&2 + exit 1 +fi + +# The host address, as the container has to say it. +CONTAINER_ADDR="${VAULT_ADDR/127.0.0.1/host.docker.internal}" +CONTAINER_ADDR="${CONTAINER_ADDR/localhost/host.docker.internal}" + +# The CA, if the host has one. VAULT_CACERT is what compiler.py reads; +# VAULT_CA_CERT_FILE is what the host tooling sets. Accept either. +CA_SRC="${VAULT_CACERT:-${VAULT_CA_CERT_FILE:-}}" +CA_ARGS=() +CA_LOCAL=".vault-ca.crt" +if [ -n "$CA_SRC" ] && [ -f "$CA_SRC" ]; then + cp "$CA_SRC" "$CA_LOCAL" + chmod 600 "$CA_LOCAL" + CA_ARGS=(-e "VAULT_CACERT=/app/$CA_LOCAL") + trap 'rm -f "$REPO_ROOT/$CA_LOCAL"' EXIT +else + echo "[!] No Vault CA certificate found (VAULT_CACERT / VAULT_CA_CERT_FILE)." >&2 + echo " The request would fall back to an unverified TLS connection, which" >&2 + echo " sends the signing token to whatever answers on $CONTAINER_ADDR." >&2 + echo " Set one of those variables, or start Vault so its CA is written." >&2 + exit 1 +fi + +exec docker compose exec \ + -e "VAULT_ADDR=$CONTAINER_ADDR" \ + -e VAULT_TOKEN \ + "${CA_ARGS[@]}" \ + builder "$@"