Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 61 additions & 16 deletions execution/lib/instance_profile_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def check_profiles(inventory, saved_config, baseline, image, phase="before"):
def input_changes(manifest):
return (
manifest["input_changes"]
if manifest.get("version") in (2, 3)
if manifest.get("version") in (2, 3, 4)
else manifest["idle_changes"]
)

Expand Down Expand Up @@ -346,11 +346,12 @@ def normalized_input(saved, manifest):
return result


def _selected_saved_command_metadata(descriptor):
def _selected_saved_command_metadata(descriptor, version=3):
require(type(version) is int and version in (3, 4), "unselected metadata version")
selected = {
"path": "/deploy",
"before_present": False,
"before": None,
"before_present": version == 4,
"before": {"strategy": "rolling"} if version == 4 else None,
"after_present": True,
"after": {"strategy": "rolling"},
}
Expand Down Expand Up @@ -397,6 +398,33 @@ def _saved_command_input(saved, manifest, phase):
return before


def _unchanged_saved_command_input(saved, manifest):
"""Version4 preserves a separately selected whole present rolling input."""
import copy

selected = _selected_saved_command_metadata(manifest["saved_command_metadata"], 4)
require(manifest["input_changes"] == [], "unchanged metadata cannot edit input")
require(
manifest["input_toml"]["before_sha256"]
== manifest["input_toml"]["after_sha256"],
"unchanged metadata raw input differs",
)
require(
manifest["profile"]["saved_config_before_sha256"]
== manifest["profile"]["saved_config_after_sha256"],
"unchanged metadata saved digests differ",
)
require(
"deploy" in saved and digest(saved["deploy"]) == digest(selected["before"]),
"unchanged saved deployment metadata drift",
)
require(
digest(saved) == manifest["profile"]["saved_config_before_sha256"],
"unchanged saved configuration drift",
)
return copy.deepcopy(saved)


def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
"""Validate private pinned evidence; emit argv, never execute deployment.

Expand All @@ -418,7 +446,7 @@ def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
"profile",
(
"input_changes"
if manifest.get("version") in (2, 3)
if manifest.get("version") in (2, 3, 4)
else "idle_changes"
),
"secrets_sha256",
Expand All @@ -427,11 +455,15 @@ def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
"command",
"packaging_gate",
}
| ({"input_toml"} if manifest.get("version") in (2, 3) else set())
| ({"saved_command_metadata"} if manifest.get("version") == 3 else set())
| ({"input_toml"} if manifest.get("version") in (2, 3, 4) else set())
| (
{"saved_command_metadata"}
if manifest.get("version") in (3, 4)
else set()
)
)
and type(manifest["version"]) is int
and manifest["version"] in (1, 2, 3),
and manifest["version"] in (1, 2, 3, 4),
"unsupported manifest",
)
require(
Expand All @@ -441,7 +473,7 @@ def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
{"canonical", "inventory", "saved", "secrets", "volumes", "tool_version"}
| (
{"provider_saved_json", "provider_saved_toml"}
if manifest["version"] == 3
if manifest["version"] in (3, 4)
else set()
)
),
Expand Down Expand Up @@ -526,8 +558,12 @@ def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
check_profiles(
evidence["inventory"], evidence["saved"], manifest["profile"], image, phase
)
if manifest["version"] == 3:
normalized = _saved_command_input(evidence["saved"], manifest, phase)
if manifest["version"] in (3, 4):
normalized = (
_saved_command_input(evidence["saved"], manifest, phase)
if manifest["version"] == 3
else _unchanged_saved_command_input(evidence["saved"], manifest)
)
import tomllib

require(
Expand Down Expand Up @@ -577,7 +613,7 @@ def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
== policy["min_machines_running"],
"normalization differs from admitted machine",
)
if manifest["version"] in (2, 3):
if manifest["version"] in (2, 3, 4):
raw_binding = manifest["input_toml"]
require(
isinstance(raw_binding, dict)
Expand All @@ -602,7 +638,7 @@ def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
"unselected build arguments in immutable-image method",
)
check_release_contract(
evidence["saved"] if manifest["version"] == 3 else normalized, command
evidence["saved"] if manifest["version"] in (3, 4) else normalized, command
)
# The reviewed immutable-image method cannot smuggle flags via an app or ID.
for value in (
Expand All @@ -620,7 +656,7 @@ def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
"commit": commit,
"scope": "PROFILE CHECK ONLY; packaging/tool/candidate gates precede command emission",
}
if manifest["version"] == 3:
if manifest["version"] in (3, 4):
result.update(
{
"provider_saved": evidence["saved"],
Expand All @@ -637,6 +673,8 @@ def prepare(evidence, manifest, manifest_sha256, image, commit, phase="before"):
"provider_saved_json_sha256": digest(evidence["provider_saved_json"]),
}
)
if manifest["version"] == 4:
result["saved_config_actual_sha256"] = digest(evidence["saved"])
return result


Expand Down Expand Up @@ -803,7 +841,12 @@ def check_release_contract(saved, command):


def check_complete_projection(
saved, source_config, tool_version, *, saved_command_metadata=None
saved,
source_config,
tool_version,
*,
saved_command_metadata=None,
saved_command_metadata_version=3,
):
"""Require the supported installed Fly update to preserve ALL static fields.

