Workload Identity: SigstorePolicy validation improvements (#54484)

* Vaildate policy requirements

* Ensure custom `trusted_roots` can actually be used to validate signatures

* Add comment explaining why we only check for CTLogs or TSAs when overriding roots

* Avoid nil TrustedMaterialCollection
This commit is contained in:
Dan Upton
2025-05-06 10:38:01 +00:00
committed by GitHub
parent f3889cff17
commit efbddad183
2 changed files with 79 additions and 2 deletions
+22 -2
View File
@@ -155,14 +155,34 @@ func ValidateSigstorePolicy(s *workloadidentityv1pb.SigstorePolicy) error {
}
}
roots := make(root.TrustedMaterialCollection, 0)
for idx, trustedRoot := range v.Keyless.GetTrustedRoots() {
if _, err := root.NewTrustedRootFromJSON([]byte(trustedRoot)); err != nil {
root, err := root.NewTrustedRootFromJSON([]byte(trustedRoot))
if err != nil {
return trace.BadParameter("spec.keyless.trusted_roots[%d]: failed to parse trusted root: %v", idx, err)
}
roots = append(roots, root)
}
// If the user is overriding the default (Public Good Instance) trusted
// roots with their own, they must specify at least one transparency log
// or timestamp authority that can be used to verify keyless certificates.
if len(roots) != 0 && len(roots.CTLogs()) == 0 && len(roots.TimestampingAuthorities()) == 0 {
return trace.BadParameter("spec.keyless.trusted_roots: must configure at least one transparency log or timestamp authority")
}
}
for idx, attestation := range s.GetSpec().GetRequirements().GetAttestations() {
requirements := s.GetSpec().GetRequirements()
if requirements.GetArtifactSignature() && len(requirements.GetAttestations()) != 0 {
return trace.BadParameter("spec.requirements: artifact_signature and attestations are mutually exclusive")
}
if !requirements.GetArtifactSignature() && len(requirements.GetAttestations()) == 0 {
return trace.BadParameter("spec.requirements: either artifact_signature or attestations is required")
}
for idx, attestation := range requirements.GetAttestations() {
if attestation.GetPredicateType() == "" {
return trace.BadParameter("spec.requirements.attestations[%d].predicate_type: is required", idx)
}
+57
View File
@@ -208,12 +208,58 @@ func TestValidateSigstorePolicy(t *testing.T) {
},
err: "spec.keyless.trusted_roots[0]: failed to parse trusted root",
},
"keyless trusted_roots contains no tlogs or timestampAuthorities": {
mod: func(p *workloadidentityv1.SigstorePolicy) {
// This is GitHub's trusted roots with the `tlogs` and timestampAuthorities`
// lists blanked out.
const root = `
{
"mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1",
"certificateAuthorities": [
{
"subject": {
"organization": "GitHub, Inc.",
"commonName": "Internal Services Root"
},
"uri": "fulcio.githubapp.com",
"certChain": {
"certificates": [
{
"rawBytes": "MIICKzCCAbCgAwIBAgIUQeyd9UH06yZ63pDuqjgUZ58CnpMwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI0MTAwMzEyMDAwMFoXDTI1MTAwMzEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEwvbET2w+j9j9j50iTInH1gb9GSXkpsCvWz5orX1zgme+/Qh/5gMkpfmgfOSLV2ZRgT1hzujYmnKQvP2mCxYnbwQELAkAf+VhEY/7Uw3zZvguGQSdF1cxzRHiMTOha5eFo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUMib9z4ZYBcQANTVvVCa3KoTGbBUwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaQAwZgIxAPIU/zlJiJrxn6oTWNdEAD/YBSnhyxcvpq1D2DzFy8E8hbkEfMZPErYL7HyoL/BkdwIxAN9KDEKyktEUBrfHehfcLAzI2kERJx+8DSslXswOIbLaeqYfWsmrQAt5C0X/nOWxXA=="
},
{
"rawBytes": "MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"
},
{
"rawBytes": "MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="
}
]
},
"validFor": {
"start": "2024-10-07T00:00:00Z"
}
}
],
"timestampAuthorities": [],
"tlogs": []
}
`
p.Spec.GetKeyless().TrustedRoots = []string{root}
},
err: "spec.keyless.trusted_roots: must configure at least one transparency log or timestamp authority",
},
"no requirements": {
mod: func(p *workloadidentityv1.SigstorePolicy) {
p.Spec.Requirements = nil
},
err: "spec.requirements: is required",
},
"empty requirements": {
mod: func(p *workloadidentityv1.SigstorePolicy) {
p.Spec.Requirements = &workloadidentityv1.SigstorePolicyRequirements{}
},
err: "spec.requirements: either artifact_signature or attestations is required",
},
"required attestation empty predicate": {
mod: func(p *workloadidentityv1.SigstorePolicy) {
p.Spec.Requirements.Attestations = []*workloadidentityv1.InTotoAttestationMatcher{
@@ -222,6 +268,17 @@ func TestValidateSigstorePolicy(t *testing.T) {
},
err: "spec.requirements.attestations[0].predicate_type: is required",
},
"attestations and artifact signature": {
mod: func(p *workloadidentityv1.SigstorePolicy) {
p.Spec.Requirements = &workloadidentityv1.SigstorePolicyRequirements{
ArtifactSignature: true,
Attestations: []*workloadidentityv1.InTotoAttestationMatcher{
{PredicateType: "https://slsa.dev/provenance/v1"},
},
}
},
err: "spec.requirements: artifact_signature and attestations are mutually exclusive",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {