diff --git a/pkg/config/config.go b/pkg/config/config.go index c0eaf2a27..900ecef26 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -34,6 +34,7 @@ import ( fulciogrpc "github.com/sigstore/fulcio/pkg/generated/protobuf" "github.com/sigstore/fulcio/pkg/log" "github.com/spiffe/go-spiffe/v2/spiffeid" + "gopkg.in/yaml.v3" ) const defaultOIDCDiscoveryTimeout = 10 * time.Second @@ -48,7 +49,7 @@ type verifierWithConfig struct { } type FulcioConfig struct { - OIDCIssuers map[string]OIDCIssuer `json:"OIDCIssuers,omitempty"` + OIDCIssuers map[string]OIDCIssuer `json:"OIDCIssuers,omitempty" yaml:"oidc-issuers,omitempty"` // A meta issuer has a templated URL of the form: // https://oidc.eks.*.amazonaws.com/id/* @@ -57,7 +58,7 @@ type FulcioConfig struct { // other special characters) Some examples we want to match: // * https://oidc.eks.us-west-2.amazonaws.com/id/B02C93B6A2D30341AD01E1B6D48164CB // * https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us-west1-b/clusters/tenant-cluster - MetaIssuers map[string]OIDCIssuer `json:"MetaIssuers,omitempty"` + MetaIssuers map[string]OIDCIssuer `json:"MetaIssuers,omitempty" yaml:"meta-issuers,omitempty"` // verifiers is a fixed mapping from our OIDCIssuers to their OIDC verifiers. verifiers map[string][]*verifierWithConfig @@ -67,24 +68,24 @@ type FulcioConfig struct { type OIDCIssuer struct { // The expected issuer of an OIDC token - IssuerURL string `json:"IssuerURL,omitempty"` + IssuerURL string `json:"IssuerURL,omitempty" yaml:"issuer-url,omitempty"` // The expected client ID of the OIDC token - ClientID string `json:"ClientID"` + ClientID string `json:"ClientID" yaml:"client-id,omitempty"` // Used to determine the subject of the certificate and if additional // certificate values are needed - Type IssuerType `json:"Type"` + Type IssuerType `json:"Type" yaml:"type,omitempty"` // Optional, if the issuer is in a different claim in the OIDC token - IssuerClaim string `json:"IssuerClaim,omitempty"` + IssuerClaim string `json:"IssuerClaim,omitempty" yaml:"issuer-claim,omitempty"` // The domain that must be present in the subject for 'uri' issuer types // Also used to create an email for 'username' issuer types - SubjectDomain string `json:"SubjectDomain,omitempty"` + SubjectDomain string `json:"SubjectDomain,omitempty" yaml:"subject-domain,omitempty"` // SPIFFETrustDomain specifies the trust domain that 'spiffe' issuer types // issue ID tokens for. Tokens with a different trust domain will be // rejected. - SPIFFETrustDomain string `json:"SPIFFETrustDomain,omitempty"` + SPIFFETrustDomain string `json:"SPIFFETrustDomain,omitempty" yaml:"spiffe-trust-domain,omitempty"` // Optional, the challenge claim expected for the issuer // Set if using a custom issuer - ChallengeClaim string `json:"ChallengeClaim,omitempty"` + ChallengeClaim string `json:"ChallengeClaim,omitempty" yaml:"challenge-claim,omitempty"` } func metaRegex(issuer string) (*regexp.Regexp, error) { @@ -287,7 +288,9 @@ const ( func parseConfig(b []byte) (cfg *FulcioConfig, err error) { cfg = &FulcioConfig{} if err := json.Unmarshal(b, cfg); err != nil { - return nil, fmt.Errorf("unmarshal: %w", err) + if err = yaml.Unmarshal(b, cfg); err != nil { + return nil, fmt.Errorf("unmarshal: %w", err) + } } return cfg, nil diff --git a/pkg/server/grpc_server_test.go b/pkg/server/grpc_server_test.go index 94ad2da9c..cf7991eec 100644 --- a/pkg/server/grpc_server_test.go +++ b/pkg/server/grpc_server_test.go @@ -322,6 +322,146 @@ func TestGetConfiguration(t *testing.T) { } } +// Tests GetConfigurationFromYaml API +func TestGetConfigurationFromYaml(t *testing.T) { + _, emailIssuer := newOIDCIssuer(t) + _, spiffeIssuer := newOIDCIssuer(t) + _, uriIssuer := newOIDCIssuer(t) + _, usernameIssuer := newOIDCIssuer(t) + _, k8sIssuer := newOIDCIssuer(t) + _, buildkiteIssuer := newOIDCIssuer(t) + _, gitHubIssuer := newOIDCIssuer(t) + _, gitLabIssuer := newOIDCIssuer(t) + _, codefreshIssuer := newOIDCIssuer(t) + + issuerDomain, err := url.Parse(usernameIssuer) + if err != nil { + t.Fatal("issuer URL could not be parsed", err) + } + + yamlBytes := []byte(fmt.Sprintf(` + oidc-issuers: + %v: + issuer-url: %q + client-id: sigstore + type: spiffe + spiffe-trust-domain: example.com + %v: + issuer-url: %q + client-id: sigstore + type: uri + subject-domain: %q + %v: + issuer-url: %q + client-id: sigstore + type: email + %v: + issuer-url: %q + client-id: sigstore + type: username + subject-domain: %q + %v: + issuer-url: %q + client-id: sigstore + type: buildkite-job + %v: + issuer-url: %q + client-id: sigstore + type: github-workflow + %v: + issuer-url: %q + client-id: sigstore + type: gitlab-pipeline + %v: + issuer-url: %q + client-id: sigstore + type: codefresh-workflow + meta-issuers: + %v: + client-id: sigstore + type: kubernetes`, + spiffeIssuer, spiffeIssuer, + uriIssuer, uriIssuer, uriIssuer, + emailIssuer, emailIssuer, + usernameIssuer, usernameIssuer, issuerDomain.Hostname(), + buildkiteIssuer, buildkiteIssuer, + gitHubIssuer, gitHubIssuer, + gitLabIssuer, gitLabIssuer, + codefreshIssuer, codefreshIssuer, + k8sIssuer)) + + cfg, err := config.Read(yamlBytes) + if err != nil { + t.Fatalf("config.Read() = %v", err) + } + + ctClient, eca := createCA(cfg, t) + ctx := context.Background() + server, conn := setupGRPCForTest(ctx, t, cfg, ctClient, eca) + defer func() { + server.Stop() + conn.Close() + }() + + client := protobuf.NewCAClient(conn) + + config, err := client.GetConfiguration(ctx, &protobuf.GetConfigurationRequest{}) + if err != nil { + t.Fatal("GetConfiguration failed", err) + } + + if len(config.Issuers) != 9 { + t.Fatalf("expected 9 issuers, got %v", len(config.Issuers)) + } + + expectedIssuers := map[string]bool{ + emailIssuer: true, spiffeIssuer: true, uriIssuer: true, + usernameIssuer: true, k8sIssuer: true, gitHubIssuer: true, + buildkiteIssuer: true, gitLabIssuer: true, codefreshIssuer: true, + } + for _, iss := range config.Issuers { + var issURL string + switch { + case expectedIssuers[iss.GetIssuerUrl()]: + delete(expectedIssuers, iss.GetIssuerUrl()) + issURL = iss.GetIssuerUrl() + case expectedIssuers[iss.GetWildcardIssuerUrl()]: + delete(expectedIssuers, iss.GetWildcardIssuerUrl()) + issURL = iss.GetWildcardIssuerUrl() + default: + t.Fatal("issuer missing from expected issuers") + } + + if iss.Audience != "sigstore" { + t.Fatalf("expected audience to be sigstore, got %v", iss.Audience) + } + + if issURL == emailIssuer { + if iss.ChallengeClaim != "email" { + t.Fatalf("expected email claim for email PoP challenge, got %v", iss.ChallengeClaim) + } + } else { + if iss.ChallengeClaim != "sub" { + t.Fatalf("expected sub claim for non-email PoP challenge, got %v", iss.ChallengeClaim) + } + } + + if issURL == spiffeIssuer { + if iss.SpiffeTrustDomain != "example.com" { + t.Fatalf("expected SPIFFE trust domain example.com, got %v", iss.SpiffeTrustDomain) + } + } else { + if iss.SpiffeTrustDomain != "" { + t.Fatalf("expected no SPIFFE trust domain, got %v", iss.SpiffeTrustDomain) + } + } + } + + if len(expectedIssuers) != 0 { + t.Fatal("not all issuers were found in configuration") + } +} + // oidcTestContainer holds values needed for each API test invocation type oidcTestContainer struct { Signer jose.Signer