Merge pull request #10750 from malancas/refactor-sigstore-verifier-logic
Refactor Sigstore verifier logic
This commit is contained in:
commit
6dbb3fe541
6 changed files with 148 additions and 70 deletions
|
|
@ -105,7 +105,11 @@ func NewInspectCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
|
|||
config.TrustDomain = td
|
||||
}
|
||||
|
||||
opts.SigstoreVerifier = verification.NewLiveSigstoreVerifier(config)
|
||||
sgVerifier, err := verification.NewLiveSigstoreVerifier(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Sigstore verifier: %w", err)
|
||||
}
|
||||
opts.SigstoreVerifier = sgVerifier
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
|
|
|
|||
|
|
@ -44,12 +44,11 @@ type SigstoreVerifier interface {
|
|||
}
|
||||
|
||||
type LiveSigstoreVerifier struct {
|
||||
TrustedRoot string
|
||||
Logger *io.Handler
|
||||
NoPublicGood bool
|
||||
// If tenancy mode is not used, trust domain is empty
|
||||
TrustDomain string
|
||||
TUFMetadataDir o.Option[string]
|
||||
PublicGood *verify.SignedEntityVerifier
|
||||
GitHub *verify.SignedEntityVerifier
|
||||
Custom map[string]*verify.SignedEntityVerifier
|
||||
}
|
||||
|
||||
var ErrNoAttestationsVerified = errors.New("no attestations were verified")
|
||||
|
|
@ -57,56 +56,43 @@ var ErrNoAttestationsVerified = errors.New("no attestations were verified")
|
|||
// NewLiveSigstoreVerifier creates a new LiveSigstoreVerifier struct
|
||||
// that is used to verify artifacts and attestations against the
|
||||
// Public Good, GitHub, or a custom trusted root.
|
||||
func NewLiveSigstoreVerifier(config SigstoreConfig) *LiveSigstoreVerifier {
|
||||
return &LiveSigstoreVerifier{
|
||||
TrustedRoot: config.TrustedRoot,
|
||||
Logger: config.Logger,
|
||||
NoPublicGood: config.NoPublicGood,
|
||||
TrustDomain: config.TrustDomain,
|
||||
TUFMetadataDir: config.TUFMetadataDir,
|
||||
func NewLiveSigstoreVerifier(config SigstoreConfig) (*LiveSigstoreVerifier, error) {
|
||||
liveVerifier := &LiveSigstoreVerifier{
|
||||
Logger: config.Logger,
|
||||
NoPublicGood: config.NoPublicGood,
|
||||
}
|
||||
}
|
||||
|
||||
func getBundleIssuer(b *bundle.Bundle) (string, error) {
|
||||
if !b.MinVersion("0.2") {
|
||||
return "", fmt.Errorf("unsupported bundle version: %s", b.MediaType)
|
||||
}
|
||||
verifyContent, err := b.VerificationContent()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get bundle verification content: %v", err)
|
||||
}
|
||||
leafCert := verifyContent.Certificate()
|
||||
if leafCert == nil {
|
||||
return "", fmt.Errorf("leaf cert not found")
|
||||
}
|
||||
if len(leafCert.Issuer.Organization) != 1 {
|
||||
return "", fmt.Errorf("expected the leaf certificate issuer to only have one organization")
|
||||
}
|
||||
return leafCert.Issuer.Organization[0], nil
|
||||
}
|
||||
|
||||
func (v *LiveSigstoreVerifier) chooseVerifier(issuer string) (*verify.SignedEntityVerifier, error) {
|
||||
// if no custom trusted root is set, attempt to create a Public Good or
|
||||
// GitHub Sigstore verifier
|
||||
if v.TrustedRoot == "" {
|
||||
switch issuer {
|
||||
case PublicGoodIssuerOrg:
|
||||
if v.NoPublicGood {
|
||||
return nil, fmt.Errorf("detected public good instance but requested verification without public good instance")
|
||||
}
|
||||
return newPublicGoodVerifier(v.TUFMetadataDir)
|
||||
case GitHubIssuerOrg:
|
||||
return newGitHubVerifier(v.TrustDomain, v.TUFMetadataDir)
|
||||
default:
|
||||
return nil, fmt.Errorf("leaf certificate issuer is not recognized")
|
||||
// if a custom trusted root is set, configure custom verifiers
|
||||
if config.TrustedRoot != "" {
|
||||
customVerifiers, err := createCustomVerifiers(config.TrustedRoot, config.NoPublicGood)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
liveVerifier.Custom = customVerifiers
|
||||
return liveVerifier, nil
|
||||
}
|
||||
|
||||
customTrustRoots, err := os.ReadFile(v.TrustedRoot)
|
||||
if !config.NoPublicGood {
|
||||
publicGoodVerifier, err := newPublicGoodVerifier(config.TUFMetadataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
liveVerifier.PublicGood = publicGoodVerifier
|
||||
}
|
||||
github, err := newGitHubVerifier(config.TrustDomain, config.TUFMetadataDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read file %s: %v", v.TrustedRoot, err)
|
||||
return nil, err
|
||||
}
|
||||
liveVerifier.GitHub = github
|
||||
|
||||
return liveVerifier, nil
|
||||
}
|
||||
|
||||
func createCustomVerifiers(trustedRoot string, noPublicGood bool) (map[string]*verify.SignedEntityVerifier, error) {
|
||||
customTrustRoots, err := os.ReadFile(trustedRoot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read file %s: %v", trustedRoot, err)
|
||||
}
|
||||
|
||||
verifiers := make(map[string]*verify.SignedEntityVerifier)
|
||||
reader := bufio.NewReader(bytes.NewReader(customTrustRoots))
|
||||
var line []byte
|
||||
var readError error
|
||||
|
|
@ -130,10 +116,11 @@ func (v *LiveSigstoreVerifier) chooseVerifier(issuer string) (*verify.SignedEnti
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// if the custom trusted root issuer is not set or doesn't match the given issuer, skip it
|
||||
if len(lowestCert.Issuer.Organization) == 0 || lowestCert.Issuer.Organization[0] != issuer {
|
||||
// if the custom trusted root issuer is not set, skip it
|
||||
if len(lowestCert.Issuer.Organization) == 0 {
|
||||
continue
|
||||
}
|
||||
issuer := lowestCert.Issuer.Organization[0]
|
||||
|
||||
// Determine what policy to use with this trusted root.
|
||||
//
|
||||
|
|
@ -141,21 +128,88 @@ func (v *LiveSigstoreVerifier) chooseVerifier(issuer string) (*verify.SignedEnti
|
|||
// issuer. We *must* use the trusted root provided.
|
||||
switch issuer {
|
||||
case PublicGoodIssuerOrg:
|
||||
if v.NoPublicGood {
|
||||
if noPublicGood {
|
||||
return nil, fmt.Errorf("detected public good instance but requested verification without public good instance")
|
||||
}
|
||||
return newPublicGoodVerifierWithTrustedRoot(trustedRoot)
|
||||
if _, ok := verifiers[PublicGoodIssuerOrg]; ok {
|
||||
// we have already created a public good verifier with this custom trusted root
|
||||
// so we skip it
|
||||
continue
|
||||
}
|
||||
publicGood, err := newPublicGoodVerifierWithTrustedRoot(trustedRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verifiers[PublicGoodIssuerOrg] = publicGood
|
||||
case GitHubIssuerOrg:
|
||||
return newGitHubVerifierWithTrustedRoot(trustedRoot)
|
||||
if _, ok := verifiers[GitHubIssuerOrg]; ok {
|
||||
// we have already created a github verifier with this custom trusted root
|
||||
// so we skip it
|
||||
continue
|
||||
}
|
||||
github, err := newGitHubVerifierWithTrustedRoot(trustedRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verifiers[GitHubIssuerOrg] = github
|
||||
default:
|
||||
if _, ok := verifiers[issuer]; ok {
|
||||
// we have already created a custom verifier with this custom trusted root
|
||||
// so we skip it
|
||||
continue
|
||||
}
|
||||
// Make best guess at reasonable policy
|
||||
return newCustomVerifier(trustedRoot)
|
||||
custom, err := newCustomVerifier(trustedRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verifiers[issuer] = custom
|
||||
}
|
||||
}
|
||||
line, readError = reader.ReadBytes('\n')
|
||||
}
|
||||
return verifiers, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unable to use provided trusted roots")
|
||||
func getBundleIssuer(b *bundle.Bundle) (string, error) {
|
||||
if !b.MinVersion("0.2") {
|
||||
return "", fmt.Errorf("unsupported bundle version: %s", b.MediaType)
|
||||
}
|
||||
verifyContent, err := b.VerificationContent()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get bundle verification content: %v", err)
|
||||
}
|
||||
leafCert := verifyContent.Certificate()
|
||||
if leafCert == nil {
|
||||
return "", fmt.Errorf("leaf cert not found")
|
||||
}
|
||||
if len(leafCert.Issuer.Organization) != 1 {
|
||||
return "", fmt.Errorf("expected the leaf certificate issuer to only have one organization")
|
||||
}
|
||||
return leafCert.Issuer.Organization[0], nil
|
||||
}
|
||||
|
||||
func (v *LiveSigstoreVerifier) chooseVerifier(issuer string) (*verify.SignedEntityVerifier, error) {
|
||||
// if no custom trusted root is set, return either the Public Good or GitHub verifier
|
||||
// If the chosen verifier has not yet been created, create it as a LiveSigstoreVerifier field for use in future calls
|
||||
if v.Custom != nil {
|
||||
custom, ok := v.Custom[issuer]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no custom verifier found for issuer \"%s\"", issuer)
|
||||
}
|
||||
return custom, nil
|
||||
}
|
||||
switch issuer {
|
||||
case PublicGoodIssuerOrg:
|
||||
if v.NoPublicGood {
|
||||
return nil, fmt.Errorf("detected public good instance but requested verification without public good instance")
|
||||
}
|
||||
return v.PublicGood, nil
|
||||
case GitHubIssuerOrg:
|
||||
return v.GitHub, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("leaf certificate issuer is not recognized")
|
||||
}
|
||||
}
|
||||
|
||||
func getLowestCertInChain(ca *root.FulcioCertificateAuthority) (*x509.Certificate, error) {
|
||||
|
|
@ -177,7 +231,7 @@ func (v *LiveSigstoreVerifier) verify(attestation *api.Attestation, policy verif
|
|||
// determine which verifier should attempt verification against the bundle
|
||||
verifier, err := v.chooseVerifier(issuer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find recognized issuer from bundle content: %v", err)
|
||||
return nil, fmt.Errorf("failed to choose verifier based on provided bundle issuer: %v", err)
|
||||
}
|
||||
|
||||
v.Logger.VerbosePrintf("Attempting verification against issuer \"%s\"\n", issuer)
|
||||
|
|
|
|||
|
|
@ -50,10 +50,11 @@ func TestLiveSigstoreVerifier(t *testing.T) {
|
|||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
verifier := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
verifier, err := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
Logger: io.NewTestHandler(),
|
||||
TUFMetadataDir: o.Some(t.TempDir()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
results, err := verifier.Verify(tc.attestations, publicGoodPolicy(t))
|
||||
|
||||
|
|
@ -69,10 +70,11 @@ func TestLiveSigstoreVerifier(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run("with 2/3 verified attestations", func(t *testing.T) {
|
||||
verifier := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
verifier, err := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
Logger: io.NewTestHandler(),
|
||||
TUFMetadataDir: o.Some(t.TempDir()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
invalidBundle := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0-bundle-v0.1.json")
|
||||
attestations := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl")
|
||||
|
|
@ -86,10 +88,11 @@ func TestLiveSigstoreVerifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("fail with 0/2 verified attestations", func(t *testing.T) {
|
||||
verifier := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
verifier, err := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
Logger: io.NewTestHandler(),
|
||||
TUFMetadataDir: o.Some(t.TempDir()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
invalidBundle := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0-bundle-v0.1.json")
|
||||
attestations := getAttestationsFor(t, "../test/data/sigstoreBundle-invalid-signature.json")
|
||||
|
|
@ -110,10 +113,11 @@ func TestLiveSigstoreVerifier(t *testing.T) {
|
|||
|
||||
attestations := getAttestationsFor(t, "../test/data/github_provenance_demo-0.0.12-py3-none-any-bundle.jsonl")
|
||||
|
||||
verifier := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
verifier, err := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
Logger: io.NewTestHandler(),
|
||||
TUFMetadataDir: o.Some(t.TempDir()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
results, err := verifier.Verify(attestations, githubPolicy)
|
||||
require.Len(t, results, 1)
|
||||
|
|
@ -123,11 +127,12 @@ func TestLiveSigstoreVerifier(t *testing.T) {
|
|||
t.Run("with custom trusted root", func(t *testing.T) {
|
||||
attestations := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl")
|
||||
|
||||
verifier := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
verifier, err := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
Logger: io.NewTestHandler(),
|
||||
TrustedRoot: test.NormalizeRelativePath("../test/data/trusted_root.json"),
|
||||
TUFMetadataDir: o.Some(t.TempDir()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
results, err := verifier.Verify(attestations, publicGoodPolicy(t))
|
||||
require.Len(t, results, 2)
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ func getAttestationsFor(t *testing.T, bundlePath string) []*api.Attestation {
|
|||
}
|
||||
|
||||
func TestVerifyAttestations(t *testing.T) {
|
||||
sgVerifier := verification.NewLiveSigstoreVerifier(verification.SigstoreConfig{
|
||||
sgVerifier, err := verification.NewLiveSigstoreVerifier(verification.SigstoreConfig{
|
||||
Logger: io.NewTestHandler(),
|
||||
TUFMetadataDir: o.Some(t.TempDir()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
certSummary := certificate.Summary{}
|
||||
certSummary.SourceRepositoryOwnerURI = "https://github.com/sigstore"
|
||||
|
|
|
|||
|
|
@ -211,7 +211,11 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
|
|||
return runF(opts)
|
||||
}
|
||||
|
||||
opts.SigstoreVerifier = verification.NewLiveSigstoreVerifier(config)
|
||||
sigstoreVerifier, err := verification.NewLiveSigstoreVerifier(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating Sigstore verifier: %w", err)
|
||||
}
|
||||
opts.SigstoreVerifier = sigstoreVerifier
|
||||
opts.Config = f.Config
|
||||
|
||||
if err := runVerify(opts); err != nil {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ func TestVerifyIntegration(t *testing.T) {
|
|||
|
||||
host, _ := auth.DefaultHost()
|
||||
|
||||
sigstoreVerifier, err := verification.NewLiveSigstoreVerifier(sigstoreConfig)
|
||||
require.NoError(t, err)
|
||||
publicGoodOpts := Options{
|
||||
APIClient: api.NewLiveClient(hc, host, logger),
|
||||
ArtifactPath: artifactPath,
|
||||
|
|
@ -44,7 +46,7 @@ func TestVerifyIntegration(t *testing.T) {
|
|||
Owner: "sigstore",
|
||||
PredicateType: verification.SLSAPredicateV1,
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
|
||||
SigstoreVerifier: sigstoreVerifier,
|
||||
}
|
||||
|
||||
t.Run("with valid owner", func(t *testing.T) {
|
||||
|
|
@ -106,6 +108,8 @@ func TestVerifyIntegration(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("with bundle from OCI registry", func(t *testing.T) {
|
||||
sigstoreVerifier, err := verification.NewLiveSigstoreVerifier(sigstoreConfig)
|
||||
require.NoError(t, err)
|
||||
opts := Options{
|
||||
APIClient: api.NewLiveClient(hc, host, logger),
|
||||
ArtifactPath: "oci://ghcr.io/github/artifact-attestations-helm-charts/policy-controller:v0.10.0-github9",
|
||||
|
|
@ -117,10 +121,10 @@ func TestVerifyIntegration(t *testing.T) {
|
|||
Owner: "github",
|
||||
PredicateType: verification.SLSAPredicateV1,
|
||||
SANRegex: "^https://github.com/github/",
|
||||
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
|
||||
SigstoreVerifier: sigstoreVerifier,
|
||||
}
|
||||
|
||||
err := runVerify(&opts)
|
||||
err = runVerify(&opts)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
@ -145,6 +149,8 @@ func TestVerifyIntegrationCustomIssuer(t *testing.T) {
|
|||
|
||||
host, _ := auth.DefaultHost()
|
||||
|
||||
sigstoreVerifier, err := verification.NewLiveSigstoreVerifier(sigstoreConfig)
|
||||
require.NoError(t, err)
|
||||
baseOpts := Options{
|
||||
APIClient: api.NewLiveClient(hc, host, logger),
|
||||
ArtifactPath: artifactPath,
|
||||
|
|
@ -154,7 +160,7 @@ func TestVerifyIntegrationCustomIssuer(t *testing.T) {
|
|||
OCIClient: oci.NewLiveClient(),
|
||||
OIDCIssuer: "https://token.actions.githubusercontent.com/hammer-time",
|
||||
PredicateType: verification.SLSAPredicateV1,
|
||||
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
|
||||
SigstoreVerifier: sigstoreVerifier,
|
||||
}
|
||||
|
||||
t.Run("with owner and valid workflow SAN", func(t *testing.T) {
|
||||
|
|
@ -216,6 +222,8 @@ func TestVerifyIntegrationReusableWorkflow(t *testing.T) {
|
|||
|
||||
host, _ := auth.DefaultHost()
|
||||
|
||||
sigstoreVerifier, err := verification.NewLiveSigstoreVerifier(sigstoreConfig)
|
||||
require.NoError(t, err)
|
||||
baseOpts := Options{
|
||||
APIClient: api.NewLiveClient(hc, host, logger),
|
||||
ArtifactPath: artifactPath,
|
||||
|
|
@ -225,7 +233,7 @@ func TestVerifyIntegrationReusableWorkflow(t *testing.T) {
|
|||
OCIClient: oci.NewLiveClient(),
|
||||
OIDCIssuer: verification.GitHubOIDCIssuer,
|
||||
PredicateType: verification.SLSAPredicateV1,
|
||||
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
|
||||
SigstoreVerifier: sigstoreVerifier,
|
||||
}
|
||||
|
||||
t.Run("with owner and valid reusable workflow SAN", func(t *testing.T) {
|
||||
|
|
@ -306,6 +314,8 @@ func TestVerifyIntegrationReusableWorkflowSignerWorkflow(t *testing.T) {
|
|||
|
||||
host, _ := auth.DefaultHost()
|
||||
|
||||
sigstoreVerifier, err := verification.NewLiveSigstoreVerifier(sigstoreConfig)
|
||||
require.NoError(t, err)
|
||||
baseOpts := Options{
|
||||
APIClient: api.NewLiveClient(hc, host, logger),
|
||||
ArtifactPath: artifactPath,
|
||||
|
|
@ -318,7 +328,7 @@ func TestVerifyIntegrationReusableWorkflowSignerWorkflow(t *testing.T) {
|
|||
Owner: "malancas",
|
||||
PredicateType: verification.SLSAPredicateV1,
|
||||
Repo: "malancas/attest-demo",
|
||||
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
|
||||
SigstoreVerifier: sigstoreVerifier,
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue