Documentation
¶
Overview ¶
Package authz is Wardyn's authorization kernel: the one Decision every door refuses with, and the registry of refusal reasons that is the audit contract a SIEM rule is written against. Pure Go — no HTTP, no store — so the same Decision can later cross a wire (schema authz/v1) unchanged.
Index ¶
Constants ¶
const AuditAction = "authz.denied"
AuditAction is the audit action every audited refusal is recorded under.
const Schema = "authz/v1"
Schema names the wire shape of Decision. Changes inside v1 are additive and readers ignore unknown fields; a removal or rename is a new schema.
Variables ¶
This section is empty.
Functions ¶
func Datum ¶
Datum is the data of d's audit row, refused to p over method (empty when no request carried it). A detail never stands in for a reserved key, so it can never forge the reason or a marker. user_view is a marker present only when true (renamed in 0.8 from member_mode); device_channel is a sibling of the ingest marker device_origin, never that key.
Types ¶
type Decision ¶
type Decision struct {
Schema string `json:"schema"`
Effect Effect `json:"effect"`
Status int `json:"status"`
Reason Reason `json:"reason,omitempty"`
// Target is what was refused: a request field (runs.image), a route, or a
// resource id.
Target string `json:"target,omitempty"`
RunID *uuid.UUID `json:"run_id,omitempty"`
// Sentence is the refusal body. Empty means the reason's own sentence.
Sentence string `json:"sentence,omitempty"`
// WireReason overrides the reason an HTTP response carries, when set
// (#656 slice 3's refuse fix). d.Reason itself is never overridden — the
// audit row's own "reason" field (authz.Datum) always uses the TRUE
// cause — this only changes what the CALLER sees. Its one use today is a
// Hidden-effect decision (EffectHidden: "byte-identical to a missing
// row"): the caller must see the SAME reason a truly-missing resource's
// 404 carries, never the internal "not_owner"/"attach_ticket_foreign_run"
// that would tell a prober the resource exists. See Decision.AsIf.
WireReason Reason `json:"wire_reason,omitempty"`
// Policy names the policy whose ceiling caused the refusal and how to ask for
// a change. Set only by a ceiling door, and written to the wire by the HTTP
// refusal emitter, never into the audit datum (Datum reads none of it). The
// emitter leaves it off a decision with a WireReason and off an EffectHidden
// one: a hidden door must stay byte-identical to a missing resource.
Policy *policyref.Ref `json:"policy,omitempty"`
Detail map[string]string `json:"detail,omitempty"`
Obligations []Obligation `json:"obligations,omitempty"`
Trace []Step `json:"trace,omitempty"`
}
Decision is one authorization answer. Every field is data (no funcs), so it serializes as-is.
func Deny ¶
Deny is the refusal of target for reason, answered with sentence. The effect and status come from the registry, never from the door; an unregistered reason still refuses (403) and is caught where it is emitted.
func Drop ¶
Drop is the refusal of the listed entries of target for reason: they are removed and the request proceeds without them.
func (Decision) AsIf ¶
AsIf makes d's HTTP response carry reason instead of d.Reason, for a Hidden-effect decision whose wire body must match a specific entity's genuinely-missing twin byte for byte (#656 slice 3) — d.Reason (and so the audit row) is untouched; only WireReason changes.
type Effect ¶
type Effect string
Effect is what a Decision does to the request. Append-only: it is part of the wire contract (testdata/wire.golden).
const ( EffectAllow Effect = "allow" EffectDeny Effect = "deny" // EffectHidden is the 404 twin: byte-identical to a missing row, so a // refusal is never an existence oracle. EffectHidden Effect = "hidden" EffectUnprocessable Effect = "unprocessable" // EffectConflict is a refusal answered 409: the request is well-formed and // the caller could otherwise make it, but their own session state (a // deleted user-view type) conflicts with it. EffectConflict Effect = "conflict" // and the request well-formed, but this server cannot prove a safety // property the answer depends on (a run's masking corpus), so it refuses // instead of passing bytes through. EffectUnavailable Effect = "unavailable" // EffectUnauthenticated is a refusal answered 401: the credential itself is // no longer acceptable (its role stamp is too old), and signing in again is // the remedy. EffectUnauthenticated Effect = "unauthenticated" // EffectBadRequest is a refusal answered 400: the request combines parameters the door does not // accept together. Input shape, not a caller's rights. EffectBadRequest Effect = "bad_request" )
type Obligation ¶
type Obligation struct {
Kind ObligationKind `json:"kind"`
Detail string `json:"detail"`
}
Obligation is one thing a Decision does to one part of the request.
type ObligationKind ¶
type ObligationKind string
ObligationKind is what an allow still does to the request. Append-only, like Effect.
const ObligationDrop ObligationKind = "drop"
ObligationDrop removes one entry from the request and lets the rest proceed.
type Origin ¶
type Origin struct {
DeviceID *uuid.UUID `json:"device_id,omitempty"`
Placement string `json:"placement,omitempty"`
}
Origin is where a request reached the deciding control plane from. Zero in 0.8. Only the org sets DeviceID, from the device channel it authenticated — never from anything a laptop asserts.
type Principal ¶
type Principal struct {
Subject string `json:"subject"`
// MemberView: an admin exercising "view as member"; only marks the row,
// the tier the kernel saw is already clamped.
MemberView bool `json:"member_view,omitempty"`
// UserType is the type a member view looks through; read only with MemberView.
UserType string `json:"user_type,omitempty"`
Origin Origin `json:"origin"`
}
Principal is who a decision is about.
type Reason ¶
type Reason string
Reason is a refusal's stable code: the `reason` of its authz.denied row and of its Decision. The set is the registry below and nothing else.
Append-only once 0.8.0 is tagged (testdata/wire.golden): a SIEM rule is written against these strings, so from then on a removal or a rename is a major version.
const ( ReasonAdminSurface Reason = "admin_surface" ReasonSecurityAdminSurface Reason = "security_admin_surface" ReasonNotOwner Reason = "not_owner" ReasonAttachTicketForeignRun Reason = "attach_ticket_foreign_run" ReasonBYOIUser Reason = "byoi_user" ReasonCapabilityAgent Reason = "capability_agent" ReasonCapabilityEgressHost Reason = "capability_egress_host" ReasonCapabilityFeature Reason = "capability_feature" ReasonCapabilityPolicy Reason = "capability_policy" ReasonCapabilitySecret Reason = "capability_secret" ReasonCapabilityWorkspace Reason = "capability_workspace" ReasonCapabilityWorkspaceProvider Reason = "capability_workspace_provider" ReasonCapabilityModelProvider Reason = "capability_model_provider" ReasonCapabilityComponent Reason = "capability_component" ReasonGovernanceProfile Reason = "governance_profile" ReasonPlacementComponentSelfDefined Reason = "placement_component_self_defined" ReasonGovernanceOverlayUnsatisfiable Reason = "governance_overlay_unsatisfiable" ReasonGrantPairingNotEligible Reason = "grant_pairing_not_eligible" ReasonGroupsSnapshotStale Reason = "groups_snapshot_stale" ReasonRunKept Reason = "run_kept" ReasonRunNotFound Reason = "run_not_found" ReasonRunTerminal Reason = "run_terminal" ReasonSecondHumanRequired Reason = "second_human_required" ReasonRunQuota Reason = "run_quota" ReasonUserTypeUnknown Reason = "user_type_unknown" ReasonUserViewTypeDeleted Reason = "user_view_type_deleted" // it — none by that name, off, not serving the agent, none chosen among // several, no usable credential of the caller's for it, or a policy grant // that would set a model credential beside it (#987). ReasonModelProviderUnavailable Reason = "model_provider_unavailable" // ReasonAdminView: an admin in the user view launched a run after the type // the view looks through was deleted. Not audited on its own — the cause // row is ReasonUserViewTypeDeleted, which the launch response answered. ReasonAdminView Reason = "admin_view" // ReasonUserViewPreview: with WARDYN_GOVERN_ADMIN_RUNS on, an admin whose // User view looks through a type other than their own sent a write; the // view is a read-only preview. 409, audited with the viewed and stamped types. ReasonUserViewPreview Reason = "user_view_preview" // ReasonDelegationScope: a portal's delegated token asked for a route // outside the delegation allow-list (#1142). ReasonDelegationScope Reason = "delegation_scope" // ReasonServiceScope: a service identity's token asked for a route outside // its fixed allow-list (#1508). ReasonServiceScope Reason = "service_scope" // ReasonEventStreamCap: the caller already holds the most concurrent run // event streams one principal may (#1407). A limit, not a denial: not // audited, like ReasonRunQuota. ReasonEventStreamCap Reason = "event_stream_cap" // ReasonRunOwnerOnly: interactive entry (terminal, UI app, SSH, take-over) // needs the run's owner; a super admin is refused unless the run has no // personal owner (#1476). Not hidden: the admin can already see the run. ReasonRunOwnerOnly Reason = "run_owner_only" // (recording upload, live attach, SSH shell, exec relay, live output read) // cannot prove this run's masking corpus complete here, so it refuses with // 503 instead of passing bytes through unmasked (ha-l2.0). ReasonMaskStateUnavailable Reason = "mask_state_unavailable" // ReasonDriveNotFound, ReasonDriveTargetConflict and ReasonDriveNotInPool: // a run's drives[] names a drive the caller's own grants do not reach, two // drives that derive one path, or a drive the run's pool cannot mount. 422, // audited; the drive_not_found row never says whether the id exists. ReasonDriveNotFound Reason = "drive_not_found" ReasonDriveTargetConflict Reason = "drive_target_conflict" ReasonDriveNotInPool Reason = "drive_not_in_pool" // ReasonRoleStampStale: an API token's role and group stamp is older than // WARDYN_ROLE_STAMP_TTL. Its owner signs in again to re-stamp it. ReasonRoleStampStale Reason = "role_stamp_stale" // ReasonRecordingGoverned: Record Mode is refused for an admin whose runs are // governed (WARDYN_GOVERN_ADMIN_RUNS) unless the deployment exempts recording. ReasonRecordingGoverned Reason = "recording_governed" // ReasonAuditExportPartitionFilter: a partition export (GET /audit/export?partition=) was asked // to narrow the partition with another filter, so the digest in its footer would not cover the // whole partition. Input shape, not a denial: not audited. ReasonAuditExportPartitionFilter Reason = "audit_export_partition_filter" // ReasonKeyDomainUnknown: a key-domain assignment named a domain the // deployment's key domains file does not declare. ReasonKeyDomainUnknown Reason = "key_domain_unknown" // ReasonKeyDomainAmbiguous: a group assignment would leave people whose // groups are assigned to different domains, with no user assignment of // their own, or while someone whose last sign-in lost groups has none, so // their next principal key would be refused; or the delete of a user // assignment would leave that person so. ReasonKeyDomainAmbiguous Reason = "key_domain_ambiguous_membership" // The five refusals of POST /audit/retention/drop, one per rule audit_retention_drop (migration // 0123) enforces. State conflicts of an authorized security operator, audited so an attempt that // was refused is on the record. ReasonAuditRetentionNotOldest Reason = "audit_retention_not_oldest" ReasonAuditRetentionNotClosed Reason = "audit_retention_not_closed" ReasonAuditRetentionInsideWindow Reason = "audit_retention_inside_window" ReasonAuditRetentionLiveRun Reason = "audit_retention_live_run" ReasonAuditRetentionDigestMismatch Reason = "audit_retention_digest_mismatch" // ReasonAuditRetentionActorReserved: the drop's caller is named "wardynd", the name the database // reads as the sweeper (a system, unattested drop), so no request may carry it. ReasonAuditRetentionActorReserved Reason = "audit_retention_actor_reserved" // ReasonComponentAutonomy: the organisation's autonomy cap on runs that // carry a self-defined component (site config components.autonomy_cap) // alone bound the run's level, and the request asks for more than it // permits. A tie with a governance profile's rubric refuses as // ReasonGovernanceProfile instead. ReasonComponentAutonomy Reason = "component_autonomy" )
type Refusal ¶
type Refusal struct {
Effect Effect
// Audit: the refusal writes an authz.denied row. False only for a caller
// who IS authorized and is at a limit — a row per quota hit would make a
// busy user look like an attacker.
Audit bool
// Sentence is the reason's own body, used when the door supplies none;
// set only where every door refusing with the reason says the same thing.
Sentence string
}
Refusal is one reason's registry row.