From 486397ec306f25a295b5bfc1bdad2007f3fa4ee1 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 27 May 2024 17:54:51 +0000 Subject: [PATCH 01/47] drafting generic issuer implementation Signed-off-by: Javan lacerda --- go.mod | 1 + pkg/config/config.yaml | 36 ++ pkg/config/config_reader.go | 72 +++ pkg/identity/generic/issuer.go | 39 ++ pkg/identity/generic/issuer_test.go | 81 +++ pkg/identity/generic/principal.go | 164 ++++++ pkg/identity/generic/principal_test.go | 662 +++++++++++++++++++++++++ 7 files changed, 1055 insertions(+) create mode 100644 pkg/config/config.yaml create mode 100644 pkg/config/config_reader.go create mode 100644 pkg/identity/generic/issuer.go create mode 100644 pkg/identity/generic/issuer_test.go create mode 100644 pkg/identity/generic/principal.go create mode 100644 pkg/identity/generic/principal_test.go diff --git a/go.mod b/go.mod index 8caa0c7bf..3d7b601d5 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.2 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/release-utils v0.8.2 ) diff --git a/pkg/config/config.yaml b/pkg/config/config.yaml new file mode 100644 index 000000000..094ff7e1e --- /dev/null +++ b/pkg/config/config.yaml @@ -0,0 +1,36 @@ +# TODO: Fill it properly for the current providers +providers: + github: + extensions: + build-signer-digest: job_workflow_sha + source-repository-digest: sha + source-repository-ref: ref + source-repository-identifier: repository_id + run-invocation-uri: "{{.url}}/{{.repository}}/actions/runs/{{.run_id}}/" + uris: + - "{{.url}}/{{.job_workflow_ref}}" + defaults: + url: https://github.com + gitlab: + extensions: + build-signer-digest: ci_config_sha + source-repository-digest: sha + source-repository-ref: ref + source-repository-identifier: project_id + run-invocation-uri: "{{.url}}/{{.project_path}}/-/jobs/{{.job_id}}" + uris: + - ci_config_ref_uri + defaults: + url: https://gitlab.com + codefresh: + extensions: + build-signer-digest: build/{{.workflow_id}} + runner-environment: runner_environment + source-repository-uri: scm_repo_url + source-repository-ref: scm_ref + build-config-uri: api/pipelines/{{.pipeline_id}} + run-invocation-uri: build/{{.workflow_id}} + uris: + - "{{.platform_url}}/{{.account_name}}/{{.pipeline_name}}:{{.account_id}}/{{.pipeline_id}}" + defaults: + platform_url: https://g.codefresh.io diff --git a/pkg/config/config_reader.go b/pkg/config/config_reader.go new file mode 100644 index 000000000..8dad9615f --- /dev/null +++ b/pkg/config/config_reader.go @@ -0,0 +1,72 @@ +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +type Extensions struct { + Issuer string // OID 1.3.6.1.4.1.57264.1.8 and 1.3.6.1.4.1.57264.1.1 (Deprecated) + Subject string + GithubWorkflowTrigger string `yaml:"github-workflow-trigger"` // OID 1.3.6.1.4.1.57264.1.2 + GithubWorkflowSHA string `yaml:"github-workflow-sha"` // OID 1.3.6.1.4.1.57264.1.3 + GithubWorkflowName string `yaml:"github-workflow-name"` // OID 1.3.6.1.4.1.57264.1.4 + GithubWorkflowRepository string `yaml:"github-workflow-repository"` // OID 1.3.6.1.4.1.57264.1.5 + GithubWorkflowRef string `yaml:"github-workflow-ref"` // 1.3.6.1.4.1.57264.1.6 + BuildSignerURI string `yaml:"build-signer-uri"` // 1.3.6.1.4.1.57264.1.9 + BuildSignerDigest string `yaml:"build-signer-digest"` // 1.3.6.1.4.1.57264.1.10 + RunnerEnvironment string `yaml:"runner-environment"` // 1.3.6.1.4.1.57264.1.11 + SourceRepositoryURI string `yaml:"source-repository-uri"` // 1.3.6.1.4.1.57264.1.12 + SourceRepositoryDigest string `yaml:"source-repository-digest"` // 1.3.6.1.4.1.57264.1.13 + SourceRepositoryRef string `yaml:"source-repository-ref"` // 1.3.6.1.4.1.57264.1.14 + SourceRepositoryIdentifier string `yaml:"source-repository-identifier"` // 1.3.6.1.4.1.57264.1.15 + SourceRepositoryOwnerURI string `yaml:"source-repository-owner-uri"` // 1.3.6.1.4.1.57264.1.16 + SourceRepositoryOwnerIdentifier string `yaml:"source-repository-owner-identifier"` // 1.3.6.1.4.1.57264.1.17 + BuildConfigURI string `yaml:"build-config-uri"` // 1.3.6.1.4.1.57264.1.18 + BuildConfigDigest string `yaml:"build-config-digest"` // 1.3.6.1.4.1.57264.1.19 + BuildTrigger string `yaml:"build-trigger"` // 1.3.6.1.4.1.57264.1.20 + RunInvocationURI string `yaml:"run-invocation-uri"` // 1.3.6.1.4.1.57264.1.21 + SourceRepositoryVisibilityAtSigning string `yaml:"source-repository-visibility-at-signing"` // 1.3.6.1.4.1.57264.1.22 +} + +type RootYaml struct { + Providers map[string]Provider +} + +type Provider struct { + Extensions Extensions + Uris []string + Defaults map[string]string +} + +func readYaml() RootYaml { + var obj RootYaml + + yamlFile, err := os.ReadFile("config.yaml") + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, &obj) + if err != nil { + fmt.Printf("Unmarshal: %v", err) + } + + return obj +} diff --git a/pkg/identity/generic/issuer.go b/pkg/identity/generic/issuer.go new file mode 100644 index 000000000..0b2d1e24e --- /dev/null +++ b/pkg/identity/generic/issuer.go @@ -0,0 +1,39 @@ +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + "context" + + "github.com/sigstore/fulcio/pkg/config" + "github.com/sigstore/fulcio/pkg/identity" + "github.com/sigstore/fulcio/pkg/identity/base" +) + +type genericIssuer struct { + identity.Issuer +} + +func Issuer(issuerURL string) identity.Issuer { + return &genericIssuer{base.Issuer(issuerURL)} +} + +func (e *genericIssuer) Authenticate(ctx context.Context, token string, opts ...config.InsecureOIDCConfigOption) (identity.Principal, error) { + idtoken, err := identity.Authorize(ctx, token, opts...) + if err != nil { + return nil, err + } + return WorkflowPrincipalFromIDToken(ctx, idtoken) +} diff --git a/pkg/identity/generic/issuer_test.go b/pkg/identity/generic/issuer_test.go new file mode 100644 index 000000000..5b06e743a --- /dev/null +++ b/pkg/identity/generic/issuer_test.go @@ -0,0 +1,81 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + "context" + "encoding/json" + "testing" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/sigstore/fulcio/pkg/config" + "github.com/sigstore/fulcio/pkg/identity" +) + +func TestIssuer(t *testing.T) { + ctx := context.Background() + url := "test-issuer-url" + issuer := Issuer(url) + + // test the Match function + t.Run("match", func(t *testing.T) { + if matches := issuer.Match(ctx, url); !matches { + t.Fatal("expected url to match but it doesn't") + } + if matches := issuer.Match(ctx, "some-other-url"); matches { + t.Fatal("expected match to fail but it didn't") + } + }) + + t.Run("authenticate", func(t *testing.T) { + token := &oidc.IDToken{ + Issuer: "https://iss.example.com", + Subject: "subject", + } + claims, err := json.Marshal(map[string]interface{}{ + "aud": "sigstore", + "iss": "https://iss.example.com", + "sub": "doesntmatter", + "email": "alice@example.com", + "email_verified": true, + }) + if err != nil { + t.Fatal(err) + } + withClaims(token, claims) + + ctx := config.With(context.Background(), &config.FulcioConfig{ + OIDCIssuers: map[string]config.OIDCIssuer{ + "https://iss.example.com": { + IssuerURL: "https://iss.example.com", + Type: config.IssuerTypeEmail, + ClientID: "sigstore", + }, + }, + }) + + identity.Authorize = func(_ context.Context, _ string, _ ...config.InsecureOIDCConfigOption) (*oidc.IDToken, error) { + return token, nil + } + principal, err := issuer.Authenticate(ctx, "token") + if err != nil { + t.Fatal(err) + } + + if principal.Name(ctx) != "alice@example.com" { + t.Fatalf("got unexpected name %s", principal.Name(ctx)) + } + }) +} diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go new file mode 100644 index 000000000..e492b2fc1 --- /dev/null +++ b/pkg/identity/generic/principal.go @@ -0,0 +1,164 @@ +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + "bytes" + "context" + "crypto/x509" + "fmt" + "net/url" + "os" + "strings" + "text/template" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/sigstore/fulcio/pkg/certificate" + "github.com/sigstore/fulcio/pkg/identity" + "gopkg.in/yaml.v2" +) + +// TODO: Delete after merged the config reader and get it directly from the github repo +type RootYaml struct { + Providers map[string]Provider +} + +type Provider struct { + Subject string + Extensions certificate.Extensions + Uris []string + Defaults map[string]string +} + +func readYaml() RootYaml { + var obj RootYaml + + yamlFile, err := os.ReadFile("../../config/config.yaml") + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, &obj) + if err != nil { + fmt.Printf("Unmarshal: %v", err) + } + + return obj +} + +func WorkflowPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (identity.Principal, error) { + var claims map[string]string + if err := token.Claims(&claims); err != nil { + return nil, err + } + + yaml := readYaml() + + // It probably doesn't work + provider := yaml.Providers[token.Subject] + e := provider.Extensions + defaults := provider.Defaults + finalExtensions := certificate.Extensions{ + Issuer: ApplyTemplate(e.Issuer, claims, defaults), + GithubWorkflowTrigger: ApplyTemplate(e.GithubWorkflowTrigger, claims, defaults), + GithubWorkflowSHA: ApplyTemplate(e.GithubWorkflowSHA, claims, defaults), + GithubWorkflowName: ApplyTemplate(e.GithubWorkflowName, claims, defaults), + GithubWorkflowRepository: ApplyTemplate(e.GithubWorkflowRepository, claims, defaults), + GithubWorkflowRef: ApplyTemplate(e.GithubWorkflowRef, claims, defaults), + BuildSignerURI: ApplyTemplate(e.BuildSignerURI, claims, defaults), + BuildConfigDigest: ApplyTemplate(e.BuildConfigDigest, claims, defaults), + RunnerEnvironment: ApplyTemplate(e.RunnerEnvironment, claims, defaults), + SourceRepositoryURI: ApplyTemplate(e.SourceRepositoryURI, claims, defaults), + SourceRepositoryDigest: ApplyTemplate(e.SourceRepositoryDigest, claims, defaults), + SourceRepositoryRef: ApplyTemplate(e.SourceRepositoryRef, claims, defaults), + SourceRepositoryIdentifier: ApplyTemplate(e.SourceRepositoryIdentifier, claims, defaults), + SourceRepositoryOwnerURI: ApplyTemplate(e.SourceRepositoryOwnerURI, claims, defaults), + SourceRepositoryOwnerIdentifier: ApplyTemplate(e.SourceRepositoryOwnerIdentifier, claims, defaults), + BuildConfigURI: ApplyTemplate(e.BuildConfigURI, claims, defaults), + BuildSignerDigest: ApplyTemplate(e.BuildSignerDigest, claims, defaults), + BuildTrigger: ApplyTemplate(e.BuildTrigger, claims, defaults), + RunInvocationURI: ApplyTemplate(e.RunInvocationURI, claims, defaults), + SourceRepositoryVisibilityAtSigning: ApplyTemplate(e.SourceRepositoryVisibilityAtSigning, claims, defaults), + } + finalUris := make([]string, len(provider.Uris)-1) + for _, val := range provider.Uris { + finalUris = append(finalUris, ApplyTemplate(val, claims, defaults)) + } + + return &Provider{ + Subject: token.Subject, + Extensions: finalExtensions, + Uris: finalUris, + }, nil +} + +func ApplyTemplate(path string, data map[string]string, defaultData map[string]string) string { + + // Here we merge the data from was claimed by the id token with the + // default data provided by the yaml file. + // The order here matter because we want to override the default data + // with the claimed data. + mergedData := make(map[string]string) + for k, v := range defaultData { + mergedData[k] = v + } + for k, v := range data { + mergedData[k] = v + } + + // It checks it is a path or a raw field by + // checking exists template syntax into the string + if strings.Contains(path, "{{.") { + var doc bytes.Buffer + t := template.New("") + p, err := t.Parse(path) + if err != nil { + panic(err) + } + err = p.Execute(&doc, mergedData) + if err != nil { + panic(err) + } + return doc.String() + } else { + return mergedData[path] + } +} + +func (p Provider) Name(_ context.Context) string { + return p.Subject +} + +func (p Provider) Embed(_ context.Context, cert *x509.Certificate) error { + + uris := make([]*url.URL, len(p.Uris)) + for _, value := range p.Uris { + url, err := url.Parse(value) + if err != nil { + panic(err) + } + uris = append(uris, url) + } + // Set workflow ref URL to SubjectAlternativeName on certificate + cert.URIs = uris + + var err error + // Embed additional information into custom extensions + cert.ExtraExtensions, err = p.Extensions.Render() + if err != nil { + return err + } + + return nil +} diff --git a/pkg/identity/generic/principal_test.go b/pkg/identity/generic/principal_test.go new file mode 100644 index 000000000..9ee4fd1f7 --- /dev/null +++ b/pkg/identity/generic/principal_test.go @@ -0,0 +1,662 @@ +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + "bytes" + "context" + "crypto/x509" + "encoding/asn1" + "encoding/json" + "errors" + "fmt" + "reflect" + "testing" + "unsafe" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/sigstore/fulcio/pkg/identity" +) + +func TestWorkflowPrincipalFromIDToken(t *testing.T) { + tests := map[string]struct { + Claims map[string]interface{} + ExpectPrincipal Provider + WantErr bool + ErrContains string + }{ + `Valid token authenticates with correct claims`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + ExpectPrincipal: Provider{}, + WantErr: false, + }, + `Token missing job_workflow_ref claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "job_workflow_ref", + }, + `Token missing sha should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "sha", + }, + `Token missing event_name claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "event_name", + }, + `Token missing repository claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "repository", + }, + `Token missing workflow claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "workflow", + }, + `Token missing ref claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "ref", + }, + `Token missing job_workflow_sha claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "job_workflow_sha", + }, + `Token missing runner_environment claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "runner_environment", + }, + `Token missing repository_id claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "repository_id", + }, + `Token missing repository_owner claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "repository_owner", + }, + `Token missing repository_owner_id claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "repository_owner_id", + }, + `Token missing workflow_ref claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "workflow_ref", + }, + `Token missing workflow_sha claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + }, + WantErr: true, + ErrContains: "workflow_sha", + }, + `Token missing run_id claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "run_id", + }, + `Token missing run_attempt claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "run_attempt", + }, + `Token missing repository_visibility claim should be rejected`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + WantErr: true, + ErrContains: "repository_visibility", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + token := &oidc.IDToken{ + Issuer: test.Claims["iss"].(string), + Subject: test.Claims["sub"].(string), + } + claims, err := json.Marshal(test.Claims) + if err != nil { + t.Fatal(err) + } + withClaims(token, claims) + + // untyped, err := WorkflowPrincipalFromIDToken(context.TODO(), token) + // if err != nil { + // if !test.WantErr { + // t.Fatal("didn't expect error", err) + // } + // if !strings.Contains(err.Error(), test.ErrContains) { + // t.Fatalf("expected error %s to contain %s", err, test.ErrContains) + // } + // return + // } + // if err == nil && test.WantErr { + // t.Fatal("expected error but got none") + // } + + // principal, ok := untyped.(*Provider) + // if !ok { + // t.Errorf("Got wrong principal type %v", untyped) + // } + // if *principal != test.ExpectPrincipal { + // t.Errorf("got %v principal and expected %v", *principal, test.ExpectPrincipal) + // } + }) + } +} + +// reflect hack because "claims" field is unexported by oidc IDToken +// https://github.com/coreos/go-oidc/pull/329 +func withClaims(token *oidc.IDToken, data []byte) { + val := reflect.Indirect(reflect.ValueOf(token)) + member := val.FieldByName("claims") + pointer := unsafe.Pointer(member.UnsafeAddr()) + realPointer := (*[]byte)(pointer) + *realPointer = data +} + +func TestName(t *testing.T) { + tests := map[string]struct { + Claims map[string]interface{} + ExpectName string + }{ + `Valid token authenticates with correct claims`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + ExpectName: "repo:sigstore/fulcio:ref:refs/heads/main", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + token := &oidc.IDToken{ + Issuer: test.Claims["iss"].(string), + Subject: test.Claims["sub"].(string), + } + claims, err := json.Marshal(test.Claims) + if err != nil { + t.Fatal(err) + } + withClaims(token, claims) + + principal, err := WorkflowPrincipalFromIDToken(context.TODO(), token) + if err != nil { + t.Fatal(err) + } + + gotName := principal.Name(context.TODO()) + if gotName != test.ExpectName { + t.Error("name should match sub claim") + } + }) + } + +} + +func TestEmbed(t *testing.T) { + tests := map[string]struct { + Principal identity.Principal + WantErr bool + WantFacts map[string]func(x509.Certificate) error + }{ + `Github workflow challenge should have all Github workflow extensions and issuer set`: { + Principal: &Provider{}, + WantErr: false, + WantFacts: map[string]func(x509.Certificate) error{ + `Certifificate should have correct issuer`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, "https://token.actions.githubusercontent.com"), + `Certificate has correct trigger extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, "trigger"), + `Certificate has correct SHA extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}, "sha"), + `Certificate has correct workflow extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}, "workflowname"), + `Certificate has correct repository extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}, "repository"), + `Certificate has correct ref extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, "ref"), + `Certificate has correct issuer (v2) extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 8}, "https://token.actions.githubusercontent.com"), + `Certificate has correct builder signer URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 9}, "https://github.com/jobWorkflowRef"), + `Certificate has correct builder signer digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 10}, "jobWorkflowSha"), + `Certificate has correct runner environment extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 11}, "runnerEnv"), + `Certificate has correct source repo URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 12}, "https://github.com/repository"), + `Certificate has correct source repo digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 13}, "sha"), + `Certificate has correct source repo ref extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 14}, "ref"), + `Certificate has correct source repo ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 15}, "repoID"), + `Certificate has correct source repo owner URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 16}, "https://github.com/repoOwner"), + `Certificate has correct source repo owner ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 17}, "repoOwnerID"), + `Certificate has correct build config URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 18}, "https://github.com/workflowRef"), + `Certificate has correct build config digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 19}, "workflowSHA"), + `Certificate has correct build trigger extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 20}, "trigger"), + `Certificate has correct run invocation ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}, "https://github.com/repository/actions/runs/runID/attempts/runAttempt"), + `Certificate has correct source repository visibility extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}, "public"), + }, + }, + `Github workflow value with bad URL fails`: { + Principal: &Provider{}, + WantErr: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var cert x509.Certificate + err := test.Principal.Embed(context.TODO(), &cert) + if err != nil { + if !test.WantErr { + t.Error(err) + } + return + } else if test.WantErr { + t.Error("expected error") + } + for factName, fact := range test.WantFacts { + t.Run(factName, func(t *testing.T) { + if err := fact(cert); err != nil { + t.Error(err) + } + }) + } + }) + } +} + +func factExtensionIs(oid asn1.ObjectIdentifier, value string) func(x509.Certificate) error { + return func(cert x509.Certificate) error { + for _, ext := range cert.ExtraExtensions { + if ext.Id.Equal(oid) { + var strVal string + _, _ = asn1.Unmarshal(ext.Value, &strVal) + if value != strVal { + return fmt.Errorf("expected oid %v to be %s, but got %s", oid, value, strVal) + } + return nil + } + } + return errors.New("extension not set") + } +} + +func factDeprecatedExtensionIs(oid asn1.ObjectIdentifier, value string) func(x509.Certificate) error { + return func(cert x509.Certificate) error { + for _, ext := range cert.ExtraExtensions { + if ext.Id.Equal(oid) { + if !bytes.Equal(ext.Value, []byte(value)) { + return fmt.Errorf("expected oid %v to be %s, but got %s", oid, value, ext.Value) + } + return nil + } + } + return errors.New("extension not set") + } +} From f74f6a6a752c793dda4f94cd9c0de35d50c9c46b Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 27 May 2024 18:11:06 +0000 Subject: [PATCH 02/47] adding license o yaml file Signed-off-by: Javan lacerda --- pkg/config/config.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/config/config.yaml b/pkg/config/config.yaml index 094ff7e1e..35176a1ef 100644 --- a/pkg/config/config.yaml +++ b/pkg/config/config.yaml @@ -1,3 +1,17 @@ +# Copyright 2024 The Sigstore Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # TODO: Fill it properly for the current providers providers: github: From cc5a602b267324cfe2011967d02c7d07f6868e75 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 28 May 2024 15:44:42 +0000 Subject: [PATCH 03/47] remove yaml2.0 Signed-off-by: Javan lacerda --- go.mod | 1 - pkg/identity/generic/principal.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 3d7b601d5..8caa0c7bf 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.2 - gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/release-utils v0.8.2 ) diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go index e492b2fc1..1823ff437 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/generic/principal.go @@ -27,7 +27,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/identity" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) // TODO: Delete after merged the config reader and get it directly from the github repo From 9c2c0f84b9c1668d3da7090e756198ee28f6a4b6 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 28 May 2024 17:45:34 +0000 Subject: [PATCH 04/47] start removing IssuerType usage Signed-off-by: Javan lacerda --- pkg/challenges/challenges.go | 58 +++++++++++++------------------ pkg/config/config.go | 2 ++ pkg/identity/generic/principal.go | 12 ++++--- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index dda3298ff..9a724c1f2 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -21,19 +21,10 @@ import ( "crypto" "crypto/x509" "errors" - "fmt" "strings" - "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" - "github.com/sigstore/fulcio/pkg/identity/buildkite" - "github.com/sigstore/fulcio/pkg/identity/email" - "github.com/sigstore/fulcio/pkg/identity/github" - "github.com/sigstore/fulcio/pkg/identity/gitlabcom" - "github.com/sigstore/fulcio/pkg/identity/kubernetes" - "github.com/sigstore/fulcio/pkg/identity/spiffe" - "github.com/sigstore/fulcio/pkg/identity/uri" - "github.com/sigstore/fulcio/pkg/identity/username" + "github.com/sigstore/fulcio/pkg/identity/generic" "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/sigstore/pkg/cryptoutils" @@ -52,32 +43,31 @@ func CheckSignature(pub crypto.PublicKey, proof []byte, subject string) error { } func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Principal, error) { - iss, ok := config.FromContext(ctx).GetIssuer(tok.Issuer) - if !ok { - return nil, fmt.Errorf("configuration can not be loaded for issuer %v", tok.Issuer) - } var principal identity.Principal var err error - switch iss.Type { - case config.IssuerTypeBuildkiteJob: - principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) - case config.IssuerTypeGitLabPipeline: - principal, err = gitlabcom.JobPrincipalFromIDToken(ctx, tok) - case config.IssuerTypeEmail: - principal, err = email.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeSpiffe: - principal, err = spiffe.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeGithubWorkflow: - principal, err = github.WorkflowPrincipalFromIDToken(ctx, tok) - case config.IssuerTypeKubernetes: - principal, err = kubernetes.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeURI: - principal, err = uri.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeUsername: - principal, err = username.PrincipalFromIDToken(ctx, tok) - default: - return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) - } + + principal, err = generic.WorkflowPrincipalFromIDToken(ctx, tok) + + // switch iss.Type { + // case config.IssuerTypeBuildkiteJob: + // principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) + // case config.IssuerTypeGitLabPipeline: + // principal, err = gitlabcom.JobPrincipalFromIDToken(ctx, tok) + // case config.IssuerTypeEmail: + // principal, err = email.PrincipalFromIDToken(ctx, tok) + // case config.IssuerTypeSpiffe: + // principal, err = spiffe.PrincipalFromIDToken(ctx, tok) + // case config.IssuerTypeGithubWorkflow: + // principal, err = github.WorkflowPrincipalFromIDToken(ctx, tok) + // case config.IssuerTypeKubernetes: + // principal, err = kubernetes.PrincipalFromIDToken(ctx, tok) + // case config.IssuerTypeURI: + // principal, err = uri.PrincipalFromIDToken(ctx, tok) + // case config.IssuerTypeUsername: + // principal, err = username.PrincipalFromIDToken(ctx, tok) + // default: + // return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) + // } if err != nil { return nil, err } diff --git a/pkg/config/config.go b/pkg/config/config.go index 6a6aca77e..d50d86673 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -271,6 +271,8 @@ func (fc *FulcioConfig) prepare() error { return nil } +// I would like to remove this completely, to be able to add or remove +// oidc providers only by the config.yaml file. type IssuerType string const ( diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go index 1823ff437..9b77a1046 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/generic/principal.go @@ -26,6 +26,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/fulcio/pkg/certificate" + "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" "gopkg.in/yaml.v3" ) @@ -57,16 +58,19 @@ func readYaml() RootYaml { return obj } -func WorkflowPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (identity.Principal, error) { +func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { + iss, ok := config.FromContext(ctx).GetIssuer(token.Issuer) + if !ok { + return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) + } + var claims map[string]string if err := token.Claims(&claims); err != nil { return nil, err } yaml := readYaml() - - // It probably doesn't work - provider := yaml.Providers[token.Subject] + provider := yaml.Providers[string(iss.Type)] e := provider.Extensions defaults := provider.Defaults finalExtensions := certificate.Extensions{ From 1261f88f88b7eec13ec0c3d3d88132d1d2d6ae32 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 28 May 2024 17:48:07 +0000 Subject: [PATCH 05/47] update license year Signed-off-by: Javan lacerda --- pkg/config/config.go | 2 +- pkg/config/config_reader.go | 2 +- pkg/identity/generic/issuer.go | 2 +- pkg/identity/generic/issuer_test.go | 2 +- pkg/identity/generic/principal.go | 2 +- pkg/identity/generic/principal_test.go | 3 ++- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index d50d86673..0777d925d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -272,7 +272,7 @@ func (fc *FulcioConfig) prepare() error { } // I would like to remove this completely, to be able to add or remove -// oidc providers only by the config.yaml file. +// oidc providers only by updating the config.yaml file. type IssuerType string const ( diff --git a/pkg/config/config_reader.go b/pkg/config/config_reader.go index 8dad9615f..2a0d55d8c 100644 --- a/pkg/config/config_reader.go +++ b/pkg/config/config_reader.go @@ -1,4 +1,4 @@ -// Copyright 2021 The Sigstore Authors. +// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/identity/generic/issuer.go b/pkg/identity/generic/issuer.go index 0b2d1e24e..5ed6a26e5 100644 --- a/pkg/identity/generic/issuer.go +++ b/pkg/identity/generic/issuer.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Sigstore Authors. +// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/identity/generic/issuer_test.go b/pkg/identity/generic/issuer_test.go index 5b06e743a..dfaaf4268 100644 --- a/pkg/identity/generic/issuer_test.go +++ b/pkg/identity/generic/issuer_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Sigstore Authors. +// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go index 9b77a1046..c11e57698 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/generic/principal.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Sigstore Authors. +// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/identity/generic/principal_test.go b/pkg/identity/generic/principal_test.go index 9ee4fd1f7..73ed3dd88 100644 --- a/pkg/identity/generic/principal_test.go +++ b/pkg/identity/generic/principal_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The Sigstore Authors. +// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import ( "github.com/sigstore/fulcio/pkg/identity" ) +// TO BE IMPLEMENTED. Just keeped as a guide func TestWorkflowPrincipalFromIDToken(t *testing.T) { tests := map[string]struct { Claims map[string]interface{} From 73c988cd3d76bc5b6934428264d70cb7a0f7daac Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 4 Jun 2024 16:31:44 +0000 Subject: [PATCH 06/47] log Signed-off-by: Javan lacerda --- federation/main.go | 29 ++++++++++++++++++++++ pkg/identity/generic/principal.go | 41 +++++-------------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/federation/main.go b/federation/main.go index 7926f772a..e33aeb6a6 100644 --- a/federation/main.go +++ b/federation/main.go @@ -17,9 +17,11 @@ package main import ( "encoding/json" + "fmt" "os" "path/filepath" + "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "gopkg.in/yaml.v3" ) @@ -48,6 +50,33 @@ type federationConfig struct { SpiffeTrustDomain string } +type RootYaml struct { + Providers map[string]Provider +} + +type Provider struct { + Subject string + Extensions certificate.Extensions + Uris []string + Defaults map[string]string + OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` +} + +func readYaml() RootYaml { + var obj RootYaml + + yamlFile, err := os.ReadFile("../../config/config.yaml") + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, &obj) + if err != nil { + fmt.Printf("Unmarshal: %v", err) + } + + return obj +} + func main() { matches := []string{} for _, rp := range rootPaths { diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go index c11e57698..8f0b34632 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/generic/principal.go @@ -20,7 +20,6 @@ import ( "crypto/x509" "fmt" "net/url" - "os" "strings" "text/template" @@ -28,37 +27,9 @@ import ( "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" - "gopkg.in/yaml.v3" ) -// TODO: Delete after merged the config reader and get it directly from the github repo -type RootYaml struct { - Providers map[string]Provider -} - -type Provider struct { - Subject string - Extensions certificate.Extensions - Uris []string - Defaults map[string]string -} - -func readYaml() RootYaml { - var obj RootYaml - - yamlFile, err := os.ReadFile("../../config/config.yaml") - if err != nil { - fmt.Printf("yamlFile.Get err #%v ", err) - } - err = yaml.Unmarshal(yamlFile, &obj) - if err != nil { - fmt.Printf("Unmarshal: %v", err) - } - - return obj -} - -func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { +func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken, yaml RootYaml) (identity.Principal, error) { iss, ok := config.FromContext(ctx).GetIssuer(token.Issuer) if !ok { return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) @@ -69,7 +40,6 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (ide return nil, err } - yaml := readYaml() provider := yaml.Providers[string(iss.Type)] e := provider.Extensions defaults := provider.Defaults @@ -101,9 +71,10 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (ide } return &Provider{ - Subject: token.Subject, - Extensions: finalExtensions, - Uris: finalUris, + Subject: token.Subject, + Extensions: finalExtensions, + Uris: finalUris, + OIDCIssuers: provider.OIDCIssuers, }, nil } @@ -123,7 +94,7 @@ func ApplyTemplate(path string, data map[string]string, defaultData map[string]s // It checks it is a path or a raw field by // checking exists template syntax into the string - if strings.Contains(path, "{{.") { + if strings.Contains(path, "{{") { var doc bytes.Buffer t := template.New("") p, err := t.Parse(path) From be6acf8dc3deb67017aafe706a29f34f58e4596e Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Thu, 6 Jun 2024 14:07:04 +0000 Subject: [PATCH 07/47] adding ci provider flag Signed-off-by: Javan lacerda --- federation/main.go | 29 -- pkg/challenges/challenges.go | 61 ++- pkg/config/config.go | 9 +- pkg/config/config_test.go | 31 +- pkg/identity/generic/issuer_test.go | 58 --- pkg/identity/generic/principal.go | 35 +- pkg/identity/generic/principal_test.go | 631 ------------------------- pkg/server/grpc_server_test.go | 3 +- 8 files changed, 99 insertions(+), 758 deletions(-) diff --git a/federation/main.go b/federation/main.go index e33aeb6a6..7926f772a 100644 --- a/federation/main.go +++ b/federation/main.go @@ -17,11 +17,9 @@ package main import ( "encoding/json" - "fmt" "os" "path/filepath" - "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "gopkg.in/yaml.v3" ) @@ -50,33 +48,6 @@ type federationConfig struct { SpiffeTrustDomain string } -type RootYaml struct { - Providers map[string]Provider -} - -type Provider struct { - Subject string - Extensions certificate.Extensions - Uris []string - Defaults map[string]string - OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` -} - -func readYaml() RootYaml { - var obj RootYaml - - yamlFile, err := os.ReadFile("../../config/config.yaml") - if err != nil { - fmt.Printf("yamlFile.Get err #%v ", err) - } - err = yaml.Unmarshal(yamlFile, &obj) - if err != nil { - fmt.Printf("Unmarshal: %v", err) - } - - return obj -} - func main() { matches := []string{} for _, rp := range rootPaths { diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index 9a724c1f2..e40a31a4a 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -21,10 +21,20 @@ import ( "crypto" "crypto/x509" "errors" + "fmt" "strings" + "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" + "github.com/sigstore/fulcio/pkg/identity/buildkite" + "github.com/sigstore/fulcio/pkg/identity/email" "github.com/sigstore/fulcio/pkg/identity/generic" + "github.com/sigstore/fulcio/pkg/identity/github" + "github.com/sigstore/fulcio/pkg/identity/gitlabcom" + "github.com/sigstore/fulcio/pkg/identity/kubernetes" + "github.com/sigstore/fulcio/pkg/identity/spiffe" + "github.com/sigstore/fulcio/pkg/identity/uri" + "github.com/sigstore/fulcio/pkg/identity/username" "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/sigstore/pkg/cryptoutils" @@ -43,31 +53,36 @@ func CheckSignature(pub crypto.PublicKey, proof []byte, subject string) error { } func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Principal, error) { + iss, ok := config.FromContext(ctx).GetIssuer(tok.Issuer) + if !ok { + return nil, fmt.Errorf("configuration can not be loaded for issuer %v", tok.Issuer) + } var principal identity.Principal var err error - - principal, err = generic.WorkflowPrincipalFromIDToken(ctx, tok) - - // switch iss.Type { - // case config.IssuerTypeBuildkiteJob: - // principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) - // case config.IssuerTypeGitLabPipeline: - // principal, err = gitlabcom.JobPrincipalFromIDToken(ctx, tok) - // case config.IssuerTypeEmail: - // principal, err = email.PrincipalFromIDToken(ctx, tok) - // case config.IssuerTypeSpiffe: - // principal, err = spiffe.PrincipalFromIDToken(ctx, tok) - // case config.IssuerTypeGithubWorkflow: - // principal, err = github.WorkflowPrincipalFromIDToken(ctx, tok) - // case config.IssuerTypeKubernetes: - // principal, err = kubernetes.PrincipalFromIDToken(ctx, tok) - // case config.IssuerTypeURI: - // principal, err = uri.PrincipalFromIDToken(ctx, tok) - // case config.IssuerTypeUsername: - // principal, err = username.PrincipalFromIDToken(ctx, tok) - // default: - // return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) - // } + if iss.IsCiProvider { + principal, err = generic.WorkflowPrincipalFromIDToken(ctx, tok) + } else { + switch iss.Type { + case config.IssuerTypeBuildkiteJob: + principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) + case config.IssuerTypeGitLabPipeline: + principal, err = gitlabcom.JobPrincipalFromIDToken(ctx, tok) + case config.IssuerTypeEmail: + principal, err = email.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeSpiffe: + principal, err = spiffe.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeGithubWorkflow: + principal, err = github.WorkflowPrincipalFromIDToken(ctx, tok) + case config.IssuerTypeKubernetes: + principal, err = kubernetes.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeURI: + principal, err = uri.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeUsername: + principal, err = username.PrincipalFromIDToken(ctx, tok) + default: + return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) + } + } if err != nil { return nil, err } diff --git a/pkg/config/config.go b/pkg/config/config.go index 0777d925d..8c4bdf2f3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -126,6 +126,7 @@ func (fc *FulcioConfig) GetIssuer(issuerURL string) (OIDCIssuer, bool) { Type: iss.Type, IssuerClaim: iss.IssuerClaim, SubjectDomain: iss.SubjectDomain, + IsCiProvider: iss.IsCiProvider, }, true } } @@ -205,7 +206,7 @@ func (fc *FulcioConfig) ToIssuers() []*fulciogrpc.OIDCIssuer { Issuer: &fulciogrpc.OIDCIssuer_IssuerUrl{IssuerUrl: cfgIss.IssuerURL}, Audience: cfgIss.ClientID, SpiffeTrustDomain: cfgIss.SPIFFETrustDomain, - ChallengeClaim: issuerToChallengeClaim(cfgIss.Type, cfgIss.ChallengeClaim), + ChallengeClaim: issuerToChallengeClaim(cfgIss, cfgIss.ChallengeClaim), } issuers = append(issuers, issuer) } @@ -215,7 +216,7 @@ func (fc *FulcioConfig) ToIssuers() []*fulciogrpc.OIDCIssuer { Issuer: &fulciogrpc.OIDCIssuer_WildcardIssuerUrl{WildcardIssuerUrl: metaIss}, Audience: cfgIss.ClientID, SpiffeTrustDomain: cfgIss.SPIFFETrustDomain, - ChallengeClaim: issuerToChallengeClaim(cfgIss.Type, cfgIss.ChallengeClaim), + ChallengeClaim: issuerToChallengeClaim(cfgIss, cfgIss.ChallengeClaim), } issuers = append(issuers, issuer) } @@ -376,7 +377,7 @@ func validateConfig(conf *FulcioConfig) error { } } - if issuerToChallengeClaim(issuer.Type, issuer.ChallengeClaim) == "" { + if issuerToChallengeClaim(issuer, issuer.ChallengeClaim) == "" { return errors.New("issuer missing challenge claim") } } @@ -388,7 +389,7 @@ func validateConfig(conf *FulcioConfig) error { return errors.New("SPIFFE meta issuers not supported") } - if issuerToChallengeClaim(metaIssuer.Type, metaIssuer.ChallengeClaim) == "" { + if issuerToChallengeClaim(metaIssuer, metaIssuer.ChallengeClaim) == "" { return errors.New("issuer missing challenge claim") } } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 4c0967660..11e4defcb 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -474,28 +474,37 @@ func Test_validateAllowedDomain(t *testing.T) { } func Test_issuerToChallengeClaim(t *testing.T) { - if claim := issuerToChallengeClaim(IssuerTypeEmail, ""); claim != "email" { + issuer := OIDCIssuer{} + issuer.Type = IssuerTypeEmail + if claim := issuerToChallengeClaim(issuer, ""); claim != "email" { t.Fatalf("expected email subject claim for email issuer, got %s", claim) } - if claim := issuerToChallengeClaim(IssuerTypeSpiffe, ""); claim != "sub" { + issuer.Type = IssuerTypeSpiffe + if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { t.Fatalf("expected sub subject claim for SPIFFE issuer, got %s", claim) } - if claim := issuerToChallengeClaim(IssuerTypeUsername, ""); claim != "sub" { + issuer.Type = IssuerTypeUsername + if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { t.Fatalf("expected sub subject claim for username issuer, got %s", claim) } - if claim := issuerToChallengeClaim(IssuerTypeURI, ""); claim != "sub" { + issuer.Type = IssuerTypeURI + if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { t.Fatalf("expected sub subject claim for URI issuer, got %s", claim) } - if claim := issuerToChallengeClaim(IssuerTypeBuildkiteJob, ""); claim != "sub" { + issuer.Type = IssuerTypeBuildkiteJob + if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { t.Fatalf("expected sub subject claim for Buildkite issuer, got %s", claim) } - if claim := issuerToChallengeClaim(IssuerTypeGithubWorkflow, ""); claim != "sub" { + issuer.Type = IssuerTypeGithubWorkflow + if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { t.Fatalf("expected sub subject claim for GitHub issuer, got %s", claim) } - if claim := issuerToChallengeClaim(IssuerTypeGitLabPipeline, ""); claim != "sub" { + issuer.Type = IssuerTypeGitLabPipeline + if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { t.Fatalf("expected sub subject claim for GitLab issuer, got %s", claim) } - if claim := issuerToChallengeClaim(IssuerTypeCodefreshWorkflow, ""); claim != "sub" { + issuer.Type = IssuerTypeCodefreshWorkflow + if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { t.Fatalf("expected sub subject claim for Codefresh issuer, got %s", claim) } if claim := issuerToChallengeClaim(IssuerTypeChainguard, ""); claim != "sub" { @@ -504,12 +513,14 @@ func Test_issuerToChallengeClaim(t *testing.T) { if claim := issuerToChallengeClaim(IssuerTypeKubernetes, ""); claim != "sub" { t.Fatalf("expected sub subject claim for K8S issuer, got %s", claim) } + issuer.Type = "invalid" // unexpected issuer has empty claim and no claim was provided - if claim := issuerToChallengeClaim("invalid", ""); claim != "" { + if claim := issuerToChallengeClaim(issuer, ""); claim != "" { t.Fatalf("expected no claim for invalid issuer, got %s", claim) } // custom issuer provides a claim - if claim := issuerToChallengeClaim("custom", "email"); claim != "email" { + issuer.Type = "custom" + if claim := issuerToChallengeClaim(issuer, "email"); claim != "email" { t.Fatalf("expected email subject claim for custom issuer, got %s", claim) } } diff --git a/pkg/identity/generic/issuer_test.go b/pkg/identity/generic/issuer_test.go index dfaaf4268..2a9e3099d 100644 --- a/pkg/identity/generic/issuer_test.go +++ b/pkg/identity/generic/issuer_test.go @@ -15,67 +15,9 @@ package generic import ( - "context" - "encoding/json" "testing" - - "github.com/coreos/go-oidc/v3/oidc" - "github.com/sigstore/fulcio/pkg/config" - "github.com/sigstore/fulcio/pkg/identity" ) func TestIssuer(t *testing.T) { - ctx := context.Background() - url := "test-issuer-url" - issuer := Issuer(url) - - // test the Match function - t.Run("match", func(t *testing.T) { - if matches := issuer.Match(ctx, url); !matches { - t.Fatal("expected url to match but it doesn't") - } - if matches := issuer.Match(ctx, "some-other-url"); matches { - t.Fatal("expected match to fail but it didn't") - } - }) - - t.Run("authenticate", func(t *testing.T) { - token := &oidc.IDToken{ - Issuer: "https://iss.example.com", - Subject: "subject", - } - claims, err := json.Marshal(map[string]interface{}{ - "aud": "sigstore", - "iss": "https://iss.example.com", - "sub": "doesntmatter", - "email": "alice@example.com", - "email_verified": true, - }) - if err != nil { - t.Fatal(err) - } - withClaims(token, claims) - - ctx := config.With(context.Background(), &config.FulcioConfig{ - OIDCIssuers: map[string]config.OIDCIssuer{ - "https://iss.example.com": { - IssuerURL: "https://iss.example.com", - Type: config.IssuerTypeEmail, - ClientID: "sigstore", - }, - }, - }) - - identity.Authorize = func(_ context.Context, _ string, _ ...config.InsecureOIDCConfigOption) (*oidc.IDToken, error) { - return token, nil - } - principal, err := issuer.Authenticate(ctx, "token") - if err != nil { - t.Fatal(err) - } - if principal.Name(ctx) != "alice@example.com" { - t.Fatalf("got unexpected name %s", principal.Name(ctx)) - } - }) } diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go index 8f0b34632..60182280c 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/generic/principal.go @@ -20,6 +20,7 @@ import ( "crypto/x509" "fmt" "net/url" + "os" "strings" "text/template" @@ -27,9 +28,37 @@ import ( "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" + "gopkg.in/yaml.v3" ) -func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken, yaml RootYaml) (identity.Principal, error) { +type RootYaml struct { + Providers map[string]Provider +} + +type Provider struct { + Subject string + Extensions certificate.Extensions + Uris []string + Defaults map[string]string + OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` +} + +func readYaml() RootYaml { + var obj RootYaml + + yamlFile, err := os.ReadFile("../../config/config.yaml") + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, &obj) + if err != nil { + fmt.Printf("Unmarshal: %v", err) + } + + return obj +} + +func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { iss, ok := config.FromContext(ctx).GetIssuer(token.Issuer) if !ok { return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) @@ -40,7 +69,9 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken, yaml return nil, err } - provider := yaml.Providers[string(iss.Type)] + configYaml := readYaml() + + provider := configYaml.Providers[string(iss.Type)] e := provider.Extensions defaults := provider.Defaults finalExtensions := certificate.Extensions{ diff --git a/pkg/identity/generic/principal_test.go b/pkg/identity/generic/principal_test.go index 73ed3dd88..25e33a5b5 100644 --- a/pkg/identity/generic/principal_test.go +++ b/pkg/identity/generic/principal_test.go @@ -15,649 +15,18 @@ package generic import ( - "bytes" - "context" - "crypto/x509" - "encoding/asn1" - "encoding/json" - "errors" - "fmt" - "reflect" "testing" - "unsafe" - - "github.com/coreos/go-oidc/v3/oidc" - "github.com/sigstore/fulcio/pkg/identity" ) // TO BE IMPLEMENTED. Just keeped as a guide func TestWorkflowPrincipalFromIDToken(t *testing.T) { - tests := map[string]struct { - Claims map[string]interface{} - ExpectPrincipal Provider - WantErr bool - ErrContains string - }{ - `Valid token authenticates with correct claims`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - ExpectPrincipal: Provider{}, - WantErr: false, - }, - `Token missing job_workflow_ref claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "job_workflow_ref", - }, - `Token missing sha should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "sha", - }, - `Token missing event_name claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "event_name", - }, - `Token missing repository claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "repository", - }, - `Token missing workflow claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "workflow", - }, - `Token missing ref claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "ref", - }, - `Token missing job_workflow_sha claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "job_workflow_sha", - }, - `Token missing runner_environment claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "runner_environment", - }, - `Token missing repository_id claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "repository_id", - }, - `Token missing repository_owner claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "repository_owner", - }, - `Token missing repository_owner_id claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "repository_owner_id", - }, - `Token missing workflow_ref claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "workflow_ref", - }, - `Token missing workflow_sha claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - }, - WantErr: true, - ErrContains: "workflow_sha", - }, - `Token missing run_id claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "run_id", - }, - `Token missing run_attempt claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "run_attempt", - }, - `Token missing repository_visibility claim should be rejected`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - WantErr: true, - ErrContains: "repository_visibility", - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - token := &oidc.IDToken{ - Issuer: test.Claims["iss"].(string), - Subject: test.Claims["sub"].(string), - } - claims, err := json.Marshal(test.Claims) - if err != nil { - t.Fatal(err) - } - withClaims(token, claims) - - // untyped, err := WorkflowPrincipalFromIDToken(context.TODO(), token) - // if err != nil { - // if !test.WantErr { - // t.Fatal("didn't expect error", err) - // } - // if !strings.Contains(err.Error(), test.ErrContains) { - // t.Fatalf("expected error %s to contain %s", err, test.ErrContains) - // } - // return - // } - // if err == nil && test.WantErr { - // t.Fatal("expected error but got none") - // } - // principal, ok := untyped.(*Provider) - // if !ok { - // t.Errorf("Got wrong principal type %v", untyped) - // } - // if *principal != test.ExpectPrincipal { - // t.Errorf("got %v principal and expected %v", *principal, test.ExpectPrincipal) - // } - }) - } -} - -// reflect hack because "claims" field is unexported by oidc IDToken -// https://github.com/coreos/go-oidc/pull/329 -func withClaims(token *oidc.IDToken, data []byte) { - val := reflect.Indirect(reflect.ValueOf(token)) - member := val.FieldByName("claims") - pointer := unsafe.Pointer(member.UnsafeAddr()) - realPointer := (*[]byte)(pointer) - *realPointer = data } func TestName(t *testing.T) { - tests := map[string]struct { - Claims map[string]interface{} - ExpectName string - }{ - `Valid token authenticates with correct claims`: { - Claims: map[string]interface{}{ - "aud": "sigstore", - "event_name": "push", - "exp": 0, - "iss": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", - "job_workflow_sha": "example-sha", - "ref": "refs/heads/main", - "repository": "sigstore/fulcio", - "repository_id": "12345", - "repository_owner": "username", - "repository_owner_id": "345", - "repository_visibility": "public", - "run_attempt": "1", - "run_id": "42", - "runner_environment": "cloud-hosted", - "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sub": "repo:sigstore/fulcio:ref:refs/heads/main", - "workflow": "foo", - "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", - "workflow_sha": "example-sha-other", - }, - ExpectName: "repo:sigstore/fulcio:ref:refs/heads/main", - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - token := &oidc.IDToken{ - Issuer: test.Claims["iss"].(string), - Subject: test.Claims["sub"].(string), - } - claims, err := json.Marshal(test.Claims) - if err != nil { - t.Fatal(err) - } - withClaims(token, claims) - - principal, err := WorkflowPrincipalFromIDToken(context.TODO(), token) - if err != nil { - t.Fatal(err) - } - - gotName := principal.Name(context.TODO()) - if gotName != test.ExpectName { - t.Error("name should match sub claim") - } - }) - } } func TestEmbed(t *testing.T) { - tests := map[string]struct { - Principal identity.Principal - WantErr bool - WantFacts map[string]func(x509.Certificate) error - }{ - `Github workflow challenge should have all Github workflow extensions and issuer set`: { - Principal: &Provider{}, - WantErr: false, - WantFacts: map[string]func(x509.Certificate) error{ - `Certifificate should have correct issuer`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, "https://token.actions.githubusercontent.com"), - `Certificate has correct trigger extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, "trigger"), - `Certificate has correct SHA extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}, "sha"), - `Certificate has correct workflow extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}, "workflowname"), - `Certificate has correct repository extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}, "repository"), - `Certificate has correct ref extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, "ref"), - `Certificate has correct issuer (v2) extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 8}, "https://token.actions.githubusercontent.com"), - `Certificate has correct builder signer URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 9}, "https://github.com/jobWorkflowRef"), - `Certificate has correct builder signer digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 10}, "jobWorkflowSha"), - `Certificate has correct runner environment extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 11}, "runnerEnv"), - `Certificate has correct source repo URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 12}, "https://github.com/repository"), - `Certificate has correct source repo digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 13}, "sha"), - `Certificate has correct source repo ref extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 14}, "ref"), - `Certificate has correct source repo ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 15}, "repoID"), - `Certificate has correct source repo owner URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 16}, "https://github.com/repoOwner"), - `Certificate has correct source repo owner ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 17}, "repoOwnerID"), - `Certificate has correct build config URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 18}, "https://github.com/workflowRef"), - `Certificate has correct build config digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 19}, "workflowSHA"), - `Certificate has correct build trigger extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 20}, "trigger"), - `Certificate has correct run invocation ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}, "https://github.com/repository/actions/runs/runID/attempts/runAttempt"), - `Certificate has correct source repository visibility extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}, "public"), - }, - }, - `Github workflow value with bad URL fails`: { - Principal: &Provider{}, - WantErr: true, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var cert x509.Certificate - err := test.Principal.Embed(context.TODO(), &cert) - if err != nil { - if !test.WantErr { - t.Error(err) - } - return - } else if test.WantErr { - t.Error("expected error") - } - for factName, fact := range test.WantFacts { - t.Run(factName, func(t *testing.T) { - if err := fact(cert); err != nil { - t.Error(err) - } - }) - } - }) - } -} - -func factExtensionIs(oid asn1.ObjectIdentifier, value string) func(x509.Certificate) error { - return func(cert x509.Certificate) error { - for _, ext := range cert.ExtraExtensions { - if ext.Id.Equal(oid) { - var strVal string - _, _ = asn1.Unmarshal(ext.Value, &strVal) - if value != strVal { - return fmt.Errorf("expected oid %v to be %s, but got %s", oid, value, strVal) - } - return nil - } - } - return errors.New("extension not set") - } -} -func factDeprecatedExtensionIs(oid asn1.ObjectIdentifier, value string) func(x509.Certificate) error { - return func(cert x509.Certificate) error { - for _, ext := range cert.ExtraExtensions { - if ext.Id.Equal(oid) { - if !bytes.Equal(ext.Value, []byte(value)) { - return fmt.Errorf("expected oid %v to be %s, but got %s", oid, value, ext.Value) - } - return nil - } - } - return errors.New("extension not set") - } } diff --git a/pkg/server/grpc_server_test.go b/pkg/server/grpc_server_test.go index 999083160..6a008e8d1 100644 --- a/pkg/server/grpc_server_test.go +++ b/pkg/server/grpc_server_test.go @@ -243,7 +243,8 @@ func TestGetConfiguration(t *testing.T) { %q: { "IssuerURL": %q, "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "IsCiProvider": true }, %q: { "IssuerURL": %q, From ee93df8a45b37ec927127db578c01f748c5c4775 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Thu, 6 Jun 2024 15:18:08 +0000 Subject: [PATCH 08/47] removing drafting logic for generic principal, to be done in another pr Signed-off-by: Javan lacerda --- pkg/identity/generic/issuer_test.go | 1 + pkg/identity/generic/principal.go | 111 +--------------------------- 2 files changed, 5 insertions(+), 107 deletions(-) diff --git a/pkg/identity/generic/issuer_test.go b/pkg/identity/generic/issuer_test.go index 2a9e3099d..e728b30e4 100644 --- a/pkg/identity/generic/issuer_test.go +++ b/pkg/identity/generic/issuer_test.go @@ -18,6 +18,7 @@ import ( "testing" ) +// TO BE IMPLEMENTED. Just keeped as a guide func TestIssuer(t *testing.T) { } diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go index 60182280c..b12596264 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/generic/principal.go @@ -15,26 +15,16 @@ package generic import ( - "bytes" "context" "crypto/x509" - "fmt" "net/url" - "os" - "strings" - "text/template" "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" - "gopkg.in/yaml.v3" ) -type RootYaml struct { - Providers map[string]Provider -} - type Provider struct { Subject string Extensions certificate.Extensions @@ -43,107 +33,14 @@ type Provider struct { OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` } -func readYaml() RootYaml { - var obj RootYaml - - yamlFile, err := os.ReadFile("../../config/config.yaml") - if err != nil { - fmt.Printf("yamlFile.Get err #%v ", err) - } - err = yaml.Unmarshal(yamlFile, &obj) - if err != nil { - fmt.Printf("Unmarshal: %v", err) - } - - return obj -} - +// TO BE IMPLEMENTED. Just keeped as a guide func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { - iss, ok := config.FromContext(ctx).GetIssuer(token.Issuer) - if !ok { - return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) - } - - var claims map[string]string - if err := token.Claims(&claims); err != nil { - return nil, err - } - - configYaml := readYaml() - - provider := configYaml.Providers[string(iss.Type)] - e := provider.Extensions - defaults := provider.Defaults - finalExtensions := certificate.Extensions{ - Issuer: ApplyTemplate(e.Issuer, claims, defaults), - GithubWorkflowTrigger: ApplyTemplate(e.GithubWorkflowTrigger, claims, defaults), - GithubWorkflowSHA: ApplyTemplate(e.GithubWorkflowSHA, claims, defaults), - GithubWorkflowName: ApplyTemplate(e.GithubWorkflowName, claims, defaults), - GithubWorkflowRepository: ApplyTemplate(e.GithubWorkflowRepository, claims, defaults), - GithubWorkflowRef: ApplyTemplate(e.GithubWorkflowRef, claims, defaults), - BuildSignerURI: ApplyTemplate(e.BuildSignerURI, claims, defaults), - BuildConfigDigest: ApplyTemplate(e.BuildConfigDigest, claims, defaults), - RunnerEnvironment: ApplyTemplate(e.RunnerEnvironment, claims, defaults), - SourceRepositoryURI: ApplyTemplate(e.SourceRepositoryURI, claims, defaults), - SourceRepositoryDigest: ApplyTemplate(e.SourceRepositoryDigest, claims, defaults), - SourceRepositoryRef: ApplyTemplate(e.SourceRepositoryRef, claims, defaults), - SourceRepositoryIdentifier: ApplyTemplate(e.SourceRepositoryIdentifier, claims, defaults), - SourceRepositoryOwnerURI: ApplyTemplate(e.SourceRepositoryOwnerURI, claims, defaults), - SourceRepositoryOwnerIdentifier: ApplyTemplate(e.SourceRepositoryOwnerIdentifier, claims, defaults), - BuildConfigURI: ApplyTemplate(e.BuildConfigURI, claims, defaults), - BuildSignerDigest: ApplyTemplate(e.BuildSignerDigest, claims, defaults), - BuildTrigger: ApplyTemplate(e.BuildTrigger, claims, defaults), - RunInvocationURI: ApplyTemplate(e.RunInvocationURI, claims, defaults), - SourceRepositoryVisibilityAtSigning: ApplyTemplate(e.SourceRepositoryVisibilityAtSigning, claims, defaults), - } - finalUris := make([]string, len(provider.Uris)-1) - for _, val := range provider.Uris { - finalUris = append(finalUris, ApplyTemplate(val, claims, defaults)) - } - - return &Provider{ - Subject: token.Subject, - Extensions: finalExtensions, - Uris: finalUris, - OIDCIssuers: provider.OIDCIssuers, - }, nil -} - -func ApplyTemplate(path string, data map[string]string, defaultData map[string]string) string { - - // Here we merge the data from was claimed by the id token with the - // default data provided by the yaml file. - // The order here matter because we want to override the default data - // with the claimed data. - mergedData := make(map[string]string) - for k, v := range defaultData { - mergedData[k] = v - } - for k, v := range data { - mergedData[k] = v - } - - // It checks it is a path or a raw field by - // checking exists template syntax into the string - if strings.Contains(path, "{{") { - var doc bytes.Buffer - t := template.New("") - p, err := t.Parse(path) - if err != nil { - panic(err) - } - err = p.Execute(&doc, mergedData) - if err != nil { - panic(err) - } - return doc.String() - } else { - return mergedData[path] - } + return nil, nil } +// TO BE IMPLEMENTED. Just keeped as a guide func (p Provider) Name(_ context.Context) string { - return p.Subject + return "" } func (p Provider) Embed(_ context.Context, cert *x509.Certificate) error { From f7cd58aabed0423ae440a6f50ce51053739d74e8 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Thu, 6 Jun 2024 15:25:39 +0000 Subject: [PATCH 09/47] fixes Signed-off-by: Javan lacerda --- pkg/config/config.go | 2 - pkg/config/config.yaml | 50 ------------------ pkg/config/config_reader.go | 72 -------------------------- pkg/identity/generic/issuer_test.go | 2 +- pkg/identity/generic/principal.go | 4 +- pkg/identity/generic/principal_test.go | 6 +-- 6 files changed, 6 insertions(+), 130 deletions(-) delete mode 100644 pkg/config/config.yaml delete mode 100644 pkg/config/config_reader.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 8c4bdf2f3..f349e1369 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -272,8 +272,6 @@ func (fc *FulcioConfig) prepare() error { return nil } -// I would like to remove this completely, to be able to add or remove -// oidc providers only by updating the config.yaml file. type IssuerType string const ( diff --git a/pkg/config/config.yaml b/pkg/config/config.yaml deleted file mode 100644 index 35176a1ef..000000000 --- a/pkg/config/config.yaml +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2024 The Sigstore Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# TODO: Fill it properly for the current providers -providers: - github: - extensions: - build-signer-digest: job_workflow_sha - source-repository-digest: sha - source-repository-ref: ref - source-repository-identifier: repository_id - run-invocation-uri: "{{.url}}/{{.repository}}/actions/runs/{{.run_id}}/" - uris: - - "{{.url}}/{{.job_workflow_ref}}" - defaults: - url: https://github.com - gitlab: - extensions: - build-signer-digest: ci_config_sha - source-repository-digest: sha - source-repository-ref: ref - source-repository-identifier: project_id - run-invocation-uri: "{{.url}}/{{.project_path}}/-/jobs/{{.job_id}}" - uris: - - ci_config_ref_uri - defaults: - url: https://gitlab.com - codefresh: - extensions: - build-signer-digest: build/{{.workflow_id}} - runner-environment: runner_environment - source-repository-uri: scm_repo_url - source-repository-ref: scm_ref - build-config-uri: api/pipelines/{{.pipeline_id}} - run-invocation-uri: build/{{.workflow_id}} - uris: - - "{{.platform_url}}/{{.account_name}}/{{.pipeline_name}}:{{.account_id}}/{{.pipeline_id}}" - defaults: - platform_url: https://g.codefresh.io diff --git a/pkg/config/config_reader.go b/pkg/config/config_reader.go deleted file mode 100644 index 2a0d55d8c..000000000 --- a/pkg/config/config_reader.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2024 The Sigstore Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package config - -import ( - "fmt" - "os" - - "gopkg.in/yaml.v3" -) - -type Extensions struct { - Issuer string // OID 1.3.6.1.4.1.57264.1.8 and 1.3.6.1.4.1.57264.1.1 (Deprecated) - Subject string - GithubWorkflowTrigger string `yaml:"github-workflow-trigger"` // OID 1.3.6.1.4.1.57264.1.2 - GithubWorkflowSHA string `yaml:"github-workflow-sha"` // OID 1.3.6.1.4.1.57264.1.3 - GithubWorkflowName string `yaml:"github-workflow-name"` // OID 1.3.6.1.4.1.57264.1.4 - GithubWorkflowRepository string `yaml:"github-workflow-repository"` // OID 1.3.6.1.4.1.57264.1.5 - GithubWorkflowRef string `yaml:"github-workflow-ref"` // 1.3.6.1.4.1.57264.1.6 - BuildSignerURI string `yaml:"build-signer-uri"` // 1.3.6.1.4.1.57264.1.9 - BuildSignerDigest string `yaml:"build-signer-digest"` // 1.3.6.1.4.1.57264.1.10 - RunnerEnvironment string `yaml:"runner-environment"` // 1.3.6.1.4.1.57264.1.11 - SourceRepositoryURI string `yaml:"source-repository-uri"` // 1.3.6.1.4.1.57264.1.12 - SourceRepositoryDigest string `yaml:"source-repository-digest"` // 1.3.6.1.4.1.57264.1.13 - SourceRepositoryRef string `yaml:"source-repository-ref"` // 1.3.6.1.4.1.57264.1.14 - SourceRepositoryIdentifier string `yaml:"source-repository-identifier"` // 1.3.6.1.4.1.57264.1.15 - SourceRepositoryOwnerURI string `yaml:"source-repository-owner-uri"` // 1.3.6.1.4.1.57264.1.16 - SourceRepositoryOwnerIdentifier string `yaml:"source-repository-owner-identifier"` // 1.3.6.1.4.1.57264.1.17 - BuildConfigURI string `yaml:"build-config-uri"` // 1.3.6.1.4.1.57264.1.18 - BuildConfigDigest string `yaml:"build-config-digest"` // 1.3.6.1.4.1.57264.1.19 - BuildTrigger string `yaml:"build-trigger"` // 1.3.6.1.4.1.57264.1.20 - RunInvocationURI string `yaml:"run-invocation-uri"` // 1.3.6.1.4.1.57264.1.21 - SourceRepositoryVisibilityAtSigning string `yaml:"source-repository-visibility-at-signing"` // 1.3.6.1.4.1.57264.1.22 -} - -type RootYaml struct { - Providers map[string]Provider -} - -type Provider struct { - Extensions Extensions - Uris []string - Defaults map[string]string -} - -func readYaml() RootYaml { - var obj RootYaml - - yamlFile, err := os.ReadFile("config.yaml") - if err != nil { - fmt.Printf("yamlFile.Get err #%v ", err) - } - err = yaml.Unmarshal(yamlFile, &obj) - if err != nil { - fmt.Printf("Unmarshal: %v", err) - } - - return obj -} diff --git a/pkg/identity/generic/issuer_test.go b/pkg/identity/generic/issuer_test.go index e728b30e4..77326f965 100644 --- a/pkg/identity/generic/issuer_test.go +++ b/pkg/identity/generic/issuer_test.go @@ -19,6 +19,6 @@ import ( ) // TO BE IMPLEMENTED. Just keeped as a guide -func TestIssuer(t *testing.T) { +func TestIssuer(_ *testing.T) { } diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go index b12596264..f3c9fbeae 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/generic/principal.go @@ -34,12 +34,12 @@ type Provider struct { } // TO BE IMPLEMENTED. Just keeped as a guide -func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { +func WorkflowPrincipalFromIDToken(_ context.Context, _ *oidc.IDToken) (identity.Principal, error) { return nil, nil } // TO BE IMPLEMENTED. Just keeped as a guide -func (p Provider) Name(_ context.Context) string { +func (Provider) Name(_ context.Context) string { return "" } diff --git a/pkg/identity/generic/principal_test.go b/pkg/identity/generic/principal_test.go index 25e33a5b5..96540fe90 100644 --- a/pkg/identity/generic/principal_test.go +++ b/pkg/identity/generic/principal_test.go @@ -19,14 +19,14 @@ import ( ) // TO BE IMPLEMENTED. Just keeped as a guide -func TestWorkflowPrincipalFromIDToken(t *testing.T) { +func TestWorkflowPrincipalFromIDToken(_ *testing.T) { } -func TestName(t *testing.T) { +func TestName(_ *testing.T) { } -func TestEmbed(t *testing.T) { +func TestEmbed(_ *testing.T) { } From ba9e42eabddabcddd8f9a2dac748cf5e9d39f43c Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Thu, 6 Jun 2024 15:37:16 +0000 Subject: [PATCH 10/47] generate config Signed-off-by: Javan lacerda --- config/fulcio-config.yaml | 51 ++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/config/fulcio-config.yaml b/config/fulcio-config.yaml index 0f7a0aded..876ae2459 100644 --- a/config/fulcio-config.yaml +++ b/config/fulcio-config.yaml @@ -20,43 +20,51 @@ data: "https://accounts.google.com": { "IssuerURL": "https://accounts.google.com", "ClientID": "sigstore", - "Type": "email" + "Type": "email", + "IsCiProvider": false }, "https://agent.buildkite.com": { "IssuerURL": "https://agent.buildkite.com", "ClientID": "sigstore", - "Type": "buildkite-job" + "Type": "buildkite-job", + "IsCiProvider": false }, "https://allow.pub": { "IssuerURL": "https://allow.pub", "ClientID": "sigstore", "Type": "spiffe", - "SPIFFETrustDomain": "allow.pub" + "SPIFFETrustDomain": "allow.pub", + "IsCiProvider": false }, "https://auth-staging.eclipse.org/realms/sigstore": { "IssuerURL": "https://auth-staging.eclipse.org/realms/sigstore", "ClientID": "sigstore", - "Type": "email" + "Type": "email", + "IsCiProvider": false }, "https://auth.eclipse.org/auth/realms/sigstore": { "IssuerURL": "https://auth.eclipse.org/auth/realms/sigstore", "ClientID": "sigstore", - "Type": "email" + "Type": "email", + "IsCiProvider": false }, "https://dev.gitlab.org": { "IssuerURL": "https://dev.gitlab.org", "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "IsCiProvider": false }, "https://gitlab.archlinux.org": { "IssuerURL": "https://gitlab.archlinux.org", "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "IsCiProvider": false }, "https://gitlab.com": { "IssuerURL": "https://gitlab.com", "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "IsCiProvider": false }, "https://issuer.enforce.dev": { "IssuerURL": "https://issuer.enforce.dev", @@ -67,44 +75,53 @@ data: "IssuerURL": "https://oauth2.sigstore.dev/auth", "ClientID": "sigstore", "Type": "email", - "IssuerClaim": "$.federated_claims.connector_id" + "IssuerClaim": "$.federated_claims.connector_id", + "IsCiProvider": false }, "https://oidc.codefresh.io": { "IssuerURL": "https://oidc.codefresh.io", "ClientID": "sigstore", - "Type": "codefresh-workflow" + "Type": "codefresh-workflow", + "IsCiProvider": false }, "https://ops.gitlab.net": { "IssuerURL": "https://ops.gitlab.net", "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "IsCiProvider": false }, "https://token.actions.githubusercontent.com": { "IssuerURL": "https://token.actions.githubusercontent.com", "ClientID": "sigstore", - "Type": "github-workflow" + "Type": "github-workflow", + "IsCiProvider": false } }, "MetaIssuers": { "https://*.oic.prod-aks.azure.com/*": { "ClientID": "sigstore", - "Type": "kubernetes" + "Type": "kubernetes", + "IsCiProvider": false }, "https://container.googleapis.com/v1/projects/*/locations/*/clusters/*": { "ClientID": "sigstore", - "Type": "kubernetes" + "Type": "kubernetes", + "IsCiProvider": false }, "https://oidc.eks.*.amazonaws.com/id/*": { "ClientID": "sigstore", - "Type": "kubernetes" + "Type": "kubernetes", + "IsCiProvider": false }, "https://oidc.prod-aks.azure.com/*": { "ClientID": "sigstore", - "Type": "kubernetes" + "Type": "kubernetes", + "IsCiProvider": false }, "https://token.actions.githubusercontent.com/*": { "ClientID": "sigstore", - "Type": "github-workflow" + "Type": "github-workflow", + "IsCiProvider": false } } } From e0e6c5390aa50b9134042d5e5b54725ceb5febda Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 11 Jun 2024 14:34:33 +0000 Subject: [PATCH 11/47] set ci provider as a new type instead of a flag Signed-off-by: Javan lacerda --- config/fulcio-config.yaml | 51 ++++++++++++------------------------ pkg/challenges/challenges.go | 43 +++++++++++++++--------------- pkg/config/config.go | 2 +- pkg/server/issuer_pool.go | 3 +++ 4 files changed, 42 insertions(+), 57 deletions(-) diff --git a/config/fulcio-config.yaml b/config/fulcio-config.yaml index 876ae2459..0f7a0aded 100644 --- a/config/fulcio-config.yaml +++ b/config/fulcio-config.yaml @@ -20,51 +20,43 @@ data: "https://accounts.google.com": { "IssuerURL": "https://accounts.google.com", "ClientID": "sigstore", - "Type": "email", - "IsCiProvider": false + "Type": "email" }, "https://agent.buildkite.com": { "IssuerURL": "https://agent.buildkite.com", "ClientID": "sigstore", - "Type": "buildkite-job", - "IsCiProvider": false + "Type": "buildkite-job" }, "https://allow.pub": { "IssuerURL": "https://allow.pub", "ClientID": "sigstore", "Type": "spiffe", - "SPIFFETrustDomain": "allow.pub", - "IsCiProvider": false + "SPIFFETrustDomain": "allow.pub" }, "https://auth-staging.eclipse.org/realms/sigstore": { "IssuerURL": "https://auth-staging.eclipse.org/realms/sigstore", "ClientID": "sigstore", - "Type": "email", - "IsCiProvider": false + "Type": "email" }, "https://auth.eclipse.org/auth/realms/sigstore": { "IssuerURL": "https://auth.eclipse.org/auth/realms/sigstore", "ClientID": "sigstore", - "Type": "email", - "IsCiProvider": false + "Type": "email" }, "https://dev.gitlab.org": { "IssuerURL": "https://dev.gitlab.org", "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "IsCiProvider": false + "Type": "gitlab-pipeline" }, "https://gitlab.archlinux.org": { "IssuerURL": "https://gitlab.archlinux.org", "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "IsCiProvider": false + "Type": "gitlab-pipeline" }, "https://gitlab.com": { "IssuerURL": "https://gitlab.com", "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "IsCiProvider": false + "Type": "gitlab-pipeline" }, "https://issuer.enforce.dev": { "IssuerURL": "https://issuer.enforce.dev", @@ -75,53 +67,44 @@ data: "IssuerURL": "https://oauth2.sigstore.dev/auth", "ClientID": "sigstore", "Type": "email", - "IssuerClaim": "$.federated_claims.connector_id", - "IsCiProvider": false + "IssuerClaim": "$.federated_claims.connector_id" }, "https://oidc.codefresh.io": { "IssuerURL": "https://oidc.codefresh.io", "ClientID": "sigstore", - "Type": "codefresh-workflow", - "IsCiProvider": false + "Type": "codefresh-workflow" }, "https://ops.gitlab.net": { "IssuerURL": "https://ops.gitlab.net", "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "IsCiProvider": false + "Type": "gitlab-pipeline" }, "https://token.actions.githubusercontent.com": { "IssuerURL": "https://token.actions.githubusercontent.com", "ClientID": "sigstore", - "Type": "github-workflow", - "IsCiProvider": false + "Type": "github-workflow" } }, "MetaIssuers": { "https://*.oic.prod-aks.azure.com/*": { "ClientID": "sigstore", - "Type": "kubernetes", - "IsCiProvider": false + "Type": "kubernetes" }, "https://container.googleapis.com/v1/projects/*/locations/*/clusters/*": { "ClientID": "sigstore", - "Type": "kubernetes", - "IsCiProvider": false + "Type": "kubernetes" }, "https://oidc.eks.*.amazonaws.com/id/*": { "ClientID": "sigstore", - "Type": "kubernetes", - "IsCiProvider": false + "Type": "kubernetes" }, "https://oidc.prod-aks.azure.com/*": { "ClientID": "sigstore", - "Type": "kubernetes", - "IsCiProvider": false + "Type": "kubernetes" }, "https://token.actions.githubusercontent.com/*": { "ClientID": "sigstore", - "Type": "github-workflow", - "IsCiProvider": false + "Type": "github-workflow" } } } diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index e40a31a4a..81bc27df4 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -59,29 +59,28 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin } var principal identity.Principal var err error - if iss.IsCiProvider { + switch iss.Type { + case config.IssuerTypeCiProvider: principal, err = generic.WorkflowPrincipalFromIDToken(ctx, tok) - } else { - switch iss.Type { - case config.IssuerTypeBuildkiteJob: - principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) - case config.IssuerTypeGitLabPipeline: - principal, err = gitlabcom.JobPrincipalFromIDToken(ctx, tok) - case config.IssuerTypeEmail: - principal, err = email.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeSpiffe: - principal, err = spiffe.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeGithubWorkflow: - principal, err = github.WorkflowPrincipalFromIDToken(ctx, tok) - case config.IssuerTypeKubernetes: - principal, err = kubernetes.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeURI: - principal, err = uri.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeUsername: - principal, err = username.PrincipalFromIDToken(ctx, tok) - default: - return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) - } + case config.IssuerTypeBuildkiteJob: + principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) + case config.IssuerTypeGitLabPipeline: + principal, err = gitlabcom.JobPrincipalFromIDToken(ctx, tok) + case config.IssuerTypeEmail: + principal, err = email.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeSpiffe: + principal, err = spiffe.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeGithubWorkflow: + principal, err = github.WorkflowPrincipalFromIDToken(ctx, tok) + case config.IssuerTypeKubernetes: + principal, err = kubernetes.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeURI: + principal, err = uri.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeUsername: + principal, err = username.PrincipalFromIDToken(ctx, tok) + default: + return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) + } if err != nil { return nil, err diff --git a/pkg/config/config.go b/pkg/config/config.go index f349e1369..e9ffa443e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -126,7 +126,6 @@ func (fc *FulcioConfig) GetIssuer(issuerURL string) (OIDCIssuer, bool) { Type: iss.Type, IssuerClaim: iss.IssuerClaim, SubjectDomain: iss.SubjectDomain, - IsCiProvider: iss.IsCiProvider, }, true } } @@ -285,6 +284,7 @@ const ( IssuerTypeSpiffe = "spiffe" IssuerTypeURI = "uri" IssuerTypeUsername = "username" + IssuerTypeCiProvider = "ci-provider" ) func parseConfig(b []byte) (cfg *FulcioConfig, err error) { diff --git a/pkg/server/issuer_pool.go b/pkg/server/issuer_pool.go index 18349f262..4f5f7303d 100644 --- a/pkg/server/issuer_pool.go +++ b/pkg/server/issuer_pool.go @@ -21,6 +21,7 @@ import ( "github.com/sigstore/fulcio/pkg/identity/chainguard" "github.com/sigstore/fulcio/pkg/identity/codefresh" "github.com/sigstore/fulcio/pkg/identity/email" + "github.com/sigstore/fulcio/pkg/identity/generic" "github.com/sigstore/fulcio/pkg/identity/github" "github.com/sigstore/fulcio/pkg/identity/gitlabcom" "github.com/sigstore/fulcio/pkg/identity/kubernetes" @@ -53,6 +54,8 @@ func getIssuer(meta string, i config.OIDCIssuer) identity.Issuer { issuerURL = meta } switch i.Type { + case config.IssuerTypeCiProvider: + return generic.Issuer(issuerURL) case config.IssuerTypeEmail: return email.Issuer(issuerURL) case config.IssuerTypeGithubWorkflow: From 4930187c55d169a0fa6b303c9175fa66000dd267 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 11 Jun 2024 16:27:29 +0000 Subject: [PATCH 12/47] rollback isserToChallengeC Signed-off-by: Javan lacerda --- pkg/challenges/challenges.go | 1 - pkg/config/config.go | 8 +++---- pkg/config/config_test.go | 31 +++++++++----------------- pkg/identity/generic/issuer_test.go | 2 +- pkg/identity/generic/principal.go | 4 ++-- pkg/identity/generic/principal_test.go | 2 +- 6 files changed, 18 insertions(+), 30 deletions(-) diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index 81bc27df4..667beaf31 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -80,7 +80,6 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin principal, err = username.PrincipalFromIDToken(ctx, tok) default: return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) - } if err != nil { return nil, err diff --git a/pkg/config/config.go b/pkg/config/config.go index e9ffa443e..2d46e64ed 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -205,7 +205,7 @@ func (fc *FulcioConfig) ToIssuers() []*fulciogrpc.OIDCIssuer { Issuer: &fulciogrpc.OIDCIssuer_IssuerUrl{IssuerUrl: cfgIss.IssuerURL}, Audience: cfgIss.ClientID, SpiffeTrustDomain: cfgIss.SPIFFETrustDomain, - ChallengeClaim: issuerToChallengeClaim(cfgIss, cfgIss.ChallengeClaim), + ChallengeClaim: issuerToChallengeClaim(cfgIss.Type, cfgIss.ChallengeClaim), } issuers = append(issuers, issuer) } @@ -215,7 +215,7 @@ func (fc *FulcioConfig) ToIssuers() []*fulciogrpc.OIDCIssuer { Issuer: &fulciogrpc.OIDCIssuer_WildcardIssuerUrl{WildcardIssuerUrl: metaIss}, Audience: cfgIss.ClientID, SpiffeTrustDomain: cfgIss.SPIFFETrustDomain, - ChallengeClaim: issuerToChallengeClaim(cfgIss, cfgIss.ChallengeClaim), + ChallengeClaim: issuerToChallengeClaim(cfgIss.Type, cfgIss.ChallengeClaim), } issuers = append(issuers, issuer) } @@ -375,7 +375,7 @@ func validateConfig(conf *FulcioConfig) error { } } - if issuerToChallengeClaim(issuer, issuer.ChallengeClaim) == "" { + if issuerToChallengeClaim(issuer.Type, issuer.ChallengeClaim) == "" { return errors.New("issuer missing challenge claim") } } @@ -387,7 +387,7 @@ func validateConfig(conf *FulcioConfig) error { return errors.New("SPIFFE meta issuers not supported") } - if issuerToChallengeClaim(metaIssuer, metaIssuer.ChallengeClaim) == "" { + if issuerToChallengeClaim(metaIssuer.Type, metaIssuer.ChallengeClaim) == "" { return errors.New("issuer missing challenge claim") } } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 11e4defcb..4c0967660 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -474,37 +474,28 @@ func Test_validateAllowedDomain(t *testing.T) { } func Test_issuerToChallengeClaim(t *testing.T) { - issuer := OIDCIssuer{} - issuer.Type = IssuerTypeEmail - if claim := issuerToChallengeClaim(issuer, ""); claim != "email" { + if claim := issuerToChallengeClaim(IssuerTypeEmail, ""); claim != "email" { t.Fatalf("expected email subject claim for email issuer, got %s", claim) } - issuer.Type = IssuerTypeSpiffe - if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { + if claim := issuerToChallengeClaim(IssuerTypeSpiffe, ""); claim != "sub" { t.Fatalf("expected sub subject claim for SPIFFE issuer, got %s", claim) } - issuer.Type = IssuerTypeUsername - if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { + if claim := issuerToChallengeClaim(IssuerTypeUsername, ""); claim != "sub" { t.Fatalf("expected sub subject claim for username issuer, got %s", claim) } - issuer.Type = IssuerTypeURI - if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { + if claim := issuerToChallengeClaim(IssuerTypeURI, ""); claim != "sub" { t.Fatalf("expected sub subject claim for URI issuer, got %s", claim) } - issuer.Type = IssuerTypeBuildkiteJob - if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { + if claim := issuerToChallengeClaim(IssuerTypeBuildkiteJob, ""); claim != "sub" { t.Fatalf("expected sub subject claim for Buildkite issuer, got %s", claim) } - issuer.Type = IssuerTypeGithubWorkflow - if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { + if claim := issuerToChallengeClaim(IssuerTypeGithubWorkflow, ""); claim != "sub" { t.Fatalf("expected sub subject claim for GitHub issuer, got %s", claim) } - issuer.Type = IssuerTypeGitLabPipeline - if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { + if claim := issuerToChallengeClaim(IssuerTypeGitLabPipeline, ""); claim != "sub" { t.Fatalf("expected sub subject claim for GitLab issuer, got %s", claim) } - issuer.Type = IssuerTypeCodefreshWorkflow - if claim := issuerToChallengeClaim(issuer, ""); claim != "sub" { + if claim := issuerToChallengeClaim(IssuerTypeCodefreshWorkflow, ""); claim != "sub" { t.Fatalf("expected sub subject claim for Codefresh issuer, got %s", claim) } if claim := issuerToChallengeClaim(IssuerTypeChainguard, ""); claim != "sub" { @@ -513,14 +504,12 @@ func Test_issuerToChallengeClaim(t *testing.T) { if claim := issuerToChallengeClaim(IssuerTypeKubernetes, ""); claim != "sub" { t.Fatalf("expected sub subject claim for K8S issuer, got %s", claim) } - issuer.Type = "invalid" // unexpected issuer has empty claim and no claim was provided - if claim := issuerToChallengeClaim(issuer, ""); claim != "" { + if claim := issuerToChallengeClaim("invalid", ""); claim != "" { t.Fatalf("expected no claim for invalid issuer, got %s", claim) } // custom issuer provides a claim - issuer.Type = "custom" - if claim := issuerToChallengeClaim(issuer, "email"); claim != "email" { + if claim := issuerToChallengeClaim("custom", "email"); claim != "email" { t.Fatalf("expected email subject claim for custom issuer, got %s", claim) } } diff --git a/pkg/identity/generic/issuer_test.go b/pkg/identity/generic/issuer_test.go index 77326f965..060796bb0 100644 --- a/pkg/identity/generic/issuer_test.go +++ b/pkg/identity/generic/issuer_test.go @@ -18,7 +18,7 @@ import ( "testing" ) -// TO BE IMPLEMENTED. Just keeped as a guide +// TO BE IMPLEMENTED. Just kept as a guide func TestIssuer(_ *testing.T) { } diff --git a/pkg/identity/generic/principal.go b/pkg/identity/generic/principal.go index f3c9fbeae..a7eea734a 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/generic/principal.go @@ -33,12 +33,12 @@ type Provider struct { OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` } -// TO BE IMPLEMENTED. Just keeped as a guide +// TO BE IMPLEMENTED. Just kept as a guide func WorkflowPrincipalFromIDToken(_ context.Context, _ *oidc.IDToken) (identity.Principal, error) { return nil, nil } -// TO BE IMPLEMENTED. Just keeped as a guide +// TO BE IMPLEMENTED. Just kept as a guide func (Provider) Name(_ context.Context) string { return "" } diff --git a/pkg/identity/generic/principal_test.go b/pkg/identity/generic/principal_test.go index 96540fe90..0c9d1adac 100644 --- a/pkg/identity/generic/principal_test.go +++ b/pkg/identity/generic/principal_test.go @@ -18,7 +18,7 @@ import ( "testing" ) -// TO BE IMPLEMENTED. Just keeped as a guide +// TO BE IMPLEMENTED. Just kept as a guide func TestWorkflowPrincipalFromIDToken(_ *testing.T) { } From cafe1db6420e604666d98821c161a276648003d9 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 11 Jun 2024 16:39:55 +0000 Subject: [PATCH 13/47] change module name to ci_provider Signed-off-by: Javan lacerda --- pkg/challenges/challenges.go | 2 +- pkg/identity/{generic => ci_provider}/issuer.go | 8 ++++---- pkg/identity/{generic => ci_provider}/issuer_test.go | 2 +- pkg/identity/{generic => ci_provider}/principal.go | 2 +- pkg/identity/{generic => ci_provider}/principal_test.go | 2 +- pkg/server/issuer_pool.go | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) rename pkg/identity/{generic => ci_provider}/issuer.go (79%) rename pkg/identity/{generic => ci_provider}/issuer_test.go (97%) rename pkg/identity/{generic => ci_provider}/principal.go (98%) rename pkg/identity/{generic => ci_provider}/principal_test.go (97%) diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index 667beaf31..09817c2b2 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -27,8 +27,8 @@ import ( "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" + generic "github.com/sigstore/fulcio/pkg/identity/ci_provider" "github.com/sigstore/fulcio/pkg/identity/email" - "github.com/sigstore/fulcio/pkg/identity/generic" "github.com/sigstore/fulcio/pkg/identity/github" "github.com/sigstore/fulcio/pkg/identity/gitlabcom" "github.com/sigstore/fulcio/pkg/identity/kubernetes" diff --git a/pkg/identity/generic/issuer.go b/pkg/identity/ci_provider/issuer.go similarity index 79% rename from pkg/identity/generic/issuer.go rename to pkg/identity/ci_provider/issuer.go index 5ed6a26e5..23fbdaa65 100644 --- a/pkg/identity/generic/issuer.go +++ b/pkg/identity/ci_provider/issuer.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package generic +package ci_provider import ( "context" @@ -22,15 +22,15 @@ import ( "github.com/sigstore/fulcio/pkg/identity/base" ) -type genericIssuer struct { +type ciProviderIssuer struct { identity.Issuer } func Issuer(issuerURL string) identity.Issuer { - return &genericIssuer{base.Issuer(issuerURL)} + return &ciProviderIssuer{base.Issuer(issuerURL)} } -func (e *genericIssuer) Authenticate(ctx context.Context, token string, opts ...config.InsecureOIDCConfigOption) (identity.Principal, error) { +func (e *ciProviderIssuer) Authenticate(ctx context.Context, token string, opts ...config.InsecureOIDCConfigOption) (identity.Principal, error) { idtoken, err := identity.Authorize(ctx, token, opts...) if err != nil { return nil, err diff --git a/pkg/identity/generic/issuer_test.go b/pkg/identity/ci_provider/issuer_test.go similarity index 97% rename from pkg/identity/generic/issuer_test.go rename to pkg/identity/ci_provider/issuer_test.go index 060796bb0..59773c3f8 100644 --- a/pkg/identity/generic/issuer_test.go +++ b/pkg/identity/ci_provider/issuer_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package generic +package ci_provider import ( "testing" diff --git a/pkg/identity/generic/principal.go b/pkg/identity/ci_provider/principal.go similarity index 98% rename from pkg/identity/generic/principal.go rename to pkg/identity/ci_provider/principal.go index a7eea734a..cdbfd3945 100644 --- a/pkg/identity/generic/principal.go +++ b/pkg/identity/ci_provider/principal.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package generic +package ci_provider import ( "context" diff --git a/pkg/identity/generic/principal_test.go b/pkg/identity/ci_provider/principal_test.go similarity index 97% rename from pkg/identity/generic/principal_test.go rename to pkg/identity/ci_provider/principal_test.go index 0c9d1adac..eda7811ab 100644 --- a/pkg/identity/generic/principal_test.go +++ b/pkg/identity/ci_provider/principal_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package generic +package ci_provider import ( "testing" diff --git a/pkg/server/issuer_pool.go b/pkg/server/issuer_pool.go index 4f5f7303d..c669fa865 100644 --- a/pkg/server/issuer_pool.go +++ b/pkg/server/issuer_pool.go @@ -19,9 +19,9 @@ import ( "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" "github.com/sigstore/fulcio/pkg/identity/chainguard" + "github.com/sigstore/fulcio/pkg/identity/ci_provider" "github.com/sigstore/fulcio/pkg/identity/codefresh" "github.com/sigstore/fulcio/pkg/identity/email" - "github.com/sigstore/fulcio/pkg/identity/generic" "github.com/sigstore/fulcio/pkg/identity/github" "github.com/sigstore/fulcio/pkg/identity/gitlabcom" "github.com/sigstore/fulcio/pkg/identity/kubernetes" @@ -55,7 +55,7 @@ func getIssuer(meta string, i config.OIDCIssuer) identity.Issuer { } switch i.Type { case config.IssuerTypeCiProvider: - return generic.Issuer(issuerURL) + return ci_provider.Issuer(issuerURL) case config.IssuerTypeEmail: return email.Issuer(issuerURL) case config.IssuerTypeGithubWorkflow: From c3651531609f960a020770389fdb034bae912bdd Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 11 Jun 2024 16:41:32 +0000 Subject: [PATCH 14/47] remove IsCiProvider from test Signed-off-by: Javan lacerda --- pkg/challenges/challenges.go | 4 ++-- pkg/server/grpc_server_test.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index 09817c2b2..4c5b31781 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -27,7 +27,7 @@ import ( "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" - generic "github.com/sigstore/fulcio/pkg/identity/ci_provider" + "github.com/sigstore/fulcio/pkg/identity/ci_provider" "github.com/sigstore/fulcio/pkg/identity/email" "github.com/sigstore/fulcio/pkg/identity/github" "github.com/sigstore/fulcio/pkg/identity/gitlabcom" @@ -61,7 +61,7 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin var err error switch iss.Type { case config.IssuerTypeCiProvider: - principal, err = generic.WorkflowPrincipalFromIDToken(ctx, tok) + principal, err = ci_provider.WorkflowPrincipalFromIDToken(ctx, tok) case config.IssuerTypeBuildkiteJob: principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) case config.IssuerTypeGitLabPipeline: diff --git a/pkg/server/grpc_server_test.go b/pkg/server/grpc_server_test.go index 6a008e8d1..999083160 100644 --- a/pkg/server/grpc_server_test.go +++ b/pkg/server/grpc_server_test.go @@ -243,8 +243,7 @@ func TestGetConfiguration(t *testing.T) { %q: { "IssuerURL": %q, "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "IsCiProvider": true + "Type": "gitlab-pipeline" }, %q: { "IssuerURL": %q, From 39bdc22ac5b88149f8af67c21baabd529ab0e4b8 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 11 Jun 2024 16:56:35 +0000 Subject: [PATCH 15/47] removing underscore Signed-off-by: Javan lacerda --- pkg/challenges/challenges.go | 4 ++-- pkg/identity/{ci_provider => ciprovider}/issuer.go | 2 +- pkg/identity/{ci_provider => ciprovider}/issuer_test.go | 2 +- pkg/identity/{ci_provider => ciprovider}/principal.go | 2 +- pkg/identity/{ci_provider => ciprovider}/principal_test.go | 2 +- pkg/server/issuer_pool.go | 3 +-- 6 files changed, 7 insertions(+), 8 deletions(-) rename pkg/identity/{ci_provider => ciprovider}/issuer.go (98%) rename pkg/identity/{ci_provider => ciprovider}/issuer_test.go (97%) rename pkg/identity/{ci_provider => ciprovider}/principal.go (98%) rename pkg/identity/{ci_provider => ciprovider}/principal_test.go (97%) diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index 4c5b31781..14f444e67 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -27,7 +27,7 @@ import ( "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" - "github.com/sigstore/fulcio/pkg/identity/ci_provider" + "github.com/sigstore/fulcio/pkg/identity/ciprovider" "github.com/sigstore/fulcio/pkg/identity/email" "github.com/sigstore/fulcio/pkg/identity/github" "github.com/sigstore/fulcio/pkg/identity/gitlabcom" @@ -61,7 +61,7 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin var err error switch iss.Type { case config.IssuerTypeCiProvider: - principal, err = ci_provider.WorkflowPrincipalFromIDToken(ctx, tok) + principal, err = ciprovider.WorkflowPrincipalFromIDToken(ctx, tok) case config.IssuerTypeBuildkiteJob: principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) case config.IssuerTypeGitLabPipeline: diff --git a/pkg/identity/ci_provider/issuer.go b/pkg/identity/ciprovider/issuer.go similarity index 98% rename from pkg/identity/ci_provider/issuer.go rename to pkg/identity/ciprovider/issuer.go index 23fbdaa65..ce82d3558 100644 --- a/pkg/identity/ci_provider/issuer.go +++ b/pkg/identity/ciprovider/issuer.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ci_provider +package ciprovider import ( "context" diff --git a/pkg/identity/ci_provider/issuer_test.go b/pkg/identity/ciprovider/issuer_test.go similarity index 97% rename from pkg/identity/ci_provider/issuer_test.go rename to pkg/identity/ciprovider/issuer_test.go index 59773c3f8..630d8f16e 100644 --- a/pkg/identity/ci_provider/issuer_test.go +++ b/pkg/identity/ciprovider/issuer_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ci_provider +package ciprovider import ( "testing" diff --git a/pkg/identity/ci_provider/principal.go b/pkg/identity/ciprovider/principal.go similarity index 98% rename from pkg/identity/ci_provider/principal.go rename to pkg/identity/ciprovider/principal.go index cdbfd3945..a5b7f0ee7 100644 --- a/pkg/identity/ci_provider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ci_provider +package ciprovider import ( "context" diff --git a/pkg/identity/ci_provider/principal_test.go b/pkg/identity/ciprovider/principal_test.go similarity index 97% rename from pkg/identity/ci_provider/principal_test.go rename to pkg/identity/ciprovider/principal_test.go index eda7811ab..0cded51fb 100644 --- a/pkg/identity/ci_provider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ci_provider +package ciprovider import ( "testing" diff --git a/pkg/server/issuer_pool.go b/pkg/server/issuer_pool.go index c669fa865..6f284c5d3 100644 --- a/pkg/server/issuer_pool.go +++ b/pkg/server/issuer_pool.go @@ -19,7 +19,6 @@ import ( "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" "github.com/sigstore/fulcio/pkg/identity/chainguard" - "github.com/sigstore/fulcio/pkg/identity/ci_provider" "github.com/sigstore/fulcio/pkg/identity/codefresh" "github.com/sigstore/fulcio/pkg/identity/email" "github.com/sigstore/fulcio/pkg/identity/github" @@ -55,7 +54,7 @@ func getIssuer(meta string, i config.OIDCIssuer) identity.Issuer { } switch i.Type { case config.IssuerTypeCiProvider: - return ci_provider.Issuer(issuerURL) + return ciprovider.Issuer(issuerURL) case config.IssuerTypeEmail: return email.Issuer(issuerURL) case config.IssuerTypeGithubWorkflow: From af67958090e12ec81a99b8e6b83ed861bc5f64b3 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 10 Jun 2024 13:46:44 +0000 Subject: [PATCH 16/47] logging Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/principal.go | 100 +++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 12 deletions(-) diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index a5b7f0ee7..edbd18a04 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -15,9 +15,13 @@ package ciprovider import ( + "bytes" "context" "crypto/x509" + "fmt" + "html/template" "net/url" + "strings" "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/fulcio/pkg/certificate" @@ -25,22 +29,94 @@ import ( "github.com/sigstore/fulcio/pkg/identity" ) -type Provider struct { - Subject string - Extensions certificate.Extensions - Uris []string - Defaults map[string]string - OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` +func ApplyTemplate(path string, data map[string]string, defaultData map[string]string) string { + + // Here we merge the data from was claimed by the id token with the + // default data provided by the yaml file. + // The order here matter because we want to override the default data + // with the claimed data. + mergedData := make(map[string]string) + for k, v := range defaultData { + mergedData[k] = v + } + for k, v := range data { + mergedData[k] = v + } + + // It checks it is a path or a raw field by + // checking exists template syntax into the string + // TODO: Check if it is a best way to check that we should apply the template + if strings.Contains(path, "{{") { + var doc bytes.Buffer + t := template.New("") + p, err := t.Parse(path) + if err != nil { + panic(err) + } + err = p.Execute(&doc, mergedData) + if err != nil { + panic(err) + } + return doc.String() + } else { + return mergedData[path] + } } -// TO BE IMPLEMENTED. Just kept as a guide -func WorkflowPrincipalFromIDToken(_ context.Context, _ *oidc.IDToken) (identity.Principal, error) { - return nil, nil +// TO BE IMPLEMENTED. Just keeped as a guide +func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { + iss, ok := config.FromContext(ctx).GetIssuer(token.Issuer) + if !ok { + return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) + } + var claims map[string]string + if err := token.Claims(&claims); err != nil { + return nil, err + } + + configYaml := readYaml() + provider := configYaml.Providers[iss.Type] + e := provider.Extensions + defaults := provider.Defaults + + finalExtensions := certificate.Extensions{ + Issuer: ApplyTemplate(e.Issuer, claims, defaults), + GithubWorkflowTrigger: ApplyTemplate(e.GithubWorkflowTrigger, claims, defaults), + GithubWorkflowSHA: ApplyTemplate(e.GithubWorkflowSHA, claims, defaults), + GithubWorkflowName: ApplyTemplate(e.GithubWorkflowName, claims, defaults), + GithubWorkflowRepository: ApplyTemplate(e.GithubWorkflowRepository, claims, defaults), + GithubWorkflowRef: ApplyTemplate(e.GithubWorkflowRef, claims, defaults), + BuildSignerURI: ApplyTemplate(e.BuildSignerURI, claims, defaults), + BuildConfigDigest: ApplyTemplate(e.BuildConfigDigest, claims, defaults), + RunnerEnvironment: ApplyTemplate(e.RunnerEnvironment, claims, defaults), + SourceRepositoryURI: ApplyTemplate(e.SourceRepositoryURI, claims, defaults), + SourceRepositoryDigest: ApplyTemplate(e.SourceRepositoryDigest, claims, defaults), + SourceRepositoryRef: ApplyTemplate(e.SourceRepositoryRef, claims, defaults), + SourceRepositoryIdentifier: ApplyTemplate(e.SourceRepositoryIdentifier, claims, defaults), + SourceRepositoryOwnerURI: ApplyTemplate(e.SourceRepositoryOwnerURI, claims, defaults), + SourceRepositoryOwnerIdentifier: ApplyTemplate(e.SourceRepositoryOwnerIdentifier, claims, defaults), + BuildConfigURI: ApplyTemplate(e.BuildConfigURI, claims, defaults), + BuildSignerDigest: ApplyTemplate(e.BuildSignerDigest, claims, defaults), + BuildTrigger: ApplyTemplate(e.BuildTrigger, claims, defaults), + RunInvocationURI: ApplyTemplate(e.RunInvocationURI, claims, defaults), + SourceRepositoryVisibilityAtSigning: ApplyTemplate(e.SourceRepositoryVisibilityAtSigning, claims, defaults), + } + finalUris := make([]string, len(provider.Uris)-1) + for _, val := range provider.Uris { + finalUris = append(finalUris, ApplyTemplate(val, claims, defaults)) + } + + return &Provider{ + Subject: token.Subject, + Extensions: finalExtensions, + Uris: finalUris, + OIDCIssuers: provider.OIDCIssuers, + }, nil + } -// TO BE IMPLEMENTED. Just kept as a guide -func (Provider) Name(_ context.Context) string { - return "" +func (p Provider) Name(_ context.Context) string { + return p.Subject } func (p Provider) Embed(_ context.Context, cert *x509.Certificate) error { From 3c66a3a8cbc56b56c7aac35579864d8a7ac4d1dc Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 12 Jun 2024 18:00:04 +0000 Subject: [PATCH 17/47] implementing ci provider principal logic and test for Name function Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/config.yaml | 33 +++++++++ pkg/identity/ciprovider/principal.go | 38 ++++++++-- pkg/identity/ciprovider/principal_test.go | 84 ++++++++++++++++++++++- 3 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 pkg/identity/ciprovider/config.yaml diff --git a/pkg/identity/ciprovider/config.yaml b/pkg/identity/ciprovider/config.yaml new file mode 100644 index 000000000..fa0a8b710 --- /dev/null +++ b/pkg/identity/ciprovider/config.yaml @@ -0,0 +1,33 @@ +# Copyright 2024 The Sigstore Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +providers: + github-workflow: + extensions: + build-signer-digest: job_workflow_sha + source-repository-digest: sha + source-repository-ref: ref + source-repository-identifier: repository_id + run-invocation-uri: "{{.url}}/{{.repository}}/actions/runs/{{.run_id}}/" + uris: + - "{{.url}}/{{.job_workflow_ref}}" + defaults: + url: https://github.com + meta-issuers: + - issuer-url: "https://token.actions.githubusercontent.com/*" + client-id: "sigstore" + oidc-issuers: + - issuer-url: https://token.actions.githubusercontent.com + contact: tac@sigstore.dev + description: "GitHub Actions OIDC auth" \ No newline at end of file diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index edbd18a04..404e37523 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -21,14 +21,43 @@ import ( "fmt" "html/template" "net/url" + "os" "strings" "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" + "gopkg.in/yaml.v3" ) +type RootYaml struct { + Providers map[config.IssuerType]Provider +} +type Provider struct { + Subject string + Extensions certificate.Extensions + Uris []string + Defaults map[string]string + OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` + MetaIssuers []config.OIDCIssuer `yaml:"meta-issuers,omitempty"` +} + +func readYaml() RootYaml { + var obj RootYaml + + yamlFile, err := os.ReadFile("config.yaml") + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, &obj) + if err != nil { + fmt.Printf("Unmarshal: %v", err) + } + + return obj +} + func ApplyTemplate(path string, data map[string]string, defaultData map[string]string) string { // Here we merge the data from was claimed by the id token with the @@ -43,9 +72,6 @@ func ApplyTemplate(path string, data map[string]string, defaultData map[string]s mergedData[k] = v } - // It checks it is a path or a raw field by - // checking exists template syntax into the string - // TODO: Check if it is a best way to check that we should apply the template if strings.Contains(path, "{{") { var doc bytes.Buffer t := template.New("") @@ -58,12 +84,10 @@ func ApplyTemplate(path string, data map[string]string, defaultData map[string]s panic(err) } return doc.String() - } else { - return mergedData[path] } + return mergedData[path] } -// TO BE IMPLEMENTED. Just keeped as a guide func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { iss, ok := config.FromContext(ctx).GetIssuer(token.Issuer) if !ok { @@ -73,7 +97,6 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (ide if err := token.Claims(&claims); err != nil { return nil, err } - configYaml := readYaml() provider := configYaml.Providers[iss.Type] e := provider.Extensions @@ -111,6 +134,7 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (ide Extensions: finalExtensions, Uris: finalUris, OIDCIssuers: provider.OIDCIssuers, + MetaIssuers: provider.MetaIssuers, }, nil } diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 0cded51fb..ed5544cda 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -15,7 +15,14 @@ package ciprovider import ( + "context" + "encoding/json" + "reflect" "testing" + "unsafe" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/sigstore/fulcio/pkg/config" ) // TO BE IMPLEMENTED. Just kept as a guide @@ -23,8 +30,83 @@ func TestWorkflowPrincipalFromIDToken(_ *testing.T) { } -func TestName(_ *testing.T) { +// reflect hack because "claims" field is unexported by oidc IDToken +// https://github.com/coreos/go-oidc/pull/329 +func withClaims(token *oidc.IDToken, data []byte) { + val := reflect.Indirect(reflect.ValueOf(token)) + member := val.FieldByName("claims") + pointer := unsafe.Pointer(member.UnsafeAddr()) + realPointer := (*[]byte)(pointer) + *realPointer = data +} + +func TestName(t *testing.T) { + tests := map[string]struct { + Claims map[string]interface{} + ExpectName string + }{ + `Valid token authenticates with correct claims`: { + Claims: map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": "0", + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }, + ExpectName: "repo:sigstore/fulcio:ref:refs/heads/main", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + token := &oidc.IDToken{ + Issuer: test.Claims["iss"].(string), + Subject: test.Claims["sub"].(string), + } + claims, err := json.Marshal(test.Claims) + if err != nil { + t.Fatal(err) + } + withClaims(token, claims) + ctx := context.TODO() + OIDCIssuers := + map[string]config.OIDCIssuer{ + token.Issuer: { + IssuerURL: token.Issuer, + Type: config.IssuerTypeGithubWorkflow, + ClientID: "sigstore", + }, + } + cfg := &config.FulcioConfig{ + OIDCIssuers: OIDCIssuers, + } + ctx = config.With(ctx, cfg) + principal, err := WorkflowPrincipalFromIDToken(ctx, token) + if err != nil { + t.Fatal(err) + } + gotName := principal.Name(context.TODO()) + if gotName != test.ExpectName { + t.Error("name should match sub claim") + } + }) + } } func TestEmbed(_ *testing.T) { From 2cbe6daeae5ae96846ba24524daf53e0d9f8b145 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Thu, 13 Jun 2024 20:37:31 +0000 Subject: [PATCH 18/47] moving embed logic to embed function and test Signed-off-by: Javan lacerda --- pkg/challenges/challenges.go | 3 - pkg/config/config.go | 1 - pkg/identity/ciprovider/principal.go | 90 +++++++------- pkg/identity/ciprovider/principal_test.go | 142 +++++++++++++++++++++- pkg/server/issuer_pool.go | 2 - 5 files changed, 185 insertions(+), 53 deletions(-) diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index 14f444e67..dda3298ff 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -27,7 +27,6 @@ import ( "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" - "github.com/sigstore/fulcio/pkg/identity/ciprovider" "github.com/sigstore/fulcio/pkg/identity/email" "github.com/sigstore/fulcio/pkg/identity/github" "github.com/sigstore/fulcio/pkg/identity/gitlabcom" @@ -60,8 +59,6 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin var principal identity.Principal var err error switch iss.Type { - case config.IssuerTypeCiProvider: - principal, err = ciprovider.WorkflowPrincipalFromIDToken(ctx, tok) case config.IssuerTypeBuildkiteJob: principal, err = buildkite.JobPrincipalFromIDToken(ctx, tok) case config.IssuerTypeGitLabPipeline: diff --git a/pkg/config/config.go b/pkg/config/config.go index 2d46e64ed..6a6aca77e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -284,7 +284,6 @@ const ( IssuerTypeSpiffe = "spiffe" IssuerTypeURI = "uri" IssuerTypeUsername = "username" - IssuerTypeCiProvider = "ci-provider" ) func parseConfig(b []byte) (cfg *FulcioConfig, err error) { diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 404e37523..96dee02b4 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -31,7 +31,7 @@ import ( "gopkg.in/yaml.v3" ) -type RootYaml struct { +type Config struct { Providers map[config.IssuerType]Provider } type Provider struct { @@ -41,16 +41,17 @@ type Provider struct { Defaults map[string]string OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` MetaIssuers []config.OIDCIssuer `yaml:"meta-issuers,omitempty"` + Claims map[string]interface{} } -func readYaml() RootYaml { - var obj RootYaml +func readConfig() Config { + var obj Config - yamlFile, err := os.ReadFile("config.yaml") + configFile, err := os.ReadFile("config.yaml") if err != nil { fmt.Printf("yamlFile.Get err #%v ", err) } - err = yaml.Unmarshal(yamlFile, &obj) + err = yaml.Unmarshal(configFile, &obj) if err != nil { fmt.Printf("Unmarshal: %v", err) } @@ -58,6 +59,14 @@ func readYaml() RootYaml { return obj } +func claimsToString(claims map[string]interface{}) map[string]string { + stringClaims := make(map[string]string) + for k, v := range claims { + stringClaims[k] = v.(string) + } + return stringClaims +} + func ApplyTemplate(path string, data map[string]string, defaultData map[string]string) string { // Here we merge the data from was claimed by the id token with the @@ -93,16 +102,40 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (ide if !ok { return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) } - var claims map[string]string + var claims map[string]interface{} if err := token.Claims(&claims); err != nil { return nil, err } - configYaml := readYaml() + configYaml := readConfig() provider := configYaml.Providers[iss.Type] - e := provider.Extensions - defaults := provider.Defaults + provider.Claims = claims + provider.Subject = token.Subject + return provider, nil +} + +func (p Provider) Name(_ context.Context) string { + return p.Subject +} + +func (p Provider) Embed(_ context.Context, cert *x509.Certificate) error { - finalExtensions := certificate.Extensions{ + e := p.Extensions + defaults := p.Defaults + claims := claimsToString(p.Claims) + uris := make([]*url.URL, len(p.Uris)) + for _, value := range p.Uris { + url, err := url.Parse(ApplyTemplate(value, claims, defaults)) + if err != nil { + panic(err) + } + uris = append(uris, url) + } + // Set workflow ref URL to SubjectAlternativeName on certificate + cert.URIs = uris + + var err error + // Embed additional information into custom extensions + cert.ExtraExtensions, err = certificate.Extensions{ Issuer: ApplyTemplate(e.Issuer, claims, defaults), GithubWorkflowTrigger: ApplyTemplate(e.GithubWorkflowTrigger, claims, defaults), GithubWorkflowSHA: ApplyTemplate(e.GithubWorkflowSHA, claims, defaults), @@ -123,42 +156,7 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (ide BuildTrigger: ApplyTemplate(e.BuildTrigger, claims, defaults), RunInvocationURI: ApplyTemplate(e.RunInvocationURI, claims, defaults), SourceRepositoryVisibilityAtSigning: ApplyTemplate(e.SourceRepositoryVisibilityAtSigning, claims, defaults), - } - finalUris := make([]string, len(provider.Uris)-1) - for _, val := range provider.Uris { - finalUris = append(finalUris, ApplyTemplate(val, claims, defaults)) - } - - return &Provider{ - Subject: token.Subject, - Extensions: finalExtensions, - Uris: finalUris, - OIDCIssuers: provider.OIDCIssuers, - MetaIssuers: provider.MetaIssuers, - }, nil - -} - -func (p Provider) Name(_ context.Context) string { - return p.Subject -} - -func (p Provider) Embed(_ context.Context, cert *x509.Certificate) error { - - uris := make([]*url.URL, len(p.Uris)) - for _, value := range p.Uris { - url, err := url.Parse(value) - if err != nil { - panic(err) - } - uris = append(uris, url) - } - // Set workflow ref URL to SubjectAlternativeName on certificate - cert.URIs = uris - - var err error - // Embed additional information into custom extensions - cert.ExtraExtensions, err = p.Extensions.Render() + }.Render() if err != nil { return err } diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index ed5544cda..16fac3dff 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -15,14 +15,21 @@ package ciprovider import ( + "bytes" "context" + "crypto/x509" + "encoding/asn1" "encoding/json" + "errors" + "fmt" "reflect" "testing" "unsafe" "github.com/coreos/go-oidc/v3/oidc" + "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" + "github.com/sigstore/fulcio/pkg/identity" ) // TO BE IMPLEMENTED. Just kept as a guide @@ -109,6 +116,139 @@ func TestName(t *testing.T) { } } -func TestEmbed(_ *testing.T) { +func TestEmbed(t *testing.T) { + tests := map[string]struct { + Principal identity.Principal + WantErr bool + WantFacts map[string]func(x509.Certificate) error + }{ + `Github workflow challenge should have all Github workflow extensions and issuer set`: { + Principal: &Provider{ + Extensions: certificate.Extensions{ + Issuer: "issuer", + GithubWorkflowTrigger: "event_name", + GithubWorkflowSHA: "sha", + GithubWorkflowName: "workflow", + GithubWorkflowRepository: "repository", + GithubWorkflowRef: "ref", + BuildSignerURI: "{{ .url }}/{{ .job_workflow_ref }}", + BuildSignerDigest: "job_workflow_sha", + RunnerEnvironment: "runner_environment", + SourceRepositoryURI: "{{ .url }}/{{ .repository }}", + SourceRepositoryDigest: "sha", + SourceRepositoryRef: "ref", + SourceRepositoryIdentifier: "repository_id", + SourceRepositoryOwnerURI: "{{ .url }}/{{ .repository_owner }}", + SourceRepositoryOwnerIdentifier: "repository_owner_id", + BuildConfigURI: "{{ .url }}/{{ .workflow_ref }}", + BuildConfigDigest: "workflow_sha", + BuildTrigger: "event_name", + RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", + SourceRepositoryVisibilityAtSigning: "repository_visibility", + }, + Claims: map[string]interface{}{ + "issuer": "https://token.actions.githubusercontent.com", + "event_name": "trigger", + "sha": "sha", + "workflow": "workflowname", + "repository": "repository", + "ref": "ref", + "job_workflow_sha": "jobWorkflowSha", + "job_workflow_ref": "jobWorkflowRef", + "runner_environment": "runnerEnv", + "repository_id": "repoID", + "repository_owner": "repoOwner", + "repository_owner_id": "repoOwnerID", + "workflow_ref": "workflowRef", + "workflow_sha": "workflowSHA", + "run_id": "runID", + "run_attempt": "runAttempt", + "repository_visibility": "public", + }, + Defaults: map[string]string{ + "url": "https://github.com", + }, + }, + WantErr: false, + WantFacts: map[string]func(x509.Certificate) error{ + `Certifificate should have correct issuer`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, "https://token.actions.githubusercontent.com"), + `Certificate has correct trigger extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, "trigger"), + `Certificate has correct SHA extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}, "sha"), + `Certificate has correct workflow extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}, "workflowname"), + `Certificate has correct repository extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}, "repository"), + `Certificate has correct ref extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, "ref"), + `Certificate has correct issuer (v2) extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 8}, "https://token.actions.githubusercontent.com"), + `Certificate has correct builder signer URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 9}, "https://github.com/jobWorkflowRef"), + `Certificate has correct builder signer digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 10}, "jobWorkflowSha"), + `Certificate has correct runner environment extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 11}, "runnerEnv"), + `Certificate has correct source repo URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 12}, "https://github.com/repository"), + `Certificate has correct source repo digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 13}, "sha"), + `Certificate has correct source repo ref extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 14}, "ref"), + `Certificate has correct source repo ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 15}, "repoID"), + `Certificate has correct source repo owner URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 16}, "https://github.com/repoOwner"), + `Certificate has correct source repo owner ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 17}, "repoOwnerID"), + `Certificate has correct build config URI extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 18}, "https://github.com/workflowRef"), + `Certificate has correct build config digest extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 19}, "workflowSHA"), + `Certificate has correct build trigger extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 20}, "trigger"), + `Certificate has correct run invocation ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}, "https://github.com/repository/actions/runs/runID/attempts/runAttempt"), + `Certificate has correct source repository visibility extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}, "public"), + }, + }, + `Github workflow value with bad URL fails`: { + Principal: &Provider{}, + WantErr: true, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var cert x509.Certificate + err := test.Principal.Embed(context.TODO(), &cert) + if err != nil { + if !test.WantErr { + t.Error(err) + } + return + } else if test.WantErr { + t.Error("expected error") + } + for factName, fact := range test.WantFacts { + t.Run(factName, func(t *testing.T) { + if err := fact(cert); err != nil { + t.Error(err) + } + }) + } + }) + } +} + +func factExtensionIs(oid asn1.ObjectIdentifier, value string) func(x509.Certificate) error { + return func(cert x509.Certificate) error { + for _, ext := range cert.ExtraExtensions { + if ext.Id.Equal(oid) { + var strVal string + _, _ = asn1.Unmarshal(ext.Value, &strVal) + if value != strVal { + return fmt.Errorf("expected oid %v to be %s, but got %s", oid, value, strVal) + } + return nil + } + } + return errors.New("extension not set") + } +} + +func factDeprecatedExtensionIs(oid asn1.ObjectIdentifier, value string) func(x509.Certificate) error { + return func(cert x509.Certificate) error { + for _, ext := range cert.ExtraExtensions { + if ext.Id.Equal(oid) { + if !bytes.Equal(ext.Value, []byte(value)) { + return fmt.Errorf("expected oid %v to be %s, but got %s", oid, value, ext.Value) + } + return nil + } + } + return errors.New("extension not set") + } } diff --git a/pkg/server/issuer_pool.go b/pkg/server/issuer_pool.go index 6f284c5d3..18349f262 100644 --- a/pkg/server/issuer_pool.go +++ b/pkg/server/issuer_pool.go @@ -53,8 +53,6 @@ func getIssuer(meta string, i config.OIDCIssuer) identity.Issuer { issuerURL = meta } switch i.Type { - case config.IssuerTypeCiProvider: - return ciprovider.Issuer(issuerURL) case config.IssuerTypeEmail: return email.Issuer(issuerURL) case config.IssuerTypeGithubWorkflow: From aa257ed15d5b66743ee9981749a16d5278ecea0a Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Fri, 14 Jun 2024 13:32:57 +0000 Subject: [PATCH 19/47] adding test for ci provider WorkflowPrincipalFromIDToken Signed-off-by: Javan lacerda --- pkg/certificate/extensions.go | 38 ++++---- pkg/identity/ciprovider/principal_test.go | 108 +++++++++++++++++++++- 2 files changed, 125 insertions(+), 21 deletions(-) diff --git a/pkg/certificate/extensions.go b/pkg/certificate/extensions.go index 38f80d5ae..e3e96fe41 100644 --- a/pkg/certificate/extensions.go +++ b/pkg/certificate/extensions.go @@ -69,69 +69,69 @@ type Extensions struct { // Deprecated // Triggering event of the Github Workflow. Matches the `event_name` claim of ID // tokens from Github Actions - GithubWorkflowTrigger string // OID 1.3.6.1.4.1.57264.1.2 + GithubWorkflowTrigger string `yaml:"github-workflow-trigger"` // OID 1.3.6.1.4.1.57264.1.2 // Deprecated // SHA of git commit being built in Github Actions. Matches the `sha` claim of ID // tokens from Github Actions - GithubWorkflowSHA string // OID 1.3.6.1.4.1.57264.1.3 + GithubWorkflowSHA string `yaml:"github-workflow-sha"` // OID 1.3.6.1.4.1.57264.1.3 // Deprecated // Name of Github Actions Workflow. Matches the `workflow` claim of the ID // tokens from Github Actions - GithubWorkflowName string // OID 1.3.6.1.4.1.57264.1.4 + GithubWorkflowName string `yaml:"github-workflow-name"` // OID 1.3.6.1.4.1.57264.1.4 // Deprecated // Repository of the Github Actions Workflow. Matches the `repository` claim of the ID // tokens from Github Actions - GithubWorkflowRepository string // OID 1.3.6.1.4.1.57264.1.5 + GithubWorkflowRepository string `yaml:"github-workflow-repository"` // OID 1.3.6.1.4.1.57264.1.5 // Deprecated // Git Ref of the Github Actions Workflow. Matches the `ref` claim of the ID tokens // from Github Actions - GithubWorkflowRef string // 1.3.6.1.4.1.57264.1.6 + GithubWorkflowRef string `yaml:"github-workflow-ref"` // 1.3.6.1.4.1.57264.1.6 // Reference to specific build instructions that are responsible for signing. - BuildSignerURI string // 1.3.6.1.4.1.57264.1.9 + BuildSignerURI string `yaml:"build-signer-uri"` // 1.3.6.1.4.1.57264.1.9 // Immutable reference to the specific version of the build instructions that is responsible for signing. - BuildSignerDigest string // 1.3.6.1.4.1.57264.1.10 + BuildSignerDigest string `yaml:"build-signer-digest"` // 1.3.6.1.4.1.57264.1.10 // Specifies whether the build took place in platform-hosted cloud infrastructure or customer/self-hosted infrastructure. - RunnerEnvironment string // 1.3.6.1.4.1.57264.1.11 + RunnerEnvironment string `yaml:"runner-environment"` // 1.3.6.1.4.1.57264.1.11 // Source repository URL that the build was based on. - SourceRepositoryURI string // 1.3.6.1.4.1.57264.1.12 + SourceRepositoryURI string `yaml:"source-repository-uri"` // 1.3.6.1.4.1.57264.1.12 // Immutable reference to a specific version of the source code that the build was based upon. - SourceRepositoryDigest string // 1.3.6.1.4.1.57264.1.13 + SourceRepositoryDigest string `yaml:"source-repository-digest"` // 1.3.6.1.4.1.57264.1.13 // Source Repository Ref that the build run was based upon. - SourceRepositoryRef string // 1.3.6.1.4.1.57264.1.14 + SourceRepositoryRef string `yaml:"source-repository-ref"` // 1.3.6.1.4.1.57264.1.14 // Immutable identifier for the source repository the workflow was based upon. - SourceRepositoryIdentifier string // 1.3.6.1.4.1.57264.1.15 + SourceRepositoryIdentifier string `yaml:"source-repository-identifier"` // 1.3.6.1.4.1.57264.1.15 // Source repository owner URL of the owner of the source repository that the build was based on. - SourceRepositoryOwnerURI string // 1.3.6.1.4.1.57264.1.16 + SourceRepositoryOwnerURI string `yaml:"source-repository-owner-uri"` // 1.3.6.1.4.1.57264.1.16 // Immutable identifier for the owner of the source repository that the workflow was based upon. - SourceRepositoryOwnerIdentifier string // 1.3.6.1.4.1.57264.1.17 + SourceRepositoryOwnerIdentifier string `yaml:"source-repository-owner-identifier"` // 1.3.6.1.4.1.57264.1.17 // Build Config URL to the top-level/initiating build instructions. - BuildConfigURI string // 1.3.6.1.4.1.57264.1.18 + BuildConfigURI string `yaml:"build-config-uri"` // 1.3.6.1.4.1.57264.1.18 // Immutable reference to the specific version of the top-level/initiating build instructions. - BuildConfigDigest string // 1.3.6.1.4.1.57264.1.19 + BuildConfigDigest string `yaml:"build-config-digest"` // 1.3.6.1.4.1.57264.1.19 // Event or action that initiated the build. - BuildTrigger string // 1.3.6.1.4.1.57264.1.20 + BuildTrigger string `yaml:"build-trigger"` // 1.3.6.1.4.1.57264.1.20 // Run Invocation URL to uniquely identify the build execution. - RunInvocationURI string // 1.3.6.1.4.1.57264.1.21 + RunInvocationURI string `yaml:"run-invocation-uri"` // 1.3.6.1.4.1.57264.1.21 // Source repository visibility at the time of signing the certificate. - SourceRepositoryVisibilityAtSigning string // 1.3.6.1.4.1.57264.1.22 + SourceRepositoryVisibilityAtSigning string `yaml:"source-repository-visibility-at-signing"` // 1.3.6.1.4.1.57264.1.22 } func (e Extensions) Render() ([]pkix.Extension, error) { diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 16fac3dff..4edea7851 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -32,9 +32,113 @@ import ( "github.com/sigstore/fulcio/pkg/identity" ) -// TO BE IMPLEMENTED. Just kept as a guide -func TestWorkflowPrincipalFromIDToken(_ *testing.T) { +func TestWorkflowPrincipalFromIDToken(t *testing.T) { + tests := map[string]struct { + Claims map[string]interface{} + ExpectedPrincipal Provider + }{ + `Valid token authenticates with correct claims`: { + Claims: map[string]interface{}{ + "issuer": "https://token.actions.githubusercontent.com", + "event_name": "trigger", + "sha": "sha", + "workflow": "workflowname", + "repository": "repository", + "ref": "ref", + "job_workflow_sha": "jobWorkflowSha", + "job_workflow_ref": "jobWorkflowRef", + "runner_environment": "runnerEnv", + "repository_id": "repoID", + "repository_owner": "repoOwner", + "repository_owner_id": "repoOwnerID", + "workflow_ref": "workflowRef", + "workflow_sha": "workflowSHA", + "run_id": "runID", + "run_attempt": "runAttempt", + "repository_visibility": "public", + }, + ExpectedPrincipal: Provider{ + Subject: "subject-test", + Extensions: certificate.Extensions{ + BuildSignerDigest: "job_workflow_sha", + SourceRepositoryDigest: "sha", + SourceRepositoryRef: "ref", + SourceRepositoryIdentifier: "repository_id", + RunInvocationURI: "{{.url}}/{{.repository}}/actions/runs/{{.run_id}}/", + }, + Uris: []string{ + "{{.url}}/{{.job_workflow_ref}}", + }, + Defaults: map[string]string{ + "url": "https://github.com", + }, + OIDCIssuers: []config.OIDCIssuer{ + { + IssuerURL: "https://token.actions.githubusercontent.com", + }, + }, + MetaIssuers: []config.OIDCIssuer{ + { + IssuerURL: "https://token.actions.githubusercontent.com/*", + ClientID: "sigstore", + }, + }, + Claims: map[string]interface{}{ + "event_name": "trigger", + "issuer": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "jobWorkflowRef", + "job_workflow_sha": "jobWorkflowSha", + "ref": "ref", + "repository": "repository", + "repository_id": "repoID", + "repository_owner": "repoOwner", + "repository_owner_id": "repoOwnerID", + "repository_visibility": "public", + "run_attempt": "runAttempt", + "run_id": "runID", + "runner_environment": "runnerEnv", + "sha": "sha", + "workflow": "workflowname", + "workflow_ref": "workflowRef", + "workflow_sha": "workflowSHA", + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + token := &oidc.IDToken{ + Subject: "subject-test", + } + claims, err := json.Marshal(test.Claims) + if err != nil { + t.Fatal(err) + } + withClaims(token, claims) + ctx := context.TODO() + OIDCIssuers := + map[string]config.OIDCIssuer{ + token.Issuer: { + IssuerURL: token.Issuer, + Type: config.IssuerTypeGithubWorkflow, + ClientID: "sigstore", + }, + } + cfg := &config.FulcioConfig{ + OIDCIssuers: OIDCIssuers, + } + ctx = config.With(ctx, cfg) + principal, err := WorkflowPrincipalFromIDToken(ctx, token) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(principal, test.ExpectedPrincipal) { + t.Error("Principals should be equals") + } + }) + } } // reflect hack because "claims" field is unexported by oidc IDToken From 4a86f57b2e63093b35024402fce551b9ccd025a7 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Fri, 14 Jun 2024 14:10:03 +0000 Subject: [PATCH 20/47] adding test issuer for ci provider Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/issuer_test.go | 79 +++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/pkg/identity/ciprovider/issuer_test.go b/pkg/identity/ciprovider/issuer_test.go index 630d8f16e..dab56caab 100644 --- a/pkg/identity/ciprovider/issuer_test.go +++ b/pkg/identity/ciprovider/issuer_test.go @@ -15,10 +15,85 @@ package ciprovider import ( + "context" + "encoding/json" "testing" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/sigstore/fulcio/pkg/config" + "github.com/sigstore/fulcio/pkg/identity" ) -// TO BE IMPLEMENTED. Just kept as a guide -func TestIssuer(_ *testing.T) { +func TestIssuer(t *testing.T) { + ctx := context.Background() + url := "test-issuer-url" + issuer := Issuer(url) + + // test the Match function + t.Run("match", func(t *testing.T) { + if matches := issuer.Match(ctx, url); !matches { + t.Fatal("expected url to match but it doesn't") + } + if matches := issuer.Match(ctx, "some-other-url"); matches { + t.Fatal("expected match to fail but it didn't") + } + }) + + t.Run("authenticate", func(t *testing.T) { + token := &oidc.IDToken{ + Issuer: "https://iss.example.com", + Subject: "repo:sigstore/fulcio:ref:refs/heads/main", + } + claims, err := json.Marshal(map[string]interface{}{ + "aud": "sigstore", + "event_name": "push", + "exp": 0, + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + }) + if err != nil { + t.Fatal(err) + } + + withClaims(token, claims) + ctx := context.TODO() + OIDCIssuers := + map[string]config.OIDCIssuer{ + token.Issuer: { + IssuerURL: token.Issuer, + Type: config.IssuerTypeGithubWorkflow, + ClientID: "sigstore", + }, + } + cfg := &config.FulcioConfig{ + OIDCIssuers: OIDCIssuers, + } + ctx = config.With(ctx, cfg) + identity.Authorize = func(_ context.Context, _ string, _ ...config.InsecureOIDCConfigOption) (*oidc.IDToken, error) { + return token, nil + } + principal, err := issuer.Authenticate(ctx, "token") + if err != nil { + t.Fatal(err) + } + if principal.Name(ctx) != "repo:sigstore/fulcio:ref:refs/heads/main" { + t.Fatalf("got unexpected name %s", principal.Name(ctx)) + } + }) } From 342f8ad75499fbcc62a043b9e547c10778e08e26 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Fri, 14 Jun 2024 14:19:47 +0000 Subject: [PATCH 21/47] change providers config file name Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/principal.go | 2 +- pkg/identity/ciprovider/{config.yaml => providers_config.yaml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pkg/identity/ciprovider/{config.yaml => providers_config.yaml} (100%) diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 96dee02b4..f9548933c 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -47,7 +47,7 @@ type Provider struct { func readConfig() Config { var obj Config - configFile, err := os.ReadFile("config.yaml") + configFile, err := os.ReadFile("providers_config.yaml") if err != nil { fmt.Printf("yamlFile.Get err #%v ", err) } diff --git a/pkg/identity/ciprovider/config.yaml b/pkg/identity/ciprovider/providers_config.yaml similarity index 100% rename from pkg/identity/ciprovider/config.yaml rename to pkg/identity/ciprovider/providers_config.yaml From 2b1ea3fec52dbde0d42821c0da30c932db2f0fe9 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 19 Jun 2024 14:33:00 +0000 Subject: [PATCH 22/47] refactoring Signed-off-by: Javan lacerda --- cmd/app/serve.go | 4 + config/fulcio-config.yaml | 50 ++-- pkg/challenges/challenges.go | 3 + pkg/config/config.go | 70 +++++- pkg/config/config_test.go | 3 + .../providers_config.yaml | 3 +- pkg/identity/ciprovider/issuer_test.go | 3 +- pkg/identity/ciprovider/principal.go | 124 ++++------ pkg/identity/ciprovider/principal_test.go | 227 +++++++++--------- pkg/server/issuer_pool.go | 3 + 10 files changed, 278 insertions(+), 212 deletions(-) rename pkg/{identity/ciprovider => config}/providers_config.yaml (95%) diff --git a/cmd/app/serve.go b/cmd/app/serve.go index de0b28194..a20918efb 100644 --- a/cmd/app/serve.go +++ b/cmd/app/serve.go @@ -214,6 +214,10 @@ func runServeCmd(cmd *cobra.Command, args []string) { //nolint: revive if err != nil { log.Logger.Fatalf("error loading --config-path=%s: %v", cp, err) } + cfg, err = config.LoadCiProvidersConfig(cfg) + if err != nil { + log.Logger.Fatalf("error loading CiProviderConfig: %v", err) + } var baseca certauth.CertificateAuthority switch viper.GetString("ca") { diff --git a/config/fulcio-config.yaml b/config/fulcio-config.yaml index 0f7a0aded..4dd5d23bd 100644 --- a/config/fulcio-config.yaml +++ b/config/fulcio-config.yaml @@ -20,43 +20,51 @@ data: "https://accounts.google.com": { "IssuerURL": "https://accounts.google.com", "ClientID": "sigstore", - "Type": "email" + "Type": "email", + "SubType": "" }, "https://agent.buildkite.com": { "IssuerURL": "https://agent.buildkite.com", "ClientID": "sigstore", - "Type": "buildkite-job" + "Type": "buildkite-job", + "SubType": "" }, "https://allow.pub": { "IssuerURL": "https://allow.pub", "ClientID": "sigstore", "Type": "spiffe", + "SubType": "", "SPIFFETrustDomain": "allow.pub" }, "https://auth-staging.eclipse.org/realms/sigstore": { "IssuerURL": "https://auth-staging.eclipse.org/realms/sigstore", "ClientID": "sigstore", - "Type": "email" + "Type": "email", + "SubType": "" }, "https://auth.eclipse.org/auth/realms/sigstore": { "IssuerURL": "https://auth.eclipse.org/auth/realms/sigstore", "ClientID": "sigstore", - "Type": "email" + "Type": "email", + "SubType": "" }, "https://dev.gitlab.org": { "IssuerURL": "https://dev.gitlab.org", "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "SubType": "" }, "https://gitlab.archlinux.org": { "IssuerURL": "https://gitlab.archlinux.org", "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "SubType": "" }, "https://gitlab.com": { "IssuerURL": "https://gitlab.com", "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "SubType": "" }, "https://issuer.enforce.dev": { "IssuerURL": "https://issuer.enforce.dev", @@ -67,46 +75,56 @@ data: "IssuerURL": "https://oauth2.sigstore.dev/auth", "ClientID": "sigstore", "Type": "email", + "SubType": "", "IssuerClaim": "$.federated_claims.connector_id" }, "https://oidc.codefresh.io": { "IssuerURL": "https://oidc.codefresh.io", "ClientID": "sigstore", - "Type": "codefresh-workflow" + "Type": "codefresh-workflow", + "SubType": "" }, "https://ops.gitlab.net": { "IssuerURL": "https://ops.gitlab.net", "ClientID": "sigstore", - "Type": "gitlab-pipeline" + "Type": "gitlab-pipeline", + "SubType": "" }, "https://token.actions.githubusercontent.com": { "IssuerURL": "https://token.actions.githubusercontent.com", "ClientID": "sigstore", - "Type": "github-workflow" + "Type": "github-workflow", + "SubType": "" } }, "MetaIssuers": { "https://*.oic.prod-aks.azure.com/*": { "ClientID": "sigstore", - "Type": "kubernetes" + "Type": "kubernetes", + "SubType": "" }, "https://container.googleapis.com/v1/projects/*/locations/*/clusters/*": { "ClientID": "sigstore", - "Type": "kubernetes" + "Type": "kubernetes", + "SubType": "" }, "https://oidc.eks.*.amazonaws.com/id/*": { "ClientID": "sigstore", - "Type": "kubernetes" + "Type": "kubernetes", + "SubType": "" }, "https://oidc.prod-aks.azure.com/*": { "ClientID": "sigstore", - "Type": "kubernetes" + "Type": "kubernetes", + "SubType": "" }, "https://token.actions.githubusercontent.com/*": { "ClientID": "sigstore", - "Type": "github-workflow" + "Type": "github-workflow", + "SubType": "" } - } + }, + "IssuersMetadata": null } server.yaml: |- host: 0.0.0.0 diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index dda3298ff..b80cdc76b 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -27,6 +27,7 @@ import ( "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" + "github.com/sigstore/fulcio/pkg/identity/ciprovider" "github.com/sigstore/fulcio/pkg/identity/email" "github.com/sigstore/fulcio/pkg/identity/github" "github.com/sigstore/fulcio/pkg/identity/gitlabcom" @@ -75,6 +76,8 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin principal, err = uri.PrincipalFromIDToken(ctx, tok) case config.IssuerTypeUsername: principal, err = username.PrincipalFromIDToken(ctx, tok) + case config.IssuerTypeCiProvider: + principal, err = ciprovider.WorkflowPrincipalFromIDToken(ctx, tok) default: return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 6a6aca77e..41cdc421b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -24,13 +24,16 @@ import ( "net/http" "net/url" "os" + "path/filepath" "reflect" "regexp" + "runtime" "strings" "time" "github.com/coreos/go-oidc/v3/oidc" lru "github.com/hashicorp/golang-lru" + "github.com/sigstore/fulcio/pkg/certificate" fulciogrpc "github.com/sigstore/fulcio/pkg/generated/protobuf" "github.com/sigstore/fulcio/pkg/log" "github.com/spiffe/go-spiffe/v2/spiffeid" @@ -60,12 +63,21 @@ type FulcioConfig struct { // * https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us-west1-b/clusters/tenant-cluster MetaIssuers map[string]OIDCIssuer `json:"MetaIssuers,omitempty" yaml:"meta-issuers,omitempty"` + // defines the metadata for the issuers + IssuersMetadata map[string]IssuersMetadata + // verifiers is a fixed mapping from our OIDCIssuers to their OIDC verifiers. verifiers map[string][]*verifierWithConfig // lru is an LRU cache of recently used verifiers for our meta issuers. lru *lru.TwoQueueCache } +type IssuersMetadata struct { + Defaults map[string]string + ClaimsMapper certificate.Extensions + SubjectAlternativeName string +} + type OIDCIssuer struct { // The expected issuer of an OIDC token IssuerURL string `json:"IssuerURL,omitempty" yaml:"issuer-url,omitempty"` @@ -74,6 +86,8 @@ type OIDCIssuer struct { // Used to determine the subject of the certificate and if additional // certificate values are needed Type IssuerType `json:"Type" yaml:"type,omitempty"` + // Issuers subtype + SubType string `json:"SubType" yaml:"sub-type,omitempty"` // Optional, if the issuer is in a different claim in the OIDC token IssuerClaim string `json:"IssuerClaim,omitempty" yaml:"issuer-claim,omitempty"` // The domain that must be present in the subject for 'uri' issuer types @@ -284,6 +298,7 @@ const ( IssuerTypeSpiffe = "spiffe" IssuerTypeURI = "uri" IssuerTypeUsername = "username" + IssuerTypeCiProvider = "ci-provider" ) func parseConfig(b []byte) (cfg *FulcioConfig, err error) { @@ -432,6 +447,52 @@ func FromContext(ctx context.Context) *FulcioConfig { return untyped.(*FulcioConfig) } +type CiProvidersConfig struct { + Providers map[string]Provider +} +type Provider struct { + Extensions certificate.Extensions `yaml:"extensions,omitempty"` + SubjectAlternativeName string `yaml:"subject-alternative-name,omitempty"` + Defaults map[string]string `yaml:"defaults,omitempty"` + OIDCIssuers []OIDCIssuer `yaml:"oidc-issuers,omitempty"` + MetaIssuers []OIDCIssuer `yaml:"meta-issuers,omitempty"` +} + +func LoadCiProvidersConfig(cfg *FulcioConfig) (*FulcioConfig, error) { + var ciProvidersConfig CiProvidersConfig + _, path, _, _ := runtime.Caller(0) + basepath := filepath.Dir(path) + providersConfigFile, err := os.ReadFile(basepath + "/providers_config.yaml") + + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(providersConfigFile, &ciProvidersConfig) + if err != nil { + fmt.Printf("Unmarshal: %v", err) + } + + cfg.IssuersMetadata = make(map[string]IssuersMetadata) + for k, v := range ciProvidersConfig.Providers { + cfg.IssuersMetadata[k] = IssuersMetadata{ + v.Defaults, + v.Extensions, + v.SubjectAlternativeName, + } + for _, issuer := range v.OIDCIssuers { + issuer.SubType = k + issuer.Type = IssuerTypeCiProvider + cfg.OIDCIssuers[issuer.IssuerURL] = issuer + } + for _, issuer := range v.MetaIssuers { + issuer.SubType = k + issuer.Type = IssuerTypeCiProvider + cfg.MetaIssuers[issuer.IssuerURL] = issuer + } + } + return cfg, err +} + // Load a config from disk, or use defaults func Load(configPath string) (*FulcioConfig, error) { if _, err := os.Stat(configPath); os.IsNotExist(err) { @@ -446,7 +507,12 @@ func Load(configPath string) (*FulcioConfig, error) { if err != nil { return nil, fmt.Errorf("read file: %w", err) } - return Read(b) + fulcioConfig, err := Read(b) + if err != nil { + return fulcioConfig, err + } + + return fulcioConfig, err } // Read parses the bytes of a config @@ -516,6 +582,8 @@ func issuerToChallengeClaim(issType IssuerType, challengeClaim string) string { return "email" case IssuerTypeGithubWorkflow: return "sub" + case IssuerTypeCiProvider: + return "sub" case IssuerTypeCodefreshWorkflow: return "sub" case IssuerTypeChainguard: diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 4c0967660..c5473a098 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -492,6 +492,9 @@ func Test_issuerToChallengeClaim(t *testing.T) { if claim := issuerToChallengeClaim(IssuerTypeGithubWorkflow, ""); claim != "sub" { t.Fatalf("expected sub subject claim for GitHub issuer, got %s", claim) } + if claim := issuerToChallengeClaim(IssuerTypeCiProvider, ""); claim != "sub" { + t.Fatalf("expected sub subject claim for GitHub issuer, got %s", claim) + } if claim := issuerToChallengeClaim(IssuerTypeGitLabPipeline, ""); claim != "sub" { t.Fatalf("expected sub subject claim for GitLab issuer, got %s", claim) } diff --git a/pkg/identity/ciprovider/providers_config.yaml b/pkg/config/providers_config.yaml similarity index 95% rename from pkg/identity/ciprovider/providers_config.yaml rename to pkg/config/providers_config.yaml index fa0a8b710..ef3c6e85f 100644 --- a/pkg/identity/ciprovider/providers_config.yaml +++ b/pkg/config/providers_config.yaml @@ -20,8 +20,7 @@ providers: source-repository-ref: ref source-repository-identifier: repository_id run-invocation-uri: "{{.url}}/{{.repository}}/actions/runs/{{.run_id}}/" - uris: - - "{{.url}}/{{.job_workflow_ref}}" + subject-alternative-name: "{{.url}}/{{.job_workflow_ref}}" defaults: url: https://github.com meta-issuers: diff --git a/pkg/identity/ciprovider/issuer_test.go b/pkg/identity/ciprovider/issuer_test.go index dab56caab..31fe91e3a 100644 --- a/pkg/identity/ciprovider/issuer_test.go +++ b/pkg/identity/ciprovider/issuer_test.go @@ -76,7 +76,8 @@ func TestIssuer(t *testing.T) { map[string]config.OIDCIssuer{ token.Issuer: { IssuerURL: token.Issuer, - Type: config.IssuerTypeGithubWorkflow, + Type: config.IssuerTypeCiProvider, + SubType: "github-workflow", ClientID: "sigstore", }, } diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index f9548933c..1b7ae7561 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -21,44 +21,14 @@ import ( "fmt" "html/template" "net/url" - "os" "strings" "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" - "gopkg.in/yaml.v3" ) -type Config struct { - Providers map[config.IssuerType]Provider -} -type Provider struct { - Subject string - Extensions certificate.Extensions - Uris []string - Defaults map[string]string - OIDCIssuers []config.OIDCIssuer `yaml:"oidc-issuers,omitempty"` - MetaIssuers []config.OIDCIssuer `yaml:"meta-issuers,omitempty"` - Claims map[string]interface{} -} - -func readConfig() Config { - var obj Config - - configFile, err := os.ReadFile("providers_config.yaml") - if err != nil { - fmt.Printf("yamlFile.Get err #%v ", err) - } - err = yaml.Unmarshal(configFile, &obj) - if err != nil { - fmt.Printf("Unmarshal: %v", err) - } - - return obj -} - func claimsToString(claims map[string]interface{}) map[string]string { stringClaims := make(map[string]string) for k, v := range claims { @@ -67,7 +37,7 @@ func claimsToString(claims map[string]interface{}) map[string]string { return stringClaims } -func ApplyTemplate(path string, data map[string]string, defaultData map[string]string) string { +func applyTemplate(path string, data map[string]string, defaultData map[string]string) string { // Here we merge the data from was claimed by the id token with the // default data provided by the yaml file. @@ -97,65 +67,69 @@ func ApplyTemplate(path string, data map[string]string, defaultData map[string]s return mergedData[path] } +type Config struct { + Token *oidc.IDToken + Metadata config.IssuersMetadata +} + func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { - iss, ok := config.FromContext(ctx).GetIssuer(token.Issuer) + cfg := config.FromContext(ctx) + issuer, ok := cfg.GetIssuer(token.Issuer) if !ok { return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) } - var claims map[string]interface{} - if err := token.Claims(&claims); err != nil { - return nil, err - } - configYaml := readConfig() - provider := configYaml.Providers[iss.Type] - provider.Claims = claims - provider.Subject = token.Subject - return provider, nil + + return Config{ + token, + cfg.IssuersMetadata[issuer.SubType], + }, nil } -func (p Provider) Name(_ context.Context) string { - return p.Subject +func (p Config) Name(_ context.Context) string { + return p.Token.Subject } -func (p Provider) Embed(_ context.Context, cert *x509.Certificate) error { +func (p Config) Embed(_ context.Context, cert *x509.Certificate) error { - e := p.Extensions - defaults := p.Defaults - claims := claimsToString(p.Claims) - uris := make([]*url.URL, len(p.Uris)) - for _, value := range p.Uris { - url, err := url.Parse(ApplyTemplate(value, claims, defaults)) - if err != nil { - panic(err) - } - uris = append(uris, url) + e := p.Metadata.ClaimsMapper + defaults := p.Metadata.Defaults + + var rawClaims map[string]interface{} + if err := p.Token.Claims(&rawClaims); err != nil { + return err + } + claims := claimsToString(rawClaims) + + subjectAlternativeNameURL, err := url.Parse(applyTemplate(p.Metadata.SubjectAlternativeName, claims, defaults)) + if err != nil { + panic(err) } + uris := []*url.URL{subjectAlternativeNameURL} // Set workflow ref URL to SubjectAlternativeName on certificate cert.URIs = uris - var err error // Embed additional information into custom extensions cert.ExtraExtensions, err = certificate.Extensions{ - Issuer: ApplyTemplate(e.Issuer, claims, defaults), - GithubWorkflowTrigger: ApplyTemplate(e.GithubWorkflowTrigger, claims, defaults), - GithubWorkflowSHA: ApplyTemplate(e.GithubWorkflowSHA, claims, defaults), - GithubWorkflowName: ApplyTemplate(e.GithubWorkflowName, claims, defaults), - GithubWorkflowRepository: ApplyTemplate(e.GithubWorkflowRepository, claims, defaults), - GithubWorkflowRef: ApplyTemplate(e.GithubWorkflowRef, claims, defaults), - BuildSignerURI: ApplyTemplate(e.BuildSignerURI, claims, defaults), - BuildConfigDigest: ApplyTemplate(e.BuildConfigDigest, claims, defaults), - RunnerEnvironment: ApplyTemplate(e.RunnerEnvironment, claims, defaults), - SourceRepositoryURI: ApplyTemplate(e.SourceRepositoryURI, claims, defaults), - SourceRepositoryDigest: ApplyTemplate(e.SourceRepositoryDigest, claims, defaults), - SourceRepositoryRef: ApplyTemplate(e.SourceRepositoryRef, claims, defaults), - SourceRepositoryIdentifier: ApplyTemplate(e.SourceRepositoryIdentifier, claims, defaults), - SourceRepositoryOwnerURI: ApplyTemplate(e.SourceRepositoryOwnerURI, claims, defaults), - SourceRepositoryOwnerIdentifier: ApplyTemplate(e.SourceRepositoryOwnerIdentifier, claims, defaults), - BuildConfigURI: ApplyTemplate(e.BuildConfigURI, claims, defaults), - BuildSignerDigest: ApplyTemplate(e.BuildSignerDigest, claims, defaults), - BuildTrigger: ApplyTemplate(e.BuildTrigger, claims, defaults), - RunInvocationURI: ApplyTemplate(e.RunInvocationURI, claims, defaults), - SourceRepositoryVisibilityAtSigning: ApplyTemplate(e.SourceRepositoryVisibilityAtSigning, claims, defaults), + Issuer: applyTemplate(e.Issuer, claims, defaults), + GithubWorkflowTrigger: applyTemplate(e.GithubWorkflowTrigger, claims, defaults), + GithubWorkflowSHA: applyTemplate(e.GithubWorkflowSHA, claims, defaults), + GithubWorkflowName: applyTemplate(e.GithubWorkflowName, claims, defaults), + GithubWorkflowRepository: applyTemplate(e.GithubWorkflowRepository, claims, defaults), + GithubWorkflowRef: applyTemplate(e.GithubWorkflowRef, claims, defaults), + BuildSignerURI: applyTemplate(e.BuildSignerURI, claims, defaults), + BuildConfigDigest: applyTemplate(e.BuildConfigDigest, claims, defaults), + RunnerEnvironment: applyTemplate(e.RunnerEnvironment, claims, defaults), + SourceRepositoryURI: applyTemplate(e.SourceRepositoryURI, claims, defaults), + SourceRepositoryDigest: applyTemplate(e.SourceRepositoryDigest, claims, defaults), + SourceRepositoryRef: applyTemplate(e.SourceRepositoryRef, claims, defaults), + SourceRepositoryIdentifier: applyTemplate(e.SourceRepositoryIdentifier, claims, defaults), + SourceRepositoryOwnerURI: applyTemplate(e.SourceRepositoryOwnerURI, claims, defaults), + SourceRepositoryOwnerIdentifier: applyTemplate(e.SourceRepositoryOwnerIdentifier, claims, defaults), + BuildConfigURI: applyTemplate(e.BuildConfigURI, claims, defaults), + BuildSignerDigest: applyTemplate(e.BuildSignerDigest, claims, defaults), + BuildTrigger: applyTemplate(e.BuildTrigger, claims, defaults), + RunInvocationURI: applyTemplate(e.RunInvocationURI, claims, defaults), + SourceRepositoryVisibilityAtSigning: applyTemplate(e.SourceRepositoryVisibilityAtSigning, claims, defaults), }.Render() if err != nil { return err diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 4edea7851..81d719a63 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -29,16 +29,49 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" - "github.com/sigstore/fulcio/pkg/identity" ) func TestWorkflowPrincipalFromIDToken(t *testing.T) { tests := map[string]struct { - Claims map[string]interface{} - ExpectedPrincipal Provider + ExpectedPrincipal Config }{ - `Valid token authenticates with correct claims`: { - Claims: map[string]interface{}{ + `Github workflow challenge should have all Github workflow extensions and issuer set`: { + ExpectedPrincipal: Config{ + Metadata: config.IssuersMetadata{ + ClaimsMapper: certificate.Extensions{ + Issuer: "issuer", + GithubWorkflowTrigger: "event_name", + GithubWorkflowSHA: "sha", + GithubWorkflowName: "workflow", + GithubWorkflowRepository: "repository", + GithubWorkflowRef: "ref", + BuildSignerURI: "{{ .url }}/{{ .job_workflow_ref }}", + BuildSignerDigest: "job_workflow_sha", + RunnerEnvironment: "runner_environment", + SourceRepositoryURI: "{{ .url }}/{{ .repository }}", + SourceRepositoryDigest: "sha", + SourceRepositoryRef: "ref", + SourceRepositoryIdentifier: "repository_id", + SourceRepositoryOwnerURI: "{{ .url }}/{{ .repository_owner }}", + SourceRepositoryOwnerIdentifier: "repository_owner_id", + BuildConfigURI: "{{ .url }}/{{ .workflow_ref }}", + BuildConfigDigest: "workflow_sha", + BuildTrigger: "event_name", + RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", + SourceRepositoryVisibilityAtSigning: "repository_visibility", + }, + Defaults: map[string]string{ + "url": "https://github.com", + }, + SubjectAlternativeName: "{{.url}}/{{.job_workflow_ref}}", + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + claims, err := json.Marshal(map[string]interface{}{ "issuer": "https://token.actions.githubusercontent.com", "event_name": "trigger", "sha": "sha", @@ -56,89 +89,41 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { "run_id": "runID", "run_attempt": "runAttempt", "repository_visibility": "public", - }, - ExpectedPrincipal: Provider{ - Subject: "subject-test", - Extensions: certificate.Extensions{ - BuildSignerDigest: "job_workflow_sha", - SourceRepositoryDigest: "sha", - SourceRepositoryRef: "ref", - SourceRepositoryIdentifier: "repository_id", - RunInvocationURI: "{{.url}}/{{.repository}}/actions/runs/{{.run_id}}/", - }, - Uris: []string{ - "{{.url}}/{{.job_workflow_ref}}", - }, - Defaults: map[string]string{ - "url": "https://github.com", - }, - OIDCIssuers: []config.OIDCIssuer{ - { - IssuerURL: "https://token.actions.githubusercontent.com", - }, - }, - MetaIssuers: []config.OIDCIssuer{ - { - IssuerURL: "https://token.actions.githubusercontent.com/*", - ClientID: "sigstore", - }, - }, - Claims: map[string]interface{}{ - "event_name": "trigger", - "issuer": "https://token.actions.githubusercontent.com", - "job_workflow_ref": "jobWorkflowRef", - "job_workflow_sha": "jobWorkflowSha", - "ref": "ref", - "repository": "repository", - "repository_id": "repoID", - "repository_owner": "repoOwner", - "repository_owner_id": "repoOwnerID", - "repository_visibility": "public", - "run_attempt": "runAttempt", - "run_id": "runID", - "runner_environment": "runnerEnv", - "sha": "sha", - "workflow": "workflowname", - "workflow_ref": "workflowRef", - "workflow_sha": "workflowSHA", - }, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - token := &oidc.IDToken{ - Subject: "subject-test", - } - claims, err := json.Marshal(test.Claims) + }) if err != nil { t.Fatal(err) } + token := &oidc.IDToken{} withClaims(token, claims) + + test.ExpectedPrincipal.Token = token ctx := context.TODO() OIDCIssuers := map[string]config.OIDCIssuer{ token.Issuer: { IssuerURL: token.Issuer, - Type: config.IssuerTypeGithubWorkflow, + Type: config.IssuerTypeCiProvider, + SubType: "github-workflow", ClientID: "sigstore", }, } + meta := make(map[string]config.IssuersMetadata) + meta["github-workflow"] = test.ExpectedPrincipal.Metadata cfg := &config.FulcioConfig{ - OIDCIssuers: OIDCIssuers, + OIDCIssuers: OIDCIssuers, + IssuersMetadata: meta, } ctx = config.With(ctx, cfg) principal, err := WorkflowPrincipalFromIDToken(ctx, token) if err != nil { t.Fatal(err) } - if !reflect.DeepEqual(principal, test.ExpectedPrincipal) { t.Error("Principals should be equals") } }) } + } // reflect hack because "claims" field is unexported by oidc IDToken @@ -199,7 +184,8 @@ func TestName(t *testing.T) { map[string]config.OIDCIssuer{ token.Issuer: { IssuerURL: token.Issuer, - Type: config.IssuerTypeGithubWorkflow, + Type: config.IssuerTypeCiProvider, + SubType: "ci-provider", ClientID: "sigstore", }, } @@ -222,57 +208,10 @@ func TestName(t *testing.T) { func TestEmbed(t *testing.T) { tests := map[string]struct { - Principal identity.Principal WantErr bool WantFacts map[string]func(x509.Certificate) error }{ `Github workflow challenge should have all Github workflow extensions and issuer set`: { - Principal: &Provider{ - Extensions: certificate.Extensions{ - Issuer: "issuer", - GithubWorkflowTrigger: "event_name", - GithubWorkflowSHA: "sha", - GithubWorkflowName: "workflow", - GithubWorkflowRepository: "repository", - GithubWorkflowRef: "ref", - BuildSignerURI: "{{ .url }}/{{ .job_workflow_ref }}", - BuildSignerDigest: "job_workflow_sha", - RunnerEnvironment: "runner_environment", - SourceRepositoryURI: "{{ .url }}/{{ .repository }}", - SourceRepositoryDigest: "sha", - SourceRepositoryRef: "ref", - SourceRepositoryIdentifier: "repository_id", - SourceRepositoryOwnerURI: "{{ .url }}/{{ .repository_owner }}", - SourceRepositoryOwnerIdentifier: "repository_owner_id", - BuildConfigURI: "{{ .url }}/{{ .workflow_ref }}", - BuildConfigDigest: "workflow_sha", - BuildTrigger: "event_name", - RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", - SourceRepositoryVisibilityAtSigning: "repository_visibility", - }, - Claims: map[string]interface{}{ - "issuer": "https://token.actions.githubusercontent.com", - "event_name": "trigger", - "sha": "sha", - "workflow": "workflowname", - "repository": "repository", - "ref": "ref", - "job_workflow_sha": "jobWorkflowSha", - "job_workflow_ref": "jobWorkflowRef", - "runner_environment": "runnerEnv", - "repository_id": "repoID", - "repository_owner": "repoOwner", - "repository_owner_id": "repoOwnerID", - "workflow_ref": "workflowRef", - "workflow_sha": "workflowSHA", - "run_id": "runID", - "run_attempt": "runAttempt", - "repository_visibility": "public", - }, - Defaults: map[string]string{ - "url": "https://github.com", - }, - }, WantErr: false, WantFacts: map[string]func(x509.Certificate) error{ `Certifificate should have correct issuer`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, "https://token.actions.githubusercontent.com"), @@ -298,16 +237,70 @@ func TestEmbed(t *testing.T) { `Certificate has correct source repository visibility extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}, "public"), }, }, - `Github workflow value with bad URL fails`: { - Principal: &Provider{}, - WantErr: true, - }, } for name, test := range tests { t.Run(name, func(t *testing.T) { var cert x509.Certificate - err := test.Principal.Embed(context.TODO(), &cert) + claims, err := json.Marshal(map[string]interface{}{ + "issuer": "https://token.actions.githubusercontent.com", + "event_name": "trigger", + "sha": "sha", + "workflow": "workflowname", + "repository": "repository", + "ref": "ref", + "job_workflow_sha": "jobWorkflowSha", + "job_workflow_ref": "jobWorkflowRef", + "runner_environment": "runnerEnv", + "repository_id": "repoID", + "repository_owner": "repoOwner", + "repository_owner_id": "repoOwnerID", + "workflow_ref": "workflowRef", + "workflow_sha": "workflowSHA", + "run_id": "runID", + "run_attempt": "runAttempt", + "repository_visibility": "public", + }) + if err != nil { + t.Fatal(err) + } + token := &oidc.IDToken{} + withClaims(token, claims) + + principal := + &Config{ + Metadata: config.IssuersMetadata{ + ClaimsMapper: certificate.Extensions{ + Issuer: "issuer", + GithubWorkflowTrigger: "event_name", + GithubWorkflowSHA: "sha", + GithubWorkflowName: "workflow", + GithubWorkflowRepository: "repository", + GithubWorkflowRef: "ref", + BuildSignerURI: "{{ .url }}/{{ .job_workflow_ref }}", + BuildSignerDigest: "job_workflow_sha", + RunnerEnvironment: "runner_environment", + SourceRepositoryURI: "{{ .url }}/{{ .repository }}", + SourceRepositoryDigest: "sha", + SourceRepositoryRef: "ref", + SourceRepositoryIdentifier: "repository_id", + SourceRepositoryOwnerURI: "{{ .url }}/{{ .repository_owner }}", + SourceRepositoryOwnerIdentifier: "repository_owner_id", + BuildConfigURI: "{{ .url }}/{{ .workflow_ref }}", + BuildConfigDigest: "workflow_sha", + BuildTrigger: "event_name", + RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", + SourceRepositoryVisibilityAtSigning: "repository_visibility", + }, + Defaults: map[string]string{ + "url": "https://github.com", + }, + SubjectAlternativeName: "{{.url}}/{{.job_workflow_ref}}", + }, + Token: token, + } + + err = principal.Embed(context.TODO(), &cert) if err != nil { if !test.WantErr { t.Error(err) diff --git a/pkg/server/issuer_pool.go b/pkg/server/issuer_pool.go index 18349f262..25f442cf4 100644 --- a/pkg/server/issuer_pool.go +++ b/pkg/server/issuer_pool.go @@ -19,6 +19,7 @@ import ( "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/fulcio/pkg/identity/buildkite" "github.com/sigstore/fulcio/pkg/identity/chainguard" + "github.com/sigstore/fulcio/pkg/identity/ciprovider" "github.com/sigstore/fulcio/pkg/identity/codefresh" "github.com/sigstore/fulcio/pkg/identity/email" "github.com/sigstore/fulcio/pkg/identity/github" @@ -57,6 +58,8 @@ func getIssuer(meta string, i config.OIDCIssuer) identity.Issuer { return email.Issuer(issuerURL) case config.IssuerTypeGithubWorkflow: return github.Issuer(issuerURL) + case config.IssuerTypeCiProvider: + return ciprovider.Issuer(issuerURL) case config.IssuerTypeGitLabPipeline: return gitlabcom.Issuer(issuerURL) case config.IssuerTypeBuildkiteJob: From c50cd30858d684ee2ac103d20b00f20aaedc39e9 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 19 Jun 2024 15:53:50 +0000 Subject: [PATCH 23/47] comment applyTemplate Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/principal.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 1b7ae7561..c2b6b6b6f 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -37,6 +37,8 @@ func claimsToString(claims map[string]interface{}) map[string]string { return stringClaims } +// It makes string interpolation for a given string by using the +// templates syntax https://pkg.go.dev/text/template func applyTemplate(path string, data map[string]string, defaultData map[string]string) string { // Here we merge the data from was claimed by the id token with the @@ -110,7 +112,7 @@ func (p Config) Embed(_ context.Context, cert *x509.Certificate) error { // Embed additional information into custom extensions cert.ExtraExtensions, err = certificate.Extensions{ - Issuer: applyTemplate(e.Issuer, claims, defaults), + Issuer: e.Issuer, GithubWorkflowTrigger: applyTemplate(e.GithubWorkflowTrigger, claims, defaults), GithubWorkflowSHA: applyTemplate(e.GithubWorkflowSHA, claims, defaults), GithubWorkflowName: applyTemplate(e.GithubWorkflowName, claims, defaults), From cedc9cff0581a9d94302b92e47b4f349ae8f0a17 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 19 Jun 2024 16:04:06 +0000 Subject: [PATCH 24/47] removing github for now, we should add it futher for rollouting Signed-off-by: Javan lacerda --- pkg/config/providers_config.yaml | 17 ------ pkg/identity/ciprovider/principal.go | 44 +++++++------- pkg/identity/ciprovider/principal_test.go | 74 ++++++++++------------- 3 files changed, 55 insertions(+), 80 deletions(-) diff --git a/pkg/config/providers_config.yaml b/pkg/config/providers_config.yaml index ef3c6e85f..3165b449e 100644 --- a/pkg/config/providers_config.yaml +++ b/pkg/config/providers_config.yaml @@ -13,20 +13,3 @@ # limitations under the License. providers: - github-workflow: - extensions: - build-signer-digest: job_workflow_sha - source-repository-digest: sha - source-repository-ref: ref - source-repository-identifier: repository_id - run-invocation-uri: "{{.url}}/{{.repository}}/actions/runs/{{.run_id}}/" - subject-alternative-name: "{{.url}}/{{.job_workflow_ref}}" - defaults: - url: https://github.com - meta-issuers: - - issuer-url: "https://token.actions.githubusercontent.com/*" - client-id: "sigstore" - oidc-issuers: - - issuer-url: https://token.actions.githubusercontent.com - contact: tac@sigstore.dev - description: "GitHub Actions OIDC auth" \ No newline at end of file diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index c2b6b6b6f..5ccebc520 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -39,7 +39,7 @@ func claimsToString(claims map[string]interface{}) map[string]string { // It makes string interpolation for a given string by using the // templates syntax https://pkg.go.dev/text/template -func applyTemplate(path string, data map[string]string, defaultData map[string]string) string { +func applyTemplateOrReplace(path string, data map[string]string, defaultData map[string]string) string { // Here we merge the data from was claimed by the id token with the // default data provided by the yaml file. @@ -102,7 +102,7 @@ func (p Config) Embed(_ context.Context, cert *x509.Certificate) error { } claims := claimsToString(rawClaims) - subjectAlternativeNameURL, err := url.Parse(applyTemplate(p.Metadata.SubjectAlternativeName, claims, defaults)) + subjectAlternativeNameURL, err := url.Parse(applyTemplateOrReplace(p.Metadata.SubjectAlternativeName, claims, defaults)) if err != nil { panic(err) } @@ -112,26 +112,26 @@ func (p Config) Embed(_ context.Context, cert *x509.Certificate) error { // Embed additional information into custom extensions cert.ExtraExtensions, err = certificate.Extensions{ - Issuer: e.Issuer, - GithubWorkflowTrigger: applyTemplate(e.GithubWorkflowTrigger, claims, defaults), - GithubWorkflowSHA: applyTemplate(e.GithubWorkflowSHA, claims, defaults), - GithubWorkflowName: applyTemplate(e.GithubWorkflowName, claims, defaults), - GithubWorkflowRepository: applyTemplate(e.GithubWorkflowRepository, claims, defaults), - GithubWorkflowRef: applyTemplate(e.GithubWorkflowRef, claims, defaults), - BuildSignerURI: applyTemplate(e.BuildSignerURI, claims, defaults), - BuildConfigDigest: applyTemplate(e.BuildConfigDigest, claims, defaults), - RunnerEnvironment: applyTemplate(e.RunnerEnvironment, claims, defaults), - SourceRepositoryURI: applyTemplate(e.SourceRepositoryURI, claims, defaults), - SourceRepositoryDigest: applyTemplate(e.SourceRepositoryDigest, claims, defaults), - SourceRepositoryRef: applyTemplate(e.SourceRepositoryRef, claims, defaults), - SourceRepositoryIdentifier: applyTemplate(e.SourceRepositoryIdentifier, claims, defaults), - SourceRepositoryOwnerURI: applyTemplate(e.SourceRepositoryOwnerURI, claims, defaults), - SourceRepositoryOwnerIdentifier: applyTemplate(e.SourceRepositoryOwnerIdentifier, claims, defaults), - BuildConfigURI: applyTemplate(e.BuildConfigURI, claims, defaults), - BuildSignerDigest: applyTemplate(e.BuildSignerDigest, claims, defaults), - BuildTrigger: applyTemplate(e.BuildTrigger, claims, defaults), - RunInvocationURI: applyTemplate(e.RunInvocationURI, claims, defaults), - SourceRepositoryVisibilityAtSigning: applyTemplate(e.SourceRepositoryVisibilityAtSigning, claims, defaults), + Issuer: applyTemplateOrReplace(e.Issuer, claims, defaults), + GithubWorkflowTrigger: applyTemplateOrReplace(e.GithubWorkflowTrigger, claims, defaults), + GithubWorkflowSHA: applyTemplateOrReplace(e.GithubWorkflowSHA, claims, defaults), + GithubWorkflowName: applyTemplateOrReplace(e.GithubWorkflowName, claims, defaults), + GithubWorkflowRepository: applyTemplateOrReplace(e.GithubWorkflowRepository, claims, defaults), + GithubWorkflowRef: applyTemplateOrReplace(e.GithubWorkflowRef, claims, defaults), + BuildSignerURI: applyTemplateOrReplace(e.BuildSignerURI, claims, defaults), + BuildConfigDigest: applyTemplateOrReplace(e.BuildConfigDigest, claims, defaults), + RunnerEnvironment: applyTemplateOrReplace(e.RunnerEnvironment, claims, defaults), + SourceRepositoryURI: applyTemplateOrReplace(e.SourceRepositoryURI, claims, defaults), + SourceRepositoryDigest: applyTemplateOrReplace(e.SourceRepositoryDigest, claims, defaults), + SourceRepositoryRef: applyTemplateOrReplace(e.SourceRepositoryRef, claims, defaults), + SourceRepositoryIdentifier: applyTemplateOrReplace(e.SourceRepositoryIdentifier, claims, defaults), + SourceRepositoryOwnerURI: applyTemplateOrReplace(e.SourceRepositoryOwnerURI, claims, defaults), + SourceRepositoryOwnerIdentifier: applyTemplateOrReplace(e.SourceRepositoryOwnerIdentifier, claims, defaults), + BuildConfigURI: applyTemplateOrReplace(e.BuildConfigURI, claims, defaults), + BuildSignerDigest: applyTemplateOrReplace(e.BuildSignerDigest, claims, defaults), + BuildTrigger: applyTemplateOrReplace(e.BuildTrigger, claims, defaults), + RunInvocationURI: applyTemplateOrReplace(e.RunInvocationURI, claims, defaults), + SourceRepositoryVisibilityAtSigning: applyTemplateOrReplace(e.SourceRepositoryVisibilityAtSigning, claims, defaults), }.Render() if err != nil { return err diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 81d719a63..88c7568f8 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -208,11 +208,10 @@ func TestName(t *testing.T) { func TestEmbed(t *testing.T) { tests := map[string]struct { - WantErr bool WantFacts map[string]func(x509.Certificate) error + Principal Config }{ `Github workflow challenge should have all Github workflow extensions and issuer set`: { - WantErr: false, WantFacts: map[string]func(x509.Certificate) error{ `Certifificate should have correct issuer`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, "https://token.actions.githubusercontent.com"), `Certificate has correct trigger extension`: factDeprecatedExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, "trigger"), @@ -236,6 +235,36 @@ func TestEmbed(t *testing.T) { `Certificate has correct run invocation ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}, "https://github.com/repository/actions/runs/runID/attempts/runAttempt"), `Certificate has correct source repository visibility extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}, "public"), }, + Principal: Config{ + Metadata: config.IssuersMetadata{ + ClaimsMapper: certificate.Extensions{ + Issuer: "issuer", + GithubWorkflowTrigger: "event_name", + GithubWorkflowSHA: "sha", + GithubWorkflowName: "workflow", + GithubWorkflowRepository: "repository", + GithubWorkflowRef: "ref", + BuildSignerURI: "{{ .url }}/{{ .job_workflow_ref }}", + BuildSignerDigest: "job_workflow_sha", + RunnerEnvironment: "runner_environment", + SourceRepositoryURI: "{{ .url }}/{{ .repository }}", + SourceRepositoryDigest: "sha", + SourceRepositoryRef: "ref", + SourceRepositoryIdentifier: "repository_id", + SourceRepositoryOwnerURI: "{{ .url }}/{{ .repository_owner }}", + SourceRepositoryOwnerIdentifier: "repository_owner_id", + BuildConfigURI: "{{ .url }}/{{ .workflow_ref }}", + BuildConfigDigest: "workflow_sha", + BuildTrigger: "event_name", + RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", + SourceRepositoryVisibilityAtSigning: "repository_visibility", + }, + Defaults: map[string]string{ + "url": "https://github.com", + }, + SubjectAlternativeName: "{{.url}}/{{.job_workflow_ref}}", + }, + }, }, } @@ -267,47 +296,10 @@ func TestEmbed(t *testing.T) { token := &oidc.IDToken{} withClaims(token, claims) - principal := - &Config{ - Metadata: config.IssuersMetadata{ - ClaimsMapper: certificate.Extensions{ - Issuer: "issuer", - GithubWorkflowTrigger: "event_name", - GithubWorkflowSHA: "sha", - GithubWorkflowName: "workflow", - GithubWorkflowRepository: "repository", - GithubWorkflowRef: "ref", - BuildSignerURI: "{{ .url }}/{{ .job_workflow_ref }}", - BuildSignerDigest: "job_workflow_sha", - RunnerEnvironment: "runner_environment", - SourceRepositoryURI: "{{ .url }}/{{ .repository }}", - SourceRepositoryDigest: "sha", - SourceRepositoryRef: "ref", - SourceRepositoryIdentifier: "repository_id", - SourceRepositoryOwnerURI: "{{ .url }}/{{ .repository_owner }}", - SourceRepositoryOwnerIdentifier: "repository_owner_id", - BuildConfigURI: "{{ .url }}/{{ .workflow_ref }}", - BuildConfigDigest: "workflow_sha", - BuildTrigger: "event_name", - RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", - SourceRepositoryVisibilityAtSigning: "repository_visibility", - }, - Defaults: map[string]string{ - "url": "https://github.com", - }, - SubjectAlternativeName: "{{.url}}/{{.job_workflow_ref}}", - }, - Token: token, - } - - err = principal.Embed(context.TODO(), &cert) + test.Principal.Token = token + err = test.Principal.Embed(context.TODO(), &cert) if err != nil { - if !test.WantErr { - t.Error(err) - } return - } else if test.WantErr { - t.Error("expected error") } for factName, fact := range test.WantFacts { t.Run(factName, func(t *testing.T) { From 31f8e11a9509d2517f2fa6cefb408e0bbcbce277 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Thu, 20 Jun 2024 16:59:29 +0000 Subject: [PATCH 25/47] Adding option to check the required claim exist Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/principal.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 5ccebc520..5bf1d5cda 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -55,7 +55,9 @@ func applyTemplateOrReplace(path string, data map[string]string, defaultData map if strings.Contains(path, "{{") { var doc bytes.Buffer - t := template.New("") + // This option forces to having the claim that is required + // for the template + t := template.New("").Option("missingkey=error") p, err := t.Parse(path) if err != nil { panic(err) From 8eff53250949a51af6b1ca5e9ec7de3a5d43e7bd Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 24 Jun 2024 13:43:39 +0000 Subject: [PATCH 26/47] omit subytype for json Signed-off-by: Javan lacerda --- config/fulcio-config.yaml | 47 +++++++++++++-------------------------- pkg/config/config.go | 2 +- 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/config/fulcio-config.yaml b/config/fulcio-config.yaml index 4dd5d23bd..c11af31b5 100644 --- a/config/fulcio-config.yaml +++ b/config/fulcio-config.yaml @@ -20,51 +20,43 @@ data: "https://accounts.google.com": { "IssuerURL": "https://accounts.google.com", "ClientID": "sigstore", - "Type": "email", - "SubType": "" + "Type": "email" }, "https://agent.buildkite.com": { "IssuerURL": "https://agent.buildkite.com", "ClientID": "sigstore", - "Type": "buildkite-job", - "SubType": "" + "Type": "buildkite-job" }, "https://allow.pub": { "IssuerURL": "https://allow.pub", "ClientID": "sigstore", "Type": "spiffe", - "SubType": "", "SPIFFETrustDomain": "allow.pub" }, "https://auth-staging.eclipse.org/realms/sigstore": { "IssuerURL": "https://auth-staging.eclipse.org/realms/sigstore", "ClientID": "sigstore", - "Type": "email", - "SubType": "" + "Type": "email" }, "https://auth.eclipse.org/auth/realms/sigstore": { "IssuerURL": "https://auth.eclipse.org/auth/realms/sigstore", "ClientID": "sigstore", - "Type": "email", - "SubType": "" + "Type": "email" }, "https://dev.gitlab.org": { "IssuerURL": "https://dev.gitlab.org", "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "SubType": "" + "Type": "gitlab-pipeline" }, "https://gitlab.archlinux.org": { "IssuerURL": "https://gitlab.archlinux.org", "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "SubType": "" + "Type": "gitlab-pipeline" }, "https://gitlab.com": { "IssuerURL": "https://gitlab.com", "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "SubType": "" + "Type": "gitlab-pipeline" }, "https://issuer.enforce.dev": { "IssuerURL": "https://issuer.enforce.dev", @@ -75,53 +67,44 @@ data: "IssuerURL": "https://oauth2.sigstore.dev/auth", "ClientID": "sigstore", "Type": "email", - "SubType": "", "IssuerClaim": "$.federated_claims.connector_id" }, "https://oidc.codefresh.io": { "IssuerURL": "https://oidc.codefresh.io", "ClientID": "sigstore", - "Type": "codefresh-workflow", - "SubType": "" + "Type": "codefresh-workflow" }, "https://ops.gitlab.net": { "IssuerURL": "https://ops.gitlab.net", "ClientID": "sigstore", - "Type": "gitlab-pipeline", - "SubType": "" + "Type": "gitlab-pipeline" }, "https://token.actions.githubusercontent.com": { "IssuerURL": "https://token.actions.githubusercontent.com", "ClientID": "sigstore", - "Type": "github-workflow", - "SubType": "" + "Type": "github-workflow" } }, "MetaIssuers": { "https://*.oic.prod-aks.azure.com/*": { "ClientID": "sigstore", - "Type": "kubernetes", - "SubType": "" + "Type": "kubernetes" }, "https://container.googleapis.com/v1/projects/*/locations/*/clusters/*": { "ClientID": "sigstore", - "Type": "kubernetes", - "SubType": "" + "Type": "kubernetes" }, "https://oidc.eks.*.amazonaws.com/id/*": { "ClientID": "sigstore", - "Type": "kubernetes", - "SubType": "" + "Type": "kubernetes" }, "https://oidc.prod-aks.azure.com/*": { "ClientID": "sigstore", - "Type": "kubernetes", - "SubType": "" + "Type": "kubernetes" }, "https://token.actions.githubusercontent.com/*": { "ClientID": "sigstore", - "Type": "github-workflow", - "SubType": "" + "Type": "github-workflow" } }, "IssuersMetadata": null diff --git a/pkg/config/config.go b/pkg/config/config.go index 41cdc421b..020558df9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -87,7 +87,7 @@ type OIDCIssuer struct { // certificate values are needed Type IssuerType `json:"Type" yaml:"type,omitempty"` // Issuers subtype - SubType string `json:"SubType" yaml:"sub-type,omitempty"` + SubType string `json:"SubType,omitempty" yaml:"sub-type,omitempty"` // Optional, if the issuer is in a different claim in the OIDC token IssuerClaim string `json:"IssuerClaim,omitempty" yaml:"issuer-claim,omitempty"` // The domain that must be present in the subject for 'uri' issuer types From b4800325bec8686c8591175e2f39f34c6363a42b Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 24 Jun 2024 18:28:10 +0000 Subject: [PATCH 27/47] rename issuers metadata Signed-off-by: Javan lacerda --- config/fulcio-config.yaml | 2 +- pkg/config/config.go | 31 +++++++++++++++-------- pkg/identity/ciprovider/issuer_test.go | 8 +++--- pkg/identity/ciprovider/principal.go | 4 +-- pkg/identity/ciprovider/principal_test.go | 26 +++++++++---------- 5 files changed, 40 insertions(+), 31 deletions(-) diff --git a/config/fulcio-config.yaml b/config/fulcio-config.yaml index c11af31b5..6619e9cd9 100644 --- a/config/fulcio-config.yaml +++ b/config/fulcio-config.yaml @@ -107,7 +107,7 @@ data: "Type": "github-workflow" } }, - "IssuersMetadata": null + "DefaultTemplateValues": null } server.yaml: |- host: 0.0.0.0 diff --git a/pkg/config/config.go b/pkg/config/config.go index 020558df9..a4eb9695f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -63,8 +63,11 @@ type FulcioConfig struct { // * https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us-west1-b/clusters/tenant-cluster MetaIssuers map[string]OIDCIssuer `json:"MetaIssuers,omitempty" yaml:"meta-issuers,omitempty"` - // defines the metadata for the issuers - IssuersMetadata map[string]IssuersMetadata + // It defines metadata to be used for the CIProvider identity provider principal. + // The CI provider has a generic logic for ci providers, this metadata is used + // to define the right behavior for each ci provider that is defined + // on the configuration file + CIIssuerMetadata map[string]DefaultTemplateValues // verifiers is a fixed mapping from our OIDCIssuers to their OIDC verifiers. verifiers map[string][]*verifierWithConfig @@ -72,9 +75,15 @@ type FulcioConfig struct { lru *lru.TwoQueueCache } -type IssuersMetadata struct { - Defaults map[string]string - ClaimsMapper certificate.Extensions +type DefaultTemplateValues struct { + // Default key and values that can be used for filling the templates + // If a key cannot be found on the token claims, the template will use the defaults + Defaults map[string]string + // It is the mapper from the id token claims to the Extensions. + // It expects strings with templates syntax https://pkg.go.dev/text/template + // or raw strings with claims keys to be replaced + ClaimsMapper certificate.Extensions + // A alternative name for the issuer subject SubjectAlternativeName string } @@ -86,8 +95,8 @@ type OIDCIssuer struct { // Used to determine the subject of the certificate and if additional // certificate values are needed Type IssuerType `json:"Type" yaml:"type,omitempty"` - // Issuers subtype - SubType string `json:"SubType,omitempty" yaml:"sub-type,omitempty"` + // Issuers CiProvider type + CIProvider string `json:"CIProvider,omitempty" yaml:"ci-provider,omitempty"` // Optional, if the issuer is in a different claim in the OIDC token IssuerClaim string `json:"IssuerClaim,omitempty" yaml:"issuer-claim,omitempty"` // The domain that must be present in the subject for 'uri' issuer types @@ -472,20 +481,20 @@ func LoadCiProvidersConfig(cfg *FulcioConfig) (*FulcioConfig, error) { fmt.Printf("Unmarshal: %v", err) } - cfg.IssuersMetadata = make(map[string]IssuersMetadata) + cfg.CIIssuerMetadata = make(map[string]DefaultTemplateValues) for k, v := range ciProvidersConfig.Providers { - cfg.IssuersMetadata[k] = IssuersMetadata{ + cfg.CIIssuerMetadata[k] = DefaultTemplateValues{ v.Defaults, v.Extensions, v.SubjectAlternativeName, } for _, issuer := range v.OIDCIssuers { - issuer.SubType = k + issuer.CIProvider = k issuer.Type = IssuerTypeCiProvider cfg.OIDCIssuers[issuer.IssuerURL] = issuer } for _, issuer := range v.MetaIssuers { - issuer.SubType = k + issuer.CIProvider = k issuer.Type = IssuerTypeCiProvider cfg.MetaIssuers[issuer.IssuerURL] = issuer } diff --git a/pkg/identity/ciprovider/issuer_test.go b/pkg/identity/ciprovider/issuer_test.go index 31fe91e3a..52753de94 100644 --- a/pkg/identity/ciprovider/issuer_test.go +++ b/pkg/identity/ciprovider/issuer_test.go @@ -75,10 +75,10 @@ func TestIssuer(t *testing.T) { OIDCIssuers := map[string]config.OIDCIssuer{ token.Issuer: { - IssuerURL: token.Issuer, - Type: config.IssuerTypeCiProvider, - SubType: "github-workflow", - ClientID: "sigstore", + IssuerURL: token.Issuer, + Type: config.IssuerTypeCiProvider, + CIProvider: "github-workflow", + ClientID: "sigstore", }, } cfg := &config.FulcioConfig{ diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 5bf1d5cda..681091e17 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -73,7 +73,7 @@ func applyTemplateOrReplace(path string, data map[string]string, defaultData map type Config struct { Token *oidc.IDToken - Metadata config.IssuersMetadata + Metadata config.DefaultTemplateValues } func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { @@ -85,7 +85,7 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (ide return Config{ token, - cfg.IssuersMetadata[issuer.SubType], + cfg.CIIssuerMetadata[issuer.CIProvider], }, nil } diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 88c7568f8..a973c033b 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -37,7 +37,7 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { }{ `Github workflow challenge should have all Github workflow extensions and issuer set`: { ExpectedPrincipal: Config{ - Metadata: config.IssuersMetadata{ + Metadata: config.DefaultTemplateValues{ ClaimsMapper: certificate.Extensions{ Issuer: "issuer", GithubWorkflowTrigger: "event_name", @@ -101,17 +101,17 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { OIDCIssuers := map[string]config.OIDCIssuer{ token.Issuer: { - IssuerURL: token.Issuer, - Type: config.IssuerTypeCiProvider, - SubType: "github-workflow", - ClientID: "sigstore", + IssuerURL: token.Issuer, + Type: config.IssuerTypeCiProvider, + CIProvider: "github-workflow", + ClientID: "sigstore", }, } - meta := make(map[string]config.IssuersMetadata) + meta := make(map[string]config.DefaultTemplateValues) meta["github-workflow"] = test.ExpectedPrincipal.Metadata cfg := &config.FulcioConfig{ - OIDCIssuers: OIDCIssuers, - IssuersMetadata: meta, + OIDCIssuers: OIDCIssuers, + CIIssuerMetadata: meta, } ctx = config.With(ctx, cfg) principal, err := WorkflowPrincipalFromIDToken(ctx, token) @@ -183,10 +183,10 @@ func TestName(t *testing.T) { OIDCIssuers := map[string]config.OIDCIssuer{ token.Issuer: { - IssuerURL: token.Issuer, - Type: config.IssuerTypeCiProvider, - SubType: "ci-provider", - ClientID: "sigstore", + IssuerURL: token.Issuer, + Type: config.IssuerTypeCiProvider, + CIProvider: "ci-provider", + ClientID: "sigstore", }, } cfg := &config.FulcioConfig{ @@ -236,7 +236,7 @@ func TestEmbed(t *testing.T) { `Certificate has correct source repository visibility extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}, "public"), }, Principal: Config{ - Metadata: config.IssuersMetadata{ + Metadata: config.DefaultTemplateValues{ ClaimsMapper: certificate.Extensions{ Issuer: "issuer", GithubWorkflowTrigger: "event_name", From 6558b317de3fdf0e8aa6de93ee68e30ca2e93875 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 24 Jun 2024 18:59:38 +0000 Subject: [PATCH 28/47] update the templates data order to prioritize default data over claimed data Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/principal.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 681091e17..8767a34e2 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -39,26 +39,27 @@ func claimsToString(claims map[string]interface{}) map[string]string { // It makes string interpolation for a given string by using the // templates syntax https://pkg.go.dev/text/template -func applyTemplateOrReplace(path string, data map[string]string, defaultData map[string]string) string { +func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]string, defaultTemplateValues map[string]string) string { // Here we merge the data from was claimed by the id token with the // default data provided by the yaml file. - // The order here matter because we want to override the default data - // with the claimed data. + // The order here matter because we want to override the claimed data + // with the default data. + // The default data will have priority over the claimed data. mergedData := make(map[string]string) - for k, v := range defaultData { + for k, v := range tokenClaims { mergedData[k] = v } - for k, v := range data { + for k, v := range defaultTemplateValues { mergedData[k] = v } - if strings.Contains(path, "{{") { + if strings.Contains(extValueTemplate, "{{") { var doc bytes.Buffer // This option forces to having the claim that is required // for the template t := template.New("").Option("missingkey=error") - p, err := t.Parse(path) + p, err := t.Parse(extValueTemplate) if err != nil { panic(err) } @@ -68,7 +69,7 @@ func applyTemplateOrReplace(path string, data map[string]string, defaultData map } return doc.String() } - return mergedData[path] + return mergedData[extValueTemplate] } type Config struct { From 5c7ee8554b4a8a3a4c8d677811e2b7ed37a849a3 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 24 Jun 2024 21:23:42 +0000 Subject: [PATCH 29/47] adding error handling for applyTemplateOrReplace Signed-off-by: Javan lacerda --- go.mod | 1 + go.sum | 2 + pkg/certificate/extensions.go | 38 ++++++------- pkg/config/config.go | 2 +- pkg/identity/ciprovider/principal.go | 66 +++++++++++------------ pkg/identity/ciprovider/principal_test.go | 6 +-- 6 files changed, 59 insertions(+), 56 deletions(-) diff --git a/go.mod b/go.mod index 8caa0c7bf..66c96ee25 100644 --- a/go.mod +++ b/go.mod @@ -82,6 +82,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chainguard-dev/slogctx v1.2.2 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect + github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-logr/logr v1.4.1 // indirect diff --git a/go.sum b/go.sum index e17dcbadb..d11f6604d 100644 --- a/go.sum +++ b/go.sum @@ -106,6 +106,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/pkg/certificate/extensions.go b/pkg/certificate/extensions.go index e3e96fe41..c07670336 100644 --- a/pkg/certificate/extensions.go +++ b/pkg/certificate/extensions.go @@ -69,69 +69,69 @@ type Extensions struct { // Deprecated // Triggering event of the Github Workflow. Matches the `event_name` claim of ID // tokens from Github Actions - GithubWorkflowTrigger string `yaml:"github-workflow-trigger"` // OID 1.3.6.1.4.1.57264.1.2 + GithubWorkflowTrigger string `yaml:"github-workflow-trigger,omitempty"` // OID 1.3.6.1.4.1.57264.1.2 // Deprecated // SHA of git commit being built in Github Actions. Matches the `sha` claim of ID // tokens from Github Actions - GithubWorkflowSHA string `yaml:"github-workflow-sha"` // OID 1.3.6.1.4.1.57264.1.3 + GithubWorkflowSHA string `yaml:"github-workflow-sha,omitempty"` // OID 1.3.6.1.4.1.57264.1.3 // Deprecated // Name of Github Actions Workflow. Matches the `workflow` claim of the ID // tokens from Github Actions - GithubWorkflowName string `yaml:"github-workflow-name"` // OID 1.3.6.1.4.1.57264.1.4 + GithubWorkflowName string `yaml:"github-workflow-name,omitempty"` // OID 1.3.6.1.4.1.57264.1.4 // Deprecated // Repository of the Github Actions Workflow. Matches the `repository` claim of the ID // tokens from Github Actions - GithubWorkflowRepository string `yaml:"github-workflow-repository"` // OID 1.3.6.1.4.1.57264.1.5 + GithubWorkflowRepository string `yaml:"github-workflow-repository,omitempty"` // OID 1.3.6.1.4.1.57264.1.5 // Deprecated // Git Ref of the Github Actions Workflow. Matches the `ref` claim of the ID tokens // from Github Actions - GithubWorkflowRef string `yaml:"github-workflow-ref"` // 1.3.6.1.4.1.57264.1.6 + GithubWorkflowRef string `yaml:"github-workflow-ref,omitempty"` // 1.3.6.1.4.1.57264.1.6 // Reference to specific build instructions that are responsible for signing. - BuildSignerURI string `yaml:"build-signer-uri"` // 1.3.6.1.4.1.57264.1.9 + BuildSignerURI string `yaml:"build-signer-uri,omitempty"` // 1.3.6.1.4.1.57264.1.9 // Immutable reference to the specific version of the build instructions that is responsible for signing. - BuildSignerDigest string `yaml:"build-signer-digest"` // 1.3.6.1.4.1.57264.1.10 + BuildSignerDigest string `yaml:"build-signer-digest,omitempty"` // 1.3.6.1.4.1.57264.1.10 // Specifies whether the build took place in platform-hosted cloud infrastructure or customer/self-hosted infrastructure. - RunnerEnvironment string `yaml:"runner-environment"` // 1.3.6.1.4.1.57264.1.11 + RunnerEnvironment string `yaml:"runner-environment,omitempty"` // 1.3.6.1.4.1.57264.1.11 // Source repository URL that the build was based on. - SourceRepositoryURI string `yaml:"source-repository-uri"` // 1.3.6.1.4.1.57264.1.12 + SourceRepositoryURI string `yaml:"source-repository-uri,omitempty"` // 1.3.6.1.4.1.57264.1.12 // Immutable reference to a specific version of the source code that the build was based upon. - SourceRepositoryDigest string `yaml:"source-repository-digest"` // 1.3.6.1.4.1.57264.1.13 + SourceRepositoryDigest string `yaml:"source-repository-digest,omitempty"` // 1.3.6.1.4.1.57264.1.13 // Source Repository Ref that the build run was based upon. - SourceRepositoryRef string `yaml:"source-repository-ref"` // 1.3.6.1.4.1.57264.1.14 + SourceRepositoryRef string `yaml:"source-repository-ref,omitempty"` // 1.3.6.1.4.1.57264.1.14 // Immutable identifier for the source repository the workflow was based upon. - SourceRepositoryIdentifier string `yaml:"source-repository-identifier"` // 1.3.6.1.4.1.57264.1.15 + SourceRepositoryIdentifier string `yaml:"source-repository-identifier,omitempty"` // 1.3.6.1.4.1.57264.1.15 // Source repository owner URL of the owner of the source repository that the build was based on. - SourceRepositoryOwnerURI string `yaml:"source-repository-owner-uri"` // 1.3.6.1.4.1.57264.1.16 + SourceRepositoryOwnerURI string `yaml:"source-repository-owner-uri,omitempty"` // 1.3.6.1.4.1.57264.1.16 // Immutable identifier for the owner of the source repository that the workflow was based upon. - SourceRepositoryOwnerIdentifier string `yaml:"source-repository-owner-identifier"` // 1.3.6.1.4.1.57264.1.17 + SourceRepositoryOwnerIdentifier string `yaml:"source-repository-owner-identifier,omitempty"` // 1.3.6.1.4.1.57264.1.17 // Build Config URL to the top-level/initiating build instructions. - BuildConfigURI string `yaml:"build-config-uri"` // 1.3.6.1.4.1.57264.1.18 + BuildConfigURI string `yaml:"build-config-uri,omitempty"` // 1.3.6.1.4.1.57264.1.18 // Immutable reference to the specific version of the top-level/initiating build instructions. - BuildConfigDigest string `yaml:"build-config-digest"` // 1.3.6.1.4.1.57264.1.19 + BuildConfigDigest string `yaml:"build-config-digest,omitempty"` // 1.3.6.1.4.1.57264.1.19 // Event or action that initiated the build. - BuildTrigger string `yaml:"build-trigger"` // 1.3.6.1.4.1.57264.1.20 + BuildTrigger string `yaml:"build-trigger,omitempty"` // 1.3.6.1.4.1.57264.1.20 // Run Invocation URL to uniquely identify the build execution. - RunInvocationURI string `yaml:"run-invocation-uri"` // 1.3.6.1.4.1.57264.1.21 + RunInvocationURI string `yaml:"run-invocation-uri,omitempty"` // 1.3.6.1.4.1.57264.1.21 // Source repository visibility at the time of signing the certificate. - SourceRepositoryVisibilityAtSigning string `yaml:"source-repository-visibility-at-signing"` // 1.3.6.1.4.1.57264.1.22 + SourceRepositoryVisibilityAtSigning string `yaml:"source-repository-visibility-at-signing,omitempty"` // 1.3.6.1.4.1.57264.1.22 } func (e Extensions) Render() ([]pkix.Extension, error) { diff --git a/pkg/config/config.go b/pkg/config/config.go index a4eb9695f..027476eb5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -82,7 +82,7 @@ type DefaultTemplateValues struct { // It is the mapper from the id token claims to the Extensions. // It expects strings with templates syntax https://pkg.go.dev/text/template // or raw strings with claims keys to be replaced - ClaimsMapper certificate.Extensions + ClaimsMap certificate.Extensions // A alternative name for the issuer subject SubjectAlternativeName string } diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 8767a34e2..7bfaa168d 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -24,12 +24,14 @@ import ( "strings" "github.com/coreos/go-oidc/v3/oidc" + "github.com/fatih/structs" + "github.com/mitchellh/mapstructure" "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" ) -func claimsToString(claims map[string]interface{}) map[string]string { +func mapValuesToString(claims map[string]interface{}) map[string]string { stringClaims := make(map[string]string) for k, v := range claims { stringClaims[k] = v.(string) @@ -39,7 +41,7 @@ func claimsToString(claims map[string]interface{}) map[string]string { // It makes string interpolation for a given string by using the // templates syntax https://pkg.go.dev/text/template -func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]string, defaultTemplateValues map[string]string) string { +func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]string, defaultTemplateValues map[string]string) (string, error) { // Here we merge the data from was claimed by the id token with the // default data provided by the yaml file. @@ -61,15 +63,19 @@ func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]stri t := template.New("").Option("missingkey=error") p, err := t.Parse(extValueTemplate) if err != nil { - panic(err) + return "", err } err = p.Execute(&doc, mergedData) if err != nil { - panic(err) + return "", err } - return doc.String() + return doc.String(), nil } - return mergedData[extValueTemplate] + claimValue, ok := mergedData[extValueTemplate] + if !ok { + return "", fmt.Errorf("value <%s> not present in either claims or defaults", extValueTemplate) + } + return claimValue, nil } type Config struct { @@ -96,46 +102,40 @@ func (p Config) Name(_ context.Context) string { func (p Config) Embed(_ context.Context, cert *x509.Certificate) error { - e := p.Metadata.ClaimsMapper + e := p.Metadata.ClaimsMap defaults := p.Metadata.Defaults var rawClaims map[string]interface{} if err := p.Token.Claims(&rawClaims); err != nil { return err } - claims := claimsToString(rawClaims) + claims := mapValuesToString(rawClaims) - subjectAlternativeNameURL, err := url.Parse(applyTemplateOrReplace(p.Metadata.SubjectAlternativeName, claims, defaults)) + subjectAlternativeName, err := applyTemplateOrReplace(p.Metadata.SubjectAlternativeName, claims, defaults) if err != nil { - panic(err) + return err + } + subjectAlternativeNameURL, err := url.Parse(subjectAlternativeName) + if err != nil { + return err } uris := []*url.URL{subjectAlternativeNameURL} // Set workflow ref URL to SubjectAlternativeName on certificate cert.URIs = uris - + mapExtensionsForTemplate := mapValuesToString(structs.Map(e)) + for k, v := range mapExtensionsForTemplate { + mapExtensionsForTemplate[k], err = applyTemplateOrReplace(v, claims, defaults) + if err != nil { + return err + } + } + ext := &certificate.Extensions{} + err = mapstructure.Decode(mapExtensionsForTemplate, &ext) + if err != nil { + return err + } // Embed additional information into custom extensions - cert.ExtraExtensions, err = certificate.Extensions{ - Issuer: applyTemplateOrReplace(e.Issuer, claims, defaults), - GithubWorkflowTrigger: applyTemplateOrReplace(e.GithubWorkflowTrigger, claims, defaults), - GithubWorkflowSHA: applyTemplateOrReplace(e.GithubWorkflowSHA, claims, defaults), - GithubWorkflowName: applyTemplateOrReplace(e.GithubWorkflowName, claims, defaults), - GithubWorkflowRepository: applyTemplateOrReplace(e.GithubWorkflowRepository, claims, defaults), - GithubWorkflowRef: applyTemplateOrReplace(e.GithubWorkflowRef, claims, defaults), - BuildSignerURI: applyTemplateOrReplace(e.BuildSignerURI, claims, defaults), - BuildConfigDigest: applyTemplateOrReplace(e.BuildConfigDigest, claims, defaults), - RunnerEnvironment: applyTemplateOrReplace(e.RunnerEnvironment, claims, defaults), - SourceRepositoryURI: applyTemplateOrReplace(e.SourceRepositoryURI, claims, defaults), - SourceRepositoryDigest: applyTemplateOrReplace(e.SourceRepositoryDigest, claims, defaults), - SourceRepositoryRef: applyTemplateOrReplace(e.SourceRepositoryRef, claims, defaults), - SourceRepositoryIdentifier: applyTemplateOrReplace(e.SourceRepositoryIdentifier, claims, defaults), - SourceRepositoryOwnerURI: applyTemplateOrReplace(e.SourceRepositoryOwnerURI, claims, defaults), - SourceRepositoryOwnerIdentifier: applyTemplateOrReplace(e.SourceRepositoryOwnerIdentifier, claims, defaults), - BuildConfigURI: applyTemplateOrReplace(e.BuildConfigURI, claims, defaults), - BuildSignerDigest: applyTemplateOrReplace(e.BuildSignerDigest, claims, defaults), - BuildTrigger: applyTemplateOrReplace(e.BuildTrigger, claims, defaults), - RunInvocationURI: applyTemplateOrReplace(e.RunInvocationURI, claims, defaults), - SourceRepositoryVisibilityAtSigning: applyTemplateOrReplace(e.SourceRepositoryVisibilityAtSigning, claims, defaults), - }.Render() + cert.ExtraExtensions, err = ext.Render() if err != nil { return err } diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index a973c033b..29e9d1b4a 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -38,7 +38,7 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { `Github workflow challenge should have all Github workflow extensions and issuer set`: { ExpectedPrincipal: Config{ Metadata: config.DefaultTemplateValues{ - ClaimsMapper: certificate.Extensions{ + ClaimsMap: certificate.Extensions{ Issuer: "issuer", GithubWorkflowTrigger: "event_name", GithubWorkflowSHA: "sha", @@ -237,7 +237,7 @@ func TestEmbed(t *testing.T) { }, Principal: Config{ Metadata: config.DefaultTemplateValues{ - ClaimsMapper: certificate.Extensions{ + ClaimsMap: certificate.Extensions{ Issuer: "issuer", GithubWorkflowTrigger: "event_name", GithubWorkflowSHA: "sha", @@ -299,7 +299,7 @@ func TestEmbed(t *testing.T) { test.Principal.Token = token err = test.Principal.Embed(context.TODO(), &cert) if err != nil { - return + t.Error(err) } for factName, fact := range test.WantFacts { t.Run(factName, func(t *testing.T) { From 54c4e6dac820fb2414a40f0ffa95c9bfd8acafcd Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 25 Jun 2024 14:11:02 +0000 Subject: [PATCH 30/47] fixes and refactoring Signed-off-by: Javan lacerda --- pkg/challenges/challenges.go | 2 +- pkg/config/config.go | 22 +++++------- pkg/config/config_test.go | 4 +-- pkg/identity/ciprovider/issuer_test.go | 2 +- pkg/identity/ciprovider/principal.go | 44 ++++++++++++----------- pkg/identity/ciprovider/principal_test.go | 26 +++++++------- pkg/server/issuer_pool.go | 2 +- 7 files changed, 51 insertions(+), 51 deletions(-) diff --git a/pkg/challenges/challenges.go b/pkg/challenges/challenges.go index b80cdc76b..143fdaab9 100644 --- a/pkg/challenges/challenges.go +++ b/pkg/challenges/challenges.go @@ -76,7 +76,7 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin principal, err = uri.PrincipalFromIDToken(ctx, tok) case config.IssuerTypeUsername: principal, err = username.PrincipalFromIDToken(ctx, tok) - case config.IssuerTypeCiProvider: + case config.IssuerTypeCIProvider: principal, err = ciprovider.WorkflowPrincipalFromIDToken(ctx, tok) default: return nil, fmt.Errorf("unsupported issuer: %s", iss.Type) diff --git a/pkg/config/config.go b/pkg/config/config.go index 027476eb5..e2c43c430 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -79,12 +79,13 @@ type DefaultTemplateValues struct { // Default key and values that can be used for filling the templates // If a key cannot be found on the token claims, the template will use the defaults Defaults map[string]string - // It is the mapper from the id token claims to the Extensions. + // It is a Extensions version which the values are template strigs. // It expects strings with templates syntax https://pkg.go.dev/text/template // or raw strings with claims keys to be replaced - ClaimsMap certificate.Extensions + ClaimsTemplates certificate.Extensions // A alternative name for the issuer subject - SubjectAlternativeName string + // It's typically the same value as Build Signer URI + SubjectAlternativeNameTemplate string } type OIDCIssuer struct { @@ -307,7 +308,7 @@ const ( IssuerTypeSpiffe = "spiffe" IssuerTypeURI = "uri" IssuerTypeUsername = "username" - IssuerTypeCiProvider = "ci-provider" + IssuerTypeCIProvider = "ci-provider" ) func parseConfig(b []byte) (cfg *FulcioConfig, err error) { @@ -490,12 +491,12 @@ func LoadCiProvidersConfig(cfg *FulcioConfig) (*FulcioConfig, error) { } for _, issuer := range v.OIDCIssuers { issuer.CIProvider = k - issuer.Type = IssuerTypeCiProvider + issuer.Type = IssuerTypeCIProvider cfg.OIDCIssuers[issuer.IssuerURL] = issuer } for _, issuer := range v.MetaIssuers { issuer.CIProvider = k - issuer.Type = IssuerTypeCiProvider + issuer.Type = IssuerTypeCIProvider cfg.MetaIssuers[issuer.IssuerURL] = issuer } } @@ -516,12 +517,7 @@ func Load(configPath string) (*FulcioConfig, error) { if err != nil { return nil, fmt.Errorf("read file: %w", err) } - fulcioConfig, err := Read(b) - if err != nil { - return fulcioConfig, err - } - - return fulcioConfig, err + return Read(b) } // Read parses the bytes of a config @@ -591,7 +587,7 @@ func issuerToChallengeClaim(issType IssuerType, challengeClaim string) string { return "email" case IssuerTypeGithubWorkflow: return "sub" - case IssuerTypeCiProvider: + case IssuerTypeCIProvider: return "sub" case IssuerTypeCodefreshWorkflow: return "sub" diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index c5473a098..390bd6f6b 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -492,8 +492,8 @@ func Test_issuerToChallengeClaim(t *testing.T) { if claim := issuerToChallengeClaim(IssuerTypeGithubWorkflow, ""); claim != "sub" { t.Fatalf("expected sub subject claim for GitHub issuer, got %s", claim) } - if claim := issuerToChallengeClaim(IssuerTypeCiProvider, ""); claim != "sub" { - t.Fatalf("expected sub subject claim for GitHub issuer, got %s", claim) + if claim := issuerToChallengeClaim(IssuerTypeCIProvider, ""); claim != "sub" { + t.Fatalf("expected sub subject claim for CI issuer, got %s", claim) } if claim := issuerToChallengeClaim(IssuerTypeGitLabPipeline, ""); claim != "sub" { t.Fatalf("expected sub subject claim for GitLab issuer, got %s", claim) diff --git a/pkg/identity/ciprovider/issuer_test.go b/pkg/identity/ciprovider/issuer_test.go index 52753de94..1d8e605e3 100644 --- a/pkg/identity/ciprovider/issuer_test.go +++ b/pkg/identity/ciprovider/issuer_test.go @@ -76,7 +76,7 @@ func TestIssuer(t *testing.T) { map[string]config.OIDCIssuer{ token.Issuer: { IssuerURL: token.Issuer, - Type: config.IssuerTypeCiProvider, + Type: config.IssuerTypeCIProvider, CIProvider: "github-workflow", ClientID: "sigstore", }, diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 7bfaa168d..7aa35f91b 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -39,6 +39,14 @@ func mapValuesToString(claims map[string]interface{}) map[string]string { return stringClaims } +func getTokenClaims(token *oidc.IDToken) (map[string]string, error) { + var tokenClaims map[string]interface{} + if err := token.Claims(&tokenClaims); err != nil { + return nil, err + } + return mapValuesToString(tokenClaims), nil +} + // It makes string interpolation for a given string by using the // templates syntax https://pkg.go.dev/text/template func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]string, defaultTemplateValues map[string]string) (string, error) { @@ -78,49 +86,45 @@ func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]stri return claimValue, nil } -type Config struct { - Token *oidc.IDToken - Metadata config.DefaultTemplateValues +type ciPrincipal struct { + Token *oidc.IDToken + ClaimsMetadata config.DefaultTemplateValues } func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { cfg := config.FromContext(ctx) - issuer, ok := cfg.GetIssuer(token.Issuer) + issuerCfg, ok := cfg.GetIssuer(token.Issuer) if !ok { return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) } - return Config{ + return ciPrincipal{ token, - cfg.CIIssuerMetadata[issuer.CIProvider], + cfg.CIIssuerMetadata[issuerCfg.CIProvider], }, nil } -func (p Config) Name(_ context.Context) string { - return p.Token.Subject +func (principal ciPrincipal) Name(_ context.Context) string { + return principal.Token.Subject } -func (p Config) Embed(_ context.Context, cert *x509.Certificate) error { +func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { - e := p.Metadata.ClaimsMap - defaults := p.Metadata.Defaults - - var rawClaims map[string]interface{} - if err := p.Token.Claims(&rawClaims); err != nil { + e := principal.ClaimsMetadata.ClaimsTemplates + defaults := principal.ClaimsMetadata.Defaults + claims, err := getTokenClaims(principal.Token) + if err != nil { return err } - claims := mapValuesToString(rawClaims) - - subjectAlternativeName, err := applyTemplateOrReplace(p.Metadata.SubjectAlternativeName, claims, defaults) + subjectAlternativeName, err := applyTemplateOrReplace(principal.ClaimsMetadata.SubjectAlternativeNameTemplate, claims, defaults) if err != nil { return err } - subjectAlternativeNameURL, err := url.Parse(subjectAlternativeName) + sanURL, err := url.Parse(subjectAlternativeName) if err != nil { return err } - uris := []*url.URL{subjectAlternativeNameURL} - // Set workflow ref URL to SubjectAlternativeName on certificate + uris := []*url.URL{sanURL} cert.URIs = uris mapExtensionsForTemplate := mapValuesToString(structs.Map(e)) for k, v := range mapExtensionsForTemplate { diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 29e9d1b4a..ea3e3e24e 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -33,12 +33,12 @@ import ( func TestWorkflowPrincipalFromIDToken(t *testing.T) { tests := map[string]struct { - ExpectedPrincipal Config + ExpectedPrincipal ciPrincipal }{ `Github workflow challenge should have all Github workflow extensions and issuer set`: { - ExpectedPrincipal: Config{ - Metadata: config.DefaultTemplateValues{ - ClaimsMap: certificate.Extensions{ + ExpectedPrincipal: ciPrincipal{ + ClaimsMetadata: config.DefaultTemplateValues{ + ClaimsTemplates: certificate.Extensions{ Issuer: "issuer", GithubWorkflowTrigger: "event_name", GithubWorkflowSHA: "sha", @@ -63,7 +63,7 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { Defaults: map[string]string{ "url": "https://github.com", }, - SubjectAlternativeName: "{{.url}}/{{.job_workflow_ref}}", + SubjectAlternativeNameTemplate: "{{.url}}/{{.job_workflow_ref}}", }, }, }, @@ -102,13 +102,13 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { map[string]config.OIDCIssuer{ token.Issuer: { IssuerURL: token.Issuer, - Type: config.IssuerTypeCiProvider, + Type: config.IssuerTypeCIProvider, CIProvider: "github-workflow", ClientID: "sigstore", }, } meta := make(map[string]config.DefaultTemplateValues) - meta["github-workflow"] = test.ExpectedPrincipal.Metadata + meta["github-workflow"] = test.ExpectedPrincipal.ClaimsMetadata cfg := &config.FulcioConfig{ OIDCIssuers: OIDCIssuers, CIIssuerMetadata: meta, @@ -184,7 +184,7 @@ func TestName(t *testing.T) { map[string]config.OIDCIssuer{ token.Issuer: { IssuerURL: token.Issuer, - Type: config.IssuerTypeCiProvider, + Type: config.IssuerTypeCIProvider, CIProvider: "ci-provider", ClientID: "sigstore", }, @@ -209,7 +209,7 @@ func TestName(t *testing.T) { func TestEmbed(t *testing.T) { tests := map[string]struct { WantFacts map[string]func(x509.Certificate) error - Principal Config + Principal ciPrincipal }{ `Github workflow challenge should have all Github workflow extensions and issuer set`: { WantFacts: map[string]func(x509.Certificate) error{ @@ -235,9 +235,9 @@ func TestEmbed(t *testing.T) { `Certificate has correct run invocation ID extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}, "https://github.com/repository/actions/runs/runID/attempts/runAttempt"), `Certificate has correct source repository visibility extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}, "public"), }, - Principal: Config{ - Metadata: config.DefaultTemplateValues{ - ClaimsMap: certificate.Extensions{ + Principal: ciPrincipal{ + ClaimsMetadata: config.DefaultTemplateValues{ + ClaimsTemplates: certificate.Extensions{ Issuer: "issuer", GithubWorkflowTrigger: "event_name", GithubWorkflowSHA: "sha", @@ -262,7 +262,7 @@ func TestEmbed(t *testing.T) { Defaults: map[string]string{ "url": "https://github.com", }, - SubjectAlternativeName: "{{.url}}/{{.job_workflow_ref}}", + SubjectAlternativeNameTemplate: "{{.url}}/{{.job_workflow_ref}}", }, }, }, diff --git a/pkg/server/issuer_pool.go b/pkg/server/issuer_pool.go index 25f442cf4..61e05fa34 100644 --- a/pkg/server/issuer_pool.go +++ b/pkg/server/issuer_pool.go @@ -58,7 +58,7 @@ func getIssuer(meta string, i config.OIDCIssuer) identity.Issuer { return email.Issuer(issuerURL) case config.IssuerTypeGithubWorkflow: return github.Issuer(issuerURL) - case config.IssuerTypeCiProvider: + case config.IssuerTypeCIProvider: return ciprovider.Issuer(issuerURL) case config.IssuerTypeGitLabPipeline: return gitlabcom.Issuer(issuerURL) From 8f54c6a3f60c8fc70e07f84fe349ee16ecaef7f5 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 25 Jun 2024 15:18:22 +0000 Subject: [PATCH 31/47] set token.issuer as extension issuer Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/principal.go | 27 ++++++++++++++--------- pkg/identity/ciprovider/principal_test.go | 3 +-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 7aa35f91b..86e8494bd 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -32,11 +32,11 @@ import ( ) func mapValuesToString(claims map[string]interface{}) map[string]string { - stringClaims := make(map[string]string) + newMap := make(map[string]string) for k, v := range claims { - stringClaims[k] = v.(string) + newMap[k] = fmt.Sprintf("%v", v) } - return stringClaims + return newMap } func getTokenClaims(token *oidc.IDToken) (map[string]string, error) { @@ -110,7 +110,7 @@ func (principal ciPrincipal) Name(_ context.Context) string { func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { - e := principal.ClaimsMetadata.ClaimsTemplates + claimsTemplates := principal.ClaimsMetadata.ClaimsTemplates defaults := principal.ClaimsMetadata.Defaults claims, err := getTokenClaims(principal.Token) if err != nil { @@ -126,23 +126,30 @@ func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) er } uris := []*url.URL{sanURL} cert.URIs = uris - mapExtensionsForTemplate := mapValuesToString(structs.Map(e)) + mapExtensionsForTemplate := mapValuesToString(structs.Map(claimsTemplates)) for k, v := range mapExtensionsForTemplate { - mapExtensionsForTemplate[k], err = applyTemplateOrReplace(v, claims, defaults) - if err != nil { - return err + // It avoids to try applying template or replace for a empty string. + if v != "" { + mapExtensionsForTemplate[k], err = applyTemplateOrReplace(v, claims, defaults) + if err != nil { + return err + } } } - ext := &certificate.Extensions{} + ext := &certificate.Extensions{ + Issuer: principal.Token.Issuer, + } err = mapstructure.Decode(mapExtensionsForTemplate, &ext) if err != nil { return err } + // Guarantees to set the extension issuer as the token issuer + // regardless of whether this field has been set before + ext.Issuer = principal.Token.Issuer // Embed additional information into custom extensions cert.ExtraExtensions, err = ext.Render() if err != nil { return err } - return nil } diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index ea3e3e24e..bc5c19778 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -238,7 +238,6 @@ func TestEmbed(t *testing.T) { Principal: ciPrincipal{ ClaimsMetadata: config.DefaultTemplateValues{ ClaimsTemplates: certificate.Extensions{ - Issuer: "issuer", GithubWorkflowTrigger: "event_name", GithubWorkflowSHA: "sha", GithubWorkflowName: "workflow", @@ -272,7 +271,6 @@ func TestEmbed(t *testing.T) { t.Run(name, func(t *testing.T) { var cert x509.Certificate claims, err := json.Marshal(map[string]interface{}{ - "issuer": "https://token.actions.githubusercontent.com", "event_name": "trigger", "sha": "sha", "workflow": "workflowname", @@ -294,6 +292,7 @@ func TestEmbed(t *testing.T) { t.Fatal(err) } token := &oidc.IDToken{} + token.Issuer = "https://token.actions.githubusercontent.com" withClaims(token, claims) test.Principal.Token = token From a35b6ccdf77c2f3614f253860c0a258a0633f463 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 25 Jun 2024 20:47:30 +0000 Subject: [PATCH 32/47] remove load for providers, merge with fulcio config Signed-off-by: Javan lacerda --- cmd/app/serve.go | 4 ---- config/fulcio-config.yaml | 2 +- pkg/config/config.go | 48 --------------------------------------- 3 files changed, 1 insertion(+), 53 deletions(-) diff --git a/cmd/app/serve.go b/cmd/app/serve.go index a20918efb..de0b28194 100644 --- a/cmd/app/serve.go +++ b/cmd/app/serve.go @@ -214,10 +214,6 @@ func runServeCmd(cmd *cobra.Command, args []string) { //nolint: revive if err != nil { log.Logger.Fatalf("error loading --config-path=%s: %v", cp, err) } - cfg, err = config.LoadCiProvidersConfig(cfg) - if err != nil { - log.Logger.Fatalf("error loading CiProviderConfig: %v", err) - } var baseca certauth.CertificateAuthority switch viper.GetString("ca") { diff --git a/config/fulcio-config.yaml b/config/fulcio-config.yaml index 6619e9cd9..9df7c7cbb 100644 --- a/config/fulcio-config.yaml +++ b/config/fulcio-config.yaml @@ -107,7 +107,7 @@ data: "Type": "github-workflow" } }, - "DefaultTemplateValues": null + "CIIssuerMetadata": null } server.yaml: |- host: 0.0.0.0 diff --git a/pkg/config/config.go b/pkg/config/config.go index e2c43c430..de0927946 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -24,10 +24,8 @@ import ( "net/http" "net/url" "os" - "path/filepath" "reflect" "regexp" - "runtime" "strings" "time" @@ -457,52 +455,6 @@ func FromContext(ctx context.Context) *FulcioConfig { return untyped.(*FulcioConfig) } -type CiProvidersConfig struct { - Providers map[string]Provider -} -type Provider struct { - Extensions certificate.Extensions `yaml:"extensions,omitempty"` - SubjectAlternativeName string `yaml:"subject-alternative-name,omitempty"` - Defaults map[string]string `yaml:"defaults,omitempty"` - OIDCIssuers []OIDCIssuer `yaml:"oidc-issuers,omitempty"` - MetaIssuers []OIDCIssuer `yaml:"meta-issuers,omitempty"` -} - -func LoadCiProvidersConfig(cfg *FulcioConfig) (*FulcioConfig, error) { - var ciProvidersConfig CiProvidersConfig - _, path, _, _ := runtime.Caller(0) - basepath := filepath.Dir(path) - providersConfigFile, err := os.ReadFile(basepath + "/providers_config.yaml") - - if err != nil { - fmt.Printf("yamlFile.Get err #%v ", err) - } - err = yaml.Unmarshal(providersConfigFile, &ciProvidersConfig) - if err != nil { - fmt.Printf("Unmarshal: %v", err) - } - - cfg.CIIssuerMetadata = make(map[string]DefaultTemplateValues) - for k, v := range ciProvidersConfig.Providers { - cfg.CIIssuerMetadata[k] = DefaultTemplateValues{ - v.Defaults, - v.Extensions, - v.SubjectAlternativeName, - } - for _, issuer := range v.OIDCIssuers { - issuer.CIProvider = k - issuer.Type = IssuerTypeCIProvider - cfg.OIDCIssuers[issuer.IssuerURL] = issuer - } - for _, issuer := range v.MetaIssuers { - issuer.CIProvider = k - issuer.Type = IssuerTypeCIProvider - cfg.MetaIssuers[issuer.IssuerURL] = issuer - } - } - return cfg, err -} - // Load a config from disk, or use defaults func Load(configPath string) (*FulcioConfig, error) { if _, err := os.Stat(configPath); os.IsNotExist(err) { From 66a07a03b044d6af44aa0f362d2a926e3bce5dbd Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 26 Jun 2024 12:58:41 +0000 Subject: [PATCH 33/47] remove providers config file Signed-off-by: Javan lacerda --- go.mod | 4 ++-- pkg/config/providers_config.yaml | 15 --------------- 2 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 pkg/config/providers_config.yaml diff --git a/go.mod b/go.mod index 66c96ee25..b46686650 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/ThalesIgnite/crypto11 v1.2.5 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/coreos/go-oidc/v3 v3.10.0 + github.com/fatih/structs v1.1.0 github.com/fsnotify/fsnotify v1.7.0 github.com/go-jose/go-jose/v4 v4.0.2 github.com/goadesign/goa v2.2.5+incompatible @@ -22,6 +23,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/hashicorp/golang-lru v1.0.2 github.com/magiconair/properties v1.8.7 + github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.54.0 @@ -82,7 +84,6 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chainguard-dev/slogctx v1.2.2 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect - github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -111,7 +112,6 @@ require ( github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect diff --git a/pkg/config/providers_config.yaml b/pkg/config/providers_config.yaml deleted file mode 100644 index 3165b449e..000000000 --- a/pkg/config/providers_config.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2024 The Sigstore Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -providers: From e7cd08fb4cc43802f6f98aabf880b84dc9f364f8 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 26 Jun 2024 16:46:33 +0000 Subject: [PATCH 34/47] adding check for parsing templates Signed-off-by: Javan lacerda --- pkg/config/config.go | 36 +++++++++++++++++++++++- pkg/config/config_network_test.go | 42 ++++++++++++++++++++++++++++ pkg/identity/ciprovider/principal.go | 2 ++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index de0927946..d80b3fb7f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "html/template" "net/http" "net/url" "os" @@ -30,6 +31,7 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" + "github.com/fatih/structs" lru "github.com/hashicorp/golang-lru" "github.com/sigstore/fulcio/pkg/certificate" fulciogrpc "github.com/sigstore/fulcio/pkg/generated/protobuf" @@ -455,6 +457,32 @@ func FromContext(ctx context.Context) *FulcioConfig { return untyped.(*FulcioConfig) } +// It checks that the templates defined are parseable +// We should check it during the service bootstrap to avoid errors further +func CheckParseTemplates(fulcioConfig *FulcioConfig) error { + + checkParse := func(temp interface{}) error { + t := template.New("").Option("missingkey=error") + _, err := t.Parse(temp.(string)) + return err + } + + for _, ciIssuerMetadata := range fulcioConfig.CIIssuerMetadata { + claimsTemplates := structs.Map(ciIssuerMetadata.ClaimsTemplates) + for _, temp := range claimsTemplates { + err := checkParse(temp) + if err != nil { + return err + } + } + err := checkParse(ciIssuerMetadata.SubjectAlternativeNameTemplate) + if err != nil { + return err + } + } + return nil +} + // Load a config from disk, or use defaults func Load(configPath string) (*FulcioConfig, error) { if _, err := os.Stat(configPath); os.IsNotExist(err) { @@ -469,7 +497,13 @@ func Load(configPath string) (*FulcioConfig, error) { if err != nil { return nil, fmt.Errorf("read file: %w", err) } - return Read(b) + + fulcioConfig, err := Read(b) + if err != nil { + return fulcioConfig, err + } + err = CheckParseTemplates(fulcioConfig) + return fulcioConfig, err } // Read parses the bytes of a config diff --git a/pkg/config/config_network_test.go b/pkg/config/config_network_test.go index 52808181a..99405b594 100644 --- a/pkg/config/config_network_test.go +++ b/pkg/config/config_network_test.go @@ -25,6 +25,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sigstore/fulcio/pkg/certificate" ) func TestLoad(t *testing.T) { @@ -68,6 +69,47 @@ func TestLoad(t *testing.T) { } } +func TestParseTemplate(t *testing.T) { + + validTemplate := "{{.foobar}}" + invalidTemplate := "{{.foobar}" + ciissuerMetadata := make(map[string]DefaultTemplateValues) + ciissuerMetadata["github"] = DefaultTemplateValues{ + ClaimsTemplates: certificate.Extensions{ + BuildTrigger: invalidTemplate, + }, + } + fulcioConfig := &FulcioConfig{ + CIIssuerMetadata: ciissuerMetadata, + } + err := CheckParseTemplates(fulcioConfig) + if err == nil { + t.Error("It should raise an error") + } + ciissuerMetadata["github"] = DefaultTemplateValues{ + ClaimsTemplates: certificate.Extensions{ + BuildTrigger: validTemplate, + }, + } + fulcioConfig = &FulcioConfig{ + CIIssuerMetadata: ciissuerMetadata, + } + err = CheckParseTemplates(fulcioConfig) + if err != nil { + t.Error("It shouldn't raise an error") + } + ciissuerMetadata["github"] = DefaultTemplateValues{ + SubjectAlternativeNameTemplate: invalidTemplate, + } + fulcioConfig = &FulcioConfig{ + CIIssuerMetadata: ciissuerMetadata, + } + err = CheckParseTemplates(fulcioConfig) + if err == nil { + t.Error("It should raise an error") + } +} + func TestLoadDefaults(t *testing.T) { td := t.TempDir() diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 86e8494bd..c54231d3e 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -69,6 +69,8 @@ func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]stri // This option forces to having the claim that is required // for the template t := template.New("").Option("missingkey=error") + // It shouldn't raise error since we already checked all + // templates in CheckParseTemplates functions in config.go p, err := t.Parse(extValueTemplate) if err != nil { return "", err From 13e70598bfc73736c8255ff166dc665ac98cb2c5 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 26 Jun 2024 17:10:19 +0000 Subject: [PATCH 35/47] remove structs usage, using mapstructure instead Signed-off-by: Javan lacerda --- go.mod | 1 - go.sum | 2 -- pkg/config/config.go | 10 +++++++--- pkg/identity/ciprovider/principal.go | 10 +++++++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b46686650..bf517cd26 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/ThalesIgnite/crypto11 v1.2.5 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/coreos/go-oidc/v3 v3.10.0 - github.com/fatih/structs v1.1.0 github.com/fsnotify/fsnotify v1.7.0 github.com/go-jose/go-jose/v4 v4.0.2 github.com/goadesign/goa v2.2.5+incompatible diff --git a/go.sum b/go.sum index d11f6604d..e17dcbadb 100644 --- a/go.sum +++ b/go.sum @@ -106,8 +106,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/pkg/config/config.go b/pkg/config/config.go index d80b3fb7f..9e309b4ba 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -31,8 +31,8 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" - "github.com/fatih/structs" lru "github.com/hashicorp/golang-lru" + "github.com/mitchellh/mapstructure" "github.com/sigstore/fulcio/pkg/certificate" fulciogrpc "github.com/sigstore/fulcio/pkg/generated/protobuf" "github.com/sigstore/fulcio/pkg/log" @@ -468,14 +468,18 @@ func CheckParseTemplates(fulcioConfig *FulcioConfig) error { } for _, ciIssuerMetadata := range fulcioConfig.CIIssuerMetadata { - claimsTemplates := structs.Map(ciIssuerMetadata.ClaimsTemplates) + claimsTemplates := make(map[string]interface{}) + err := mapstructure.Decode(ciIssuerMetadata.ClaimsTemplates, &claimsTemplates) + if err != nil { + return err + } for _, temp := range claimsTemplates { err := checkParse(temp) if err != nil { return err } } - err := checkParse(ciIssuerMetadata.SubjectAlternativeNameTemplate) + err = checkParse(ciIssuerMetadata.SubjectAlternativeNameTemplate) if err != nil { return err } diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index c54231d3e..e6c3879c6 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -24,7 +24,6 @@ import ( "strings" "github.com/coreos/go-oidc/v3/oidc" - "github.com/fatih/structs" "github.com/mitchellh/mapstructure" "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" @@ -128,8 +127,13 @@ func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) er } uris := []*url.URL{sanURL} cert.URIs = uris - mapExtensionsForTemplate := mapValuesToString(structs.Map(claimsTemplates)) - for k, v := range mapExtensionsForTemplate { + mapExtensionsForTemplate := make(map[string]interface{}) + err = mapstructure.Decode(claimsTemplates, &mapExtensionsForTemplate) + if err != nil { + return err + } + + for k, v := range mapValuesToString(mapExtensionsForTemplate) { // It avoids to try applying template or replace for a empty string. if v != "" { mapExtensionsForTemplate[k], err = applyTemplateOrReplace(v, claims, defaults) From 397eee3970d514e077c203fcf70b8edc13ef6b54 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Sun, 30 Jun 2024 15:59:09 +0000 Subject: [PATCH 36/47] renamme IssuerMetadata Signed-off-by: Javan lacerda --- pkg/config/config.go | 6 +++--- pkg/config/config_network_test.go | 8 ++++---- pkg/identity/ciprovider/principal.go | 8 ++++---- pkg/identity/ciprovider/principal_test.go | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 9e309b4ba..6b601a28b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -67,7 +67,7 @@ type FulcioConfig struct { // The CI provider has a generic logic for ci providers, this metadata is used // to define the right behavior for each ci provider that is defined // on the configuration file - CIIssuerMetadata map[string]DefaultTemplateValues + CIIssuerMetadata map[string]IssuerMetadata // verifiers is a fixed mapping from our OIDCIssuers to their OIDC verifiers. verifiers map[string][]*verifierWithConfig @@ -75,10 +75,10 @@ type FulcioConfig struct { lru *lru.TwoQueueCache } -type DefaultTemplateValues struct { +type IssuerMetadata struct { // Default key and values that can be used for filling the templates // If a key cannot be found on the token claims, the template will use the defaults - Defaults map[string]string + DefaultTemplateValues map[string]string // It is a Extensions version which the values are template strigs. // It expects strings with templates syntax https://pkg.go.dev/text/template // or raw strings with claims keys to be replaced diff --git a/pkg/config/config_network_test.go b/pkg/config/config_network_test.go index 99405b594..54a67dc63 100644 --- a/pkg/config/config_network_test.go +++ b/pkg/config/config_network_test.go @@ -73,8 +73,8 @@ func TestParseTemplate(t *testing.T) { validTemplate := "{{.foobar}}" invalidTemplate := "{{.foobar}" - ciissuerMetadata := make(map[string]DefaultTemplateValues) - ciissuerMetadata["github"] = DefaultTemplateValues{ + ciissuerMetadata := make(map[string]IssuerMetadata) + ciissuerMetadata["github"] = IssuerMetadata{ ClaimsTemplates: certificate.Extensions{ BuildTrigger: invalidTemplate, }, @@ -86,7 +86,7 @@ func TestParseTemplate(t *testing.T) { if err == nil { t.Error("It should raise an error") } - ciissuerMetadata["github"] = DefaultTemplateValues{ + ciissuerMetadata["github"] = IssuerMetadata{ ClaimsTemplates: certificate.Extensions{ BuildTrigger: validTemplate, }, @@ -98,7 +98,7 @@ func TestParseTemplate(t *testing.T) { if err != nil { t.Error("It shouldn't raise an error") } - ciissuerMetadata["github"] = DefaultTemplateValues{ + ciissuerMetadata["github"] = IssuerMetadata{ SubjectAlternativeNameTemplate: invalidTemplate, } fulcioConfig = &FulcioConfig{ diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index e6c3879c6..ac50c6514 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -48,7 +48,7 @@ func getTokenClaims(token *oidc.IDToken) (map[string]string, error) { // It makes string interpolation for a given string by using the // templates syntax https://pkg.go.dev/text/template -func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]string, defaultTemplateValues map[string]string) (string, error) { +func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]string, issuerMetadata map[string]string) (string, error) { // Here we merge the data from was claimed by the id token with the // default data provided by the yaml file. @@ -59,7 +59,7 @@ func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]stri for k, v := range tokenClaims { mergedData[k] = v } - for k, v := range defaultTemplateValues { + for k, v := range issuerMetadata { mergedData[k] = v } @@ -89,7 +89,7 @@ func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]stri type ciPrincipal struct { Token *oidc.IDToken - ClaimsMetadata config.DefaultTemplateValues + ClaimsMetadata config.IssuerMetadata } func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) { @@ -112,7 +112,7 @@ func (principal ciPrincipal) Name(_ context.Context) string { func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { claimsTemplates := principal.ClaimsMetadata.ClaimsTemplates - defaults := principal.ClaimsMetadata.Defaults + defaults := principal.ClaimsMetadata.DefaultTemplateValues claims, err := getTokenClaims(principal.Token) if err != nil { return err diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index bc5c19778..2d70e5959 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -37,7 +37,7 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { }{ `Github workflow challenge should have all Github workflow extensions and issuer set`: { ExpectedPrincipal: ciPrincipal{ - ClaimsMetadata: config.DefaultTemplateValues{ + ClaimsMetadata: config.IssuerMetadata{ ClaimsTemplates: certificate.Extensions{ Issuer: "issuer", GithubWorkflowTrigger: "event_name", @@ -60,7 +60,7 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", SourceRepositoryVisibilityAtSigning: "repository_visibility", }, - Defaults: map[string]string{ + DefaultTemplateValues: map[string]string{ "url": "https://github.com", }, SubjectAlternativeNameTemplate: "{{.url}}/{{.job_workflow_ref}}", @@ -107,7 +107,7 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { ClientID: "sigstore", }, } - meta := make(map[string]config.DefaultTemplateValues) + meta := make(map[string]config.IssuerMetadata) meta["github-workflow"] = test.ExpectedPrincipal.ClaimsMetadata cfg := &config.FulcioConfig{ OIDCIssuers: OIDCIssuers, @@ -236,7 +236,7 @@ func TestEmbed(t *testing.T) { `Certificate has correct source repository visibility extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}, "public"), }, Principal: ciPrincipal{ - ClaimsMetadata: config.DefaultTemplateValues{ + ClaimsMetadata: config.IssuerMetadata{ ClaimsTemplates: certificate.Extensions{ GithubWorkflowTrigger: "event_name", GithubWorkflowSHA: "sha", @@ -258,7 +258,7 @@ func TestEmbed(t *testing.T) { RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", SourceRepositoryVisibilityAtSigning: "repository_visibility", }, - Defaults: map[string]string{ + DefaultTemplateValues: map[string]string{ "url": "https://github.com", }, SubjectAlternativeNameTemplate: "{{.url}}/{{.job_workflow_ref}}", From 04ef3c73d21408da06bb137cc5478f828b61a99b Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Sun, 30 Jun 2024 15:59:54 +0000 Subject: [PATCH 37/47] update SubjectAlternativeNameTemplate comment Signed-off-by: Javan lacerda --- pkg/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 6b601a28b..3181fdfbf 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -83,7 +83,7 @@ type IssuerMetadata struct { // It expects strings with templates syntax https://pkg.go.dev/text/template // or raw strings with claims keys to be replaced ClaimsTemplates certificate.Extensions - // A alternative name for the issuer subject + // Template for the Subject Alternative Name extension // It's typically the same value as Build Signer URI SubjectAlternativeNameTemplate string } From 96a66631b712eb17bf822496a7c13709222de318 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Sun, 30 Jun 2024 16:33:06 +0000 Subject: [PATCH 38/47] several fixes Signed-off-by: Javan lacerda --- pkg/config/config.go | 28 ++++++++++------------- pkg/config/config_network_test.go | 10 ++++---- pkg/identity/ciprovider/principal.go | 4 ++-- pkg/identity/ciprovider/principal_test.go | 4 ++-- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 3181fdfbf..c2d2c36cd 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -76,13 +76,15 @@ type FulcioConfig struct { } type IssuerMetadata struct { - // Default key and values that can be used for filling the templates + // Defaults contains key-value pairs that can be used for filling the templates from ExtensionTemplates // If a key cannot be found on the token claims, the template will use the defaults DefaultTemplateValues map[string]string - // It is a Extensions version which the values are template strigs. - // It expects strings with templates syntax https://pkg.go.dev/text/template - // or raw strings with claims keys to be replaced - ClaimsTemplates certificate.Extensions + // ExtensionTemplates contains a mapping between certificate extension and token claim + // Provide either strings following https://pkg.go.dev/text/template syntax, + // e.g "{{ .url }}/{{ .repository }}" + // or non-templated strings with token claim keys to be replaced, + // e.g "job_workflow_sha" + ExtensionTemplates certificate.Extensions // Template for the Subject Alternative Name extension // It's typically the same value as Build Signer URI SubjectAlternativeNameTemplate string @@ -96,7 +98,7 @@ type OIDCIssuer struct { // Used to determine the subject of the certificate and if additional // certificate values are needed Type IssuerType `json:"Type" yaml:"type,omitempty"` - // Issuers CiProvider type + // CIProvider is an optional configuration to map token claims to extensions for CI workflows CIProvider string `json:"CIProvider,omitempty" yaml:"ci-provider,omitempty"` // Optional, if the issuer is in a different claim in the OIDC token IssuerClaim string `json:"IssuerClaim,omitempty" yaml:"issuer-claim,omitempty"` @@ -416,7 +418,7 @@ func validateConfig(conf *FulcioConfig) error { } } - return nil + return validateCIIssuerMetadata(conf) } var DefaultConfig = &FulcioConfig{ @@ -459,7 +461,7 @@ func FromContext(ctx context.Context) *FulcioConfig { // It checks that the templates defined are parseable // We should check it during the service bootstrap to avoid errors further -func CheckParseTemplates(fulcioConfig *FulcioConfig) error { +func validateCIIssuerMetadata(fulcioConfig *FulcioConfig) error { checkParse := func(temp interface{}) error { t := template.New("").Option("missingkey=error") @@ -469,7 +471,7 @@ func CheckParseTemplates(fulcioConfig *FulcioConfig) error { for _, ciIssuerMetadata := range fulcioConfig.CIIssuerMetadata { claimsTemplates := make(map[string]interface{}) - err := mapstructure.Decode(ciIssuerMetadata.ClaimsTemplates, &claimsTemplates) + err := mapstructure.Decode(ciIssuerMetadata.ExtensionTemplates, &claimsTemplates) if err != nil { return err } @@ -501,13 +503,7 @@ func Load(configPath string) (*FulcioConfig, error) { if err != nil { return nil, fmt.Errorf("read file: %w", err) } - - fulcioConfig, err := Read(b) - if err != nil { - return fulcioConfig, err - } - err = CheckParseTemplates(fulcioConfig) - return fulcioConfig, err + return Read(b) } // Read parses the bytes of a config diff --git a/pkg/config/config_network_test.go b/pkg/config/config_network_test.go index 54a67dc63..093b33df9 100644 --- a/pkg/config/config_network_test.go +++ b/pkg/config/config_network_test.go @@ -75,26 +75,26 @@ func TestParseTemplate(t *testing.T) { invalidTemplate := "{{.foobar}" ciissuerMetadata := make(map[string]IssuerMetadata) ciissuerMetadata["github"] = IssuerMetadata{ - ClaimsTemplates: certificate.Extensions{ + ExtensionTemplates: certificate.Extensions{ BuildTrigger: invalidTemplate, }, } fulcioConfig := &FulcioConfig{ CIIssuerMetadata: ciissuerMetadata, } - err := CheckParseTemplates(fulcioConfig) + err := validateCIIssuerMetadata(fulcioConfig) if err == nil { t.Error("It should raise an error") } ciissuerMetadata["github"] = IssuerMetadata{ - ClaimsTemplates: certificate.Extensions{ + ExtensionTemplates: certificate.Extensions{ BuildTrigger: validTemplate, }, } fulcioConfig = &FulcioConfig{ CIIssuerMetadata: ciissuerMetadata, } - err = CheckParseTemplates(fulcioConfig) + err = validateCIIssuerMetadata(fulcioConfig) if err != nil { t.Error("It shouldn't raise an error") } @@ -104,7 +104,7 @@ func TestParseTemplate(t *testing.T) { fulcioConfig = &FulcioConfig{ CIIssuerMetadata: ciissuerMetadata, } - err = CheckParseTemplates(fulcioConfig) + err = validateCIIssuerMetadata(fulcioConfig) if err == nil { t.Error("It should raise an error") } diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index ac50c6514..f45068e42 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -69,7 +69,7 @@ func applyTemplateOrReplace(extValueTemplate string, tokenClaims map[string]stri // for the template t := template.New("").Option("missingkey=error") // It shouldn't raise error since we already checked all - // templates in CheckParseTemplates functions in config.go + // templates in validateCIIssuerMetadata functions in config.go p, err := t.Parse(extValueTemplate) if err != nil { return "", err @@ -111,7 +111,7 @@ func (principal ciPrincipal) Name(_ context.Context) string { func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { - claimsTemplates := principal.ClaimsMetadata.ClaimsTemplates + claimsTemplates := principal.ClaimsMetadata.ExtensionTemplates defaults := principal.ClaimsMetadata.DefaultTemplateValues claims, err := getTokenClaims(principal.Token) if err != nil { diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 2d70e5959..4e407d82b 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -38,7 +38,7 @@ func TestWorkflowPrincipalFromIDToken(t *testing.T) { `Github workflow challenge should have all Github workflow extensions and issuer set`: { ExpectedPrincipal: ciPrincipal{ ClaimsMetadata: config.IssuerMetadata{ - ClaimsTemplates: certificate.Extensions{ + ExtensionTemplates: certificate.Extensions{ Issuer: "issuer", GithubWorkflowTrigger: "event_name", GithubWorkflowSHA: "sha", @@ -237,7 +237,7 @@ func TestEmbed(t *testing.T) { }, Principal: ciPrincipal{ ClaimsMetadata: config.IssuerMetadata{ - ClaimsTemplates: certificate.Extensions{ + ExtensionTemplates: certificate.Extensions{ GithubWorkflowTrigger: "event_name", GithubWorkflowSHA: "sha", GithubWorkflowName: "workflow", From 611d5c4fe5d40d0b5f1ad6137aa198e9d66bdade Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Sun, 30 Jun 2024 18:31:31 +0000 Subject: [PATCH 39/47] update for using reflect instead of mapstructure Signed-off-by: Javan lacerda --- go.mod | 2 +- pkg/config/config.go | 15 +++++------ pkg/identity/ciprovider/principal.go | 38 ++++++++++------------------ 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index bf517cd26..8caa0c7bf 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/hashicorp/golang-lru v1.0.2 github.com/magiconair/properties v1.8.7 - github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.54.0 @@ -111,6 +110,7 @@ require ( github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect diff --git a/pkg/config/config.go b/pkg/config/config.go index c2d2c36cd..58a898fc3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -32,7 +32,6 @@ import ( "github.com/coreos/go-oidc/v3/oidc" lru "github.com/hashicorp/golang-lru" - "github.com/mitchellh/mapstructure" "github.com/sigstore/fulcio/pkg/certificate" fulciogrpc "github.com/sigstore/fulcio/pkg/generated/protobuf" "github.com/sigstore/fulcio/pkg/log" @@ -470,18 +469,16 @@ func validateCIIssuerMetadata(fulcioConfig *FulcioConfig) error { } for _, ciIssuerMetadata := range fulcioConfig.CIIssuerMetadata { - claimsTemplates := make(map[string]interface{}) - err := mapstructure.Decode(ciIssuerMetadata.ExtensionTemplates, &claimsTemplates) - if err != nil { - return err - } - for _, temp := range claimsTemplates { - err := checkParse(temp) + v := reflect.Indirect(reflect.ValueOf(&ciIssuerMetadata.ExtensionTemplates)) + for i := 0; i < v.NumField(); i++ { + s := v.Field(i).String() + err := checkParse(s) if err != nil { return err } } - err = checkParse(ciIssuerMetadata.SubjectAlternativeNameTemplate) + + err := checkParse(ciIssuerMetadata.SubjectAlternativeNameTemplate) if err != nil { return err } diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index f45068e42..00a963bb9 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -21,11 +21,10 @@ import ( "fmt" "html/template" "net/url" + "reflect" "strings" "github.com/coreos/go-oidc/v3/oidc" - "github.com/mitchellh/mapstructure" - "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/identity" ) @@ -127,33 +126,24 @@ func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) er } uris := []*url.URL{sanURL} cert.URIs = uris - mapExtensionsForTemplate := make(map[string]interface{}) - err = mapstructure.Decode(claimsTemplates, &mapExtensionsForTemplate) - if err != nil { - return err - } - - for k, v := range mapValuesToString(mapExtensionsForTemplate) { - // It avoids to try applying template or replace for a empty string. - if v != "" { - mapExtensionsForTemplate[k], err = applyTemplateOrReplace(v, claims, defaults) - if err != nil { - return err - } + v := reflect.Indirect(reflect.ValueOf(&claimsTemplates)) + for i := 0; i < v.NumField(); i++ { + s := v.Field(i).String() // value of each field, e.g the template string + if s == "" { + continue } + extValue, err := applyTemplateOrReplace(s, claims, defaults) + if err != nil { + return err + } + v.Field(i).SetString(extValue) } - ext := &certificate.Extensions{ - Issuer: principal.Token.Issuer, - } - err = mapstructure.Decode(mapExtensionsForTemplate, &ext) - if err != nil { - return err - } + // Guarantees to set the extension issuer as the token issuer // regardless of whether this field has been set before - ext.Issuer = principal.Token.Issuer + claimsTemplates.Issuer = principal.Token.Issuer // Embed additional information into custom extensions - cert.ExtraExtensions, err = ext.Render() + cert.ExtraExtensions, err = claimsTemplates.Render() if err != nil { return err } From 1a481ebd3146c25bd2f7001f49d813175357616e Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 1 Jul 2024 14:46:09 +0000 Subject: [PATCH 40/47] adding test for ciprovider API Signed-off-by: Javan lacerda --- pkg/config/config.go | 4 +- pkg/identity/ciprovider/principal.go | 4 +- pkg/server/grpc_server_test.go | 184 ++++++++++++++++++++++++++- 3 files changed, 186 insertions(+), 6 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 58a898fc3..ed77872f3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -462,9 +462,9 @@ func FromContext(ctx context.Context) *FulcioConfig { // We should check it during the service bootstrap to avoid errors further func validateCIIssuerMetadata(fulcioConfig *FulcioConfig) error { - checkParse := func(temp interface{}) error { + checkParse := func(temp string) error { t := template.New("").Option("missingkey=error") - _, err := t.Parse(temp.(string)) + _, err := t.Parse(temp) return err } diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 00a963bb9..55d2f106d 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -97,7 +97,6 @@ func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (ide if !ok { return nil, fmt.Errorf("configuration can not be loaded for issuer %v", token.Issuer) } - return ciPrincipal{ token, cfg.CIIssuerMetadata[issuerCfg.CIProvider], @@ -127,9 +126,10 @@ func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) er uris := []*url.URL{sanURL} cert.URIs = uris v := reflect.Indirect(reflect.ValueOf(&claimsTemplates)) + vType := v.Type() for i := 0; i < v.NumField(); i++ { s := v.Field(i).String() // value of each field, e.g the template string - if s == "" { + if s == "" || vType.Field(i).Name == "Issuer" { continue } extValue, err := applyTemplateOrReplace(s, claims, defaults) diff --git a/pkg/server/grpc_server_test.go b/pkg/server/grpc_server_test.go index 999083160..56f05b71a 100644 --- a/pkg/server/grpc_server_test.go +++ b/pkg/server/grpc_server_test.go @@ -53,6 +53,7 @@ import ( "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/ca/ephemeralca" + "github.com/sigstore/fulcio/pkg/certificate" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/generated/protobuf" "github.com/sigstore/fulcio/pkg/identity" @@ -199,6 +200,7 @@ func TestGetConfiguration(t *testing.T) { _, gitLabIssuer := newOIDCIssuer(t) _, codefreshIssuer := newOIDCIssuer(t) _, chainguardIssuer := newOIDCIssuer(t) + _, ciProviderIssuer := newOIDCIssuer(t) issuerDomain, err := url.Parse(usernameIssuer) if err != nil { @@ -254,6 +256,11 @@ func TestGetConfiguration(t *testing.T) { "IssuerURL": %q, "ClientID": "sigstore", "Type": "chainguard-identity" + }, + %q: { + "IssuerURL": %q, + "ClientID": "sigstore", + "Type": "ci-provider" } }, "MetaIssuers": { @@ -271,6 +278,7 @@ func TestGetConfiguration(t *testing.T) { gitLabIssuer, gitLabIssuer, codefreshIssuer, codefreshIssuer, chainguardIssuer, chainguardIssuer, + ciProviderIssuer, ciProviderIssuer, k8sIssuer))) if err != nil { t.Fatalf("config.Read() = %v", err) @@ -291,7 +299,7 @@ func TestGetConfiguration(t *testing.T) { t.Fatal("GetConfiguration failed", err) } - if got, want := len(config.Issuers), 10; got != want { + if got, want := len(config.Issuers), 11; got != want { t.Fatalf("expected %d issuers, got %d", want, got) } @@ -299,7 +307,7 @@ func TestGetConfiguration(t *testing.T) { emailIssuer: true, spiffeIssuer: true, uriIssuer: true, usernameIssuer: true, k8sIssuer: true, gitHubIssuer: true, buildkiteIssuer: true, gitLabIssuer: true, codefreshIssuer: true, - chainguardIssuer: true, + chainguardIssuer: true, ciProviderIssuer: true, } for _, iss := range config.Issuers { var issURL string @@ -1123,6 +1131,178 @@ func TestAPIWithGitHub(t *testing.T) { } } +// Tests API for CiProvider subject types +func TestAPIWithCiProvider(t *testing.T) { + ciProviderSigner, ciProviderIssuer := newOIDCIssuer(t) + // Create a FulcioConfig that supports these issuers. + cfg, err := config.Read([]byte(fmt.Sprintf(`{ + "OIDCIssuers": { + %q: { + "IssuerURL": %q, + "ClientID": "sigstore", + "Type": "ci-provider", + "CIProvider": "github-workflow" + } + } + }`, ciProviderIssuer, ciProviderIssuer))) + if err != nil { + t.Fatalf("config.Read() = %v", err) + } + claims := githubClaims{ + JobWorkflowRef: "job/workflow/ref", + Sha: "sha", + EventName: "trigger", + Repository: "sigstore/fulcio", + Workflow: "workflow", + Ref: "refs/heads/main", + JobWorkflowSha: "example-sha", + RunnerEnvironment: "cloud-hosted", + RepositoryID: "12345", + RepositoryOwner: "username", + RepositoryOwnerID: "345", + RepositoryVisibility: "public", + WorkflowRef: "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + WorkflowSha: "example-sha-other", + RunID: "42", + RunAttempt: "1", + } + githubSubject := fmt.Sprintf("repo:%s:ref:%s", claims.Repository, claims.Ref) + // Create an OIDC token using this issuer's signer. + tok, err := jwt.Signed(ciProviderSigner).Claims(jwt.Claims{ + Issuer: ciProviderIssuer, + IssuedAt: jwt.NewNumericDate(time.Now()), + Expiry: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), + Subject: githubSubject, + Audience: jwt.Audience{"sigstore"}, + }).Claims(&claims).Serialize() + if err != nil { + t.Fatalf("Serialize() = %v", err) + } + + ctClient, eca := createCA(cfg, t) + ctx := context.Background() + cfg.CIIssuerMetadata = make(map[string]config.IssuerMetadata) + cfg.CIIssuerMetadata["github-workflow"] = config.IssuerMetadata{ + ExtensionTemplates: certificate.Extensions{ + Issuer: "issuer", + GithubWorkflowTrigger: "event_name", + GithubWorkflowSHA: "sha", + GithubWorkflowName: "workflow", + GithubWorkflowRepository: "repository", + GithubWorkflowRef: "ref", + BuildSignerURI: "{{ .url }}/{{ .job_workflow_ref }}", + BuildSignerDigest: "job_workflow_sha", + RunnerEnvironment: "runner_environment", + SourceRepositoryURI: "{{ .url }}/{{ .repository }}", + SourceRepositoryDigest: "sha", + SourceRepositoryRef: "ref", + SourceRepositoryIdentifier: "repository_id", + SourceRepositoryOwnerURI: "{{ .url }}/{{ .repository_owner }}", + SourceRepositoryOwnerIdentifier: "repository_owner_id", + BuildConfigURI: "{{ .url }}/{{ .workflow_ref }}", + BuildConfigDigest: "workflow_sha", + BuildTrigger: "event_name", + RunInvocationURI: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", + SourceRepositoryVisibilityAtSigning: "repository_visibility", + }, + DefaultTemplateValues: map[string]string{ + "url": "https://github.com", + }, + SubjectAlternativeNameTemplate: "{{.url}}/{{.job_workflow_ref}}", + } + + server, conn := setupGRPCForTest(t, cfg, ctClient, eca) + defer func() { + server.Stop() + conn.Close() + }() + client := protobuf.NewCAClient(conn) + pubBytes, proof := generateKeyAndProof(githubSubject, t) + // Hit the API to have it sign our certificate. + resp, err := client.CreateSigningCertificate(ctx, &protobuf.CreateSigningCertificateRequest{ + Credentials: &protobuf.Credentials{ + Credentials: &protobuf.Credentials_OidcIdentityToken{ + OidcIdentityToken: tok, + }, + }, + Key: &protobuf.CreateSigningCertificateRequest_PublicKeyRequest{ + PublicKeyRequest: &protobuf.PublicKeyRequest{ + PublicKey: &protobuf.PublicKey{ + Content: pubBytes, + }, + ProofOfPossession: proof, + }, + }, + }) + if err != nil { + t.Fatalf("SigningCert() = %v", err) + } + leafCert := verifyResponse(resp, eca, ciProviderIssuer, t) + // Expect URI values + if len(leafCert.URIs) != 1 { + t.Fatalf("unexpected length of leaf certificate URIs, expected 1, got %d", len(leafCert.URIs)) + } + githubURL := fmt.Sprintf("https://github.com/%s", claims.JobWorkflowRef) + githubURI, err := url.Parse(githubURL) + if err != nil { + t.Fatalf("failed to parse expected url") + } + if *leafCert.URIs[0] != *githubURI { + t.Fatalf("URIs do not match: Expected %v, got %v", githubURI, leafCert.URIs[0]) + } + // Verify custom OID values + deprecatedExpectedExts := map[int]string{ + 2: claims.EventName, + 3: claims.Sha, + 4: claims.Workflow, + 5: claims.Repository, + 6: claims.Ref, + } + for o, value := range deprecatedExpectedExts { + ext, found := findCustomExtension(leafCert, asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, o}) + if !found { + t.Fatalf("expected extension in custom OID 1.3.6.1.4.1.57264.1.%d", o) + } + if string(ext.Value) != value { + t.Fatalf("unexpected extension value, expected %s, got %s", value, ext.Value) + } + } + url := "https://github.com/" + expectedExts := map[int]string{ + 9: url + claims.JobWorkflowRef, + 10: claims.JobWorkflowSha, + 11: claims.RunnerEnvironment, + 12: url + claims.Repository, + 13: claims.Sha, + 14: claims.Ref, + 15: claims.RepositoryID, + 16: url + claims.RepositoryOwner, + 17: claims.RepositoryOwnerID, + 18: url + claims.WorkflowRef, + 19: claims.WorkflowSha, + 20: claims.EventName, + 21: url + claims.Repository + "/actions/runs/" + claims.RunID + "/attempts/" + claims.RunAttempt, + 22: claims.RepositoryVisibility, + } + for o, value := range expectedExts { + ext, found := findCustomExtension(leafCert, asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, o}) + if !found { + t.Fatalf("expected extension in custom OID 1.3.6.1.4.1.57264.1.%d", o) + } + var extValue string + rest, err := asn1.Unmarshal(ext.Value, &extValue) + if err != nil { + t.Fatalf("error unmarshalling extension: :%v", err) + } + if len(rest) != 0 { + t.Fatal("error unmarshalling extension, rest is not 0") + } + if string(extValue) != value { + t.Fatalf("unexpected extension value, expected %s, got %s", value, extValue) + } + } +} + // gitlabClaims holds the additional JWT claims for GitLab OIDC tokens type gitlabClaims struct { ProjectPath string `json:"project_path"` From 8ab3f2a837ab858c442c33336c2f9eafc78c2775 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 1 Jul 2024 15:00:51 +0000 Subject: [PATCH 41/47] adding json tags for Extensions Signed-off-by: Javan lacerda --- pkg/certificate/extensions.go | 38 ++++++++++++++-------------- pkg/identity/ciprovider/principal.go | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pkg/certificate/extensions.go b/pkg/certificate/extensions.go index c07670336..584aac971 100644 --- a/pkg/certificate/extensions.go +++ b/pkg/certificate/extensions.go @@ -69,69 +69,69 @@ type Extensions struct { // Deprecated // Triggering event of the Github Workflow. Matches the `event_name` claim of ID // tokens from Github Actions - GithubWorkflowTrigger string `yaml:"github-workflow-trigger,omitempty"` // OID 1.3.6.1.4.1.57264.1.2 + GithubWorkflowTrigger string `json:"GithubWorkflowTrigger,omitempty" yaml:"github-workflow-trigger,omitempty"` // OID 1.3.6.1.4.1.57264.1.2 // Deprecated // SHA of git commit being built in Github Actions. Matches the `sha` claim of ID // tokens from Github Actions - GithubWorkflowSHA string `yaml:"github-workflow-sha,omitempty"` // OID 1.3.6.1.4.1.57264.1.3 + GithubWorkflowSHA string `json:"GithubWorkflowSHA,omitempty" yaml:"github-workflow-sha,omitempty"` // OID 1.3.6.1.4.1.57264.1.3 // Deprecated // Name of Github Actions Workflow. Matches the `workflow` claim of the ID // tokens from Github Actions - GithubWorkflowName string `yaml:"github-workflow-name,omitempty"` // OID 1.3.6.1.4.1.57264.1.4 + GithubWorkflowName string `json:"GithubWorkflowName,omitempty" yaml:"github-workflow-name,omitempty"` // OID 1.3.6.1.4.1.57264.1.4 // Deprecated // Repository of the Github Actions Workflow. Matches the `repository` claim of the ID // tokens from Github Actions - GithubWorkflowRepository string `yaml:"github-workflow-repository,omitempty"` // OID 1.3.6.1.4.1.57264.1.5 + GithubWorkflowRepository string `json:"GithubWorkflowRepository,omitempty" yaml:"github-workflow-repository,omitempty"` // OID 1.3.6.1.4.1.57264.1.5 // Deprecated // Git Ref of the Github Actions Workflow. Matches the `ref` claim of the ID tokens // from Github Actions - GithubWorkflowRef string `yaml:"github-workflow-ref,omitempty"` // 1.3.6.1.4.1.57264.1.6 + GithubWorkflowRef string `json:"GithubWorkflowRef,omitempty" yaml:"github-workflow-ref,omitempty"` // 1.3.6.1.4.1.57264.1.6 // Reference to specific build instructions that are responsible for signing. - BuildSignerURI string `yaml:"build-signer-uri,omitempty"` // 1.3.6.1.4.1.57264.1.9 + BuildSignerURI string `json:"BuildSignerURI,omitempty" yaml:"build-signer-uri,omitempty"` // 1.3.6.1.4.1.57264.1.9 // Immutable reference to the specific version of the build instructions that is responsible for signing. - BuildSignerDigest string `yaml:"build-signer-digest,omitempty"` // 1.3.6.1.4.1.57264.1.10 + BuildSignerDigest string `json:"BuildSignerDigest,omitempty" yaml:"build-signer-digest,omitempty"` // 1.3.6.1.4.1.57264.1.10 // Specifies whether the build took place in platform-hosted cloud infrastructure or customer/self-hosted infrastructure. - RunnerEnvironment string `yaml:"runner-environment,omitempty"` // 1.3.6.1.4.1.57264.1.11 + RunnerEnvironment string `json:"RunnerEnvironment,omitempty" yaml:"runner-environment,omitempty"` // 1.3.6.1.4.1.57264.1.11 // Source repository URL that the build was based on. - SourceRepositoryURI string `yaml:"source-repository-uri,omitempty"` // 1.3.6.1.4.1.57264.1.12 + SourceRepositoryURI string `json:"SourceRepositoryURI,omitempty" yaml:"source-repository-uri,omitempty"` // 1.3.6.1.4.1.57264.1.12 // Immutable reference to a specific version of the source code that the build was based upon. - SourceRepositoryDigest string `yaml:"source-repository-digest,omitempty"` // 1.3.6.1.4.1.57264.1.13 + SourceRepositoryDigest string `json:"SourceRepositoryDigest,omitempty" yaml:"source-repository-digest,omitempty"` // 1.3.6.1.4.1.57264.1.13 // Source Repository Ref that the build run was based upon. - SourceRepositoryRef string `yaml:"source-repository-ref,omitempty"` // 1.3.6.1.4.1.57264.1.14 + SourceRepositoryRef string `json:"SourceRepositoryRef,omitempty" yaml:"source-repository-ref,omitempty"` // 1.3.6.1.4.1.57264.1.14 // Immutable identifier for the source repository the workflow was based upon. - SourceRepositoryIdentifier string `yaml:"source-repository-identifier,omitempty"` // 1.3.6.1.4.1.57264.1.15 + SourceRepositoryIdentifier string `json:"SourceRepositoryIdentifier,omitempty" yaml:"source-repository-identifier,omitempty"` // 1.3.6.1.4.1.57264.1.15 // Source repository owner URL of the owner of the source repository that the build was based on. - SourceRepositoryOwnerURI string `yaml:"source-repository-owner-uri,omitempty"` // 1.3.6.1.4.1.57264.1.16 + SourceRepositoryOwnerURI string `json:"SourceRepositoryOwnerURI,omitempty" yaml:"source-repository-owner-uri,omitempty"` // 1.3.6.1.4.1.57264.1.16 // Immutable identifier for the owner of the source repository that the workflow was based upon. - SourceRepositoryOwnerIdentifier string `yaml:"source-repository-owner-identifier,omitempty"` // 1.3.6.1.4.1.57264.1.17 + SourceRepositoryOwnerIdentifier string `json:"SourceRepositoryOwnerIdentifier,omitempty" yaml:"source-repository-owner-identifier,omitempty"` // 1.3.6.1.4.1.57264.1.17 // Build Config URL to the top-level/initiating build instructions. - BuildConfigURI string `yaml:"build-config-uri,omitempty"` // 1.3.6.1.4.1.57264.1.18 + BuildConfigURI string `json:"BuildConfigURI,omitempty" yaml:"build-config-uri,omitempty"` // 1.3.6.1.4.1.57264.1.18 // Immutable reference to the specific version of the top-level/initiating build instructions. - BuildConfigDigest string `yaml:"build-config-digest,omitempty"` // 1.3.6.1.4.1.57264.1.19 + BuildConfigDigest string `json:"BuildConfigDigest,omitempty" yaml:"build-config-digest,omitempty"` // 1.3.6.1.4.1.57264.1.19 // Event or action that initiated the build. - BuildTrigger string `yaml:"build-trigger,omitempty"` // 1.3.6.1.4.1.57264.1.20 + BuildTrigger string `json:"BuildTrigger,omitempty" yaml:"build-trigger,omitempty"` // 1.3.6.1.4.1.57264.1.20 // Run Invocation URL to uniquely identify the build execution. - RunInvocationURI string `yaml:"run-invocation-uri,omitempty"` // 1.3.6.1.4.1.57264.1.21 + RunInvocationURI string `json:"RunInvocationURI,omitempty" yaml:"run-invocation-uri,omitempty"` // 1.3.6.1.4.1.57264.1.21 // Source repository visibility at the time of signing the certificate. - SourceRepositoryVisibilityAtSigning string `yaml:"source-repository-visibility-at-signing,omitempty"` // 1.3.6.1.4.1.57264.1.22 + SourceRepositoryVisibilityAtSigning string `json:"SourceRepositoryVisibilityAtSigning,omitempty" yaml:"source-repository-visibility-at-signing,omitempty"` // 1.3.6.1.4.1.57264.1.22 } func (e Extensions) Render() ([]pkix.Extension, error) { diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 55d2f106d..44dc0ebc3 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -32,7 +32,7 @@ import ( func mapValuesToString(claims map[string]interface{}) map[string]string { newMap := make(map[string]string) for k, v := range claims { - newMap[k] = fmt.Sprintf("%v", v) + newMap[k] = fmt.Sprintf("%s", v) } return newMap } From 31f0318fc595da737ff2835f65c655c2613aa439 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Tue, 2 Jul 2024 18:19:34 +0000 Subject: [PATCH 42/47] adding CIIssuerMetadata json and yaml mapping Signed-off-by: Javan lacerda --- pkg/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index ed77872f3..d0feb95ea 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -66,7 +66,7 @@ type FulcioConfig struct { // The CI provider has a generic logic for ci providers, this metadata is used // to define the right behavior for each ci provider that is defined // on the configuration file - CIIssuerMetadata map[string]IssuerMetadata + CIIssuerMetadata map[string]IssuerMetadata `json:"CIIssuerMetadata,omitempty" yaml:"ci-issuer-metadata,omitempty"` // verifiers is a fixed mapping from our OIDCIssuers to their OIDC verifiers. verifiers map[string][]*verifierWithConfig From b9cc94e42b3e69132c273b482236e1e6ce7c784f Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 3 Jul 2024 20:43:51 +0000 Subject: [PATCH 43/47] fixes Signed-off-by: Javan lacerda --- pkg/config/config.go | 2 +- pkg/config/config_network_test.go | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index d0feb95ea..4cbb491d9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -469,7 +469,7 @@ func validateCIIssuerMetadata(fulcioConfig *FulcioConfig) error { } for _, ciIssuerMetadata := range fulcioConfig.CIIssuerMetadata { - v := reflect.Indirect(reflect.ValueOf(&ciIssuerMetadata.ExtensionTemplates)) + v := reflect.ValueOf(ciIssuerMetadata.ExtensionTemplates) for i := 0; i < v.NumField(); i++ { s := v.Field(i).String() err := checkParse(s) diff --git a/pkg/config/config_network_test.go b/pkg/config/config_network_test.go index 093b33df9..4e00720bf 100644 --- a/pkg/config/config_network_test.go +++ b/pkg/config/config_network_test.go @@ -82,9 +82,10 @@ func TestParseTemplate(t *testing.T) { fulcioConfig := &FulcioConfig{ CIIssuerMetadata: ciissuerMetadata, } + // BuildTrigger as a invalid template should raise an error err := validateCIIssuerMetadata(fulcioConfig) if err == nil { - t.Error("It should raise an error") + t.Error("invalid template should raise an error") } ciissuerMetadata["github"] = IssuerMetadata{ ExtensionTemplates: certificate.Extensions{ @@ -94,9 +95,10 @@ func TestParseTemplate(t *testing.T) { fulcioConfig = &FulcioConfig{ CIIssuerMetadata: ciissuerMetadata, } + // BuildTrigger as a valid template shouldn't raise an error err = validateCIIssuerMetadata(fulcioConfig) if err != nil { - t.Error("It shouldn't raise an error") + t.Error("valid template shouldn't raise an error, error: %w", err) } ciissuerMetadata["github"] = IssuerMetadata{ SubjectAlternativeNameTemplate: invalidTemplate, @@ -104,9 +106,21 @@ func TestParseTemplate(t *testing.T) { fulcioConfig = &FulcioConfig{ CIIssuerMetadata: ciissuerMetadata, } + // A SAN as a invalid template should raise an error err = validateCIIssuerMetadata(fulcioConfig) if err == nil { - t.Error("It should raise an error") + t.Error("invalid SAN should raise an error") + } + ciissuerMetadata["github"] = IssuerMetadata{ + SubjectAlternativeNameTemplate: invalidTemplate, + } + fulcioConfig = &FulcioConfig{ + CIIssuerMetadata: ciissuerMetadata, + } + // A SAN as a valid template should raise an error + err = validateCIIssuerMetadata(fulcioConfig) + if err == nil { + t.Error("valid SAN shouldn't raise an error") } } From 42f803c0f8ae50f6ea8e8434b97c4294c2da9cc0 Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 3 Jul 2024 21:22:29 +0000 Subject: [PATCH 44/47] adding test for applyTemplateOrReplace Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/principal.go | 4 ++ pkg/identity/ciprovider/principal_test.go | 56 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index 44dc0ebc3..f9820b3f3 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -126,9 +126,13 @@ func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) er uris := []*url.URL{sanURL} cert.URIs = uris v := reflect.Indirect(reflect.ValueOf(&claimsTemplates)) + // Type of the reflect value is needed as it is necessary + // for getting the field name. vType := v.Type() for i := 0; i < v.NumField(); i++ { s := v.Field(i).String() // value of each field, e.g the template string + // We check the field name to avoid to apply the template for the Issuer + // Issuer field should always come from the token issuer if s == "" || vType.Field(i).Name == "Issuer" { continue } diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 4e407d82b..2f04ab93d 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -206,6 +206,62 @@ func TestName(t *testing.T) { } } +func TestApplyTemplateOrReplace(t *testing.T) { + + tokenClaims := map[string]string{ + "aud": "sigstore", + "event_name": "push", + "exp": "0", + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + "job_workflow_sha": "example-sha", + "ref": "refs/heads/main", + "repository": "sigstore/fulcio", + "repository_id": "12345", + "repository_owner": "username", + "repository_owner_id": "345", + "repository_visibility": "public", + "run_attempt": "1", + "run_id": "42", + "runner_environment": "cloud-hosted", + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sub": "repo:sigstore/fulcio:ref:refs/heads/main", + "workflow": "foo", + "workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main", + "workflow_sha": "example-sha-other", + } + issuerMetadata := map[string]string{ + "url": "https://github.com", + } + + tests := map[string]struct { + Template string + ExpectedResult string + ExpectErr bool + }{ + `Valid template`: { + Template: "{{ .url }}/{{ .repository }}/actions/runs/{{ .run_id }}/attempts/{{ .run_attempt }}", + ExpectedResult: "https://github.com/sigstore/fulcio/actions/runs/42/attempts/1", + ExpectErr: false, + }, + // Add more tests for edge cases + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + res, err := applyTemplateOrReplace(test.Template, tokenClaims, issuerMetadata) + if res != test.ExpectedResult { + t.Errorf("expected result don't matches: Expected %s, received: %s", + test.ExpectedResult, res) + } + if (err == nil) == test.ExpectErr { + t.Errorf("should raise an error don't matches: Expected %v, received: %v", + test.ExpectErr, err) + } + }) + } +} + func TestEmbed(t *testing.T) { tests := map[string]struct { WantFacts map[string]func(x509.Certificate) error From 478c37a6fdab103018392831d1befa4ecdd6c37a Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Wed, 3 Jul 2024 21:26:56 +0000 Subject: [PATCH 45/47] omit CIIIssuerMetadata Signed-off-by: Javan lacerda --- config/fulcio-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/fulcio-config.yaml b/config/fulcio-config.yaml index 9df7c7cbb..0f7a0aded 100644 --- a/config/fulcio-config.yaml +++ b/config/fulcio-config.yaml @@ -106,8 +106,7 @@ data: "ClientID": "sigstore", "Type": "github-workflow" } - }, - "CIIssuerMetadata": null + } } server.yaml: |- host: 0.0.0.0 From 7455b8782e90f97474bf5a1daefaf330e7c9373b Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Thu, 4 Jul 2024 13:56:14 +0000 Subject: [PATCH 46/47] adding tests case for ApplyTemplateOrReplace Signed-off-by: Javan lacerda --- pkg/config/config.go | 6 ++--- pkg/identity/ciprovider/principal_test.go | 30 ++++++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 4cbb491d9..6b5f01f9a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -77,16 +77,16 @@ type FulcioConfig struct { type IssuerMetadata struct { // Defaults contains key-value pairs that can be used for filling the templates from ExtensionTemplates // If a key cannot be found on the token claims, the template will use the defaults - DefaultTemplateValues map[string]string + DefaultTemplateValues map[string]string `json:"DefaultTemplateValues,omitempty" yaml:"default-template-values,omitempty"` // ExtensionTemplates contains a mapping between certificate extension and token claim // Provide either strings following https://pkg.go.dev/text/template syntax, // e.g "{{ .url }}/{{ .repository }}" // or non-templated strings with token claim keys to be replaced, // e.g "job_workflow_sha" - ExtensionTemplates certificate.Extensions + ExtensionTemplates certificate.Extensions `json:"ExtensionTemplates,omitempty" yaml:"extension-templates,omitempty"` // Template for the Subject Alternative Name extension // It's typically the same value as Build Signer URI - SubjectAlternativeNameTemplate string + SubjectAlternativeNameTemplate string `json:"SubjectAlternativeNameTemplate,omitempty" yaml:"subject-alternative-name-template,omitempty"` } type OIDCIssuer struct { diff --git a/pkg/identity/ciprovider/principal_test.go b/pkg/identity/ciprovider/principal_test.go index 2f04ab93d..aa387f995 100644 --- a/pkg/identity/ciprovider/principal_test.go +++ b/pkg/identity/ciprovider/principal_test.go @@ -244,7 +244,31 @@ func TestApplyTemplateOrReplace(t *testing.T) { ExpectedResult: "https://github.com/sigstore/fulcio/actions/runs/42/attempts/1", ExpectErr: false, }, - // Add more tests for edge cases + `Empty template`: { + Template: "{{}}", + ExpectedResult: "", + ExpectErr: true, + }, + `Missing key for template`: { + Template: "{{ .foo }}", + ExpectedResult: "", + ExpectErr: true, + }, + `Empty string`: { + Template: "", + ExpectedResult: "", + ExpectErr: true, + }, + `Replaceable string`: { + Template: "job_workflow_ref", + ExpectedResult: "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main", + ExpectErr: false, + }, + `Missing string`: { + Template: "bar", + ExpectedResult: "", + ExpectErr: true, + }, } for name, test := range tests { @@ -254,9 +278,9 @@ func TestApplyTemplateOrReplace(t *testing.T) { t.Errorf("expected result don't matches: Expected %s, received: %s", test.ExpectedResult, res) } - if (err == nil) == test.ExpectErr { + if (err != nil) != test.ExpectErr { t.Errorf("should raise an error don't matches: Expected %v, received: %v", - test.ExpectErr, err) + test.ExpectErr, err != nil) } }) } From e248f434dd2c389faf19ead8a046df87267e997b Mon Sep 17 00:00:00 2001 From: Javan lacerda Date: Mon, 8 Jul 2024 17:47:45 +0000 Subject: [PATCH 47/47] update code for using Elem() instead of indirect Signed-off-by: Javan lacerda --- pkg/identity/ciprovider/principal.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/identity/ciprovider/principal.go b/pkg/identity/ciprovider/principal.go index f9820b3f3..fb94df3bd 100644 --- a/pkg/identity/ciprovider/principal.go +++ b/pkg/identity/ciprovider/principal.go @@ -125,7 +125,9 @@ func (principal ciPrincipal) Embed(_ context.Context, cert *x509.Certificate) er } uris := []*url.URL{sanURL} cert.URIs = uris - v := reflect.Indirect(reflect.ValueOf(&claimsTemplates)) + // We should use value.Elem() here as we need a + // addressable reference of the templates for applying the SetString(). + v := reflect.ValueOf(&claimsTemplates).Elem() // Type of the reflect value is needed as it is necessary // for getting the field name. vType := v.Type()