cert extension funcs are now policy methods

Signed-off-by: Meredith Lancaster <malancas@github.com>
This commit is contained in:
Meredith Lancaster 2024-10-29 17:27:47 -06:00
parent e5b2b09a6e
commit e16b69bd08
3 changed files with 101 additions and 41 deletions

View file

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
"github.com/sigstore/sigstore-go/pkg/verify"
@ -20,11 +21,13 @@ const (
)
type ExpectedExtensions struct {
RunnerEnvironment string
SANRegex string
SAN string
BuildSourceRepo string
SignerWorkflow string
RunnerEnvironment string
SANRegex string
SAN string
BuildSourceRepoURI string
SignerWorkflow string
SourceRepositoryOwnerURI string
SourceRepositoryURI string
}
type SigstoreInstance string
@ -40,10 +43,13 @@ type Policy struct {
ExpectedPredicateType string
ExpectedSigstoreInstance string
Artifact artifact.DigestedArtifact
OIDCIssuer string
}
func newPolicy(opts *Options, a artifact.DigestedArtifact) (Policy, error) {
p := Policy{}
p := Policy{
Artifact: a,
}
if opts.SignerRepo != "" {
signedRepoRegex := expandToGitHubURL(opts.Tenant, opts.SignerRepo)
@ -68,10 +74,25 @@ func newPolicy(opts *Options, a artifact.DigestedArtifact) (Policy, error) {
if opts.Repo != "" {
if opts.Tenant != "" {
p.ExpectedExtensions.BuildSourceRepo = fmt.Sprintf("https://%s.ghe.com/%s", opts.Tenant, opts.Repo)
p.ExpectedExtensions.BuildSourceRepoURI = fmt.Sprintf("https://%s.ghe.com/%s", opts.Tenant, opts.Repo)
}
p.ExpectedExtensions.BuildSourceRepo = fmt.Sprintf("https://github.com/%s", opts.Repo)
p.ExpectedExtensions.BuildSourceRepoURI = fmt.Sprintf("https://github.com/%s", opts.Repo)
}
if opts.Tenant != "" {
p.ExpectedExtensions.SourceRepositoryOwnerURI = fmt.Sprintf("https://%s.ghe.com/%s", opts.Tenant, opts.Owner)
} else {
p.ExpectedExtensions.SourceRepositoryOwnerURI = fmt.Sprintf("https://github.com/%s", opts.Owner)
}
// if issuer is anything other than the default, use the user-provided value;
// otherwise, select the appropriate default based on the tenant
if opts.Tenant != "" {
p.OIDCIssuer = fmt.Sprintf(verification.GitHubTenantOIDCIssuer, opts.Tenant)
} else {
p.OIDCIssuer = opts.OIDCIssuer
}
return p, nil
}
@ -115,6 +136,16 @@ func (p *Policy) buildCertificateIdentityOption() (verify.PolicyOption, error) {
return verify.WithCertificateIdentity(certId), nil
}
func (p *Policy) VerifyPredicateType(a []*api.Attestation) ([]*api.Attestation, error) {
filteredAttestations := verification.FilterAttestations(p.ExpectedPredicateType, a)
if len(filteredAttestations) == 0 {
return nil, fmt.Errorf("✗ No attestations found with predicate type: %s\n", p.ExpectedPredicateType)
}
return filteredAttestations, nil
}
func (p *Policy) SigstorePolicy() (verify.PolicyBuilder, error) {
artifactDigestPolicyOption, err := verification.BuildDigestPolicyOption(p.Artifact)
if err != nil {
@ -130,10 +161,6 @@ func (p *Policy) SigstorePolicy() (verify.PolicyBuilder, error) {
return policy, nil
}
func addSchemeToRegex(s string) string {
return fmt.Sprintf("^https://%s", s)
}
func validateSignerWorkflow(opts *Options) (string, error) {
// we expect a provided workflow argument be in the format [HOST/]/<OWNER>/<REPO>/path/to/workflow.yml
// if the provided workflow does not contain a host, set the host
@ -150,5 +177,55 @@ func validateSignerWorkflow(opts *Options) (string, error) {
return "", errors.New("unknown host")
}
return fmt.Sprintf("^https://%s/%s/%s", opts.Hostname, opts.SignerWorkflow), nil
return fmt.Sprintf("^https://%s/%s", opts.Hostname, opts.SignerWorkflow), nil
}
func (p *Policy) VerifyCertExtensions(results []*verification.AttestationProcessingResult) error {
if len(results) == 0 {
return errors.New("no attestations proccessing results")
}
var atLeastOneVerified bool
for _, attestation := range results {
if err := p.verifyCertExtensions(attestation); err != nil {
return err
}
atLeastOneVerified = true
}
if atLeastOneVerified {
return nil
} else {
return verification.ErrNoAttestationsVerified
}
}
func (p *Policy) verifyCertExtensions(attestation *verification.AttestationProcessingResult) error {
if p.ExpectedExtensions.SourceRepositoryOwnerURI != "" {
sourceRepositoryOwnerURI := attestation.VerificationResult.Signature.Certificate.Extensions.SourceRepositoryOwnerURI
if !strings.EqualFold(p.ExpectedExtensions.SourceRepositoryOwnerURI, sourceRepositoryOwnerURI) {
return fmt.Errorf("expected SourceRepositoryOwnerURI to be %s, got %s", p.ExpectedExtensions.SourceRepositoryOwnerURI, sourceRepositoryOwnerURI)
}
}
// if repo is set, check the SourceRepositoryURI field
if p.ExpectedExtensions.SourceRepositoryURI != "" {
sourceRepositoryURI := attestation.VerificationResult.Signature.Certificate.Extensions.SourceRepositoryURI
if !strings.EqualFold(p.ExpectedExtensions.SourceRepositoryURI, sourceRepositoryURI) {
return fmt.Errorf("expected SourceRepositoryURI to be %s, got %s", p.ExpectedExtensions.SourceRepositoryURI, sourceRepositoryURI)
}
}
if p.OIDCIssuer != "" {
certIssuer := attestation.VerificationResult.Signature.Certificate.Extensions.Issuer
if !strings.EqualFold(p.OIDCIssuer, certIssuer) {
if strings.Index(certIssuer, p.OIDCIssuer+"/") == 0 {
return fmt.Errorf("expected Issuer to be %s, got %s -- if you have a custom OIDC issuer policy for your enterprise, use the --cert-oidc-issuer flag with your expected issuer", p.OIDCIssuer, certIssuer)
} else {
return fmt.Errorf("expected Issuer to be %s, got %s", p.OIDCIssuer, certIssuer)
}
}
}
return nil
}

View file

@ -26,7 +26,7 @@ func TestBuildPolicy(t *testing.T) {
SANRegex: "^https://github.com/sigstore/",
}
_, err = buildVerifyPolicy(opts, *artifact)
_, err = newPolicy(opts, *artifact)
require.NoError(t, err)
}
@ -87,7 +87,6 @@ func TestValidateSignerWorkflow(t *testing.T) {
workflowRegex, err := validateSignerWorkflow(opts)
require.NoError(t, err)
require.Equal(t, tc.expectedWorkflowRegex, workflowRegex)
}
}

View file

@ -255,21 +255,9 @@ func runVerify(opts *Options) error {
attestations = filteredAttestations
}
policy, err := buildVerifyPolicy(opts, *artifact)
sigstoreResults, err := verifyAll(opts, *artifact, attestations)
if err != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to build verification policy"))
return err
}
sigstoreRes := opts.SigstoreVerifier.Verify(attestations, policy)
if sigstoreRes.Error != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Verification failed"))
return sigstoreRes.Error
}
// Verify extensions
if err := verification.VerifyCertExtensions(sigstoreRes.VerifyResults, opts.Tenant, opts.Owner, opts.Repo, opts.OIDCIssuer); err != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Verification failed"))
opts.Logger.Println(opts.Logger.ColorScheme.Red(err.Error()))
return err
}
@ -278,7 +266,7 @@ func runVerify(opts *Options) error {
// If an exporter is provided with the --json flag, write the results to the terminal in JSON format
if opts.exporter != nil {
// print the results to the terminal as an array of JSON objects
if err = opts.exporter.Write(opts.Logger.IO, sigstoreRes.VerifyResults); err != nil {
if err = opts.exporter.Write(opts.Logger.IO, sigstoreResults.VerifyResults); err != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to write JSON output"))
return err
}
@ -288,7 +276,7 @@ func runVerify(opts *Options) error {
opts.Logger.Printf("%s was attested by:\n", artifact.DigestWithAlg())
// Otherwise print the results to the terminal in a table
tableContent, err := buildTableVerifyContent(opts.Tenant, sigstoreRes.VerifyResults)
tableContent, err := buildTableVerifyContent(opts.Tenant, sigstoreResults.VerifyResults)
if err != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("failed to parse results"))
return err
@ -365,30 +353,26 @@ func buildTableVerifyContent(tenant string, results []*verification.AttestationP
return content, nil
}
func verifyAll(opts *Options, artifact artifact.DigestedArtifact, attestations []*api.Attestation) error {
func verifyAll(opts *Options, artifact artifact.DigestedArtifact, attestations []*api.Attestation) (*verification.SigstoreResults, error) {
policy, err := newPolicy(opts, artifact)
if err != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to build verification policy"))
return err
return nil, fmt.Errorf("✗ Failed to build verification policy")
}
sp, err := policy.SigstorePolicy()
if err != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to build verification policy"))
return err
return nil, fmt.Errorf("✗ Failed to build Sigstore verification policy")
}
sigstoreRes := opts.SigstoreVerifier.Verify(attestations, sp)
if sigstoreRes.Error != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Verification failed"))
return sigstoreRes.Error
return nil, fmt.Errorf("✗ Sigstore verification failed")
}
// Verify extensions
if err := verification.VerifyCertExtensions(sigstoreRes.VerifyResults, opts.Tenant, opts.Owner, opts.Repo, opts.OIDCIssuer); err != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Verification failed"))
return err
return nil, fmt.Errorf("✗ Policy verification failed")
}
return nil
return sigstoreRes, nil
}