diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index df4a097b7e..bbbaa569d4 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -13,12 +13,13 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -288,8 +289,14 @@ func TestAPIGateway_Basic(t *testing.T) { } } +const ( + // valid JWT token with role of "doctor". + doctorToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJkb2N0b3IifQ.FfgpzjMf8Evh6K-fJ1cLXklfIXOm-vojVbWlPPbGVFtzxZ9hxMxoyAY_G8i36SfGrpUlp-RJ6ohMvprMrEgyRgbenu7u5kkm5iGHW-zpMus4izXRxPELBcpWOGF105HIssT2NYRstXieNR8EVzvGfLdvR0GW8ttEERgseqGvuAfdb4-aNYsysGwUUHbsZjazA6H1rZmWqHdCLOJ2ZwFsIdckO9CadnkyTILpcPUmLYyUVJdtlLGOySb0GG8c_dPML_IR5jSXCSUZt6S2JBNBNBdqukrlqpA-fIaaWft0dbWVMhv8DqPC8znult8dKvLZ1qXeU0itsqqJUyE16ihJjw" + // valid JWT token with role of "pet". + petToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJwZXQifQ.l94rJayGGTMB426HwEw5ipSjaIHjm-UWDHiBAlB_Slmi814AxAfl_0AdRwSz67UDnkoygKbvPpR5xUB03JCXNshLZuKLegWsBeQg_OJYvZGmFagl5NglBFvH7Jbta4e1eQoAxZI6Xyy1jHbu7jFBjQPVnK8EaRvWoW8Pe8a8rp_5xhub0pomhvRF6Pm5kAS4cMnxvqpVc5Oo5nO7ws_SmoNnbt2Ok14k23Zx5E2EWmGStOfbgFsdbhVbepB2DMzqv1j8jvBbwa_OxCwc_7pEOthOOxRV6L3ZjgbRSB4GumlXAOCBYXD1cRLgrMSrWB1GkefAKu8PV0Ho1px6sI9Evg" +) + func TestAPIGateway_JWTAuth_Basic(t *testing.T) { - t.Skip("skipping this test until GW JWT auth is complete") ctx := suite.Environment().DefaultContext(t) cfg := suite.Config() @@ -300,7 +307,7 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { helmValues := map[string]string{ "connectInject.enabled": "true", "connectInject.consulNamespaces.mirroringK8S": "true", - "global.acls.manageSystemACLs": "true", // acls must be enabled for JWT auth to take place + "global.acls.manageSystemACLs": "true", "global.tls.enabled": "true", "global.logLevel": "trace", } @@ -310,8 +317,10 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { consulCluster.Create(t) + // this is necesary when running tests with ACLs enabled + runTestsAsSecure := true // Override the default proxy config settings for this test - consulClient, _ := consulCluster.SetupConsulClient(t, true) + consulClient, _ := consulCluster.SetupConsulClient(t, runTestsAsSecure) _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ Kind: api.ProxyDefaults, Name: api.ProxyConfigGlobal, @@ -321,8 +330,17 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { }, nil) require.NoError(t, err) + logger.Log(t, "creating other namespace") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "create", "namespace", "other") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "namespace", "other") + }) + logger.Log(t, "creating api-gateway resources") - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth") + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth") require.NoError(t, err, out) helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected @@ -330,6 +348,23 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth") }) + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-n", "other", "-f", "../fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-n", "other", "-f", "../fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml") + }) + + logger.Log(t, "try (and fail) to add a second gateway policy to the gateway") + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy") + require.Error(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy") + }) + // Create certificate secret, we do this separately since // applying the secret will make an invalid certificate that breaks other tests logger.Log(t, "creating certificate secret") @@ -360,16 +395,19 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { // leader election so we may need to wait a long time for // the reconcile loop to run (hence the 2m timeout here). var ( - gatewayAddress string - gatewayClass gwv1beta1.GatewayClass - httpRoute gwv1beta1.HTTPRoute - httpRouteAuth gwv1beta1.HTTPRoute + gatewayAddress string + gatewayClass gwv1beta1.GatewayClass + httpRoute gwv1beta1.HTTPRoute + httpRouteAuth gwv1beta1.HTTPRoute + httpRouteAuth2 gwv1beta1.HTTPRoute + httpRouteNoAuthOnAuthListener gwv1beta1.HTTPRoute + httpRouteInvalid gwv1beta1.HTTPRoute ) counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) require.NoError(r, err) // check our finalizers @@ -379,13 +417,13 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { // check our statuses checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) - require.Len(r, gateway.Status.Listeners, 4) + require.Len(r, gateway.Status.Listeners, 5) - require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) + require.EqualValues(r, int32(3), gateway.Status.Listeners[0].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, 1, gateway.Status.Listeners[1].AttachedRoutes) + require.EqualValues(r, int32(1), gateway.Status.Listeners[1].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) @@ -411,6 +449,18 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth", Namespace: "default"}, &httpRouteAuth) require.NoError(r, err) + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-no-auth-on-auth-listener", Namespace: "default"}, &httpRouteNoAuthOnAuthListener) + require.NoError(r, err) + + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route2-auth", Namespace: "default"}, &httpRouteAuth2) + require.NoError(r, err) + + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth-invalid", Namespace: "default"}, &httpRouteInvalid) + require.NoError(r, err) + // check our finalizers require.Len(r, httpRoute.Finalizers, 1) require.EqualValues(r, gatewayFinalizer, httpRoute.Finalizers[0]) @@ -434,6 +484,38 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + + // check our finalizers + require.Len(r, httpRouteNoAuthOnAuthListener.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, httpRouteNoAuthOnAuthListener.Finalizers[0]) + + // check parent status + require.Len(r, httpRouteNoAuthOnAuthListener.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRouteNoAuthOnAuthListener.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRouteNoAuthOnAuthListener.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + + // check our finalizers + require.Len(r, httpRouteAuth2.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, httpRouteAuth2.Finalizers[0]) + + // check parent status + require.Len(r, httpRouteAuth2.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRouteAuth2.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRouteAuth2.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + + // check parent status + require.Len(r, httpRouteInvalid.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRouteInvalid.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRouteInvalid.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, falseCondition("Accepted", "FilterNotFound")) + checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) }) // check that the Consul entries were created @@ -459,22 +541,12 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { // finally we check that we can actually route to the service(s) via the gateway k8sOptions := ctx.KubectlOptions(t) targetHTTPAddress := fmt.Sprintf("http://%s/v1", gatewayAddress) - targetHTTPAddressAdmin := fmt.Sprintf("http://%s:8080/admin", gatewayAddress) - targetHTTPAddressPet := fmt.Sprintf("http://%s:8080/pet", gatewayAddress) - // valid JWT token with role of "doctor" - doctorToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJkb2N0b3IifQ.FfgpzjMf8Evh6K-fJ1cLXklfIXOm-vojVbWlPPbGVFtzxZ9hxMxoyAY_G8i36SfGrpUlp-RJ6ohMvprMrEgyRgbenu7u5kkm5iGHW-zpMus4izXRxPELBcpWOGF105HIssT2NYRstXieNR8EVzvGfLdvR0GW8ttEERgseqGvuAfdb4-aNYsysGwUUHbsZjazA6H1rZmWqHdCLOJ2ZwFsIdckO9CadnkyTILpcPUmLYyUVJdtlLGOySb0GG8c_dPML_IR5jSXCSUZt6S2JBNBNBdqukrlqpA-fIaaWft0dbWVMhv8DqPC8znult8dKvLZ1qXeU0itsqqJUyE16ihJjw" - // valid JWT token with role of "pet" - petToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJwZXQifQ.l94rJayGGTMB426HwEw5ipSjaIHjm-UWDHiBAlB_Slmi814AxAfl_0AdRwSz67UDnkoygKbvPpR5xUB03JCXNshLZuKLegWsBeQg_OJYvZGmFagl5NglBFvH7Jbta4e1eQoAxZI6Xyy1jHbu7jFBjQPVnK8EaRvWoW8Pe8a8rp_5xhub0pomhvRF6Pm5kAS4cMnxvqpVc5Oo5nO7ws_SmoNnbt2Ok14k23Zx5E2EWmGStOfbgFsdbhVbepB2DMzqv1j8jvBbwa_OxCwc_7pEOthOOxRV6L3ZjgbRSB4GumlXAOCBYXD1cRLgrMSrWB1GkefAKu8PV0Ho1px6sI9Evg" - - // check that intentions keep our connection from happening - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddress) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressAdmin) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressAdmin) - - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressPet) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressPet) + targetHTTPAddressAdmin := fmt.Sprintf("http://%s:8081/admin", gatewayAddress) + targetHTTPAddressPet := fmt.Sprintf("http://%s:8081/pet", gatewayAddress) + targetHTTPAddressAdmin2 := fmt.Sprintf("http://%s:8081/admin-2", gatewayAddress) + targetHTTPAddressPet2 := fmt.Sprintf("http://%s:8081/pet-2", gatewayAddress) + targetHTTPAddressAdminNoAuthOnRoute := fmt.Sprintf("http://%s:8081/admin-no-auth", gatewayAddress) + targetHTTPAddressPetNotAuthOnRoute := fmt.Sprintf("http://%s:8081/pet-no-auth", gatewayAddress) // Now we create the allow intention. _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ @@ -505,38 +577,93 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { logger.Log(t, "trying calls to api gateway http") k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) - // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has an issuer of "local" and a "role" of "doctor" + // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that a "role" of "doctor" // we can see that: - // * the "iss" verification in the gateway override takes precedence over the "iss" verification in the route filter // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default + // should fail because we're missing JWT logger.Log(t, "trying calls to api gateway /admin should fail without JWT token") k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin) // should fail because we use the token with the wrong role and correct issuer logger.Log(t, "trying calls to api gateway /admin should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressAdmin) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdmin) // will succeed because we use the token with the correct role and the correct issuer logger.Log(t, "trying calls to api gateway /admin should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressAdmin) + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdmin) - // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has an issuer of "local" and a "role" of "pet" + // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has a "role" of "pet" // the route does not define // we can see that: - // * the "iss" verification in the gateway override takes precedence over the "iss" verification in the route filter - // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default + // * the "role" verification in the gateway default is used + // should fail because we're missing JWT logger.Log(t, "trying calls to api gateway /pet should fail without JWT token") k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet) // should fail because we use the token with the wrong role and correct issuer logger.Log(t, "trying calls to api gateway /pet should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressPet) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPet) // will succeed because we use the token with the correct role and the correct issuer logger.Log(t, "trying calls to api gateway /pet should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressPet) + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPet) + + // ensure that routes attached to the same gateway don't cause changes in another route + // * the verification on the gateway is the only one used as this route does not define any JWT configuration + + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /pet-no-auth should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPetNotAuthOnRoute) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /pet-no-auth should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPetNotAuthOnRoute) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /pet-no-auth should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPetNotAuthOnRoute) + + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /admin-no-auth should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdminNoAuthOnRoute) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /admin-no-auth should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdminNoAuthOnRoute) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /admin-no-auth should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdminNoAuthOnRoute) + + // multiple routes can use the same external ref + // we can see that: + // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default + + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /admin-2 should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin2) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /admin-2 should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdmin2) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /admin-2 should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdmin2) + + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /pet-2 should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet2) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /pet-2 should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPet2) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /pet-2 should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPet2) } func checkStatusCondition(t require.TestingT, conditions []metav1.Condition, toCheck metav1.Condition) { diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml index bef7e96f12..64e3d3c8d5 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml @@ -9,11 +9,17 @@ spec: gatewayClassName: gateway-class listeners: - protocol: HTTP - port: 8080 + port: 8081 name: http-auth allowedRoutes: namespaces: from: "All" + - protocol: HTTP + port: 8082 + name: http-invalid-attach + allowedRoutes: + namespaces: + from: "All" - protocol: HTTP port: 80 name: http diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml new file mode 100644 index 0000000000..19b0669c1a --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml @@ -0,0 +1,16 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteAuthFilter +metadata: + name: route-jwt-auth-filter-other + namespace: other +spec: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml new file mode 100644 index 0000000000..03960f67be --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml @@ -0,0 +1,25 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +# This is used to show that a gateway cannot have more than one gateway policy attached to it +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayPolicy +metadata: + name: bad-policy +spec: + targetRef: + name: gateway + sectionName: http-auth + group: gateway.networking.k8s.io/v1beta1 + kind: Gateway + override: + jwt: + providers: + - name: "local" + default: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml new file mode 100644 index 0000000000..0886ca4bed --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- extra-gateway-policy.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml index 6c58632d87..5552d7e085 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml @@ -2,26 +2,24 @@ # SPDX-License-Identifier: MPL-2.0 apiVersion: consul.hashicorp.com/v1alpha1 -kind: ConsulGatewayPolicy +kind: GatewayPolicy metadata: name: my-policy spec: targetRef: name: gateway + sectionName: http-auth + group: gateway.networking.k8s.io/v1beta1 kind: Gateway - group: gateway.networking.kuberenetes.io - sectionName: http override: - Providers: - - Provider: "local" - VerifyClaims: - - Path: - - "iss" - Value: "local" + jwt: + providers: + - name: "local" default: - Providers: - - Provider: "local" - VerifyClaims: - - Path: - - "iss" - Value: "local" + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml index 4963277c55..93fee5f24a 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml @@ -16,12 +16,12 @@ spec: value: "/admin" backendRefs: - name: static-server - port: 80 + port: 8080 filters: - type: ExtensionRef extensionRef: group: consul.hashicorp.com - kind: HTTPRouteAuthFilter + kind: RouteAuthFilter name: route-jwt-auth-filter - matches: - path: @@ -29,4 +29,4 @@ spec: value: "/pet" backendRefs: - name: static-server - port: 80 + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml new file mode 100644 index 0000000000..55753c29aa --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml @@ -0,0 +1,32 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route-auth-invalid +spec: + parentRefs: + - name: gateway + sectionName: http-invalid-attach + rules: + - matches: + - path: + type: PathPrefix + value: "/admin" + backendRefs: + - name: static-server + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteAuthFilter + name: route-jwt-auth-filter-other + - matches: + - path: + type: PathPrefix + value: "/pet" + backendRefs: + - name: static-server + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml new file mode 100644 index 0000000000..e4dc1b5a8b --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml @@ -0,0 +1,26 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route-no-auth-on-auth-listener +spec: + parentRefs: + - name: gateway + sectionName: http-auth + rules: + - matches: + - path: + type: PathPrefix + value: "/admin-no-auth" + backendRefs: + - name: static-server + port: 8080 + - matches: + - path: + type: PathPrefix + value: "/pet-no-auth" + backendRefs: + - name: static-server + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml index 52e206a91e..b505d36cb1 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml @@ -16,4 +16,4 @@ spec: value: "/v1" backendRefs: - name: static-server - port: 80 + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml new file mode 100644 index 0000000000..3894e654ff --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml @@ -0,0 +1,32 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route2-auth +spec: + parentRefs: + - name: gateway + sectionName: http-auth + rules: + - matches: + - path: + type: PathPrefix + value: "/admin-2" + backendRefs: + - name: static-server + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteAuthFilter + name: route-jwt-auth-filter + - matches: + - path: + type: PathPrefix + value: "/pet-2" + backendRefs: + - name: static-server + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml index ed0b0bbe5e..9ea3ee2acd 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml @@ -2,14 +2,14 @@ # SPDX-License-Identifier: MPL-2.0 apiVersion: consul.hashicorp.com/v1alpha1 -kind: HTTPRouteAuthFilter +kind: RouteAuthFilter metadata: - name: example-route-jwt-filter + name: route-jwt-auth-filter spec: - type: JWT - JWTProviders: - - Provider: "local" - VerifyClaims: - - Path: - - "role" - Value: "doctor" + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml index 9730a1a4ac..648c936746 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml @@ -1,14 +1,20 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - ../../../bases/api-gateway - ../../static-server-inject -- ./httproute.yaml -- ./jwt-provider.yaml +- httproute-auth.yaml +- httproute-invalid-external-ref.yaml +- httproute2-auth.yaml +- httproute-no-auth-on-auth-listener.yaml +- jwt-provider.yaml +- jwt-route-filter.yaml +- gateway-policy.yaml + -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization patches: -- patch: httproute-no-auth.yaml +- path: httproute.yaml - path: api-gateway.yaml