Publish curated Unraid-AppOps-Gateway source from 84f2a9958b2bdf1c1c13553925026d75f32703c3
This commit is contained in:
1 parent
8dd4b40252
commit
5d598db0df
14 files changed
+2765
-46
No files matched your search
@@ -264,6 +264,19 @@ class _DockerComposeBoundary:
|
||||
config_hash="new-config-hash",
|
||||
)
|
||||
self.live["Config"]["Image"] = service["image"]
|
||||
environment = service.get("environment") or {}
|
||||
if isinstance(environment, dict):
|
||||
current_env = {}
|
||||
for entry in self.live["Config"]["Env"]:
|
||||
name, value = entry.split("=", 1)
|
||||
current_env[name] = value
|
||||
current_env.update({
|
||||
str(name): str(value).replace("$$", "$")
|
||||
for name, value in environment.items()
|
||||
})
|
||||
self.live["Config"]["Env"] = [
|
||||
f"{name}={value}" for name, value in current_env.items()
|
||||
]
|
||||
return ""
|
||||
raise AssertionError(f"unexpected Docker command: {command}")
|
||||
|
||||
@@ -374,6 +387,107 @@ def test_production_observe_rejects_runtime_drift_before_baseline_capture(
|
||||
with pytest.raises(module.RegisteredLegacyAdoptionError):
|
||||
runtime.observe(REPOSITORY, project, receipt_key=KEY)
|
||||
|
||||
|
||||
def test_capture_observer_allows_only_declared_environment_drift_and_reports_names(
|
||||
production_runtime, module
|
||||
):
|
||||
runtime, boundary, project, model = production_runtime
|
||||
model["services"]["quiz-server"]["environment"]["KNOWLEDGE_OPTIMIZER_MODEL"] = "gpt-5.5"
|
||||
boundary.live["Config"]["Env"].append("KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna")
|
||||
|
||||
with pytest.raises(module.RegisteredLegacyAdoptionError):
|
||||
runtime.observe(REPOSITORY, project, receipt_key=KEY)
|
||||
|
||||
observed = runtime.observe_for_baseline_capture(
|
||||
REPOSITORY, project, receipt_key=KEY,
|
||||
)
|
||||
assert observed["status"] == "observed"
|
||||
assert observed["environmentMismatchKeys"] == [
|
||||
"quiz-server/KNOWLEDGE_OPTIMIZER_MODEL",
|
||||
]
|
||||
assert observed["capturedEnvironmentKeys"] == [
|
||||
"quiz-server/KNOWLEDGE_OPTIMIZER_MODEL",
|
||||
]
|
||||
assert observed["sourceFingerprint"]
|
||||
assert observed["runtimeFingerprint"]
|
||||
assert observed["artifacts"]["containers"][CONTAINER_NAME]["container_id"] == "d" * 64
|
||||
registry_project = copy.deepcopy(project)
|
||||
registry_project["source_dir"] = "/srv/example/appdata/quiz-monitor/source"
|
||||
|
||||
class _CaptureOnlyRuntime:
|
||||
def observe_for_baseline_capture(self, *_args, **_kwargs):
|
||||
return copy.deepcopy(observed)
|
||||
|
||||
public = module.observe(
|
||||
{"repository": REPOSITORY},
|
||||
registry={REPOSITORY: registry_project},
|
||||
read_state=lambda _repository, _project: ({}, b"{}\n"),
|
||||
runtime=_CaptureOnlyRuntime(),
|
||||
receipt_key=KEY,
|
||||
state_root=runtime.state_root,
|
||||
)
|
||||
assert public["containerIDs"] == {CONTAINER_NAME: "d" * 64}
|
||||
assert public["imageIDs"] == {CONTAINER_NAME: BASELINE_IMAGE}
|
||||
assert public["environmentMismatchKeys"] == observed["environmentMismatchKeys"]
|
||||
serialized = json.dumps(public)
|
||||
assert "gpt-6-luna" not in serialized
|
||||
assert "gpt-5.5" not in serialized
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"entries",
|
||||
[
|
||||
["OPENRGB_HOST=198.19.1.2", "OPENRGB_HOST=attacker-value"],
|
||||
["OPENRGB_HOST=198.19.1.2", "malformed-entry"],
|
||||
],
|
||||
)
|
||||
def test_capture_observer_rejects_duplicate_or_malformed_inspected_environment(
|
||||
production_runtime, module, entries
|
||||
):
|
||||
runtime, boundary, project, model = production_runtime
|
||||
model["services"]["quiz-server"]["environment"]["KNOWLEDGE_OPTIMIZER_MODEL"] = "gpt-5.5"
|
||||
boundary.live["Config"]["Env"].append("KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna")
|
||||
boundary.live["Config"]["Env"] = entries + [
|
||||
"QUIZ_TEMPLATE=" + TEMPLATE_VALUE,
|
||||
"KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna",
|
||||
]
|
||||
|
||||
with pytest.raises(module.RegisteredLegacyAdoptionError):
|
||||
runtime.observe_for_baseline_capture(REPOSITORY, project, receipt_key=KEY)
|
||||
|
||||
|
||||
def test_capture_observer_still_rejects_unrendered_unsafe_runtime_options(
|
||||
production_runtime, module
|
||||
):
|
||||
runtime, boundary, project, model = production_runtime
|
||||
model["services"]["quiz-server"]["environment"]["KNOWLEDGE_OPTIMIZER_MODEL"] = "gpt-5.5"
|
||||
boundary.live["Config"]["Env"].append("KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna")
|
||||
boundary.live["HostConfig"]["Privileged"] = True
|
||||
|
||||
with pytest.raises(module.RegisteredLegacyAdoptionError, match="privileged"):
|
||||
runtime.observe_for_baseline_capture(REPOSITORY, project, receipt_key=KEY)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_key,model_value,actual_entry",
|
||||
[
|
||||
(None, None, "UNDECLARED_RUNTIME_VALUE=unexpected"),
|
||||
("APPOPS_SOURCE_REVISION", "a" * 40, "APPOPS_SOURCE_REVISION=" + "b" * 40),
|
||||
],
|
||||
)
|
||||
def test_capture_observer_rejects_undeclared_and_provenance_environment_drift(
|
||||
production_runtime, module, model_key, model_value, actual_entry
|
||||
):
|
||||
runtime, boundary, project, model = production_runtime
|
||||
model["services"]["quiz-server"]["environment"]["KNOWLEDGE_OPTIMIZER_MODEL"] = "gpt-5.5"
|
||||
boundary.live["Config"]["Env"].append("KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna")
|
||||
boundary.live["Config"]["Env"].append(actual_entry)
|
||||
if model_key:
|
||||
model["services"]["quiz-server"]["environment"][model_key] = model_value
|
||||
|
||||
with pytest.raises(module.RegisteredLegacyAdoptionError):
|
||||
runtime.observe_for_baseline_capture(REPOSITORY, project, receipt_key=KEY)
|
||||
|
||||
assert boundary.compose_ups == 0
|
||||
assert not any(
|
||||
command[:3] == ["docker", "image", "tag"] for command in boundary.commands
|
||||
@@ -574,7 +688,8 @@ def _validation_api(path, *, query):
|
||||
raise AssertionError(f"unexpected validation request: {path} {query}")
|
||||
|
||||
|
||||
def _adopt_with_production_runtime(module, boundary, project, state_root, monkeypatch):
|
||||
def _adopt_with_production_runtime(module, boundary, project, state_root, monkeypatch,
|
||||
*, capture_runtime_environment=False):
|
||||
original_validate = module.validate_registered_legacy_target
|
||||
|
||||
def validate_temp_target(repository, registered_project):
|
||||
@@ -600,8 +715,23 @@ def _adopt_with_production_runtime(module, boundary, project, state_root, monkey
|
||||
|
||||
runtime = module._ProductionRuntime(state_root)
|
||||
registry = {REPOSITORY: project}
|
||||
baseline_payload = {"repository": REPOSITORY, "requestId": REQUEST_ID}
|
||||
if capture_runtime_environment:
|
||||
observed = runtime.observe_for_baseline_capture(
|
||||
REPOSITORY, project, receipt_key=KEY,
|
||||
)
|
||||
baseline_payload.update({
|
||||
"captureRuntimeEnvironment": True,
|
||||
"expectedSourceFingerprint": observed["sourceFingerprint"],
|
||||
"expectedRuntimeFingerprint": observed["runtimeFingerprint"],
|
||||
"expectedStateSha256": module._state_digest(state_bytes),
|
||||
"expectedContainerIds": {
|
||||
name: artifact["container_id"]
|
||||
for name, artifact in observed["artifacts"]["containers"].items()
|
||||
},
|
||||
})
|
||||
baseline = module.baseline(
|
||||
{"repository": REPOSITORY, "requestId": REQUEST_ID},
|
||||
baseline_payload,
|
||||
registry=registry,
|
||||
read_state=read_state,
|
||||
runtime=runtime,
|
||||
@@ -675,6 +805,75 @@ def adopted_production_runtime(production_runtime, tmp_path, monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def test_captured_environment_survives_signed_baseline_into_candidate_overlay(
|
||||
production_runtime, tmp_path, monkeypatch, module
|
||||
):
|
||||
_runtime, boundary, project, model = production_runtime
|
||||
model["services"]["quiz-server"]["environment"][
|
||||
"KNOWLEDGE_OPTIMIZER_MODEL"
|
||||
] = "gpt-5.5"
|
||||
boundary.live["Config"]["Env"].append(
|
||||
"KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna"
|
||||
)
|
||||
runtime, _registry, _read_state, _state_bytes, _snapshot = (
|
||||
_adopt_with_production_runtime(
|
||||
module, boundary, project, tmp_path, monkeypatch,
|
||||
capture_runtime_environment=True,
|
||||
)
|
||||
)
|
||||
|
||||
bound_project = _prepare_candidate_overlay(module, boundary, project, tmp_path)
|
||||
overlay_path = module.validated_compose_overlay(
|
||||
bound_project, receipt_key=KEY, state_root=tmp_path, repository=REPOSITORY,
|
||||
)
|
||||
assert overlay_path is not None
|
||||
overlay = json.loads(overlay_path.read_text(encoding="utf-8"))
|
||||
assert overlay["services"]["quiz-server"]["environment"][
|
||||
"KNOWLEDGE_OPTIMIZER_MODEL"
|
||||
] == "gpt-6-luna"
|
||||
assert runtime.observe_for_baseline_capture(
|
||||
REPOSITORY, project, receipt_key=KEY,
|
||||
)["environmentMismatchKeys"] == [
|
||||
"quiz-server/KNOWLEDGE_OPTIMIZER_MODEL",
|
||||
]
|
||||
with pytest.raises(module.RegisteredLegacyAdoptionError):
|
||||
runtime.observe(REPOSITORY, project, receipt_key=KEY)
|
||||
|
||||
|
||||
def test_captured_environment_is_restored_from_signed_old_baseline(
|
||||
production_runtime, tmp_path, monkeypatch, module
|
||||
):
|
||||
_runtime, boundary, project, model = production_runtime
|
||||
model["services"]["quiz-server"]["environment"][
|
||||
"KNOWLEDGE_OPTIMIZER_MODEL"
|
||||
] = "gpt-5.5"
|
||||
boundary.live["Config"]["Env"].append(
|
||||
"KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna"
|
||||
)
|
||||
runtime, registry, _read_state, state_bytes, snapshot = (
|
||||
_adopt_with_production_runtime(
|
||||
module, boundary, project, tmp_path, monkeypatch,
|
||||
capture_runtime_environment=True,
|
||||
)
|
||||
)
|
||||
_candidate_deployment(
|
||||
module, runtime, boundary, project, registry, state_bytes, tmp_path,
|
||||
use_signed_overlay=True,
|
||||
)
|
||||
assert boundary.live["Image"] == CANDIDATE_IMAGE
|
||||
|
||||
restored = module.restore_deployment_snapshot(
|
||||
REPOSITORY, project, snapshot, TARGET_SHA,
|
||||
receipt_key=KEY, state_root=tmp_path, runtime=runtime,
|
||||
)
|
||||
|
||||
assert restored["imageIDs"] == {CONTAINER_NAME: BASELINE_IMAGE}
|
||||
assert boundary.compose_ups == 1
|
||||
assert boundary.live["Image"] == BASELINE_IMAGE
|
||||
assert "KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna" in boundary.live["Config"]["Env"]
|
||||
runtime.observe(REPOSITORY, project, receipt_key=KEY)
|
||||
|
||||
|
||||
def _prepare_candidate_overlay(module, boundary, project, tmp_path):
|
||||
bound_project = dict(project)
|
||||
bound_project.update(
|
||||
@@ -714,8 +913,17 @@ def _candidate_deployment(
|
||||
*,
|
||||
receipt_container_id=None,
|
||||
receipt_image_id=None,
|
||||
use_signed_overlay=False,
|
||||
):
|
||||
bound_project = _prepare_candidate_overlay(module, boundary, project, tmp_path)
|
||||
if use_signed_overlay:
|
||||
overlay_path = module.validated_compose_overlay(
|
||||
bound_project, receipt_key=KEY, state_root=tmp_path, repository=REPOSITORY,
|
||||
)
|
||||
assert overlay_path is not None
|
||||
boundary.model = _compose_config_resolve_escaped(
|
||||
json.loads(overlay_path.read_text(encoding="utf-8"))
|
||||
)
|
||||
sealed = module.prepare_candidate_deployment(
|
||||
REPOSITORY,
|
||||
bound_project,
|
||||
@@ -733,6 +941,7 @@ def _candidate_deployment(
|
||||
receipt_key=KEY,
|
||||
state_root=tmp_path,
|
||||
runtime=runtime,
|
||||
api_json=_validation_api,
|
||||
)
|
||||
boundary.live = _container(
|
||||
image_id=CANDIDATE_IMAGE,
|
||||
@@ -741,6 +950,8 @@ def _candidate_deployment(
|
||||
config_hash="candidate-config-hash",
|
||||
)
|
||||
boundary.live["Config"]["Image"] = "quiz-monitor:latest"
|
||||
if use_signed_overlay:
|
||||
boundary.live["Config"]["Env"].append("KNOWLEDGE_OPTIMIZER_MODEL=gpt-6-luna")
|
||||
module.record_candidate_runtime(
|
||||
REPOSITORY,
|
||||
bound_project,
|
||||
@@ -803,6 +1014,17 @@ def _candidate_deployment(
|
||||
return live, controller_state
|
||||
|
||||
|
||||
def _compose_config_resolve_escaped(value):
|
||||
"""Model Compose config resolving dollars escaped in its saved input file."""
|
||||
if isinstance(value, str):
|
||||
return value.replace("$$", "$")
|
||||
if isinstance(value, list):
|
||||
return [_compose_config_resolve_escaped(item) for item in value]
|
||||
if isinstance(value, dict):
|
||||
return {key: _compose_config_resolve_escaped(item) for key, item in value.items()}
|
||||
return value
|
||||
|
||||
|
||||
def test_candidate_and_live_runtime_are_bound_to_exact_production_observation(
|
||||
adopted_production_runtime, tmp_path
|
||||
):
|
||||
@@ -956,6 +1178,50 @@ def test_candidate_sealing_rejects_runtime_drift_after_build_before_mutation(
|
||||
assert boundary.compose_ups == 0
|
||||
|
||||
|
||||
def test_candidate_runtime_rejection_does_not_leak_environment_values(
|
||||
adopted_production_runtime, monkeypatch, tmp_path, module, capsys
|
||||
):
|
||||
(
|
||||
runtime,
|
||||
_boundary,
|
||||
project,
|
||||
model,
|
||||
_registry,
|
||||
_read_state,
|
||||
_state_bytes,
|
||||
_snapshot,
|
||||
) = adopted_production_runtime
|
||||
sentinel = "fixture-secret-runtime-value-7c8f2f"
|
||||
candidate_model = copy.deepcopy(model)
|
||||
candidate_model["services"]["quiz-server"]["environment"]["QUIZ_TEMPLATE"] = sentinel
|
||||
|
||||
def prepare_candidate(*_args, **_kwargs):
|
||||
return {
|
||||
"composeModel": candidate_model,
|
||||
"imageIDsByService": {"quiz-server": CANDIDATE_IMAGE},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(runtime, "prepare_candidate", prepare_candidate)
|
||||
with pytest.raises(module.RegisteredLegacyAdoptionError) as caught:
|
||||
# Use the public operation so logging/exception behavior is tested at
|
||||
# the boundary where candidate runtime drift is rejected.
|
||||
module.prepare_candidate_deployment(
|
||||
REPOSITORY,
|
||||
project,
|
||||
REQUEST_ID,
|
||||
TARGET_SHA,
|
||||
receipt_key=KEY,
|
||||
state_root=tmp_path,
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
assert "captured runtime configuration" in str(caught.value)
|
||||
assert sentinel not in str(caught.value)
|
||||
output = capsys.readouterr()
|
||||
assert sentinel not in output.out
|
||||
assert sentinel not in output.err
|
||||
|
||||
|
||||
def test_pre_mutation_cas_rejects_runtime_drift_after_candidate_is_sealed(
|
||||
adopted_production_runtime, tmp_path
|
||||
):
|
||||
@@ -992,6 +1258,7 @@ def test_pre_mutation_cas_rejects_runtime_drift_after_candidate_is_sealed(
|
||||
receipt_key=KEY,
|
||||
state_root=tmp_path,
|
||||
runtime=runtime,
|
||||
api_json=_validation_api,
|
||||
)
|
||||
|
||||
assert boundary.compose_ups == 0
|
||||
|
||||
Reference in new issue
Block a user