Skip to content

securitypolicy: Handle SCITT envelope and transparent fragments#2779

Open
micromaomao wants to merge 10 commits into
microsoft:mainfrom
micromaomao:new-style-fragment-mainrebase
Open

securitypolicy: Handle SCITT envelope and transparent fragments#2779
micromaomao wants to merge 10 commits into
microsoft:mainfrom
micromaomao:new-style-fragment-mainrebase

Conversation

@micromaomao

@micromaomao micromaomao commented Jun 17, 2026

Copy link
Copy Markdown
Member

Require corresponding containerd / azcri changes to send MediaType in PolicyFragment struct.

TODO:

  • Test WCOW

Tested with LCOW using this policy:

package policy

import future.keywords.every
import future.keywords.in

api_version := "0.12.0"
framework_version := "0.5.0"

transparency_trust_lists := [
  {
    "issuer": "did:x509:0:sha256:wVkSM46SDpw4LuyEYoH6r5ym07whL5lWxNlGN-UPtf4::eku:1.3.6.1.4.1.311.10.3.13",
    "subject": "acidevacr.azurecr.io/internal/aci/aci-cc-ttl",
    "minimum_svn": 0,
    "allowed_ledgers": [
      "esrp-cts-dev.confidential-ledger.azure.com",
      # or
      "*"
    ]
  }
]

fragments := [
  {
    "feed": "acidevacr.azurecr.io/internal/aci/aci-cc-infra-fragment",
    "includes": [
      "containers",
      "fragments"
    ],
    "issuer": "did:x509:0:sha256:wVkSM46SDpw4LuyEYoH6r5ym07whL5lWxNlGN-UPtf4::eku:1.3.6.1.4.1.311.10.3.13",
    "minimum_svn": "0",
    "receipt_issuers": [
      "esrp-cts-dev.confidential-ledger.azure.com",
      # or
      "ttl:acidevacr.azurecr.io/internal/aci/aci-cc-ttl",
      # or
      "*"
    ]
  }
]

...
PS C:\Users\azureuser\lcow_volume> .\runp.ps1
Started pod lcow_volume with ID:
18ffa67e5663b49ee3ad3b1471fc5760db61f2c1af1f941fdf707fd5633cac54
PS C:\Users\azureuser\lcow_volume> $env:podId="18ffa67e5663b49ee3ad3b1471fc5760db61f2c1af1f941fdf707fd5633cac54"
PS C:\Users\azureuser\lcow_volume> azcrictl inject-fragment $env:podId 'acidevacr.azurecr.io/internal/aci/aci-cc-infra-fragment:transparent-fragment-signing-0-20260615.3'
time="2026-06-17T18:16:49Z" level=fatal msg="injecting policy fragment: rpc error: code = Unknown desc = failed to inject any fragment: guest modify: guest RPC failure: error loading security policy fragment: policyDecision< eyJkZWNpc2lvbiI6ImRlbnkiLCJpbnB1dCI6eyJmZWVkIjoiYWNpZGV2YWNyLmF6dXJlY3IuaW8vaW50ZXJuYWwvYWNpL2FjaS1jYy1pbmZyYS1mcmFnbWVudCIsImZyYWdtZW50X2xvYWRlZCI6ZmFsc2UsImhhc19oZWFkZXJfc3ZuIjpmYWxzZSwiaGVhZGVyX3N2biI6bnVsbCwiaXNzdWVyIjoiZGlkOng1MDk6MDpzaGEyNTY6d1ZrU000NlNEcHc0THV5RVlvSDZyNXltMDd3aEw1bFd4TmxHTi1VUHRmNDo6ZWt1OjEuMy42LjEuNC4xLjMxMS4xMC4zLjEzIiwibmFtZXNwYWNlIjoiYXp1cmVjb250YWluZXJpbnN0YW5jZSIsInJlY2VpcHRfaXNzdWVycyI6W10sInJ1bGUiOiJsb2FkX2ZyYWdtZW50In0sInJlYXNvbiI6eyJlcnJvcnMiOlsibWlzc2luZyByZWNlaXB0IGZyb20gZXNycC1jdHMtZGV2LmNvbmZpZGVudGlhbC1sZWRnZXIuYXp1cmUuY29tIl19fQ== >policyDecision\nguest modify: guest RPC failure: error loading security policy fragment: policyDecision< eyJkZWNpc2lvbiI6ImRlbnkiLCJpbnB1dCI6eyJmZWVkIjoiYWNpZGV2YWNyLmF6dXJlY3IuaW8vaW50ZXJuYWwvYWNpL2FjaS1jYy1pbmZyYS1mcmFnbWVudCIsImZyYWdtZW50X2xvYWRlZCI6ZmFsc2UsImhhc19oZWFkZXJfc3ZuIjp0cnVlLCJoZWFkZXJfc3ZuIjowLCJpc3N1ZXIiOiJkaWQ6eDUwOTowOnNoYTI1Njp3VmtTTTQ2U0RwdzRMdXlFWW9INnI1eW0wN3doTDVsV3hObEdOLVVQdGY0Ojpla3U6MS4zLjYuMS40LjEuMzExLjEwLjMuMTMiLCJuYW1lc3BhY2UiOiJhenVyZWNvbnRhaW5lcmluc3RhbmNlIiwicmVjZWlwdF9pc3N1ZXJzIjpbXSwicnVsZSI6ImxvYWRfZnJhZ21lbnQifSwicmVhc29uIjp7ImVycm9ycyI6WyJtaXNzaW5nIHJlY2VpcHQgZnJvbSBlc3JwLWN0cy1kZXYuY29uZmlkZW50aWFsLWxlZGdlci5henVyZS5jb20iXX19 >policyDecision"

