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
15 changes: 9 additions & 6 deletions src/lib/onboard/managed-bootstrap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,15 @@ the provider and sandbox identities, plan and profile
fingerprints, exact original and replacement IDs, rollback target, and phase.
Exact commit and cleanup receipts are durable terminal records, so adapter
recreation does not depend on process-local transaction sets or tombstone maps.
Rollback retains an `owner-cleanup-required` phase only after image-owned shared
state is restored and the exact replacement is absent. That phase keeps the
restored original quiescent and preserves the journal without a terminal
receipt until the owning sandbox service removes the exact runtime and the
provider proves its absence. Unknown runtime presence is a retryable durable
cleanup failure, never evidence of absence.
Rollback retains an `owner-cleanup-required` phase after image-owned shared
state is restored and the exact replacement is absent. If
`DockerManagedStartupSharedStateRestoreError` reports a restoration failure,
rollback can retain the same phase only after it removes the exact replacement.
This path restores the exact original to its canonical name and keeps it
stopped. In both cases, the durable journal remains without a terminal receipt.
The owning sandbox service remains responsible for `destroy`, and the provider
must prove the exact runtime is absent. Unknown runtime presence is a retryable
durable cleanup failure, never evidence of absence.

The dormant Podman candidate keeps the same provider-neutral coordinator
boundary but owns its engine-specific authority internally. It binds one
Expand Down
33 changes: 33 additions & 0 deletions src/lib/onboard/managed-bootstrap/docker-shared-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,39 @@ describe("Docker managed-bootstrap shared-state helper environment", () => {
helpers.forEach(expectCleanRunNodeHelper);
});

it("grants only the capabilities needed to restore exact Hermes root metadata (#9486)", () => {
const fake = fixture({ sharedState: "pending" });
finalizeDockerManagedStartupSharedState(
{
transaction: sharedStateTransaction(),
retainContainerAfterRollback: true,
supervisorReady: false,
},
fake.deps,
);

const rollbackHelper = nodeHelperCalls(fake.deps).find((args) =>
args.includes("--rollback-shared-state-transaction"),
);
expect(rollbackHelper).toBeDefined();
const capabilities = rollbackHelper!.flatMap((value, index, args) =>
value === "--cap-add" ? [args[index + 1]] : [],
);
expect(capabilities).toEqual(["CHOWN", "DAC_OVERRIDE", "FOWNER", "FSETID"]);
expect(rollbackHelper).toEqual(
expect.arrayContaining([
"--cap-drop",
"ALL",
"--network",
"none",
"--read-only",
"--volumes-from",
NEW_ID,
]),
);
expect(rollbackHelper).not.toContain("--privileged");
});

it("clears arbitrary container environment before the durable receipt-clear helper", () => {
const fake = fixture({ sharedState: "committed" });
clearDockerManagedStartupSharedStateCommitReceipt(sharedStateTransaction(), fake.deps);
Expand Down
13 changes: 12 additions & 1 deletion src/lib/onboard/managed-bootstrap/docker-shared-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ export class DockerManagedStartupSharedStateCommitIndeterminateError extends Err
}
}

export class DockerManagedStartupSharedStateRestoreError extends Error {
constructor(detail: string) {
super(detail);
this.name = "DockerManagedStartupSharedStateRestoreError";
}
}

export function probeDockerManagedStartupSharedState(
input: {
readonly transaction: DockerManagedBootstrapSharedStateTransaction;
Expand Down Expand Up @@ -511,6 +518,10 @@ function rollbackManagedStartupSharedState(
"DAC_OVERRIDE",
"--cap-add",
"FOWNER",
// Hermes keeps its shared state root setgid. FSETID lets this isolated
// helper restore that bit after CHOWN changes the directory group.
"--cap-add",
"FSETID",
...NEUTRALIZED_PRE_ENTRYPOINT_ENV,
"--volumes-from",
transaction.containerId,
Expand All @@ -526,7 +537,7 @@ function rollbackManagedStartupSharedState(
DOCKER_MUTATION_OPTIONS,
);
if (!hasZeroDockerExitStatus(helper)) {
throw new Error(
throw new DockerManagedStartupSharedStateRestoreError(
`Immutable managed-startup helper could not restore and verify shared state: ${commandDetail(helper)}. ` +
`Protected receipt retained at ${receiptPath}`,
);
Expand Down
9 changes: 6 additions & 3 deletions src/lib/onboard/managed-bootstrap/docker-test-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export type DockerFixtureOptions = {
readonly replacementEnvironment?: (environment: readonly string[]) => readonly string[];
readonly sharedState?: "committed" | "none" | "pending";
readonly sharedStateCommitResult?: FixtureCommandResult;
readonly sharedStateRollbackResult?: FixtureCommandResult;
readonly sharedReceiptClearFailures?: readonly Error[];
};

Expand Down Expand Up @@ -456,10 +457,12 @@ export function fixture(options: DockerFixtureOptions = {}) {
switch (true) {
case args.includes("--shared-state-transaction-status"):
return ok(`${sharedState}\n`);
case args.includes("--rollback-shared-state-transaction"):
sharedState = "none";
case args.includes("--rollback-shared-state-transaction"): {
const result = options.sharedStateRollbackResult ?? ok();
if (result.status === 0) sharedState = "none";
events.push("shared:rollback");
return ok();
return result;
}
}
break;
case "exec":
Expand Down
Loading
Loading