Skip to content
Closed
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
4 changes: 2 additions & 2 deletions DESIGN.md

Large diffs are not rendered by default.

48 changes: 46 additions & 2 deletions dag/extdeps/access/posix_effective_principal.dag
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module extdeps.access.posix_effective_principal

import std.types { String }
import std.types { Bool, Int, NonEmptyStr, String }
import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }

Expand All @@ -11,6 +11,50 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
}
}

data posix_effective_principal_read_note: String = "Read the effective POSIX user name (whoami utility semantics). Interface shape only — typed argv transport lives in extdeps.access.posix_effective_principal_read_op at the realization edge."
data posix_effective_principal_read_note: String = "Read the effective POSIX user name (whoami utility semantics). Interface shape and result decoding live here; typed argv transport lives in extdeps.access.posix_effective_principal_read_op at the realization edge. EffectivePosixPrincipalName is opaque outside this authority, so downstream principal projections can consume only a successful, non-empty decoded read observation rather than pairing the request token with an authored name."

type EffectivePosixPrincipalRead {}

type EffectivePosixPrincipalReadOutcome {
read: EffectivePosixPrincipalRead
exit_code: Int
success: Bool
stdout: String
stderr: String
}

type EffectivePosixPrincipalName nominal_opaque = NonEmptyStr

type EffectivePosixPrincipalObservation {
read: EffectivePosixPrincipalRead
effective_name: EffectivePosixPrincipalName
}

type EffectivePosixPrincipalObservationResult
= EffectivePosixPrincipalObserved { observation: EffectivePosixPrincipalObservation }
| EffectivePosixPrincipalObservationRefused { read: EffectivePosixPrincipalRead, reason: String }

fn effective_posix_principal_observation(
outcome: EffectivePosixPrincipalReadOutcome,
) -> EffectivePosixPrincipalObservationResult {
let name = trim(s: outcome.stdout)
if !outcome.success || outcome.exit_code != 0 {
EffectivePosixPrincipalObservationRefused { read: outcome.read, reason: outcome.stderr }
} else if name == "" {
EffectivePosixPrincipalObservationRefused {
read: outcome.read,
reason: "successful effective-principal read returned no name",
}
} else {
EffectivePosixPrincipalObserved { observation: EffectivePosixPrincipalObservation {
read: outcome.read,
effective_name: (name as NonEmptyStr) as EffectivePosixPrincipalName,
} }
}
}

fn effective_posix_principal_name(
observation: EffectivePosixPrincipalObservation,
) -> NonEmptyStr {
observation.effective_name as NonEmptyStr
}
21 changes: 18 additions & 3 deletions dag/extdeps/access/posix_effective_principal_read_op.dag
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module extdeps.access.posix_effective_principal_read_op

import std.types { String, Int, Bool, List }
import extdeps.access.posix_effective_principal { EffectivePosixPrincipalRead }
import extdeps.access.posix_effective_principal {
EffectivePosixPrincipalRead, EffectivePosixPrincipalReadOutcome,
}
import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }

Expand All @@ -12,12 +14,25 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
}
}

data posix_effective_principal_read_op_note: String = "Typed argv transport for access.PosixEffectivePrincipal.Read — program+args dispatch at the realization edge without shell.Exec.Run (Wave C5 / bash-emit #5828 slice)."
data posix_effective_principal_read_op_note: String = "Typed argv transport for access.PosixEffectivePrincipal.Read — program+args dispatch at the realization edge without shell.Exec.Run (Wave C5 / bash-emit #5828 slice). Hermetic realization refuses this external identity observation; its mock response is a failed read and cannot become principal evidence."

fn posix_effective_principal_read_argv() -> List<String> {
["whoami"]
}

func effective_posix_principal_read(
read: EffectivePosixPrincipalRead,
) -> EffectivePosixPrincipalReadOutcome {
let result = access.PosixEffectivePrincipal.Read(read: read)
return EffectivePosixPrincipalReadOutcome {
read: read,
exit_code: result.exit_code,
success: result.success,
stdout: result.stdout,
stderr: result.stderr,
}
}

