Перенесены секреты из .env в SM

This commit is contained in:
mi
2026-07-30 19:22:48 +03:00
parent 049c45db5c
commit e24ed9d8ef
58 changed files with 3350 additions and 1054 deletions
@@ -0,0 +1,471 @@
from __future__ import annotations
import base64
import importlib.util
import io
import json
import os
import stat
import subprocess
import sys
import tempfile
import unittest
import urllib.error
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
LOADER_PATH = ROOT / "deployment" / "secrets" / "secrets_loader.py"
LAUNCHER_PATH = ROOT / "deployment" / "secrets" / "han-secrets"
SPEC = importlib.util.spec_from_file_location("han_secrets_loader", LOADER_PATH)
assert SPEC and SPEC.loader
loader = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = loader
SPEC.loader.exec_module(loader)
class FakeResponse:
def __init__(
self, status: int, body: bytes, headers: dict[str, str] | None = None
) -> None:
self.status = status
self._body = body
self.headers = headers or {}
def __enter__(self) -> "FakeResponse":
return self
def __exit__(self, *args: Any) -> None:
return None
def read(self, amount: int) -> bytes:
return self._body[:amount]
class FakeOpener:
def __init__(self, outcomes: list[Any]) -> None:
self.outcomes = outcomes
self.requests: list[Any] = []
def open(self, request: Any, timeout: float) -> FakeResponse:
self.requests.append((request, timeout))
outcome = self.outcomes.pop(0)
if isinstance(outcome, BaseException):
raise outcome
return outcome
def identity_response(*, project_scoped: bool = True) -> FakeResponse:
token: dict[str, Any] = {
"catalog": [
{
"type": "secrets-manager",
"endpoints": [
{
"region": "ru-test",
"interface": "public",
"url": "https://secrets.example",
}
],
}
]
}
if project_scoped:
token["project"] = {"id": "project-id"}
return FakeResponse(
201,
json.dumps({"token": token}).encode(),
{"X-Subject-Token": "iam-token-do-not-log"},
)
def secret_response(value: bytes) -> FakeResponse:
return FakeResponse(
200, json.dumps({"value": base64.b64encode(value).decode()}).encode()
)
def selectel_config(runtime_dir: Path) -> dict[str, Any]:
return {
"version": 1,
"mode": "selectel",
"runtime_dir": str(runtime_dir),
"http": {"timeout_seconds": 2, "retries": 1, "max_response_bytes": 4096},
"selectel": {
"account_id": "123456",
"username": "reader",
"project_name": "production",
"region": "ru-test",
"password_file": "selectel-password",
},
"secrets": {
"DATABASE_URL": {
"remote": "han/database-url",
"consumers": ["api", "migration"],
"max_bytes": 512,
},
"SHARED_TOKEN": {
"remote": "han/shared-token",
"consumers": ["api"],
"max_bytes": 128,
},
},
}
def write_json(path: Path, value: dict[str, Any]) -> None:
path.write_text(json.dumps(value), encoding="utf-8")
def make_private(path: Path) -> None:
os.chmod(path, 0o600)
class SecretsLoaderTests(unittest.TestCase):
def test_decodes_current_secret_from_nested_selectel_version(self) -> None:
encoded = base64.b64encode(b"current-secret").decode()
value = loader.decode_secret(
{"name": "DATABASE_URL", "version": {"version_id": 1, "value": encoded}},
"DATABASE_URL",
1024,
)
self.assertEqual(value, b"current-secret")
def test_empty_literal_never_requires_provider_or_fallback_value(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
fallback = root / "fallback.env"
fallback.write_text("REQUIRED_TOKEN=required-value\n", encoding="utf-8")
make_private(fallback)
config = {
"version": 1,
"mode": "file",
"runtime_dir": str(root / "run"),
"file": {"path": str(fallback)},
"secrets": {
"REQUIRED_TOKEN": {
"remote": "provider-token",
"consumers": ["service"],
},
"OPTIONAL_HEADER": {
"literal": "",
"consumers": ["service"],
},
},
}
config_path = root / "file.json"
write_json(config_path, config)
loader.run(config_path, environ={})
self.assertEqual((root / "run" / "OPTIONAL_HEADER").read_bytes(), b"")
self.assertIn(
'OPTIONAL_HEADER=""',
(root / "run" / "service.env").read_text(encoding="utf-8"),
)
def test_launcher_uses_public_source_switch_without_exporting_values(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
runtime = root / "runtime"
fallback = root / "fallback.env"
fallback.write_text("TEST_SECRET=canary-secret-value\n", encoding="utf-8")
make_private(fallback)
public = root / ".env"
public.write_text(
"SECRETS_SOURCE=file\nAPP_ENV=production\n", encoding="utf-8"
)
loader_config = root / "production.file.json"
write_json(
loader_config,
{
"version": 1,
"mode": "file",
"runtime_dir": str(runtime),
"file": {"path": str(fallback)},
"secrets": {
"TEST_SECRET": {
"remote": "unused-in-file-mode",
"consumers": ["test-service"],
}
},
},
)
probe = (
"import json,os,pathlib;"
"p=os.environ['TEST_SECRET_FILE'];"
"print(json.dumps({'raw':os.environ.get('TEST_SECRET'),"
"'value':pathlib.Path(p).read_text()}))"
)
result = subprocess.run(
[
sys.executable,
str(LAUNCHER_PATH),
"run",
"--config",
str(public),
"--loader-config",
str(loader_config),
"--",
sys.executable,
"-c",
probe,
],
text=True,
capture_output=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
payload = json.loads(result.stdout)
self.assertIsNone(payload["raw"])
self.assertEqual(payload["value"], "canary-secret-value")
self.assertEqual(
(runtime / "manifest").read_text(encoding="utf-8").strip(),
f"TEST_SECRET={(runtime / 'TEST_SECRET').resolve()}",
)
def test_selectel_flow_uses_project_scope_catalog_and_per_service_files(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
credentials = root / "credentials"
credentials.mkdir()
(credentials / "selectel-password").write_text(
"service-user-password\n", encoding="utf-8"
)
make_private(credentials / "selectel-password")
runtime = root / "run"
config_path = root / "config.json"
write_json(config_path, selectel_config(runtime))
opener = FakeOpener(
[
identity_response(),
secret_response(b"postgresql://user:password@db/app"),
secret_response(b'token with spaces and "quotes"'),
]
)
def factory(**kwargs: Any) -> Any:
return loader.HTTPClient(
**kwargs, opener=opener, sleeper=lambda _: None, jitter=lambda: 0
)
consumers = loader.run(
config_path,
environ={"CREDENTIALS_DIRECTORY": str(credentials)},
client_factory=factory,
)
self.assertEqual(consumers, ["api", "migration"])
auth_request = opener.requests[0][0]
auth_payload = json.loads(auth_request.data)
self.assertEqual(
auth_payload["auth"]["scope"]["project"]["name"], "production"
)
self.assertEqual(
auth_payload["auth"]["identity"]["password"]["user"]["domain"]["name"],
"123456",
)
self.assertEqual(
opener.requests[1][0].get_header("X-auth-token"),
"iam-token-do-not-log",
)
self.assertEqual(
opener.requests[1][0].full_url,
"https://secrets.example/v1/han%2Fdatabase-url",
)
api_text = (runtime / "api.env").read_text(encoding="utf-8")
migration_text = (runtime / "migration.env").read_text(encoding="utf-8")
self.assertIn('DATABASE_URL="postgresql://user:password@db/app"', api_text)
self.assertIn(
'SHARED_TOKEN="token with spaces and \\"quotes\\""', api_text
)
self.assertNotIn("SHARED_TOKEN", migration_text)
if os.name != "nt":
self.assertEqual(stat.S_IMODE((runtime / "api.env").stat().st_mode), 0o600)
self.assertEqual(stat.S_IMODE(runtime.stat().st_mode), 0o700)
def test_retry_uses_jitter_and_then_succeeds(self) -> None:
opener = FakeOpener(
[urllib.error.URLError("temporary"), FakeResponse(200, b"{}")]
)
sleeps: list[float] = []
client = loader.HTTPClient(
timeout=1,
retries=1,
max_response_bytes=1024,
opener=opener,
sleeper=sleeps.append,
jitter=lambda: 0.25,
)
result = client.request(
"GET", "https://provider.example/v1/key", expected=frozenset({200})
)
self.assertEqual(result.status, 200)
self.assertEqual(sleeps, [0.1875])
def test_paired_canonical_names_fetch_provider_version_once(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
credentials = root / "credentials"
credentials.mkdir()
credential = credentials / "selectel-password"
credential.write_text("password", encoding="utf-8")
make_private(credential)
config = selectel_config(root / "run")
config["secrets"]["SHARED_TOKEN"]["remote"] = "han/database-url"
config_path = root / "config.json"
write_json(config_path, config)
opener = FakeOpener([identity_response(), secret_response(b"same-value")])
def factory(**kwargs: Any) -> Any:
return loader.HTTPClient(**kwargs, opener=opener)
loader.run(
config_path,
environ={"CREDENTIALS_DIRECTORY": str(credentials)},
client_factory=factory,
)
self.assertEqual(len(opener.requests), 2)
output = (root / "run" / "api.env").read_text(encoding="utf-8")
self.assertIn('DATABASE_URL="same-value"', output)
self.assertIn('SHARED_TOKEN="same-value"', output)
def test_http_error_is_redacted_and_body_is_not_read(self) -> None:
leaked = b"postgresql://admin:secret@db/app iam-token response-body"
class ExplodingBody(io.BytesIO):
def read(self, *args: Any, **kwargs: Any) -> bytes:
raise AssertionError("HTTP error body must not be read")
error = urllib.error.HTTPError(
"https://provider.example/v1/key",
403,
"body contains a secret",
{},
ExplodingBody(leaked),
)
client = loader.HTTPClient(
timeout=1,
retries=0,
max_response_bytes=1024,
opener=FakeOpener([error]),
)
with self.assertRaises(loader.LoaderError) as caught:
client.request(
"GET",
"https://provider.example/v1/key",
headers={"X-Auth-Token": "iam-token"},
expected=frozenset({200}),
)
message = str(caught.exception)
self.assertEqual(message, "provider request failed with HTTP 403")
for forbidden in ("secret", "iam-token", "response-body", "postgresql://"):
self.assertNotIn(forbidden, message)
def test_unscoped_token_fails_without_replacing_existing_output(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
credentials = root / "credentials"
credentials.mkdir()
(credentials / "selectel-password").write_text("password", encoding="utf-8")
make_private(credentials / "selectel-password")
runtime = root / "run"
runtime.mkdir()
existing = runtime / "api.env"
existing.write_text('DATABASE_URL="old-value"\n', encoding="utf-8")
config_path = root / "config.json"
write_json(config_path, selectel_config(runtime))
opener = FakeOpener([identity_response(project_scoped=False)])
def factory(**kwargs: Any) -> Any:
return loader.HTTPClient(**kwargs, opener=opener)
with self.assertRaisesRegex(loader.LoaderError, "not project-scoped"):
loader.run(
config_path,
environ={"CREDENTIALS_DIRECTORY": str(credentials)},
client_factory=factory,
)
self.assertEqual(
existing.read_text(encoding="utf-8"),
'DATABASE_URL="old-value"\n',
)
def test_invalid_base64_and_size_limit_fail_closed(self) -> None:
with self.assertRaisesRegex(loader.LoaderError, "invalid base64"):
loader.decode_secret({"value": "not-base64!"}, "TOKEN", 128)
with self.assertRaisesRegex(loader.LoaderError, "configured limit"):
loader.decode_secret(
{"value": base64.b64encode(b"too-long").decode()}, "TOKEN", 3
)
with self.assertRaisesRegex(loader.LoaderError, "dotenv"):
loader.decode_secret(
{"value": base64.b64encode(b"line1\nline2").decode()}, "TOKEN", 128
)
def test_file_mode_is_explicit_strict_and_narrow(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
source = root / "fallback.env"
source.write_text(
"# exact recovery set\n"
'DATABASE_URL="postgresql://user:pass@db/app"\n'
"SHARED_TOKEN='literal value'\n",
encoding="utf-8",
)
make_private(source)
runtime = root / "run"
config = selectel_config(runtime)
config["mode"] = "file"
config.pop("selectel")
config.pop("http")
config["file"] = {"path": str(source)}
config_path = root / "config.json"
write_json(config_path, config)
loader.run(config_path, environ={})
output = (runtime / "api.env").read_text(encoding="utf-8")
self.assertIn("literal value", output)
self.assertNotIn("selectel", output)
source.write_text(
"DATABASE_URL=ok\nSHARED_TOKEN=ok\nUNDECLARED=leak\n",
encoding="utf-8",
)
with self.assertRaisesRegex(loader.LoaderError, "undeclared key"):
loader.run(config_path, environ={})
def test_selectel_failure_never_falls_back_to_file_section(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
config = selectel_config(root / "run")
config["file"] = {"path": str(root / "fallback.env")}
config_path = root / "config.json"
write_json(config_path, config)
with self.assertRaisesRegex(loader.LoaderError, "forbidden"):
loader.run(config_path, environ={})
def test_relative_credential_requires_systemd_directory(self) -> None:
selectel = {"password_file": "credential"}
with self.assertRaisesRegex(loader.LoaderError, "CREDENTIALS_DIRECTORY"):
loader.credential_value(selectel, {})
def test_response_content_length_limit_is_enforced(self) -> None:
client = loader.HTTPClient(
timeout=1,
retries=0,
max_response_bytes=10,
opener=FakeOpener(
[FakeResponse(200, b"{}", {"Content-Length": "100"})]
),
)
with self.assertRaisesRegex(loader.LoaderError, "exceeds"):
client.request(
"GET", "https://provider.example/v1/key", expected=frozenset({200})
)
if __name__ == "__main__":
unittest.main()