# This is because we haven't loaded the TTL yet. The denial message will say "receipt_issuers: []" since we did not successfully validate any receipts: 

PS C:\Users\azureuser\lcow_volume> base64d ... | ConvertFrom-Json | ConvertTo-Json
{
  "decision": "deny",
  "input": {
    "feed": "acidevacr.azurecr.io/internal/aci/aci-cc-infra-fragment",
    "fragment_loaded": false,
    "has_header_svn": true,
    "header_svn": 0,
    "issuer": "did:x509:0:sha256:wVkSM46SDpw4LuyEYoH6r5ym07whL5lWxNlGN-UPtf4::eku:1.3.6.1.4.1.311.10.3.13",
    "namespace": "azurecontainerinstance",
    "receipt_issuers": [],
    "rule": "load_fragment"
  },
  "reason": {
    "errors": [
      "missing receipt from esrp-cts-dev.confidential-ledger.azure.com"
    ]
  }
}
PS C:\Users\azureuser\lcow_volume> azcrictl inject-fragment $env:podId 'acidevacr.azurecr.io/internal/aci/aci-cc-ttl:transparent-fragment-signing-0-20260615.1'
sha256:2cacf5a79d1d6f28eeeb8ff5e30bb634a6caffb6afd85b9e4549a90556cd45f3

# TTL injection succeed

PS C:\Users\azureuser\lcow_volume> azcrictl inject-fragment $env:podId 'acidevacr.azurecr.io/internal/aci/aci-cc-infra-fragment:transparent-fragment-signing-0-20260615.3'
sha256:b59e1ac5ea85d58a515d1369b05f21245f76627858803ccbdb543e0b3b64362c

# Infra fragment injection succeed

PS C:\Users\azureuser\lcow_volume> .\createc.ps1
936784644be5bd9eacd76594ddde31f859011182755ad1e882b728373bc7c51a
PS C:\Users\azureuser\lcow_volume> .\startc.ps1
936784644be5bd9eacd76594ddde31f859011182755ad1e882b728373bc7c51a
Started container lcow_volume_volume_mounthost_volume with ID:
936784644be5bd9eacd76594ddde31f859011182755ad1e882b728373bc7c51a
PS C:\Users\azureuser\lcow_volume> cat -wait container_mounthost_volume.log
2026-06-17T18:22:38.7144427Z stderr F mount error: could not resolve address for aaa.file.core.windows.net: Unknown error
2026-06-17T18:22:38.7144427Z stdout F Error: Failed to mount the Azure File Share.

