diff --git a/cmd/helper_server.go b/cmd/helper_server.go index f6989c751f..fb4b776cc6 100644 --- a/cmd/helper_server.go +++ b/cmd/helper_server.go @@ -24,6 +24,8 @@ import ( "strings" "time" + "net/url" + "github.com/ory/fosite" "github.com/ory/hydra/sdk/go/hydra" "github.com/ory/keto/sdk/go/keto" @@ -178,41 +180,84 @@ func getScopeStrategy(key string) fosite.ScopeStrategy { return nil } +func authenticatorFactory(f func() (proxy.Authenticator, error)) proxy.Authenticator { + a, err := f() + if err != nil { + logger.WithError(err).Fatalf("Unable to initialize authenticator \"%s\" because an environment variable is missing or misconfigured.", a.GetID()) + } + return a +} +func credentialsIssuerFactory(f func() (proxy.CredentialsIssuer, error)) proxy.CredentialsIssuer { + a, err := f() + if err != nil { + logger.WithError(err).Fatalf("Unable to initialize authenticator \"%s\" because an environment variable is missing or misconfigured.", a.GetID()) + } + return a +} + func handlerFactories(keyManager rsakey.Manager) ([]proxy.Authenticator, []proxy.Authorizer, []proxy.CredentialsIssuer) { var authorizers = []proxy.Authorizer{ proxy.NewAuthorizerAllow(), proxy.NewAuthorizerDeny(), } - - if u := viper.GetString("AUTHORIZER_KETO_WARDEN_KETO_URL"); len(u) > 0 { - ketoSdk, err := keto.NewCodeGenSDK(&keto.Configuration{ - EndpointURL: viper.GetString("AUTHORIZER_KETO_WARDEN_KETO_URL"), - }) - if err != nil { - logger.WithError(err).Fatal("Unable to initialize the ORY Keto SDK") - } - authorizers = append(authorizers, proxy.NewAuthorizerKetoWarden(ketoSdk)) + var authenticators = []proxy.Authenticator{ + proxy.NewAuthenticatorNoOp(), + proxy.NewAuthenticatorAnonymous(viper.GetString("AUTHENTICATOR_ANONYMOUS_USERNAME")), } - return []proxy.Authenticator{ - proxy.NewAuthenticatorNoOp(), - proxy.NewAuthenticatorAnonymous(viper.GetString("AUTHENTICATOR_ANONYMOUS_USERNAME")), - proxy.NewAuthenticatorOAuth2Introspection( + if u := viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_URL"); len(u) > 0 { + authenticators = append(authenticators, authenticatorFactory(func() (proxy.Authenticator, error) { + return proxy.NewAuthenticatorOAuth2Introspection( viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_CLIENT_ID"), viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_CLIENT_SECRET"), viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_TOKEN_URL"), - viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_URL"), + u, strings.Split(viper.GetString("AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_SCOPE"), ","), getScopeStrategy("AUTHENTICATOR_OAUTH2_INTROSPECTION_SCOPE_STRATEGY"), - ), - proxy.NewAuthenticatorOAuth2ClientCredentials( - viper.GetString("AUTHENTICATOR_OAUTH2_CLIENT_CREDENTIALS_TOKEN_URL"), - ), - proxy.NewAuthenticatorJWT( + ) + })) + logger.Info("Authenticator \"oauth2_client_credentials\" was configured and enabled successfully.") + } else { + logger.Warn("Authenticator \"oauth2_client_credentials\" is not configured and thus disabled.") + } + + if u := viper.GetString("AUTHENTICATOR_OAUTH2_CLIENT_CREDENTIALS_TOKEN_URL"); len(u) > 0 { + authenticators = append(authenticators, authenticatorFactory(func() (proxy.Authenticator, error) { + return proxy.NewAuthenticatorOAuth2ClientCredentials(u) + })) + logger.Info("Authenticator \"oauth2_client_credentials\" was configured and enabled successfully.") + } else { + logger.Warn("Authenticator \"oauth2_client_credentials\" is not configured and thus disabled.") + } + + if u := viper.GetString("AUTHENTICATOR_JWT_JWKS_URL"); len(u) > 0 { + authenticators = append(authenticators, authenticatorFactory(func() (proxy.Authenticator, error) { + return proxy.NewAuthenticatorJWT( viper.GetString("AUTHENTICATOR_JWT_JWKS_URL"), getScopeStrategy("AUTHENTICATOR_JWT_SCOPE_STRATEGY"), - ), - }, + ) + })) + logger.Info("Authenticator \"jwt\" was configured and enabled successfully.") + } else { + logger.Warn("Authenticator \"jwt\" is not configured and thus disabled.") + } + + if u := viper.GetString("AUTHORIZER_KETO_WARDEN_KETO_URL"); len(u) > 0 { + if _, err := url.ParseRequestURI(u); err != nil { + logger.WithError(err).Fatalf("Value \"%s\" from environment variable \"AUTHORIZER_KETO_WARDEN_KETO_URL\" is not a valid URL.", u) + } + ketoSdk, err := keto.NewCodeGenSDK(&keto.Configuration{ + EndpointURL: u, + }) + if err != nil { + logger.WithError(err).Fatal("Unable to initialize the ORY Keto SDK.") + } + authorizers = append(authorizers, proxy.NewAuthorizerKetoWarden(ketoSdk)) + } else { + logger.Warn("Authorizer \"ory-keto\" is not configured and thus disabled.") + } + + return authenticators, authorizers, []proxy.CredentialsIssuer{ proxy.NewCredentialsIssuerNoOp(), diff --git a/proxy/authenticator_jwt.go b/proxy/authenticator_jwt.go index ad90753708..8a254d6756 100644 --- a/proxy/authenticator_jwt.go +++ b/proxy/authenticator_jwt.go @@ -9,6 +9,8 @@ import ( "crypto/rsa" "fmt" + "net/url" + "github.com/dgrijalva/jwt-go" "github.com/ory/fosite" "github.com/ory/go-convenience/jwtx" @@ -45,12 +47,16 @@ type AuthenticatorJWT struct { scopeStrategy fosite.ScopeStrategy } -func NewAuthenticatorJWT(jwksURL string, scopeStrategy fosite.ScopeStrategy) *AuthenticatorJWT { +func NewAuthenticatorJWT(jwksURL string, scopeStrategy fosite.ScopeStrategy) (*AuthenticatorJWT, error) { + if _, err := url.ParseRequestURI(jwksURL); err != nil { + return new(AuthenticatorJWT), errors.Errorf(`unable to validate the JSON Web Token Authenticator's JWKs URL "%s" because %s`, jwksURL, err) + } + return &AuthenticatorJWT{ jwksURL: jwksURL, fetcher: fosite.NewDefaultJWKSFetcherStrategy(), scopeStrategy: scopeStrategy, - } + }, nil } func (a *AuthenticatorJWT) GetID() string { diff --git a/proxy/authenticator_jwt_test.go b/proxy/authenticator_jwt_test.go index 359d3e18a1..9b1f7dc39a 100644 --- a/proxy/authenticator_jwt_test.go +++ b/proxy/authenticator_jwt_test.go @@ -83,6 +83,13 @@ func generateJWT(t *testing.T, claims jwt.Claims, method string) string { return fmt.Sprintf("%s.%s", sign, j) } +func TestNewAuthenticatorJWT(t *testing.T) { + _, err := NewAuthenticatorJWT("", fosite.ExactScopeStrategy) + require.Error(t, err) + _, err = NewAuthenticatorJWT("foo", fosite.ExactScopeStrategy) + require.Error(t, err) +} + func TestAuthenticatorJWT(t *testing.T) { generateKeys(t) @@ -97,7 +104,8 @@ func TestAuthenticatorJWT(t *testing.T) { })) defer ts.Close() - authenticator := NewAuthenticatorJWT(ts.URL, fosite.ExactScopeStrategy) + authenticator, err := NewAuthenticatorJWT(ts.URL, fosite.ExactScopeStrategy) + require.NoError(t, err) assert.NotEmpty(t, authenticator.GetID()) now := time.Now().Round(time.Second) diff --git a/proxy/authenticator_oauth2_client_credentials.go b/proxy/authenticator_oauth2_client_credentials.go index 1e6b37c087..4754944a12 100644 --- a/proxy/authenticator_oauth2_client_credentials.go +++ b/proxy/authenticator_oauth2_client_credentials.go @@ -24,10 +24,14 @@ type AuthenticatorOAuth2ClientCredentials struct { tokenURL string } -func NewAuthenticatorOAuth2ClientCredentials(tokenURL string) *AuthenticatorOAuth2ClientCredentials { +func NewAuthenticatorOAuth2ClientCredentials(tokenURL string) (*AuthenticatorOAuth2ClientCredentials, error) { + if _, err := url.ParseRequestURI(tokenURL); err != nil { + return new(AuthenticatorOAuth2ClientCredentials), errors.Errorf(`unable to validate the OAuth 2.0 Client Credentials Authenticator's Token Introspection URL "%s" because %s`, tokenURL, err) + } + return &AuthenticatorOAuth2ClientCredentials{ tokenURL: tokenURL, - } + }, nil } func (a *AuthenticatorOAuth2ClientCredentials) GetID() string { diff --git a/proxy/authenticator_oauth2_client_credentials_test.go b/proxy/authenticator_oauth2_client_credentials_test.go index 899b973ac5..a74070c09b 100644 --- a/proxy/authenticator_oauth2_client_credentials_test.go +++ b/proxy/authenticator_oauth2_client_credentials_test.go @@ -36,6 +36,13 @@ import ( "github.com/stretchr/testify/require" ) +func TestNewAuthenticatorOAuth2ClientCredentials(t *testing.T) { + _, err := NewAuthenticatorOAuth2ClientCredentials("") + require.Error(t, err) + _, err = NewAuthenticatorOAuth2ClientCredentials("oauth2/token") + require.Error(t, err) +} + func TestAuthenticatorOAuth2ClientCredentials(t *testing.T) { h := httprouter.New() h.POST("/oauth2/token", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -49,12 +56,13 @@ func TestAuthenticatorOAuth2ClientCredentials(t *testing.T) { }) ts := httptest.NewServer(h) - a := NewAuthenticatorOAuth2ClientCredentials(ts.URL + "/oauth2/token") + a, err := NewAuthenticatorOAuth2ClientCredentials(ts.URL + "/oauth2/token") + require.NoError(t, err) // "client", // "secret", //, //[]string{"foo-scope"},) - assert.NotEmpty(t, NewAuthenticatorNoOp().GetID()) + assert.NotEmpty(t, a.GetID()) authOk := &http.Request{Header: http.Header{}} authOk.SetBasicAuth("client", "secret") diff --git a/proxy/authenticator_oauth2_introspection.go b/proxy/authenticator_oauth2_introspection.go index d00ab3cb73..00812b556a 100644 --- a/proxy/authenticator_oauth2_introspection.go +++ b/proxy/authenticator_oauth2_introspection.go @@ -38,9 +38,26 @@ type AuthenticatorOAuth2Introspection struct { scopeStrategy fosite.ScopeStrategy } -func NewAuthenticatorOAuth2Introspection(clientID, clientSecret, tokenURL, introspectionURL string, scopes []string, strategy fosite.ScopeStrategy) *AuthenticatorOAuth2Introspection { +func NewAuthenticatorOAuth2Introspection( + clientID, clientSecret, tokenURL, introspectionURL string, + scopes []string, strategy fosite.ScopeStrategy, +) (*AuthenticatorOAuth2Introspection, error) { + if _, err := url.ParseRequestURI(introspectionURL); err != nil { + return new(AuthenticatorOAuth2Introspection), errors.Errorf(`unable to validate the OAuth 2.0 Introspection Authenticator's Token Introspection URL "%s" because %s`, introspectionURL, err) + } + c := http.DefaultClient if len(clientID)+len(clientSecret)+len(tokenURL)+len(scopes) > 0 { + if len(clientID) == 0 { + return new(AuthenticatorOAuth2Introspection), errors.Errorf("if OAuth 2.0 Authorization is used in the OAuth 2.0 Introspection Authenticator, the OAuth 2.0 Client ID must be set but was not") + } + if len(clientSecret) == 0 { + return new(AuthenticatorOAuth2Introspection), errors.Errorf("if OAuth 2.0 Authorization is used in the OAuth 2.0 Introspection Authenticator, the OAuth 2.0 Client ID must be set but was not") + } + if _, err := url.ParseRequestURI(tokenURL); err != nil { + return new(AuthenticatorOAuth2Introspection), errors.Errorf(`if OAuth 2.0 Authorization is used in the OAuth 2.0 Introspection Authenticator, the OAuth 2.0 Token URL must be set but validating URL "%s" failed because %s`, tokenURL, err) + } + c = (&clientcredentials.Config{ ClientID: clientID, ClientSecret: clientSecret, @@ -52,7 +69,8 @@ func NewAuthenticatorOAuth2Introspection(clientID, clientSecret, tokenURL, intro return &AuthenticatorOAuth2Introspection{ client: c, introspectionURL: introspectionURL, - } + scopeStrategy: strategy, + }, nil } func (a *AuthenticatorOAuth2Introspection) GetID() string { diff --git a/proxy/authenticator_oauth2_introspection_test.go b/proxy/authenticator_oauth2_introspection_test.go index ca7de90a2f..f4e7160670 100644 --- a/proxy/authenticator_oauth2_introspection_test.go +++ b/proxy/authenticator_oauth2_introspection_test.go @@ -35,10 +35,22 @@ import ( "github.com/stretchr/testify/require" ) -func TestAuthenticatorOAuth2Introspection(t *testing.T) { - a := NewAuthenticatorOAuth2Introspection("", "", "", "", []string{}, fosite.ExactScopeStrategy) - assert.NotEmpty(t, a.GetID()) +func TestNewAuthenticatorOAuth2Introspection(t *testing.T) { + _, err := NewAuthenticatorOAuth2Introspection("", "", "", "", []string{}, fosite.ExactScopeStrategy) + require.Error(t, err) + _, err = NewAuthenticatorOAuth2Introspection("", "", "", "http://localhost:1234/oauth2/introspect", []string{}, fosite.ExactScopeStrategy) + require.NoError(t, err) + _, err = NewAuthenticatorOAuth2Introspection("foo", "", "", "http://localhost:1234/oauth2/introspect", []string{}, fosite.ExactScopeStrategy) + require.Error(t, err) + _, err = NewAuthenticatorOAuth2Introspection("", "foo", "", "http://localhost:1234/oauth2/introspect", []string{}, fosite.ExactScopeStrategy) + require.Error(t, err) + _, err = NewAuthenticatorOAuth2Introspection("foo", "foo", "foo", "http://localhost:1234/oauth2/introspect", []string{}, fosite.ExactScopeStrategy) + require.Error(t, err) + _, err = NewAuthenticatorOAuth2Introspection("foo", "foo", "http://localhost:1234/oauth2/token", "http://localhost:1234/oauth2/introspect", []string{}, fosite.ExactScopeStrategy) + require.NoError(t, err) +} +func TestAuthenticatorOAuth2Introspection(t *testing.T) { for k, tc := range []struct { d string setup func(*testing.T, *httprouter.Router) @@ -148,7 +160,7 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) { })) }) }, - expectErr: false, + expectErr: true, }, { d: "should fail because active but issuer not matching", @@ -238,7 +250,10 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) { } ts := httptest.NewServer(router) defer ts.Close() - a.introspectionURL = ts.URL + "/oauth2/introspect" + + a, err := NewAuthenticatorOAuth2Introspection("", "", "", ts.URL+"/oauth2/introspect", []string{}, fosite.ExactScopeStrategy) + require.NoError(t, err) + assert.NotEmpty(t, a.GetID()) sess, err := a.Authenticate(tc.r, tc.config, nil) if tc.expectErr { diff --git a/rule/matcher_cached_http.go b/rule/matcher_cached_http.go index 5313e149ce..88c23c646d 100644 --- a/rule/matcher_cached_http.go +++ b/rule/matcher_cached_http.go @@ -48,7 +48,7 @@ func (m *HTTPMatcher) Refresh() error { return errors.WithStack(err) } if response.StatusCode != http.StatusOK { - return errors.Errorf("Unable to fetch rules from backend, got status code %d but expected %s", response.StatusCode, http.StatusOK) + return errors.Errorf("unable to fetch rules from backend, got status code %d but expected %d", response.StatusCode, http.StatusOK) } inserted := map[string]bool{} diff --git a/rule/rule.go b/rule/rule.go index ba0196f313..779e963c54 100644 --- a/rule/rule.go +++ b/rule/rule.go @@ -119,7 +119,7 @@ type Upstream struct { // IsMatching returns an error if the provided method and URL do not match the rule. func (r *Rule) IsMatching(method string, u *url.URL) error { if !stringInSlice(method, r.Match.Methods) { - return errors.Errorf("Rule %s does not match URL %s", u) + return errors.Errorf("rule %s does not match URL %s", r.ID, u) } c, err := r.CompileURL() @@ -128,7 +128,7 @@ func (r *Rule) IsMatching(method string, u *url.URL) error { } if !c.MatchString(u.String()) { - return errors.Errorf("Rule %s does not match URL %s", u) + return errors.Errorf("rule %s does not match URL %s", r.ID, u) } return nil