diff --git a/router-tests/authentication_test.go b/router-tests/authentication_test.go index 68a44e55c0..eec893d4f8 100644 --- a/router-tests/authentication_test.go +++ b/router-tests/authentication_test.go @@ -28,11 +28,13 @@ import ( ) const ( - employeesQuery = `{"query":"{ employees { id } }"}` - employeesQueryRequiringClaims = `{"query":"{ employees { id startDate } }"}` - employeesExpectedData = `{"data":{"employees":[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":7},{"id":8},{"id":10},{"id":11},{"id":12}]}}` - unauthorizedExpectedData = `{"errors":[{"message":"unauthorized"}]}` - xAuthenticatedByHeader = "X-Authenticated-By" + employeesQuery = `{"query":"{ employees { id } }"}` + employeesQueryRequiringClaims = `{"query":"{ employees { id startDate } }"}` + employeesExpectedData = `{"data":{"employees":[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":7},{"id":8},{"id":10},{"id":11},{"id":12}]}}` + unauthorizedExpectedData = `{"errors":[{"message":"unauthorized"}]}` + xAuthenticatedByHeader = "X-Authenticated-By" + simpleIntrospectionQuery = `{"query":"{ __type(name: \"Query\") { name } }"}` + simpleIntrospectionExpectedData = `{"data":{"__type":{"name":"Query"}}}` ) func TestAuthentication(t *testing.T) { @@ -42,10 +44,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations without token should succeed @@ -79,9 +88,17 @@ func TestAuthentication(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.TokenForKID("unknown_kid", nil, true) @@ -129,9 +146,17 @@ func TestAuthentication(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.TokenForKID("unknown_kid", nil, true) @@ -183,9 +208,17 @@ func TestAuthentication(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.TokenForKID("unknown_kid", nil, true) @@ -234,9 +267,17 @@ func TestAuthentication(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.TokenForKID("unknown_kid", nil, true) @@ -289,9 +330,17 @@ func TestAuthentication(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.TokenForKID("unknown_kid", nil, true) @@ -357,9 +406,17 @@ func TestAuthentication(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Create a token signed with a valid key but with an unknown kid header @@ -391,9 +448,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with an invalid token should fail @@ -415,9 +480,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -441,9 +514,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", nil, strings.NewReader(employeesQueryRequiringClaims)) @@ -459,9 +540,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -484,9 +573,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -511,9 +608,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -537,9 +642,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with an token should succeed @@ -564,9 +677,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -595,9 +716,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -624,9 +753,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -649,9 +786,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -672,9 +817,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -699,9 +852,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -726,9 +887,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -755,9 +924,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", nil, strings.NewReader(` @@ -775,9 +952,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", nil, strings.NewReader(` @@ -795,9 +980,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", nil, strings.NewReader(` @@ -815,9 +1008,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.Token(nil) @@ -840,9 +1041,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.Token(map[string]any{ @@ -867,9 +1076,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.Token(map[string]any{ @@ -894,9 +1111,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { token, err := authServer.Token(map[string]any{ @@ -921,9 +1146,17 @@ func TestAuthentication(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -974,13 +1207,21 @@ func TestAuthenticationWithCustomHeaders(t *testing.T) { require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token, err := authServer.Token(nil) require.NoError(t, err) runTest := func(t *testing.T, headerValue string) { testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { @@ -1039,9 +1280,17 @@ func TestHttpJwksAuthorization(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations without token should fail @@ -1060,9 +1309,17 @@ func TestHttpJwksAuthorization(t *testing.T) { t.Parallel() authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with an invalid token should fail @@ -1084,11 +1341,19 @@ func TestHttpJwksAuthorization(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token, err := authServer.Token(nil) require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -1139,9 +1404,17 @@ func TestHttpJwksAuthorization(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -1197,11 +1470,19 @@ func TestNonHttpAuthorization(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token := generateToken(t, kid, secret, jwt.SigningMethodHS256, nil) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -1240,11 +1521,19 @@ func TestNonHttpAuthorization(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token := generateToken(t, kid, secret, jwt.SigningMethodHS256, nil) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -1279,11 +1568,19 @@ func TestNonHttpAuthorization(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token := generateToken(t, "differentKID", secret, jwt.SigningMethodHS256, nil) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { header := http.Header{ @@ -1320,7 +1617,13 @@ func TestAuthenticationValuePrefixes(t *testing.T) { require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator1} - accessController := core.NewAccessController(authenticators, false) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) t.Run("no prefix", func(t *testing.T) { t.Parallel() @@ -1407,7 +1710,13 @@ func TestAuthenticationMultipleProviders(t *testing.T) { }) require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator1, authenticator2} - accessController := core.NewAccessController(authenticators, false) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) t.Run("authenticate with first provider due to matching prefix", func(t *testing.T) { t.Parallel() @@ -1540,12 +1849,20 @@ func TestAlgorithmMismatch(t *testing.T) { Bytes: x509.MarshalPKCS1PublicKey(&publicKey), } + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token, err := signer.SignedString(pem.EncodeToMemory(publicKeyPEM)) require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operation with forged token should fail @@ -1568,12 +1885,20 @@ func TestAlgorithmMismatch(t *testing.T) { // We will create a token with none algorithm _, authenticators := testSetup(t, rsaCrypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token, err := jwt.New(jwt.SigningMethodNone).SignedString(jwt.UnsafeAllowNoneSignatureType) require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { header := http.Header{ @@ -1691,10 +2016,17 @@ func TestOidcDiscovery(t *testing.T) { require.NoError(t, err) tokens, authenticators := testSetup(t, rsa) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { for _, token := range tokens { @@ -1775,9 +2107,17 @@ func TestMultipleKeys(t *testing.T) { tokens, authenticators := testSetup(t, rsa1, rsa2) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { for _, token := range tokens { @@ -1797,9 +2137,17 @@ func TestMultipleKeys(t *testing.T) { tokens, authenticators := testSetup(t, ec1, ec2) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { for _, token := range tokens { @@ -1819,9 +2167,17 @@ func TestMultipleKeys(t *testing.T) { tokens, authenticators := testSetup(t, rsa, ec) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { for _, token := range tokens { @@ -1845,9 +2201,17 @@ func TestMultipleKeys(t *testing.T) { tokens, authenticators := testSetup(t, hs1, hs2) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { for _, token := range tokens { @@ -1871,9 +2235,17 @@ func TestMultipleKeys(t *testing.T) { tokens, authenticators := testSetup(t, rsa, hs) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { for _, token := range tokens { @@ -1952,9 +2324,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, rsaCrypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -1981,9 +2361,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, rsaCrypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2010,9 +2398,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, rsaCrypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2039,9 +2435,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, rsaCrypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2068,9 +2472,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, rsaCrypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2097,9 +2509,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, rsaCrypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2130,9 +2550,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, hmac) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2159,9 +2587,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, hmac) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2188,9 +2624,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, hmac) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2221,9 +2665,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, ed25519Crypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2253,9 +2705,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, es256Crypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2282,9 +2742,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, es384Crypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2311,9 +2779,17 @@ func TestSupportedAlgorithms(t *testing.T) { token, authenticators := testSetup(t, es512Crypto) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { t.Run("Should succeed when providing token", func(t *testing.T) { @@ -2342,9 +2818,17 @@ func TestSupportedAlgorithms(t *testing.T) { // We are adding an RSA key but only allow HMAC token, authenticators := testSetup(t, rsaCrypto, jwkset.AlgHS256.String()) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should fail @@ -2377,9 +2861,17 @@ func TestAuthenticationOverWebsocket(t *testing.T) { require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { @@ -2433,9 +2925,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2473,9 +2973,17 @@ func TestAudienceValidation(t *testing.T) { "aud": tokenAudiences, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2517,9 +3025,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2553,13 +3069,21 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token := generateToken(t, kid, secret, jwt.SigningMethodHS256, jwt.MapClaims{ "aud": tokenAudience, }) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2606,9 +3130,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2643,13 +3175,21 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token := generateToken(t, kid, secret, jwt.SigningMethodHS256, jwt.MapClaims{ "aud": tokenAudiences, }) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2691,9 +3231,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2727,13 +3275,21 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token := generateToken(t, kid, secret, jwt.SigningMethodHS256, jwt.MapClaims{ "aud": matchingAud, }) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2773,9 +3329,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2825,9 +3389,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2871,9 +3443,17 @@ func TestAudienceValidation(t *testing.T) { "aud": matchingAud, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2911,9 +3491,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2948,9 +3536,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -2992,9 +3588,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Fail with RS512 @@ -3027,9 +3631,17 @@ func TestAudienceValidation(t *testing.T) { toJWKSConfig(authServer.JWKSURL(), time.Second*5), }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Manually craft a JWT with an unregistered/unknown alg value @@ -3079,9 +3691,17 @@ func TestAudienceValidation(t *testing.T) { }, }) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -3104,6 +3724,298 @@ func TestAudienceValidation(t *testing.T) { }) } +func TestIntrospectionAuthentication(t *testing.T) { + t.Run("unauthenticated introspection query fails on full auth", func(t *testing.T) { + t.Parallel() + + authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", http.Header{}, + strings.NewReader(simpleIntrospectionQuery)) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusUnauthorized, res.StatusCode) + require.Equal(t, "", res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, unauthorizedExpectedData, string(data)) + }) + }) + + t.Run("introspection query skips auth when allowed to skip", func(t *testing.T) { + t.Parallel() + + authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: true, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", nil, strings.NewReader(simpleIntrospectionQuery)) + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, "", res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, simpleIntrospectionExpectedData, string(data)) + }) + }) + + t.Run("introspection query auth skip works over http get", func(t *testing.T) { + t.Parallel() + + // introspection queries over http get should be recognized and + // handled equally to introspection queries over http post. + + authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: true, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + res, err := xEnv.MakeGraphQLRequestOverGET(testenv.GraphQLRequest{ + Query: "{ __type(name: \"Query\") { name } }", + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.Response.StatusCode) + require.Equal(t, "", res.Response.Header.Get(xAuthenticatedByHeader)) + require.Equal(t, simpleIntrospectionExpectedData, res.Body) + }) + }) + + t.Run("introspection query with bearer token is authenticated even with auth skip", func(t *testing.T) { + t.Parallel() + + // though auth skip is enabled, the introspection query is authenticated + // normally because it contains a valid jwt token + + authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: true, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + token, err := authServer.Token(nil) + require.NoError(t, err) + header := http.Header{ + "Authorization": []string{"Bearer " + token}, + } + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(simpleIntrospectionQuery)) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, JwksName, res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, simpleIntrospectionExpectedData, string(data)) + }) + }) + + t.Run("introspection query with invalid token still succeeds on auth skip", func(t *testing.T) { + t.Parallel() + + authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: true, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + header := http.Header{ + "Authorization": []string{"Bearer invalid"}, + } + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(simpleIntrospectionQuery)) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, "", res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, simpleIntrospectionExpectedData, string(data)) + }) + }) + + t.Run("introspection query with valid token succeeds when token is required", func(t *testing.T) { + t.Parallel() + + authenticators, _ := ConfigureAuth(t) + secret := "wg_test_introspection_secret" + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: true, + IntrospectionSkipSecret: secret, + }) + require.NoError(t, err) + + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + header := http.Header{ + "Authorization": []string{secret}, + } + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(simpleIntrospectionQuery)) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, "", res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, simpleIntrospectionExpectedData, string(data)) + }) + }) + + t.Run("introspection query with invalid token fails when token is required", func(t *testing.T) { + t.Parallel() + + authenticators, _ := ConfigureAuth(t) + secret := "wg_test_introspection_secret" + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: true, + IntrospectionSkipSecret: secret, + }) + require.NoError(t, err) + + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + header := http.Header{ + "Authorization": []string{"doesnotmatchtoken"}, + } + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(simpleIntrospectionQuery)) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusUnauthorized, res.StatusCode) + require.Equal(t, "", res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, unauthorizedExpectedData, string(data)) + }) + }) + + t.Run("normal query passes auth with valid bearer token when auth skip is enabled", func(t *testing.T) { + t.Parallel() + + authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: true, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + token, err := authServer.Token(nil) + require.NoError(t, err) + header := http.Header{ + "Authorization": []string{"Bearer " + token}, + } + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(employeesQuery)) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, JwksName, res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, employeesExpectedData, string(data)) + }) + }) + + t.Run("normal query fails auth with invalid bearer token when auth skip is enabled", func(t *testing.T) { + t.Parallel() + + // This ensures auth skip is only allowed for introspection queries, not others. + + authenticators, _ := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: true, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + + testenv.Run(t, &testenv.Config{ + RouterOptions: []core.Option{ + core.WithAccessController(accessController), + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + header := http.Header{ + "Authorization": []string{"Bearer invalid"}, + } + res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(employeesQuery)) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusUnauthorized, res.StatusCode) + require.Equal(t, "", res.Header.Get(xAuthenticatedByHeader)) + data, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, unauthorizedExpectedData, string(data)) + }) + }) +} + func toJWKSConfig(url string, refresh time.Duration, allowedAlgorithms ...string) authentication.JWKSConfig { return authentication.JWKSConfig{ URL: url, diff --git a/router-tests/batch_test.go b/router-tests/batch_test.go index bde151cf63..a075c326e4 100644 --- a/router-tests/batch_test.go +++ b/router-tests/batch_test.go @@ -333,11 +333,18 @@ func TestBatch(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, BatchingConfig: config.BatchingConfig{ Enabled: true, @@ -692,6 +699,14 @@ func TestBatch(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ BatchingConfig: config.BatchingConfig{ Enabled: true, @@ -699,7 +714,7 @@ func TestBatch(t *testing.T) { MaxEntriesPerBatch: 100, }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithRouterTrafficConfig(&config.RouterTrafficConfiguration{ MaxRequestBodyBytes: 5 << 20, // 5MiB DecompressionEnabled: true, diff --git a/router-tests/block_operations_test.go b/router-tests/block_operations_test.go index 071ecd7a66..1509fe1edc 100644 --- a/router-tests/block_operations_test.go +++ b/router-tests/block_operations_test.go @@ -150,9 +150,17 @@ func TestBlockOperations(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, ModifySecurityConfiguration: func(securityConfiguration *config.SecurityConfiguration) { securityConfiguration.BlockMutations = config.BlockOperationConfiguration{ @@ -303,10 +311,17 @@ func TestBlockOperations(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: false, }), @@ -395,6 +410,13 @@ func TestBlockOperations(t *testing.T) { t.Parallel() authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ ModifyWebsocketConfiguration: func(cfg *config.WebSocketConfiguration) { @@ -402,7 +424,7 @@ func TestBlockOperations(t *testing.T) { cfg.Enabled = true }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: false, }), diff --git a/router-tests/header_set_test.go b/router-tests/header_set_test.go index a8f15d69af..6dd407361f 100644 --- a/router-tests/header_set_test.go +++ b/router-tests/header_set_test.go @@ -275,13 +275,21 @@ func TestHeaderSetWithExpression(t *testing.T) { authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions) require.NoError(t, err) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: []authentication.Authenticator{authenticator}, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + token, err := authServer.TokenForKID(rsa1.KID(), map[string]any{"user_id": "TestId"}, false) require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterOptions: append( global(customHeader, `request.auth.claims.user_id`), - core.WithAccessController(core.NewAccessController([]authentication.Authenticator{authenticator}, true)), + core.WithAccessController(accessController), ), }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ diff --git a/router-tests/modules/router_on_request_test.go b/router-tests/modules/router_on_request_test.go index 371b0b5cc9..6d0c52df09 100644 --- a/router-tests/modules/router_on_request_test.go +++ b/router-tests/modules/router_on_request_test.go @@ -2,11 +2,12 @@ package module_test import ( "encoding/json" - "github.com/wundergraph/cosmo/router-tests/modules/router-on-request" - "go.uber.org/zap/zapcore" "net/http" "testing" + router_on_request "github.com/wundergraph/cosmo/router-tests/modules/router-on-request" + "go.uber.org/zap/zapcore" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/wundergraph/cosmo/router-tests/testenv" @@ -69,9 +70,17 @@ func TestRouterOnRequestHook(t *testing.T) { }, } + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), core.WithModulesConfig(cfg.Modules), core.WithCustomModules(&router_on_request.RouterOnRequestModule{}), }, diff --git a/router-tests/modules/set_authentication_scopes_test.go b/router-tests/modules/set_authentication_scopes_test.go index 4b8c14d172..1f69509ce1 100644 --- a/router-tests/modules/set_authentication_scopes_test.go +++ b/router-tests/modules/set_authentication_scopes_test.go @@ -32,9 +32,17 @@ func TestCustomModuleSetAuthenticationScopes(t *testing.T) { }, } authenticators, authServer := configureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithModulesConfig(cfg.Modules), core.WithCustomModules(&setScopesModule.SetAuthenticationScopesModule{}, &verifyScopes.VerifyScopesModule{}), }, @@ -73,9 +81,17 @@ func TestCustomModuleSetAuthenticationScopes(t *testing.T) { }, } authenticators, authServer := configureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithModulesConfig(cfg.Modules), core.WithCustomModules(&setScopesModule.SetAuthenticationScopesModule{}, &verifyScopes.VerifyScopesModule{}), }, @@ -116,9 +132,17 @@ func TestCustomModuleSetAuthenticationScopes(t *testing.T) { }, } authenticators, authServer := configureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithModulesConfig(cfg.Modules), core.WithCustomModules(&setScopesModule.SetAuthenticationScopesModule{}, &verifyScopes.VerifyScopesModule{}), }, diff --git a/router-tests/modules/set_scopes_test.go b/router-tests/modules/set_scopes_test.go index bb9bb617ce..980c746c96 100644 --- a/router-tests/modules/set_scopes_test.go +++ b/router-tests/modules/set_scopes_test.go @@ -61,9 +61,17 @@ func TestCustomModuleSetScopes(t *testing.T) { }, } authenticators, authServer := configureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithModulesConfig(cfg.Modules), core.WithCustomModules(&module.MyModule{}, &setScopesModule.SetScopesModule{}), }, @@ -101,9 +109,17 @@ func TestCustomModuleSetScopes(t *testing.T) { }, } authenticators, authServer := configureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithModulesConfig(cfg.Modules), core.WithCustomModules(&module.MyModule{}, &setScopesModule.SetScopesModule{}), }, diff --git a/router-tests/prometheus_test.go b/router-tests/prometheus_test.go index cce563f760..541a8614db 100644 --- a/router-tests/prometheus_test.go +++ b/router-tests/prometheus_test.go @@ -4119,6 +4119,14 @@ func TestPrometheus(t *testing.T) { const claimVal = "customClaimValue" authenticators, authServer := ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + exporter := tracetest.NewInMemoryExporter(t) metricReader := metric.NewManualReader() promRegistry := prometheus.NewRegistry() @@ -4128,7 +4136,7 @@ func TestPrometheus(t *testing.T) { MetricReader: metricReader, PrometheusRegistry: promRegistry, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), }, CustomMetricAttributes: []config.CustomAttribute{ { diff --git a/router-tests/ratelimit_test.go b/router-tests/ratelimit_test.go index d3189c5d95..f84b3595b2 100644 --- a/router-tests/ratelimit_test.go +++ b/router-tests/ratelimit_test.go @@ -270,9 +270,17 @@ func TestRateLimit(t *testing.T) { require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithRateLimitConfig(&config.RateLimitConfiguration{ Enabled: true, Strategy: "simple", diff --git a/router-tests/security_test.go b/router-tests/security_test.go index dde331b55f..74fba8fd7c 100644 --- a/router-tests/security_test.go +++ b/router-tests/security_test.go @@ -2,13 +2,13 @@ package integration import ( "fmt" - "github.com/wundergraph/cosmo/router/core" "net/http" "testing" "github.com/stretchr/testify/require" "github.com/wundergraph/cosmo/router-tests/testenv" + "github.com/wundergraph/cosmo/router/core" "github.com/wundergraph/cosmo/router/pkg/config" ) @@ -341,7 +341,9 @@ func TestQueryNamingLimits(t *testing.T) { securityConfiguration.OperationNameLengthLimit = maxLength }, RouterOptions: []core.Option{ - core.WithIntrospection(false), + core.WithIntrospection(false, config.IntrospectionConfiguration{ + Enabled: false, + }), }, }, func(t *testing.T, xEnv *testenv.Environment) { expectedErrorMessage := fmt.Sprintf(`{"errors":[{"message":"operation name of length %d exceeds max length of %d"}]}`, len(query1Name), maxLength) diff --git a/router-tests/telemetry/telemetry_test.go b/router-tests/telemetry/telemetry_test.go index 267bbe7d5f..7ebe7cc1fa 100644 --- a/router-tests/telemetry/telemetry_test.go +++ b/router-tests/telemetry/telemetry_test.go @@ -9044,6 +9044,14 @@ func TestFlakyTelemetry(t *testing.T) { metricReader := metric.NewManualReader() authenticators, authServer := integration.ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + claimKey := "extraclaim" claimVal := "extravalue" testenv.Run(t, &testenv.Config{ @@ -9057,7 +9065,7 @@ func TestFlakyTelemetry(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -9094,6 +9102,14 @@ func TestFlakyTelemetry(t *testing.T) { metricReader := metric.NewManualReader() authenticators, authServer := integration.ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + claimKey := "extraclaim" testenv.Run(t, &testenv.Config{ MetricReader: metricReader, @@ -9106,7 +9122,7 @@ func TestFlakyTelemetry(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -9142,6 +9158,14 @@ func TestFlakyTelemetry(t *testing.T) { metricReader := metric.NewManualReader() authenticators, authServer := integration.ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ MetricReader: metricReader, CustomMetricAttributes: []config.CustomAttribute{ @@ -9153,7 +9177,7 @@ func TestFlakyTelemetry(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -9215,6 +9239,14 @@ func TestFlakyTelemetry(t *testing.T) { exporter := tracetest.NewInMemoryExporter(t) metricReader := metric.NewManualReader() authenticators, authServer := integration.ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + claimKeyWithAuth := "extraclaim" claimValWithAuth := "extravalue" headerKey := "X-Custom-Header" @@ -9237,7 +9269,7 @@ func TestFlakyTelemetry(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -9290,6 +9322,14 @@ func TestFlakyTelemetry(t *testing.T) { exporter := tracetest.NewInMemoryExporter(t) metricReader := metric.NewManualReader() authenticators, authServer := integration.ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + claimKey := "extraclaim" claimVal := "extravalue" testenv.Run(t, &testenv.Config{ @@ -9304,7 +9344,7 @@ func TestFlakyTelemetry(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -9348,6 +9388,14 @@ func TestFlakyTelemetry(t *testing.T) { metricReader := metric.NewManualReader() exporter := tracetest.NewInMemoryExporter(t) authenticators, authServer := integration.ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ TraceExporter: exporter, MetricReader: metricReader, @@ -9360,7 +9408,7 @@ func TestFlakyTelemetry(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed @@ -9549,6 +9597,13 @@ func TestFlakyTelemetry(t *testing.T) { exporter := tracetest.NewInMemoryExporter(t) authenticators, authServer := integration.ConfigureAuth(t) + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ TraceExporter: exporter, @@ -9567,7 +9622,7 @@ func TestFlakyTelemetry(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, }, func(t *testing.T, xEnv *testenv.Environment) { // Operations with a token should succeed diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index 0cba295266..a7b2b01ef8 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -1420,7 +1420,9 @@ func configureRouter(listenerAddr string, testConfig *Config, routerConfig *node core.WithTLSConfig(testConfig.TLSConfig), core.WithInstanceID("test-instance"), core.WithGracePeriod(15 * time.Second), - core.WithIntrospection(true), + core.WithIntrospection(true, config.IntrospectionConfiguration{ + Enabled: true, + }), core.WithQueryPlans(true), core.WithEvents(eventsConfiguration), } diff --git a/router-tests/websocket_test.go b/router-tests/websocket_test.go index 95f20f0cff..169b9fbc05 100644 --- a/router-tests/websocket_test.go +++ b/router-tests/websocket_test.go @@ -136,9 +136,17 @@ func TestWebSockets(t *testing.T) { require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -185,9 +193,17 @@ func TestWebSockets(t *testing.T) { require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) + testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: false, }), @@ -233,12 +249,19 @@ func TestWebSockets(t *testing.T) { authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions) require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterConfigJSONTemplate: testenv.ConfigWithEdfsNatsJSONTemplate, EnableNats: true, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: false, }), @@ -292,12 +315,19 @@ func TestWebSockets(t *testing.T) { authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions) require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterConfigJSONTemplate: testenv.ConfigWithEdfsNatsJSONTemplate, EnableNats: true, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -350,6 +380,13 @@ func TestWebSockets(t *testing.T) { authenticator, err := authentication.NewWebsocketInitialPayloadAuthenticator(authOptions) require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ RouterConfigJSONTemplate: testenv.ConfigWithEdfsNatsJSONTemplate, @@ -359,7 +396,7 @@ func TestWebSockets(t *testing.T) { cfg.Enabled = true }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -412,6 +449,13 @@ func TestWebSockets(t *testing.T) { authenticator, err := authentication.NewWebsocketInitialPayloadAuthenticator(authOptions) require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ ModifyWebsocketConfiguration: func(cfg *config.WebSocketConfiguration) { @@ -419,7 +463,7 @@ func TestWebSockets(t *testing.T) { cfg.Enabled = true }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: true, }), @@ -461,6 +505,13 @@ func TestWebSockets(t *testing.T) { authenticator, err := authentication.NewWebsocketInitialPayloadAuthenticator(authOptions) require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: true, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) testenv.Run(t, &testenv.Config{ ModifyWebsocketConfiguration: func(cfg *config.WebSocketConfiguration) { @@ -468,7 +519,7 @@ func TestWebSockets(t *testing.T) { cfg.Enabled = true }, RouterOptions: []core.Option{ - core.WithAccessController(core.NewAccessController(authenticators, true)), + core.WithAccessController(accessController), core.WithAuthorizationConfig(&config.AuthorizationConfiguration{ RejectOperationIfUnauthorized: false, }), @@ -862,6 +913,13 @@ func TestWebSockets(t *testing.T) { authenticator, err := authentication.NewHttpHeaderAuthenticator(authOptions) require.NoError(t, err) authenticators := []authentication.Authenticator{authenticator} + accessController, err := core.NewAccessController(core.AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: false, + SkipIntrospectionQueries: false, + IntrospectionSkipSecret: "", + }) + require.NoError(t, err) headerRules := config.HeaderRules{ All: &config.GlobalHeaderRule{ @@ -883,7 +941,7 @@ func TestWebSockets(t *testing.T) { testenv.Run(t, &testenv.Config{ RouterOptions: []core.Option{ core.WithHeaderRules(headerRules), - core.WithAccessController(core.NewAccessController(authenticators, false)), + core.WithAccessController(accessController), }, Subgraphs: testenv.SubgraphsConfig{ GlobalMiddleware: func(next http.Handler) http.Handler { diff --git a/router/core/access_controller.go b/router/core/access_controller.go index 2fbc3aa0f5..02b7660842 100644 --- a/router/core/access_controller.go +++ b/router/core/access_controller.go @@ -1,8 +1,10 @@ package core import ( + "crypto/subtle" "errors" "net/http" + "strings" "github.com/wundergraph/cosmo/router/pkg/authentication" ) @@ -14,17 +16,31 @@ var ( ErrUnauthorized = errors.New("unauthorized") ) +// AccessControllerOptions holds configuration options for creating a new AccessController +type AccessControllerOptions struct { + Authenticators []authentication.Authenticator + AuthenticationRequired bool + SkipIntrospectionQueries bool + IntrospectionSkipSecret string +} + // AccessController handles both authentication and authorization for the Router type AccessController struct { - authenticationRequired bool - authenticators []authentication.Authenticator + authenticationRequired bool + authenticators []authentication.Authenticator + skipIntrospectionQueries bool + introspectionSkipSecret string } -func NewAccessController(authenticators []authentication.Authenticator, authenticationRequired bool) *AccessController { +// NewAccessController creates a new AccessController. +// It returns an error if the introspection auth mode is invalid. +func NewAccessController(opts AccessControllerOptions) (*AccessController, error) { return &AccessController{ - authenticationRequired: authenticationRequired, - authenticators: authenticators, - } + authenticationRequired: opts.AuthenticationRequired, + skipIntrospectionQueries: opts.SkipIntrospectionQueries, + authenticators: opts.Authenticators, + introspectionSkipSecret: opts.IntrospectionSkipSecret, + }, nil } // Access performs authorization and authentication, returning an error if the request @@ -44,3 +60,28 @@ func (a *AccessController) Access(w http.ResponseWriter, r *http.Request) (*http } return r, nil } + +func (a *AccessController) IntrospectionSecretConfigured() bool { + return a.introspectionSkipSecret != "" +} + +// IntrospectionAccess is a dedicated access method check specifically for +// introspection queries. +// It should only be used when introspection authentication skip is enabled. +func (a *AccessController) IntrospectionAccess(r *http.Request, body []byte) bool { + if !a.skipIntrospectionQueries { + return false + } + + if a.introspectionSkipSecret == "" { + return true + } + + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + return false + } + + authHeader = strings.TrimSpace(authHeader) + return subtle.ConstantTimeCompare([]byte(authHeader), []byte(a.introspectionSkipSecret)) == 1 +} diff --git a/router/core/graphql_prehandler.go b/router/core/graphql_prehandler.go index dd21615f70..cd0b9020a9 100644 --- a/router/core/graphql_prehandler.go +++ b/router/core/graphql_prehandler.go @@ -109,15 +109,25 @@ type PreHandler struct { } type httpOperation struct { - requestContext *requestContext - body []byte - files []*httpclient.FileUpload - requestLogger *zap.Logger - routerSpan trace.Span - operationMetrics *OperationMetrics - traceTimings *art.TraceTimings + requestContext *requestContext + body []byte + files []*httpclient.FileUpload + requestLogger *zap.Logger + routerSpan trace.Span + operationMetrics *OperationMetrics + traceTimings *art.TraceTimings + authenticationPass authenticationPass } +type authenticationPass int + +const ( + authenticationPassNone authenticationPass = iota + authenticationPassNormal + authenticationPassIntrospectionSecret + authenticationPassSkip +) + func NewPreHandler(opts *PreHandlerOptions) *PreHandler { return &PreHandler{ log: opts.Logger, @@ -328,8 +338,8 @@ func (h *PreHandler) Handler(next http.Handler) http.Handler { variablesParser := h.variableParsePool.Get() defer h.variableParsePool.Put(variablesParser) + authenticationPass := authenticationPassNone - // If we have authenticators, we try to authenticate the request if h.accessController != nil { _, authenticateSpan := h.tracer.Start(r.Context(), "Authenticate", trace.WithSpanKind(trace.SpanKindServer), @@ -338,39 +348,51 @@ func (h *PreHandler) Handler(next http.Handler) http.Handler { validatedReq, err := h.accessController.Access(w, r) if err != nil { - requestContext.SetError(err) - requestLogger.Debug("Failed to authenticate request", zap.Error(err)) - - // Mark the root span of the router as failed, so we can easily identify failed requests - rtrace.AttachErrToSpan(routerSpan, err) - rtrace.AttachErrToSpan(authenticateSpan, err) - - authenticateSpan.End() + // Auth failed but introspection queries might be allowed to skip auth. + // At this early stage we don't know wether this query is an introspection query or not. + // We verify if the operation is allowed to skip auth, remember the result in authMode and continue. + // At a later stage, when we know the operation type, we recall this decision, to either reject or allow + // the operation based on wether this is an introspection query or not. + + if !h.accessController.skipIntrospectionQueries { + // Reject the request since auth has failed + // and skipping auth for introspection queries is not allowed, + // so it does not matter wether this is an introspection query or not. + h.handleAuthenticationFailure(requestContext, requestLogger, err, routerSpan, authenticateSpan, r, w) + authenticateSpan.End() + return + } - writeOperationError(r, w, requestLogger, &httpGraphqlError{ - message: err.Error(), - statusCode: http.StatusUnauthorized, - }) - return + if h.accessController.IntrospectionSecretConfigured() { + if !h.accessController.IntrospectionAccess(r, body) { + h.handleAuthenticationFailure(requestContext, requestLogger, err, routerSpan, authenticateSpan, r, w) + authenticateSpan.End() + return + } + authenticationPass = authenticationPassIntrospectionSecret + } else { + authenticationPass = authenticationPassSkip + } + } else { + r = validatedReq + requestContext.expressionContext.Request.Auth = expr.LoadAuth(r.Context()) + authenticationPass = authenticationPassNormal } authenticateSpan.End() - - r = validatedReq - - requestContext.expressionContext.Request.Auth = expr.LoadAuth(r.Context()) } setTelemetryAttributes(r.Context(), requestContext, expr.BucketAuth) - err = h.handleOperation(r, variablesParser, &httpOperation{ - requestContext: requestContext, - requestLogger: requestLogger, - routerSpan: routerSpan, - operationMetrics: metrics, - traceTimings: traceTimings, - files: files, - body: body, + err = h.handleOperation(w, r, variablesParser, &httpOperation{ + requestContext: requestContext, + requestLogger: requestLogger, + routerSpan: routerSpan, + operationMetrics: metrics, + traceTimings: traceTimings, + files: files, + body: body, + authenticationPass: authenticationPass, }) if err != nil { requestContext.SetError(err) @@ -450,7 +472,7 @@ func (h *PreHandler) shouldFetchPersistedOperation(operationKit *OperationKit) b return operationKit.parsedOperation.IsPersistedOperation || h.operationBlocker.safelistEnabled || h.operationBlocker.logUnknownOperationsEnabled } -func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson.Parser, httpOperation *httpOperation) error { +func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, variablesParser *astjson.Parser, httpOperation *httpOperation) error { operationKit, err := h.operationProcessor.NewKit() if err != nil { return err @@ -621,6 +643,37 @@ func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson engineParseSpan.End() } + if h.accessController != nil { + // Based on the authentication result, the introspection config, + // and wether this is an introspection query, + // we decide here if we need to abort the request or not. + isIntrospection, err := operationKit.isIntrospectionQuery() + if err != nil { + requestContext.logger.Error("failed to check if operation is introspection, treat it like non-introspection operation", zap.Error(err)) + isIntrospection = false + } + + // non-introspection queries are only allowed when authenticated via normal authentication + if !isIntrospection && httpOperation.authenticationPass != authenticationPassNormal { + return &httpGraphqlError{ + message: "unauthorized", + statusCode: http.StatusUnauthorized, + } + } + + // introspection queries are only allowed when authenticated normally or via dedicated token, or when auth skip is enabled + // note: httpOperation.authMethod is only set when authentication is successful and the config allows such authentication. + if isIntrospection && + httpOperation.authenticationPass != authenticationPassNormal && + httpOperation.authenticationPass != authenticationPassIntrospectionSecret && + httpOperation.authenticationPass != authenticationPassSkip { + return &httpGraphqlError{ + message: "unauthorized", + statusCode: http.StatusUnauthorized, + } + } + } + requestContext.operation.name = operationKit.parsedOperation.Request.OperationName requestContext.operation.opType = operationKit.parsedOperation.Type @@ -1086,6 +1139,20 @@ func (h *PreHandler) getErrorCodes(err error) []string { // flushMetrics flushes all metrics to the respective exporters // only used for serverless router build +func (h *PreHandler) handleAuthenticationFailure(requestContext *requestContext, requestLogger *zap.Logger, err error, routerSpan trace.Span, authenticateSpan trace.Span, r *http.Request, w http.ResponseWriter) { + requestContext.SetError(err) + requestLogger.Debug("Failed to authenticate request", zap.Error(err)) + + // Mark the root span of the router as failed, so we can easily identify failed requests + rtrace.AttachErrToSpan(routerSpan, err) + rtrace.AttachErrToSpan(authenticateSpan, err) + + writeOperationError(r, w, requestLogger, &httpGraphqlError{ + message: err.Error(), + statusCode: http.StatusUnauthorized, + }) +} + func (h *PreHandler) flushMetrics(ctx context.Context, requestLogger *zap.Logger) { requestLogger.Debug("Flushing metrics ...") diff --git a/router/core/router.go b/router/core/router.go index ca4a5aaa36..fd07b166cc 100644 --- a/router/core/router.go +++ b/router/core/router.go @@ -211,17 +211,26 @@ func NewRouter(opts ...Option) (*Router, error) { // this is set via the deprecated method if !r.playground { r.playgroundConfig.Enabled = r.playground - r.logger.Warn("The playground_enabled option is deprecated. Use the playground.enabled option in the config instead.") + r.logger.Warn("The playground_enabled option is deprecated. Use the /playground/enabled option in the config instead.") } if r.playgroundPath != "" && r.playgroundPath != "/" { r.playgroundConfig.Path = r.playgroundPath - r.logger.Warn("The playground_path option is deprecated. Use the playground.path option in the config instead.") + r.logger.Warn("The playground_path option is deprecated. Use the /playground/path option in the config instead.") } if r.playgroundConfig.Path == "" { r.playgroundConfig.Path = "/" } + // handle introspection config deprecation + // if either the old deprecated or the new config is set to false, introspection is disabled + if !r.introspection { + r.introspectionConfig.Enabled = r.introspection + r.logger.Warn("The introspection_enabled option is deprecated. Use the /introspection/enabled option in the config instead.") + } else if !r.introspectionConfig.Enabled { + r.introspection = r.introspectionConfig.Enabled + } + if r.instanceID == "" { r.instanceID = nuid.Next() } @@ -1536,9 +1545,10 @@ func WithPlayground(enable bool) Option { } } -func WithIntrospection(enable bool) Option { +func WithIntrospection(enable bool, config config.IntrospectionConfiguration) Option { return func(r *Router) { r.introspection = enable + r.introspectionConfig = config } } diff --git a/router/core/router_config.go b/router/core/router_config.go index 89d99f2ce1..b9cdac68b6 100644 --- a/router/core/router_config.go +++ b/router/core/router_config.go @@ -50,6 +50,7 @@ type Config struct { graphqlPath string playground bool introspection bool + introspectionConfig config.IntrospectionConfiguration queryPlansEnabled bool graphApiToken string healthCheckPath string diff --git a/router/core/supervisor_instance.go b/router/core/supervisor_instance.go index 01f1daaef1..33f69d3b2c 100644 --- a/router/core/supervisor_instance.go +++ b/router/core/supervisor_instance.go @@ -58,7 +58,26 @@ func newRouter(ctx context.Context, params RouterResources, additionalOptions .. } if len(authenticators) > 0 { - options = append(options, WithAccessController(NewAccessController(authenticators, cfg.Authorization.RequireAuthentication))) + accessController, err := NewAccessController(AccessControllerOptions{ + Authenticators: authenticators, + AuthenticationRequired: cfg.Authorization.RequireAuthentication, + SkipIntrospectionQueries: cfg.Authentication.IgnoreIntrospection, + IntrospectionSkipSecret: cfg.IntrospectionConfig.Secret, + }) + if err != nil { + return nil, fmt.Errorf("could not create access controller: %w", err) + } + + options = append(options, WithAccessController(accessController)) + } + + if cfg.Authentication.IgnoreIntrospection && cfg.IntrospectionConfig.Secret == "" { + params.Logger.Warn("introspection operations are not authenticated. " + + "Consider setting introspection.secret configuration parameter or set authentication.ignore_introspection to false. Do not use this in production.") + } + + if !cfg.Authentication.IgnoreIntrospection && cfg.IntrospectionConfig.Secret != "" { + params.Logger.Warn("introspection.secret configuration parameter is ignored because authentication.ignore_introspection is false.") } // HTTP_PROXY, HTTPS_PROXY and NO_PROXY @@ -167,7 +186,7 @@ func optionsFromResources(logger *zap.Logger, config *config.Config) []Option { WithOverrideRoutingURL(config.OverrideRoutingURL), WithOverrides(config.Overrides), WithLogger(logger), - WithIntrospection(config.IntrospectionEnabled), + WithIntrospection(config.IntrospectionEnabled, config.IntrospectionConfig), WithQueryPlans(config.QueryPlansEnabled), WithPlayground(config.PlaygroundEnabled), WithGraphApiToken(config.Graph.Token), diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 73e8f85e28..caf7738266 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -505,7 +505,8 @@ type JWTAuthenticationConfiguration struct { } type AuthenticationConfiguration struct { - JWT JWTAuthenticationConfiguration `yaml:"jwt"` + JWT JWTAuthenticationConfiguration `yaml:"jwt"` + IgnoreIntrospection bool `yaml:"ignore_introspection" envDefault:"false"` } type AuthorizationConfiguration struct { @@ -997,6 +998,11 @@ type PluginRegistryConfiguration struct { URL string `yaml:"url" env:"URL" envDefault:"cosmo-registry.wundergraph.com"` } +type IntrospectionConfiguration struct { + Enabled bool `yaml:"enabled" envDefault:"true" env:"INTROSPECTION_ENABLED"` + Secret string `yaml:"secret" env:"INTROSPECTION_SECRET"` +} + type Config struct { Version string `yaml:"version,omitempty" ignored:"true"` @@ -1023,7 +1029,8 @@ type Config struct { ControlplaneURL string `yaml:"controlplane_url" envDefault:"https://cosmo-cp.wundergraph.com" env:"CONTROLPLANE_URL"` PlaygroundConfig PlaygroundConfig `yaml:"playground,omitempty"` PlaygroundEnabled bool `yaml:"playground_enabled" envDefault:"true" env:"PLAYGROUND_ENABLED"` - IntrospectionEnabled bool `yaml:"introspection_enabled" envDefault:"true" env:"INTROSPECTION_ENABLED"` + IntrospectionEnabled bool `yaml:"introspection_enabled" envDefault:"true"` // deprecated, use IntrospectionConfiguration instead + IntrospectionConfig IntrospectionConfiguration `yaml:"introspection,omitempty"` QueryPlansEnabled bool `yaml:"query_plans_enabled" envDefault:"true" env:"QUERY_PLANS_ENABLED"` LogLevel zapcore.Level `yaml:"log_level" envDefault:"info" env:"LOG_LEVEL"` JSONLog bool `yaml:"json_log" envDefault:"true" env:"JSON_LOG"` diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index 528e0e1ce7..fa4ee056d8 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -1373,12 +1373,31 @@ "description": "Enable the GraphQL Playground. The GraphQL Playground is a web-based GraphQL IDE that allows you to interact with the GraphQL API. The default value is true. If the value is false, the GraphQL Playground is disabled.", "default": true, "deprecated": true, - "deprecationMessage": "playground_enabled is deprecated. Please use the playground.enabled configuration instead." + "deprecationMessage": "This option is deprecated. Please use the playground.enabled configuration instead." }, "introspection_enabled": { "type": "boolean", "description": "Enable the GraphQL introspection. The GraphQL introspection allows you to query the schema of the GraphQL API. The default value is true. If the value is false, the GraphQL introspection is disabled. In production, it is recommended to disable the introspection.", - "default": true + "default": true, + "deprecated": true, + "deprecationMessage": "This option is deprecated. Please use the introspection.enabled configuration instead." + }, + "introspection": { + "type": "object", + "description": "", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the GraphQL introspection. The GraphQL introspection allows you to query the schema of the GraphQL API. The default value is true. If the value is false, the GraphQL introspection is disabled. In production, it is recommended to disable the introspection.", + "default": true + }, + "secret": { + "type": "string", + "description": "A dedicated secret to protect introspection when skipping standard authentication. Needs to be passed via the 'Authorization' header. Effective only when /authentication/ignore_introspection is true.", + "minLength": 32 + } + } }, "query_plans_enabled": { "type": "boolean", @@ -1847,6 +1866,11 @@ } } } + }, + "ignore_introspection": { + "type": "boolean", + "description": "If the value is true, introspection requests not need to be authenticated. The default value is false.", + "default": false } } }, diff --git a/router/pkg/config/config_test.go b/router/pkg/config/config_test.go index 583db5fcf2..adb1e08c81 100644 --- a/router/pkg/config/config_test.go +++ b/router/pkg/config/config_test.go @@ -1294,6 +1294,60 @@ authentication: } +func TestValidateIntrospectionAuthConfig(t *testing.T) { + t.Parallel() + + t.Run("verify authentication can be skipped for introspection queries", func(t *testing.T) { + t.Parallel() + + f := createTempFileFromFixture(t, ` +version: "1" + +introspection: + enabled: true + +authentication: + ignore_introspection: true +`) + _, err := LoadConfig([]string{f}) + require.NoError(t, err) + }) + + t.Run("verify a secret can be set for introspection query authentication skip", func(t *testing.T) { + t.Parallel() + + f := createTempFileFromFixture(t, ` +version: "1" + +introspection: + enabled: true + secret: dedicated_secret_for_introspection + +authentication: + ignore_introspection: true +`) + _, err := LoadConfig([]string{f}) + require.NoError(t, err) + }) + + t.Run("A secret too short is invalid", func(t *testing.T) { + t.Parallel() + + f := createTempFileFromFixture(t, ` +version: "1" + +introspection: + enabled: true + secret: too_short_token + +authentication: + ignore_introspection: true +`) + _, err := LoadConfig([]string{f}) + require.ErrorContains(t, err, "at '/introspection/secret': minLength: got 15, want 32") + }) +} + func TestValidateAccessLogFileMode(t *testing.T) { t.Parallel() diff --git a/router/pkg/config/fixtures/full.yaml b/router/pkg/config/fixtures/full.yaml index a43691cc12..ba24a18dbe 100644 --- a/router/pkg/config/fixtures/full.yaml +++ b/router/pkg/config/fixtures/full.yaml @@ -20,6 +20,9 @@ playground: path: '/my-playground' concurrency_limit: 1500 introspection_enabled: true +introspection: + enabled: true + secret: 'AN_EXAMPLE_PLACEHOLDER_SECRET_ONLY' json_log: true shutdown_delay: 15s grace_period: 20s @@ -267,6 +270,7 @@ headers: # Authentication and Authorization # See https://cosmo-docs.wundergraph.com/router/authentication-and-authorization for more information authentication: + ignore_introspection: false jwt: jwks: - url: 'https://example.com/.well-known/jwks.json' diff --git a/router/pkg/config/testdata/config_defaults.json b/router/pkg/config/testdata/config_defaults.json index c14af6023c..4a965af451 100644 --- a/router/pkg/config/testdata/config_defaults.json +++ b/router/pkg/config/testdata/config_defaults.json @@ -231,6 +231,10 @@ }, "PlaygroundEnabled": true, "IntrospectionEnabled": true, + "IntrospectionConfig": { + "Enabled": true, + "Secret": "" + }, "QueryPlansEnabled": true, "LogLevel": "info", "JSONLog": true, @@ -249,7 +253,8 @@ "HeaderName": "Authorization", "HeaderValuePrefix": "Bearer", "HeaderSources": null - } + }, + "IgnoreIntrospection": false }, "Authorization": { "RequireAuthentication": false, diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index d2a5695072..f74e741d01 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -462,6 +462,10 @@ }, "PlaygroundEnabled": true, "IntrospectionEnabled": true, + "IntrospectionConfig": { + "Enabled": true, + "Secret": "AN_EXAMPLE_PLACEHOLDER_SECRET_ONLY" + }, "QueryPlansEnabled": true, "LogLevel": "info", "JSONLog": true, @@ -545,7 +549,8 @@ "ValuePrefixes": null } ] - } + }, + "IgnoreIntrospection": false }, "Authorization": { "RequireAuthentication": false,