diff --git a/go.mod b/go.mod index 2491329b..cfa3063a 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.5.0 github.com/go-jose/go-jose/v3 v3.0.1 + github.com/sigstore/sigstore-go v0.2.0 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.1 @@ -227,6 +228,7 @@ require ( github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/sigstore/protobuf-specs v0.2.1 // indirect github.com/sigstore/timestamp-authority v1.2.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -237,6 +239,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/thales-e-security/pool v0.0.2 // indirect + github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/vbatts/tar-split v0.11.5 // indirect @@ -255,7 +258,7 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect - golang.org/x/mod v0.14.0 // indirect + golang.org/x/mod v0.15.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/term v0.17.0 // indirect diff --git a/go.sum b/go.sum index c693347e..0077b2e0 100644 --- a/go.sum +++ b/go.sum @@ -718,14 +718,20 @@ github.com/sassoftware/relic/v7 v7.6.1 h1:O5s8ewCgq5QYNpv45dK4u6IpBmDM9RIcsbf/G1 github.com/sassoftware/relic/v7 v7.6.1/go.mod h1:NxwtWxWxlUa9as2qZi635Ye6bBT/tGnMALLq7dSfOOU= github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/sigstore/cosign/v2 v2.2.3 h1:WX7yawI+EXu9h7S5bZsfYCbB9XW6Jc43ctKy/NoOSiA= github.com/sigstore/cosign/v2 v2.2.3/go.mod h1:WpMn4MBt0cI23GdHsePwO4NxhX1FOz1ITGB3ALUjFaI= +github.com/sigstore/protobuf-specs v0.2.1 h1:KIoM7E3C4uaK092q8YoSj/XSf9720f8dlsbYwwOmgEA= +github.com/sigstore/protobuf-specs v0.2.1/go.mod h1:xPqQGnH/HllKuZ4VFPz/g+78epWM/NLRGl7Fuy45UdE= github.com/sigstore/rekor v1.3.5 h1:QoVXcS7NppKY+rpbEFVHr4evGDZBBSh65X0g8PXoUkQ= github.com/sigstore/rekor v1.3.5/go.mod h1:CWqOk/fmnPwORQmm7SyDgB54GTJizqobbZ7yOP1lvw8= github.com/sigstore/sigstore v1.8.1 h1:mAVposMb14oplk2h/bayPmIVdzbq2IhCgy4g6R0ZSjo= github.com/sigstore/sigstore v1.8.1/go.mod h1:02SL1158BSj15bZyOFz7m+/nJzLZfFd9A8ab3Kz7w/E= +github.com/sigstore/sigstore-go v0.2.0 h1:pbDfn8voPQZCySzCpiDE+3qljzsczHUX26dQsnjH2Cg= +github.com/sigstore/sigstore-go v0.2.0/go.mod h1:M6iQfFjmK0wbez+lRTg+O7cJxjYa7s++zfW30rzZBKk= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1 h1:rEDdUefulkIQaMJyzLwtgPDLNXBIltBABiFYfb0YmgQ= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1/go.mod h1:RCdYCc1IxCYWzh2IdzdA6Yf7JIY0cMRqH08fpQYechw= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1 h1:DvRWG99QGWZC5mp42SEde2Xke/Q384Idnj2da7yB+Mk= @@ -785,6 +791,8 @@ github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gt github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141 h1:SsiWxSpJ9AD71/vqiZVUjXW1Uusv1wlKn4zPKFNq25w= +github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141/go.mod h1:D7dcS4bZMmF3pXOgUo8Vs6GLYM9sdrFFd37JqiP3hN4= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= @@ -909,8 +917,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/pkg/webhook/validator.go b/pkg/webhook/validator.go index dfe45942..ce426731 100644 --- a/pkg/webhook/validator.go +++ b/pkg/webhook/validator.go @@ -54,8 +54,11 @@ import ( "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" + "github.com/sigstore/policy-controller/pkg/webhook/verify" kubeclient "knative.dev/pkg/client/injection/kube/client" "knative.dev/pkg/logging" + + sgverify "github.com/sigstore/sigstore-go/pkg/verify" ) type Validator struct{} @@ -523,8 +526,17 @@ func ValidatePolicy(ctx context.Context, namespace string, ref name.Reference, c result.static = true case len(authority.Attestations) > 0: - // We're doing the verify-attestations path, so validate (.att) - result.attestations, result.err = ValidatePolicyAttestationsForAuthority(ctx, ref, authority, authorityRemoteOpts...) + // To support bundle verification, we need to decide when to invoke the + // bundle verifier. For now, we just use the bundle verifier if the + // authority name is "github", otherwise we use the regular verifier. + // TODO: Add option to ClusterImagePolicy to allow user to specify which + // verifier to use. + if authority.Keyless != nil && authority.Keyless.TrustRootRef == "github" { + result.attestations, result.err = ValidatePolicyAttestationsForAuthorityWithBundle(ctx, ref, authority, kc) + } else { + // We're doing the verify-attestations path, so validate (.att) + result.attestations, result.err = ValidatePolicyAttestationsForAuthority(ctx, ref, authority, authorityRemoteOpts...) + } default: result.signatures, result.err = ValidatePolicySignaturesForAuthority(ctx, ref, authority, authorityRemoteOpts...) @@ -964,6 +976,83 @@ func ValidatePolicyAttestationsForAuthority(ctx context.Context, ref name.Refere return ret, nil } +func ValidatePolicyAttestationsForAuthorityWithBundle(ctx context.Context, ref name.Reference, authority webhookcip.Authority, kc authn.Keychain) (map[string][]PolicyAttestation, error) { + _ = ctx // TODO: Use context for verifier when it lands in sigstore-go + trustedRoot, err := verify.TrustedRootGithubStaging() + if err != nil { + return nil, err + } + + if authority.Keyless.Identities == nil { + return nil, errors.New("must specify at least one identity for keyless authority") + } + + // TODO: support more than one identity + id := authority.Keyless.Identities[0] + certID, err := sgverify.NewShortCertificateIdentity(id.Issuer, id.Subject, "", id.SubjectRegExp) + if err != nil { + return nil, err + } + + bundle, result, err := verify.AttestationBundle(ref, trustedRoot, kc, sgverify.WithCertificateIdentity(certID)) + if err != nil { + return nil, err + } + + statementBytes, err := json.Marshal(result.Statement) + if err != nil { + return nil, err + } + + // sha256 of statement + statementDigest := sha256.Sum256(statementBytes) + if err != nil { + return nil, err + } + + // TODO: generate "signature ID" from the signature? + sig := string(bundle.GetDsseEnvelope().Signatures[0].Sig) + + ret := make(map[string][]PolicyAttestation, 1) + pa := PolicyAttestation{ + PolicySignature: PolicySignature{ + ID: sig, + Subject: result.VerifiedIdentity.SubjectAlternativeName.Value, + Issuer: result.VerifiedIdentity.Issuer, + GithubExtensions: GithubExtensions{ + WorkflowTrigger: result.VerifiedIdentity.Extensions.GithubWorkflowTrigger, + WorkflowSHA: result.VerifiedIdentity.Extensions.GithubWorkflowSHA, + WorkflowName: result.VerifiedIdentity.Extensions.GithubWorkflowName, + WorkflowRepo: result.VerifiedIdentity.Extensions.GithubWorkflowRepository, + WorkflowRef: result.VerifiedIdentity.Extensions.GithubWorkflowRef, + }, + }, + PredicateType: result.Statement.PredicateType, + Payload: statementBytes, + Digest: string(statementDigest[:]), + } + + // TODO: support more than one attestation + att := authority.Attestations[0] + if att.PredicateType != result.Statement.PredicateType { + return nil, fmt.Errorf("predicate type mismatch: %s != %s", att.PredicateType, result.Statement.PredicateType) + } + ret[att.Name] = []PolicyAttestation{pa} + + if att.Type != "" { + warn, err := policy.EvaluatePolicyAgainstJSON(ctx, att.Name, att.Type, att.Data, statementBytes) + if err != nil || warn != nil { + logging.FromContext(ctx).Warnf("failed policy validation for %s: %v", att.Name, err) + if err != nil { + return nil, err + } + return nil, warn + } + } + + return nil, nil +} + // ResolvePodScalable implements policyduckv1beta1.PodScalableValidator func (v *Validator) ResolvePodScalable(ctx context.Context, ps *policyduckv1beta1.PodScalable) { // Don't mess with things that are being deleted or already deleted or diff --git a/pkg/webhook/verify/trusted-root-github-staging.json b/pkg/webhook/verify/trusted-root-github-staging.json new file mode 100644 index 00000000..158ad18f --- /dev/null +++ b/pkg/webhook/verify/trusted-root-github-staging.json @@ -0,0 +1,53 @@ +{ + "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": "MIICKjCCAbCgAwIBAgIUW3TJVeOvr+NSvJXdOw8nEEn7HhQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTIzMDkxMjE0MDY1NFoXDTI0MDkxMTE0MDY1NFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEsosodObhuHG6Pr5vp5y+pmnKawS1h2hwv3r3hBwqh3ZHJAw64mhDnDs9fw4jKkZEBYRSVyOHyZppz4day8hgpTIDwdj44Oan4RDb+wmj04jfhVLjLsQ4Q/X4K/ynRgNXo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUm0vkDkQZ29hutYdayJobIRmf/iMwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaAAwZQIwIBl93E7vkWTvdeIm1WSIM4qNsj0ApE8LCj3k1vrY5x6/7yhAZs7QlO3/FBCoEeaZAjEAlJcNr37uZq9BYHODHBeO/gP+6EfbzsNaLV22ASBlhF/a9y83ESLuqCNN7IxGxmWT" + }, + { + "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": "2023-10-27T16:30:00Z" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "uri": "timestamp.githubapp.com", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICHDCCAaGgAwIBAgIUNDVlmtZuvoujn4KwiC/oxIr8hxAwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDgzMTEyMDAwMFoXDTI0MDgzMDEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV/zJhNTdu0Fa9hGCUih/JvqEoE81tEWrAVwUXXhdRgIY9hIFErLhNo6sSOpV9d7Zuy0KWMHhcimCUr41a1732ByVRy3f+Z4QhqpsgFMh5b5J90HJLK7HOyUZjehAnvSno3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUGwE6T5ZIh6lY9wP6vt42UHyVMewwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaQAwZgIxAJo48LtrSsn0UmLtqGiUKg2EUvso+aDN5EyjpvMmobZ/Oq9zjnR7Of369hoABW4/1gIxANg5ZW4FqijhsXnA3md6jM9yLrLCI9QL+KnuZnXq6WgAcNQaAN7PNNjVDKV3iJEklw==" + }, + { + "rawBytes": "MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH" + }, + { + "rawBytes": "MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ==" + } + ] + }, + "validFor": { + "start": "2023-10-27T16:30:00Z" + } + } + ] +} diff --git a/pkg/webhook/verify/verify.go b/pkg/webhook/verify/verify.go new file mode 100644 index 00000000..7bf1ad97 --- /dev/null +++ b/pkg/webhook/verify/verify.go @@ -0,0 +1,108 @@ +package verify + +import ( + "encoding/hex" + "fmt" + "io" + "strings" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + + _ "embed" + + "github.com/sigstore/sigstore-go/pkg/bundle" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/verify" +) + +//go:embed trusted-root-github-staging.json +var trustedRootGithubStaging []byte + +// TODO: Replace with TUF from TrustRoot CRD when TUF client is updated to support TrustedRoot files. +func TrustedRootGithubStaging() (root.TrustedMaterial, error) { + return root.NewTrustedRootFromJSON(trustedRootGithubStaging) +} + +func AttestationBundle(ref name.Reference, trustedMaterial root.TrustedMaterial, kc authn.Keychain, policyOption verify.PolicyOption) (*bundle.ProtobufBundle, *verify.VerificationResult, error) { + b, imageDigest, err := getBundle(ref, kc) + if err != nil { + return nil, nil, err + } + _ = imageDigest + + verifierConfig := []verify.VerifierOption{} + var artifactPolicy verify.ArtifactPolicyOption + + verifierConfig = append(verifierConfig, verify.WithObserverTimestamps(1)) + + sev, err := verify.NewSignedEntityVerifier(trustedMaterial, verifierConfig...) + if err != nil { + return nil, nil, err + } + + digestBytes, err := hex.DecodeString(imageDigest.Hex) + if err != nil { + return nil, nil, err + } + artifactPolicy = verify.WithArtifactDigest(imageDigest.Algorithm, digestBytes) + + result, err := sev.Verify(b, verify.NewPolicy(artifactPolicy, policyOption)) + if err != nil { + return nil, nil, err + } + return b, result, nil +} + +func getBundle(ref name.Reference, kc authn.Keychain) (*bundle.ProtobufBundle, *v1.Hash, error) { + desc, err := remote.Get(ref, remote.WithAuthFromKeychain(kc)) + if err != nil { + return nil, nil, fmt.Errorf("error getting image descriptor: %w", err) + } + + digest := ref.Context().Digest(desc.Digest.String()) + + referrers, err := remote.Referrers(digest, remote.WithAuthFromKeychain(kc)) + if err != nil { + return nil, nil, fmt.Errorf("error getting referrers: %w", err) + } + refManifest, err := referrers.IndexManifest() + if err != nil { + return nil, nil, fmt.Errorf("error getting referrers manifest: %w", err) + } + + var bundleBytes []byte + for _, refDesc := range refManifest.Manifests { + if !strings.HasPrefix(refDesc.ArtifactType, "application/vnd.dev.sigstore.bundle+json") { + continue + } + + refImg, err := remote.Image(ref.Context().Digest(refDesc.Digest.String()), remote.WithAuthFromKeychain(kc)) + if err != nil { + return nil, nil, fmt.Errorf("error getting referrer image: %w", err) + } + layers, err := refImg.Layers() + if err != nil { + return nil, nil, fmt.Errorf("error getting referrer image: %w", err) + } + layer0, err := layers[0].Uncompressed() + if err != nil { + return nil, nil, fmt.Errorf("error getting referrer image: %w", err) + } + bundleBytes, err = io.ReadAll(layer0) + if err != nil { + return nil, nil, fmt.Errorf("error getting referrer image: %w", err) + } + } + if len(refManifest.Manifests) == 0 || len(bundleBytes) == 0 { + return nil, nil, fmt.Errorf("no bundle found in referrers") + } + b := &bundle.ProtobufBundle{} + err = b.UnmarshalJSON(bundleBytes) + if err != nil { + return nil, nil, fmt.Errorf("error unmarshalling bundle: %w", err) + } + return b, &desc.Digest, nil +}