service access.PosixEffectivePrincipal {
operation Read {
input { read: EffectivePosixPrincipalRead }
Expand All @@ -35,7 +50,7 @@ service access.PosixEffectivePrincipal {
nonzero => String "whoami failed"
}
mock_response {
0 => { exit_code: 0, success: true, stdout: "ghrunner", stderr: "" } "hermetic access.PosixEffectivePrincipal.Read"
0 => { exit_code: 1, success: false, stdout: "", stderr: "hermetic effective-principal observation unavailable" } "hermetic access.PosixEffectivePrincipal.Read refuses external identity observation"
}
}
}
40 changes: 27 additions & 13 deletions dag/extdeps/realization/artifact_store_fs.dag
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import extdeps.filesystem.filesystem_io
import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }
import std.types { ContentHash, String, Bool, Int, List }
import std.access { Permit, Deny }
import std.effect_grant {
Verb, Write, Read,
NamespacePosition,
Admitted, EffectOutsideGrant,
}
import std.filesystem_path_grant { absolute_path_position }
import std.materialization_ladder { Frame }
import std.disposition { Disposition, Terminal }
import std.artifact_store_scratch_grant {
artifact_store_scratch_read_admissible,
artifact_store_scratch_write_admissible,
artifact_store_scratch_frame,
}

data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
Expand Down Expand Up @@ -51,9 +53,12 @@ type ArtifactFsListResult

func artifact_fs_put(store_root: String, key: ContentHash, payload: String) -> ArtifactFsPutResult {
match artifact_store_scratch_write_admissible(store_root: store_root) {
EffectOutsideGrant { verb: v, target: t, frame: f } =>
ArtifactFsPutRefused { verb: v, target: t, frame: f }
Admitted { via_root: r } => {
Deny { refusal: refusal } => ArtifactFsPutRefused {
verb: Write,
target: absolute_path_position(path: store_root),
frame: artifact_store_scratch_frame,
}
Permit => {
let path = artifact_fs_path(store_root: store_root, key: key)
let w = Filesystem.Write(path: path, content: payload)
if w.success {
Expand All @@ -66,9 +71,12 @@ func artifact_fs_put(store_root: String, key: ContentHash, payload: String) -> A

func artifact_fs_get(store_root: String, key: ContentHash) -> ArtifactFsLookup {
match artifact_store_scratch_read_admissible(store_root: store_root) {
EffectOutsideGrant { verb: v, target: t, frame: f } =>
ArtifactFsRefused { verb: v, target: t, frame: f }
Admitted { via_root: _ } => {
Deny { refusal: refusal } => ArtifactFsRefused {
verb: Read,
target: absolute_path_position(path: store_root),
frame: artifact_store_scratch_frame,
}
Permit => {
let path = artifact_fs_path(store_root: store_root, key: key)
let read_out = Filesystem.Read(path: path)
if read_out.success {
Expand All @@ -81,9 +89,12 @@ func artifact_fs_get(store_root: String, key: ContentHash) -> ArtifactFsLookup {

func artifact_fs_delete(store_root: String, key: ContentHash) -> ArtifactFsDeleteResult {
match artifact_store_scratch_write_admissible(store_root: store_root) {
EffectOutsideGrant { verb: v, target: t, frame: f } =>
ArtifactFsDeleteRefused { verb: v, target: t, frame: f }
Admitted { via_root: r } => {
Deny { refusal: refusal } => ArtifactFsDeleteRefused {
verb: Write,
target: absolute_path_position(path: store_root),
frame: artifact_store_scratch_frame,
}
Permit => {
let path = artifact_fs_path(store_root: store_root, key: key)
let d = Filesystem.Delete(path: path)
if d.success {
Expand All @@ -96,9 +107,12 @@ func artifact_fs_delete(store_root: String, key: ContentHash) -> ArtifactFsDelet

func artifact_fs_list(store_root: String) -> ArtifactFsListResult {
match artifact_store_scratch_read_admissible(store_root: store_root) {
EffectOutsideGrant { verb: v, target: t, frame: f } =>
ArtifactFsListRefused { verb: v, target: t, frame: f }
Admitted { via_root: r } => {
Deny { refusal: refusal } => ArtifactFsListRefused {
verb: Read,
target: absolute_path_position(path: store_root),
frame: artifact_store_scratch_frame,
}
Permit => {
let l = Filesystem.List(path: store_root)
if l.success {
if l.entries == "" {
Expand Down
30 changes: 11 additions & 19 deletions dag/gunbc/ci_deploy_access.dag
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
module gunbc.ci_deploy_access

import std.types { NonEmptyStr, List, String, Bool }
import std.access { AccessDecision, Permit, Deny }
import std.access { AccessDecision, Permit, Deny, access_policy_refusal, access_refusal_reason }
import std.disposition { Disposition, Scaffold, RealizationDispatch, Terminal }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import std.materialization_ladder { Frame, SharedStateFrame }
import std.effect_grant { Grant, Envelope, verb_of_effect_shape, admit_effect, EffectAdmission, Admitted, EffectOutsideGrant }
import std.access { AccessDecision }
import std.effect_grant { Grant, Envelope, verb_of_effect_shape, admit_effect }
import std.effects { ExecuteEffect }
import gunbc.fleet_intent { ComputeHost, srv1_host }
import gunbc.fleet_intent_network { operator_host_srv1 }
Expand Down Expand Up @@ -246,7 +247,7 @@ fn deploy_access_sudo_probe_effect(cmd: PrivilegedCommand) -> HostEffect {
fn deploy_access_grant_admits_probe(
access: DeployAccess,
cmd: PrivilegedCommand
) -> EffectAdmission {
) -> AccessDecision {
let grant = posix_sudo_grant_to_effect_grant(g: cmd.grant)
admit_effect(
env: deploy_access_grant_envelope(access: access),
Expand Down Expand Up @@ -295,38 +296,29 @@ fn deploy_access_sudo_miss_reason(access: DeployAccess) -> String {
fn deploy_access_check(access: DeployAccess, actor: DeployAccessActor) -> AccessDecision {
let expected = grounded_posix_principal_name(p: access.principal)
if actor.observed_name != expected {
Deny {
reason: Present {
value: concat(
access_policy_refusal(
profile: "deploy-access", subject: actor.observed_name, action: "execute", object: expected,
reason: concat(
concat(
concat(deploy_access_refusal_prefix, " — principal mismatch expected="),
expected
),
concat(" actual=", actor.observed_name)
)
}
}
),
)
} else {
if actor.sudo_granted {
Permit
} else {
Deny {
reason: Present {
value: deploy_access_sudo_miss_reason(access: access)
}
}
access_policy_refusal(profile: "deploy-access", subject: actor.observed_name, action: "execute", object: expected, reason: deploy_access_sudo_miss_reason(access: access))
}
}
}

fn deploy_access_decision_reason(d: AccessDecision) -> String {
match d {
Permit => ""
Deny { reason: r } =>
match r {
Present { value: why } => why
Absent => deploy_access_refusal_prefix
}
Deny { refusal: refusal } => access_refusal_reason(refusal: refusal)
}
}

Expand Down
32 changes: 13 additions & 19 deletions dag/gunbc/ci_deploy_target_host.dag
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module gunbc.ci_deploy_target_host

import std.types { String, NonEmptyStr, Bool, List }
import std.access { AccessDecision, Permit, Deny }
import std.access { AccessDecision, Permit, Deny, access_policy_refusal, access_refusal_reason }
import std.disposition { Disposition, Scaffold, SingleAuthority }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import extdeps.shell
Expand Down Expand Up @@ -57,11 +57,7 @@ fn deploy_target_host_observed() -> DeployTargetHostObservation {
fn deploy_target_host_decision_reason(d: AccessDecision) -> String {
match d {
Permit => ""
Deny { reason: r } =>
match r {
Present { value: why } => why
Absent => deploy_target_host_refusal_prefix
}
Deny { refusal: refusal } => access_refusal_reason(refusal: refusal)
}
}

Expand All @@ -70,7 +66,7 @@ fn deploy_target_host_check(
observed: DeployTargetHostObservation
) -> AccessDecision {
if !deploy_target_ssh_proof_structurally_valid(proof: proof) {
Deny { reason: Present { value: deploy_target_host_refusal_ssh } }
access_policy_refusal(profile: "deploy-target-host", subject: "runner", action: "deploy", object: "target-host", reason: deploy_target_host_refusal_ssh)
} else {
if deploy_target_host_apply_permitted(
proof: proof,
Expand All @@ -81,9 +77,9 @@ fn deploy_target_host_check(
} else {
match proof {
RunnerIdentityOnHost { host: id } =>
Deny {
reason: Present {
value: concat(
access_policy_refusal(
profile: "deploy-target-host", subject: observed.runner_name, action: "deploy", object: id,
reason: concat(
concat(
concat(deploy_target_host_refusal_prefix, " — "),
deploy_target_host_refusal_local
Expand All @@ -95,21 +91,19 @@ fn deploy_target_host_check(
concat(" runner=", observed.runner_name)
)
)
)
}
}
),
)
SshExplicitHost { host: id, ssh_host: h } =>
Deny {
reason: Present {
value: concat(
access_policy_refusal(
profile: "deploy-target-host", subject: observed.runner_name, action: "deploy", object: id,
reason: concat(
concat(deploy_target_host_refusal_prefix, " — "),
concat(
deploy_target_host_refusal_ssh,
concat(" expected=", concat(id, concat(" ssh_host=", h)))
)
)
}
}
),
)
}
}
}
Expand Down
11 changes: 6 additions & 5 deletions dag/gunbc/cli_run_repo_grant.dag
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
module gunbc.cli_run_repo_grant

import std.types { String, Bool }
import std.access { AccessDecision }
import std.effect_grant {
Verb, Read, Write,
filesystem_position,
NamespacePosition,
RealTransport,
LifecycleByConstruction,
Grant, Envelope,
EffectAdmission, Admitted, EffectOutsideGrant, admit_effect,
admit_effect,
}
import std.filesystem_path_grant { repo_rel_path_position }
import std.materialization_ladder { Frame, SharedStateFrame }

data cli_run_repo_grant_note: String = "P-B repo-tree grant authority (docs/plans/effect-namespace-grants.md 6): dissolves the hand-rolled workspace_root containment gate in src/v1/stage0/src/cli_run.rs (repo_relative_path / repo_relative_path_normalized) into the landed std.effect_grant model. The process workspace is Read+Write under an abstract repo subtree; paths that escape (absolute outside repo, or a `..` path segment) are located EffectOutsideGrant refusals, never widened (DESIGN 5). HAND-RUST until Chunk F (docs/plans/cli-run-reconcile-defork.md, seed-shrink-census Chunk F — scheduled, not indefinite): Rust gate and this authority must not diverge; pinned by dag/test/claim/cli_run_repo_grant_hand_rust_equivalence_witness_test.dag + cli_run.rs cli_run_repo_grant_equivalence_tests. dissolve-on = cli_run.rs Chunk F lands (GENERATED workflow host-effect apply) OR ROADMAP 5-dissolve-patches retires HAND path handling."
data cli_run_repo_grant_note: String = "P-B repo-tree grant authority (docs/plans/effect-namespace-grants.md 6): dissolves the hand-rolled workspace_root containment gate in src/v1/stage0/src/cli_run.rs (repo_relative_path / repo_relative_path_normalized) into the landed std.effect_grant model. The process workspace is Read+Write under an abstract repo subtree; paths that escape (absolute outside repo, or a `..` path segment) are located std.access.Deny refusals from the EffectRequest profile, never widened (DESIGN 5). HAND-RUST until Chunk F (docs/plans/cli-run-reconcile-defork.md, seed-shrink-census Chunk F — scheduled, not indefinite): Rust gate and this authority must not diverge; pinned by dag/test/claim/cli_run_repo_grant_hand_rust_equivalence_witness_test.dag + cli_run.rs cli_run_repo_grant_equivalence_tests. dissolve-on = cli_run.rs Chunk F lands (GENERATED workflow host-effect apply) OR ROADMAP 5-dissolve-patches retires HAND path handling."

data cli_run_repo_frame: Frame = Frame {
name: "cli-run-repo",
Expand Down Expand Up @@ -42,18 +43,18 @@ fn cli_run_repo_envelope() -> Envelope {
}
}

fn cli_run_repo_path_admissible(rel: String, verb: Verb) -> EffectAdmission {
fn cli_run_repo_path_admissible(rel: String, verb: Verb) -> AccessDecision {
admit_effect(
env: cli_run_repo_envelope(),
verb: verb,
target: repo_rel_path_position(rel: rel)
)
}

fn cli_run_repo_write_admissible(rel: String) -> EffectAdmission {
fn cli_run_repo_write_admissible(rel: String) -> AccessDecision {
cli_run_repo_path_admissible(rel: rel, verb: Write)
}

fn cli_run_repo_read_admissible(rel: String) -> EffectAdmission {
fn cli_run_repo_read_admissible(rel: String) -> AccessDecision {
cli_run_repo_path_admissible(rel: rel, verb: Read)
}
Loading
Loading