{"id":12653,"date":"2026-08-18T19:57:55","date_gmt":"2026-08-19T00:57:55","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=12653"},"modified":"2026-11-02T10:04:46","modified_gmt":"2026-11-02T15:04:46","slug":"spire-can-we-reconstruct-what-happened-audit-and-investigation","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=12653","title":{"rendered":"SPIFFE: Can We Reconstruct What Happened? Audit and Investigation"},"content":{"rendered":"<p>A useful audit trail must reconstruct whose authority applied, which software acted, what was approved, and what actually changed. A single username field cannot represent that model.<\/p>\n<h2>Create a verified request-scoped identity<\/h2>\n<p>At authentication, construct an immutable context from validated claims or trusted introspection: subject with issuer, current actor, prior actor history, OAuth client, transport peer, delegation reference, token identifier, granted constraints, and policy version. Propagate it through authorization and business-event logging. Do not use a mutable global user context across concurrent requests or workers.<\/p>\n<p>For delegated tokens, the outermost act identifies the current actor; nested actors represent earlier actors. Prior actors are informational rather than an additional permission source. Preserve history for traceability without authorizing from a union of actor privileges.<\/p>\n<h2>A proposed structured business event<\/h2>\n<pre>{\r\n\"event\": \"case.update\",\r\n\"identity_mode\": \"user_delegation\",\r\n\"effective_subject\": {\r\n\"issuer\": \"https:\/\/identity.example.org\",\r\n\"id\": \"user:lisa\"\r\n},\r\n\"current_actor\": {\r\n\"id\": \"spiffe:\/\/agents.example.org\/agent\/xyz\"\r\n},\r\n\"transport_peer\": \"spiffe:\/\/services.example.org\/tool\/case-editor\",\r\n\"oauth_client_id\": \"agent-xyz-client\",\r\n\"delegation_id\": \"approval-81a6\",\r\n\"token_id\": \"token-4b781\",\r\n\"request_id\": \"req-a740\",\r\n\"agent_run_id\": \"run-93a2\",\r\n\"resource\": \"case\/4821\",\r\n\"authorization\": {\r\n\"decision\": \"allow\",\r\n\"policy_version\": \"case-policy-17\"\r\n},\r\n\"outcome\": \"committed\"\r\n}<\/pre>\n<p>These field names are recommendations, not a standards-defined event schema. A run ID helps correlation but does not prove identity. A token ID helps join events but does not make a token single-use. One-time semantics require protected state and atomic consumption at the appropriate transaction boundary.<\/p>\n<h2>One approval, five API calls<\/h2>\n<table>\n<tbody>\n<tr>\n<td width=\"221\"><strong>Call<\/strong><\/td>\n<td width=\"221\"><strong>Observed behavior<\/strong><\/td>\n<td width=\"221\"><strong>Expected investigation conclusion<\/strong><\/td>\n<\/tr>\n<tr>\n<td width=\"221\"><strong>1<\/strong><\/td>\n<td width=\"221\">Read case\/4821 under approved read authority<\/td>\n<td width=\"221\">Authorized read, if included in approval<\/td>\n<\/tr>\n<tr>\n<td width=\"221\"><strong>2<\/strong><\/td>\n<td width=\"221\">Update status on case\/4821<\/td>\n<td width=\"221\">Authorized mutation and committed result<\/td>\n<\/tr>\n<tr>\n<td width=\"221\"><strong>3<\/strong><\/td>\n<td width=\"221\">Update another field on case\/4821<\/td>\n<td width=\"221\">Denied field-level violation<\/td>\n<\/tr>\n<tr>\n<td width=\"221\"><strong>4<\/strong><\/td>\n<td width=\"221\">Update case\/9920<\/td>\n<td width=\"221\">Denied resource mismatch<\/td>\n<\/tr>\n<tr>\n<td width=\"221\"><strong>5<\/strong><\/td>\n<td width=\"221\">Repeat one-time status update<\/td>\n<td width=\"221\">Denied replay or recorded idempotent retry under policy<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n<p>Join the approval event, STS decision, API decision, and business commit. A 200 response in an agent trace does not independently prove a database commit. An authorization allow does not prove the operation succeeded. Conversely, a failed response after a commit can cause a retry; the audit model must distinguish this from deliberate duplicate execution.<\/p>\n<h2>Background work and provenance<\/h2>\n<p>Queue messages should reference a protected grant and carry authenticated provenance. Re-evaluate permission when the worker executes. Record the originating agent and current worker separately, because the worker may become the current actor in a new token exchange. Preserve request and job correlation without trusting identity fields supplied by arbitrary producers.<\/p>\n<p>Mark log fields by provenance: token-verified, authorization-service-issued, infrastructure-derived, or application-supplied. This prevents an agent-provided display name from being mistaken for the verified SPIFFE actor. Use stable identifiers for joins and display names only for presentation.<\/p>\n<h2>Logging controls<\/h2>\n<ul>\n<li>Never log bearer tokens, private keys, or full authorization headers.<\/li>\n<li>Redact business payloads and approval details according to sensitivity.<\/li>\n<li>Record denied attempts as well as successful operations.<\/li>\n<li>Protect log transport, administrative access, and retention configuration.<\/li>\n<li>Synchronize clocks and retain event time separately from ingestion time.<\/li>\n<li>Version audit schemas and ensure downstream parsers preserve nested identity context.<\/li>\n<li>Record configuration\/policy changes so investigators can reconstruct the decision in force.<\/li>\n<\/ul>\n<p>Takeaway: attribution becomes useful only when it survives all the way to the committed business event.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A useful audit trail must reconstruct whose authority applied, which software acted, what was approved, and what actually changed. A single username field cannot represent that model. Create a verified request-scoped identity At authentication, construct an immutable context from validated claims or trusted introspection: subject with issuer, current actor, prior actor history, OAuth client, transport &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2243],"tags":[2246,2244,2216,2245],"class_list":["post-12653","post","type-post","status-publish","format-standard","hentry","category-spiffe","tag-agent-identity","tag-spiffe","tag-spire","tag-workload-identity"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12653","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=12653"}],"version-history":[{"count":2,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12653\/revisions"}],"predecessor-version":[{"id":12661,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12653\/revisions\/12661"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12653"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=12653"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=12653"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}