From 00970f362ab903f7939ed4be576fbd809292e9c7 Mon Sep 17 00:00:00 2001 From: sgayangi Date: Wed, 13 Mar 2024 10:53:41 +0530 Subject: [PATCH] Add mTLS for GraphQL APIs --- .../internal/operator/synchronizer/gql_api.go | 10 ++ .../org/wso2/apk/enforcer/api/GraphQLAPI.java | 3 +- .../graphql_with_disabled_auth.apk-conf | 44 +++++++ .../graphql/graphql_with_mtls.apk-conf | 50 ++++++++ ...th_mtls_optional_oauth2_mandatory.apk-conf | 50 ++++++++ .../test/resources/tests/api/GraphQL.feature | 110 ++++++++++++++++-- 6 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_disabled_auth.apk-conf create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_mtls.apk-conf create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_mtls_optional_oauth2_mandatory.apk-conf diff --git a/adapter/internal/operator/synchronizer/gql_api.go b/adapter/internal/operator/synchronizer/gql_api.go index 95d986fda..b65566600 100644 --- a/adapter/internal/operator/synchronizer/gql_api.go +++ b/adapter/internal/operator/synchronizer/gql_api.go @@ -61,10 +61,20 @@ func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, e RateLimitPolicies: apiState.RateLimitPolicies, ResourceRateLimitPolicies: apiState.ResourceRateLimitPolicies, } + if err := adapterInternalAPI.SetInfoGQLRouteCR(gqlRoute.GQLRouteCombined, resourceParams); err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2631, logging.MAJOR, "Error setting GQLRoute CR info to adapterInternalAPI. %v", err)) return nil, nil, err } + + if apiState.MutualSSL != nil && apiState.MutualSSL.Required != "" && !adapterInternalAPI.GetDisableAuthentications() { + adapterInternalAPI.SetDisableMtls(apiState.MutualSSL.Disabled) + adapterInternalAPI.SetMutualSSL(apiState.MutualSSL.Required) + adapterInternalAPI.SetClientCerts(apiState.APIDefinition.Name, apiState.MutualSSL.ClientCertificates) + } else { + adapterInternalAPI.SetDisableMtls(true) + } + vHosts := getVhostsForGQLAPI(gqlRoute.GQLRouteCombined) labels := getLabelsForGQLAPI(gqlRoute.GQLRouteCombined) listeners, relativeSectionNames := getListenersForGQLAPI(gqlRoute.GQLRouteCombined, adapterInternalAPI.UUID) diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GraphQLAPI.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GraphQLAPI.java index 8552f909d..2a3bb9fdc 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GraphQLAPI.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GraphQLAPI.java @@ -80,7 +80,6 @@ public String init(Api api) { String version = api.getVersion(); String apiType = api.getApiType(); List resources = new ArrayList<>(); - Map mtlsCertificateTiers = new HashMap<>(); String mutualSSL = api.getMutualSSL(); boolean applicationSecurity = api.getApplicationSecurity(); @@ -145,7 +144,7 @@ public String init(Api api) { .resources(resources).apiType(apiType).apiLifeCycleState(apiLifeCycleState).tier(api.getTier()) .envType(api.getEnvType()).disableAuthentication(api.getDisableAuthentications()) .disableScopes(api.getDisableScopes()).trustStore(trustStore).organizationId(api.getOrganizationId()) - .mutualSSL(mutualSSL) + .mutualSSL(mutualSSL).transportSecurity(api.getTransportSecurity()) .applicationSecurity(applicationSecurity).jwtConfigurationDto(jwtConfigurationDto) .apiDefinition(apiDefinition).environment(api.getEnvironment()) .subscriptionValidation(api.getSubscriptionValidation()).graphQLSchemaDTO(graphQLSchemaDTO).build(); diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_disabled_auth.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_disabled_auth.apk-conf new file mode 100644 index 000000000..5df4958be --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_disabled_auth.apk-conf @@ -0,0 +1,44 @@ +--- +name: "GraphQL API" +basePath: "/graphql" +version: "3.14" +type: "GRAPHQL" +id: "graphql-auth-disabled" +defaultVersion: false +subscriptionValidation: false +endpointConfigurations: + production: + endpoint: "http://graphql-faker-service:9002/graphql" +operations: + - target: "hero" + verb: "QUERY" + secured: true + scopes: [] + - target: "reviews" + verb: "QUERY" + secured: true + scopes: [] + - target: "search" + verb: "QUERY" + secured: true + scopes: [] + - target: "character" + verb: "QUERY" + secured: true + scopes: [] + - target: "droid" + verb: "QUERY" + secured: true + scopes: [] + - target: "human" + verb: "QUERY" + secured: true + scopes: [] + - target: "allHumans" + verb: "QUERY" + secured: true + scopes: [] +authentication: + - authType: OAuth2 + required: mandatory + enabled: false diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_mtls.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_mtls.apk-conf new file mode 100644 index 000000000..bb62c7ac0 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_mtls.apk-conf @@ -0,0 +1,50 @@ +--- +name: "GraphQL API" +basePath: "/graphql" +version: "3.14" +type: "GRAPHQL" +id: "graphql-mtls" +defaultVersion: false +subscriptionValidation: false +endpointConfigurations: + production: + endpoint: "http://graphql-faker-service:9002/graphql" +operations: + - target: "hero" + verb: "QUERY" + secured: true + scopes: [] + - target: "reviews" + verb: "QUERY" + secured: true + scopes: [] + - target: "search" + verb: "QUERY" + secured: true + scopes: [] + - target: "character" + verb: "QUERY" + secured: true + scopes: [] + - target: "droid" + verb: "QUERY" + secured: true + scopes: [] + - target: "human" + verb: "QUERY" + secured: true + scopes: [] + - target: "allHumans" + verb: "QUERY" + secured: true + scopes: [] +authentication: + - authType: OAuth2 + required: mandatory + enabled: true + - authType: mTLS + enabled: true + required: mandatory + certificates: + - name: mtls-test-configmap + key: tls.crt diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_mtls_optional_oauth2_mandatory.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_mtls_optional_oauth2_mandatory.apk-conf new file mode 100644 index 000000000..70a4ffabf --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/graphql/graphql_with_mtls_optional_oauth2_mandatory.apk-conf @@ -0,0 +1,50 @@ +--- +name: "GraphQL API" +basePath: "/graphql" +version: "3.14" +type: "GRAPHQL" +id: "graphql-mtls-optional" +defaultVersion: false +subscriptionValidation: false +endpointConfigurations: + production: + endpoint: "http://graphql-faker-service:9002/graphql" +operations: + - target: "hero" + verb: "QUERY" + secured: true + scopes: [] + - target: "reviews" + verb: "QUERY" + secured: true + scopes: [] + - target: "search" + verb: "QUERY" + secured: true + scopes: [] + - target: "character" + verb: "QUERY" + secured: true + scopes: [] + - target: "droid" + verb: "QUERY" + secured: true + scopes: [] + - target: "human" + verb: "QUERY" + secured: true + scopes: [] + - target: "allHumans" + verb: "QUERY" + secured: true + scopes: [] +authentication: + - authType: OAuth2 + required: mandatory + enabled: true + - authType: mTLS + enabled: true + required: mandatory + certificates: + - name: mtls-test-configmap + key: tls.crt diff --git a/test/cucumber-tests/src/test/resources/tests/api/GraphQL.feature b/test/cucumber-tests/src/test/resources/tests/api/GraphQL.feature index 23e7578b2..eb04dd38d 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GraphQL.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GraphQL.feature @@ -19,6 +19,107 @@ Feature: Generating APK conf for GraphQL API | 429 | | 500 | + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "graphql-without-sub" + Then the response status code should be 202 + + Scenario: Deploying APK conf using a valid GraphQL API definition with mTLS mandatory and valid certificate + Given The system is ready + And I have a valid token with a client certificate "config-map-1.txt" + When I use the APK Conf file "artifacts/apk-confs/graphql/graphql_with_mtls.apk-conf" + And the definition file "artifacts/definitions/graphql_sample_api.graphql" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + | Authorization | bearer ${accessToken} | + | X-WSO2-CLIENT-CERTIFICATE | ${clientCertificate} | + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ allHumans { name } }\"}" + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "graphql-mtls" + Then the response status code should be 202 + + Scenario: Deploying APK conf using a valid GraphQL API definition with mTLS mandatory and no certificate + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/graphql/graphql_with_mtls.apk-conf" + And the definition file "artifacts/definitions/graphql_sample_api.graphql" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + | Authorization | bearer ${accessToken} | + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ allHumans { name } }\"}" + And I eventually receive 401 response code, not accepting + | 200 | + | 429 | + | 500 | + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "graphql-mtls" + Then the response status code should be 202 + + Scenario: Deploying APK conf using a valid GraphQL API definition with OAuth2 mandatory mTLS optional + Given The system is ready + And I have a valid token with a client certificate "config-map-1.txt" + When I use the APK Conf file "artifacts/apk-confs/graphql/graphql_with_mtls_optional_oauth2_mandatory.apk-conf" + And the definition file "artifacts/definitions/graphql_sample_api.graphql" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + | Authorization | bearer ${accessToken} | + | X-WSO2-CLIENT-CERTIFICATE | ${clientCertificate} | + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ allHumans { name } }\"}" + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + Then I set headers + | Authorization | bearer ${accessToken} | + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ allHumans { name } }\"}" + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + And I have a valid token with a client certificate "invalid-cert.txt" + Then I set headers + | Authorization | bearer ${accessToken} | + | X-WSO2-CLIENT-CERTIFICATE | ${clientCertificate} | + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ allHumans { name } }\"}" + And I eventually receive 401 response code, not accepting + | 429 | + | 500 | + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "graphql-mtls-optional" + Then the response status code should be 202 + + Scenario: Deploying GraphQL API with OAuth2 disabled + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/graphql/graphql_with_disabled_auth.apk-conf" + And the definition file "artifacts/definitions/graphql_sample_api.graphql" + And make the API deployment request + Then the response status code should be 200 + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ allHumans { name } }\"}" + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "graphql-auth-disabled" + Then the response status code should be 202 + Scenario: Deploying APK conf using a valid GraphQL API definition containing a subscription resource Given The system is ready And I have a valid subscription @@ -36,10 +137,5 @@ Feature: Generating APK conf for GraphQL API Scenario Outline: Undeploy API Given The system is ready And I have a valid subscription - When I undeploy the API whose ID is "" - Then the response status code should be - - Examples: - | apiID | expectedStatusCode | - | graphql-with-sub | 202 | - | graphql-without-sub | 202 | + When I undeploy the API whose ID is "graphql-with-sub" + Then the response status code should be 202 \ No newline at end of file