Files
Ramon PetgraveandGitHub 4cd7d4802e chore: update go and golanci lint (#810)
This PR updates go to 1.23.1 and updates golanci-lint to v1.61.1, while
fixing new lint errors.

---------

Signed-off-by: Ramon Petgrave <ramon.petgrave64@gmail.com>
Signed-off-by: Ramon Petgrave <32398091+ramonpetgrave64@users.noreply.github.com>
2024-10-10 13:07:08 -04:00

1338 lines
40 KiB
Go

package gha
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"net/url"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
fulcio "github.com/sigstore/fulcio/pkg/certificate"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/options"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
)
var (
refs123 = "@refs/tags/v1.2.3"
githubWorkflows = "/.github/workflows/"
builderGoSlsa3 = githubWorkflows + "builder_go_slsa3.yml"
builderGoSlsa3GitURL = httpsGithubCom + trustedBuilderRepository + builderGoSlsa3
delegatorGenericSlsa3 = githubWorkflows + "delegator_generic_slsa3.yml"
generatorGenericSlsa3 = githubWorkflows + "generator_generic_slsa3.yml"
)
// Must checks the error and panics if not nil.
func Must[T any](val T, err error) T {
if err != nil {
panic(err)
}
return val
}
func Test_VerifyBuilderIdentity(t *testing.T) {
t.Parallel()
tests := []struct {
name string
workflow *WorkflowIdentity
buildOpts *options.BuilderOpts
builderID string
defaults map[string]bool
err error
byob bool
}{
{
name: "invalid job workflow ref",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse("https://github.com/random/workflow/ref")),
BuildTrigger: "workflow_dispatch",
Issuer: "https://token.actions.githubusercontent.com",
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorMalformedURI,
},
{
name: "untrusted job workflow ref",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse("https://github.com/malicious/slsa-go/.github/workflows/builder.yml@refs/tags/v1.2.3")),
BuildTrigger: "workflow_dispatch",
Issuer: "https://token.actions.githubusercontent.com",
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "untrusted job workflow ref for general repos",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + "@refs/heads/main")),
BuildTrigger: "workflow_dispatch",
Issuer: "https://token.actions.githubusercontent.com",
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorInvalidRef,
},
{
name: "untrusted cert issuer for general repos",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: "https://bad.issuer.com",
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorInvalidOIDCIssuer,
},
{
name: "valid trusted builder without tag",
workflow: &WorkflowIdentity{
SourceRepository: trustedBuilderRepository,
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: "https://token.actions.githubusercontent.com",
},
defaults: defaultArtifactTrustedReusableWorkflows,
builderID: builderGoSlsa3GitURL,
},
{
name: "valid generic delegator builder without tag",
workflow: &WorkflowIdentity{
SourceRepository: trustedBuilderRepository,
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GenericDelegatorBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: "https://token.actions.githubusercontent.com",
},
defaults: defaultBYOBReusableWorkflows,
builderID: httpsGithubCom + trustedBuilderRepository + delegatorGenericSlsa3,
byob: true,
},
{
name: "valid low-perms delegator builder with short tag",
workflow: &WorkflowIdentity{
SourceRepository: trustedBuilderRepository,
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GenericLowPermsDelegatorBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: "https://token.actions.githubusercontent.com",
},
defaults: defaultBYOBReusableWorkflows,
builderID: httpsGithubCom + trustedBuilderRepository + "/.github/workflows/delegator_lowperms-generic_slsa3.yml@v1.2.3",
byob: true,
},
{
name: "valid main ref for e2e test",
workflow: &WorkflowIdentity{
SourceRepository: e2eTestRepository,
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
defaults: defaultArtifactTrustedReusableWorkflows,
builderID: builderGoSlsa3GitURL,
},
{
name: "valid main ref for e2e test - match builderID",
workflow: &WorkflowIdentity{
SourceRepository: e2eTestRepository,
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
buildOpts: &options.BuilderOpts{
ExpectedID: asStringPointer(builderGoSlsa3GitURL),
},
defaults: defaultArtifactTrustedReusableWorkflows,
builderID: builderGoSlsa3GitURL,
},
{
name: "valid main ref for e2e test - mismatch builderID",
workflow: &WorkflowIdentity{
SourceRepository: e2eTestRepository,
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
buildOpts: &options.BuilderOpts{
ExpectedID: asStringPointer("some-other-builderID"),
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "valid workflow identity - match builderID",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
buildOpts: &options.BuilderOpts{
ExpectedID: asStringPointer(builderGoSlsa3GitURL),
},
defaults: defaultArtifactTrustedReusableWorkflows,
builderID: builderGoSlsa3GitURL,
},
{
name: "valid workflow identity - mismatch builderID",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
buildOpts: &options.BuilderOpts{
ExpectedID: asStringPointer("some-other-builderID"),
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "invalid workflow identity with prerelease",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + "@refs/tags/v1.2.3-alpha")),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
err: serrors.ErrorInvalidRef,
defaults: defaultArtifactTrustedReusableWorkflows,
builderID: builderGoSlsa3GitURL,
},
{
name: "invalid workflow identity with build",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + "@refs/tags/v1.2.3+123")),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorInvalidRef,
},
{
name: "invalid workflow identity with metadata",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + "@refs/tags/v1.2.3-alpha+123")),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorInvalidRef,
},
{
name: "valid workflow identity with fully qualified source",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
defaults: defaultArtifactTrustedReusableWorkflows,
builderID: builderGoSlsa3GitURL,
},
{
name: "valid workflow identity with fully qualified source - no default",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
buildOpts: &options.BuilderOpts{
ExpectedID: asStringPointer(builderGoSlsa3GitURL),
},
builderID: builderGoSlsa3GitURL,
},
{
name: "valid workflow identity with fully qualified source - match builderID",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
buildOpts: &options.BuilderOpts{
ExpectedID: asStringPointer(builderGoSlsa3GitURL),
},
defaults: defaultArtifactTrustedReusableWorkflows,
builderID: builderGoSlsa3GitURL,
},
{
name: "valid workflow identity with fully qualified source - mismatch builderID",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
buildOpts: &options.BuilderOpts{
ExpectedID: asStringPointer("some-other-builderID"),
},
defaults: defaultArtifactTrustedReusableWorkflows,
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "valid workflow identity with fully qualified source - mismatch defaults",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
defaults: defaultContainerTrustedReusableWorkflows,
err: serrors.ErrorUntrustedReusableWorkflow,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
opts := tt.buildOpts
if opts == nil {
opts = &options.BuilderOpts{}
}
if tt.builderID != "" {
opts.ExpectedID = &tt.builderID
}
id, byob, err := VerifyBuilderIdentity(tt.workflow, opts, tt.defaults)
if byob != tt.byob {
t.Errorf("unexpected byob value:\n%s", cmp.Diff(tt.byob, byob))
}
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("unexpected error (-want +got):\n%s", diff)
}
if err != nil {
return
}
if err := id.MatchesLoose(tt.builderID, true); err != nil {
t.Errorf("matches failed:%v", err)
}
})
}
}
func Test_isTrustedDelegatorBuilder(t *testing.T) {
t.Parallel()
tests := []struct {
name string
certBuilderID string
trustedBuilderIDs map[string]bool
result bool
}{
{
name: "match byob",
certBuilderID: common.GenericLowPermsDelegatorBuilderID + "@refs/tags/v1.6.0",
trustedBuilderIDs: map[string]bool{
common.GenericLowPermsDelegatorBuilderID: true,
"https://github.com/slsa-framework/slsa-github-generator/.github/workflows/some_delegator.yml": true,
},
result: true,
},
{
name: "match byob but not caller trusted",
certBuilderID: common.GenericLowPermsDelegatorBuilderID + "@refs/tags/v1.6.0",
trustedBuilderIDs: map[string]bool{
"slsa-framework/slsa-github-generator/.github/workflows/some_other_delegator.yml": true,
"slsa-framework/slsa-github-generator/.github/workflows/some_delegator.yml": true,
},
result: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
trustedBuilderID, err := utils.TrustedBuilderIDNew(tt.certBuilderID, true)
if err != nil {
t.Fatal(err.Error())
}
res := isTrustedDelegatorBuilder(trustedBuilderID, tt.trustedBuilderIDs)
if res != tt.result {
t.Error(cmp.Diff(res, tt.result))
}
})
}
}
func Test_VerifyCertficateSourceRepository(t *testing.T) {
t.Parallel()
tests := []struct {
name string
workflow *WorkflowIdentity
source string
err error
}{
{
name: "repo match",
workflow: &WorkflowIdentity{
SourceRepository: "asraa/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
source: "github.com/asraa/slsa-on-github-test",
},
{
name: "unexpected source for e2e test",
workflow: &WorkflowIdentity{
SourceRepository: e2eTestRepository,
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
source: "malicious/source",
err: serrors.ErrorMismatchSource,
},
{
name: "valid main ref for builder",
workflow: &WorkflowIdentity{
SourceRepository: trustedBuilderRepository,
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
source: "malicious/source",
err: serrors.ErrorMismatchSource,
},
{
name: "unexpected source",
workflow: &WorkflowIdentity{
SourceRepository: "malicious/slsa-on-github-test",
SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b",
SubjectWorkflow: Must(url.Parse(common.GoBuilderID + refs123)),
BuildTrigger: "workflow_dispatch",
Issuer: certOidcIssuer,
},
source: "asraa/slsa-on-github-test",
err: serrors.ErrorMismatchSource,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := VerifyCertficateSourceRepository(tt.workflow, tt.source)
if !errCmp(err, tt.err) {
t.Error(cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
}
})
}
}
func asStringPointer(s string) *string {
return &s
}
func Test_verifyTrustedBuilderID(t *testing.T) {
t.Parallel()
tests := []struct {
name string
id *string
path string
tag string
defaults map[string]bool
err error
byob bool
}{
{
name: "default trusted short tag",
path: trustedBuilderRepository + generatorGenericSlsa3,
tag: "v1.2.3",
defaults: defaultArtifactTrustedReusableWorkflows,
},
{
name: "default trusted long tag",
path: trustedBuilderRepository + generatorGenericSlsa3,
tag: "refs/tags/v1.2.3",
defaults: defaultArtifactTrustedReusableWorkflows,
},
{
name: "generic delegator workflow long tag",
path: trustedBuilderRepository + delegatorGenericSlsa3,
id: asStringPointer(trustedBuilderRepository + "/.github/workflows/delegator_generic_slsa3.yml@refs/tags/v1.2.3"),
tag: "refs/tags/v1.2.3",
defaults: defaultBYOBReusableWorkflows,
byob: true,
},
{
// This is a BYOB workflow without an id that tests non-compulsory builder-id
// feature of slsa-verifier and expects byob to be true
name: "generic delegator workflow no id",
path: trustedBuilderRepository + delegatorGenericSlsa3,
// NOTE: id is nil.
id: nil,
tag: "refs/tags/v1.2.3",
defaults: defaultBYOBReusableWorkflows,
byob: true,
},
{
name: "low perms delegator workflow short tag",
path: trustedBuilderRepository + "/.github/workflows/delegator_lowperms-generic_slsa3.yml",
id: asStringPointer(trustedBuilderRepository + "/.github/workflows/delegator_lowperms-generic_slsa3.yml@refs/tags/v1.2.3"),
tag: "v1.2.3",
defaults: defaultBYOBReusableWorkflows,
byob: true,
},
{
// This is a BYOB workflow without an id that tests non-compulsory builder-id
// feature of slsa-verifier and expects byob to be true
name: "low perms delegator workflow no ID provided",
path: trustedBuilderRepository + "/.github/workflows/delegator_lowperms-generic_slsa3.yml",
// NOTE: id is nil.
id: nil,
tag: "v1.2.3",
defaults: defaultBYOBReusableWorkflows,
byob: true,
},
{
name: "default mismatch against container defaults long tag",
path: trustedBuilderRepository + generatorGenericSlsa3,
tag: "refs/tags/v1.2.3",
defaults: defaultContainerTrustedReusableWorkflows,
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "valid ID for GitHub builder short tag",
path: "some/repo/someBuilderID",
tag: "v1.2.3",
id: asStringPointer("https://github.com/some/repo/someBuilderID@v1.2.3"),
},
{
name: "valid ID for GitHub builder long tag",
path: "some/repo/someBuilderID",
tag: "refs/tags/v1.2.3",
id: asStringPointer("https://github.com/some/repo/someBuilderID@refs/tags/v1.2.3"),
},
{
name: "valid short ID for GitHub builder long tag",
path: "some/repo/someBuilderID",
tag: "refs/tags/v1.2.3",
id: asStringPointer("https://github.com/some/repo/someBuilderID@v1.2.3"),
},
{
name: "valid long ID for GitHub builder short tag",
path: "some/repo/someBuilderID",
tag: "v1.2.3",
id: asStringPointer("https://github.com/some/repo/someBuilderID@refs/tags/v1.2.3"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "valid ID for GitHub builder long tag",
path: "some/repo/someBuilderID",
tag: "refs/tags/v1.2.3",
id: asStringPointer("https://github.com/some/repo/someBuilderID@refs/tags/v1.2.3"),
},
{
name: "valid ID for GitHub builder short tag",
path: "some/repo/someBuilderID",
tag: "v1.2.3",
id: asStringPointer("https://github.com/some/repo/someBuilderID@v1.2.3"),
},
{
name: "valid short ID for GitHub builder long tag",
path: "some/repo/someBuilderID",
tag: "refs/tags/v1.2.3",
id: asStringPointer("https://github.com/some/repo/someBuilderID@v1.2.3"),
},
{
name: "valid long ID for GitHub builder short tag",
path: "some/repo/someBuilderID",
tag: "v1.2.3",
id: asStringPointer("https://github.com/some/repo/someBuilderID@refs/tags/v1.2.3"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "non GitHub builder ID long builder tag",
path: "some/repo/someBuilderID",
tag: "refs/tags/v1.2.3",
id: asStringPointer("https://not-github.com/some/repo/someBuilderID"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "mismatch org GitHub short builder tag",
path: "some/repo/someBuilderID",
tag: "v1.2.3",
id: asStringPointer("https://github.com/other/repo/someBuilderID"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "mismatch org GitHub long builder tag",
path: "some/repo/someBuilderID",
tag: "refs/tags/v1.2.3",
id: asStringPointer("https://github.com/other/repo/someBuilderID"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "mismatch name GitHub long builder tag",
path: "some/repo/someBuilderID",
tag: "refs/tags/v1.2.3",
id: asStringPointer("https://github.com/some/other/someBuilderID"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "mismatch id GitHub long builder tag",
path: "some/repo/someBuilderID",
tag: "refs/tags/v1.2.3",
id: asStringPointer("https://github.com/some/repo/ID"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
id, byob, err := verifyTrustedBuilderID(httpsGithubCom+tt.path, tt.tag, tt.id, tt.defaults)
if byob != tt.byob {
t.Error(cmp.Diff(byob, tt.byob))
}
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got):\n%s", diff)
}
if tt.err == nil {
expectedID := httpsGithubCom + tt.path + "@" + tt.tag
if err := id.MatchesLoose(expectedID, true); err != nil {
t.Errorf("matches failed:%v", err)
}
}
})
}
}
func Test_verifyTrustedBuilderRef(t *testing.T) {
tests := []struct {
name string
callerRepo string
builderRef string
expected error
testingEnabled bool
}{
// Trusted repo.
{
name: "main not allowed for builder",
callerRepo: trustedBuilderRepository,
builderRef: "refs/heads/main",
expected: serrors.ErrorInvalidRef,
},
{
name: "main allowed for builder w/ testing enabled",
callerRepo: trustedBuilderRepository,
builderRef: "refs/heads/main",
testingEnabled: true,
},
{
name: "full semver for builder",
callerRepo: trustedBuilderRepository,
builderRef: "refs/tags/v1.2.3",
},
{
name: "no patch semver for other builder",
callerRepo: trustedBuilderRepository,
builderRef: "refs/tags/v1.2",
expected: serrors.ErrorInvalidRef,
},
{
name: "no min semver for builder",
callerRepo: trustedBuilderRepository,
builderRef: "refs/tags/v1",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with prerelease for builder",
callerRepo: trustedBuilderRepository,
builderRef: "refs/tags/v1.2.3-alpha",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with build for builder",
callerRepo: trustedBuilderRepository,
builderRef: "refs/tags/v1.2.3+123",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with build/prerelease for builder",
callerRepo: trustedBuilderRepository,
builderRef: "refs/tags/v1.2.3-alpha+123",
expected: serrors.ErrorInvalidRef,
},
// E2e tests repo.
{
name: "main not allowed for test repo",
callerRepo: e2eTestRepository,
builderRef: "refs/heads/main",
expected: serrors.ErrorInvalidRef,
},
{
name: "main allowed for test repo w/ testing enabled",
callerRepo: e2eTestRepository,
builderRef: "refs/heads/main",
testingEnabled: true,
},
{
name: "full semver for test repo",
callerRepo: e2eTestRepository,
builderRef: "refs/tags/v1.2.3",
},
{
name: "no patch semver for test repo",
callerRepo: e2eTestRepository,
builderRef: "refs/tags/v1.2",
expected: serrors.ErrorInvalidRef,
},
{
name: "no min semver for test repo",
callerRepo: e2eTestRepository,
builderRef: "refs/tags/v1",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with prerelease for test repo",
callerRepo: e2eTestRepository,
builderRef: "refs/tags/v1.2.3-alpha",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with build for test repo",
callerRepo: e2eTestRepository,
builderRef: "refs/tags/v1.2.3+123",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with build/prerelease for test repo",
callerRepo: e2eTestRepository,
builderRef: "refs/tags/v1.2.3-alpha+123",
expected: serrors.ErrorInvalidRef,
},
// Other repos.
{
name: "main not allowed for other repos",
callerRepo: "some/repo",
builderRef: "refs/heads/main",
expected: serrors.ErrorInvalidRef,
},
{
name: "main not allowed for other repos w/ testing enabled",
callerRepo: "some/repo",
builderRef: "refs/heads/main",
testingEnabled: true,
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver for other repos",
callerRepo: "some/repo",
builderRef: "refs/tags/v1.2.3",
},
{
name: "no patch semver for other repos",
callerRepo: "some/repo",
builderRef: "refs/tags/v1.2",
expected: serrors.ErrorInvalidRef,
},
{
name: "no min semver for other repos",
callerRepo: "some/repo",
builderRef: "refs/tags/v1",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with prerelease for other repos",
callerRepo: "some/repo",
builderRef: "refs/tags/v1.2.3-alpha",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with prerelease for other repos w/ testing enabled",
callerRepo: "some/repo",
builderRef: "refs/tags/v1.2.3-alpha",
testingEnabled: true,
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with build for other repos",
callerRepo: "some/repo",
builderRef: "refs/tags/v1.2.3+123",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with build for other repos w/ testing enabled",
callerRepo: "some/repo",
builderRef: "refs/tags/v1.2.3+123",
testingEnabled: true,
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with build/prerelease for other repos",
callerRepo: "some/repo",
builderRef: "refs/tags/v1.2.3-alpha+123",
expected: serrors.ErrorInvalidRef,
},
{
name: "full semver with build/prerelease for other repos w/ testing enabled",
callerRepo: "some/repo",
builderRef: "refs/tags/v1.2.3-alpha+123",
testingEnabled: true,
expected: serrors.ErrorInvalidRef,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wf := WorkflowIdentity{
SourceRepository: tt.callerRepo,
}
if tt.testingEnabled {
t.Setenv("SLSA_VERIFIER_TESTING", "1")
} else {
// Ensure that the variable is not set.
t.Setenv("SLSA_VERIFIER_TESTING", "")
}
err := verifyTrustedBuilderRef(&wf, tt.builderRef)
if !errCmp(err, tt.expected) {
t.Error(cmp.Diff(err, tt.expected, cmpopts.EquateErrors()))
}
})
}
}
func Test_GetWorkflowInfoFromCertificate(t *testing.T) {
t.Parallel()
// See https://github.com/sigstore/fulcio/blob/e763d76e3f7786b52db4b27ab87dc446da24895a/pkg/certificate/extensions.go.
trigger := "workflow_dispatch"
encodedTrigger, err := asn1.MarshalWithParams(trigger, "utf8")
if err != nil {
t.Error(err.Error())
}
repo := "org/repo"
encodedRepoURI, err := asn1.MarshalWithParams(httpsGithubCom+repo, "utf8")
if err != nil {
t.Error(err.Error())
}
issuer := "the-issuer"
encodedIssuer, err := asn1.MarshalWithParams(issuer, "utf8")
if err != nil {
t.Error(err.Error())
}
digest := "abcdef"
encodedDigest, err := asn1.MarshalWithParams(digest, "utf8")
if err != nil {
t.Error(err.Error())
}
encodedHosted, err := asn1.MarshalWithParams("github-hosted", "utf8")
if err != nil {
t.Error(err.Error())
}
hosted := HostedGitHub
ref := "refs/tags/v1.2.3"
encodedRef, err := asn1.MarshalWithParams(ref, "utf8")
if err != nil {
t.Error(err.Error())
}
sourceID := "12345"
encodedSourceID, err := asn1.MarshalWithParams(sourceID, "utf8")
if err != nil {
t.Error(err.Error())
}
sourceOwnerID := "12345"
encodedSourceOwnerID, err := asn1.MarshalWithParams(sourceOwnerID, "utf8")
if err != nil {
t.Error(err.Error())
}
buildConfigSha1 := "abcdef"
encodedBuildConfigSha1, err := asn1.MarshalWithParams(buildConfigSha1, "utf8")
if err != nil {
t.Error(err.Error())
}
buildConfigPath := "path/to/workflow"
encodedBuildConfigURI, err := asn1.MarshalWithParams(httpsGithubCom+repo+"/"+buildConfigPath+"@"+ref, "utf8")
if err != nil {
t.Error(err.Error())
}
invocationID := "9207262"
encodedInvocationURI, err := asn1.MarshalWithParams(httpsGithubCom+repo+"/actions/runs/"+invocationID, "utf8")
if err != nil {
t.Error(err.Error())
}
subjectSha1 := "subjectSha1"
encodedSubjectSha1, err := asn1.MarshalWithParams(subjectSha1, "utf8")
if err != nil {
t.Error(err.Error())
}
tests := []struct {
name string
cert x509.Certificate
workflow WorkflowIdentity
err error
}{
{
name: "old cert",
cert: x509.Certificate{
URIs: []*url.URL{
{
Scheme: "https",
Host: "github.com",
Path: "/" + repo + "/" + buildConfigPath,
},
},
Extensions: []pkix.Extension{
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDIssuer,
Value: []byte(issuer),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowTrigger,
Value: []byte(trigger),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowSHA,
Value: []byte(digest),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowRepository,
Value: []byte(repo),
},
},
},
workflow: WorkflowIdentity{
Issuer: issuer,
SubjectWorkflow: Must(url.Parse(httpsGithubCom + repo + "/" + buildConfigPath)),
SourceRepository: repo,
SourceSha1: digest,
BuildTrigger: trigger,
},
},
{
name: "old cert empty URIs",
cert: x509.Certificate{
Extensions: []pkix.Extension{
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDIssuer,
Value: []byte(issuer),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowTrigger,
Value: []byte(trigger),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowSHA,
Value: []byte(digest),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowRepository,
Value: []byte(repo),
},
},
},
err: serrors.ErrorInvalidFormat,
},
{
name: "new cert",
cert: x509.Certificate{
URIs: []*url.URL{
{
Scheme: "https",
Host: "github.com",
Path: "/" + repo + "/" + buildConfigPath,
},
},
Extensions: []pkix.Extension{
// Deprecated claims.
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDIssuer,
Value: []byte(issuer),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowTrigger,
Value: []byte(trigger),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowSHA,
Value: []byte(digest),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowRepository,
Value: []byte(repo),
},
// New claims.
{
Id: fulcio.OIDBuildTrigger,
Value: encodedTrigger,
},
{
Id: fulcio.OIDSourceRepositoryURI,
Value: encodedRepoURI,
},
{
Id: fulcio.OIDIssuerV2,
Value: encodedIssuer,
},
{
Id: fulcio.OIDSourceRepositoryDigest,
Value: encodedDigest,
},
{
Id: fulcio.OIDRunnerEnvironment,
Value: encodedHosted,
},
{
Id: fulcio.OIDSourceRepositoryRef,
Value: encodedRef,
},
{
Id: fulcio.OIDSourceRepositoryIdentifier,
Value: encodedSourceID,
},
{
Id: fulcio.OIDSourceRepositoryOwnerIdentifier,
Value: encodedSourceOwnerID,
},
{
Id: fulcio.OIDBuildConfigDigest,
Value: encodedBuildConfigSha1,
},
{
Id: fulcio.OIDBuildConfigURI,
Value: encodedBuildConfigURI,
},
{
Id: fulcio.OIDRunInvocationURI,
Value: encodedInvocationURI,
},
{
Id: fulcio.OIDBuildSignerDigest,
Value: encodedSubjectSha1,
},
},
},
workflow: WorkflowIdentity{
Issuer: issuer,
SubjectSha1: &subjectSha1,
SubjectHosted: &hosted,
SubjectWorkflow: Must(url.Parse(httpsGithubCom + repo + "/" + buildConfigPath)),
SourceRepository: repo,
SourceSha1: digest,
SourceRef: &ref,
SourceID: &sourceID,
SourceOwnerID: &sourceOwnerID,
BuildTrigger: trigger,
BuildConfigPath: &buildConfigPath,
RunID: &invocationID,
},
},
{
name: "new cert empty URIs",
cert: x509.Certificate{
Extensions: []pkix.Extension{
// Deprecated claims.
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDIssuer,
Value: []byte(issuer),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowTrigger,
Value: []byte(trigger),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowSHA,
Value: []byte(digest),
},
{
//nolint: staticcheck // SA1019: Need to support older signatures.
Id: fulcio.OIDGitHubWorkflowRepository,
Value: []byte(repo),
},
// New claims.
{
Id: fulcio.OIDBuildTrigger,
Value: encodedTrigger,
},
{
Id: fulcio.OIDSourceRepositoryURI,
Value: encodedRepoURI,
},
{
Id: fulcio.OIDIssuerV2,
Value: encodedIssuer,
},
{
Id: fulcio.OIDSourceRepositoryDigest,
Value: encodedDigest,
},
{
Id: fulcio.OIDRunnerEnvironment,
Value: encodedHosted,
},
{
Id: fulcio.OIDSourceRepositoryRef,
Value: encodedRef,
},
{
Id: fulcio.OIDSourceRepositoryIdentifier,
Value: encodedSourceID,
},
{
Id: fulcio.OIDSourceRepositoryOwnerIdentifier,
Value: encodedSourceOwnerID,
},
{
Id: fulcio.OIDBuildConfigDigest,
Value: encodedBuildConfigSha1,
},
{
Id: fulcio.OIDBuildConfigURI,
Value: encodedBuildConfigURI,
},
{
Id: fulcio.OIDRunInvocationURI,
Value: encodedInvocationURI,
},
},
},
err: serrors.ErrorInvalidFormat,
},
{
name: "new cert no deprecated claims",
cert: x509.Certificate{
URIs: []*url.URL{
{
Scheme: "https",
Host: "github.com",
Path: "/" + repo + "/" + buildConfigPath,
},
},
Extensions: []pkix.Extension{
// New claims.
{
Id: fulcio.OIDBuildTrigger,
Value: encodedTrigger,
},
{
Id: fulcio.OIDSourceRepositoryURI,
Value: encodedRepoURI,
},
{
Id: fulcio.OIDIssuerV2,
Value: encodedIssuer,
},
{
Id: fulcio.OIDSourceRepositoryDigest,
Value: encodedDigest,
},
{
Id: fulcio.OIDRunnerEnvironment,
Value: encodedHosted,
},
{
Id: fulcio.OIDSourceRepositoryRef,
Value: encodedRef,
},
{
Id: fulcio.OIDSourceRepositoryIdentifier,
Value: encodedSourceID,
},
{
Id: fulcio.OIDSourceRepositoryOwnerIdentifier,
Value: encodedSourceOwnerID,
},
{
Id: fulcio.OIDBuildConfigDigest,
Value: encodedBuildConfigSha1,
},
{
Id: fulcio.OIDBuildConfigURI,
Value: encodedBuildConfigURI,
},
{
Id: fulcio.OIDRunInvocationURI,
Value: encodedInvocationURI,
},
},
},
workflow: WorkflowIdentity{
Issuer: issuer,
SubjectWorkflow: Must(url.Parse(httpsGithubCom + repo + "/" + buildConfigPath)),
SourceRepository: repo,
SourceSha1: digest,
BuildTrigger: trigger,
SubjectHosted: &hosted,
SourceRef: &ref,
SourceID: &sourceID,
SourceOwnerID: &sourceOwnerID,
BuildConfigPath: &buildConfigPath,
RunID: &invocationID,
},
},
{
name: "new cert no deprecated claims empty URIs",
cert: x509.Certificate{
Extensions: []pkix.Extension{
// New claims.
{
Id: fulcio.OIDBuildTrigger,
Value: encodedTrigger,
},
{
Id: fulcio.OIDSourceRepositoryURI,
Value: encodedRepoURI,
},
{
Id: fulcio.OIDIssuerV2,
Value: encodedIssuer,
},
{
Id: fulcio.OIDSourceRepositoryDigest,
Value: encodedDigest,
},
{
Id: fulcio.OIDRunnerEnvironment,
Value: encodedHosted,
},
{
Id: fulcio.OIDSourceRepositoryRef,
Value: encodedRef,
},
{
Id: fulcio.OIDSourceRepositoryIdentifier,
Value: encodedSourceID,
},
{
Id: fulcio.OIDSourceRepositoryOwnerIdentifier,
Value: encodedSourceOwnerID,
},
{
Id: fulcio.OIDBuildConfigDigest,
Value: encodedBuildConfigSha1,
},
{
Id: fulcio.OIDBuildConfigURI,
Value: encodedBuildConfigURI,
},
{
Id: fulcio.OIDRunInvocationURI,
Value: encodedInvocationURI,
},
},
},
err: serrors.ErrorInvalidFormat,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
workflow, err := GetWorkflowInfoFromCertificate(&tt.cert)
if !errCmp(err, tt.err) {
t.Error(cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
}
if err != nil {
return
}
if !cmp.Equal(*workflow, tt.workflow) {
t.Error(cmp.Diff(*workflow, tt.workflow))
}
})
}
}
func TestWorkflowIdentity(t *testing.T) {
t.Parallel()
tests := []struct {
name string
workflow WorkflowIdentity
workflowName string
workflowPath string
workflowRef string
}{
{
name: "no ref",
workflow: WorkflowIdentity{
SubjectWorkflow: Must(url.Parse("https://github.com/random/workflow/ref")),
},
workflowName: "https://github.com/random/workflow/ref",
workflowPath: "/random/workflow/ref",
workflowRef: "",
},
{
name: "with ref",
workflow: WorkflowIdentity{
SubjectWorkflow: Must(url.Parse("https://github.com/random/workflow/ref@refs/heads/foo")),
},
workflowName: "https://github.com/random/workflow/ref",
workflowPath: "/random/workflow/ref",
workflowRef: "refs/heads/foo",
},
{
name: "multiple (at) symbols",
workflow: WorkflowIdentity{
SubjectWorkflow: Must(url.Parse("https://github.com/random/work@flow/ref@refs/heads/foo")),
},
workflowName: "https://github.com/random/work@flow/ref",
workflowPath: "/random/work@flow/ref",
workflowRef: "refs/heads/foo",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got, want := tt.workflow.SubjectWorkflowName(), tt.workflowName; got != want {
t.Errorf("unexpected subject workflow name, got %q, want %q", got, want)
}
if got, want := tt.workflow.SubjectWorkflowPath(), tt.workflowPath; got != want {
t.Errorf("unexpected subject workflow path, got %q, want %q", got, want)
}
if got, want := tt.workflow.SubjectWorkflowRef(), tt.workflowRef; got != want {
t.Errorf("unexpected subject workflow ref, got %q, want %q", got, want)
}
})
}
}