@micromaomao micromaomao requested a review from a team as a code owner June 17, 2026 17:31
@micromaomao micromaomao force-pushed the new-style-fragment-mainrebase branch from d3be079 to 05c03c7 Compare June 17, 2026 17:33
@micromaomao micromaomao requested a review from Copilot June 17, 2026 17:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds support for transparency receipts and Transparency Trust Lists (TTLs) to the security policy fragment injection workflow, and bumps the policy framework/API versions and cosesign1go dependency accordingly.

Changes:

  • Introduces TTL ingestion (load_transparency_trust_list) and receipt validation during LoadFragment.
  • Extends fragment injection to support multiple blob media types (policy fragments vs TTLs) and allows SVN to be provided via COSE header.
  • Updates framework/API versions and vendored module/dependency versions.

Reviewed changes

Copilot reviewed 15 out of 24 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
vendor/modules.txt Updates vendored cosesign1go module version and go version metadata.
go.mod Bumps github.com/Microsoft/cosesign1go to v1.6.0-alpha1.
go.sum Adds checksums for updated cosesign1go versions.
pkg/securitypolicy/version_framework Bumps framework version to 0.5.0 for new enforcement behavior.
pkg/securitypolicy/version_api Bumps API version to 0.12.0 to expose the new enforcement point.
pkg/securitypolicy/securitypolicyenforcer.go Extends enforcer interface: LoadFragment options + TTL loading API.
pkg/securitypolicy/securitypolicyenforcer_rego.go Implements receipt validation/TTL storage + rollback snapshotting.
pkg/securitypolicy/securitypolicy_options.go Adds media type dispatch, header SVN extraction, TTL parsing/loading.
pkg/securitypolicy/framework.rego Adds new policy constructs: header SVN, required receipts, TTL roots + new rule.
pkg/securitypolicy/api.rego Registers load_transparency_trust_list enforcement point (0.12.0).
pkg/securitypolicy/policy.rego Wires load_transparency_trust_list to framework rule.
pkg/securitypolicy/open_door.rego Allows TTL loading under open-door policy.
pkg/securitypolicy/regopolicy_linux_test.go Adds/updates tests for header SVN, TTL loading, and receipt requirements.
pkg/ctrdtaskapi/update.go Adds mediaType to fragment injection request shape.
internal/uvm/security_policy.go Passes media type through to guest resource settings.
internal/protocol/guestresource/resources.go Adds MediaType field to guest SecurityPolicyFragment.
internal/regopolicyinterpreter/regopolicyinterpreter.go Adds RegoQueryResult.Array() helper for TTL rule results.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/securitypolicy/securitypolicyenforcer_rego.go Outdated
Comment on lines +1144 to +1147
receiptIssuers := make([]string, 0, len(receiptIssuersSet))
for issuer := range receiptIssuersSet {
receiptIssuers = append(receiptIssuers, issuer)
}
Comment thread internal/regopolicyinterpreter/regopolicyinterpreter.go Outdated
Comment thread pkg/securitypolicy/regopolicy_linux_test.go Outdated
Comment thread pkg/securitypolicy/securitypolicy_options.go Outdated
@micromaomao micromaomao force-pushed the new-style-fragment-mainrebase branch 2 times, most recently from f2dae99 to 9f91c1b Compare June 17, 2026 18:37
@KenGordon KenGordon self-assigned this Jun 18, 2026
@micromaomao

Copy link
Copy Markdown
Member Author

required_receipt_issuers -> receipt_issuers

@KenGordon

KenGordon commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator
package policy

import future.keywords.every
import future.keywords.in

api_version := "0.12.0"
framework_version := "0.5.0"

transparency_trust_lists := [ <<<< name 
  {
    "issuer": "did:x509:0:sha256:wVkSM46SDpw4LuyEYoH6r5ym07whL5lWxNlGN-UPtf4::eku:1.3.6.1.4.1.311.10.3.13",
    "subject": "acidevacr.azurecr.io/internal/aci/esrp-cts-dev.confidential-ledger.azure.com-ttl", <<<< name so that it reflects the collection of ledgers we expect it to cover
    "minimum_svn": 0,
    "allowed_ledgers": [
       "esrp-cts-dev.confidential-ledger.azure.com", << fragile?
       "*"<<<<< example wildcard - ie any ledger in the ttl acidevacr.azurecr.io/internal/aci/esrp-cts-dev.confidential-ledger.azure.com-ttl
    ]
  },
  {
    "issuer": "did:x509:0:sha256:wVkSM46SDpw4LuyEYoH6r5ym07whL5lWxNlGN-UPtf4::eku:1.3.6.1.4.1.311.10.3.13",
    "subject": "bob.azurecr.io/bob.confidential-ledger.azure.com-ttl", <<<< name so that it reflects the collection of ledgers we expect it to cover
    "minimum_svn": 0,
    "allowed_ledgers": [
       "bob.confidential-ledger.azure.com", << fragile?
       "*"<<<<< example wildcard
    ]
  }
]


fragments := [
  {
    "feed": "acidevacr.azurecr.io/internal/aci/kube-proxy",
    "includes": [
      "containers",
      "fragments"
    ],
    "issuer": "did:x509:0:sha256:wVkSM46SDpw4LuyEYoH6r5ym07whL5lWxNlGN-UPtf4::eku:1.3.6.1.4.1.311.10.3.13",
    "minimum_svn": "0",
    "receipt_issuers": [ <<<
      "esrp-cts-dev.confidential-ledger.azure.com" <<<<< CHECK SET IN STONE FOREVER (the subject of the receipt)

or maybe

     "acidevacr.azurecr.io/internal/aci/esrp-cts-dev.confidential-ledger.azure.com-ttl" <<< instead of the the subject of the receipt

 maybe a "*" => this fragment so long as it is in a ledger whose ttl we trust above. (as an alternative to specifying the receipt must be in a given TTL.) - but you may take a receipt from bob's ledger which is unlikely to be what you want.
    ] 
  },
 {
    "feed": "bob.azurecr.io/internal/bob-the-builder",
    "includes": [
      "containers",
      "fragments"
    ],
    "issuer": "did:x509:0:sha256:wVkSM46SDpw4LuyEYoH6r5ym07whL5lWxNlGN-UPtf4::eku:1.3.6.1.4.1.311.10.3.13",
    "minimum_svn": "0",
    "receipt_issuers": [ <<<
      "bob.confidential-ledger.azure.com" <<<<< CHECK SET IN STONE FOREVER (the subject of the receipt)

or maybe

     "bob.azurecr.io/bob.confidential-ledger.azure.com-ttl" <<< instead of the the subject of the receipt

 maybe a "*" => this fragment so long as it is in a ledger whose ttl we trust above. (as an alternative to specifying the receipt must be in a given TTL.)
    ] 
  }
]


...


Says `kube-proxy` fragment must have a reciept signed with a key which is in a TTL which was introduced via  acidevacr.azurecr.io/internal/aci/esrp-cts-dev.confidential-ledger.azure.com-ttl 

Comment thread pkg/securitypolicy/framework.rego Outdated
"fragments": apply_defaults("fragment", fragment_fragments, framework_version),
"external_processes": apply_defaults("external_process", fragment_external_processes, framework_version)
"external_processes": apply_defaults("external_process", fragment_external_processes, framework_version),
"transparency_roots": apply_defaults("transparency_roots", fragment_transparency_roots, framework_version),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question - should the tooling add this in by default so that the infra fragment can add TTLs.
Answer - no, we want to reduce the power of the infra fragment and make things more explicit.

@KenGordon KenGordon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per minor inline comments but also the overall policy shape discussed in the top level comment/discussion.

