From 745017cf9ab0290384aed4c5f60b5701237d54ea Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 6 Feb 2024 23:04:40 +0100 Subject: [PATCH] Add test for OIDC auto discovery configuration --- authority/provisioner/wire/oidc_options.go | 26 ++-- .../provisioner/wire/oidc_options_test.go | 132 ++++++++++++++++++ .../provisioner/wire/wire_options_test.go | 2 +- 3 files changed, 147 insertions(+), 13 deletions(-) diff --git a/authority/provisioner/wire/oidc_options.go b/authority/provisioner/wire/oidc_options.go index 60403e43..e9139caa 100644 --- a/authority/provisioner/wire/oidc_options.go +++ b/authority/provisioner/wire/oidc_options.go @@ -15,13 +15,13 @@ import ( ) type Provider struct { - DiscoveryBaseURL string `json:"discoveryBaseUrl,omitempty"` // TODO: probably safe to change to our usual configuration style - IssuerURL string `json:"issuer,omitempty"` - AuthURL string `json:"authorization_endpoint,omitempty"` - TokenURL string `json:"token_endpoint,omitempty"` - JWKSURL string `json:"jwks_uri,omitempty"` - UserInfoURL string `json:"userinfo_endpoint,omitempty"` - Algorithms []string `json:"id_token_signing_alg_values_supported,omitempty"` + DiscoveryBaseURL string `json:"discoveryBaseUrl,omitempty"` + IssuerURL string `json:"issuerUrl,omitempty"` + AuthURL string `json:"authorizationUrl,omitempty"` + TokenURL string `json:"tokenUrl,omitempty"` + JWKSURL string `json:"jwksUrl,omitempty"` + UserInfoURL string `json:"userInfoUrl,omitempty"` + Algorithms []string `json:"signatureAlgorithms,omitempty"` } type Config struct { @@ -94,13 +94,15 @@ func (o *OIDCOptions) validateAndInitialize() (err error) { if o.Provider == nil { return errors.New("provider not set") } - if o.Provider.IssuerURL == "" { - return errors.New("issuer URL must not be empty") + if o.Provider.IssuerURL == "" && o.Provider.DiscoveryBaseURL == "" { + return errors.New("either OIDC discovery or issuer URL must be set") } - o.oidcProviderConfig, err = toOIDCProviderConfig(o.Provider) - if err != nil { - return fmt.Errorf("failed creationg OIDC provider config: %w", err) + if o.Provider.DiscoveryBaseURL == "" { + o.oidcProviderConfig, err = toOIDCProviderConfig(o.Provider) + if err != nil { + return fmt.Errorf("failed creationg OIDC provider config: %w", err) + } } o.target, err = template.New("DeviceID").Parse(o.Provider.IssuerURL) diff --git a/authority/provisioner/wire/oidc_options_test.go b/authority/provisioner/wire/oidc_options_test.go index 9a2203ca..a0bf17e9 100644 --- a/authority/provisioner/wire/oidc_options_test.go +++ b/authority/provisioner/wire/oidc_options_test.go @@ -1,12 +1,20 @@ package wire import ( + "context" + "encoding/json" "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" "testing" "text/template" + "github.com/coreos/go-oidc/v3/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.step.sm/crypto/jose" ) func TestOIDCOptions_Transform(t *testing.T) { @@ -171,3 +179,127 @@ func TestOIDCOptions_EvaluateTarget(t *testing.T) { }) } } + +func TestOIDCOptions_GetVerifier(t *testing.T) { + signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + require.NoError(t, err) + require.NoError(t, err) + srv := mustDiscoveryServer(t, signerJWK.Public()) + defer srv.Close() + type fields struct { + Provider *Provider + Config *Config + TransformTemplate string + } + tests := []struct { + name string + fields fields + ctx context.Context + want *oidc.IDTokenVerifier + wantErr bool + }{ + { + name: "fail/invalid-discovery-url", + fields: fields{ + Provider: &Provider{ + DiscoveryBaseURL: "http://invalid.example.com", + }, + Config: &Config{ + ClientID: "client-id", + }, + TransformTemplate: "http://target.example.com/{{.DeviceID}}", + }, + ctx: context.Background(), + wantErr: true, + }, + { + name: "ok/auto", + fields: fields{ + Provider: &Provider{ + DiscoveryBaseURL: srv.URL, + }, + Config: &Config{ + ClientID: "client-id", + }, + TransformTemplate: "http://target.example.com/{{.DeviceID}}", + }, + ctx: context.Background(), + }, + { + name: "ok/fixed", + fields: fields{ + Provider: &Provider{ + IssuerURL: "http://issuer.example.com", + }, + Config: &Config{ + ClientID: "client-id", + }, + TransformTemplate: "http://target.example.com/{{.DeviceID}}", + }, + ctx: context.Background(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &OIDCOptions{ + Provider: tt.fields.Provider, + Config: tt.fields.Config, + TransformTemplate: tt.fields.TransformTemplate, + } + + err := o.validateAndInitialize() + require.NoError(t, err) + + verifier, err := o.GetVerifier(tt.ctx) + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, verifier) + return + } + + assert.NoError(t, err) + assert.NotNil(t, verifier) + if assert.NotNil(t, o.provider) { + assert.NotNil(t, o.provider.Endpoint()) + } + }) + } +} + +func mustDiscoveryServer(t *testing.T, pub jose.JSONWebKey) *httptest.Server { + t.Helper() + mux := http.NewServeMux() + server := httptest.NewServer(mux) + b, err := json.Marshal(struct { + Keys []jose.JSONWebKey `json:"keys,omitempty"` + }{ + Keys: []jose.JSONWebKey{pub}, + }) + require.NoError(t, err) + jwks := string(b) + + wellKnown := fmt.Sprintf(`{ + "issuer": "%[1]s", + "authorization_endpoint": "%[1]s/auth", + "token_endpoint": "%[1]s/token", + "jwks_uri": "%[1]s/keys", + "userinfo_endpoint": "%[1]s/userinfo", + "id_token_signing_alg_values_supported": ["ES256"] + }`, server.URL) + + mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, req *http.Request) { + _, err := io.WriteString(w, wellKnown) + if err != nil { + w.WriteHeader(500) + } + }) + mux.HandleFunc("/keys", func(w http.ResponseWriter, req *http.Request) { + _, err := io.WriteString(w, jwks) + if err != nil { + w.WriteHeader(500) + } + }) + + t.Cleanup(server.Close) + return server +} diff --git a/authority/provisioner/wire/wire_options_test.go b/authority/provisioner/wire/wire_options_test.go index fd0acf02..c9fc844b 100644 --- a/authority/provisioner/wire/wire_options_test.go +++ b/authority/provisioner/wire/wire_options_test.go @@ -55,7 +55,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, DPOP: &DPOPOptions{}, }, - expectedErr: errors.New("failed initializing OIDC options: issuer URL must not be empty"), + expectedErr: errors.New("failed initializing OIDC options: either OIDC discovery or issuer URL must be set"), }, { name: "fail/invalid-issuer-url",