Dev #72
No files matched your search
@@ -38,6 +38,7 @@ NAME_PREFIX = "aegis-it-"
|
|||||||
GITEA_PORT = 3000
|
GITEA_PORT = 3000
|
||||||
ADMIN_LOGIN = "aegis-admin"
|
ADMIN_LOGIN = "aegis-admin"
|
||||||
ADMIN_EMAIL = "aegis-admin@example.test"
|
ADMIN_EMAIL = "aegis-admin@example.test"
|
||||||
|
ADMIN_PASSWORD_ENV = "GITEA_ADMIN_PASSWORD"
|
||||||
READY_TIMEOUT_SECONDS = 90.0
|
READY_TIMEOUT_SECONDS = 90.0
|
||||||
NETWORK_MODE_ENV = "AEGIS_IT_NETWORK_MODE"
|
NETWORK_MODE_ENV = "AEGIS_IT_NETWORK_MODE"
|
||||||
HTTP_TIMEOUT_SECONDS = 30.0
|
HTTP_TIMEOUT_SECONDS = 30.0
|
||||||
@@ -53,12 +54,16 @@ class HarnessError(RuntimeError):
|
|||||||
|
|
||||||
|
|
||||||
def run_docker(
|
def run_docker(
|
||||||
*args: str, check: bool = True, timeout: float = 120.0
|
*args: str,
|
||||||
|
check: bool = True,
|
||||||
|
timeout: float = 120.0,
|
||||||
|
env: Mapping[str, str] | None = None,
|
||||||
) -> subprocess.CompletedProcess[str]:
|
) -> subprocess.CompletedProcess[str]:
|
||||||
"""Run ``docker <args>`` and return the completed process.
|
"""Run ``docker <args>`` and return the completed process.
|
||||||
|
|
||||||
On failure the error names only the first two arguments (never the full
|
Secrets must not go into ``args`` (argv is visible to every local user): pass them
|
||||||
argv, which may carry a password) plus the command's stderr.
|
in ``env`` and forward them with ``docker exec -e NAME``. On failure the error names
|
||||||
|
only the first two arguments plus the command's stderr.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
@@ -67,6 +72,7 @@ def run_docker(
|
|||||||
text=True,
|
text=True,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
check=False,
|
check=False,
|
||||||
|
env={**os.environ, **env} if env else None,
|
||||||
)
|
)
|
||||||
except (FileNotFoundError, PermissionError) as exc:
|
except (FileNotFoundError, PermissionError) as exc:
|
||||||
raise HarnessError("the 'docker' CLI was not found or is not executable") from exc
|
raise HarnessError("the 'docker' CLI was not found or is not executable") from exc
|
||||||
@@ -480,23 +486,24 @@ def start_gitea() -> GiteaInstance:
|
|||||||
res.attached_container = job_container
|
res.attached_container = job_container
|
||||||
_wait_ready(base_url, res.container)
|
_wait_ready(base_url, res.container)
|
||||||
admin_password = secrets.token_urlsafe(24)
|
admin_password = secrets.token_urlsafe(24)
|
||||||
|
# The password travels in the environment of the docker CLI process and is forwarded
|
||||||
|
# with ``-e NAME`` (no value), so it never appears in any argv on the host. Inside
|
||||||
|
# the container it is read from the shell environment, not from the command line.
|
||||||
run_docker(
|
run_docker(
|
||||||
"exec",
|
"exec",
|
||||||
|
"-e",
|
||||||
|
ADMIN_PASSWORD_ENV,
|
||||||
"-u",
|
"-u",
|
||||||
"git",
|
"git",
|
||||||
res.container,
|
res.container,
|
||||||
"gitea",
|
"sh",
|
||||||
"admin",
|
"-c",
|
||||||
"user",
|
'exec gitea admin user create --admin --username "$1" --email "$2" '
|
||||||
"create",
|
f'--password "${ADMIN_PASSWORD_ENV}" --must-change-password=false',
|
||||||
"--admin",
|
"sh",
|
||||||
"--username",
|
|
||||||
ADMIN_LOGIN,
|
ADMIN_LOGIN,
|
||||||
"--password",
|
|
||||||
admin_password,
|
|
||||||
"--email",
|
|
||||||
ADMIN_EMAIL,
|
ADMIN_EMAIL,
|
||||||
"--must-change-password=false",
|
env={ADMIN_PASSWORD_ENV: admin_password},
|
||||||
)
|
)
|
||||||
instance = GiteaInstance(
|
instance = GiteaInstance(
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
|
|||||||
@@ -157,3 +157,27 @@ def test_container_id_candidates_fall_back_to_proc(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
def test_container_id_candidates_tolerate_missing_files(tmp_path: Path) -> None:
|
def test_container_id_candidates_tolerate_missing_files(tmp_path: Path) -> None:
|
||||||
assert harness.container_id_candidates({}, proc_root=tmp_path) == []
|
assert harness.container_id_candidates({}, proc_root=tmp_path) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_docker_passes_secrets_through_the_environment_not_argv(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
seen: dict[str, object] = {}
|
||||||
|
|
||||||
|
def fake_run(argv: list[str], **kwargs: object) -> object:
|
||||||
|
seen["argv"] = argv
|
||||||
|
seen["env"] = kwargs.get("env")
|
||||||
|
|
||||||
|
class Done:
|
||||||
|
returncode = 0
|
||||||
|
stdout = ""
|
||||||
|
stderr = ""
|
||||||
|
|
||||||
|
return Done()
|
||||||
|
|
||||||
|
monkeypatch.setattr(harness.subprocess, "run", fake_run)
|
||||||
|
harness.run_docker("exec", "-e", "SECRET_VAR", "box", env={"SECRET_VAR": "s3cret-value"})
|
||||||
|
|
||||||
|
assert "s3cret-value" not in " ".join(seen["argv"]) # type: ignore[call-overload]
|
||||||
|
assert seen["env"]["SECRET_VAR"] == "s3cret-value" # type: ignore[index]
|
||||||
|
assert "PATH" in seen["env"] # type: ignore[operator]
|
||||||
Reference in new issue
Block a user