Expand Down Expand Up @@ -871,7 +914,9 @@ def check_complete_projection(
)
deploy = saved.get("deploy", {})
if saved_command_metadata is not None:
_selected_saved_command_metadata(saved_command_metadata)
_selected_saved_command_metadata(
saved_command_metadata, saved_command_metadata_version
)
require(
isinstance(deploy, dict)
and set(deploy)
Expand Down
27 changes: 19 additions & 8 deletions execution/scripts/prepare_instance_deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def run(args):
commit = command(["git", "rev-parse", "HEAD"], context).strip()
require(commit == args.commit, "candidate commit drift")
raw_bytes = Path(args.saved_toml).read_bytes()
if manifest.get("version") in (2, 3):
if manifest.get("version") in (2, 3, 4):
binding = manifest.get("input_toml", {})
require(
hashlib.sha256(raw_bytes).hexdigest()
Expand All @@ -57,9 +57,19 @@ def run(args):
m for m in evidence["inventory"] if m["id"] == manifest["profile"]["source_id"]
)
projection = check_complete_projection(
result["normalized"], source["config"], manifest["tool_version"]
result["normalized"],
source["config"],
manifest["tool_version"],
**(
{
"saved_command_metadata": result["saved_command_metadata"],
"saved_command_metadata_version": 4,
}
if manifest["version"] == 4
else {}
),
)
if manifest["version"] == 3:
if manifest["version"] in (3, 4):
import tomllib

require(
Expand All @@ -72,6 +82,7 @@ def run(args):
source["config"],
manifest["tool_version"],
saved_command_metadata=result["saved_command_metadata"],
saved_command_metadata_version=manifest["version"],
)
require(provider_projection == projection, "physical projections differ")
gate = manifest["packaging_gate"]
Expand Down Expand Up @@ -205,7 +216,7 @@ def execute_gate(name):
for flag in argv[2:]:
if flag.startswith("--"):
require(flag.split("=", 1)[0] in help_text, "unsupported installed option")
if args.phase == "before" and manifest["version"] != 3:
if args.phase == "before" and manifest["version"] not in (3, 4):
rendered, edits = render_normalized_toml(
raw_bytes.decode("utf-8"),
evidence["saved"],
Expand All @@ -217,7 +228,7 @@ def execute_gate(name):
# After evidence already contains the pinned final configuration. Keep
# its bytes intact; an absent-before insertion must never run twice.
rendered, edits = raw_bytes.decode("utf-8"), []
if manifest["version"] in (2, 3):
if manifest["version"] in (2, 3, 4):
require(
hashlib.sha256(rendered.encode("utf-8")).hexdigest()
== manifest["input_toml"]["after_sha256"],
Expand All @@ -244,7 +255,7 @@ def execute_gate(name):
(
digest(parsed)
== digest(installed_config_projection(result["normalized"]))
if manifest["version"] == 3
if manifest["version"] in (3, 4)
else parsed == installed_config_projection(result["normalized"])
),
"installed config parser changed input",
Expand Down Expand Up @@ -304,15 +315,15 @@ def execute_gate(name):
).hexdigest(),
}
result["machine_projection"] = projection
if manifest["version"] == 3:
if manifest["version"] in (3, 4):
result["provider_machine_projection"] = provider_projection
result["tool_version"] = version
result["phase"] = args.phase
result["scope"] = (
"PREPARATION ONLY; pinned candidate/packaging/tool/profile checks "
"completed; no deployment or application/readiness clearance"
)
if manifest["version"] != 3:
if manifest["version"] not in (3, 4):
result.pop("normalized")
fd = os.open(out / "prepared.json", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w") as handle:
Expand Down
Loading
Loading