Comment thread pkg/securitypolicy/framework.rego
Comment thread pkg/securitypolicy/securitypolicy_options.go Outdated
Comment thread pkg/securitypolicy/securitypolicyenforcer.go Outdated
Comment thread pkg/securitypolicy/securitypolicyenforcer_rego.go
Comment thread pkg/securitypolicy/securitypolicyenforcer_rego.go
Comment thread pkg/securitypolicy/securitypolicyenforcer_rego.go Outdated
// section 3.2 of
// https://datatracker.ietf.org/doc/html/draft-ietf-scitt-receipts-ccf-profile-02
// Returns the recomputed Merkle root and the data-hash from the leaf (this
// needs to be verified by the caller against an expected value).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explain why this is CCF - ie a subtype of the SCITT schemes. ie not meaning just for Amaury.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
This version update is backwards compatible with v1.4.0, so gcs still builds.
It adds support for parsing new "SCITT"-style COSESign1 envelops, and the issuer
and feed is automatically extracted from their new location in the CWT claims,
if we get a new style fragment.  It also adds support for parsing receipts and
TTLs.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Refactor LoadFragment parameter to add a HeaderSvn field.  If the SVN is set in
the protected headers, pass it to LoadFragment, which will let framework
validate that it is at least the minimum required SVN before loading any
fragment Rego code.

Assisted-by: GitHub Copilot:auto copilot-review
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Assisted-by: GitHub Copilot:auto copilot-review
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Let InjectFragment accept an extra media type field, populated by azcri, which
will determine if we're handling a Rego policy fragment or a Transparency Trust
List (TTL).  Backward compatibility is maintained by defaulting to
application/cose-x509+rego.

Add a new load_transparency_trust_list enforcement point so that whether a TTL
is accepted can be gated by policy, and the policy can select which ledgers the
TTL can add keys for.

The framework uses transparency_trust_lists in a policy / fragments as the set
of allowed TTL signers.

Use cosesign1.ParseTTLPayload to parse the payload.

Bump version_api to 0.12.0.

Add apply_defaults for transparency_trust_lists

Assisted-by: GitHub Copilot copilot-review
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Fragments may now declare receipt_issuers, a list of ledgers from which a valid
transparency receipt, verifiable with loaded TTLs, must be present on the
fragment's COSE envelope before the fragment is allowed to load.

Add a Receipts field to LoadFragmentOptions, populated from the COSE envelope in
InjectFragment.

The Rego enforcer validates each receipt against the keys for its claimed issuer
only, so a ledger cannot sign a receipt pretending to be a different ledger, and
passes the set of validated issuers to the policy as input.receipt_issuers in
both load_fragment phases.

framework.rego load_fragment now checks receipt_issuers against
input.receipt_issuers and denies with 'missing receipt from <ledger>';
check_fragment defaults the field to [] so older policies are unaffected.

Receipt validation is behind a swappable closure so tests can exercise the logic
without a real CCF receipt.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
@micromaomao micromaomao force-pushed the new-style-fragment-mainrebase branch from 9f91c1b to 2cf640e Compare June 19, 2026 00:01
Assisted-by: GitHub Copilot copilot-review
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Assisted-by: GitHub Copilot copilot-review
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
@micromaomao micromaomao force-pushed the new-style-fragment-mainrebase branch from aa13087 to 6f8623b Compare June 19, 2026 12:12
…t's required receipt_issuers list.

This allows a fragment to be signed by any ledger keys introduced by specific
TTLs, either constraining the eligible TTL's subject, or allow any trusted TTL.

Assisted-by: GitHub Copilot copilot-review
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 21 changed files in this pull request and generated 2 comments.

Comment on lines +1145 to +1147
policy.ttlKeysLock.Lock()
defer policy.ttlKeysLock.Unlock()

Comment on lines +154 to +158
ctx, span := oc.StartSpan(ctx, "securitypolicy::InjectFragment")
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()
span.AddAttributes(trace.StringAttribute("fragment", fmt.Sprintf("%+v", fragment)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants