diff --git a/adapter/api/proto/wso2/discovery/api/api.proto b/adapter/api/proto/wso2/discovery/api/api.proto index 95524398b..887582b7b 100644 --- a/adapter/api/proto/wso2/discovery/api/api.proto +++ b/adapter/api/proto/wso2/discovery/api/api.proto @@ -21,7 +21,9 @@ package wso2.discovery.api; import "wso2/discovery/api/Resource.proto"; import "wso2/discovery/api/Certificate.proto"; import "wso2/discovery/api/BackendJWTTokenInfo.proto"; -// import "wso2/discovery/api/graphql.proto"; +import "wso2/discovery/api/endpoint_cluster.proto"; +import "wso2/discovery/api/security_info.proto"; +import "wso2/discovery/api/graphql.proto"; option go_package = "github.com/envoyproxy/go-control-plane/wso2/discovery/api;api"; option java_package = "org.wso2.apk.enforcer.discovery.api"; @@ -50,10 +52,12 @@ message Api { string mutualSSL = 15; bool applicationSecurity = 16; /// string graphQLSchema = 22; - // repeated GraphqlComplexity graphqlComplexityInfo = 23; + repeated GraphqlComplexity graphqlComplexityInfo = 23; bool systemAPI = 24; BackendJWTTokenInfo backendJWTTokenInfo = 25; bytes apiDefinitionFile = 26; string environment = 27; bool subscriptionValidation = 28; + EndpointCluster endpoints = 29; + repeated SecurityInfo endpointSecurity = 30; } diff --git a/adapter/api/proto/wso2/discovery/config/enforcer/analytics_publisher.proto b/adapter/api/proto/wso2/discovery/config/enforcer/analytics_publisher.proto index a6bbf5505..ac159b6f0 100644 --- a/adapter/api/proto/wso2/discovery/config/enforcer/analytics_publisher.proto +++ b/adapter/api/proto/wso2/discovery/config/enforcer/analytics_publisher.proto @@ -18,8 +18,6 @@ syntax = "proto3"; package wso2.discovery.config.enforcer; -import "wso2/discovery/config/enforcer/service.proto"; - option go_package = "github.com/envoyproxy/go-control-plane/wso2/discovery/config/enforcer;enforcer"; option java_package = "org.wso2.apk.enforcer.discovery.config.enforcer"; option java_outer_classname = "AnalyticsPublisherProto"; diff --git a/adapter/config/default_config.go b/adapter/config/default_config.go index 4a0dfe28d..6abd41fd4 100644 --- a/adapter/config/default_config.go +++ b/adapter/config/default_config.go @@ -95,7 +95,6 @@ var defaultConfig = &Config{ }, }, PayloadPassingToEnforcer: payloadPassingToEnforcer{ - PassRequestPayload: false, MaxRequestBytes: 102400, AllowPartialMessage: false, PackAsBytes: false, diff --git a/adapter/config/types.go b/adapter/config/types.go index 22b0f3a53..14b6e8e4b 100644 --- a/adapter/config/types.go +++ b/adapter/config/types.go @@ -185,7 +185,6 @@ type consul struct { // Router to enforcer request body passing configurations type payloadPassingToEnforcer struct { - PassRequestPayload bool MaxRequestBytes uint32 AllowPartialMessage bool PackAsBytes bool diff --git a/adapter/go.mod b/adapter/go.mod index 795e2f808..70761a6e0 100644 --- a/adapter/go.mod +++ b/adapter/go.mod @@ -2,6 +2,8 @@ module github.com/wso2/apk/adapter go 1.19 +replace github.com/wso2/apk/common-go-libs => ../common-go-libs + require ( github.com/envoyproxy/go-control-plane v0.11.2-0.20230802074621-eea0b3bd0f81 github.com/fsnotify/fsnotify v1.6.0 diff --git a/adapter/go.sum b/adapter/go.sum index 438e9279b..a45b991ba 100644 --- a/adapter/go.sum +++ b/adapter/go.sum @@ -170,8 +170,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/wso2/apk/common-go-libs v0.0.0-20231208100153-24bee7b4bd81 h1:Iobh0zi81XVNjC8dCckNWvr5VkpDwHlbJWq5jmOZtww= -github.com/wso2/apk/common-go-libs v0.0.0-20231208100153-24bee7b4bd81/go.mod h1:fvkFU/8JJpx4Pem9srTjWmD3c89AKsyRpLyNPdQriDc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= diff --git a/adapter/internal/discovery/xds/server.go b/adapter/internal/discovery/xds/server.go index 4d3f4c9b5..098f2fdfd 100644 --- a/adapter/internal/discovery/xds/server.go +++ b/adapter/internal/discovery/xds/server.go @@ -165,7 +165,7 @@ func init() { orgIDvHostBasepathMap = make(map[string]map[string]string) enforcerLabelMap = make(map[string]*EnforcerInternalAPI) - //TODO(amali) currently subscriptions, configs, applications, applicationPolicies, subscriptionPolicies, + // currently subscriptions, configs, applications, applicationPolicies, subscriptionPolicies, // applicationKeyMappings, keyManagerConfigList, revokedTokens are supported with the hard coded label for Enforcer enforcerLabelMap[commonEnforcerLabel] = &EnforcerInternalAPI{} rand.Seed(time.Now().UnixNano()) diff --git a/adapter/internal/oasparser/config_generator.go b/adapter/internal/oasparser/config_generator.go index 76ed824de..0b09afb9d 100644 --- a/adapter/internal/oasparser/config_generator.go +++ b/adapter/internal/oasparser/config_generator.go @@ -211,6 +211,8 @@ func GetEnforcerAPI(adapterInternalAPI model.AdapterInternalAPI, vhost string) * Vhost: vhost, EnvType: adapterInternalAPI.EnvType, BackendJWTTokenInfo: backendJWTTokenInfo, + Endpoints: generateRPCEndpointCluster(adapterInternalAPI.Endpoints), + EndpointSecurity: generateRPCEndpointSecurity(adapterInternalAPI.EndpointSecurity), // IsMockedApi: isMockedAPI, ClientCertificates: clientCertificates, MutualSSL: adapterInternalAPI.GetXWSO2MutualSSL(), diff --git a/adapter/internal/oasparser/constants/constants.go b/adapter/internal/oasparser/constants/constants.go index 3151babda..df863dc5a 100644 --- a/adapter/internal/oasparser/constants/constants.go +++ b/adapter/internal/oasparser/constants/constants.go @@ -97,7 +97,7 @@ const ( REST string = "REST" SOAP string = "SOAP" WS string = "WS" - GRAPHQL string = "GRAPHQL" + GRAPHQL string = "GraphQL" WEBHOOK string = "WEBHOOK" SSE string = "SSE" Prototyped string = "prototyped" @@ -131,3 +131,9 @@ const ( KindScope = "Scope" KindRateLimitPolicy = "RateLimitPolicy" ) + +// API environment types +const ( + Production = "Production" + Sandbox = "Sandbox" +) diff --git a/adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go b/adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go index af43b31ab..da8549d35 100644 --- a/adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go +++ b/adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go @@ -73,7 +73,7 @@ func TestCreateRoute(t *testing.T) { resourceWithGet := model.CreateMinimalDummyResourceForTests("/xWso2BasePath/resourcePath", []*model.Operation{model.NewOperationWithPolicies("GET", policies)}, - "resource_operation_id", []model.Endpoint{}, true) + "resource_operation_id", []model.Endpoint{endpoint}, true) clusterName := "resource_operation_id" hostRewriteSpecifier := &routev3.RouteAction_AutoHostRewrite{ AutoHostRewrite: &wrapperspb.BoolValue{ @@ -107,11 +107,11 @@ func TestCreateRoute(t *testing.T) { }, } - routeParams := generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version, - endpoint.Basepath, &resourceWithGet, clusterName, nil, false) - routeParams.routeConfig = &model.EndpointConfig{ + resourceWithGet.GetEndpoints().Config = &model.EndpointConfig{ IdleTimeoutInSeconds: 300, } + routeParams := generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version, + endpoint.Basepath, &resourceWithGet, clusterName, nil, false) generatedRouteArrayWithXWso2BasePath, err := createRoutes(routeParams) assert.Nil(t, err, "Error while creating routes WithXWso2BasePath") @@ -136,8 +136,14 @@ func TestCreateRouteClusterSpecifier(t *testing.T) { version := "1.0.0" apiType := "HTTP" + endpoint := model.Endpoint{ + Host: "abc.com", + URLType: "http", + Port: 80, + RawURL: "http://abc.com", + } resourceWithGet := model.CreateMinimalDummyResourceForTests("/resourcePath", []*model.Operation{model.NewOperation("GET", nil, nil)}, - "resource_operation_id", []model.Endpoint{}, false) + "resource_operation_id", []model.Endpoint{endpoint}, false) route, err := createRoutes(generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version, endpointBasePath, &resourceWithGet, clusterName, nil, false)) @@ -161,8 +167,14 @@ func TestCreateRouteExtAuthzContext(t *testing.T) { version := "1.0.0" apiType := "HTTP" + endpoint := model.Endpoint{ + Host: "abc.com", + URLType: "http", + Port: 80, + RawURL: "http://abc.com", + } resourceWithGet := model.CreateMinimalDummyResourceForTests("/resourcePath", []*model.Operation{model.NewOperation("GET", nil, nil)}, - "resource_operation_id", []model.Endpoint{}, false) + "resource_operation_id", []model.Endpoint{endpoint}, false) route, err := createRoutes(generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version, endpointBasePath, &resourceWithGet, clusterName, nil, false)) @@ -518,6 +530,12 @@ func TestGetCorsPolicy(t *testing.T) { AccessControlAllowMethods: []string{"GET"}, AccessControlAllowOrigins: []string{"http://test1.com", "http://test2.com"}, } + endpoint := model.Endpoint{ + Host: "abc.com", + URLType: "http", + Port: 80, + RawURL: "http://abc.com", + } // Test the configuration when cors is disabled. corsPolicy1 := getCorsPolicy(corsConfigModel1) @@ -553,7 +571,7 @@ func TestGetCorsPolicy(t *testing.T) { assert.Empty(t, corsPolicy3.GetAllowCredentials(), "Allow Credential property should not be assigned.") resourceWithGet := model.CreateMinimalDummyResourceForTests("/resourcePath", []*model.Operation{model.NewOperation("GET", nil, nil)}, - "resource_operation_id", []model.Endpoint{}, false) + "resource_operation_id", []model.Endpoint{endpoint}, false) // Route without CORS configuration routeWithoutCors, err := createRoutes(generateRouteCreateParamsForUnitTests("test", "HTTP", "localhost", "/test", "1.0.0", "/test", diff --git a/adapter/internal/oasparser/envoyconf/internal_dtos.go b/adapter/internal/oasparser/envoyconf/internal_dtos.go index 3f2454f39..2f72f7caf 100644 --- a/adapter/internal/oasparser/envoyconf/internal_dtos.go +++ b/adapter/internal/oasparser/envoyconf/internal_dtos.go @@ -33,7 +33,7 @@ type routeCreateParams struct { endpointBasePath string resource *model.Resource clusterName string - routeConfig *model.EndpointConfig + endpoints *model.EndpointCluster authHeader string requestInterceptor map[string]model.InterceptEndpoint responseInterceptor map[string]model.InterceptEndpoint diff --git a/adapter/internal/oasparser/envoyconf/listener_test.go b/adapter/internal/oasparser/envoyconf/listener_test.go index bcd5ae03d..c799f134b 100644 --- a/adapter/internal/oasparser/envoyconf/listener_test.go +++ b/adapter/internal/oasparser/envoyconf/listener_test.go @@ -189,17 +189,24 @@ func testCreateRoutesForUnitTests(t *testing.T) []*routev3.Route { AccessControlAllowOrigins: []string{"http://test1.com", "http://test2.com"}, } + endpoint := model.Endpoint{ + Host: "abc.com", + URLType: "http", + Port: 80, + RawURL: "http://abc.com", + } + operationGet := model.NewOperation("GET", nil, nil) operationPost := model.NewOperation("POST", nil, nil) operationPut := model.NewOperation("PUT", nil, nil) resourceWithGet := model.CreateMinimalDummyResourceForTests("/resourcePath", []*model.Operation{operationGet}, - "resource_operation_id", []model.Endpoint{}, false) + "resource_operation_id", []model.Endpoint{endpoint}, false) resourceWithPost := model.CreateMinimalDummyResourceForTests("/resourcePath", []*model.Operation{operationPost}, - "resource_operation_id", []model.Endpoint{}, false) + "resource_operation_id", []model.Endpoint{endpoint}, false) resourceWithPut := model.CreateMinimalDummyResourceForTests("/resourcePath", []*model.Operation{operationPut}, - "resource_operation_id", []model.Endpoint{}, false) + "resource_operation_id", []model.Endpoint{endpoint}, false) resourceWithMultipleOperations := model.CreateMinimalDummyResourceForTests("/resourcePath", []*model.Operation{operationGet, operationPut}, - "resource_operation_id", []model.Endpoint{}, false) + "resource_operation_id", []model.Endpoint{endpoint}, false) route1, err := createRoutes(generateRouteCreateParamsForUnitTests("test", "HTTP", "localhost", "/test", "1.0.0", "/test", &resourceWithGet, "test-cluster", corsConfigModel3, false)) diff --git a/adapter/internal/oasparser/envoyconf/routes_configs.go b/adapter/internal/oasparser/envoyconf/routes_configs.go index 20e54fc36..373e6d215 100644 --- a/adapter/internal/oasparser/envoyconf/routes_configs.go +++ b/adapter/internal/oasparser/envoyconf/routes_configs.go @@ -207,9 +207,8 @@ func generateHeaderMatcher(headerName, valueRegex string) *routev3.HeaderMatcher return headerMatcherArray } -func generateRegexMatchAndSubstitute(routePath, endpointBasePath, - endpointResourcePath string, pathMatchType gwapiv1b1.PathMatchType) *envoy_type_matcherv3.RegexMatchAndSubstitute { - +func generateRegexMatchAndSubstitute(routePath, endpointResourcePath string, + pathMatchType gwapiv1b1.PathMatchType) *envoy_type_matcherv3.RegexMatchAndSubstitute { substitutionString := generateSubstitutionString(endpointResourcePath, pathMatchType) return &envoy_type_matcherv3.RegexMatchAndSubstitute{ Pattern: &envoy_type_matcherv3.RegexMatcher{ @@ -259,8 +258,8 @@ func generateHeaderToRemoveString(policyParams interface{}) (string, error) { return requestHeaderToRemove, nil } -func generateRewritePathRouteConfig(routePath, endpointBasepath string, - policyParams interface{}, pathMatchType gwapiv1b1.PathMatchType, isDefaultVersion bool) (*envoy_type_matcherv3.RegexMatchAndSubstitute, error) { +func generateRewritePathRouteConfig(routePath string, policyParams interface{}, pathMatchType gwapiv1b1.PathMatchType, + isDefaultVersion bool) (*envoy_type_matcherv3.RegexMatchAndSubstitute, error) { var paramsToSetHeader map[string]interface{} var ok bool diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 7d30b679c..9d8ca4326 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -127,11 +127,11 @@ func CreateRoutesWithClusters(adapterInternalAPI model.AdapterInternalAPI, inter methods = append(methods, "GET") } routeP := CreateAPIDefinitionEndpoint(adapterInternalAPI.GetXWso2Basepath(), vHost, methods, false, adapterInternalAPI.GetVersion(), adapterInternalAPI.GetAPIDefinitionEndpoint()) + routes = append(routes, routeP) if (&adapterInternalAPI).IsDefaultVersion { defaultDefRoutes := CreateAPIDefinitionEndpoint(adapterInternalAPI.GetXWso2Basepath(), vHost, methods, true, adapterInternalAPI.GetVersion(), adapterInternalAPI.GetAPIDefinitionEndpoint()) routes = append(routes, defaultDefRoutes) } - routes = append(routes, routeP) var endpointForAPIDefinitions []model.Endpoint endpoint := &model.Endpoint{ // Localhost is set as the two containers are in the same pod @@ -150,6 +150,49 @@ func CreateRoutesWithClusters(adapterInternalAPI model.AdapterInternalAPI, inter clusters = append(clusters, cluster) endpoints = append(endpoints, address...) + if adapterInternalAPI.GetAPIType() == constants.GRAPHQL { + basePath := strings.TrimSuffix(adapterInternalAPI.Endpoints.Endpoints[0].Basepath, "/") + + clusterName := getClusterName(adapterInternalAPI.Endpoints.EndpointPrefix, organizationID, vHost, + adapterInternalAPI.GetTitle(), apiVersion, "") + cluster, address, err := processEndpoints(clusterName, adapterInternalAPI.Endpoints, timeout, basePath) + + if err != nil { + logger.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2239, logging.MAJOR, + "Error while adding gql endpoints for %s:%v. %v", apiTitle, apiVersion, err.Error())) + return nil, nil, nil, fmt.Errorf("error while adding gql endpoints for %s:%v. %v", apiTitle, apiVersion, + err.Error()) + } + clusters = append(clusters, cluster) + endpoints = append(endpoints, address...) + + // The current code requires to create policy for all routes to support backend endpoint. + policyParameters := make(map[string]interface{}) + policyParameters[constants.RewritePathType] = gwapiv1b1.FullPathHTTPPathModifier + policyParameters[constants.IncludeQueryParams] = true + policyParameters[constants.RewritePathResourcePath] = basePath + var policies = model.OperationPolicies{ + Request: []model.Policy{ + { + PolicyName: string(gwapiv1b1.HTTPRouteFilterURLRewrite), + Action: constants.ActionRewritePath, + Parameters: policyParameters, + }, + }, + } + gqlop := model.NewOperationWithPolicies("POST", policies) + resource := model.CreateMinimalResource(adapterInternalAPI.GetXWso2Basepath(), []*model.Operation{gqlop}, "", adapterInternalAPI.Endpoints, true, gwapiv1b1.PathMatchExact) + routesP, err := createRoutes(genRouteCreateParams(&adapterInternalAPI, &resource, vHost, basePath, clusterName, nil, + nil, organizationID, false, false)) + if err != nil { + logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2231, logging.MAJOR, + "Error while creating routes for GQL API %s %s Error: %s", adapterInternalAPI.GetTitle(), + adapterInternalAPI.GetVersion(), err.Error())) + return nil, nil, nil, fmt.Errorf("error while creating routes. %v", err) + } + routes = append(routes, routesP...) + return routes, clusters, endpoints, nil + } for _, resource := range adapterInternalAPI.GetResources() { var clusterName string resourcePath := resource.GetPath() @@ -651,6 +694,9 @@ func createTLSProtocolVersion(tlsVersion string) tlsv3.TlsParameters_TlsProtocol // createRoutes creates route elements for the route configurations. API title, VHost, xWso2Basepath, API version, // endpoint's basePath, resource Object (Microgateway's internal representation), clusterName needs to be provided. func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error) { + if params.resource == nil || params.resource.GetEndpoints() == nil { + return nil, errors.New("resource and resource endpoints cannot be empty") + } title := params.title version := params.version vHost := params.vHost @@ -661,7 +707,6 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error corsPolicy := getCorsPolicy(params.corsPolicy) resource := params.resource clusterName := params.clusterName - routeConfig := params.routeConfig endpointBasepath := params.endpointBasePath requestInterceptor := params.requestInterceptor responseInterceptor := params.responseInterceptor @@ -671,16 +716,9 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error basePath := strings.TrimSuffix(xWso2Basepath, "/") - resourcePath := "" - var resourceMethods []string - var pathMatchType gwapiv1b1.PathMatchType - if params.apiType == constants.GRAPHQL { - resourceMethods = []string{"POST"} - } else { - resourcePath = resource.GetPath() - resourceMethods = resource.GetMethodList() - pathMatchType = resource.GetPathMatchType() - } + resourcePath := resource.GetPath() + resourceMethods := resource.GetMethodList() + pathMatchType := resource.GetPathMatchType() contextExtensions := make(map[string]string) contextExtensions[pathContextExtension] = resourcePath @@ -840,8 +878,8 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error Operation: vHost + ":" + routePath, } } - - if resource != nil && resource.HasPolicies() { + routeConfig := resource.GetEndpoints().Config + if resource.HasPolicies() { logger.LoggerOasparser.Debug("Start creating routes for resource with policies") operations := resource.GetOperations() @@ -898,8 +936,8 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error case constants.ActionRewritePath: logger.LoggerOasparser.Debugf("Adding %s policy to request flow for %s %s", constants.ActionRewritePath, resourcePath, operation.GetMethod()) - regexRewrite, err := generateRewritePathRouteConfig(routePath, endpointBasepath, - requestPolicy.Parameters, pathMatchType, isDefaultVersion) + regexRewrite, err := generateRewritePathRouteConfig(routePath, requestPolicy.Parameters, pathMatchType, + isDefaultVersion) if err != nil { errMsg := fmt.Sprintf("Error adding request policy %s to operation %s of resource %s. %v", constants.ActionRewritePath, operation.GetMethod(), resourcePath, err) @@ -982,7 +1020,7 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error if pathRewriteConfig != nil { action2.Route.RegexRewrite = pathRewriteConfig } else { - action2.Route.RegexRewrite = generateRegexMatchAndSubstitute(routePath, endpointBasepath, resourcePath, pathMatchType) + action2.Route.RegexRewrite = generateRegexMatchAndSubstitute(routePath, resourcePath, pathMatchType) } configToSkipEnforcer := generateFilterConfigToSkipEnforcer() route2 := generateRouteConfig(xWso2Basepath, match2, action2, nil, decorator, configToSkipEnforcer, @@ -1000,13 +1038,12 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error if pathRewriteConfig != nil { action.Route.RegexRewrite = pathRewriteConfig } else { - action.Route.RegexRewrite = generateRegexMatchAndSubstitute(routePath, endpointBasepath, resourcePath, pathMatchType) + action.Route.RegexRewrite = generateRegexMatchAndSubstitute(routePath, resourcePath, pathMatchType) } route := generateRouteConfig(xWso2Basepath, match, action, nil, decorator, perRouteFilterConfigs, requestHeadersToAdd, requestHeadersToRemove, responseHeadersToAdd, responseHeadersToRemove) routes = append(routes, route) } - } } else { logger.LoggerOasparser.Debugf("Creating routes for resource : %s that has no policies", resourcePath) @@ -1019,7 +1056,7 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error match.Headers = generateHTTPMethodMatcher(methodRegex, clusterName) action := generateRouteAction(apiType, routeConfig, rateLimitPolicyCriteria) rewritePath := generateRoutePathForReWrite(basePath, resourcePath, pathMatchType) - action.Route.RegexRewrite = generateRegexMatchAndSubstitute(rewritePath, endpointBasepath, resourcePath, pathMatchType) + action.Route.RegexRewrite = generateRegexMatchAndSubstitute(rewritePath, resourcePath, pathMatchType) route := generateRouteConfig(xWso2Basepath, match, action, nil, decorator, perRouteFilterConfigs, nil, nil, nil, nil) // general headers to add and remove are included in this methods @@ -1156,10 +1193,7 @@ func CreateAPIDefinitionRoute(basePath string, vHost string, methods []string, i // CreateAPIDefinitionEndpoint generates a route for the api defition endpoint func CreateAPIDefinitionEndpoint(basePath string, vHost string, methods []string, isDefaultversion bool, version string, providedAPIDefinitionPath string) *routev3.Route { - endpoint := apiDefinitionPath - if providedAPIDefinitionPath != "" { - endpoint = providedAPIDefinitionPath - } + endpoint := providedAPIDefinitionPath rewritePath := basePath + "/" + vHost + "?" + apiDefinitionQueryParam basePath = strings.TrimSuffix(basePath, "/") var ( @@ -1528,7 +1562,6 @@ func genRouteCreateParams(swagger *model.AdapterInternalAPI, resource *model.Res isDefaultVersion: swagger.IsDefaultVersion, apiLevelRateLimitPolicy: swagger.RateLimitPolicy, apiProperties: swagger.APIProperties, - routeConfig: resource.GetEndpoints().Config, createDefaultPath: createDefaultPath, environment: swagger.GetEnvironment(), envType: swagger.EnvType, diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 4d40aeeb7..01807d77a 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -25,12 +25,18 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/wso2/apk/adapter/config" "github.com/wso2/apk/adapter/internal/interceptor" + "github.com/wso2/apk/adapter/internal/loggers" logger "github.com/wso2/apk/adapter/internal/loggers" "github.com/wso2/apk/adapter/internal/oasparser/constants" + "github.com/wso2/apk/adapter/internal/operator/utils" dpv1alpha1 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha1" dpv1alpha2 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha2" + "golang.org/x/exp/maps" + "k8s.io/apimachinery/pkg/types" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) // AdapterInternalAPI represents the object structure holding the information related to the @@ -44,7 +50,6 @@ type AdapterInternalAPI struct { title string version string vendorExtensions map[string]interface{} - xWso2Endpoints map[string]*EndpointCluster resources []*Resource xWso2Basepath string xWso2HTTP2BackendEnabled bool @@ -70,9 +75,11 @@ type AdapterInternalAPI struct { APIProperties []dpv1alpha2.Property // GraphQLSchema string // GraphQLComplexities GraphQLComplexityYaml - IsSystemAPI bool - RateLimitPolicy *RateLimitPolicy - environment string + IsSystemAPI bool + RateLimitPolicy *RateLimitPolicy + environment string + Endpoints *EndpointCluster + EndpointSecurity []*EndpointSecurity } // BackendJWTTokenInfo represents the object structure holding the information related to the JWT Generator @@ -225,213 +232,702 @@ type Certificate struct { } // GetAPIDefinitionFile returns the API Definition File. -func (swagger *AdapterInternalAPI) GetAPIDefinitionFile() []byte { - return swagger.apiDefinitionFile +func (adapterInternalAPI *AdapterInternalAPI) GetAPIDefinitionFile() []byte { + return adapterInternalAPI.apiDefinitionFile } // GetAPIDefinitionEndpoint returns the API Definition Endpoint. -func (swagger *AdapterInternalAPI) GetAPIDefinitionEndpoint() string { - return swagger.apiDefinitionEndpoint +func (adapterInternalAPI *AdapterInternalAPI) GetAPIDefinitionEndpoint() string { + return adapterInternalAPI.apiDefinitionEndpoint } // GetSubscriptionValidation returns the subscription validation status. -func (swagger *AdapterInternalAPI) GetSubscriptionValidation() bool { - return swagger.subscriptionValidation +func (adapterInternalAPI *AdapterInternalAPI) GetSubscriptionValidation() bool { + return adapterInternalAPI.subscriptionValidation } // GetBackendJWTTokenInfo returns the BackendJWTTokenInfo Object. -func (swagger *AdapterInternalAPI) GetBackendJWTTokenInfo() *BackendJWTTokenInfo { - return swagger.backendJWTTokenInfo +func (adapterInternalAPI *AdapterInternalAPI) GetBackendJWTTokenInfo() *BackendJWTTokenInfo { + return adapterInternalAPI.backendJWTTokenInfo } // GetCorsConfig returns the CorsConfiguration Object. -func (swagger *AdapterInternalAPI) GetCorsConfig() *CorsConfig { - return swagger.xWso2Cors +func (adapterInternalAPI *AdapterInternalAPI) GetCorsConfig() *CorsConfig { + return adapterInternalAPI.xWso2Cors } // GetAPIType returns the openapi version -func (swagger *AdapterInternalAPI) GetAPIType() string { - return swagger.apiType +func (adapterInternalAPI *AdapterInternalAPI) GetAPIType() string { + return adapterInternalAPI.apiType } // GetVersion returns the API version -func (swagger *AdapterInternalAPI) GetVersion() string { - return swagger.version +func (adapterInternalAPI *AdapterInternalAPI) GetVersion() string { + return adapterInternalAPI.version } // GetTitle returns the API Title -func (swagger *AdapterInternalAPI) GetTitle() string { - return swagger.title +func (adapterInternalAPI *AdapterInternalAPI) GetTitle() string { + return adapterInternalAPI.title } // GetXWso2Basepath returns the basepath set via the vendor extension. -func (swagger *AdapterInternalAPI) GetXWso2Basepath() string { - return swagger.xWso2Basepath +func (adapterInternalAPI *AdapterInternalAPI) GetXWso2Basepath() string { + return adapterInternalAPI.xWso2Basepath } // GetXWso2HTTP2BackendEnabled returns the http2 backend enabled set via the vendor extension. -func (swagger *AdapterInternalAPI) GetXWso2HTTP2BackendEnabled() bool { - return swagger.xWso2HTTP2BackendEnabled +func (adapterInternalAPI *AdapterInternalAPI) GetXWso2HTTP2BackendEnabled() bool { + return adapterInternalAPI.xWso2HTTP2BackendEnabled } // GetVendorExtensions returns the map of vendor extensions which are defined // at openAPI's root level. -func (swagger *AdapterInternalAPI) GetVendorExtensions() map[string]interface{} { - return swagger.vendorExtensions -} - -// GetXWso2Endpoints returns the array of x wso2 endpoints. -func (swagger *AdapterInternalAPI) GetXWso2Endpoints() map[string]*EndpointCluster { - return swagger.xWso2Endpoints +func (adapterInternalAPI *AdapterInternalAPI) GetVendorExtensions() map[string]interface{} { + return adapterInternalAPI.vendorExtensions } // GetResources returns the array of resources (openAPI path level info) -func (swagger *AdapterInternalAPI) GetResources() []*Resource { - return swagger.resources +func (adapterInternalAPI *AdapterInternalAPI) GetResources() []*Resource { + return adapterInternalAPI.resources } // GetDescription returns the description of the openapi -func (swagger *AdapterInternalAPI) GetDescription() string { - return swagger.description +func (adapterInternalAPI *AdapterInternalAPI) GetDescription() string { + return adapterInternalAPI.description } // GetXWso2ThrottlingTier returns the Throttling tier via the vendor extension. -func (swagger *AdapterInternalAPI) GetXWso2ThrottlingTier() string { - return swagger.xWso2ThrottlingTier +func (adapterInternalAPI *AdapterInternalAPI) GetXWso2ThrottlingTier() string { + return adapterInternalAPI.xWso2ThrottlingTier } // GetDisableAuthentications returns the authType via the vendor extension. -func (swagger *AdapterInternalAPI) GetDisableAuthentications() bool { - return swagger.disableAuthentications +func (adapterInternalAPI *AdapterInternalAPI) GetDisableAuthentications() bool { + return adapterInternalAPI.disableAuthentications } // GetDisableScopes returns the authType via the vendor extension. -func (swagger *AdapterInternalAPI) GetDisableScopes() bool { - return swagger.disableScopes +func (adapterInternalAPI *AdapterInternalAPI) GetDisableScopes() bool { + return adapterInternalAPI.disableScopes } // GetID returns the Id of the API -func (swagger *AdapterInternalAPI) GetID() string { - return swagger.id +func (adapterInternalAPI *AdapterInternalAPI) GetID() string { + return adapterInternalAPI.id } // GetXWso2RequestBodyPass returns boolean value to indicate // whether it is allowed to pass request body to the enforcer or not. -func (swagger *AdapterInternalAPI) GetXWso2RequestBodyPass() bool { - return swagger.xWso2RequestBodyPass +func (adapterInternalAPI *AdapterInternalAPI) GetXWso2RequestBodyPass() bool { + return adapterInternalAPI.xWso2RequestBodyPass +} + +// SetXWso2RequestBodyPass returns boolean value to indicate +// whether it is allowed to pass request body to the enforcer or not. +func (adapterInternalAPI *AdapterInternalAPI) SetXWso2RequestBodyPass(passBody bool) { + adapterInternalAPI.xWso2RequestBodyPass = passBody } // GetClientCerts returns the client certificates of the API -func (swagger *AdapterInternalAPI) GetClientCerts() []Certificate { - return swagger.clientCertificates +func (adapterInternalAPI *AdapterInternalAPI) GetClientCerts() []Certificate { + return adapterInternalAPI.clientCertificates } // SetClientCerts set the client certificates of the API -func (swagger *AdapterInternalAPI) SetClientCerts(certs []Certificate) { - swagger.clientCertificates = certs +func (adapterInternalAPI *AdapterInternalAPI) SetClientCerts(certs []Certificate) { + adapterInternalAPI.clientCertificates = certs } // SetID set the Id of the API -func (swagger *AdapterInternalAPI) SetID(id string) { - swagger.id = id +func (adapterInternalAPI *AdapterInternalAPI) SetID(id string) { + adapterInternalAPI.id = id } // SetAPIDefinitionFile sets the API Definition File. -func (swagger *AdapterInternalAPI) SetAPIDefinitionFile(file []byte) { - swagger.apiDefinitionFile = file +func (adapterInternalAPI *AdapterInternalAPI) SetAPIDefinitionFile(file []byte) { + adapterInternalAPI.apiDefinitionFile = file } // SetAPIDefinitionEndpoint sets the API Definition Endpoint. -func (swagger *AdapterInternalAPI) SetAPIDefinitionEndpoint(endpoint string) { - swagger.apiDefinitionEndpoint = endpoint +func (adapterInternalAPI *AdapterInternalAPI) SetAPIDefinitionEndpoint(endpoint string) { + adapterInternalAPI.apiDefinitionEndpoint = endpoint } // SetSubscriptionValidation sets the subscription validation status. -func (swagger *AdapterInternalAPI) SetSubscriptionValidation(subscriptionValidation bool) { - swagger.subscriptionValidation = subscriptionValidation +func (adapterInternalAPI *AdapterInternalAPI) SetSubscriptionValidation(subscriptionValidation bool) { + adapterInternalAPI.subscriptionValidation = subscriptionValidation } // SetName sets the name of the API -func (swagger *AdapterInternalAPI) SetName(name string) { - swagger.title = name +func (adapterInternalAPI *AdapterInternalAPI) SetName(name string) { + adapterInternalAPI.title = name } // SetVersion sets the version of the API -func (swagger *AdapterInternalAPI) SetVersion(version string) { - swagger.version = version +func (adapterInternalAPI *AdapterInternalAPI) SetVersion(version string) { + adapterInternalAPI.version = version } // SetIsDefaultVersion sets whether this API is the default -func (swagger *AdapterInternalAPI) SetIsDefaultVersion(isDefaultVersion bool) { - swagger.IsDefaultVersion = isDefaultVersion +func (adapterInternalAPI *AdapterInternalAPI) SetIsDefaultVersion(isDefaultVersion bool) { + adapterInternalAPI.IsDefaultVersion = isDefaultVersion } // SetXWso2AuthHeader sets the authHeader of the API -func (swagger *AdapterInternalAPI) SetXWso2AuthHeader(authHeader string) { - if swagger.xWso2AuthHeader == "" { - swagger.xWso2AuthHeader = authHeader +func (adapterInternalAPI *AdapterInternalAPI) SetXWso2AuthHeader(authHeader string) { + if adapterInternalAPI.xWso2AuthHeader == "" { + adapterInternalAPI.xWso2AuthHeader = authHeader } } // GetXWSO2AuthHeader returns the auth header set via the vendor extension. -func (swagger *AdapterInternalAPI) GetXWSO2AuthHeader() string { - return swagger.xWso2AuthHeader +func (adapterInternalAPI *AdapterInternalAPI) GetXWSO2AuthHeader() string { + return adapterInternalAPI.xWso2AuthHeader } // SetXWSO2MutualSSL sets the optional or mandatory mTLS -func (swagger *AdapterInternalAPI) SetXWSO2MutualSSL(mutualSSl string) { - swagger.xWso2MutualSSL = mutualSSl +func (adapterInternalAPI *AdapterInternalAPI) SetXWSO2MutualSSL(mutualSSl string) { + adapterInternalAPI.xWso2MutualSSL = mutualSSl } // GetXWSO2MutualSSL returns the optional or mandatory mTLS -func (swagger *AdapterInternalAPI) GetXWSO2MutualSSL() string { - return swagger.xWso2MutualSSL +func (adapterInternalAPI *AdapterInternalAPI) GetXWSO2MutualSSL() string { + return adapterInternalAPI.xWso2MutualSSL } // SetXWSO2ApplicationSecurity sets the optional or mandatory application security -func (swagger *AdapterInternalAPI) SetXWSO2ApplicationSecurity(applicationSecurity bool) { - swagger.xWso2ApplicationSecurity = applicationSecurity +func (adapterInternalAPI *AdapterInternalAPI) SetXWSO2ApplicationSecurity(applicationSecurity bool) { + adapterInternalAPI.xWso2ApplicationSecurity = applicationSecurity } // GetXWSO2ApplicationSecurity returns the optional or mandatory application security -func (swagger *AdapterInternalAPI) GetXWSO2ApplicationSecurity() bool { - return swagger.xWso2ApplicationSecurity +func (adapterInternalAPI *AdapterInternalAPI) GetXWSO2ApplicationSecurity() bool { + return adapterInternalAPI.xWso2ApplicationSecurity } // GetOrganizationID returns OrganizationID -func (swagger *AdapterInternalAPI) GetOrganizationID() string { - return swagger.OrganizationID +func (adapterInternalAPI *AdapterInternalAPI) GetOrganizationID() string { + return adapterInternalAPI.OrganizationID } // SetEnvironment sets the environment of the API. -func (swagger *AdapterInternalAPI) SetEnvironment(environment string) { - swagger.environment = environment +func (adapterInternalAPI *AdapterInternalAPI) SetEnvironment(environment string) { + adapterInternalAPI.environment = environment } // GetEnvironment returns the environment of the API -func (swagger *AdapterInternalAPI) GetEnvironment() string { - return swagger.environment +func (adapterInternalAPI *AdapterInternalAPI) GetEnvironment() string { + return adapterInternalAPI.environment } // Validate method confirms that the adapterInternalAPI has all required fields in the required format. // This needs to be checked prior to generate router/enforcer related resources. -func (swagger *AdapterInternalAPI) Validate() error { - for _, res := range swagger.resources { +func (adapterInternalAPI *AdapterInternalAPI) Validate() error { + for _, res := range adapterInternalAPI.resources { if res.endpoints == nil || len(res.endpoints.Endpoints) == 0 { logger.LoggerOasparser.Errorf("No Endpoints are provided for the resources in %s:%s, API_UUID: %v", - swagger.title, swagger.version, swagger.UUID) + adapterInternalAPI.title, adapterInternalAPI.version, adapterInternalAPI.UUID) return errors.New("no endpoints are provided for the API") } err := res.endpoints.validateEndpointCluster() if err != nil { logger.LoggerOasparser.Errorf("Error while parsing the endpoints of the API %s:%s - %v, API_UUID: %v", - swagger.title, swagger.version, err, swagger.UUID) + adapterInternalAPI.title, adapterInternalAPI.version, err, adapterInternalAPI.UUID) return err } } return nil } +// SetInfoHTTPRouteCR populates resources and endpoints of adapterInternalAPI. httpRoute.Spec.Rules.Matches +// are used to create resources and httpRoute.Spec.Rules.BackendRefs are used to create EndpointClusters. +func (adapterInternalAPI *AdapterInternalAPI) SetInfoHTTPRouteCR(httpRoute *gwapiv1b1.HTTPRoute, resourceParams ResourceParams) error { + var resources []*Resource + outputAuthScheme := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.AuthSchemes))) + outputAPIPolicy := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.APIPolicies))) + outputRatelimitPolicy := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.RateLimitPolicies))) + + disableScopes := true + config := config.ReadConfigs() + + var authScheme *dpv1alpha1.Authentication + if outputAuthScheme != nil { + authScheme = *outputAuthScheme + } + var apiPolicy *dpv1alpha2.APIPolicy + if outputAPIPolicy != nil { + apiPolicy = *outputAPIPolicy + } + var ratelimitPolicy *dpv1alpha1.RateLimitPolicy + if outputRatelimitPolicy != nil { + ratelimitPolicy = *outputRatelimitPolicy + } + + for _, rule := range httpRoute.Spec.Rules { + var endPoints []Endpoint + var policies = OperationPolicies{} + var circuitBreaker *dpv1alpha1.CircuitBreaker + var healthCheck *dpv1alpha1.HealthCheck + resourceAuthScheme := authScheme + resourceAPIPolicy := apiPolicy + resourceRatelimitPolicy := ratelimitPolicy + var scopes []string + var timeoutInMillis uint32 + var idleTimeoutInSeconds uint32 + isRetryConfig := false + isRouteTimeout := false + var backendRetryCount uint32 + var statusCodes []uint32 + statusCodes = append(statusCodes, config.Envoy.Upstream.Retry.StatusCodes...) + var baseIntervalInMillis uint32 + hasURLRewritePolicy := false + var securityConfig []EndpointSecurity + backendBasePath := "" + for _, backend := range rule.BackendRefs { + backendName := types.NamespacedName{ + Name: string(backend.Name), + Namespace: utils.GetNamespace(backend.Namespace, httpRoute.Namespace), + } + resolvedBackend, ok := resourceParams.BackendMapping[backendName.String()] + if ok { + if resolvedBackend.CircuitBreaker != nil { + circuitBreaker = &dpv1alpha1.CircuitBreaker{ + MaxConnections: resolvedBackend.CircuitBreaker.MaxConnections, + MaxPendingRequests: resolvedBackend.CircuitBreaker.MaxPendingRequests, + MaxRequests: resolvedBackend.CircuitBreaker.MaxRequests, + MaxRetries: resolvedBackend.CircuitBreaker.MaxRetries, + MaxConnectionPools: resolvedBackend.CircuitBreaker.MaxConnectionPools, + } + } + if resolvedBackend.Timeout != nil { + isRouteTimeout = true + timeoutInMillis = resolvedBackend.Timeout.UpstreamResponseTimeout * 1000 + idleTimeoutInSeconds = resolvedBackend.Timeout.DownstreamRequestIdleTimeout + } + + if resolvedBackend.Retry != nil { + isRetryConfig = true + backendRetryCount = resolvedBackend.Retry.Count + baseIntervalInMillis = resolvedBackend.Retry.BaseIntervalMillis + if len(resolvedBackend.Retry.StatusCodes) > 0 { + statusCodes = resolvedBackend.Retry.StatusCodes + } + } + if resolvedBackend.HealthCheck != nil { + healthCheck = &dpv1alpha1.HealthCheck{ + Interval: resolvedBackend.HealthCheck.Interval, + Timeout: resolvedBackend.HealthCheck.Timeout, + UnhealthyThreshold: resolvedBackend.HealthCheck.UnhealthyThreshold, + HealthyThreshold: resolvedBackend.HealthCheck.HealthyThreshold, + } + } + endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) + backendBasePath = GetBackendBasePath(backendName, resourceParams.BackendMapping) + switch resolvedBackend.Security.Type { + case "Basic": + securityConfig = append(securityConfig, EndpointSecurity{ + Password: string(resolvedBackend.Security.Basic.Password), + Username: string(resolvedBackend.Security.Basic.Username), + Type: string(resolvedBackend.Security.Type), + Enabled: true, + }) + } + } else { + return fmt.Errorf("backend: %s has not been resolved", backendName) + } + } + for _, filter := range rule.Filters { + switch filter.Type { + case gwapiv1b1.HTTPRouteFilterURLRewrite: + policyParameters := make(map[string]interface{}) + policyParameters[constants.RewritePathType] = filter.URLRewrite.Path.Type + policyParameters[constants.IncludeQueryParams] = true + + switch filter.URLRewrite.Path.Type { + case gwapiv1b1.FullPathHTTPPathModifier: + policyParameters[constants.RewritePathResourcePath] = backendBasePath + *filter.URLRewrite.Path.ReplaceFullPath + case gwapiv1b1.PrefixMatchHTTPPathModifier: + policyParameters[constants.RewritePathResourcePath] = backendBasePath + *filter.URLRewrite.Path.ReplacePrefixMatch + } + + policies.Request = append(policies.Request, Policy{ + PolicyName: string(gwapiv1b1.HTTPRouteFilterURLRewrite), + Action: constants.ActionRewritePath, + Parameters: policyParameters, + }) + hasURLRewritePolicy = true + case gwapiv1b1.HTTPRouteFilterExtensionRef: + if filter.ExtensionRef.Kind == constants.KindAuthentication { + if ref, found := resourceParams.ResourceAuthSchemes[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: httpRoute.Namespace, + }.String()]; found { + resourceAuthScheme = concatAuthSchemes(authScheme, &ref) + } else { + return fmt.Errorf(`auth scheme: %s has not been resolved, spec.targetRef.kind should be + 'Resource' in resource level Authentications`, filter.ExtensionRef.Name) + } + } + if filter.ExtensionRef.Kind == constants.KindAPIPolicy { + if ref, found := resourceParams.ResourceAPIPolicies[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: httpRoute.Namespace, + }.String()]; found { + resourceAPIPolicy = concatAPIPolicies(apiPolicy, &ref) + } else { + return fmt.Errorf(`apipolicy: %s has not been resolved, spec.targetRef.kind should be + 'Resource' in resource level APIPolicies`, filter.ExtensionRef.Name) + } + } + if filter.ExtensionRef.Kind == constants.KindScope { + if ref, found := resourceParams.ResourceScopes[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: httpRoute.Namespace, + }.String()]; found { + scopes = ref.Spec.Names + disableScopes = false + } else { + return fmt.Errorf("scope: %s has not been resolved in namespace %s", filter.ExtensionRef.Name, httpRoute.Namespace) + } + } + if filter.ExtensionRef.Kind == constants.KindRateLimitPolicy { + if ref, found := resourceParams.ResourceRateLimitPolicies[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: httpRoute.Namespace, + }.String()]; found { + resourceRatelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, &ref) + } else { + return fmt.Errorf(`ratelimitpolicy: %s has not been resolved, spec.targetRef.kind should be + 'Resource' in resource level RateLimitPolicies`, filter.ExtensionRef.Name) + } + } + case gwapiv1b1.HTTPRouteFilterRequestHeaderModifier: + for _, header := range filter.RequestHeaderModifier.Add { + policyParameters := make(map[string]interface{}) + policyParameters[constants.HeaderName] = string(header.Name) + policyParameters[constants.HeaderValue] = string(header.Value) + + policies.Request = append(policies.Request, Policy{ + PolicyName: string(gwapiv1b1.HTTPRouteFilterRequestHeaderModifier), + Action: constants.ActionHeaderAdd, + Parameters: policyParameters, + }) + } + for _, header := range filter.RequestHeaderModifier.Remove { + policyParameters := make(map[string]interface{}) + policyParameters[constants.HeaderName] = string(header) + + policies.Request = append(policies.Request, Policy{ + PolicyName: string(gwapiv1b1.HTTPRouteFilterRequestHeaderModifier), + Action: constants.ActionHeaderRemove, + Parameters: policyParameters, + }) + } + for _, header := range filter.RequestHeaderModifier.Set { + policyParameters := make(map[string]interface{}) + policyParameters[constants.HeaderName] = string(header.Name) + policyParameters[constants.HeaderValue] = string(header.Value) + + policies.Request = append(policies.Request, Policy{ + PolicyName: string(gwapiv1b1.HTTPRouteFilterRequestHeaderModifier), + Action: constants.ActionHeaderAdd, + Parameters: policyParameters, + }) + } + case gwapiv1b1.HTTPRouteFilterResponseHeaderModifier: + for _, header := range filter.ResponseHeaderModifier.Add { + policyParameters := make(map[string]interface{}) + policyParameters[constants.HeaderName] = string(header.Name) + policyParameters[constants.HeaderValue] = string(header.Value) + + policies.Response = append(policies.Response, Policy{ + PolicyName: string(gwapiv1b1.HTTPRouteFilterResponseHeaderModifier), + Action: constants.ActionHeaderAdd, + Parameters: policyParameters, + }) + } + for _, header := range filter.ResponseHeaderModifier.Remove { + policyParameters := make(map[string]interface{}) + policyParameters[constants.HeaderName] = string(header) + + policies.Response = append(policies.Response, Policy{ + PolicyName: string(gwapiv1b1.HTTPRouteFilterResponseHeaderModifier), + Action: constants.ActionHeaderRemove, + Parameters: policyParameters, + }) + } + for _, header := range filter.ResponseHeaderModifier.Set { + policyParameters := make(map[string]interface{}) + policyParameters[constants.HeaderName] = string(header.Name) + policyParameters[constants.HeaderValue] = string(header.Value) + + policies.Response = append(policies.Response, Policy{ + PolicyName: string(gwapiv1b1.HTTPRouteFilterResponseHeaderModifier), + Action: constants.ActionHeaderAdd, + Parameters: policyParameters, + }) + } + } + } + resourceAPIPolicy = concatAPIPolicies(resourceAPIPolicy, nil) + resourceAuthScheme = concatAuthSchemes(resourceAuthScheme, nil) + resourceRatelimitPolicy = concatRateLimitPolicies(resourceRatelimitPolicy, nil) + addOperationLevelInterceptors(&policies, resourceAPIPolicy, resourceParams.InterceptorServiceMapping, resourceParams.BackendMapping, httpRoute.Namespace) + + loggers.LoggerOasparser.Debugf("Calculating auths for API ..., API_UUID = %v", adapterInternalAPI.UUID) + apiAuth := getSecurity(resourceAuthScheme) + if len(rule.BackendRefs) < 1 { + return fmt.Errorf("no backendref were provided") + } + + for _, match := range rule.Matches { + if !hasURLRewritePolicy { + policyParameters := make(map[string]interface{}) + if *match.Path.Type == gwapiv1b1.PathMatchPathPrefix { + policyParameters[constants.RewritePathType] = gwapiv1b1.PrefixMatchHTTPPathModifier + } else { + policyParameters[constants.RewritePathType] = gwapiv1b1.FullPathHTTPPathModifier + } + policyParameters[constants.IncludeQueryParams] = true + policyParameters[constants.RewritePathResourcePath] = strings.TrimSuffix(backendBasePath, "/") + *match.Path.Value + policies.Request = append(policies.Request, Policy{ + PolicyName: string(gwapiv1b1.HTTPRouteFilterURLRewrite), + Action: constants.ActionRewritePath, + Parameters: policyParameters, + }) + } + resourcePath := adapterInternalAPI.xWso2Basepath + *match.Path.Value + resource := &Resource{path: resourcePath, + methods: getAllowedOperations(match.Method, policies, apiAuth, + parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes), + pathMatchType: *match.Path.Type, + hasPolicies: true, + iD: uuid.New().String(), + } + + resource.endpoints = &EndpointCluster{ + Endpoints: endPoints, + } + + endpointConfig := &EndpointConfig{} + + if isRouteTimeout { + endpointConfig.TimeoutInMillis = timeoutInMillis + endpointConfig.IdleTimeoutInSeconds = idleTimeoutInSeconds + } + if circuitBreaker != nil { + endpointConfig.CircuitBreakers = &CircuitBreakers{ + MaxConnections: int32(circuitBreaker.MaxConnections), + MaxRequests: int32(circuitBreaker.MaxRequests), + MaxPendingRequests: int32(circuitBreaker.MaxPendingRequests), + MaxRetries: int32(circuitBreaker.MaxRetries), + MaxConnectionPools: int32(circuitBreaker.MaxConnectionPools), + } + } + if isRetryConfig { + endpointConfig.RetryConfig = &RetryConfig{ + Count: int32(backendRetryCount), + StatusCodes: statusCodes, + BaseIntervalInMillis: int32(baseIntervalInMillis), + } + } + if healthCheck != nil { + resource.endpoints.HealthCheck = &HealthCheck{ + Interval: healthCheck.Interval, + Timeout: healthCheck.Timeout, + UnhealthyThreshold: healthCheck.UnhealthyThreshold, + HealthyThreshold: healthCheck.HealthyThreshold, + } + } + if isRouteTimeout || circuitBreaker != nil || healthCheck != nil || isRetryConfig { + resource.endpoints.Config = endpointConfig + } + resource.endpointSecurity = utils.GetPtrSlice(securityConfig) + resources = append(resources, resource) + } + } + + ratelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, nil) + apiPolicy = concatAPIPolicies(apiPolicy, nil) + authScheme = concatAuthSchemes(authScheme, nil) + + adapterInternalAPI.RateLimitPolicy = parseRateLimitPolicyToInternal(ratelimitPolicy) + adapterInternalAPI.resources = resources + adapterInternalAPI.xWso2Cors = getCorsConfigFromAPIPolicy(apiPolicy) + if authScheme.Spec.Override != nil && authScheme.Spec.Override.Disabled != nil { + adapterInternalAPI.disableAuthentications = *authScheme.Spec.Override.Disabled + } + adapterInternalAPI.disableScopes = disableScopes + + // Check whether the API has a backend JWT token + if apiPolicy != nil && apiPolicy.Spec.Override != nil && apiPolicy.Spec.Override.BackendJWTPolicy != nil { + backendJWTPolicy := resourceParams.BackendJWTMapping[types.NamespacedName{ + Name: apiPolicy.Spec.Override.BackendJWTPolicy.Name, + Namespace: httpRoute.Namespace, + }.String()].Spec + adapterInternalAPI.backendJWTTokenInfo = parseBackendJWTTokenToInternal(backendJWTPolicy) + } + return nil +} + +// SetInfoGQLRouteCR populates resources and endpoints of adapterInternalAPI. httpRoute.Spec.Rules.Matches +// are used to create resources and httpRoute.Spec.Rules.BackendRefs are used to create EndpointClusters. +func (adapterInternalAPI *AdapterInternalAPI) SetInfoGQLRouteCR(gqlRoute *dpv1alpha2.GQLRoute, resourceParams ResourceParams) error { + var resources []*Resource + outputAuthScheme := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.AuthSchemes))) + outputAPIPolicy := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.APIPolicies))) + outputRatelimitPolicy := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.RateLimitPolicies))) + + disableScopes := true + config := config.ReadConfigs() + + var authScheme *dpv1alpha1.Authentication + if outputAuthScheme != nil { + authScheme = *outputAuthScheme + } + var apiPolicy *dpv1alpha2.APIPolicy + if outputAPIPolicy != nil { + apiPolicy = *outputAPIPolicy + } + var ratelimitPolicy *dpv1alpha1.RateLimitPolicy + if outputRatelimitPolicy != nil { + ratelimitPolicy = *outputRatelimitPolicy + } + + //We are only supporting one backend for now + backend := gqlRoute.Spec.BackendRefs[0] + backendName := types.NamespacedName{ + Name: string(backend.Name), + Namespace: utils.GetNamespace(backend.Namespace, gqlRoute.Namespace), + } + resolvedBackend, ok := resourceParams.BackendMapping[backendName.String()] + if ok { + endpointConfig := &EndpointConfig{} + if resolvedBackend.CircuitBreaker != nil { + endpointConfig.CircuitBreakers = &CircuitBreakers{ + MaxConnections: int32(resolvedBackend.CircuitBreaker.MaxConnections), + MaxRequests: int32(resolvedBackend.CircuitBreaker.MaxRequests), + MaxPendingRequests: int32(resolvedBackend.CircuitBreaker.MaxPendingRequests), + MaxRetries: int32(resolvedBackend.CircuitBreaker.MaxRetries), + MaxConnectionPools: int32(resolvedBackend.CircuitBreaker.MaxConnectionPools), + } + } + if resolvedBackend.Timeout != nil { + endpointConfig.TimeoutInMillis = resolvedBackend.Timeout.UpstreamResponseTimeout * 1000 + endpointConfig.IdleTimeoutInSeconds = resolvedBackend.Timeout.DownstreamRequestIdleTimeout + } + if resolvedBackend.Retry != nil { + statusCodes := config.Envoy.Upstream.Retry.StatusCodes + if len(resolvedBackend.Retry.StatusCodes) > 0 { + statusCodes = resolvedBackend.Retry.StatusCodes + } + endpointConfig.RetryConfig = &RetryConfig{ + Count: int32(resolvedBackend.Retry.Count), + StatusCodes: statusCodes, + BaseIntervalInMillis: int32(resolvedBackend.Retry.BaseIntervalMillis), + } + } + adapterInternalAPI.Endpoints = &EndpointCluster{ + Endpoints: GetEndpoints(backendName, resourceParams.BackendMapping), + Config: endpointConfig, + } + if resolvedBackend.HealthCheck != nil { + adapterInternalAPI.Endpoints.HealthCheck = &HealthCheck{ + Interval: resolvedBackend.HealthCheck.Interval, + Timeout: resolvedBackend.HealthCheck.Timeout, + UnhealthyThreshold: resolvedBackend.HealthCheck.UnhealthyThreshold, + HealthyThreshold: resolvedBackend.HealthCheck.HealthyThreshold, + } + } + + var securityConfig []EndpointSecurity + switch resolvedBackend.Security.Type { + case "Basic": + securityConfig = append(securityConfig, EndpointSecurity{ + Password: string(resolvedBackend.Security.Basic.Password), + Username: string(resolvedBackend.Security.Basic.Username), + Type: string(resolvedBackend.Security.Type), + Enabled: true, + }) + } + adapterInternalAPI.EndpointSecurity = utils.GetPtrSlice(securityConfig) + } else { + return fmt.Errorf("backend: %s has not been resolved", backendName) + } + + for _, rule := range gqlRoute.Spec.Rules { + var policies = OperationPolicies{} + resourceAuthScheme := authScheme + resourceRatelimitPolicy := ratelimitPolicy + var scopes []string + + for _, filter := range rule.Filters { + if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindAuthentication { + if ref, found := resourceParams.ResourceAuthSchemes[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: gqlRoute.Namespace, + }.String()]; found { + resourceAuthScheme = concatAuthSchemes(authScheme, &ref) + } else { + return fmt.Errorf(`auth scheme: %s has not been resolved, spec.targetRef.kind should be + 'Resource' in resource level Authentications`, filter.ExtensionRef.Name) + } + } + if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindScope { + if ref, found := resourceParams.ResourceScopes[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: gqlRoute.Namespace, + }.String()]; found { + scopes = ref.Spec.Names + disableScopes = false + } else { + return fmt.Errorf("scope: %s has not been resolved in namespace %s", filter.ExtensionRef.Name, gqlRoute.Namespace) + } + } + if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindRateLimitPolicy { + if ref, found := resourceParams.ResourceRateLimitPolicies[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: gqlRoute.Namespace, + }.String()]; found { + resourceRatelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, &ref) + } else { + return fmt.Errorf(`ratelimitpolicy: %s has not been resolved, spec.targetRef.kind should be + 'Resource' in resource level RateLimitPolicies`, filter.ExtensionRef.Name) + } + } + } + resourceAuthScheme = concatAuthSchemes(resourceAuthScheme, nil) + resourceRatelimitPolicy = concatRateLimitPolicies(resourceRatelimitPolicy, nil) + + loggers.LoggerOasparser.Debugf("Calculating auths for API ..., API_UUID = %v", adapterInternalAPI.UUID) + apiAuth := getSecurity(resourceAuthScheme) + + for _, match := range rule.Matches { + resourcePath := *match.Path + resource := &Resource{path: resourcePath, + methods: []*Operation{{iD: uuid.New().String(), method: string(*match.Type), policies: policies, + auth: apiAuth, RateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, + iD: uuid.New().String(), + } + resources = append(resources, resource) + } + } + + ratelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, nil) + apiPolicy = concatAPIPolicies(apiPolicy, nil) + authScheme = concatAuthSchemes(authScheme, nil) + + adapterInternalAPI.RateLimitPolicy = parseRateLimitPolicyToInternal(ratelimitPolicy) + adapterInternalAPI.resources = resources + adapterInternalAPI.xWso2Cors = getCorsConfigFromAPIPolicy(apiPolicy) + if authScheme.Spec.Override != nil && authScheme.Spec.Override.Disabled != nil { + adapterInternalAPI.disableAuthentications = *authScheme.Spec.Override.Disabled + } + adapterInternalAPI.disableScopes = disableScopes + return nil +} + func (endpoint *Endpoint) validateEndpoint() error { if len(endpoint.ServiceDiscoveryString) > 0 { return nil @@ -505,19 +1001,19 @@ func generateEndpointCluster(endpoints []Endpoint, endpointType string) *Endpoin } // GetOperationInterceptors returns operation interceptors -func (swagger *AdapterInternalAPI) GetOperationInterceptors(apiInterceptor InterceptEndpoint, resourceInterceptor InterceptEndpoint, operations []*Operation, isIn bool) map[string]InterceptEndpoint { +func (adapterInternalAPI *AdapterInternalAPI) GetOperationInterceptors(apiInterceptor InterceptEndpoint, resourceInterceptor InterceptEndpoint, operations []*Operation, isIn bool) map[string]InterceptEndpoint { interceptorOperationMap := make(map[string]InterceptEndpoint) for _, op := range operations { extensionName := constants.XWso2RequestInterceptor // first get operational policies operationInterceptor := op.GetCallInterceptorService(isIn) - // if operational policy interceptor not given check operational level swagger extension + // if operational policy interceptor not given check operational level adapterInternalAPI extension if !operationInterceptor.Enable { if !isIn { extensionName = constants.XWso2ResponseInterceptor } - operationInterceptor = swagger.GetInterceptor(op.GetVendorExtensions(), extensionName, constants.OperationLevelInterceptor) + operationInterceptor = adapterInternalAPI.GetInterceptor(op.GetVendorExtensions(), extensionName, constants.OperationLevelInterceptor) } operationInterceptor.ClusterName = op.iD // if operation interceptor not given @@ -540,7 +1036,7 @@ func (swagger *AdapterInternalAPI) GetOperationInterceptors(apiInterceptor Inter } // GetInterceptor returns interceptors -func (swagger *AdapterInternalAPI) GetInterceptor(vendorExtensions map[string]interface{}, extensionName string, level string) InterceptEndpoint { +func (adapterInternalAPI *AdapterInternalAPI) GetInterceptor(vendorExtensions map[string]interface{}, extensionName string, level string) InterceptEndpoint { var endpointCluster EndpointCluster conf := config.ReadConfigs() clusterTimeoutV := conf.Envoy.ClusterTimeoutInSeconds diff --git a/adapter/internal/oasparser/model/graphql_api.go b/adapter/internal/oasparser/model/graphql_api.go deleted file mode 100644 index 08e6da161..000000000 --- a/adapter/internal/oasparser/model/graphql_api.go +++ /dev/null @@ -1,108 +0,0 @@ -// /* -// * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// * -// */ - -package model - -// import ( -// "errors" - -// "github.com/google/uuid" -// "github.com/wso2/apk/adapter/internal/oasparser/constants" -// "github.com/wso2/apk/adapter/pkg/discovery/api/wso2/discovery/api" -// ) - -// // GraphQLComplexityYaml contains complexity values relevant to the fields included in the GraphQL schema. -// type GraphQLComplexityYaml struct { -// Data struct { -// List []*api.GraphqlComplexity -// } -// } - -// // SetInfoGraphQLAPI populates the AdapterInternalAPI object with information in api.yaml. -// func (swagger *AdapterInternalAPI) SetInfoGraphQLAPI(apiYaml APIYaml) error { - -// var securitySchemes []SecurityScheme -// var isAPIKeyEnabled bool - -// // assigns security schemes -// for _, securitySchemeValue := range apiYaml.Data.SecurityScheme { -// if securitySchemeValue == constants.Oauth2TypeInOAS { -// securitySchemes = append(securitySchemes, SecurityScheme{DefinitionName: "default", Type: securitySchemeValue}) -// } else if securitySchemeValue == constants.APIMAPIKeyType { -// isAPIKeyEnabled = true -// securitySchemes = append(securitySchemes, SecurityScheme{DefinitionName: constants.APIMAPIKeyInHeader, -// Type: constants.APIKeyTypeInOAS, Name: constants.APIKeyNameWithApim, In: constants.APIKeyInHeaderOAS}) -// securitySchemes = append(securitySchemes, SecurityScheme{DefinitionName: constants.APIMAPIKeyInQuery, -// Type: constants.APIKeyTypeInOAS, Name: constants.APIKeyNameWithApim, In: constants.APIKeyInQueryOAS}) -// } -// } -// swagger.securityScheme = securitySchemes - -// // sets resources relevant to the GraphQL API considering api.yaml -// var resources []*Resource -// if len(apiYaml.Data.Operations) < 1 { -// return errors.New("cannot process api.yaml since operations not defined in the api.yaml") -// } - -// for _, operation := range apiYaml.Data.Operations { -// var resource Resource -// var methods []*Operation -// var resourceMethod Operation -// var scopes []string -// var security []map[string][]string - -// // assigns resource level attribute values -// resource.iD = uuid.New().String() -// resource.path = operation.Target - -// // assigns operation level attribute values -// resourceMethod.method = operation.Verb -// resourceMethod.tier = operation.ThrottlingPolicy -// resourceMethod.iD = operation.ID - -// scopes = append(scopes, operation.Scopes...) -// if operation.AuthType == "None" { -// resourceMethod.disableSecurity = true -// } else { -// security = append(security, map[string][]string{constants.APIMDefaultOauth2Security: scopes}) -// if isAPIKeyEnabled { -// security = append(security, map[string][]string{constants.APIMAPIKeyInHeader: {}}) -// security = append(security, map[string][]string{constants.APIMAPIKeyInQuery: {}}) -// } -// } -// resourceMethod.security = security - -// methods = append(methods, &resourceMethod) -// resource.methods = methods -// resources = append(resources, &resource) -// } -// swagger.resources = resources - -// var corsConfig = generateGlobalCors() -// if apiYaml.Data.CorsConfiguration.CorsConfigurationEnabled { -// corsConfig.AccessControlAllowOrigins = apiYaml.Data.CorsConfiguration.AccessControlAllowOrigins -// corsConfig.AccessControlAllowCredentials = apiYaml.Data.CorsConfiguration.AccessControlAllowCredentials -// corsConfig.AccessControlAllowHeaders = apiYaml.Data.CorsConfiguration.AccessControlAllowHeaders -// corsConfig.AccessControlAllowMethods = apiYaml.Data.CorsConfiguration.AccessControlAllowMethods -// } -// swagger.xWso2Cors = corsConfig - -// // enables request body passing feature for GraphQL APIs -// swagger.xWso2RequestBodyPass = true - -// return nil -// } diff --git a/adapter/internal/oasparser/model/http_route.go b/adapter/internal/oasparser/model/http_route.go index 38d8d7eb3..9c18558d3 100644 --- a/adapter/internal/oasparser/model/http_route.go +++ b/adapter/internal/oasparser/model/http_route.go @@ -18,17 +18,12 @@ package model import ( - "fmt" - "strings" - "github.com/google/uuid" - "github.com/wso2/apk/adapter/config" "github.com/wso2/apk/adapter/internal/loggers" "github.com/wso2/apk/adapter/internal/oasparser/constants" "github.com/wso2/apk/adapter/internal/operator/utils" dpv1alpha1 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha1" dpv1alpha2 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha2" - "golang.org/x/exp/maps" "k8s.io/apimachinery/pkg/types" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -47,340 +42,6 @@ type ResourceParams struct { ResourceRateLimitPolicies map[string]dpv1alpha1.RateLimitPolicy } -// SetInfoHTTPRouteCR populates resources and endpoints of adapterInternalAPI. httpRoute.Spec.Rules.Matches -// are used to create resources and httpRoute.Spec.Rules.BackendRefs are used to create EndpointClusters. -func (swagger *AdapterInternalAPI) SetInfoHTTPRouteCR(httpRoute *gwapiv1b1.HTTPRoute, resourceParams ResourceParams) error { - var resources []*Resource - outputAuthScheme := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.AuthSchemes))) - outputAPIPolicy := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.APIPolicies))) - outputRatelimitPolicy := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.RateLimitPolicies))) - - disableScopes := true - config := config.ReadConfigs() - - var authScheme *dpv1alpha1.Authentication - if outputAuthScheme != nil { - authScheme = *outputAuthScheme - } - var apiPolicy *dpv1alpha2.APIPolicy - if outputAPIPolicy != nil { - apiPolicy = *outputAPIPolicy - } - var ratelimitPolicy *dpv1alpha1.RateLimitPolicy - if outputRatelimitPolicy != nil { - ratelimitPolicy = *outputRatelimitPolicy - } - - for _, rule := range httpRoute.Spec.Rules { - var endPoints []Endpoint - var policies = OperationPolicies{} - var circuitBreaker *dpv1alpha1.CircuitBreaker - var healthCheck *dpv1alpha1.HealthCheck - resourceAuthScheme := authScheme - resourceAPIPolicy := apiPolicy - resourceRatelimitPolicy := ratelimitPolicy - hasPolicies := false - var scopes []string - var timeoutInMillis uint32 - var idleTimeoutInSeconds uint32 - isRetryConfig := false - isRouteTimeout := false - var backendRetryCount uint32 - var statusCodes []uint32 - statusCodes = append(statusCodes, config.Envoy.Upstream.Retry.StatusCodes...) - var baseIntervalInMillis uint32 - hasURLRewritePolicy := false - var securityConfig []EndpointSecurity - backendBasePath := "" - for _, backend := range rule.BackendRefs { - backendName := types.NamespacedName{ - Name: string(backend.Name), - Namespace: utils.GetNamespace(backend.Namespace, httpRoute.Namespace), - } - resolvedBackend, ok := resourceParams.BackendMapping[backendName.String()] - if ok { - if resolvedBackend.CircuitBreaker != nil { - circuitBreaker = &dpv1alpha1.CircuitBreaker{ - MaxConnections: resolvedBackend.CircuitBreaker.MaxConnections, - MaxPendingRequests: resolvedBackend.CircuitBreaker.MaxPendingRequests, - MaxRequests: resolvedBackend.CircuitBreaker.MaxRequests, - MaxRetries: resolvedBackend.CircuitBreaker.MaxRetries, - MaxConnectionPools: resolvedBackend.CircuitBreaker.MaxConnectionPools, - } - } - if resolvedBackend.Timeout != nil { - isRouteTimeout = true - timeoutInMillis = resolvedBackend.Timeout.UpstreamResponseTimeout * 1000 - idleTimeoutInSeconds = resolvedBackend.Timeout.DownstreamRequestIdleTimeout - } - - if resolvedBackend.Retry != nil { - isRetryConfig = true - backendRetryCount = resolvedBackend.Retry.Count - baseIntervalInMillis = resolvedBackend.Retry.BaseIntervalMillis - if len(resolvedBackend.Retry.StatusCodes) > 0 { - statusCodes = resolvedBackend.Retry.StatusCodes - } - } - if resolvedBackend.HealthCheck != nil { - healthCheck = &dpv1alpha1.HealthCheck{ - Interval: resolvedBackend.HealthCheck.Interval, - Timeout: resolvedBackend.HealthCheck.Timeout, - UnhealthyThreshold: resolvedBackend.HealthCheck.UnhealthyThreshold, - HealthyThreshold: resolvedBackend.HealthCheck.HealthyThreshold, - } - } - endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) - backendBasePath = GetBackendBasePath(backendName, resourceParams.BackendMapping) - switch resolvedBackend.Security.Type { - case "Basic": - securityConfig = append(securityConfig, EndpointSecurity{ - Password: string(resolvedBackend.Security.Basic.Password), - Username: string(resolvedBackend.Security.Basic.Username), - Type: string(resolvedBackend.Security.Type), - Enabled: true, - }) - } - } else { - return fmt.Errorf("backend: %s has not been resolved", backendName) - } - } - for _, filter := range rule.Filters { - hasPolicies = true - switch filter.Type { - case gwapiv1b1.HTTPRouteFilterURLRewrite: - policyParameters := make(map[string]interface{}) - policyParameters[constants.RewritePathType] = filter.URLRewrite.Path.Type - policyParameters[constants.IncludeQueryParams] = true - - switch filter.URLRewrite.Path.Type { - case gwapiv1b1.FullPathHTTPPathModifier: - policyParameters[constants.RewritePathResourcePath] = backendBasePath + *filter.URLRewrite.Path.ReplaceFullPath - case gwapiv1b1.PrefixMatchHTTPPathModifier: - policyParameters[constants.RewritePathResourcePath] = backendBasePath + *filter.URLRewrite.Path.ReplacePrefixMatch - } - - policies.Request = append(policies.Request, Policy{ - PolicyName: string(gwapiv1b1.HTTPRouteFilterURLRewrite), - Action: constants.ActionRewritePath, - Parameters: policyParameters, - }) - hasURLRewritePolicy = true - case gwapiv1b1.HTTPRouteFilterExtensionRef: - if filter.ExtensionRef.Kind == constants.KindAuthentication { - if ref, found := resourceParams.ResourceAuthSchemes[types.NamespacedName{ - Name: string(filter.ExtensionRef.Name), - Namespace: httpRoute.Namespace, - }.String()]; found { - resourceAuthScheme = concatAuthSchemes(authScheme, &ref) - } else { - return fmt.Errorf(`auth scheme: %s has not been resolved, spec.targetRef.kind should be - 'Resource' in resource level Authentications`, filter.ExtensionRef.Name) - } - } - if filter.ExtensionRef.Kind == constants.KindAPIPolicy { - if ref, found := resourceParams.ResourceAPIPolicies[types.NamespacedName{ - Name: string(filter.ExtensionRef.Name), - Namespace: httpRoute.Namespace, - }.String()]; found { - resourceAPIPolicy = concatAPIPolicies(apiPolicy, &ref) - } else { - return fmt.Errorf(`apipolicy: %s has not been resolved, spec.targetRef.kind should be - 'Resource' in resource level APIPolicies`, filter.ExtensionRef.Name) - } - } - if filter.ExtensionRef.Kind == constants.KindScope { - if ref, found := resourceParams.ResourceScopes[types.NamespacedName{ - Name: string(filter.ExtensionRef.Name), - Namespace: httpRoute.Namespace, - }.String()]; found { - scopes = ref.Spec.Names - disableScopes = false - } else { - return fmt.Errorf("scope: %s has not been resolved in namespace %s", filter.ExtensionRef.Name, httpRoute.Namespace) - } - } - if filter.ExtensionRef.Kind == constants.KindRateLimitPolicy { - if ref, found := resourceParams.ResourceRateLimitPolicies[types.NamespacedName{ - Name: string(filter.ExtensionRef.Name), - Namespace: httpRoute.Namespace, - }.String()]; found { - resourceRatelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, &ref) - } else { - return fmt.Errorf(`ratelimitpolicy: %s has not been resolved, spec.targetRef.kind should be - 'Resource' in resource level RateLimitPolicies`, filter.ExtensionRef.Name) - } - } - case gwapiv1b1.HTTPRouteFilterRequestHeaderModifier: - for _, header := range filter.RequestHeaderModifier.Add { - policyParameters := make(map[string]interface{}) - policyParameters[constants.HeaderName] = string(header.Name) - policyParameters[constants.HeaderValue] = string(header.Value) - - policies.Request = append(policies.Request, Policy{ - PolicyName: string(gwapiv1b1.HTTPRouteFilterRequestHeaderModifier), - Action: constants.ActionHeaderAdd, - Parameters: policyParameters, - }) - } - for _, header := range filter.RequestHeaderModifier.Remove { - policyParameters := make(map[string]interface{}) - policyParameters[constants.HeaderName] = string(header) - - policies.Request = append(policies.Request, Policy{ - PolicyName: string(gwapiv1b1.HTTPRouteFilterRequestHeaderModifier), - Action: constants.ActionHeaderRemove, - Parameters: policyParameters, - }) - } - for _, header := range filter.RequestHeaderModifier.Set { - policyParameters := make(map[string]interface{}) - policyParameters[constants.HeaderName] = string(header.Name) - policyParameters[constants.HeaderValue] = string(header.Value) - - policies.Request = append(policies.Request, Policy{ - PolicyName: string(gwapiv1b1.HTTPRouteFilterRequestHeaderModifier), - Action: constants.ActionHeaderAdd, - Parameters: policyParameters, - }) - } - case gwapiv1b1.HTTPRouteFilterResponseHeaderModifier: - for _, header := range filter.ResponseHeaderModifier.Add { - policyParameters := make(map[string]interface{}) - policyParameters[constants.HeaderName] = string(header.Name) - policyParameters[constants.HeaderValue] = string(header.Value) - - policies.Response = append(policies.Response, Policy{ - PolicyName: string(gwapiv1b1.HTTPRouteFilterResponseHeaderModifier), - Action: constants.ActionHeaderAdd, - Parameters: policyParameters, - }) - } - for _, header := range filter.ResponseHeaderModifier.Remove { - policyParameters := make(map[string]interface{}) - policyParameters[constants.HeaderName] = string(header) - - policies.Response = append(policies.Response, Policy{ - PolicyName: string(gwapiv1b1.HTTPRouteFilterResponseHeaderModifier), - Action: constants.ActionHeaderRemove, - Parameters: policyParameters, - }) - } - for _, header := range filter.ResponseHeaderModifier.Set { - policyParameters := make(map[string]interface{}) - policyParameters[constants.HeaderName] = string(header.Name) - policyParameters[constants.HeaderValue] = string(header.Value) - - policies.Response = append(policies.Response, Policy{ - PolicyName: string(gwapiv1b1.HTTPRouteFilterResponseHeaderModifier), - Action: constants.ActionHeaderAdd, - Parameters: policyParameters, - }) - } - } - } - resourceAPIPolicy = concatAPIPolicies(resourceAPIPolicy, nil) - resourceAuthScheme = concatAuthSchemes(resourceAuthScheme, nil) - resourceRatelimitPolicy = concatRateLimitPolicies(resourceRatelimitPolicy, nil) - addOperationLevelInterceptors(&policies, resourceAPIPolicy, resourceParams.InterceptorServiceMapping, resourceParams.BackendMapping, httpRoute.Namespace) - - loggers.LoggerOasparser.Debugf("Calculating auths for API ..., API_UUID = %v", swagger.UUID) - apiAuth := getSecurity(resourceAuthScheme) - if len(rule.BackendRefs) < 1 { - return fmt.Errorf("no backendref were provided") - } - - for _, match := range rule.Matches { - if !hasURLRewritePolicy { - policyParameters := make(map[string]interface{}) - if *match.Path.Type == gwapiv1b1.PathMatchPathPrefix { - policyParameters[constants.RewritePathType] = gwapiv1b1.PrefixMatchHTTPPathModifier - } else { - policyParameters[constants.RewritePathType] = gwapiv1b1.FullPathHTTPPathModifier - } - policyParameters[constants.IncludeQueryParams] = true - policyParameters[constants.RewritePathResourcePath] = strings.TrimSuffix(backendBasePath, "/") + *match.Path.Value - policies.Request = append(policies.Request, Policy{ - PolicyName: string(gwapiv1b1.HTTPRouteFilterURLRewrite), - Action: constants.ActionRewritePath, - Parameters: policyParameters, - }) - hasPolicies = true - } - resourcePath := swagger.xWso2Basepath + *match.Path.Value - resource := &Resource{path: resourcePath, - methods: getAllowedOperations(match.Method, policies, apiAuth, - parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes), - pathMatchType: *match.Path.Type, - hasPolicies: hasPolicies, - iD: uuid.New().String(), - } - - resource.endpoints = &EndpointCluster{ - Endpoints: endPoints, - } - - endpointConfig := &EndpointConfig{} - - if isRouteTimeout { - endpointConfig.TimeoutInMillis = timeoutInMillis - endpointConfig.IdleTimeoutInSeconds = idleTimeoutInSeconds - } - if circuitBreaker != nil { - endpointConfig.CircuitBreakers = &CircuitBreakers{ - MaxConnections: int32(circuitBreaker.MaxConnections), - MaxRequests: int32(circuitBreaker.MaxRequests), - MaxPendingRequests: int32(circuitBreaker.MaxPendingRequests), - MaxRetries: int32(circuitBreaker.MaxRetries), - MaxConnectionPools: int32(circuitBreaker.MaxConnectionPools), - } - } - if isRetryConfig { - endpointConfig.RetryConfig = &RetryConfig{ - Count: int32(backendRetryCount), - StatusCodes: statusCodes, - BaseIntervalInMillis: int32(baseIntervalInMillis), - } - } - if healthCheck != nil { - resource.endpoints.HealthCheck = &HealthCheck{ - Interval: healthCheck.Interval, - Timeout: healthCheck.Timeout, - UnhealthyThreshold: healthCheck.UnhealthyThreshold, - HealthyThreshold: healthCheck.HealthyThreshold, - } - } - if isRouteTimeout || circuitBreaker != nil || healthCheck != nil || isRetryConfig { - resource.endpoints.Config = endpointConfig - } - resource.endpointSecurity = utils.GetPtrSlice(securityConfig) - resources = append(resources, resource) - } - } - - ratelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, nil) - apiPolicy = concatAPIPolicies(apiPolicy, nil) - authScheme = concatAuthSchemes(authScheme, nil) - - swagger.RateLimitPolicy = parseRateLimitPolicyToInternal(ratelimitPolicy) - swagger.resources = resources - swagger.xWso2Cors = getCorsConfigFromAPIPolicy(apiPolicy) - if authScheme.Spec.Override != nil && authScheme.Spec.Override.Disabled != nil { - swagger.disableAuthentications = *authScheme.Spec.Override.Disabled - } - swagger.disableScopes = disableScopes - - // Check whether the API has a backend JWT token - if apiPolicy != nil && apiPolicy.Spec.Override != nil && apiPolicy.Spec.Override.BackendJWTPolicy != nil { - backendJWTPolicy := resourceParams.BackendJWTMapping[types.NamespacedName{ - Name: apiPolicy.Spec.Override.BackendJWTPolicy.Name, - Namespace: httpRoute.Namespace, - }.String()].Spec - swagger.backendJWTTokenInfo = parseBackendJWTTokenToInternal(backendJWTPolicy) - } - return nil -} - func parseBackendJWTTokenToInternal(backendJWTToken dpv1alpha1.BackendJWTSpec) *BackendJWTTokenInfo { var customClaims []ClaimMapping for _, value := range backendJWTToken.CustomClaims { @@ -500,6 +161,7 @@ func GetEndpoints(backendName types.NamespacedName, backendMapping map[string]*d endpoints = append(endpoints, Endpoint{ Host: service.Host, Port: service.Port, + Basepath: backend.BasePath, URLType: string(backend.Protocol), Certificate: []byte(backend.TLS.ResolvedCertificate), AllowedSANs: backend.TLS.AllowedSANs, diff --git a/adapter/internal/oasparser/model/resource.go b/adapter/internal/oasparser/model/resource.go index a33488df7..314e58273 100644 --- a/adapter/internal/oasparser/model/resource.go +++ b/adapter/internal/oasparser/model/resource.go @@ -64,10 +64,6 @@ func (resource *Resource) GetPath() string { // GetPathMatchType returns the path match type of the resource. // https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.PathMatchType func (resource *Resource) GetPathMatchType() gwapiv1b1.PathMatchType { - //todo(amila) fix cannot be nil issue - if &resource.pathMatchType == nil { - return gwapiv1b1.PathMatchPathPrefix - } return resource.pathMatchType } @@ -113,14 +109,18 @@ func (resource *Resource) HasPolicies() bool { // which could be used for unit tests. func CreateMinimalDummyResourceForTests(path string, methods []*Operation, id string, urls []Endpoint, hasPolicies bool) Resource { - endpints := generateEndpointCluster(urls, constants.LoadBalance) + endpoints := generateEndpointCluster(urls, constants.LoadBalance) + return CreateMinimalResource(path, methods, id, endpoints, hasPolicies, gwapiv1b1.PathMatchPathPrefix) +} +// CreateMinimalResource create a resource object with minimal required set of values +func CreateMinimalResource(path string, methods []*Operation, id string, endpoints *EndpointCluster, hasPolicies bool, pathMatchType gwapiv1b1.PathMatchType) Resource { return Resource{ path: path, methods: methods, iD: id, - endpoints: endpints, - pathMatchType: gwapiv1b1.PathMatchPathPrefix, + endpoints: endpoints, + pathMatchType: pathMatchType, hasPolicies: hasPolicies, } } diff --git a/adapter/internal/operator/PROJECT b/adapter/internal/operator/PROJECT index 8960d64ab..a20a3f8f3 100644 --- a/adapter/internal/operator/PROJECT +++ b/adapter/internal/operator/PROJECT @@ -1,7 +1,3 @@ -# Code generated by tool. DO NOT EDIT. -# This file is used to track the info used to scaffold your project -# and allow the plugins properly work. -# More info: https://book.kubebuilder.io/reference/project-config.html domain: wso2.com layout: - go.kubebuilder.io/v3 @@ -141,4 +137,13 @@ resources: kind: TokenIssuer path: github.com/wso2/apk/adapter/internal/operator/apis/dp/v1alpha2 version: v1alpha2 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: wso2.com + group: dp + kind: GQLRoute + path: github.com/wso2/apk/adapter/internal/operator/apis/dp/v1alpha2 + version: v1alpha2 version: "3" diff --git a/adapter/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml b/adapter/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml index 1da03bb32..fc4e0a2a9 100644 --- a/adapter/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml +++ b/adapter/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml @@ -247,6 +247,7 @@ spec: could be REST, GraphQL, Async enum: - REST + - GraphQL type: string apiVersion: description: APIVersion is the version number of the API. @@ -259,8 +260,8 @@ spec: pattern: ^[/][a-zA-Z0-9~/_.-]*$ type: string definitionFileRef: - description: DefinitionFileRef contains the OpenAPI 3 or Swagger definition - of the API in a ConfigMap. + description: DefinitionFileRef contains the OpenAPI 3 or SDL file + in gzipped format. definition of the API in a ConfigMap. type: string definitionPath: default: /api-definition diff --git a/adapter/internal/operator/config/crd/bases/dp.wso2.com_gqlroutes.yaml b/adapter/internal/operator/config/crd/bases/dp.wso2.com_gqlroutes.yaml new file mode 100644 index 000000000..557436f2d --- /dev/null +++ b/adapter/internal/operator/config/crd/bases/dp.wso2.com_gqlroutes.yaml @@ -0,0 +1,890 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: gqlroutes.dp.wso2.com +spec: + group: dp.wso2.com + names: + kind: GQLRoute + listKind: GQLRouteList + plural: gqlroutes + singular: gqlroute + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: GQLRoute is the Schema for the gqlroutes API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GQLRouteSpec defines the desired state of GQLRoute + properties: + backendRefs: + description: BackendRefs defines the backend(s) where matching requests + should be sent. + items: + description: HTTPBackendRef defines how a HTTPRoute should forward + an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed + if and only if the request is being forwarded to the backend + defined here. \n Support: Implementation-specific (For broader + support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that + must be completed during the request or response lifecycle. + HTTPRouteFilters are meant as an extension point to express + processing that may be done in Gateway implementations. + Some examples include request or response modification, + implementing authentication strategies, rate-limiting, and + traffic shaping. API guarantee/conformance is defined based + on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for + a filter that modifies request headers. \n Support: + Core" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter + that mirrors requests. Requests are sent to the specified + destination, but responses from that destination are + ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where + mirrored requests are sent. \n If the referent cannot + be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure + the \"ResolvedRefs\" condition on the Route status + is set to `status: False` and not configure this + backend in the underlying implementation. \n If + there is a cross-namespace reference to an *existing* + object that is not allowed by a ReferenceGrant, + the controller must ensure the \"ResolvedRefs\" + \ condition on the Route is set to `status: False`, + with the \"RefNotPermitted\" reason and not configure + this backend in the underlying implementation. \n + In either error case, the Message of the `ResolvedRefs` + Condition should be used to provide more detail + about the problem. \n Support: Extended for Kubernetes + Service \n Support: Implementation-specific for + any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". When + unspecified or empty string, core API group + is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to CNAME + DNS records that may live outside of the cluster + and as such are difficult to reason about in + terms of conformance. They also may not be safe + to forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a + type other than ExternalName) \n Support: Implementation-specific + (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the + backend. When unspecified, the local namespace + is inferred. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept the + reference. See the ReferenceGrant documentation + for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port + number to use for this resource. Port is required + when the referent is a Kubernetes Service. In + this case, the port number is the service port + number, not the target port. For other resources, + destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter + that responds to the request with an HTTP redirection. + \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used + in the value of the `Location` header in the response. + When empty, the hostname in the `Host` header of + the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify + the path of the incoming request. The modified path + is then used to construct the `Location` header. + When empty, the request path is used as-is. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not." + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value + of the `Location` header in the response. \n If + no port is specified, the redirect port MUST be + derived using the following rules: \n * If redirect + scheme is not-empty, the redirect port MUST be the + well-known port associated with the redirect scheme. + Specifically \"http\" to port 80 and \"https\" to + port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway + SHOULD be used. * If redirect scheme is empty, the + redirect port MUST be the Gateway Listener port. + \n Implementations SHOULD NOT add the port number + in the 'Location' header in the following cases: + \n * A Location header that will use HTTP (whether + that is determined via the Listener protocol or + the Scheme field) _and_ use port 80. * A Location + header that will use HTTPS (whether that is determined + via the Listener protocol or the Scheme field) _and_ + use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the + value of the `Location` header in the response. + When empty, the scheme of the request is used. \n + Scheme redirects can affect the port of the redirect, + for more information, refer to the documentation + for the port field of this filter. \n Note that + values may be added to this enum, implementations + must ensure that unknown values will not cause a + crash. \n Unknown values here must result in the + implementation setting the Accepted Condition for + the Route to `status: False`, with a Reason of `UnsupportedValue`. + \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to + be used in response. \n Note that values may be + added to this enum, implementations must ensure + that unknown values will not cause a crash. \n Unknown + values here must result in the implementation setting + the Accepted Condition for the Route to `status: + False`, with a Reason of `UnsupportedValue`. \n + Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n Support: + Extended" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. + As with other API fields, types are classified into + three conformance levels: \n - Core: Filter types and + their corresponding configuration defined by \"Support: + Core\" in this package, e.g. \"RequestHeaderModifier\". + All implementations must support core filters. \n - + Extended: Filter types and their corresponding configuration + defined by \"Support: Extended\" in this package, e.g. + \"RequestMirror\". Implementers are encouraged to support + extended filters. \n - Implementation-specific: Filters + that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior + across multiple implementations will be considered for + inclusion in extended or core conformance levels. Filter-specific + configuration for such filters is specified using the + ExtensionRef field. `Type` should be set to \"ExtensionRef\" + for custom filters. \n Implementers are encouraged to + define custom implementation types to extend the core + API with implementation-specific behavior. \n If a reference + to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have + been processed by that filter MUST receive a HTTP error + response. \n Note that values may be added to this enum, + implementations must ensure that unknown values will + not cause a crash. \n Unknown values here must result + in the implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter + that modifies a request during forwarding. \n Support: + Extended" + properties: + hostname: + description: "Hostname is the value to be used to + replace the Host header value during forwarding. + \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not." + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty string, + core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. + For example \"Service\". \n Defaults to \"Service\" when not + specified. \n ExternalName services can refer to CNAME DNS + records that may live outside of the cluster and as such are + difficult to reason about in terms of conformance. They also + may not be safe to forward to (see CVE-2021-25740 for more + information). Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a type other than + ExternalName) \n Support: Implementation-specific (Services + with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When + unspecified, the local namespace is inferred. \n Note that + when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace + to allow that namespace's owner to accept the reference. See + the ReferenceGrant documentation for details. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use + for this resource. Port is required when the referent is a + Kubernetes Service. In this case, the port number is the service + port number, not the target port. For other resources, destination + port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded + to the referenced backend. This is computed as weight/(sum + of all weights in this BackendRefs list). For non-zero values, + there may be some epsilon from the exact proportion defined + here depending on the precision an implementation supports. + Weight is not a percentage and the sum of weights does not + need to equal 100. \n If only one backend is specified and + it has a weight greater than 0, 100% of the traffic is forwarded + to that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight defaults + to 1. \n Support for this field varies based on the context + where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + type: array + hostnames: + description: Hostnames defines a set of hostname that should match + against the HTTP Host header to select a GQLRoute used to process + the request. + items: + description: "Hostname is the fully qualified domain name of a network + host. This matches the RFC 1123 definition of a hostname with + 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname + may be prefixed with a wildcard label (`*.`). The wildcard label + must appear by itself as the first label. \n Hostname can be \"precise\" + which is a domain name without the terminating dot of a network + host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain + name prefixed with a single wildcard label (e.g. `*.example.com`). + \n Note that as per RFC1035 and RFC1123, a *label* must consist + of lower case alphanumeric characters or '-', and must start and + end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) + that a Route wants to be attached to. Note that the referenced parent + resource needs to allow this for the attachment to be complete. + For Gateways, that means the Gateway needs to allow attachment from + Routes of this kind and namespace. \n The only kind of parent resource + with \"Core\" support is Gateway. This API may be extended in the + future to support additional kinds of parent resources such as one + of the route kinds. \n It is invalid to reference an identical parent + more than once. It is valid to reference multiple distinct sections + within the same parent resource, such as 2 Listeners within a Gateway. + \n It is possible to separately reference multiple distinct objects + that may be collapsed by an implementation. For example, some implementations + may choose to merge compatible Gateway Listeners together. If that + is the case, the list of routes attached to those resources should + also be merged. \n Note that for ParentRefs that cross namespace + boundaries, there are specific rules. Cross-namespace references + are only valid if they are explicitly allowed by something in the + namespace they are referring to. For example, Gateway has the AllowedRoutes + field, and ReferenceGrant provides a generic way to enable any other + kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). The only kind of parent resource with \"Core\" support + is Gateway. This API may be extended in the future to support + additional kinds of parent resources, such as HTTPRoute. \n The + API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: + Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core + (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document how/if + Port is interpreted. \n For the purpose of status, an attachment + is considered successful as long as the parent resource accepts + it partially. For example, Gateway listeners can restrict + which Routes can attach to them by Route kind, namespace, + or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this + Route, the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. \n Implementations MAY choose to support + attaching Routes to other resources. If that is the case, + they MUST clearly document how SectionName is interpreted. + \n When unspecified (empty string), this will reference the + entire resource. For the purpose of status, an attachment + is considered successful if at least one section in the parent + resource accepts it. For example, Gateway listeners can restrict + which Routes can attach to them by Route kind, namespace, + or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this + Route, the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of GraphQL resources, filters and actions. + items: + description: GQLRouteRules defines semantics for matching an GraphQL + request based on conditions (matches), processing it (filters), + and forwarding the request to an API object (backendRefs). + properties: + filters: + description: Filters define the filters that are applied to + requests that match this rule. + items: + description: GQLRouteFilter defines the filter to be applied + to a request. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + maxItems: 16 + type: array + matches: + description: Matches define conditions used for matching the + rule against incoming graphQL requests. Each match is independent, + i.e. this rule will be matched if **any** one of the matches + is satisfied. + items: + description: GQLRouteMatch defines the predicate used to match + requests to a given action. + properties: + path: + description: Path specifies a GQL request resource matcher. + type: string + type: + description: "Type specifies GQL typematcher. When specified, + this route will be matched only if the request has the + specified method. \n Support: Extended" + enum: + - QUERY + - MUTATION + type: string + type: object + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: GQLRouteStatus defines the observed state of GQLRoute + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/adapter/internal/operator/config/crd/kustomization.yaml b/adapter/internal/operator/config/crd/kustomization.yaml index 2a61fd5a0..f2eb17419 100644 --- a/adapter/internal/operator/config/crd/kustomization.yaml +++ b/adapter/internal/operator/config/crd/kustomization.yaml @@ -14,6 +14,7 @@ resources: - bases/dp.wso2.com_jwtissuers.yaml - bases/dp.wso2.com_backendjwts.yaml - bases/dp.wso2.com_tokenissuers.yaml +- bases/dp.wso2.com_gqlroutes.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -30,6 +31,7 @@ patchesStrategicMerge: #- patches/webhook_in_interceptorservices.yaml #- patches/webhook_in_jwtissuers.yaml #- patches/webhook_in_backendjwts.yaml +#- patches/webhook_in_gqlroutes.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -46,6 +48,7 @@ patchesStrategicMerge: #- patches/cainjection_in_jwtissuers.yaml #- patches/cainjection_in_backendjwts.yaml #- patches/cainjection_in_tokenissuers.yaml +#- patches/cainjection_in_gqlroutes.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/adapter/internal/operator/config/crd/patches/cainjection_in_dp_gqlroutes.yaml b/adapter/internal/operator/config/crd/patches/cainjection_in_dp_gqlroutes.yaml new file mode 100644 index 000000000..d2385ab16 --- /dev/null +++ b/adapter/internal/operator/config/crd/patches/cainjection_in_dp_gqlroutes.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: gqlroutes.dp.wso2.com diff --git a/adapter/internal/operator/config/crd/patches/webhook_in_dp_gqlroutes.yaml b/adapter/internal/operator/config/crd/patches/webhook_in_dp_gqlroutes.yaml new file mode 100644 index 000000000..8e2338be7 --- /dev/null +++ b/adapter/internal/operator/config/crd/patches/webhook_in_dp_gqlroutes.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gqlroutes.dp.wso2.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/adapter/internal/operator/config/rbac/dp_gqlroute_editor_role.yaml b/adapter/internal/operator/config/rbac/dp_gqlroute_editor_role.yaml new file mode 100644 index 000000000..511c795e4 --- /dev/null +++ b/adapter/internal/operator/config/rbac/dp_gqlroute_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit gqlroutes. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: gqlroute-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: operator + app.kubernetes.io/part-of: operator + app.kubernetes.io/managed-by: kustomize + name: gqlroute-editor-role +rules: +- apiGroups: + - dp.wso2.com + resources: + - gqlroutes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - dp.wso2.com + resources: + - gqlroutes/status + verbs: + - get diff --git a/adapter/internal/operator/config/rbac/dp_gqlroute_viewer_role.yaml b/adapter/internal/operator/config/rbac/dp_gqlroute_viewer_role.yaml new file mode 100644 index 000000000..4dbda2d51 --- /dev/null +++ b/adapter/internal/operator/config/rbac/dp_gqlroute_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view gqlroutes. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: gqlroute-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: operator + app.kubernetes.io/part-of: operator + app.kubernetes.io/managed-by: kustomize + name: gqlroute-viewer-role +rules: +- apiGroups: + - dp.wso2.com + resources: + - gqlroutes + verbs: + - get + - list + - watch +- apiGroups: + - dp.wso2.com + resources: + - gqlroutes/status + verbs: + - get diff --git a/adapter/internal/operator/config/samples/dp_v1alpha2_gqlroute.yaml b/adapter/internal/operator/config/samples/dp_v1alpha2_gqlroute.yaml new file mode 100644 index 000000000..4d35867b4 --- /dev/null +++ b/adapter/internal/operator/config/samples/dp_v1alpha2_gqlroute.yaml @@ -0,0 +1,12 @@ +apiVersion: dp.wso2.com/v1alpha2 +kind: GQLRoute +metadata: + labels: + app.kubernetes.io/name: gqlroute + app.kubernetes.io/instance: gqlroute-sample + app.kubernetes.io/part-of: operator + app.kuberentes.io/managed-by: kustomize + app.kubernetes.io/created-by: operator + name: gqlroute-sample +spec: + # TODO(user): Add fields here diff --git a/adapter/internal/operator/constants/constants.go b/adapter/internal/operator/constants/constants.go index 8d53843ed..872c3491e 100644 --- a/adapter/internal/operator/constants/constants.go +++ b/adapter/internal/operator/constants/constants.go @@ -23,7 +23,7 @@ const ( GatewayController string = "GatewayController" ApplicationController string = "ApplicationController" SubscriptionController string = "SubscriptionController" - TokenIssuerReconSiler string = "TokenIssuerReconSiler" + TokenIssuerController string = "TokenIssuerController" ) // API events related constants diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index 857531f06..35a88f0d5 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -19,6 +19,7 @@ package dp import ( "context" + "errors" "fmt" "sync" @@ -53,6 +54,7 @@ import ( const ( httpRouteAPIIndex = "httpRouteAPIIndex" + gqlRouteAPIIndex = "gqlRouteAPIIndex" // apiAuthenticationIndex Index for API level authentications apiAuthenticationIndex = "apiAuthenticationIndex" // apiAuthenticationResourceIndex Index for resource level authentications @@ -70,6 +72,7 @@ const ( serviceHTTPRouteIndex = "serviceHTTPRouteIndex" httprouteScopeIndex = "httprouteScopeIndex" configMapBackend = "configMapBackend" + configMapAPIDefinition = "configMapAPIDefinition" secretBackend = "secretBackend" backendHTTPRouteIndex = "backendHTTPRouteIndex" interceptorServiceAPIPolicyIndex = "interceptorServiceAPIPolicyIndex" @@ -129,6 +132,12 @@ func NewAPIController(mgr manager.Manager, operatorDataStore *synchronizer.Opera return err } + if err := c.Watch(source.Kind(mgr.GetCache(), &dpv1alpha2.GQLRoute{}), handler.EnqueueRequestsFromMapFunc(apiReconciler.getAPIForGQLRoute), + predicates...); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2667, logging.BLOCKER, "Error watching GQLRoute resources: %v", err)) + return err + } + if err := c.Watch(source.Kind(mgr.GetCache(), &gwapiv1b1.Gateway{}), handler.EnqueueRequestsFromMapFunc(apiReconciler.getAPIsForGateway), predicates...); err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2611, logging.BLOCKER, "Error watching API resources: %v", err)) @@ -200,6 +209,9 @@ func NewAPIController(mgr manager.Manager, operatorDataStore *synchronizer.Opera // +kubebuilder:rbac:groups=dp.wso2.com,resources=httproutes,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=dp.wso2.com,resources=httproutes/status,verbs=get;update;patch // +kubebuilder:rbac:groups=dp.wso2.com,resources=httproutes/finalizers,verbs=update +// +kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes/finalizers,verbs=update // +kubebuilder:rbac:groups=dp.wso2.com,resources=authentications,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=dp.wso2.com,resources=authentications/status,verbs=get;update;patch // +kubebuilder:rbac:groups=dp.wso2.com,resources=authentications/finalizers,verbs=update @@ -273,12 +285,12 @@ func (apiReconciler *APIReconciler) applyStartupAPIs() { // resolveAPIRefs validates following references related to the API // - HTTPRoutes func (apiReconciler *APIReconciler) resolveAPIRefs(ctx context.Context, api dpv1alpha2.API) (*synchronizer.APIEvent, error) { - var prodHTTPRouteRefs, sandHTTPRouteRefs []string + var prodRouteRefs, sandRouteRefs []string if len(api.Spec.Production) > 0 { - prodHTTPRouteRefs = api.Spec.Production[0].HTTPRouteRefs + prodRouteRefs = api.Spec.Production[0].HTTPRouteRefs } if len(api.Spec.Sandbox) > 0 { - sandHTTPRouteRefs = api.Spec.Sandbox[0].HTTPRouteRefs + sandRouteRefs = api.Spec.Sandbox[0].HTTPRouteRefs } apiState := &synchronizer.APIState{ @@ -327,30 +339,29 @@ func (apiReconciler *APIReconciler) resolveAPIRefs(ctx context.Context, api dpv1 } } - if len(prodHTTPRouteRefs) > 0 { + if len(prodRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == "REST" { apiState.ProdHTTPRoute = &synchronizer.HTTPRouteState{} - if apiState.ProdHTTPRoute, err = apiReconciler.resolveHTTPRouteRefs(ctx, apiState.ProdHTTPRoute, prodHTTPRouteRefs, + if apiState.ProdHTTPRoute, err = apiReconciler.resolveHTTPRouteRefs(ctx, apiState.ProdHTTPRoute, prodRouteRefs, namespace, apiState.InterceptorServiceMapping, api); err != nil { return nil, fmt.Errorf("error while resolving production httpRouteref %s in namespace :%s has not found. %s", - prodHTTPRouteRefs, namespace, err.Error()) + prodRouteRefs, namespace, err.Error()) } - // TODO(amali) check what happens if parents are not available if !apiReconciler.ods.IsGatewayAvailable(types.NamespacedName{ Name: string(apiState.ProdHTTPRoute.HTTPRouteCombined.Spec.ParentRefs[0].Name), Namespace: utils.GetNamespace(apiState.ProdHTTPRoute.HTTPRouteCombined.Spec.ParentRefs[0].Namespace, apiState.ProdHTTPRoute.HTTPRouteCombined.Namespace), }) { return nil, fmt.Errorf("no gateway available for httpRouteref %s in namespace :%s has not found", - prodHTTPRouteRefs, namespace) + prodRouteRefs, namespace) } } - if len(sandHTTPRouteRefs) > 0 { + if len(sandRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == "REST" { apiState.SandHTTPRoute = &synchronizer.HTTPRouteState{} - if apiState.SandHTTPRoute, err = apiReconciler.resolveHTTPRouteRefs(ctx, apiState.SandHTTPRoute, sandHTTPRouteRefs, + if apiState.SandHTTPRoute, err = apiReconciler.resolveHTTPRouteRefs(ctx, apiState.SandHTTPRoute, sandRouteRefs, namespace, apiState.InterceptorServiceMapping, api); err != nil { return nil, fmt.Errorf("error while resolving sandbox httpRouteref %s in namespace :%s has not found. %s", - sandHTTPRouteRefs, namespace, err.Error()) + sandRouteRefs, namespace, err.Error()) } if !apiReconciler.ods.IsGatewayAvailable(types.NamespacedName{ Name: string(apiState.SandHTTPRoute.HTTPRouteCombined.Spec.ParentRefs[0].Name), @@ -358,11 +369,28 @@ func (apiReconciler *APIReconciler) resolveAPIRefs(ctx context.Context, api dpv1 apiState.SandHTTPRoute.HTTPRouteCombined.Namespace), }) { return nil, fmt.Errorf("no gateway available for httpRouteref %s in namespace :%s has not found", - sandHTTPRouteRefs, namespace) + sandRouteRefs, namespace) + } + } + + // handle gql apis + if len(prodRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == "GraphQL" { + if apiState.ProdGQLRoute, err = apiReconciler.resolveGQLRouteRefs(ctx, prodRouteRefs, namespace, + api); err != nil { + return nil, fmt.Errorf("error while resolving production gqlRouteref %s in namespace :%s has not found. %s", + prodRouteRefs, namespace, err.Error()) + } + + } + if len(sandRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == "GraphQL" { + if apiState.SandGQLRoute, err = apiReconciler.resolveGQLRouteRefs(ctx, sandRouteRefs, namespace, + api); err != nil { + return nil, fmt.Errorf("error while resolving sandbox gqlRouteref %s in namespace :%s has not found. %s", + sandRouteRefs, namespace, err.Error()) } } - loggers.LoggerAPKOperator.Debugf("HTTPRoutes are retrieved successfully for API CR %s", apiRef.String()) + loggers.LoggerAPKOperator.Debugf("Child references are retrieved successfully for API CR %s", apiRef.String()) if !api.Status.DeploymentStatus.Accepted { apiReconciler.ods.AddAPIState(apiRef, apiState) @@ -386,14 +414,19 @@ func (apiReconciler *APIReconciler) removeOldOwnerRefs(ctx context.Context, apiS if apiState.SandHTTPRoute != nil { apiReconciler.removeOldOwnerRefsFromHTTPRoute(ctx, apiState.SandHTTPRoute, api.Name, api.Namespace) } + if apiState.ProdGQLRoute != nil { + apiReconciler.removeOldOwnerRefsFromGQLRoute(ctx, apiState.ProdGQLRoute, api.Name, api.Namespace) + } + if apiState.SandGQLRoute != nil { + apiReconciler.removeOldOwnerRefsFromGQLRoute(ctx, apiState.SandGQLRoute, api.Name, api.Namespace) + } // remove old owner refs from interceptor services interceptorServiceList := &dpv1alpha1.InterceptorServiceList{} if err := apiReconciler.client.List(ctx, interceptorServiceList, &k8client.ListOptions{ Namespace: api.Namespace, }); err != nil { - loggers.LoggerAPKOperator.Errorf("error while listing CRs for API CR %s, %s", - api.Name, err.Error()) + loggers.LoggerAPKOperator.Errorf("error while listing CRs for API CR %s, %s", api.Name, err.Error()) } for item := range interceptorServiceList.Items { interceptorService := interceptorServiceList.Items[item] @@ -436,6 +469,47 @@ func (apiReconciler *APIReconciler) removeOldOwnerRefs(ctx context.Context, apiS } } +func (apiReconciler *APIReconciler) removeOldOwnerRefsFromGQLRoute(ctx context.Context, + gqlRouteState *synchronizer.GQLRouteState, apiName, apiNamespace string) { + // scope CRs + scopeList := &dpv1alpha1.ScopeList{} + if err := apiReconciler.client.List(ctx, scopeList, &k8client.ListOptions{ + Namespace: apiNamespace, + }); err != nil { + loggers.LoggerAPKOperator.Errorf("error while listing authentication CRs for API CR %s, %s", + apiName, err.Error()) + } + for scopeListItem := range scopeList.Items { + scope := scopeList.Items[scopeListItem] + // check scope has similar item inside the apiState.ProdHTTPRoute.Scopes + scopeFound := false + for _, attachedScope := range gqlRouteState.Scopes { + if scope.GetName() == attachedScope.GetName() { + scopeFound = true + break + } + } + if !scopeFound { + apiReconciler.removeOldOwnerRefsFromChild(ctx, &scope, apiName, apiNamespace) + } + } + + // backend CRs + backendList := &dpv1alpha1.BackendList{} + if err := apiReconciler.client.List(ctx, backendList, &k8client.ListOptions{ + Namespace: apiNamespace, + }); err != nil { + loggers.LoggerAPKOperator.Errorf("error while listing authentication CRs for API CR %s, %s", + apiName, err.Error()) + } + for item := range backendList.Items { + backend := backendList.Items[item] + if backend.GetName() != string(gqlRouteState.GQLRouteCombined.Spec.BackendRefs[0].Name) { + apiReconciler.removeOldOwnerRefsFromChild(ctx, &backend, apiName, apiNamespace) + } + } +} + func (apiReconciler *APIReconciler) removeOldOwnerRefsFromHTTPRoute(ctx context.Context, httpRouteState *synchronizer.HTTPRouteState, apiName, apiNamespace string) { // scope CRs @@ -502,6 +576,16 @@ func (apiReconciler *APIReconciler) removeOldOwnerRefsFromChild(ctx context.Cont } } +func (apiReconciler *APIReconciler) resolveGQLRouteRefs(ctx context.Context, gqlRouteRefs []string, + namespace string, api dpv1alpha2.API) (*synchronizer.GQLRouteState, error) { + gqlRouteState, err := apiReconciler.concatGQLRoutes(ctx, gqlRouteRefs, namespace, api) + if err != nil { + return nil, err + } + gqlRouteState.Scopes, err = apiReconciler.getScopesForGQLRoute(ctx, gqlRouteState.GQLRouteCombined, api) + return &gqlRouteState, err +} + // resolveHTTPRouteRefs validates following references related to the API // - Authentications func (apiReconciler *APIReconciler) resolveHTTPRouteRefs(ctx context.Context, httpRouteState *synchronizer.HTTPRouteState, @@ -517,6 +601,40 @@ func (apiReconciler *APIReconciler) resolveHTTPRouteRefs(ctx context.Context, ht return httpRouteState, err } +func (apiReconciler *APIReconciler) concatGQLRoutes(ctx context.Context, gqlRouteRefs []string, + namespace string, api dpv1alpha2.API) (synchronizer.GQLRouteState, error) { + gqlRouteState := synchronizer.GQLRouteState{} + gqlRoutePartitions := make(map[string]*dpv1alpha2.GQLRoute) + for _, gqlRouteRef := range gqlRouteRefs { + var gqlRoute dpv1alpha2.GQLRoute + namespacedName := types.NamespacedName{Namespace: namespace, Name: gqlRouteRef} + if err := utils.ResolveRef(ctx, apiReconciler.client, &api, namespacedName, true, &gqlRoute); err != nil { + return gqlRouteState, fmt.Errorf("error while getting gqlroute %s in namespace :%s, %s", gqlRouteRef, + namespace, err.Error()) + } + gqlRoutePartitions[namespacedName.String()] = &gqlRoute + if gqlRouteState.GQLRouteCombined == nil { + gqlRouteState.GQLRouteCombined = &gqlRoute + } else { + gqlRouteState.GQLRouteCombined.Spec.Rules = append(gqlRouteState.GQLRouteCombined.Spec.Rules, + gqlRoute.Spec.Rules...) + } + } + gqlRouteState.GQLRoutePartitions = gqlRoutePartitions + backendNamespacedName := types.NamespacedName{ + Name: string(gqlRouteState.GQLRouteCombined.Spec.BackendRefs[0].Name), + Namespace: namespace, + } + resolvedBackend := utils.GetResolvedBackend(ctx, apiReconciler.client, backendNamespacedName, &api) + if resolvedBackend != nil { + gqlRouteState.BackendMapping = map[string]*dpv1alpha1.ResolvedBackend{ + backendNamespacedName.String(): resolvedBackend, + } + return gqlRouteState, nil + } + return gqlRouteState, errors.New("error while resolving backend for gqlroute") +} + func (apiReconciler *APIReconciler) concatHTTPRoutes(ctx context.Context, httpRouteRefs []string, namespace string, api dpv1alpha2.API) (*gwapiv1b1.HTTPRoute, map[string]*gwapiv1b1.HTTPRoute, error) { var combinedHTTPRoute *gwapiv1b1.HTTPRoute @@ -578,6 +696,26 @@ func (apiReconciler *APIReconciler) getRatelimitPoliciesForAPI(ctx context.Conte return ratelimitPolicies, nil } +func (apiReconciler *APIReconciler) getScopesForGQLRoute(ctx context.Context, + gqlRoute *dpv1alpha2.GQLRoute, api dpv1alpha2.API) (map[string]dpv1alpha1.Scope, error) { + scopes := make(map[string]dpv1alpha1.Scope) + for _, rule := range gqlRoute.Spec.Rules { + for _, filter := range rule.Filters { + if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindScope { + scope := &dpv1alpha1.Scope{} + if err := utils.ResolveRef(ctx, apiReconciler.client, &api, + types.NamespacedName{Namespace: gqlRoute.Namespace, Name: string(filter.ExtensionRef.Name)}, false, + scope); err != nil { + return nil, fmt.Errorf("error while getting scope %s in namespace :%s, %s", filter.ExtensionRef.Name, + gqlRoute.Namespace, err.Error()) + } + scopes[utils.NamespacedName(scope).String()] = *scope + } + } + } + return scopes, nil +} + func (apiReconciler *APIReconciler) getScopesForHTTPRoute(ctx context.Context, httpRoute *gwapiv1b1.HTTPRoute, api dpv1alpha2.API) (map[string]dpv1alpha1.Scope, error) { scopes := make(map[string]dpv1alpha1.Scope) @@ -790,6 +928,40 @@ func (apiReconciler *APIReconciler) getResolvedBackendsMapping(ctx context.Conte return backendMapping } +// getAPIForGQLRoute triggers the API controller reconcile method based on the changes detected +// from GQLRoute objects. If the changes are done for an API stored in the Operator Data store, +// a new reconcile event will be created and added to the reconcile event queue. +func (apiReconciler *APIReconciler) getAPIForGQLRoute(ctx context.Context, obj k8client.Object) []reconcile.Request { + gqlRoute, ok := obj.(*dpv1alpha2.GQLRoute) + if !ok { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2665, logging.TRIVIAL, "Unexpected object type, bypassing reconciliation: %v", gqlRoute)) + return []reconcile.Request{} + } + apiList := &dpv1alpha2.APIList{} + if err := apiReconciler.client.List(ctx, apiList, &k8client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(gqlRouteAPIIndex, utils.NamespacedName(gqlRoute).String()), + }); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2666, logging.CRITICAL, "Unable to find associated APIs: %s", utils.NamespacedName(gqlRoute).String())) + return []reconcile.Request{} + } + if len(apiList.Items) == 0 { + loggers.LoggerAPKOperator.Debugf("APIs for GQLRoute not found: %s", utils.NamespacedName(gqlRoute).String()) + return []reconcile.Request{} + } + requests := []reconcile.Request{} + for _, api := range apiList.Items { + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: api.Name, + Namespace: api.Namespace}, + } + requests = append(requests, req) + loggers.LoggerAPKOperator.Infof("Adding reconcile request for API: %s/%s with API UUID: %v", api.Namespace, api.Name, + string(api.ObjectMeta.UID)) + } + return requests +} + // getAPIForHTTPRoute triggers the API controller reconcile method based on the changes detected // from HTTPRoute objects. If the changes are done for an API stored in the Operator Data store, // a new reconcile event will be created and added to the reconcile event queue. @@ -837,19 +1009,39 @@ func (apiReconciler *APIReconciler) getAPIsForConfigMap(ctx context.Context, obj } backendList := &dpv1alpha1.BackendList{} - if err := apiReconciler.client.List(ctx, backendList, &k8client.ListOptions{ + err := apiReconciler.client.List(ctx, backendList, &k8client.ListOptions{ FieldSelector: fields.OneTermEqualSelector(configMapBackend, utils.NamespacedName(configMap).String()), - }); err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2647, logging.CRITICAL, "Unable to find associated Backends for ConfigMap: %s", utils.NamespacedName(configMap).String())) - return []reconcile.Request{} + }) + if err == nil { + requests := []reconcile.Request{} + for item := range backendList.Items { + backend := backendList.Items[item] + requests = append(requests, apiReconciler.getAPIsForBackend(ctx, &backend)...) + } + return requests } - requests := []reconcile.Request{} - for item := range backendList.Items { - backend := backendList.Items[item] - requests = append(requests, apiReconciler.getAPIsForBackend(ctx, &backend)...) + apiList := &dpv1alpha2.APIList{} + err = apiReconciler.client.List(ctx, apiList, &k8client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(configMapAPIDefinition, utils.NamespacedName(configMap).String()), + }) + if err == nil { + requests := []reconcile.Request{} + for _, api := range apiList.Items { + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: api.Name, + Namespace: api.Namespace}, + } + requests = append(requests, req) + loggers.LoggerAPKOperator.Infof("Adding reconcile request for API: %s/%s with API UUID: %v", api.Namespace, api.Name, + string(api.ObjectMeta.UID)) + } + return requests } - return requests + + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2647, logging.MINOR, "Unable to find associated APIs for ConfigMap: %s", utils.NamespacedName(configMap).String())) + return []reconcile.Request{} } // getAPIsForSecret triggers the API controller reconcile method based on the changes detected @@ -865,7 +1057,7 @@ func (apiReconciler *APIReconciler) getAPIsForSecret(ctx context.Context, obj k8 if err := apiReconciler.client.List(ctx, backendList, &k8client.ListOptions{ FieldSelector: fields.OneTermEqualSelector(secretBackend, utils.NamespacedName(secret).String()), }); err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2621, logging.CRITICAL, "Unable to find associated Backends for Secret: %s", utils.NamespacedName(secret).String())) + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2621, logging.MINOR, "Unable to find associated Backends for Secret: %s", utils.NamespacedName(secret).String())) return []reconcile.Request{} } @@ -1183,6 +1375,53 @@ func addIndexes(ctx context.Context, mgr manager.Manager) error { return err } + if err := mgr.GetFieldIndexer().IndexField(ctx, &dpv1alpha2.API{}, gqlRouteAPIIndex, + func(rawObj k8client.Object) []string { + api := rawObj.(*dpv1alpha2.API) + var gqlRoutes []string + if len(api.Spec.Production) > 0 { + for _, ref := range api.Spec.Production[0].HTTPRouteRefs { + if ref != "" { + gqlRoutes = append(gqlRoutes, + types.NamespacedName{ + Namespace: api.Namespace, + Name: ref, + }.String()) + } + } + } + if len(api.Spec.Sandbox) > 0 { + for _, ref := range api.Spec.Sandbox[0].HTTPRouteRefs { + if ref != "" { + gqlRoutes = append(gqlRoutes, + types.NamespacedName{ + Namespace: api.Namespace, + Name: ref, + }.String()) + } + } + } + return gqlRoutes + }); err != nil { + return err + } + + if err := mgr.GetFieldIndexer().IndexField(ctx, &dpv1alpha2.API{}, configMapAPIDefinition, + func(rawObj k8client.Object) []string { + api := rawObj.(*dpv1alpha2.API) + var configMaps []string + if api.Spec.DefinitionFileRef == "" { + configMaps = append(configMaps, + types.NamespacedName{ + Name: string(api.Spec.DefinitionFileRef), + Namespace: api.Namespace, + }.String()) + } + return configMaps + }); err != nil { + return err + } + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.HTTPRoute{}, httprouteScopeIndex, func(rawObj k8client.Object) []string { httpRoute := rawObj.(*gwapiv1b1.HTTPRoute) diff --git a/adapter/internal/operator/controllers/dp/gqlroute_controller.go b/adapter/internal/operator/controllers/dp/gqlroute_controller.go new file mode 100644 index 000000000..4ef8c8dfe --- /dev/null +++ b/adapter/internal/operator/controllers/dp/gqlroute_controller.go @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package dp + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + dpv1alpha2 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha2" +) + +// GQLRouteReconciler reconciles a GQLRoute object +type GQLRouteReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the GQLRoute object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile +func (r *GQLRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GQLRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&dpv1alpha2.GQLRoute{}). + Complete(r) +} diff --git a/adapter/internal/operator/controllers/dp/suite_test.go b/adapter/internal/operator/controllers/dp/suite_test.go index 976db3582..017df70bc 100644 --- a/adapter/internal/operator/controllers/dp/suite_test.go +++ b/adapter/internal/operator/controllers/dp/suite_test.go @@ -32,6 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" dpv1alpha1 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha1" + dpv1alpha2 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha2" //+kubebuilder:scaffold:imports ) @@ -66,6 +67,9 @@ var _ = BeforeSuite(func() { err = dpv1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = dpv1alpha2.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/adapter/internal/operator/controllers/dp/tokenissuer_controller.go b/adapter/internal/operator/controllers/dp/tokenissuer_controller.go index bcdadd5b0..a399da059 100644 --- a/adapter/internal/operator/controllers/dp/tokenissuer_controller.go +++ b/adapter/internal/operator/controllers/dp/tokenissuer_controller.go @@ -76,12 +76,12 @@ func (r *TokenssuerReconciler) Reconcile(ctx context.Context, req ctrl.Request) jwtKey := req.NamespacedName var jwtIssuerList = new(dpv1alpha1.TokenIssuerList) if err := r.client.List(ctx, jwtIssuerList); err != nil { - return reconcile.Result{}, fmt.Errorf("failed to get jwtIssuer %s/%s", - jwtKey.Namespace, jwtKey.Name) + return reconcile.Result{}, fmt.Errorf("failed to get jwtIssuer %s/%s", jwtKey.Namespace, jwtKey.Name) } jwtIssuerMapping, err := getJWTIssuers(ctx, r.client, jwtKey) if err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2660, logging.CRITICAL, "Unable to find associated JWTIssuers: %s", err.Error())) + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2660, logging.CRITICAL, + "Unable to find associated JWTIssuers for %s : %s", req.NamespacedName.String(), err.Error())) return ctrl.Result{}, err } UpdateEnforcerJWTIssuers(jwtIssuerMapping) @@ -99,7 +99,7 @@ func NewTokenIssuerReconciler(mgr manager.Manager) error { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2658, logging.CRITICAL, "Error adding indexes: %v", err)) return err } - c, err := controller.New(constants.TokenIssuerReconSiler, mgr, controller.Options{Reconciler: r}) + c, err := controller.New(constants.TokenIssuerController, mgr, controller.Options{Reconciler: r}) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2657, logging.BLOCKER, "Error creating TokenIssuer controller: %v", err.Error())) return err diff --git a/adapter/internal/operator/synchronizer/api_state.go b/adapter/internal/operator/synchronizer/api_state.go index 4f4a9e5f7..41e5c2fe4 100644 --- a/adapter/internal/operator/synchronizer/api_state.go +++ b/adapter/internal/operator/synchronizer/api_state.go @@ -30,6 +30,8 @@ type APIState struct { APIDefinition *v1alpha2.API ProdHTTPRoute *HTTPRouteState SandHTTPRoute *HTTPRouteState + ProdGQLRoute *GQLRouteState + SandGQLRoute *GQLRouteState Authentications map[string]v1alpha1.Authentication RateLimitPolicies map[string]v1alpha1.RateLimitPolicy ResourceAuthentications map[string]v1alpha1.Authentication @@ -52,3 +54,13 @@ type HTTPRouteState struct { BackendMapping map[string]*v1alpha1.ResolvedBackend Scopes map[string]v1alpha1.Scope } + +// GQLRouteState holds the state of the deployed gqlRoutes. This state is compared with +// the state of the Kubernetes controller cache to detect updates. +// +k8s:deepcopy-gen=true +type GQLRouteState struct { + GQLRouteCombined *v1alpha2.GQLRoute + GQLRoutePartitions map[string]*v1alpha2.GQLRoute + BackendMapping map[string]*v1alpha1.ResolvedBackend + Scopes map[string]v1alpha1.Scope +} diff --git a/adapter/internal/operator/synchronizer/data_store.go b/adapter/internal/operator/synchronizer/data_store.go index 97c2e080d..9f09a9ed7 100644 --- a/adapter/internal/operator/synchronizer/data_store.go +++ b/adapter/internal/operator/synchronizer/data_store.go @@ -23,6 +23,7 @@ import ( "github.com/wso2/apk/adapter/internal/loggers" "github.com/wso2/apk/adapter/internal/operator/utils" dpv1alpha1 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha1" + dpv1alpha2 "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha2" "k8s.io/apimachinery/pkg/types" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -98,6 +99,23 @@ func (ods *OperatorDataStore) processAPIState(apiNamespacedName types.Namespaced } cachedAPI.ProdHTTPRoute = nil } + if apiState.ProdGQLRoute != nil { + if cachedAPI.ProdGQLRoute == nil { + cachedAPI.ProdGQLRoute = apiState.ProdGQLRoute + updated = true + events = append(events, "Production") + } else if routeEvents, routesUpdated := updateGQLRoute(apiState.ProdGQLRoute, cachedAPI.ProdGQLRoute, + "Production"); routesUpdated { + updated = true + events = append(events, routeEvents...) + } + } else { + if cachedAPI.ProdGQLRoute != nil { + updated = true + events = append(events, "Production") + } + cachedAPI.ProdGQLRoute = nil + } if apiState.SandHTTPRoute != nil { if cachedAPI.SandHTTPRoute == nil { cachedAPI.SandHTTPRoute = apiState.SandHTTPRoute @@ -114,6 +132,22 @@ func (ods *OperatorDataStore) processAPIState(apiNamespacedName types.Namespaced } cachedAPI.SandHTTPRoute = nil } + if apiState.SandGQLRoute != nil { + if cachedAPI.SandGQLRoute == nil { + cachedAPI.SandGQLRoute = apiState.SandGQLRoute + updated = true + events = append(events, "Sandbox") + } else if routeEvents, routesUpdated := updateGQLRoute(apiState.SandGQLRoute, cachedAPI.SandGQLRoute, "Sandbox"); routesUpdated { + updated = true + events = append(events, routeEvents...) + } + } else { + if cachedAPI.SandGQLRoute != nil { + updated = true + events = append(events, "Sandbox") + } + cachedAPI.SandGQLRoute = nil + } if len(apiState.Authentications) != len(cachedAPI.Authentications) { cachedAPI.Authentications = apiState.Authentications updated = true @@ -293,7 +327,7 @@ func (ods *OperatorDataStore) processAPIState(apiNamespacedName types.Namespaced return *cachedAPI, events, updated } -// UpdateAPIState update the APIState on ref updates +// updateHTTPRoute update the APIState on ref updates func updateHTTPRoute(httpRoute *HTTPRouteState, cachedHTTPRoute *HTTPRouteState, endpointType string) ([]string, bool) { var updated bool events := []string{} @@ -350,6 +384,63 @@ func updateHTTPRoute(httpRoute *HTTPRouteState, cachedHTTPRoute *HTTPRouteState, return events, updated } +// updateGQLRoute update the APIState on ref updates +func updateGQLRoute(gqlRoute *GQLRouteState, cachedGQLRoute *GQLRouteState, endpointType string) ([]string, bool) { + var updated bool + events := []string{} + if cachedGQLRoute.GQLRouteCombined == nil || !isEqualGQLRoutes(cachedGQLRoute.GQLRoutePartitions, gqlRoute.GQLRoutePartitions) { + cachedGQLRoute.GQLRouteCombined = gqlRoute.GQLRouteCombined + cachedGQLRoute.GQLRoutePartitions = gqlRoute.GQLRoutePartitions + updated = true + events = append(events, endpointType+" Endpoint") + } + + if len(gqlRoute.Scopes) != len(cachedGQLRoute.Scopes) { + cachedGQLRoute.Scopes = gqlRoute.Scopes + updated = true + events = append(events, "Resource Scopes") + } else { + for key, scope := range gqlRoute.Scopes { + if existingScope, found := cachedGQLRoute.Scopes[key]; found { + if scope.UID != existingScope.UID || scope.Generation > existingScope.Generation { + cachedGQLRoute.Scopes = gqlRoute.Scopes + updated = true + events = append(events, "Resource Scopes") + break + } + } else { + cachedGQLRoute.Scopes = gqlRoute.Scopes + updated = true + events = append(events, "Resource Scopes") + break + } + } + } + + if len(gqlRoute.BackendMapping) != len(cachedGQLRoute.BackendMapping) { + cachedGQLRoute.BackendMapping = gqlRoute.BackendMapping + updated = true + events = append(events, endpointType+" Backend Properties") + } else { + for key, backend := range gqlRoute.BackendMapping { + if existingBackend, found := cachedGQLRoute.BackendMapping[key]; found { + if backend.Backend.UID != existingBackend.Backend.UID || backend.Backend.Generation > existingBackend.Backend.Generation { + cachedGQLRoute.BackendMapping = gqlRoute.BackendMapping + updated = true + events = append(events, endpointType+" Backend Properties") + break + } + } else { + cachedGQLRoute.BackendMapping = gqlRoute.BackendMapping + updated = true + events = append(events, endpointType+" Backend Properties") + break + } + } + } + return events, updated +} + func isEqualHTTPRoutes(cachedHTTPRoutes, newHTTPRoutes map[string]*gwapiv1b1.HTTPRoute) bool { for key, cachedHTTPRoute := range cachedHTTPRoutes { if newHTTPRoutes[key] == nil { @@ -363,6 +454,19 @@ func isEqualHTTPRoutes(cachedHTTPRoutes, newHTTPRoutes map[string]*gwapiv1b1.HTT return true } +func isEqualGQLRoutes(cachedGQLRoutes, newGQLRoutes map[string]*dpv1alpha2.GQLRoute) bool { + for key, cachedGQLRoute := range cachedGQLRoutes { + if newGQLRoutes[key] == nil { + return false + } + if newGQLRoutes[key].UID == cachedGQLRoute.UID && + newGQLRoutes[key].Generation > cachedGQLRoute.Generation { + return false + } + } + return true +} + // GetCachedAPI get cached apistate func (ods *OperatorDataStore) GetCachedAPI(apiName types.NamespacedName) (APIState, bool) { if cachedAPI, found := ods.apiStore[apiName]; found { diff --git a/adapter/internal/operator/synchronizer/gql_api.go b/adapter/internal/operator/synchronizer/gql_api.go new file mode 100644 index 000000000..5a2b387b1 --- /dev/null +++ b/adapter/internal/operator/synchronizer/gql_api.go @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package synchronizer + +import ( + "errors" + "fmt" + + "github.com/wso2/apk/adapter/config" + "github.com/wso2/apk/adapter/internal/dataholder" + "github.com/wso2/apk/adapter/internal/discovery/xds" + "github.com/wso2/apk/adapter/internal/discovery/xds/common" + "github.com/wso2/apk/adapter/internal/loggers" + "github.com/wso2/apk/adapter/internal/oasparser/model" + "github.com/wso2/apk/adapter/internal/operator/constants" + "github.com/wso2/apk/adapter/pkg/logging" + "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha2" + "k8s.io/apimachinery/pkg/types" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// deployGQLAPIInGateway deploys the related API in CREATE and UPDATE events. +func deployGQLAPIInGateway(apiState APIState) error { + var err error + if len(apiState.OldOrganizationID) != 0 { + xds.RemoveAPIFromOrgAPIMap(string((*apiState.APIDefinition).ObjectMeta.UID), apiState.OldOrganizationID) + } + if apiState.ProdGQLRoute == nil { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Production) + } + if apiState.SandGQLRoute == nil { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Sandbox) + } + if apiState.ProdGQLRoute != nil { + _, err = generateGQLAdapterInternalAPI(apiState, apiState.ProdGQLRoute, constants.Production) + } + if err != nil { + return err + } + if apiState.SandGQLRoute != nil { + _, err = generateGQLAdapterInternalAPI(apiState, apiState.SandGQLRoute, constants.Sandbox) + } + return err +} + +// generateGQLAdapterInternalAPI this will populate a AdapterInternalAPI representation for an GQLRoute +func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, envType string) (*model.AdapterInternalAPI, error) { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetIsDefaultVersion(apiState.APIDefinition.Spec.IsDefaultVersion) + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + adapterInternalAPI.SetAPIDefinitionFile(apiState.APIDefinitionFile) + adapterInternalAPI.SetAPIDefinitionEndpoint(apiState.APIDefinition.Spec.DefinitionPath) + adapterInternalAPI.SetSubscriptionValidation(apiState.SubscriptionValidation) + adapterInternalAPI.EnvType = envType + + environment := apiState.APIDefinition.Spec.Environment + if environment == "" { + conf := config.ReadConfigs() + environment = conf.Adapter.Environment + } + adapterInternalAPI.SetEnvironment(environment) + + resourceParams := model.ResourceParams{ + AuthSchemes: apiState.Authentications, + ResourceAuthSchemes: apiState.ResourceAuthentications, + BackendMapping: gqlRoute.BackendMapping, + APIPolicies: apiState.APIPolicies, + ResourceAPIPolicies: apiState.ResourceAPIPolicies, + ResourceScopes: gqlRoute.Scopes, + InterceptorServiceMapping: apiState.InterceptorServiceMapping, + BackendJWTMapping: apiState.BackendJWTMapping, + 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, err + } + vHosts := getVhostsForGQLAPI(gqlRoute.GQLRouteCombined) + labels := getLabelsForGQLAPI(gqlRoute.GQLRouteCombined) + listeners, relativeSectionNames := getListenersForGQLAPI(gqlRoute.GQLRouteCombined, adapterInternalAPI.UUID) + // We dont have a use case where a perticular API's two different gql routes refer to two different gateway. Hence get the first listener name for the list for processing. + if len(listeners) == 0 || len(relativeSectionNames) == 0 { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MINOR, "Failed to find a matching listener for gql route: %v. ", + gqlRoute.GQLRouteCombined.Name)) + return nil, errors.New("failed to find matching listener name for the provided gql route") + } + listenerName := listeners[0] + sectionName := relativeSectionNames[0] + if len(listeners) != 0 { + err := xds.UpdateAPICache(vHosts, labels, listenerName, sectionName, adapterInternalAPI) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MAJOR, "Error updating the API : %s:%s in vhosts: %s, API_UUID: %v. %v", + adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), vHosts, adapterInternalAPI.UUID, err)) + } + } + + return &adapterInternalAPI, nil +} + +// getVhostForAPI returns the vHosts related to an API. +func getVhostsForGQLAPI(gqlRoute *v1alpha2.GQLRoute) []string { + var vHosts []string + for _, hostName := range gqlRoute.Spec.Hostnames { + vHosts = append(vHosts, string(hostName)) + } + fmt.Println("vhosts size: ", len(vHosts)) + return vHosts +} + +// getLabelsForAPI returns the labels related to an API. +func getLabelsForGQLAPI(gqlRoute *v1alpha2.GQLRoute) []string { + var labels []string + var err error + for _, parentRef := range gqlRoute.Spec.ParentRefs { + err = xds.SanitizeGateway(string(parentRef.Name), false) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2653, logging.CRITICAL, "Gateway Label is invalid: %s", string(parentRef.Name))) + } else { + labels = append(labels, string(parentRef.Name)) + } + } + return labels +} + +// getListenersForGQLAPI returns the listeners related to an API. +func getListenersForGQLAPI(gqlRoute *v1alpha2.GQLRoute, apiUUID string) ([]string, []string) { + var listeners []string + var sectionNames []string + for _, parentRef := range gqlRoute.Spec.ParentRefs { + namespace := gqlRoute.GetNamespace() + if parentRef.Namespace != nil && *parentRef.Namespace != "" { + namespace = string(*parentRef.Namespace) + } + gateway, found := dataholder.GetGatewayMap()[types.NamespacedName{ + Namespace: namespace, + Name: string(parentRef.Name), + }.String()] + if found { + // find the matching listener + matchedListener, listenerFound := common.FindElement(gateway.Spec.Listeners, func(listener gwapiv1b1.Listener) bool { + if string(listener.Name) == string(*parentRef.SectionName) { + return true + } + return false + }) + if listenerFound { + sectionNames = append(sectionNames, string(matchedListener.Name)) + listeners = append(listeners, common.GetEnvoyListenerName(string(matchedListener.Protocol), uint32(matchedListener.Port))) + continue + } + } + loggers.LoggerAPKOperator.Errorf("Failed to find matching listeners for the gqlroute: %+v", gqlRoute.Name) + } + return listeners, sectionNames +} + +func deleteGQLAPIFromEnv(gqlRoute *v1alpha2.GQLRoute, apiState APIState) error { + labels := getLabelsForGQLAPI(gqlRoute) + org := apiState.APIDefinition.Spec.Organization + uuid := string(apiState.APIDefinition.ObjectMeta.UID) + return xds.DeleteAPICREvent(labels, uuid, org) +} + +// undeployGQLAPIInGateway undeploys the related API in CREATE and UPDATE events. +func undeployGQLAPIInGateway(apiState APIState) error { + var err error + if apiState.ProdGQLRoute != nil { + err = deleteGQLAPIFromEnv(apiState.ProdGQLRoute.GQLRouteCombined, apiState) + } + if err != nil { + loggers.LoggerXds.ErrorC(logging.PrintError(logging.Error2630, logging.MAJOR, "Error undeploying prod gqlRoute of API : %v in Organization %v from environments."+ + " Hence not checking on deleting the sand gqlRoute of the API", string(apiState.APIDefinition.ObjectMeta.UID), apiState.APIDefinition.Spec.Organization)) + return err + } + if apiState.SandGQLRoute != nil { + err = deleteGQLAPIFromEnv(apiState.SandGQLRoute.GQLRouteCombined, apiState) + } + return err +} diff --git a/adapter/internal/operator/synchronizer/rest_api.go b/adapter/internal/operator/synchronizer/rest_api.go new file mode 100644 index 000000000..a3137f858 --- /dev/null +++ b/adapter/internal/operator/synchronizer/rest_api.go @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package synchronizer + +import ( + "errors" + "fmt" + + "github.com/wso2/apk/adapter/config" + "github.com/wso2/apk/adapter/internal/dataholder" + "github.com/wso2/apk/adapter/internal/discovery/xds" + "github.com/wso2/apk/adapter/internal/discovery/xds/common" + "github.com/wso2/apk/adapter/internal/loggers" + "github.com/wso2/apk/adapter/internal/oasparser/model" + "github.com/wso2/apk/adapter/internal/operator/constants" + "github.com/wso2/apk/adapter/pkg/logging" + "k8s.io/apimachinery/pkg/types" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// deployRestAPIInGateway deploys the related API in CREATE and UPDATE events. +func deployRestAPIInGateway(apiState APIState) error { + var err error + if len(apiState.OldOrganizationID) != 0 { + xds.RemoveAPIFromOrgAPIMap(string((*apiState.APIDefinition).ObjectMeta.UID), apiState.OldOrganizationID) + } + if apiState.ProdHTTPRoute == nil { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Production) + } + if apiState.SandHTTPRoute == nil { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Sandbox) + } + if apiState.ProdHTTPRoute != nil { + _, err = GenerateAdapterInternalAPI(apiState, apiState.ProdHTTPRoute, constants.Production) + } + if err != nil { + return err + } + if apiState.SandHTTPRoute != nil { + _, err = GenerateAdapterInternalAPI(apiState, apiState.SandHTTPRoute, constants.Sandbox) + } + return err +} + +// undeployAPIInGateway undeploys the related API in CREATE and UPDATE events. +func undeployRestAPIInGateway(apiState APIState) error { + var err error + if apiState.ProdHTTPRoute != nil { + err = deleteAPIFromEnv(apiState.ProdHTTPRoute.HTTPRouteCombined, apiState) + } + if err != nil { + loggers.LoggerXds.ErrorC(logging.PrintError(logging.Error2630, logging.MAJOR, "Error undeploying prod httpRoute of API : %v in Organization %v from environments %v."+ + " Hence not checking on deleting the sand httpRoute of the API", string(apiState.APIDefinition.ObjectMeta.UID), apiState.APIDefinition.Spec.Organization, + getLabelsForAPI(apiState.ProdHTTPRoute.HTTPRouteCombined))) + return err + } + if apiState.SandHTTPRoute != nil { + err = deleteAPIFromEnv(apiState.SandHTTPRoute.HTTPRouteCombined, apiState) + } + return err +} + +// GenerateAdapterInternalAPI this will populate a AdapterInternalAPI representation for an HTTPRoute +func GenerateAdapterInternalAPI(apiState APIState, httpRoute *HTTPRouteState, envType string) (*model.AdapterInternalAPI, error) { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetIsDefaultVersion(apiState.APIDefinition.Spec.IsDefaultVersion) + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + adapterInternalAPI.SetAPIDefinitionFile(apiState.APIDefinitionFile) + adapterInternalAPI.SetAPIDefinitionEndpoint(apiState.APIDefinition.Spec.DefinitionPath) + adapterInternalAPI.SetSubscriptionValidation(apiState.SubscriptionValidation) + adapterInternalAPI.EnvType = envType + + environment := apiState.APIDefinition.Spec.Environment + if environment == "" { + conf := config.ReadConfigs() + environment = conf.Adapter.Environment + } + adapterInternalAPI.SetEnvironment(environment) + + resourceParams := model.ResourceParams{ + AuthSchemes: apiState.Authentications, + ResourceAuthSchemes: apiState.ResourceAuthentications, + BackendMapping: httpRoute.BackendMapping, + APIPolicies: apiState.APIPolicies, + ResourceAPIPolicies: apiState.ResourceAPIPolicies, + ResourceScopes: httpRoute.Scopes, + InterceptorServiceMapping: apiState.InterceptorServiceMapping, + BackendJWTMapping: apiState.BackendJWTMapping, + RateLimitPolicies: apiState.RateLimitPolicies, + ResourceRateLimitPolicies: apiState.ResourceRateLimitPolicies, + } + if err := adapterInternalAPI.SetInfoHTTPRouteCR(httpRoute.HTTPRouteCombined, resourceParams); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2631, logging.MAJOR, "Error setting HttpRoute CR info to adapterInternalAPI. %v", err)) + return nil, err + } + if err := adapterInternalAPI.Validate(); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2632, logging.MAJOR, "Error validating adapterInternalAPI intermediate representation. %v", err)) + return nil, err + } + vHosts := getVhostsForAPI(httpRoute.HTTPRouteCombined) + labels := getLabelsForAPI(httpRoute.HTTPRouteCombined) + listeners, relativeSectionNames := getListenersForAPI(httpRoute.HTTPRouteCombined, adapterInternalAPI.UUID) + // We dont have a use case where a perticular API's two different http routes refer to two different gateway. Hence get the first listener name for the list for processing. + if len(listeners) == 0 || len(relativeSectionNames) == 0 { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MINOR, "Failed to find a matching listener for http route: %v. ", + httpRoute.HTTPRouteCombined.Name)) + return nil, errors.New("failed to find matching listener name for the provided http route") + } + listenerName := listeners[0] + sectionName := relativeSectionNames[0] + if len(listeners) != 0 { + err := xds.UpdateAPICache(vHosts, labels, listenerName, sectionName, adapterInternalAPI) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MAJOR, "Error updating the API : %s:%s in vhosts: %s, API_UUID: %v. %v", + adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), vHosts, adapterInternalAPI.UUID, err)) + } + } + + return &adapterInternalAPI, nil +} + +// getVhostForAPI returns the vHosts related to an API. +func getVhostsForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string { + var vHosts []string + for _, hostName := range httpRoute.Spec.Hostnames { + vHosts = append(vHosts, string(hostName)) + } + fmt.Println("vhosts size: ", len(vHosts)) + return vHosts +} + +// getLabelsForAPI returns the labels related to an API. +func getLabelsForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string { + var labels []string + var err error + for _, parentRef := range httpRoute.Spec.ParentRefs { + err = xds.SanitizeGateway(string(parentRef.Name), false) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2653, logging.CRITICAL, "Gateway Label is invalid: %s", string(parentRef.Name))) + } else { + labels = append(labels, string(parentRef.Name)) + } + } + return labels +} + +// getListenersForAPI returns the listeners related to an API. +func getListenersForAPI(httpRoute *gwapiv1b1.HTTPRoute, apiUUID string) ([]string, []string) { + var listeners []string + var sectionNames []string + for _, parentRef := range httpRoute.Spec.ParentRefs { + namespace := httpRoute.GetNamespace() + if parentRef.Namespace != nil && *parentRef.Namespace != "" { + namespace = string(*parentRef.Namespace) + } + gateway, found := dataholder.GetGatewayMap()[types.NamespacedName{ + Namespace: namespace, + Name: string(parentRef.Name), + }.String()] + if found { + // find the matching listener + matchedListener, listenerFound := common.FindElement(gateway.Spec.Listeners, func(listener gwapiv1b1.Listener) bool { + if string(listener.Name) == string(*parentRef.SectionName) { + return true + } + return false + }) + if listenerFound { + sectionNames = append(sectionNames, string(matchedListener.Name)) + listeners = append(listeners, common.GetEnvoyListenerName(string(matchedListener.Protocol), uint32(matchedListener.Port))) + continue + } + } + loggers.LoggerAPKOperator.Errorf("Failed to find matching listeners for the httproute: %+v", httpRoute.Name) + } + return listeners, sectionNames +} + +func deleteAPIFromEnv(httpRoute *gwapiv1b1.HTTPRoute, apiState APIState) error { + labels := getLabelsForAPI(httpRoute) + org := apiState.APIDefinition.Spec.Organization + uuid := string(apiState.APIDefinition.ObjectMeta.UID) + return xds.DeleteAPICREvent(labels, uuid, org) +} diff --git a/adapter/internal/operator/synchronizer/synchronizer.go b/adapter/internal/operator/synchronizer/synchronizer.go index 75cd00033..e876c6804 100644 --- a/adapter/internal/operator/synchronizer/synchronizer.go +++ b/adapter/internal/operator/synchronizer/synchronizer.go @@ -21,24 +21,18 @@ import ( "bytes" "crypto/tls" "encoding/json" - "errors" "fmt" "net/http" "time" - "github.com/wso2/apk/adapter/internal/dataholder" - "github.com/wso2/apk/adapter/internal/discovery/xds" - "github.com/wso2/apk/adapter/internal/discovery/xds/common" "k8s.io/apimachinery/pkg/types" "github.com/wso2/apk/adapter/config" "github.com/wso2/apk/adapter/internal/loggers" - model "github.com/wso2/apk/adapter/internal/oasparser/model" "github.com/wso2/apk/adapter/internal/operator/constants" "github.com/wso2/apk/adapter/internal/operator/utils" "github.com/wso2/apk/adapter/pkg/logging" "github.com/wso2/apk/adapter/pkg/utils/tlsutils" - gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) // APIEvent holds the data structure used for passing API @@ -100,173 +94,25 @@ func HandleAPILifeCycleEvents(ch *chan APIEvent, successChannel *chan SuccessEve } } -// deleteAPIInGateway undeploys the related API in CREATE and UPDATE events. -func undeployAPIInGateway(apiState APIState) error { - var err error - if apiState.ProdHTTPRoute != nil { - err = deleteAPIFromEnv(apiState.ProdHTTPRoute.HTTPRouteCombined, apiState) - } - if err != nil { - loggers.LoggerXds.ErrorC(logging.PrintError(logging.Error2630, logging.MAJOR, "Error undeploying prod httpRoute of API : %v in Organization %v from environments %v."+ - " Hence not checking on deleting the sand httpRoute of the API", string(apiState.APIDefinition.ObjectMeta.UID), apiState.APIDefinition.Spec.Organization, - getLabelsForAPI(apiState.ProdHTTPRoute.HTTPRouteCombined))) - return err - } - if apiState.SandHTTPRoute != nil { - err = deleteAPIFromEnv(apiState.SandHTTPRoute.HTTPRouteCombined, apiState) - } - return err -} - -func deleteAPIFromEnv(httpRoute *gwapiv1b1.HTTPRoute, apiState APIState) error { - labels := getLabelsForAPI(httpRoute) - org := apiState.APIDefinition.Spec.Organization - uuid := string(apiState.APIDefinition.ObjectMeta.UID) - return xds.DeleteAPICREvent(labels, uuid, org) -} - // deployAPIInGateway deploys the related API in CREATE and UPDATE events. func deployAPIInGateway(apiState APIState) error { - var err error - if len(apiState.OldOrganizationID) != 0 { - xds.RemoveAPIFromOrgAPIMap(string((*apiState.APIDefinition).ObjectMeta.UID), apiState.OldOrganizationID) - } - if apiState.ProdHTTPRoute == nil { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Production) - } - if apiState.SandHTTPRoute == nil { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Sandbox) - } - if apiState.ProdHTTPRoute != nil { - _, err = GenerateAdapterInternalAPI(apiState, apiState.ProdHTTPRoute, constants.Production) - } - if err != nil { - return err - } - if apiState.SandHTTPRoute != nil { - _, err = GenerateAdapterInternalAPI(apiState, apiState.SandHTTPRoute, constants.Sandbox) - } - return err -} - -// GenerateAdapterInternalAPI this will populate a AdapterInternalAPI representation for an HTTPRoute -func GenerateAdapterInternalAPI(apiState APIState, httpRoute *HTTPRouteState, envType string) (*model.AdapterInternalAPI, error) { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetIsDefaultVersion(apiState.APIDefinition.Spec.IsDefaultVersion) - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - adapterInternalAPI.SetAPIDefinitionFile(apiState.APIDefinitionFile) - adapterInternalAPI.SetAPIDefinitionEndpoint(apiState.APIDefinition.Spec.DefinitionPath) - adapterInternalAPI.SetSubscriptionValidation(apiState.SubscriptionValidation) - adapterInternalAPI.EnvType = envType - - environment := apiState.APIDefinition.Spec.Environment - if environment == "" { - conf := config.ReadConfigs() - environment = conf.Adapter.Environment - } - adapterInternalAPI.SetEnvironment(environment) - - resourceParams := model.ResourceParams{ - AuthSchemes: apiState.Authentications, - ResourceAuthSchemes: apiState.ResourceAuthentications, - BackendMapping: httpRoute.BackendMapping, - APIPolicies: apiState.APIPolicies, - ResourceAPIPolicies: apiState.ResourceAPIPolicies, - ResourceScopes: httpRoute.Scopes, - InterceptorServiceMapping: apiState.InterceptorServiceMapping, - BackendJWTMapping: apiState.BackendJWTMapping, - RateLimitPolicies: apiState.RateLimitPolicies, - ResourceRateLimitPolicies: apiState.ResourceRateLimitPolicies, + if apiState.APIDefinition.Spec.APIType == "REST" { + return deployRestAPIInGateway(apiState) } - if err := adapterInternalAPI.SetInfoHTTPRouteCR(httpRoute.HTTPRouteCombined, resourceParams); err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2631, logging.MAJOR, "Error setting HttpRoute CR info to adapterInternalAPI. %v", err)) - return nil, err + if apiState.APIDefinition.Spec.APIType == "GraphQL" { + return deployGQLAPIInGateway(apiState) } - if err := adapterInternalAPI.Validate(); err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2632, logging.MAJOR, "Error validating adapterInternalAPI intermediate representation. %v", err)) - return nil, err - } - vHosts := getVhostsForAPI(httpRoute.HTTPRouteCombined) - labels := getLabelsForAPI(httpRoute.HTTPRouteCombined) - listeners, relativeSectionNames := getListenersForAPI(httpRoute.HTTPRouteCombined, adapterInternalAPI.UUID) - // We dont have a use case where a perticular API's two different http routes refer to two different gateway. Hence get the first listener name for the list for processing. - if len(listeners) == 0 || len(relativeSectionNames) == 0 { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MINOR, "Failed to find a matching listener for http route: %v. ", - httpRoute.HTTPRouteCombined.Name)) - return nil, errors.New("failed to find matching listener name for the provided http route") - } - listenerName := listeners[0] - sectionName := relativeSectionNames[0] - if len(listeners) != 0 { - err := xds.UpdateAPICache(vHosts, labels, listenerName, sectionName, adapterInternalAPI) - if err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MAJOR, "Error updating the API : %s:%s in vhosts: %s, API_UUID: %v. %v", - adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), vHosts, adapterInternalAPI.UUID, err)) - } - } - - return &adapterInternalAPI, nil + return nil } -// getVhostForAPI returns the vHosts related to an API. -func getVhostsForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string { - var vHosts []string - for _, hostName := range httpRoute.Spec.Hostnames { - vHosts = append(vHosts, string(hostName)) - } - fmt.Println("vhosts size: ", len(vHosts)) - return vHosts -} - -// getLabelsForAPI returns the labels related to an API. -func getLabelsForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string { - var labels []string - var err error - for _, parentRef := range httpRoute.Spec.ParentRefs { - err = xds.SanitizeGateway(string(parentRef.Name), false) - if err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2653, logging.CRITICAL, "Gateway Label is invalid: %s", string(parentRef.Name))) - } else { - labels = append(labels, string(parentRef.Name)) - } +func undeployAPIInGateway(apiState APIState) error { + if apiState.APIDefinition.Spec.APIType == "REST" { + return undeployRestAPIInGateway(apiState) } - return labels -} - -// getListenersForAPI returns the listeners related to an API. -func getListenersForAPI(httpRoute *gwapiv1b1.HTTPRoute, apiUUID string) ([]string, []string) { - var listeners []string - var sectionNames []string - for _, parentRef := range httpRoute.Spec.ParentRefs { - namespace := httpRoute.GetNamespace() - if parentRef.Namespace != nil && *parentRef.Namespace != "" { - namespace = string(*parentRef.Namespace) - } - gateway, found := dataholder.GetGatewayMap()[types.NamespacedName{ - Namespace: namespace, - Name: string(parentRef.Name), - }.String()] - if found { - // find the matching listener - matchedListener, listenerFound := common.FindElement(gateway.Spec.Listeners, func(listener gwapiv1b1.Listener) bool { - if string(listener.Name) == string(*parentRef.SectionName) { - return true - } - return false - }) - if listenerFound { - sectionNames = append(sectionNames, string(matchedListener.Name)) - listeners = append(listeners, common.GetEnvoyListenerName(string(matchedListener.Protocol), uint32(matchedListener.Port))) - continue - } - } - loggers.LoggerAPKOperator.Errorf("Failed to find matching listeners for the httproute: %+v", httpRoute.Name) + if apiState.APIDefinition.Spec.APIType == "GraphQL" { + return undeployGQLAPIInGateway(apiState) } - return listeners, sectionNames + return nil } // Runtime client connetion diff --git a/adapter/internal/operator/synchronizer/zz_generated.deepcopy.go b/adapter/internal/operator/synchronizer/zz_generated.deepcopy.go index 3de23393d..eef2fda32 100644 --- a/adapter/internal/operator/synchronizer/zz_generated.deepcopy.go +++ b/adapter/internal/operator/synchronizer/zz_generated.deepcopy.go @@ -46,6 +46,16 @@ func (in *APIState) DeepCopyInto(out *APIState) { *out = new(HTTPRouteState) (*in).DeepCopyInto(*out) } + if in.ProdGQLRoute != nil { + in, out := &in.ProdGQLRoute, &out.ProdGQLRoute + *out = new(GQLRouteState) + (*in).DeepCopyInto(*out) + } + if in.SandGQLRoute != nil { + in, out := &in.SandGQLRoute, &out.SandGQLRoute + *out = new(GQLRouteState) + (*in).DeepCopyInto(*out) + } if in.Authentications != nil { in, out := &in.Authentications, &out.Authentications *out = make(map[string]v1alpha1.Authentication, len(*in)) @@ -119,6 +129,63 @@ func (in *APIState) DeepCopy() *APIState { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GQLRouteState) DeepCopyInto(out *GQLRouteState) { + *out = *in + if in.GQLRouteCombined != nil { + in, out := &in.GQLRouteCombined, &out.GQLRouteCombined + *out = new(v1alpha2.GQLRoute) + (*in).DeepCopyInto(*out) + } + if in.GQLRoutePartitions != nil { + in, out := &in.GQLRoutePartitions, &out.GQLRoutePartitions + *out = make(map[string]*v1alpha2.GQLRoute, len(*in)) + for key, val := range *in { + var outVal *v1alpha2.GQLRoute + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(v1alpha2.GQLRoute) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + if in.BackendMapping != nil { + in, out := &in.BackendMapping, &out.BackendMapping + *out = make(map[string]*v1alpha1.ResolvedBackend, len(*in)) + for key, val := range *in { + var outVal *v1alpha1.ResolvedBackend + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(v1alpha1.ResolvedBackend) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make(map[string]v1alpha1.Scope, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GQLRouteState. +func (in *GQLRouteState) DeepCopy() *GQLRouteState { + if in == nil { + return nil + } + out := new(GQLRouteState) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayState) DeepCopyInto(out *GatewayState) { *out = *in diff --git a/adapter/internal/operator/utils/utils.go b/adapter/internal/operator/utils/utils.go index 9e96b9234..c90f88950 100644 --- a/adapter/internal/operator/utils/utils.go +++ b/adapter/internal/operator/utils/utils.go @@ -329,9 +329,7 @@ func GetResolvedBackend(ctx context.Context, client k8client.Client, resolvedTLSConfig := dpv1alpha1.ResolvedTLSConfig{} var backend dpv1alpha1.Backend if err := ResolveRef(ctx, client, api, backendNamespacedName, false, &backend); err != nil { - if !apierrors.IsNotFound(err) { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2646, logging.CRITICAL, "Error while getting backend: %v, error: %v", backendNamespacedName, err.Error())) - } + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2646, logging.CRITICAL, "Error while getting backend: %v, error: %v", backendNamespacedName, err.Error())) return nil } resolvedBackend.Backend = backend @@ -459,7 +457,8 @@ func getResolvedBackendSecurity(ctx context.Context, client k8client.Client, // ResolveCertificate reads the certificate from TLSConfig, first checks the certificateInline field, // if no value then load the certificate from secretRef using util function called getSecretValue -func ResolveCertificate(ctx context.Context, client k8client.Client, namespace string, certificateInline *string, configMapRef *dpv1alpha1.RefConfig, secretRef *dpv1alpha1.RefConfig) (string, error) { +func ResolveCertificate(ctx context.Context, client k8client.Client, namespace string, certificateInline *string, + configMapRef *dpv1alpha1.RefConfig, secretRef *dpv1alpha1.RefConfig) (string, error) { var certificate string var err error if certificateInline != nil && len(*certificateInline) > 0 { @@ -467,12 +466,14 @@ func ResolveCertificate(ctx context.Context, client k8client.Client, namespace s } else if secretRef != nil { if certificate, err = getSecretValue(ctx, client, namespace, secretRef.Name, secretRef.Key); err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2642, logging.CRITICAL, "Error while reading certificate from secretRef: %s", secretRef)) + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2642, logging.CRITICAL, + "Error while reading certificate from secretRef %s: %s", secretRef, err.Error())) } } else if configMapRef != nil { if certificate, err = getConfigMapValue(ctx, client, namespace, configMapRef.Name, configMapRef.Key); err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2643, logging.CRITICAL, "Error while reading certificate from configMapRef: %s", configMapRef)) + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2643, logging.CRITICAL, + "Error while reading certificate from configMapRef %s : %s", configMapRef, err.Error())) } } if err != nil { diff --git a/adapter/pkg/discovery/api/wso2/discovery/api/api.pb.go b/adapter/pkg/discovery/api/wso2/discovery/api/api.pb.go index 411c3af8b..c306038f6 100644 --- a/adapter/pkg/discovery/api/wso2/discovery/api/api.pb.go +++ b/adapter/pkg/discovery/api/wso2/discovery/api/api.pb.go @@ -60,12 +60,14 @@ type Api struct { MutualSSL string `protobuf:"bytes,15,opt,name=mutualSSL,proto3" json:"mutualSSL,omitempty"` ApplicationSecurity bool `protobuf:"varint,16,opt,name=applicationSecurity,proto3" json:"applicationSecurity,omitempty"` /// string graphQLSchema = 22; - // repeated GraphqlComplexity graphqlComplexityInfo = 23; + GraphqlComplexityInfo []*GraphqlComplexity `protobuf:"bytes,23,rep,name=graphqlComplexityInfo,proto3" json:"graphqlComplexityInfo,omitempty"` SystemAPI bool `protobuf:"varint,24,opt,name=systemAPI,proto3" json:"systemAPI,omitempty"` BackendJWTTokenInfo *BackendJWTTokenInfo `protobuf:"bytes,25,opt,name=backendJWTTokenInfo,proto3" json:"backendJWTTokenInfo,omitempty"` ApiDefinitionFile []byte `protobuf:"bytes,26,opt,name=apiDefinitionFile,proto3" json:"apiDefinitionFile,omitempty"` Environment string `protobuf:"bytes,27,opt,name=environment,proto3" json:"environment,omitempty"` SubscriptionValidation bool `protobuf:"varint,28,opt,name=subscriptionValidation,proto3" json:"subscriptionValidation,omitempty"` + Endpoints *EndpointCluster `protobuf:"bytes,29,opt,name=endpoints,proto3" json:"endpoints,omitempty"` + EndpointSecurity []*SecurityInfo `protobuf:"bytes,30,rep,name=endpointSecurity,proto3" json:"endpointSecurity,omitempty"` } func (x *Api) Reset() { @@ -212,6 +214,13 @@ func (x *Api) GetApplicationSecurity() bool { return false } +func (x *Api) GetGraphqlComplexityInfo() []*GraphqlComplexity { + if x != nil { + return x.GraphqlComplexityInfo + } + return nil +} + func (x *Api) GetSystemAPI() bool { if x != nil { return x.SystemAPI @@ -247,6 +256,20 @@ func (x *Api) GetSubscriptionValidation() bool { return false } +func (x *Api) GetEndpoints() *EndpointCluster { + if x != nil { + return x.Endpoints + } + return nil +} + +func (x *Api) GetEndpointSecurity() []*SecurityInfo { + if x != nil { + return x.EndpointSecurity + } + return nil +} + var File_wso2_discovery_api_api_proto protoreflect.FileDescriptor var file_wso2_discovery_api_api_proto_rawDesc = []byte{ @@ -260,68 +283,90 @@ var file_wso2_discovery_api_api_proto_rawDesc = []byte{ 0x69, 0x63, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2c, 0x77, 0x73, 0x6f, 0x32, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x57, 0x54, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, - 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd1, 0x06, 0x0a, 0x03, 0x41, 0x70, - 0x69, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x54, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x69, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x63, - 0x6f, 0x70, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x61, - 0x62, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x76, - 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x76, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x64, 0x69, - 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, - 0x1a, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x69, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x65, 0x72, 0x12, - 0x2c, 0x0a, 0x11, 0x61, 0x70, 0x69, 0x4c, 0x69, 0x66, 0x65, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x61, 0x70, 0x69, 0x4c, - 0x69, 0x66, 0x65, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x68, - 0x6f, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, - 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x4f, 0x0a, 0x12, 0x63, + 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x29, 0x77, 0x73, 0x6f, 0x32, 0x2f, + 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x26, 0x77, 0x73, 0x6f, 0x32, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, + 0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x77, 0x73, + 0x6f, 0x32, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x67, 0x72, 0x61, 0x70, 0x68, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbf, + 0x08, 0x0a, 0x03, 0x41, 0x70, 0x69, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x54, 0x79, 0x70, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x69, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x36, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x61, + 0x62, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0d, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x65, 0x6e, 0x76, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x65, 0x6e, 0x76, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x77, 0x73, + 0x6f, 0x32, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x69, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x61, 0x70, 0x69, 0x4c, 0x69, 0x66, 0x65, 0x43, + 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x11, 0x61, 0x70, 0x69, 0x4c, 0x69, 0x66, 0x65, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x4f, 0x0a, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, + 0x73, 0x6f, 0x32, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x64, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, - 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x53, 0x4c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x53, 0x4c, 0x12, 0x30, 0x0a, 0x13, 0x61, 0x70, + 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x53, 0x4c, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x53, 0x53, 0x4c, 0x12, + 0x30, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, - 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x0a, 0x09, - 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x50, 0x49, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x50, 0x49, 0x12, 0x59, 0x0a, 0x13, 0x62, 0x61, - 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x57, 0x54, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x66, - 0x6f, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x64, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x61, 0x63, - 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x57, 0x54, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x13, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x57, 0x54, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x11, 0x61, 0x70, 0x69, 0x44, 0x65, 0x66, 0x69, - 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x11, 0x61, 0x70, 0x69, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x46, - 0x69, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, - 0x6e, 0x74, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, - 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x16, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x1c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x70, 0x0a, - 0x23, 0x6f, 0x72, 0x67, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x61, 0x70, 0x6b, 0x2e, 0x65, 0x6e, - 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, - 0x2e, 0x61, 0x70, 0x69, 0x42, 0x08, 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, - 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x6e, 0x76, - 0x6f, 0x79, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x2d, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x77, 0x73, 0x6f, 0x32, 0x2f, 0x64, 0x69, - 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x3b, 0x61, 0x70, 0x69, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x12, 0x5b, 0x0a, 0x15, 0x67, 0x72, 0x61, 0x70, 0x68, 0x71, 0x6c, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x78, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x17, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x79, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x71, 0x6c, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x78, 0x69, 0x74, 0x79, 0x52, 0x15, 0x67, 0x72, 0x61, 0x70, 0x68, 0x71, 0x6c, + 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x50, 0x49, 0x18, 0x18, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x50, 0x49, 0x12, 0x59, 0x0a, 0x13, + 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x57, 0x54, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, + 0x6e, 0x66, 0x6f, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x77, 0x73, 0x6f, 0x32, + 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x42, + 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x57, 0x54, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x13, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x4a, 0x57, 0x54, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2c, 0x0a, 0x11, 0x61, 0x70, 0x69, 0x44, 0x65, + 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x1a, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x11, 0x61, 0x70, 0x69, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x6e, 0x76, 0x69, + 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x16, 0x73, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x41, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1d, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x73, 0x12, 0x4c, 0x0a, 0x10, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x65, + 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x1e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x77, + 0x73, 0x6f, 0x32, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x10, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, + 0x42, 0x70, 0x0a, 0x23, 0x6f, 0x72, 0x67, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x61, 0x70, 0x6b, + 0x2e, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x08, 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2d, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x77, 0x73, 0x6f, 0x32, + 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x3b, 0x61, + 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -341,17 +386,23 @@ var file_wso2_discovery_api_api_proto_goTypes = []interface{}{ (*Api)(nil), // 0: wso2.discovery.api.Api (*Resource)(nil), // 1: wso2.discovery.api.Resource (*Certificate)(nil), // 2: wso2.discovery.api.Certificate - (*BackendJWTTokenInfo)(nil), // 3: wso2.discovery.api.BackendJWTTokenInfo + (*GraphqlComplexity)(nil), // 3: wso2.discovery.api.GraphqlComplexity + (*BackendJWTTokenInfo)(nil), // 4: wso2.discovery.api.BackendJWTTokenInfo + (*EndpointCluster)(nil), // 5: wso2.discovery.api.EndpointCluster + (*SecurityInfo)(nil), // 6: wso2.discovery.api.SecurityInfo } var file_wso2_discovery_api_api_proto_depIdxs = []int32{ 1, // 0: wso2.discovery.api.Api.resources:type_name -> wso2.discovery.api.Resource 2, // 1: wso2.discovery.api.Api.clientCertificates:type_name -> wso2.discovery.api.Certificate - 3, // 2: wso2.discovery.api.Api.backendJWTTokenInfo:type_name -> wso2.discovery.api.BackendJWTTokenInfo - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 3, // 2: wso2.discovery.api.Api.graphqlComplexityInfo:type_name -> wso2.discovery.api.GraphqlComplexity + 4, // 3: wso2.discovery.api.Api.backendJWTTokenInfo:type_name -> wso2.discovery.api.BackendJWTTokenInfo + 5, // 4: wso2.discovery.api.Api.endpoints:type_name -> wso2.discovery.api.EndpointCluster + 6, // 5: wso2.discovery.api.Api.endpointSecurity:type_name -> wso2.discovery.api.SecurityInfo + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_wso2_discovery_api_api_proto_init() } @@ -362,6 +413,9 @@ func file_wso2_discovery_api_api_proto_init() { file_wso2_discovery_api_Resource_proto_init() file_wso2_discovery_api_Certificate_proto_init() file_wso2_discovery_api_BackendJWTTokenInfo_proto_init() + file_wso2_discovery_api_endpoint_cluster_proto_init() + file_wso2_discovery_api_security_info_proto_init() + file_wso2_discovery_api_graphql_proto_init() if !protoimpl.UnsafeEnabled { file_wso2_discovery_api_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Api); i { diff --git a/adapter/pkg/discovery/api/wso2/discovery/config/enforcer/analytics_publisher.pb.go b/adapter/pkg/discovery/api/wso2/discovery/config/enforcer/analytics_publisher.pb.go index 4da7aa15b..78dc2eccd 100644 --- a/adapter/pkg/discovery/api/wso2/discovery/config/enforcer/analytics_publisher.pb.go +++ b/adapter/pkg/discovery/api/wso2/discovery/config/enforcer/analytics_publisher.pb.go @@ -109,36 +109,34 @@ var file_wso2_discovery_config_enforcer_analytics_publisher_proto_rawDesc = []by 0x2f, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x74, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x1a, 0x2c, 0x77, 0x73, 0x6f, 0x32, - 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2f, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 0x01, 0x0a, 0x12, 0x41, 0x6e, 0x61, - 0x6c, 0x79, 0x74, 0x69, 0x63, 0x73, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x12, - 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x74, 0x0a, 0x10, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x48, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x65, 0x6e, 0x66, 0x6f, - 0x72, 0x63, 0x65, 0x72, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x74, 0x69, 0x63, 0x73, 0x50, 0x75, - 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x72, - 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, - 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x1a, 0x43, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x72, 0x6f, - 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x9c, 0x01, 0x0a, 0x2f, 0x6f, 0x72, 0x67, - 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x61, 0x70, 0x6b, 0x2e, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x72, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x42, 0x17, 0x41, 0x6e, - 0x61, 0x6c, 0x79, 0x74, 0x69, 0x63, 0x73, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x67, - 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2d, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, - 0x77, 0x73, 0x6f, 0x32, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x3b, 0x65, - 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x67, 0x2e, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x22, 0xfd, 0x01, 0x0a, 0x12, 0x41, + 0x6e, 0x61, 0x6c, 0x79, 0x74, 0x69, 0x63, 0x73, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, + 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x74, 0x0a, 0x10, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x48, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x65, 0x6e, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x74, 0x69, 0x63, 0x73, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0x43, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x9c, 0x01, 0x0a, 0x2f, 0x6f, + 0x72, 0x67, 0x2e, 0x77, 0x73, 0x6f, 0x32, 0x2e, 0x61, 0x70, 0x6b, 0x2e, 0x65, 0x6e, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x72, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x42, 0x17, + 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x74, 0x69, 0x63, 0x73, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, + 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2d, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2f, 0x77, 0x73, 0x6f, 0x32, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, + 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, + 0x3b, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -172,7 +170,6 @@ func file_wso2_discovery_config_enforcer_analytics_publisher_proto_init() { if File_wso2_discovery_config_enforcer_analytics_publisher_proto != nil { return } - file_wso2_discovery_config_enforcer_service_proto_init() if !protoimpl.UnsafeEnabled { file_wso2_discovery_config_enforcer_analytics_publisher_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AnalyticsPublisher); i { diff --git a/adapter/pkg/logging/logging_constant.go b/adapter/pkg/logging/logging_constant.go index 45e83a7ff..93dd429c6 100644 --- a/adapter/pkg/logging/logging_constant.go +++ b/adapter/pkg/logging/logging_constant.go @@ -161,6 +161,9 @@ const ( Error2662 = 2662 Error2663 = 2663 Error2664 = 2664 + Error2665 = 2665 + Error2666 = 2666 + Error2667 = 2667 ) // Error Log Pkg auth(3001-3099) Config Constants diff --git a/adapter/pkg/utils/stringutils/string_utils.go b/adapter/pkg/utils/stringutils/string_utils.go index dff5a735d..829f02bdd 100644 --- a/adapter/pkg/utils/stringutils/string_utils.go +++ b/adapter/pkg/utils/stringutils/string_utils.go @@ -18,6 +18,7 @@ package stringutils import ( + "fmt" "strings" ) @@ -57,3 +58,12 @@ func StringInSlice(a string, list []string) bool { } return false } + +// ToStringArray returns the vHosts related to an API. +func ToStringArray[T any](array []T) []string { + var vHosts []string + for _, stringVal := range array { + vHosts = append(vHosts, fmt.Sprintf("%v", stringVal)) + } + return vHosts +} diff --git a/common-controller/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml b/common-controller/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml index 55ab3158c..304529c66 100644 --- a/common-controller/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml +++ b/common-controller/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml @@ -246,6 +246,7 @@ spec: could be REST, GraphQL, Async enum: - REST + - GraphQL type: string apiVersion: description: APIVersion is the version number of the API. diff --git a/common-controller/internal/operator/controllers/dp/ratelimitpolicy_controller.go b/common-controller/internal/operator/controllers/dp/ratelimitpolicy_controller.go index 642630105..c2b97b682 100644 --- a/common-controller/internal/operator/controllers/dp/ratelimitpolicy_controller.go +++ b/common-controller/internal/operator/controllers/dp/ratelimitpolicy_controller.go @@ -281,9 +281,9 @@ func (ratelimitReconsiler *RateLimitPolicyReconciler) marshelRateLimit(ctx conte resolveRatelimit.UUID = string(api.ObjectMeta.UID) resolveRatelimit.Environment = environment - if len(api.Spec.Production) > 0 { - - resolveResourceList, err := ratelimitReconsiler.getResourceList(ctx, ratelimitKey, ratelimitPolicy, api.Spec.Production[0].HTTPRouteRefs) + if len(api.Spec.Production) > 0 && api.Spec.APIType == "REST" { + resolveResourceList, err := ratelimitReconsiler.getHTTPRouteResourceList(ctx, ratelimitKey, ratelimitPolicy, + api.Spec.Production[0].HTTPRouteRefs) if err != nil { return nil, err } @@ -293,9 +293,9 @@ func (ratelimitReconsiler *RateLimitPolicyReconciler) marshelRateLimit(ctx conte } } - if len(api.Spec.Sandbox) > 0 { - - resolveResourceList, err := ratelimitReconsiler.getResourceList(ctx, ratelimitKey, ratelimitPolicy, api.Spec.Sandbox[0].HTTPRouteRefs) + if len(api.Spec.Sandbox) > 0 && api.Spec.APIType == "REST" { + resolveResourceList, err := ratelimitReconsiler.getHTTPRouteResourceList(ctx, ratelimitKey, ratelimitPolicy, + api.Spec.Sandbox[0].HTTPRouteRefs) if err != nil { return nil, err } @@ -310,7 +310,7 @@ func (ratelimitReconsiler *RateLimitPolicyReconciler) marshelRateLimit(ctx conte return policyList, nil } -func (ratelimitReconsiler *RateLimitPolicyReconciler) getResourceList(ctx context.Context, ratelimitKey types.NamespacedName, +func (ratelimitReconsiler *RateLimitPolicyReconciler) getHTTPRouteResourceList(ctx context.Context, ratelimitKey types.NamespacedName, ratelimitPolicy dpv1alpha1.RateLimitPolicy, httpRefs []string) ([]dpv1alpha1.ResolveResource, error) { var resolveResourceList []dpv1alpha1.ResolveResource @@ -375,6 +375,7 @@ func getCustomRateLimitPolicy(customRateLimitPolicy *dpv1alpha1.RateLimitPolicy) } func addIndexes(ctx context.Context, mgr manager.Manager) error { + //todo(amali) if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.HTTPRoute{}, httprouteRateLimitIndex, func(rawObj k8client.Object) []string { httpRoute := rawObj.(*gwapiv1b1.HTTPRoute) diff --git a/common-go-libs/apis/dp/v1alpha2/api_types.go b/common-go-libs/apis/dp/v1alpha2/api_types.go index 9d21a2f04..741814b45 100644 --- a/common-go-libs/apis/dp/v1alpha2/api_types.go +++ b/common-go-libs/apis/dp/v1alpha2/api_types.go @@ -82,7 +82,7 @@ type APISpec struct { // APIType denotes the type of the API. // Possible values could be REST, GraphQL, Async // - // +kubebuilder:validation:Enum=REST + // +kubebuilder:validation:Enum=REST;GraphQL APIType string `json:"apiType"` // BasePath denotes the basepath of the API. diff --git a/common-go-libs/apis/dp/v1alpha2/api_webhook.go b/common-go-libs/apis/dp/v1alpha2/api_webhook.go index c42286a96..104f27b31 100644 --- a/common-go-libs/apis/dp/v1alpha2/api_webhook.go +++ b/common-go-libs/apis/dp/v1alpha2/api_webhook.go @@ -104,6 +104,10 @@ func (r *API) validateAPI() error { allErrs = append(allErrs, err) } + if r.Spec.APIType == "GraphQL" && r.Spec.DefinitionFileRef == "" { + allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("definitionFileRef"), "GraphQL API definitionFileRef is required")) + } + // Organization value should not be empty as it required when applying ratelimit policy if r.Spec.Organization == "" { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("organization"), "Organization can not be empty")) diff --git a/common-go-libs/apis/dp/v1alpha2/gqlroute_types.go b/common-go-libs/apis/dp/v1alpha2/gqlroute_types.go new file mode 100644 index 000000000..6afe79177 --- /dev/null +++ b/common-go-libs/apis/dp/v1alpha2/gqlroute_types.go @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// GQLRouteSpec defines the desired state of GQLRoute +type GQLRouteSpec struct { + v1beta1.CommonRouteSpec `json:",inline"` + + // Hostnames defines a set of hostname that should match against the HTTP Host + // header to select a GQLRoute used to process the request. + // +optional + // +kubebuilder:validation:MaxItems=16 + Hostnames []v1beta1.Hostname `json:"hostnames,omitempty"` + + // BackendRefs defines the backend(s) where matching requests should be + // sent. + BackendRefs []v1beta1.HTTPBackendRef `json:"backendRefs,omitempty"` + + // Rules are a list of GraphQL resources, filters and actions. + // + // +optional + // +kubebuilder:validation:MaxItems=16 + Rules []GQLRouteRules `json:"rules,omitempty"` +} + +// GQLRouteRules defines semantics for matching an GraphQL request based on +// conditions (matches), processing it (filters), and forwarding the request to +// an API object (backendRefs). +type GQLRouteRules struct { + + // Matches define conditions used for matching the rule against incoming + // graphQL requests. Each match is independent, i.e. this rule will be matched + // if **any** one of the matches is satisfied. + Matches []GQLRouteMatch `json:"matches,omitempty"` + + // Filters define the filters that are applied to requests that match + // this rule. + // + // +kubebuilder:validation:MaxItems=16 + Filters []GQLRouteFilter `json:"filters,omitempty"` +} + +// GQLRouteFilter defines the filter to be applied to a request. +type GQLRouteFilter struct { + // ExtensionRef is an optional, implementation-specific extension to the + // "filter" behavior. For example, resource "myroutefilter" in group + // "networking.example.net"). ExtensionRef MUST NOT be used for core and + // extended filters. + // + // Support: Implementation-specific + // + // +optional + ExtensionRef *v1beta1.LocalObjectReference `json:"extensionRef,omitempty"` +} + +// GQLRouteMatch defines the predicate used to match requests to a given +// action. +type GQLRouteMatch struct { + // Type specifies GQL typematcher. + // When specified, this route will be matched only if the request has the + // specified method. + // + // Support: Extended + // + // +optional + // +kubebuilder:validation:Default=QUERY + Type *GQLType `json:"type,omitempty"` + + // Path specifies a GQL request resource matcher. + Path *string `json:"path,omitempty"` +} + +// GQLType describes how to select a GQL request by matching the GQL Type. +// The value is expected in upper case. +// +// Note that values may be added to this enum, implementations +// must ensure that unknown values will not cause a crash. +// +// Unknown values here must result in the implementation setting the +// Accepted Condition for the Route to `status: False`, with a +// Reason of `UnsupportedValue`. +// +// +kubebuilder:validation:Enum=QUERY;MUTATION +type GQLType string + +// GQLRouteStatus defines the observed state of GQLRoute +type GQLRouteStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// GQLRoute is the Schema for the gqlroutes API +type GQLRoute struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GQLRouteSpec `json:"spec,omitempty"` + Status GQLRouteStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// GQLRouteList contains a list of GQLRoute +type GQLRouteList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GQLRoute `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GQLRoute{}, &GQLRouteList{}) +} diff --git a/common-go-libs/apis/dp/v1alpha2/zz_generated.deepcopy.go b/common-go-libs/apis/dp/v1alpha2/zz_generated.deepcopy.go index afe0b0ab3..4bd94c66e 100644 --- a/common-go-libs/apis/dp/v1alpha2/zz_generated.deepcopy.go +++ b/common-go-libs/apis/dp/v1alpha2/zz_generated.deepcopy.go @@ -25,6 +25,7 @@ package v1alpha2 import ( "k8s.io/apimachinery/pkg/runtime" apisv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/apis/v1beta1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -380,6 +381,189 @@ func (in *EnvConfig) DeepCopy() *EnvConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GQLRoute) DeepCopyInto(out *GQLRoute) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GQLRoute. +func (in *GQLRoute) DeepCopy() *GQLRoute { + if in == nil { + return nil + } + out := new(GQLRoute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GQLRoute) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GQLRouteFilter) DeepCopyInto(out *GQLRouteFilter) { + *out = *in + if in.ExtensionRef != nil { + in, out := &in.ExtensionRef, &out.ExtensionRef + *out = new(v1beta1.LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GQLRouteFilter. +func (in *GQLRouteFilter) DeepCopy() *GQLRouteFilter { + if in == nil { + return nil + } + out := new(GQLRouteFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GQLRouteList) DeepCopyInto(out *GQLRouteList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GQLRoute, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GQLRouteList. +func (in *GQLRouteList) DeepCopy() *GQLRouteList { + if in == nil { + return nil + } + out := new(GQLRouteList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GQLRouteList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GQLRouteMatch) DeepCopyInto(out *GQLRouteMatch) { + *out = *in + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(GQLType) + **out = **in + } + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GQLRouteMatch. +func (in *GQLRouteMatch) DeepCopy() *GQLRouteMatch { + if in == nil { + return nil + } + out := new(GQLRouteMatch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GQLRouteRules) DeepCopyInto(out *GQLRouteRules) { + *out = *in + if in.Matches != nil { + in, out := &in.Matches, &out.Matches + *out = make([]GQLRouteMatch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Filters != nil { + in, out := &in.Filters, &out.Filters + *out = make([]GQLRouteFilter, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GQLRouteRules. +func (in *GQLRouteRules) DeepCopy() *GQLRouteRules { + if in == nil { + return nil + } + out := new(GQLRouteRules) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GQLRouteSpec) DeepCopyInto(out *GQLRouteSpec) { + *out = *in + in.CommonRouteSpec.DeepCopyInto(&out.CommonRouteSpec) + if in.Hostnames != nil { + in, out := &in.Hostnames, &out.Hostnames + *out = make([]v1beta1.Hostname, len(*in)) + copy(*out, *in) + } + if in.BackendRefs != nil { + in, out := &in.BackendRefs, &out.BackendRefs + *out = make([]v1beta1.HTTPBackendRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]GQLRouteRules, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GQLRouteSpec. +func (in *GQLRouteSpec) DeepCopy() *GQLRouteSpec { + if in == nil { + return nil + } + out := new(GQLRouteSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GQLRouteStatus) DeepCopyInto(out *GQLRouteStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GQLRouteStatus. +func (in *GQLRouteStatus) DeepCopy() *GQLRouteStatus { + if in == nil { + return nil + } + out := new(GQLRouteStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InterceptorReference) DeepCopyInto(out *InterceptorReference) { *out = *in diff --git a/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml b/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml index 55ab3158c..304529c66 100644 --- a/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml +++ b/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml @@ -246,6 +246,7 @@ spec: could be REST, GraphQL, Async enum: - REST + - GraphQL type: string apiVersion: description: APIVersion is the version number of the API. diff --git a/common-go-libs/config/crd/bases/dp.wso2.com_gqlroutes.yaml b/common-go-libs/config/crd/bases/dp.wso2.com_gqlroutes.yaml new file mode 100644 index 000000000..5537761a1 --- /dev/null +++ b/common-go-libs/config/crd/bases/dp.wso2.com_gqlroutes.yaml @@ -0,0 +1,889 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: gqlroutes.dp.wso2.com +spec: + group: dp.wso2.com + names: + kind: GQLRoute + listKind: GQLRouteList + plural: gqlroutes + singular: gqlroute + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: GQLRoute is the Schema for the gqlroutes API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GQLRouteSpec defines the desired state of GQLRoute + properties: + backendRefs: + description: BackendRefs defines the backend(s) where matching requests + should be sent. + items: + description: HTTPBackendRef defines how a HTTPRoute should forward + an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed + if and only if the request is being forwarded to the backend + defined here. \n Support: Implementation-specific (For broader + support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that + must be completed during the request or response lifecycle. + HTTPRouteFilters are meant as an extension point to express + processing that may be done in Gateway implementations. + Some examples include request or response modification, + implementing authentication strategies, rate-limiting, and + traffic shaping. API guarantee/conformance is defined based + on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for + a filter that modifies request headers. \n Support: + Core" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter + that mirrors requests. Requests are sent to the specified + destination, but responses from that destination are + ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where + mirrored requests are sent. \n If the referent cannot + be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure + the \"ResolvedRefs\" condition on the Route status + is set to `status: False` and not configure this + backend in the underlying implementation. \n If + there is a cross-namespace reference to an *existing* + object that is not allowed by a ReferenceGrant, + the controller must ensure the \"ResolvedRefs\" + \ condition on the Route is set to `status: False`, + with the \"RefNotPermitted\" reason and not configure + this backend in the underlying implementation. \n + In either error case, the Message of the `ResolvedRefs` + Condition should be used to provide more detail + about the problem. \n Support: Extended for Kubernetes + Service \n Support: Implementation-specific for + any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". When + unspecified or empty string, core API group + is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to CNAME + DNS records that may live outside of the cluster + and as such are difficult to reason about in + terms of conformance. They also may not be safe + to forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a + type other than ExternalName) \n Support: Implementation-specific + (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the + backend. When unspecified, the local namespace + is inferred. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept the + reference. See the ReferenceGrant documentation + for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port + number to use for this resource. Port is required + when the referent is a Kubernetes Service. In + this case, the port number is the service port + number, not the target port. For other resources, + destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter + that responds to the request with an HTTP redirection. + \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used + in the value of the `Location` header in the response. + When empty, the hostname in the `Host` header of + the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify + the path of the incoming request. The modified path + is then used to construct the `Location` header. + When empty, the request path is used as-is. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not." + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value + of the `Location` header in the response. \n If + no port is specified, the redirect port MUST be + derived using the following rules: \n * If redirect + scheme is not-empty, the redirect port MUST be the + well-known port associated with the redirect scheme. + Specifically \"http\" to port 80 and \"https\" to + port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway + SHOULD be used. * If redirect scheme is empty, the + redirect port MUST be the Gateway Listener port. + \n Implementations SHOULD NOT add the port number + in the 'Location' header in the following cases: + \n * A Location header that will use HTTP (whether + that is determined via the Listener protocol or + the Scheme field) _and_ use port 80. * A Location + header that will use HTTPS (whether that is determined + via the Listener protocol or the Scheme field) _and_ + use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the + value of the `Location` header in the response. + When empty, the scheme of the request is used. \n + Scheme redirects can affect the port of the redirect, + for more information, refer to the documentation + for the port field of this filter. \n Note that + values may be added to this enum, implementations + must ensure that unknown values will not cause a + crash. \n Unknown values here must result in the + implementation setting the Accepted Condition for + the Route to `status: False`, with a Reason of `UnsupportedValue`. + \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to + be used in response. \n Note that values may be + added to this enum, implementations must ensure + that unknown values will not cause a crash. \n Unknown + values here must result in the implementation setting + the Accepted Condition for the Route to `status: + False`, with a Reason of `UnsupportedValue`. \n + Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n Support: + Extended" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. + As with other API fields, types are classified into + three conformance levels: \n - Core: Filter types and + their corresponding configuration defined by \"Support: + Core\" in this package, e.g. \"RequestHeaderModifier\". + All implementations must support core filters. \n - + Extended: Filter types and their corresponding configuration + defined by \"Support: Extended\" in this package, e.g. + \"RequestMirror\". Implementers are encouraged to support + extended filters. \n - Implementation-specific: Filters + that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior + across multiple implementations will be considered for + inclusion in extended or core conformance levels. Filter-specific + configuration for such filters is specified using the + ExtensionRef field. `Type` should be set to \"ExtensionRef\" + for custom filters. \n Implementers are encouraged to + define custom implementation types to extend the core + API with implementation-specific behavior. \n If a reference + to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have + been processed by that filter MUST receive a HTTP error + response. \n Note that values may be added to this enum, + implementations must ensure that unknown values will + not cause a crash. \n Unknown values here must result + in the implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter + that modifies a request during forwarding. \n Support: + Extended" + properties: + hostname: + description: "Hostname is the value to be used to + replace the Host header value during forwarding. + \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not." + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty string, + core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. + For example \"Service\". \n Defaults to \"Service\" when not + specified. \n ExternalName services can refer to CNAME DNS + records that may live outside of the cluster and as such are + difficult to reason about in terms of conformance. They also + may not be safe to forward to (see CVE-2021-25740 for more + information). Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a type other than + ExternalName) \n Support: Implementation-specific (Services + with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When + unspecified, the local namespace is inferred. \n Note that + when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace + to allow that namespace's owner to accept the reference. See + the ReferenceGrant documentation for details. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use + for this resource. Port is required when the referent is a + Kubernetes Service. In this case, the port number is the service + port number, not the target port. For other resources, destination + port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded + to the referenced backend. This is computed as weight/(sum + of all weights in this BackendRefs list). For non-zero values, + there may be some epsilon from the exact proportion defined + here depending on the precision an implementation supports. + Weight is not a percentage and the sum of weights does not + need to equal 100. \n If only one backend is specified and + it has a weight greater than 0, 100% of the traffic is forwarded + to that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight defaults + to 1. \n Support for this field varies based on the context + where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + type: array + hostnames: + description: Hostnames defines a set of hostname that should match + against the HTTP Host header to select a GQLRoute used to process + the request. + items: + description: "Hostname is the fully qualified domain name of a network + host. This matches the RFC 1123 definition of a hostname with + 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname + may be prefixed with a wildcard label (`*.`). The wildcard label + must appear by itself as the first label. \n Hostname can be \"precise\" + which is a domain name without the terminating dot of a network + host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain + name prefixed with a single wildcard label (e.g. `*.example.com`). + \n Note that as per RFC1035 and RFC1123, a *label* must consist + of lower case alphanumeric characters or '-', and must start and + end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) + that a Route wants to be attached to. Note that the referenced parent + resource needs to allow this for the attachment to be complete. + For Gateways, that means the Gateway needs to allow attachment from + Routes of this kind and namespace. \n The only kind of parent resource + with \"Core\" support is Gateway. This API may be extended in the + future to support additional kinds of parent resources such as one + of the route kinds. \n It is invalid to reference an identical parent + more than once. It is valid to reference multiple distinct sections + within the same parent resource, such as 2 Listeners within a Gateway. + \n It is possible to separately reference multiple distinct objects + that may be collapsed by an implementation. For example, some implementations + may choose to merge compatible Gateway Listeners together. If that + is the case, the list of routes attached to those resources should + also be merged. \n Note that for ParentRefs that cross namespace + boundaries, there are specific rules. Cross-namespace references + are only valid if they are explicitly allowed by something in the + namespace they are referring to. For example, Gateway has the AllowedRoutes + field, and ReferenceGrant provides a generic way to enable any other + kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). The only kind of parent resource with \"Core\" support + is Gateway. This API may be extended in the future to support + additional kinds of parent resources, such as HTTPRoute. \n The + API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: + Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core + (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document how/if + Port is interpreted. \n For the purpose of status, an attachment + is considered successful as long as the parent resource accepts + it partially. For example, Gateway listeners can restrict + which Routes can attach to them by Route kind, namespace, + or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this + Route, the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. \n Implementations MAY choose to support + attaching Routes to other resources. If that is the case, + they MUST clearly document how SectionName is interpreted. + \n When unspecified (empty string), this will reference the + entire resource. For the purpose of status, an attachment + is considered successful if at least one section in the parent + resource accepts it. For example, Gateway listeners can restrict + which Routes can attach to them by Route kind, namespace, + or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this + Route, the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of GraphQL resources, filters and actions. + items: + description: GQLRouteRules defines semantics for matching an GraphQL + request based on conditions (matches), processing it (filters), + and forwarding the request to an API object (backendRefs). + properties: + filters: + description: Filters define the filters that are applied to + requests that match this rule. + items: + description: GQLRouteFilter defines the filter to be applied + to a request. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + maxItems: 16 + type: array + matches: + description: Matches define conditions used for matching the + rule against incoming graphQL requests. Each match is independent, + i.e. this rule will be matched if **any** one of the matches + is satisfied. + items: + description: GQLRouteMatch defines the predicate used to match + requests to a given action. + properties: + path: + description: Path specifies a GQL request resource matcher. + type: string + type: + description: "Type specifies GQL typematcher. When specified, + this route will be matched only if the request has the + specified method. \n Support: Extended" + enum: + - QUERY + - MUTATION + type: string + type: object + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: GQLRouteStatus defines the observed state of GQLRoute + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/common-go-libs/go.mod b/common-go-libs/go.mod index 155cfcf5f..3cd5bb68c 100644 --- a/common-go-libs/go.mod +++ b/common-go-libs/go.mod @@ -2,6 +2,8 @@ module github.com/wso2/apk/common-go-libs go 1.19 +replace github.com/wso2/apk/adapter => ../adapter + require ( github.com/envoyproxy/go-control-plane v0.11.2-0.20230802074621-eea0b3bd0f81 github.com/onsi/ginkgo/v2 v2.9.5 diff --git a/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/APIConfig.java b/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/APIConfig.java index 31248f44e..63cd27902 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/APIConfig.java +++ b/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/APIConfig.java @@ -55,6 +55,17 @@ public class APIConfig { private byte[] apiDefinition; private String environment; private boolean subscriptionValidation; + private EndpointSecurity[] endpointSecurity; + private EndpointCluster endpoints; + + public EndpointCluster getEndpoints() { + return endpoints; + } + + public void getEndpointSecurity(EndpointSecurity[] endpointSecurity) { + this.endpointSecurity = endpointSecurity; + } + /** * getApiType returns the API type. This could be one of the following. * HTTP, WS, WEBHOOK diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java index bf609352b..58c9c3dd2 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java @@ -72,24 +72,24 @@ public void addApis(List apis) { // webSocketAPI.init(api); // String apiKey = getApiKey(webSocketAPI); // newApis.put(apiKey, webSocketAPI); -// } else if (APIConstants.ApiType.GRAPHQL.equals(api.getApiType())) { -// GraphQLAPI graphQLAPI = new GraphQLAPI(); -// graphQLAPI.init(api); -// String apiKey = getApiKey(graphQLAPI); -// newApis.put(apiKey, graphQLAPI); -// } else { - RestAPI enforcerApi = new RestAPI(); - enforcerApi.init(api); - String apiKey = getApiKey(enforcerApi); - newApis.put(apiKey, enforcerApi); -// } +// } else + if (APIConstants.ApiType.GRAPHQL.equals(api.getApiType())) { + GraphQLAPI graphQLAPI = new GraphQLAPI(); + graphQLAPI.init(api); + String apiKey = getApiKey(graphQLAPI); + newApis.put(apiKey, graphQLAPI); + } else { + RestAPI enforcerApi = new RestAPI(); + enforcerApi.init(api); + String apiKey = getApiKey(enforcerApi); + newApis.put(apiKey, enforcerApi); + } } if (logger.isDebugEnabled()) { logger.debug("Total APIs in new cache: {}", newApis.size()); } this.apis = newApis; - //todo(amali) check if cache is initialized even if the cache is disabled CacheProviderUtil.initializeCacheHolder(newApis); } @@ -109,10 +109,11 @@ public API getMatchedAPI(CheckRequest request) { return apis.get(apiKey); } + public byte[] getAPIDefinition(final String basePath, final String version, final String vHost) { String apiKey = getApiKey(vHost, basePath, version); API api = apis.get(apiKey); - if(api == null) { + if (api == null) { return null; } return api.getAPIConfig().getApiDefinition(); 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 96f4492d4..4fc6ace7c 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 @@ -1,304 +1,251 @@ -///* -// * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. -// * -// * WSO2 LLC. licenses this file to you under the Apache License, -// * Version 2.0 (the "License"); you may not use this file except -// * in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, -// * software distributed under the License is distributed on an -// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// * KIND, either express or implied. See the License for the -// * specific language governing permissions and limitations -// * under the License. -// */ -//package org.wso2.apk.enforcer.api; -// -//import graphql.schema.GraphQLSchema; -//import graphql.schema.idl.SchemaParser; -//import graphql.schema.idl.TypeDefinitionRegistry; -//import graphql.schema.idl.UnExecutableSchemaGenerator; -//import org.apache.commons.lang3.StringUtils; -//import org.apache.logging.log4j.LogManager; -//import org.apache.logging.log4j.Logger; -//import org.wso2.apk.enforcer.discovery.api.Api; -//import org.wso2.apk.enforcer.discovery.api.Operation; -//import org.wso2.apk.enforcer.discovery.api.Resource; -//import org.wso2.apk.enforcer.discovery.api.Scopes; -//import org.wso2.apk.enforcer.discovery.api.SecurityList; -//import org.wso2.apk.enforcer.discovery.api.SecurityScheme; -//import org.wso2.apk.enforcer.analytics.AnalyticsFilter; -//import org.wso2.apk.enforcer.commons.Filter; -//import org.wso2.apk.enforcer.commons.logging.ErrorDetails; -//import org.wso2.apk.enforcer.commons.logging.LoggingConstants; -//import org.wso2.apk.enforcer.commons.model.APIConfig; -//import org.wso2.apk.enforcer.commons.model.EndpointCluster; -//import org.wso2.apk.enforcer.commons.model.EndpointSecurity; -//import org.wso2.apk.enforcer.commons.model.GraphQLSchemaDTO; -//import org.wso2.apk.enforcer.commons.model.RequestContext; -//import org.wso2.apk.enforcer.commons.model.ResourceConfig; -//import org.wso2.apk.enforcer.commons.model.SecuritySchemaConfig; -//import org.wso2.apk.enforcer.config.ConfigHolder; -//import org.wso2.apk.enforcer.config.dto.FilterDTO; -//import org.wso2.apk.enforcer.constants.APIConstants; -//import org.wso2.apk.enforcer.constants.HttpConstants; -//import org.wso2.apk.enforcer.cors.CorsFilter; -//import org.wso2.apk.enforcer.graphql.GraphQLPayloadUtils; -//import org.wso2.apk.enforcer.graphql.GraphQLQueryAnalysisFilter; -//import org.wso2.apk.enforcer.security.AuthFilter; -//import org.wso2.apk.enforcer.throttle.ThrottleFilter; -//import org.wso2.apk.enforcer.util.FilterUtils; -// -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.Comparator; -//import java.util.HashMap; -//import java.util.List; -//import java.util.Map; -//import java.util.ServiceLoader; -// -///** -// * Specific implementation for a Rest API type APIs. -// */ -//public class GraphQLAPI implements API { -// private static final Logger logger = LogManager.getLogger(GraphQLAPI.class); -// private final List filters = new ArrayList<>(); -// private APIConfig apiConfig; -// -// @Override -// public List getFilters() { -// return filters; -// } -// -// @Override -// public String init(Api api) { -// String vhost = api.getVhost(); -// String basePath = api.getBasePath(); -// String name = api.getTitle(); -// String version = api.getVersion(); -// String apiType = api.getApiType(); -// Map endpoints = new HashMap<>(); -// Map securitySchemeDefinitions = new HashMap<>(); -// Map> securityScopesMap = new HashMap<>(); -// List resources = new ArrayList<>(); -// EndpointSecurity endpointSecurity = new EndpointSecurity(); -// -// EndpointCluster productionEndpoints = Utils.processEndpoints(api.getProductionEndpoints()); -// EndpointCluster sandboxEndpoints = Utils.processEndpoints(api.getSandboxEndpoints()); -// if (productionEndpoints != null) { -// endpoints.put(APIConstants.API_KEY_TYPE_PRODUCTION, productionEndpoints); -// } -// if (sandboxEndpoints != null) { -// endpoints.put(APIConstants.API_KEY_TYPE_SANDBOX, sandboxEndpoints); -// } -// -// for (SecurityScheme securityScheme : api.getSecuritySchemeList()) { -// String definitionName = securityScheme.getDefinitionName(); -// SecuritySchemaConfig securitySchemaConfig = new SecuritySchemaConfig(); -// securitySchemaConfig.setDefinitionName(definitionName); -// securitySchemaConfig.setType(securityScheme.getType()); -// securitySchemaConfig.setName(securityScheme.getName()); -// securitySchemaConfig.setIn(securityScheme.getIn()); -// securitySchemeDefinitions.put(definitionName, securitySchemaConfig); -// } -// -// for (SecurityList securityList : api.getSecurityList()) { -// for (Map.Entry entry : securityList.getScopeListMap().entrySet()) { -// securityScopesMap.put(entry.getKey(), new ArrayList<>()); -// if (entry.getValue() != null && entry.getValue().getScopesList().size() > 0) { -// List scopeList = new ArrayList<>(entry.getValue().getScopesList()); -// securityScopesMap.replace(entry.getKey(), scopeList); -// } -// // only supports security scheme OR combinations. Example - -// // Security: -// // - api_key: [] -// // oauth: [] <-- AND operation is not supported hence ignoring oauth here. -// break; -// } -// } -// -// if (api.getEndpointSecurity().hasProductionSecurityInfo()) { -// endpointSecurity.setProductionSecurityInfo( -// APIProcessUtils.convertProtoEndpointSecurity( -// api.getEndpointSecurity().getProductionSecurityInfo())); -// } -// if (api.getEndpointSecurity().hasSandBoxSecurityInfo()) { -// endpointSecurity.setSandBoxSecurityInfo( -// APIProcessUtils.convertProtoEndpointSecurity( -// api.getEndpointSecurity().getSandBoxSecurityInfo())); -// } -// -// for (Resource res : api.getResourcesList()) { -// for (Operation operation : res.getMethodsList()) { -// ResourceConfig resConfig = Utils.buildResource(operation, res.getPath(), securityScopesMap); -// resources.add(resConfig); -// } -// } -// SchemaParser schemaParser = new SchemaParser(); -// TypeDefinitionRegistry registry = schemaParser.parse(api.getGraphQLSchema()); -// GraphQLSchema schema = UnExecutableSchemaGenerator.makeUnExecutableSchema(registry); -// -// GraphQLSchemaDTO graphQLSchemaDTO = new GraphQLSchemaDTO(schema, registry, -// GraphQLPayloadUtils.parseComplexityDTO(api.getGraphqlComplexityInfoList())); -// String apiLifeCycleState = api.getApiLifeCycleState(); -// this.apiConfig = new APIConfig.Builder(name).uuid(api.getId()).vhost(vhost).basePath(basePath) -// .version(version).apiType(apiType).apiLifeCycleState(apiLifeCycleState) -// .apiSecurity(securityScopesMap).tier(api.getTier()).endpointSecurity(endpointSecurity) -// .authHeader(api.getAuthorizationHeader()).disableSecurity(api.getDisableSecurity()) -// .organizationId(api.getOrganizationId()).endpoints(endpoints).resources(resources) -// .securitySchemeDefinitions(securitySchemeDefinitions).graphQLSchemaDTO(graphQLSchemaDTO).build(); -// initFilters(); -// return basePath; -// } -// -// @Override -// public ResponseObject process(RequestContext requestContext) { -// ResponseObject responseObject = new ResponseObject(requestContext.getRequestID()); -// responseObject.setRequestPath(requestContext.getRequestPath()); -// boolean analyticsEnabled = ConfigHolder.getInstance().getConfig().getAnalyticsConfig().isEnabled(); -// -// populateRemoveAndProtectedHeaders(requestContext); -// boolean isExistsMatchedOperations = requestContext.getMatchedResourcePaths() != null && -// requestContext.getMatchedResourcePaths().size() > 0; -// // This flag is used to apply CORS filter -// boolean isOptionCall = requestContext.getRequestMethod().contains(HttpConstants.OPTIONS); -// -// // handle other not allowed && non option request && not yet handled error scenarios. -// if ((!isOptionCall && !isExistsMatchedOperations) && !requestContext.getProperties() -// .containsKey(APIConstants.MessageFormat.ERROR_CODE)) { -// requestContext.getProperties() -// .put(APIConstants.MessageFormat.STATUS_CODE, APIConstants.StatusCodes.NOTFOUND.getCode()); -// requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_CODE, -// APIConstants.StatusCodes.NOTFOUND.getValue()); -// requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_MESSAGE, -// APIConstants.NOT_FOUND_MESSAGE); -// requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_DESCRIPTION, -// APIConstants.NOT_FOUND_DESCRIPTION); -// } -// -// if ((isExistsMatchedOperations || isOptionCall) && executeFilterChain(requestContext)) { -// responseObject.setRemoveHeaderMap(requestContext.getRemoveHeaders()); -// responseObject.setQueryParamsToRemove(requestContext.getQueryParamsToRemove()); -// responseObject.setRemoveAllQueryParams(requestContext.isRemoveAllQueryParams()); -// responseObject.setQueryParamsToAdd(requestContext.getQueryParamsToAdd()); -// responseObject.setQueryParamMap(requestContext.getQueryParameters()); -// responseObject.setStatusCode(APIConstants.StatusCodes.OK.getCode()); -// if (requestContext.getAddHeaders() != null && requestContext.getAddHeaders().size() > 0) { -// responseObject.setHeaderMap(requestContext.getAddHeaders()); -// } -// if (analyticsEnabled) { -// AnalyticsFilter.getInstance().handleSuccessRequest(requestContext); -// } -// // set metadata for interceptors -// responseObject.setMetaDataMap(requestContext.getMetadataMap()); -// } else { -// // If enforcer stops with a false, it will be passed directly to the client. -// responseObject.setDirectResponse(true); -// responseObject.setStatusCode(Integer.parseInt( -// requestContext.getProperties().get(APIConstants.MessageFormat.STATUS_CODE).toString())); -// if (requestContext.getProperties().containsKey(APIConstants.MessageFormat.ERROR_CODE)) { -// responseObject.setErrorCode( -// requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_CODE).toString()); -// } -// if (requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_MESSAGE) != null) { -// responseObject.setErrorMessage(requestContext.getProperties() -// .get(APIConstants.MessageFormat.ERROR_MESSAGE).toString()); -// } -// if (requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_DESCRIPTION) != null) { -// responseObject.setErrorDescription(requestContext.getProperties() -// .get(APIConstants.MessageFormat.ERROR_DESCRIPTION).toString()); -// } -// if (requestContext.getAddHeaders() != null && requestContext.getAddHeaders().size() > 0) { -// responseObject.setHeaderMap(requestContext.getAddHeaders()); -// } -// if (analyticsEnabled && !FilterUtils.isSkippedAnalyticsFaultEvent(responseObject.getErrorCode())) { -// AnalyticsFilter.getInstance().handleFailureRequest(requestContext); -// responseObject.setMetaDataMap(new HashMap<>(0)); -// } -// } -// return responseObject; -// } -// -// @Override -// public APIConfig getAPIConfig() { -// return this.apiConfig; -// } -// -// private void initFilters() { -// AuthFilter authFilter = new AuthFilter(); -// authFilter.init(apiConfig, null); -// this.filters.add(authFilter); -// -// GraphQLQueryAnalysisFilter queryAnalysisFilter = new GraphQLQueryAnalysisFilter(); -// queryAnalysisFilter.init(apiConfig, null); -// this.filters.add(queryAnalysisFilter); -// -// // enable throttle filter -// ThrottleFilter throttleFilter = new ThrottleFilter(); -// throttleFilter.init(apiConfig, null); -// this.filters.add(throttleFilter); -// -// loadCustomFilters(apiConfig); -// -// // CORS filter is added as the first filter, and it is not customizable. -// CorsFilter corsFilter = new CorsFilter(); -// this.filters.add(0, corsFilter); -// } -// -// private void populateRemoveAndProtectedHeaders(RequestContext requestContext) { -// Map securitySchemeDefinitions = -// requestContext.getMatchedAPI().getSecuritySchemeDefinitions(); -// // API key headers are considered to be protected headers, such that the header would not be sent -// // to backend and traffic manager. -// // This would prevent leaking credentials, even if user is invoking unsecured resource with some -// // credentials. -// for (Map.Entry entry : securitySchemeDefinitions.entrySet()) { -// SecuritySchemaConfig schema = entry.getValue(); -// if (APIConstants.SWAGGER_API_KEY_AUTH_TYPE_NAME.equalsIgnoreCase(schema.getType())) { -// if (APIConstants.SWAGGER_API_KEY_IN_HEADER.equals(schema.getIn())) { -// String header = StringUtils.lowerCase(schema.getName()); -// requestContext.getProtectedHeaders().add(header); -// requestContext.getRemoveHeaders().add(header); -// continue; -// } -// if (APIConstants.SWAGGER_API_KEY_IN_QUERY.equals(schema.getIn())) { -// requestContext.getQueryParamsToRemove().add(schema.getName()); -// } -// } -// } -// -// Utils.removeCommonAuthHeaders(requestContext); -// } -// -// private void loadCustomFilters(APIConfig apiConfig) { -// FilterDTO[] customFilters = ConfigHolder.getInstance().getConfig().getCustomFilters(); -// // Needs to sort the filter in ascending order to position the filter in the given position. -// Arrays.sort(customFilters, Comparator.comparing(FilterDTO::getPosition)); -// Map filterImplMap = new HashMap<>(customFilters.length); -// ServiceLoader loader = ServiceLoader.load(Filter.class); -// for (Filter filter : loader) { -// filterImplMap.put(filter.getClass().getName(), filter); -// } -// -// for (FilterDTO filterDTO : customFilters) { -// if (filterImplMap.containsKey(filterDTO.getClassName())) { -// if (filterDTO.getPosition() <= 0 || filterDTO.getPosition() - 1 > filters.size()) { -// logger.error("Position provided for the filter is invalid. {} : {} (Filters list size is {})", -// filterDTO.getClassName(), filterDTO.getPosition(), filters.size(), -// ErrorDetails.errorLog(LoggingConstants.Severity.MAJOR, 5203)); -// continue; -// } -// Filter filter = filterImplMap.get(filterDTO.getClassName()); -// filter.init(apiConfig, filterDTO.getConfigProperties()); -// // Since the position starts from 1 -// this.filters.add(filterDTO.getPosition() - 1, filter); -// } else { -// logger.error("No Filter Implementation is found in the classPath under the provided name : {}", -// filterDTO.getClassName(), ErrorDetails.errorLog(LoggingConstants.Severity.MAJOR, 5204)); -// } -// } -// } -//} +/* + * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wso2.apk.enforcer.api; + +import graphql.schema.GraphQLSchema; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import graphql.schema.idl.UnExecutableSchemaGenerator; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.wso2.apk.enforcer.commons.dto.ClaimValueDTO; +import org.wso2.apk.enforcer.commons.dto.JWTConfigurationDto; +import org.wso2.apk.enforcer.config.EnforcerConfig; +import org.wso2.apk.enforcer.discovery.api.Api; +import org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo; +import org.wso2.apk.enforcer.discovery.api.Certificate; +import org.wso2.apk.enforcer.discovery.api.Claim; +import org.wso2.apk.enforcer.discovery.api.Operation; +import org.wso2.apk.enforcer.discovery.api.Resource; +import org.wso2.apk.enforcer.analytics.AnalyticsFilter; +import org.wso2.apk.enforcer.commons.Filter; +import org.wso2.apk.enforcer.commons.model.APIConfig; +import org.wso2.apk.enforcer.commons.model.EndpointCluster; +import org.wso2.apk.enforcer.commons.model.EndpointSecurity; +import org.wso2.apk.enforcer.commons.model.GraphQLSchemaDTO; +import org.wso2.apk.enforcer.commons.model.RequestContext; +import org.wso2.apk.enforcer.commons.model.ResourceConfig; +import org.wso2.apk.enforcer.config.ConfigHolder; +import org.wso2.apk.enforcer.constants.APIConstants; +import org.wso2.apk.enforcer.constants.HttpConstants; +import org.wso2.apk.enforcer.cors.CorsFilter; +import org.wso2.apk.enforcer.graphql.GraphQLPayloadUtils; +import org.wso2.apk.enforcer.graphql.GraphQLQueryAnalysisFilter; +import org.wso2.apk.enforcer.security.AuthFilter; +import org.wso2.apk.enforcer.security.mtls.MtlsUtils; +import org.wso2.apk.enforcer.server.swagger.APIDefinitionUtils; +import org.wso2.apk.enforcer.util.EndpointUtils; +import org.wso2.apk.enforcer.util.FilterUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Specific implementation for a Rest API type APIs. + */ +public class GraphQLAPI implements API { + private static final Logger logger = LogManager.getLogger(GraphQLAPI.class); + private final List filters = new ArrayList<>(); + private APIConfig apiConfig; + + @Override + public List getFilters() { + return filters; + } + + @Override + public String init(Api api) { + String vhost = api.getVhost(); + String basePath = api.getBasePath(); + String name = api.getTitle(); + String version = api.getVersion(); + String apiType = api.getApiType(); + List resources = new ArrayList<>(); + Map mtlsCertificateTiers = new HashMap<>(); + String mutualSSL = api.getMutualSSL(); + boolean applicationSecurity = api.getApplicationSecurity(); + + EndpointCluster endpoints = Utils.processEndpoints(api.getEndpoints()); + EndpointSecurity[] endpointSecurity = APIProcessUtils.convertProtoEndpointSecurity( + api.getEndpointSecurityList()); + + for (Resource res : api.getResourcesList()) { + for (Operation operation : res.getMethodsList()) { + ResourceConfig resConfig = Utils.buildResource(operation, res.getPath(), endpointSecurity); + resConfig.setEndpoints(endpoints); + resConfig.setPolicyConfig(Utils.genPolicyConfig(operation.getPolicies())); + resources.add(resConfig); + } + } + + KeyStore trustStore; + try { + trustStore = MtlsUtils.createTrustStore(api.getClientCertificatesList()); + } catch (KeyStoreException e) { + throw new SecurityException(e); + } + + for (Certificate certificate : api.getClientCertificatesList()) { + mtlsCertificateTiers.put(certificate.getAlias(), certificate.getTier()); + } + + BackendJWTTokenInfo backendJWTTokenInfo = api.getBackendJWTTokenInfo(); + JWTConfigurationDto jwtConfigurationDto = new JWTConfigurationDto(); + + // If backendJWTTokeInfo is available + if (api.hasBackendJWTTokenInfo()) { + Map claims = backendJWTTokenInfo.getCustomClaimsMap(); + Map claimsMap = new HashMap<>(); + for (Map.Entry claimEntry : claims.entrySet()) { + Claim claim = claimEntry.getValue(); + ClaimValueDTO claimVal = new ClaimValueDTO(claim.getValue(), claim.getType()); + claimsMap.put(claimEntry.getKey(), claimVal); + } + EnforcerConfig enforcerConfig = ConfigHolder.getInstance().getConfig(); + jwtConfigurationDto.populateConfigValues(backendJWTTokenInfo.getEnabled(), + backendJWTTokenInfo.getHeader(), backendJWTTokenInfo.getSigningAlgorithm(), + backendJWTTokenInfo.getEncoding(), enforcerConfig.getJwtConfigurationDto().getPublicCert(), + enforcerConfig.getJwtConfigurationDto().getPrivateKey(), backendJWTTokenInfo.getTokenTTL(), + claimsMap, enforcerConfig.getJwtConfigurationDto().useKid(), + enforcerConfig.getJwtConfigurationDto().getKidValue()); + } + + SchemaParser schemaParser = new SchemaParser(); + + byte[] apiDefinition = api.getApiDefinitionFile().toByteArray(); + TypeDefinitionRegistry registry; + try { + String scheme = APIDefinitionUtils.ReadGzip(apiDefinition); + registry = schemaParser.parse(scheme); + } catch (IOException e) { + logger.error("Error while parsing the GQL schema definition of the API: " + name, e); + throw new RuntimeException(e); + } + GraphQLSchema schema = UnExecutableSchemaGenerator.makeUnExecutableSchema(registry); + + GraphQLSchemaDTO graphQLSchemaDTO = new GraphQLSchemaDTO(schema, registry, + GraphQLPayloadUtils.parseComplexityDTO(api.getGraphqlComplexityInfoList())); + String apiLifeCycleState = api.getApiLifeCycleState(); + this.apiConfig = new APIConfig.Builder(name).uuid(api.getId()).vhost(vhost).basePath(basePath).version(version) + .resources(resources).apiType(apiType).apiLifeCycleState(apiLifeCycleState).tier(api.getTier()) + .envType(api.getEnvType()).disableAuthentication(api.getDisableAuthentications()) + .disableScopes(api.getDisableScopes()).trustStore(trustStore).organizationId(api.getOrganizationId()) + .mtlsCertificateTiers(mtlsCertificateTiers).mutualSSL(mutualSSL) + .applicationSecurity(applicationSecurity).jwtConfigurationDto(jwtConfigurationDto) + .apiDefinition(apiDefinition).environment(api.getEnvironment()) + .subscriptionValidation(api.getSubscriptionValidation()).graphQLSchemaDTO(graphQLSchemaDTO).build(); + initFilters(); + return basePath; + } + + @Override + public ResponseObject process(RequestContext requestContext) { + + ResponseObject responseObject = new ResponseObject(requestContext.getRequestID()); + responseObject.setRequestPath(requestContext.getRequestPath()); + boolean analyticsEnabled = ConfigHolder.getInstance().getConfig().getAnalyticsConfig().isEnabled(); + + Utils.handleCommonHeaders(requestContext); + boolean isExistsMatchedOperations = requestContext.getMatchedResourcePaths() != null && + requestContext.getMatchedResourcePaths().size() > 0; + // This flag is used to apply CORS filter + boolean isOptionCall = requestContext.getRequestMethod().contains(HttpConstants.OPTIONS); + + // handle other not allowed && non option request && not yet handled error scenarios. + if ((!isOptionCall && !isExistsMatchedOperations) && !requestContext.getProperties() + .containsKey(APIConstants.MessageFormat.ERROR_CODE)) { + requestContext.getProperties() + .put(APIConstants.MessageFormat.STATUS_CODE, APIConstants.StatusCodes.NOTFOUND.getCode()); + requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_CODE, + APIConstants.StatusCodes.NOTFOUND.getValue()); + requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_MESSAGE, + APIConstants.NOT_FOUND_MESSAGE); + requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_DESCRIPTION, + APIConstants.NOT_FOUND_DESCRIPTION); + } + + if ((isExistsMatchedOperations || isOptionCall) && executeFilterChain(requestContext)) { + EndpointUtils.updateClusterHeaderAndCheckEnv(requestContext); + responseObject.setOrganizationId(requestContext.getMatchedAPI().getOrganizationId()); + responseObject.setRemoveHeaderMap(requestContext.getRemoveHeaders()); + responseObject.setQueryParamsToRemove(requestContext.getQueryParamsToRemove()); + responseObject.setRemoveAllQueryParams(requestContext.isRemoveAllQueryParams()); + responseObject.setQueryParamsToAdd(requestContext.getQueryParamsToAdd()); + responseObject.setQueryParamMap(requestContext.getQueryParameters()); + responseObject.setStatusCode(APIConstants.StatusCodes.OK.getCode()); + if (requestContext.getAddHeaders() != null && requestContext.getAddHeaders().size() > 0) { + responseObject.setHeaderMap(requestContext.getAddHeaders()); + } + if (analyticsEnabled) { + AnalyticsFilter.getInstance().handleSuccessRequest(requestContext); + } + // set metadata for interceptors + responseObject.setMetaDataMap(requestContext.getMetadataMap()); + } else { + // If enforcer stops with a false, it will be passed directly to the client. + responseObject.setDirectResponse(true); + responseObject.setStatusCode(Integer.parseInt( + requestContext.getProperties().get(APIConstants.MessageFormat.STATUS_CODE).toString())); + if (requestContext.getProperties().containsKey(APIConstants.MessageFormat.ERROR_CODE)) { + responseObject.setErrorCode( + requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_CODE).toString()); + } + if (requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_MESSAGE) != null) { + responseObject.setErrorMessage(requestContext.getProperties() + .get(APIConstants.MessageFormat.ERROR_MESSAGE).toString()); + } + if (requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_DESCRIPTION) != null) { + responseObject.setErrorDescription(requestContext.getProperties() + .get(APIConstants.MessageFormat.ERROR_DESCRIPTION).toString()); + } + if (requestContext.getAddHeaders() != null && requestContext.getAddHeaders().size() > 0) { + responseObject.setHeaderMap(requestContext.getAddHeaders()); + } + if (analyticsEnabled && !FilterUtils.isSkippedAnalyticsFaultEvent(responseObject.getErrorCode())) { + AnalyticsFilter.getInstance().handleFailureRequest(requestContext); + responseObject.setMetaDataMap(new HashMap<>(0)); + } + } + + return responseObject; + } + + @Override + public APIConfig getAPIConfig() { + return this.apiConfig; + } + + private void initFilters() { + AuthFilter authFilter = new AuthFilter(); + authFilter.init(apiConfig, null); + this.filters.add(authFilter); + + GraphQLQueryAnalysisFilter queryAnalysisFilter = new GraphQLQueryAnalysisFilter(); + queryAnalysisFilter.init(apiConfig, null); + this.filters.add(queryAnalysisFilter); + + // CORS filter is added as the first filter, and it is not customizable. + CorsFilter corsFilter = new CorsFilter(); + this.filters.add(0, corsFilter); + } +} diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/RestAPI.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/RestAPI.java index c220107b4..ab6d9d578 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/RestAPI.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/RestAPI.java @@ -226,7 +226,6 @@ private void initFilters() { this.filters.add(authFilter); if (!apiConfig.isSystemAPI()) { - loadCustomFilters(apiConfig); MediationPolicyFilter mediationPolicyFilter = new MediationPolicyFilter(); this.filters.add(mediationPolicyFilter); } @@ -235,34 +234,4 @@ private void initFilters() { CorsFilter corsFilter = new CorsFilter(); this.filters.add(0, corsFilter); } - - private void loadCustomFilters(APIConfig apiConfig) { - - FilterDTO[] customFilters = ConfigHolder.getInstance().getConfig().getCustomFilters(); - // Needs to sort the filter in ascending order to position the filter in the given position. - Arrays.sort(customFilters, Comparator.comparing(FilterDTO::getPosition)); - Map filterImplMap = new HashMap<>(customFilters.length); - ServiceLoader loader = ServiceLoader.load(Filter.class); - for (Filter filter : loader) { - filterImplMap.put(filter.getClass().getName(), filter); - } - - for (FilterDTO filterDTO : customFilters) { - if (filterImplMap.containsKey(filterDTO.getClassName())) { - if (filterDTO.getPosition() <= 0 || filterDTO.getPosition() - 1 > filters.size()) { - logger.error("Position provided for the filter is invalid. " - + filterDTO.getClassName() + " : " + filterDTO.getPosition() + "(Filters list size is " - + filters.size() + ")"); - continue; - } - Filter filter = filterImplMap.get(filterDTO.getClassName()); - filter.init(apiConfig, filterDTO.getConfigProperties()); - // Since the position starts from 1 - this.filters.add(filterDTO.getPosition() - 1, filter); - } else { - logger.error("No Filter Implementation is found in the classPath under the provided name : " - + filterDTO.getClassName()); - } - } - } } diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/constants/APIConstants.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/constants/APIConstants.java index 978bfb18b..55678ff8b 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/constants/APIConstants.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/constants/APIConstants.java @@ -263,7 +263,7 @@ public int getCode() { public static class ApiType { public static final String WEB_SOCKET = "WS"; - public static final String GRAPHQL = "GRAPHQL"; + public static final String GRAPHQL = "GraphQL"; } /** diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/Api.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/Api.java index 13e45af13..451fd9277 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/Api.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/Api.java @@ -33,8 +33,10 @@ private Api() { organizationId_ = ""; clientCertificates_ = java.util.Collections.emptyList(); mutualSSL_ = ""; + graphqlComplexityInfo_ = java.util.Collections.emptyList(); apiDefinitionFile_ = com.google.protobuf.ByteString.EMPTY; environment_ = ""; + endpointSecurity_ = java.util.Collections.emptyList(); } @java.lang.Override @@ -167,6 +169,15 @@ private Api( applicationSecurity_ = input.readBool(); break; } + case 186: { + if (!((mutable_bitField0_ & 0x00000004) != 0)) { + graphqlComplexityInfo_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000004; + } + graphqlComplexityInfo_.add( + input.readMessage(org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.parser(), extensionRegistry)); + break; + } case 192: { systemAPI_ = input.readBool(); @@ -201,6 +212,28 @@ private Api( subscriptionValidation_ = input.readBool(); break; } + case 234: { + org.wso2.apk.enforcer.discovery.api.EndpointCluster.Builder subBuilder = null; + if (endpoints_ != null) { + subBuilder = endpoints_.toBuilder(); + } + endpoints_ = input.readMessage(org.wso2.apk.enforcer.discovery.api.EndpointCluster.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(endpoints_); + endpoints_ = subBuilder.buildPartial(); + } + + break; + } + case 242: { + if (!((mutable_bitField0_ & 0x00000008) != 0)) { + endpointSecurity_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000008; + } + endpointSecurity_.add( + input.readMessage(org.wso2.apk.enforcer.discovery.api.SecurityInfo.parser(), extensionRegistry)); + break; + } default: { if (!parseUnknownField( input, unknownFields, extensionRegistry, tag)) { @@ -222,6 +255,12 @@ private Api( if (((mutable_bitField0_ & 0x00000002) != 0)) { clientCertificates_ = java.util.Collections.unmodifiableList(clientCertificates_); } + if (((mutable_bitField0_ & 0x00000004) != 0)) { + graphqlComplexityInfo_ = java.util.Collections.unmodifiableList(graphqlComplexityInfo_); + } + if (((mutable_bitField0_ & 0x00000008) != 0)) { + endpointSecurity_ = java.util.Collections.unmodifiableList(endpointSecurity_); + } this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); } @@ -790,14 +829,69 @@ public boolean getApplicationSecurity() { return applicationSecurity_; } - public static final int SYSTEMAPI_FIELD_NUMBER = 24; - private boolean systemAPI_; + public static final int GRAPHQLCOMPLEXITYINFO_FIELD_NUMBER = 23; + private java.util.List graphqlComplexityInfo_; + /** + *
+   */ string graphQLSchema = 22;
+   * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + @java.lang.Override + public java.util.List getGraphqlComplexityInfoList() { + return graphqlComplexityInfo_; + } + /** + *
+   */ string graphQLSchema = 22;
+   * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + @java.lang.Override + public java.util.List + getGraphqlComplexityInfoOrBuilderList() { + return graphqlComplexityInfo_; + } /** *
    */ string graphQLSchema = 22;
-   * repeated GraphqlComplexity graphqlComplexityInfo = 23;
    * 
* + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + @java.lang.Override + public int getGraphqlComplexityInfoCount() { + return graphqlComplexityInfo_.size(); + } + /** + *
+   */ string graphQLSchema = 22;
+   * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + @java.lang.Override + public org.wso2.apk.enforcer.discovery.api.GraphqlComplexity getGraphqlComplexityInfo(int index) { + return graphqlComplexityInfo_.get(index); + } + /** + *
+   */ string graphQLSchema = 22;
+   * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + @java.lang.Override + public org.wso2.apk.enforcer.discovery.api.GraphqlComplexityOrBuilder getGraphqlComplexityInfoOrBuilder( + int index) { + return graphqlComplexityInfo_.get(index); + } + + public static final int SYSTEMAPI_FIELD_NUMBER = 24; + private boolean systemAPI_; + /** * bool systemAPI = 24; * @return The systemAPI. */ @@ -892,6 +986,72 @@ public boolean getSubscriptionValidation() { return subscriptionValidation_; } + public static final int ENDPOINTS_FIELD_NUMBER = 29; + private org.wso2.apk.enforcer.discovery.api.EndpointCluster endpoints_; + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + * @return Whether the endpoints field is set. + */ + @java.lang.Override + public boolean hasEndpoints() { + return endpoints_ != null; + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + * @return The endpoints. + */ + @java.lang.Override + public org.wso2.apk.enforcer.discovery.api.EndpointCluster getEndpoints() { + return endpoints_ == null ? org.wso2.apk.enforcer.discovery.api.EndpointCluster.getDefaultInstance() : endpoints_; + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + @java.lang.Override + public org.wso2.apk.enforcer.discovery.api.EndpointClusterOrBuilder getEndpointsOrBuilder() { + return getEndpoints(); + } + + public static final int ENDPOINTSECURITY_FIELD_NUMBER = 30; + private java.util.List endpointSecurity_; + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + @java.lang.Override + public java.util.List getEndpointSecurityList() { + return endpointSecurity_; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + @java.lang.Override + public java.util.List + getEndpointSecurityOrBuilderList() { + return endpointSecurity_; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + @java.lang.Override + public int getEndpointSecurityCount() { + return endpointSecurity_.size(); + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + @java.lang.Override + public org.wso2.apk.enforcer.discovery.api.SecurityInfo getEndpointSecurity(int index) { + return endpointSecurity_.get(index); + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + @java.lang.Override + public org.wso2.apk.enforcer.discovery.api.SecurityInfoOrBuilder getEndpointSecurityOrBuilder( + int index) { + return endpointSecurity_.get(index); + } + private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { @@ -954,6 +1114,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (applicationSecurity_ != false) { output.writeBool(16, applicationSecurity_); } + for (int i = 0; i < graphqlComplexityInfo_.size(); i++) { + output.writeMessage(23, graphqlComplexityInfo_.get(i)); + } if (systemAPI_ != false) { output.writeBool(24, systemAPI_); } @@ -969,6 +1132,12 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (subscriptionValidation_ != false) { output.writeBool(28, subscriptionValidation_); } + if (endpoints_ != null) { + output.writeMessage(29, getEndpoints()); + } + for (int i = 0; i < endpointSecurity_.size(); i++) { + output.writeMessage(30, endpointSecurity_.get(i)); + } unknownFields.writeTo(output); } @@ -1031,6 +1200,10 @@ public int getSerializedSize() { size += com.google.protobuf.CodedOutputStream .computeBoolSize(16, applicationSecurity_); } + for (int i = 0; i < graphqlComplexityInfo_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(23, graphqlComplexityInfo_.get(i)); + } if (systemAPI_ != false) { size += com.google.protobuf.CodedOutputStream .computeBoolSize(24, systemAPI_); @@ -1050,6 +1223,14 @@ public int getSerializedSize() { size += com.google.protobuf.CodedOutputStream .computeBoolSize(28, subscriptionValidation_); } + if (endpoints_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(29, getEndpoints()); + } + for (int i = 0; i < endpointSecurity_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(30, endpointSecurity_.get(i)); + } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; @@ -1097,6 +1278,8 @@ public boolean equals(final java.lang.Object obj) { .equals(other.getMutualSSL())) return false; if (getApplicationSecurity() != other.getApplicationSecurity()) return false; + if (!getGraphqlComplexityInfoList() + .equals(other.getGraphqlComplexityInfoList())) return false; if (getSystemAPI() != other.getSystemAPI()) return false; if (hasBackendJWTTokenInfo() != other.hasBackendJWTTokenInfo()) return false; @@ -1110,6 +1293,13 @@ public boolean equals(final java.lang.Object obj) { .equals(other.getEnvironment())) return false; if (getSubscriptionValidation() != other.getSubscriptionValidation()) return false; + if (hasEndpoints() != other.hasEndpoints()) return false; + if (hasEndpoints()) { + if (!getEndpoints() + .equals(other.getEndpoints())) return false; + } + if (!getEndpointSecurityList() + .equals(other.getEndpointSecurityList())) return false; if (!unknownFields.equals(other.unknownFields)) return false; return true; } @@ -1160,6 +1350,10 @@ public int hashCode() { hash = (37 * hash) + APPLICATIONSECURITY_FIELD_NUMBER; hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( getApplicationSecurity()); + if (getGraphqlComplexityInfoCount() > 0) { + hash = (37 * hash) + GRAPHQLCOMPLEXITYINFO_FIELD_NUMBER; + hash = (53 * hash) + getGraphqlComplexityInfoList().hashCode(); + } hash = (37 * hash) + SYSTEMAPI_FIELD_NUMBER; hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( getSystemAPI()); @@ -1174,6 +1368,14 @@ public int hashCode() { hash = (37 * hash) + SUBSCRIPTIONVALIDATION_FIELD_NUMBER; hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( getSubscriptionValidation()); + if (hasEndpoints()) { + hash = (37 * hash) + ENDPOINTS_FIELD_NUMBER; + hash = (53 * hash) + getEndpoints().hashCode(); + } + if (getEndpointSecurityCount() > 0) { + hash = (37 * hash) + ENDPOINTSECURITY_FIELD_NUMBER; + hash = (53 * hash) + getEndpointSecurityList().hashCode(); + } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -1308,6 +1510,8 @@ private void maybeForceBuilderInitialization() { .alwaysUseFieldBuilders) { getResourcesFieldBuilder(); getClientCertificatesFieldBuilder(); + getGraphqlComplexityInfoFieldBuilder(); + getEndpointSecurityFieldBuilder(); } } @java.lang.Override @@ -1353,6 +1557,12 @@ public Builder clear() { applicationSecurity_ = false; + if (graphqlComplexityInfoBuilder_ == null) { + graphqlComplexityInfo_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + } else { + graphqlComplexityInfoBuilder_.clear(); + } systemAPI_ = false; if (backendJWTTokenInfoBuilder_ == null) { @@ -1367,6 +1577,18 @@ public Builder clear() { subscriptionValidation_ = false; + if (endpointsBuilder_ == null) { + endpoints_ = null; + } else { + endpoints_ = null; + endpointsBuilder_ = null; + } + if (endpointSecurityBuilder_ == null) { + endpointSecurity_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000008); + } else { + endpointSecurityBuilder_.clear(); + } return this; } @@ -1426,6 +1648,15 @@ public org.wso2.apk.enforcer.discovery.api.Api buildPartial() { } result.mutualSSL_ = mutualSSL_; result.applicationSecurity_ = applicationSecurity_; + if (graphqlComplexityInfoBuilder_ == null) { + if (((bitField0_ & 0x00000004) != 0)) { + graphqlComplexityInfo_ = java.util.Collections.unmodifiableList(graphqlComplexityInfo_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.graphqlComplexityInfo_ = graphqlComplexityInfo_; + } else { + result.graphqlComplexityInfo_ = graphqlComplexityInfoBuilder_.build(); + } result.systemAPI_ = systemAPI_; if (backendJWTTokenInfoBuilder_ == null) { result.backendJWTTokenInfo_ = backendJWTTokenInfo_; @@ -1435,6 +1666,20 @@ public org.wso2.apk.enforcer.discovery.api.Api buildPartial() { result.apiDefinitionFile_ = apiDefinitionFile_; result.environment_ = environment_; result.subscriptionValidation_ = subscriptionValidation_; + if (endpointsBuilder_ == null) { + result.endpoints_ = endpoints_; + } else { + result.endpoints_ = endpointsBuilder_.build(); + } + if (endpointSecurityBuilder_ == null) { + if (((bitField0_ & 0x00000008) != 0)) { + endpointSecurity_ = java.util.Collections.unmodifiableList(endpointSecurity_); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.endpointSecurity_ = endpointSecurity_; + } else { + result.endpointSecurity_ = endpointSecurityBuilder_.build(); + } onBuilt(); return result; } @@ -1588,6 +1833,32 @@ public Builder mergeFrom(org.wso2.apk.enforcer.discovery.api.Api other) { if (other.getApplicationSecurity() != false) { setApplicationSecurity(other.getApplicationSecurity()); } + if (graphqlComplexityInfoBuilder_ == null) { + if (!other.graphqlComplexityInfo_.isEmpty()) { + if (graphqlComplexityInfo_.isEmpty()) { + graphqlComplexityInfo_ = other.graphqlComplexityInfo_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensureGraphqlComplexityInfoIsMutable(); + graphqlComplexityInfo_.addAll(other.graphqlComplexityInfo_); + } + onChanged(); + } + } else { + if (!other.graphqlComplexityInfo_.isEmpty()) { + if (graphqlComplexityInfoBuilder_.isEmpty()) { + graphqlComplexityInfoBuilder_.dispose(); + graphqlComplexityInfoBuilder_ = null; + graphqlComplexityInfo_ = other.graphqlComplexityInfo_; + bitField0_ = (bitField0_ & ~0x00000004); + graphqlComplexityInfoBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getGraphqlComplexityInfoFieldBuilder() : null; + } else { + graphqlComplexityInfoBuilder_.addAllMessages(other.graphqlComplexityInfo_); + } + } + } if (other.getSystemAPI() != false) { setSystemAPI(other.getSystemAPI()); } @@ -1604,6 +1875,35 @@ public Builder mergeFrom(org.wso2.apk.enforcer.discovery.api.Api other) { if (other.getSubscriptionValidation() != false) { setSubscriptionValidation(other.getSubscriptionValidation()); } + if (other.hasEndpoints()) { + mergeEndpoints(other.getEndpoints()); + } + if (endpointSecurityBuilder_ == null) { + if (!other.endpointSecurity_.isEmpty()) { + if (endpointSecurity_.isEmpty()) { + endpointSecurity_ = other.endpointSecurity_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensureEndpointSecurityIsMutable(); + endpointSecurity_.addAll(other.endpointSecurity_); + } + onChanged(); + } + } else { + if (!other.endpointSecurity_.isEmpty()) { + if (endpointSecurityBuilder_.isEmpty()) { + endpointSecurityBuilder_.dispose(); + endpointSecurityBuilder_ = null; + endpointSecurity_ = other.endpointSecurity_; + bitField0_ = (bitField0_ & ~0x00000008); + endpointSecurityBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getEndpointSecurityFieldBuilder() : null; + } else { + endpointSecurityBuilder_.addAllMessages(other.endpointSecurity_); + } + } + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -3115,139 +3415,436 @@ public Builder clearApplicationSecurity() { return this; } - private boolean systemAPI_ ; + private java.util.List graphqlComplexityInfo_ = + java.util.Collections.emptyList(); + private void ensureGraphqlComplexityInfoIsMutable() { + if (!((bitField0_ & 0x00000004) != 0)) { + graphqlComplexityInfo_ = new java.util.ArrayList(graphqlComplexityInfo_); + bitField0_ |= 0x00000004; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.GraphqlComplexity, org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder, org.wso2.apk.enforcer.discovery.api.GraphqlComplexityOrBuilder> graphqlComplexityInfoBuilder_; + /** *
      */ string graphQLSchema = 22;
-     * repeated GraphqlComplexity graphqlComplexityInfo = 23;
      * 
* - * bool systemAPI = 24; - * @return The systemAPI. + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - @java.lang.Override - public boolean getSystemAPI() { - return systemAPI_; + public java.util.List getGraphqlComplexityInfoList() { + if (graphqlComplexityInfoBuilder_ == null) { + return java.util.Collections.unmodifiableList(graphqlComplexityInfo_); + } else { + return graphqlComplexityInfoBuilder_.getMessageList(); + } } /** *
      */ string graphQLSchema = 22;
-     * repeated GraphqlComplexity graphqlComplexityInfo = 23;
      * 
* - * bool systemAPI = 24; - * @param value The systemAPI to set. - * @return This builder for chaining. + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - public Builder setSystemAPI(boolean value) { - - systemAPI_ = value; - onChanged(); - return this; + public int getGraphqlComplexityInfoCount() { + if (graphqlComplexityInfoBuilder_ == null) { + return graphqlComplexityInfo_.size(); + } else { + return graphqlComplexityInfoBuilder_.getCount(); + } } /** *
      */ string graphQLSchema = 22;
-     * repeated GraphqlComplexity graphqlComplexityInfo = 23;
      * 
* - * bool systemAPI = 24; - * @return This builder for chaining. + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - public Builder clearSystemAPI() { - - systemAPI_ = false; - onChanged(); - return this; + public org.wso2.apk.enforcer.discovery.api.GraphqlComplexity getGraphqlComplexityInfo(int index) { + if (graphqlComplexityInfoBuilder_ == null) { + return graphqlComplexityInfo_.get(index); + } else { + return graphqlComplexityInfoBuilder_.getMessage(index); + } } - - private org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo_; - private com.google.protobuf.SingleFieldBuilderV3< - org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo, org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.Builder, org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfoOrBuilder> backendJWTTokenInfoBuilder_; /** - * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; - * @return Whether the backendJWTTokenInfo field is set. + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - public boolean hasBackendJWTTokenInfo() { - return backendJWTTokenInfoBuilder_ != null || backendJWTTokenInfo_ != null; + public Builder setGraphqlComplexityInfo( + int index, org.wso2.apk.enforcer.discovery.api.GraphqlComplexity value) { + if (graphqlComplexityInfoBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureGraphqlComplexityInfoIsMutable(); + graphqlComplexityInfo_.set(index, value); + onChanged(); + } else { + graphqlComplexityInfoBuilder_.setMessage(index, value); + } + return this; } /** - * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; - * @return The backendJWTTokenInfo. + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - public org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo getBackendJWTTokenInfo() { - if (backendJWTTokenInfoBuilder_ == null) { - return backendJWTTokenInfo_ == null ? org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.getDefaultInstance() : backendJWTTokenInfo_; + public Builder setGraphqlComplexityInfo( + int index, org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder builderForValue) { + if (graphqlComplexityInfoBuilder_ == null) { + ensureGraphqlComplexityInfoIsMutable(); + graphqlComplexityInfo_.set(index, builderForValue.build()); + onChanged(); } else { - return backendJWTTokenInfoBuilder_.getMessage(); + graphqlComplexityInfoBuilder_.setMessage(index, builderForValue.build()); } + return this; } /** - * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - public Builder setBackendJWTTokenInfo(org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo value) { - if (backendJWTTokenInfoBuilder_ == null) { + public Builder addGraphqlComplexityInfo(org.wso2.apk.enforcer.discovery.api.GraphqlComplexity value) { + if (graphqlComplexityInfoBuilder_ == null) { if (value == null) { throw new NullPointerException(); } - backendJWTTokenInfo_ = value; + ensureGraphqlComplexityInfoIsMutable(); + graphqlComplexityInfo_.add(value); onChanged(); } else { - backendJWTTokenInfoBuilder_.setMessage(value); + graphqlComplexityInfoBuilder_.addMessage(value); } - return this; } /** - * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - public Builder setBackendJWTTokenInfo( - org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.Builder builderForValue) { - if (backendJWTTokenInfoBuilder_ == null) { - backendJWTTokenInfo_ = builderForValue.build(); + public Builder addGraphqlComplexityInfo( + int index, org.wso2.apk.enforcer.discovery.api.GraphqlComplexity value) { + if (graphqlComplexityInfoBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureGraphqlComplexityInfoIsMutable(); + graphqlComplexityInfo_.add(index, value); onChanged(); } else { - backendJWTTokenInfoBuilder_.setMessage(builderForValue.build()); + graphqlComplexityInfoBuilder_.addMessage(index, value); } - return this; } /** - * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - public Builder mergeBackendJWTTokenInfo(org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo value) { - if (backendJWTTokenInfoBuilder_ == null) { - if (backendJWTTokenInfo_ != null) { - backendJWTTokenInfo_ = - org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.newBuilder(backendJWTTokenInfo_).mergeFrom(value).buildPartial(); - } else { - backendJWTTokenInfo_ = value; - } + public Builder addGraphqlComplexityInfo( + org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder builderForValue) { + if (graphqlComplexityInfoBuilder_ == null) { + ensureGraphqlComplexityInfoIsMutable(); + graphqlComplexityInfo_.add(builderForValue.build()); onChanged(); } else { - backendJWTTokenInfoBuilder_.mergeFrom(value); + graphqlComplexityInfoBuilder_.addMessage(builderForValue.build()); } - return this; } /** - * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; */ - public Builder clearBackendJWTTokenInfo() { - if (backendJWTTokenInfoBuilder_ == null) { - backendJWTTokenInfo_ = null; + public Builder addGraphqlComplexityInfo( + int index, org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder builderForValue) { + if (graphqlComplexityInfoBuilder_ == null) { + ensureGraphqlComplexityInfoIsMutable(); + graphqlComplexityInfo_.add(index, builderForValue.build()); onChanged(); } else { - backendJWTTokenInfo_ = null; - backendJWTTokenInfoBuilder_ = null; + graphqlComplexityInfoBuilder_.addMessage(index, builderForValue.build()); } - return this; } /** - * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; - */ - public org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.Builder getBackendJWTTokenInfoBuilder() { + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public Builder addAllGraphqlComplexityInfo( + java.lang.Iterable values) { + if (graphqlComplexityInfoBuilder_ == null) { + ensureGraphqlComplexityInfoIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, graphqlComplexityInfo_); + onChanged(); + } else { + graphqlComplexityInfoBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public Builder clearGraphqlComplexityInfo() { + if (graphqlComplexityInfoBuilder_ == null) { + graphqlComplexityInfo_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + } else { + graphqlComplexityInfoBuilder_.clear(); + } + return this; + } + /** + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public Builder removeGraphqlComplexityInfo(int index) { + if (graphqlComplexityInfoBuilder_ == null) { + ensureGraphqlComplexityInfoIsMutable(); + graphqlComplexityInfo_.remove(index); + onChanged(); + } else { + graphqlComplexityInfoBuilder_.remove(index); + } + return this; + } + /** + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder getGraphqlComplexityInfoBuilder( + int index) { + return getGraphqlComplexityInfoFieldBuilder().getBuilder(index); + } + /** + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public org.wso2.apk.enforcer.discovery.api.GraphqlComplexityOrBuilder getGraphqlComplexityInfoOrBuilder( + int index) { + if (graphqlComplexityInfoBuilder_ == null) { + return graphqlComplexityInfo_.get(index); } else { + return graphqlComplexityInfoBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public java.util.List + getGraphqlComplexityInfoOrBuilderList() { + if (graphqlComplexityInfoBuilder_ != null) { + return graphqlComplexityInfoBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(graphqlComplexityInfo_); + } + } + /** + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder addGraphqlComplexityInfoBuilder() { + return getGraphqlComplexityInfoFieldBuilder().addBuilder( + org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.getDefaultInstance()); + } + /** + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder addGraphqlComplexityInfoBuilder( + int index) { + return getGraphqlComplexityInfoFieldBuilder().addBuilder( + index, org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.getDefaultInstance()); + } + /** + *
+     */ string graphQLSchema = 22;
+     * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + public java.util.List + getGraphqlComplexityInfoBuilderList() { + return getGraphqlComplexityInfoFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.GraphqlComplexity, org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder, org.wso2.apk.enforcer.discovery.api.GraphqlComplexityOrBuilder> + getGraphqlComplexityInfoFieldBuilder() { + if (graphqlComplexityInfoBuilder_ == null) { + graphqlComplexityInfoBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.GraphqlComplexity, org.wso2.apk.enforcer.discovery.api.GraphqlComplexity.Builder, org.wso2.apk.enforcer.discovery.api.GraphqlComplexityOrBuilder>( + graphqlComplexityInfo_, + ((bitField0_ & 0x00000004) != 0), + getParentForChildren(), + isClean()); + graphqlComplexityInfo_ = null; + } + return graphqlComplexityInfoBuilder_; + } + + private boolean systemAPI_ ; + /** + * bool systemAPI = 24; + * @return The systemAPI. + */ + @java.lang.Override + public boolean getSystemAPI() { + return systemAPI_; + } + /** + * bool systemAPI = 24; + * @param value The systemAPI to set. + * @return This builder for chaining. + */ + public Builder setSystemAPI(boolean value) { + + systemAPI_ = value; + onChanged(); + return this; + } + /** + * bool systemAPI = 24; + * @return This builder for chaining. + */ + public Builder clearSystemAPI() { + + systemAPI_ = false; + onChanged(); + return this; + } + + private org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo_; + private com.google.protobuf.SingleFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo, org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.Builder, org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfoOrBuilder> backendJWTTokenInfoBuilder_; + /** + * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + * @return Whether the backendJWTTokenInfo field is set. + */ + public boolean hasBackendJWTTokenInfo() { + return backendJWTTokenInfoBuilder_ != null || backendJWTTokenInfo_ != null; + } + /** + * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + * @return The backendJWTTokenInfo. + */ + public org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo getBackendJWTTokenInfo() { + if (backendJWTTokenInfoBuilder_ == null) { + return backendJWTTokenInfo_ == null ? org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.getDefaultInstance() : backendJWTTokenInfo_; + } else { + return backendJWTTokenInfoBuilder_.getMessage(); + } + } + /** + * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + */ + public Builder setBackendJWTTokenInfo(org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo value) { + if (backendJWTTokenInfoBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + backendJWTTokenInfo_ = value; + onChanged(); + } else { + backendJWTTokenInfoBuilder_.setMessage(value); + } + + return this; + } + /** + * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + */ + public Builder setBackendJWTTokenInfo( + org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.Builder builderForValue) { + if (backendJWTTokenInfoBuilder_ == null) { + backendJWTTokenInfo_ = builderForValue.build(); + onChanged(); + } else { + backendJWTTokenInfoBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + */ + public Builder mergeBackendJWTTokenInfo(org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo value) { + if (backendJWTTokenInfoBuilder_ == null) { + if (backendJWTTokenInfo_ != null) { + backendJWTTokenInfo_ = + org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.newBuilder(backendJWTTokenInfo_).mergeFrom(value).buildPartial(); + } else { + backendJWTTokenInfo_ = value; + } + onChanged(); + } else { + backendJWTTokenInfoBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + */ + public Builder clearBackendJWTTokenInfo() { + if (backendJWTTokenInfoBuilder_ == null) { + backendJWTTokenInfo_ = null; + onChanged(); + } else { + backendJWTTokenInfo_ = null; + backendJWTTokenInfoBuilder_ = null; + } + + return this; + } + /** + * .wso2.discovery.api.BackendJWTTokenInfo backendJWTTokenInfo = 25; + */ + public org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo.Builder getBackendJWTTokenInfoBuilder() { onChanged(); return getBackendJWTTokenInfoFieldBuilder().getBuilder(); @@ -3420,6 +4017,365 @@ public Builder clearSubscriptionValidation() { onChanged(); return this; } + + private org.wso2.apk.enforcer.discovery.api.EndpointCluster endpoints_; + private com.google.protobuf.SingleFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.EndpointCluster, org.wso2.apk.enforcer.discovery.api.EndpointCluster.Builder, org.wso2.apk.enforcer.discovery.api.EndpointClusterOrBuilder> endpointsBuilder_; + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + * @return Whether the endpoints field is set. + */ + public boolean hasEndpoints() { + return endpointsBuilder_ != null || endpoints_ != null; + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + * @return The endpoints. + */ + public org.wso2.apk.enforcer.discovery.api.EndpointCluster getEndpoints() { + if (endpointsBuilder_ == null) { + return endpoints_ == null ? org.wso2.apk.enforcer.discovery.api.EndpointCluster.getDefaultInstance() : endpoints_; + } else { + return endpointsBuilder_.getMessage(); + } + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + public Builder setEndpoints(org.wso2.apk.enforcer.discovery.api.EndpointCluster value) { + if (endpointsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + endpoints_ = value; + onChanged(); + } else { + endpointsBuilder_.setMessage(value); + } + + return this; + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + public Builder setEndpoints( + org.wso2.apk.enforcer.discovery.api.EndpointCluster.Builder builderForValue) { + if (endpointsBuilder_ == null) { + endpoints_ = builderForValue.build(); + onChanged(); + } else { + endpointsBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + public Builder mergeEndpoints(org.wso2.apk.enforcer.discovery.api.EndpointCluster value) { + if (endpointsBuilder_ == null) { + if (endpoints_ != null) { + endpoints_ = + org.wso2.apk.enforcer.discovery.api.EndpointCluster.newBuilder(endpoints_).mergeFrom(value).buildPartial(); + } else { + endpoints_ = value; + } + onChanged(); + } else { + endpointsBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + public Builder clearEndpoints() { + if (endpointsBuilder_ == null) { + endpoints_ = null; + onChanged(); + } else { + endpoints_ = null; + endpointsBuilder_ = null; + } + + return this; + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + public org.wso2.apk.enforcer.discovery.api.EndpointCluster.Builder getEndpointsBuilder() { + + onChanged(); + return getEndpointsFieldBuilder().getBuilder(); + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + public org.wso2.apk.enforcer.discovery.api.EndpointClusterOrBuilder getEndpointsOrBuilder() { + if (endpointsBuilder_ != null) { + return endpointsBuilder_.getMessageOrBuilder(); + } else { + return endpoints_ == null ? + org.wso2.apk.enforcer.discovery.api.EndpointCluster.getDefaultInstance() : endpoints_; + } + } + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + private com.google.protobuf.SingleFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.EndpointCluster, org.wso2.apk.enforcer.discovery.api.EndpointCluster.Builder, org.wso2.apk.enforcer.discovery.api.EndpointClusterOrBuilder> + getEndpointsFieldBuilder() { + if (endpointsBuilder_ == null) { + endpointsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.EndpointCluster, org.wso2.apk.enforcer.discovery.api.EndpointCluster.Builder, org.wso2.apk.enforcer.discovery.api.EndpointClusterOrBuilder>( + getEndpoints(), + getParentForChildren(), + isClean()); + endpoints_ = null; + } + return endpointsBuilder_; + } + + private java.util.List endpointSecurity_ = + java.util.Collections.emptyList(); + private void ensureEndpointSecurityIsMutable() { + if (!((bitField0_ & 0x00000008) != 0)) { + endpointSecurity_ = new java.util.ArrayList(endpointSecurity_); + bitField0_ |= 0x00000008; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.SecurityInfo, org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder, org.wso2.apk.enforcer.discovery.api.SecurityInfoOrBuilder> endpointSecurityBuilder_; + + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public java.util.List getEndpointSecurityList() { + if (endpointSecurityBuilder_ == null) { + return java.util.Collections.unmodifiableList(endpointSecurity_); + } else { + return endpointSecurityBuilder_.getMessageList(); + } + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public int getEndpointSecurityCount() { + if (endpointSecurityBuilder_ == null) { + return endpointSecurity_.size(); + } else { + return endpointSecurityBuilder_.getCount(); + } + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public org.wso2.apk.enforcer.discovery.api.SecurityInfo getEndpointSecurity(int index) { + if (endpointSecurityBuilder_ == null) { + return endpointSecurity_.get(index); + } else { + return endpointSecurityBuilder_.getMessage(index); + } + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder setEndpointSecurity( + int index, org.wso2.apk.enforcer.discovery.api.SecurityInfo value) { + if (endpointSecurityBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureEndpointSecurityIsMutable(); + endpointSecurity_.set(index, value); + onChanged(); + } else { + endpointSecurityBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder setEndpointSecurity( + int index, org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder builderForValue) { + if (endpointSecurityBuilder_ == null) { + ensureEndpointSecurityIsMutable(); + endpointSecurity_.set(index, builderForValue.build()); + onChanged(); + } else { + endpointSecurityBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder addEndpointSecurity(org.wso2.apk.enforcer.discovery.api.SecurityInfo value) { + if (endpointSecurityBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureEndpointSecurityIsMutable(); + endpointSecurity_.add(value); + onChanged(); + } else { + endpointSecurityBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder addEndpointSecurity( + int index, org.wso2.apk.enforcer.discovery.api.SecurityInfo value) { + if (endpointSecurityBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureEndpointSecurityIsMutable(); + endpointSecurity_.add(index, value); + onChanged(); + } else { + endpointSecurityBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder addEndpointSecurity( + org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder builderForValue) { + if (endpointSecurityBuilder_ == null) { + ensureEndpointSecurityIsMutable(); + endpointSecurity_.add(builderForValue.build()); + onChanged(); + } else { + endpointSecurityBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder addEndpointSecurity( + int index, org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder builderForValue) { + if (endpointSecurityBuilder_ == null) { + ensureEndpointSecurityIsMutable(); + endpointSecurity_.add(index, builderForValue.build()); + onChanged(); + } else { + endpointSecurityBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder addAllEndpointSecurity( + java.lang.Iterable values) { + if (endpointSecurityBuilder_ == null) { + ensureEndpointSecurityIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, endpointSecurity_); + onChanged(); + } else { + endpointSecurityBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder clearEndpointSecurity() { + if (endpointSecurityBuilder_ == null) { + endpointSecurity_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + } else { + endpointSecurityBuilder_.clear(); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public Builder removeEndpointSecurity(int index) { + if (endpointSecurityBuilder_ == null) { + ensureEndpointSecurityIsMutable(); + endpointSecurity_.remove(index); + onChanged(); + } else { + endpointSecurityBuilder_.remove(index); + } + return this; + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder getEndpointSecurityBuilder( + int index) { + return getEndpointSecurityFieldBuilder().getBuilder(index); + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public org.wso2.apk.enforcer.discovery.api.SecurityInfoOrBuilder getEndpointSecurityOrBuilder( + int index) { + if (endpointSecurityBuilder_ == null) { + return endpointSecurity_.get(index); } else { + return endpointSecurityBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public java.util.List + getEndpointSecurityOrBuilderList() { + if (endpointSecurityBuilder_ != null) { + return endpointSecurityBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(endpointSecurity_); + } + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder addEndpointSecurityBuilder() { + return getEndpointSecurityFieldBuilder().addBuilder( + org.wso2.apk.enforcer.discovery.api.SecurityInfo.getDefaultInstance()); + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder addEndpointSecurityBuilder( + int index) { + return getEndpointSecurityFieldBuilder().addBuilder( + index, org.wso2.apk.enforcer.discovery.api.SecurityInfo.getDefaultInstance()); + } + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + public java.util.List + getEndpointSecurityBuilderList() { + return getEndpointSecurityFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.SecurityInfo, org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder, org.wso2.apk.enforcer.discovery.api.SecurityInfoOrBuilder> + getEndpointSecurityFieldBuilder() { + if (endpointSecurityBuilder_ == null) { + endpointSecurityBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + org.wso2.apk.enforcer.discovery.api.SecurityInfo, org.wso2.apk.enforcer.discovery.api.SecurityInfo.Builder, org.wso2.apk.enforcer.discovery.api.SecurityInfoOrBuilder>( + endpointSecurity_, + ((bitField0_ & 0x00000008) != 0), + getParentForChildren(), + isClean()); + endpointSecurity_ = null; + } + return endpointSecurityBuilder_; + } @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/ApiOrBuilder.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/ApiOrBuilder.java index 2eb44452c..68a84404c 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/ApiOrBuilder.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/ApiOrBuilder.java @@ -228,9 +228,48 @@ org.wso2.apk.enforcer.discovery.api.CertificateOrBuilder getClientCertificatesOr /** *
    */ string graphQLSchema = 22;
-   * repeated GraphqlComplexity graphqlComplexityInfo = 23;
    * 
* + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + java.util.List + getGraphqlComplexityInfoList(); + /** + *
+   */ string graphQLSchema = 22;
+   * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + org.wso2.apk.enforcer.discovery.api.GraphqlComplexity getGraphqlComplexityInfo(int index); + /** + *
+   */ string graphQLSchema = 22;
+   * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + int getGraphqlComplexityInfoCount(); + /** + *
+   */ string graphQLSchema = 22;
+   * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + java.util.List + getGraphqlComplexityInfoOrBuilderList(); + /** + *
+   */ string graphQLSchema = 22;
+   * 
+ * + * repeated .wso2.discovery.api.GraphqlComplexity graphqlComplexityInfo = 23; + */ + org.wso2.apk.enforcer.discovery.api.GraphqlComplexityOrBuilder getGraphqlComplexityInfoOrBuilder( + int index); + + /** * bool systemAPI = 24; * @return The systemAPI. */ @@ -274,4 +313,43 @@ org.wso2.apk.enforcer.discovery.api.CertificateOrBuilder getClientCertificatesOr * @return The subscriptionValidation. */ boolean getSubscriptionValidation(); + + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + * @return Whether the endpoints field is set. + */ + boolean hasEndpoints(); + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + * @return The endpoints. + */ + org.wso2.apk.enforcer.discovery.api.EndpointCluster getEndpoints(); + /** + * .wso2.discovery.api.EndpointCluster endpoints = 29; + */ + org.wso2.apk.enforcer.discovery.api.EndpointClusterOrBuilder getEndpointsOrBuilder(); + + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + java.util.List + getEndpointSecurityList(); + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + org.wso2.apk.enforcer.discovery.api.SecurityInfo getEndpointSecurity(int index); + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + int getEndpointSecurityCount(); + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + java.util.List + getEndpointSecurityOrBuilderList(); + /** + * repeated .wso2.discovery.api.SecurityInfo endpointSecurity = 30; + */ + org.wso2.apk.enforcer.discovery.api.SecurityInfoOrBuilder getEndpointSecurityOrBuilder( + int index); } diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/ApiProto.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/ApiProto.java index d3deec7d7..9350f8db7 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/ApiProto.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/api/ApiProto.java @@ -32,24 +32,31 @@ public static void registerAllExtensions( "covery.api\032!wso2/discovery/api/Resource." + "proto\032$wso2/discovery/api/Certificate.pr" + "oto\032,wso2/discovery/api/BackendJWTTokenI" + - "nfo.proto\"\263\004\n\003Api\022\n\n\002id\030\001 \001(\t\022\r\n\005title\030\002" + - " \001(\t\022\017\n\007version\030\003 \001(\t\022\017\n\007apiType\030\004 \001(\t\022\036" + - "\n\026disableAuthentications\030\005 \001(\010\022\025\n\rdisabl" + - "eScopes\030\006 \001(\010\022\017\n\007envType\030\007 \001(\t\022/\n\tresour" + - "ces\030\010 \003(\0132\034.wso2.discovery.api.Resource\022" + - "\020\n\010basePath\030\t \001(\t\022\014\n\004tier\030\n \001(\t\022\031\n\021apiLi" + - "feCycleState\030\013 \001(\t\022\r\n\005vhost\030\014 \001(\t\022\026\n\016org" + - "anizationId\030\r \001(\t\022;\n\022clientCertificates\030" + - "\016 \003(\0132\037.wso2.discovery.api.Certificate\022\021" + - "\n\tmutualSSL\030\017 \001(\t\022\033\n\023applicationSecurity" + - "\030\020 \001(\010\022\021\n\tsystemAPI\030\030 \001(\010\022D\n\023backendJWTT" + - "okenInfo\030\031 \001(\0132\'.wso2.discovery.api.Back" + - "endJWTTokenInfo\022\031\n\021apiDefinitionFile\030\032 \001" + - "(\014\022\023\n\013environment\030\033 \001(\t\022\036\n\026subscriptionV" + - "alidation\030\034 \001(\010Bp\n#org.wso2.apk.enforcer" + - ".discovery.apiB\010ApiProtoP\001Z=github.com/e" + - "nvoyproxy/go-control-plane/wso2/discover" + - "y/api;apib\006proto3" + "nfo.proto\032)wso2/discovery/api/endpoint_c" + + "luster.proto\032&wso2/discovery/api/securit" + + "y_info.proto\032 wso2/discovery/api/graphql" + + ".proto\"\355\005\n\003Api\022\n\n\002id\030\001 \001(\t\022\r\n\005title\030\002 \001(" + + "\t\022\017\n\007version\030\003 \001(\t\022\017\n\007apiType\030\004 \001(\t\022\036\n\026d" + + "isableAuthentications\030\005 \001(\010\022\025\n\rdisableSc" + + "opes\030\006 \001(\010\022\017\n\007envType\030\007 \001(\t\022/\n\tresources" + + "\030\010 \003(\0132\034.wso2.discovery.api.Resource\022\020\n\010" + + "basePath\030\t \001(\t\022\014\n\004tier\030\n \001(\t\022\031\n\021apiLifeC" + + "ycleState\030\013 \001(\t\022\r\n\005vhost\030\014 \001(\t\022\026\n\016organi" + + "zationId\030\r \001(\t\022;\n\022clientCertificates\030\016 \003" + + "(\0132\037.wso2.discovery.api.Certificate\022\021\n\tm" + + "utualSSL\030\017 \001(\t\022\033\n\023applicationSecurity\030\020 " + + "\001(\010\022D\n\025graphqlComplexityInfo\030\027 \003(\0132%.wso" + + "2.discovery.api.GraphqlComplexity\022\021\n\tsys" + + "temAPI\030\030 \001(\010\022D\n\023backendJWTTokenInfo\030\031 \001(" + + "\0132\'.wso2.discovery.api.BackendJWTTokenIn" + + "fo\022\031\n\021apiDefinitionFile\030\032 \001(\014\022\023\n\013environ" + + "ment\030\033 \001(\t\022\036\n\026subscriptionValidation\030\034 \001" + + "(\010\0226\n\tendpoints\030\035 \001(\0132#.wso2.discovery.a" + + "pi.EndpointCluster\022:\n\020endpointSecurity\030\036" + + " \003(\0132 .wso2.discovery.api.SecurityInfoBp" + + "\n#org.wso2.apk.enforcer.discovery.apiB\010A" + + "piProtoP\001Z=github.com/envoyproxy/go-cont" + + "rol-plane/wso2/discovery/api;apib\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, @@ -57,16 +64,22 @@ public static void registerAllExtensions( org.wso2.apk.enforcer.discovery.api.ResourceProto.getDescriptor(), org.wso2.apk.enforcer.discovery.api.CertificateProto.getDescriptor(), org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfoProto.getDescriptor(), + org.wso2.apk.enforcer.discovery.api.EndpointClusterProto.getDescriptor(), + org.wso2.apk.enforcer.discovery.api.SecurityInfoProto.getDescriptor(), + org.wso2.apk.enforcer.discovery.api.GraphQLProto.getDescriptor(), }); internal_static_wso2_discovery_api_Api_descriptor = getDescriptor().getMessageTypes().get(0); internal_static_wso2_discovery_api_Api_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_wso2_discovery_api_Api_descriptor, - new java.lang.String[] { "Id", "Title", "Version", "ApiType", "DisableAuthentications", "DisableScopes", "EnvType", "Resources", "BasePath", "Tier", "ApiLifeCycleState", "Vhost", "OrganizationId", "ClientCertificates", "MutualSSL", "ApplicationSecurity", "SystemAPI", "BackendJWTTokenInfo", "ApiDefinitionFile", "Environment", "SubscriptionValidation", }); + new java.lang.String[] { "Id", "Title", "Version", "ApiType", "DisableAuthentications", "DisableScopes", "EnvType", "Resources", "BasePath", "Tier", "ApiLifeCycleState", "Vhost", "OrganizationId", "ClientCertificates", "MutualSSL", "ApplicationSecurity", "GraphqlComplexityInfo", "SystemAPI", "BackendJWTTokenInfo", "ApiDefinitionFile", "Environment", "SubscriptionValidation", "Endpoints", "EndpointSecurity", }); org.wso2.apk.enforcer.discovery.api.ResourceProto.getDescriptor(); org.wso2.apk.enforcer.discovery.api.CertificateProto.getDescriptor(); org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfoProto.getDescriptor(); + org.wso2.apk.enforcer.discovery.api.EndpointClusterProto.getDescriptor(); + org.wso2.apk.enforcer.discovery.api.SecurityInfoProto.getDescriptor(); + org.wso2.apk.enforcer.discovery.api.GraphQLProto.getDescriptor(); } // @@protoc_insertion_point(outer_class_scope) diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/config/enforcer/AnalyticsPublisherProto.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/config/enforcer/AnalyticsPublisherProto.java index f4aeee9d5..cb95e9049 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/config/enforcer/AnalyticsPublisherProto.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/discovery/config/enforcer/AnalyticsPublisherProto.java @@ -35,22 +35,20 @@ public static void registerAllExtensions( java.lang.String[] descriptorData = { "\n8wso2/discovery/config/enforcer/analyti" + "cs_publisher.proto\022\036wso2.discovery.confi" + - "g.enforcer\032,wso2/discovery/config/enforc" + - "er/service.proto\"\320\001\n\022AnalyticsPublisher\022" + - "\017\n\007enabled\030\001 \001(\010\022b\n\020configProperties\030\002 \003" + - "(\0132H.wso2.discovery.config.enforcer.Anal" + - "yticsPublisher.ConfigPropertiesEntry\022\014\n\004" + - "type\030\003 \001(\t\0327\n\025ConfigPropertiesEntry\022\013\n\003k" + - "ey\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001B\234\001\n/org.wso2" + - ".apk.enforcer.discovery.config.enforcerB" + - "\027AnalyticsPublisherProtoP\001ZNgithub.com/e" + - "nvoyproxy/go-control-plane/wso2/discover" + - "y/config/enforcer;enforcerb\006proto3" + "g.enforcer\"\320\001\n\022AnalyticsPublisher\022\017\n\007ena" + + "bled\030\001 \001(\010\022b\n\020configProperties\030\002 \003(\0132H.w" + + "so2.discovery.config.enforcer.AnalyticsP" + + "ublisher.ConfigPropertiesEntry\022\014\n\004type\030\003" + + " \001(\t\0327\n\025ConfigPropertiesEntry\022\013\n\003key\030\001 \001" + + "(\t\022\r\n\005value\030\002 \001(\t:\0028\001B\234\001\n/org.wso2.apk.e" + + "nforcer.discovery.config.enforcerB\027Analy" + + "ticsPublisherProtoP\001ZNgithub.com/envoypr" + + "oxy/go-control-plane/wso2/discovery/conf" + + "ig/enforcer;enforcerb\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] { - org.wso2.apk.enforcer.discovery.config.enforcer.ServiceProto.getDescriptor(), }); internal_static_wso2_discovery_config_enforcer_AnalyticsPublisher_descriptor = getDescriptor().getMessageTypes().get(0); @@ -64,7 +62,6 @@ public static void registerAllExtensions( com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_wso2_discovery_config_enforcer_AnalyticsPublisher_ConfigPropertiesEntry_descriptor, new java.lang.String[] { "Key", "Value", }); - org.wso2.apk.enforcer.discovery.config.enforcer.ServiceProto.getDescriptor(); } // @@protoc_insertion_point(outer_class_scope) diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/security/jwt/JWTAuthenticator.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/security/jwt/JWTAuthenticator.java index 510783ab3..c9e8a1e0d 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/security/jwt/JWTAuthenticator.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/security/jwt/JWTAuthenticator.java @@ -438,7 +438,6 @@ private JWTValidationInfo getJwtValidationInfo(String jwtToken, String organizat } signedJWT = SignedJWT.parse(jwtToken); jwtClaimsSet = signedJWT.getJWTClaimsSet(); - // todo(amali) create validationinfo directly signedJWTInfo = new SignedJWTInfo(jwtToken, signedJWT, jwtClaimsSet); } catch (ParseException | IllegalArgumentException e) { log.error("Failed to decode the token header. {}", e.getMessage()); diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/APIDefinitionUtils.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/APIDefinitionUtils.java index 3258c4cdf..5f320de8a 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/APIDefinitionUtils.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/APIDefinitionUtils.java @@ -3,6 +3,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.zip.GZIPInputStream; import org.wso2.apk.enforcer.models.ResponsePayload; public class APIDefinitionUtils { @@ -22,4 +27,18 @@ public static ResponsePayload buildResponsePayload(Object dataModel, HttpRespons responsePayload.setStatus(status); return responsePayload; } + + public static String ReadGzip(byte[] gzip) throws IOException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(gzip); + GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream); + InputStreamReader inputStreamReader = new InputStreamReader(gzipInputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); + String line; + StringBuilder stringBuilder = new StringBuilder(); + + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line).append("\n"); + } + return new String(stringBuilder); + } } diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java index a7d3af70b..16538f4d0 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java @@ -2,35 +2,22 @@ import io.grpc.netty.shaded.io.netty.buffer.Unpooled; import io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext; -import io.grpc.netty.shaded.io.netty.channel.ChannelInboundHandlerAdapter; import io.grpc.netty.shaded.io.netty.channel.SimpleChannelInboundHandler; import io.grpc.netty.shaded.io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.grpc.netty.shaded.io.netty.handler.codec.http.FullHttpMessage; import io.grpc.netty.shaded.io.netty.handler.codec.http.FullHttpResponse; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpObject; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpRequest; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpVersion; import io.grpc.netty.shaded.io.netty.util.CharsetUtil; -import io.netty.handler.codec.http.FullHttpRequest; import org.apache.http.protocol.HTTP; import org.wso2.apk.enforcer.api.APIFactory; import org.wso2.apk.enforcer.constants.APIDefinitionConstants; import org.wso2.apk.enforcer.constants.AdminConstants; import org.wso2.apk.enforcer.constants.HttpConstants; import org.wso2.apk.enforcer.models.ResponsePayload; -import org.wso2.apk.enforcer.subscription.SubscriptionDataHolder; -import org.wso2.apk.enforcer.subscription.SubscriptionDataStore; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import java.util.zip.GZIPInputStream; public class SwaggerServerHandler extends SimpleChannelInboundHandler { @@ -76,18 +63,8 @@ public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Excep buildAndSendResponse(ctx, responsePayload); return; } - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(apiDefinition); - GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream); - InputStreamReader inputStreamReader = new InputStreamReader(gzipInputStream); - BufferedReader bufferedReader = new BufferedReader(inputStreamReader); - String line; - StringBuilder stringBuilder = new StringBuilder(); - - while ((line = bufferedReader.readLine()) != null) { - stringBuilder.append(line); - } Map map = new HashMap<>(); - map.put("apiDefinition", new String(stringBuilder)); + map.put("apiDefinition", APIDefinitionUtils.ReadGzip(apiDefinition)); responsePayload = APIDefinitionUtils.buildResponsePayload(map, HttpResponseStatus.OK, false); buildAndSendResponse(ctx, responsePayload); } diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/util/EndpointUtils.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/util/EndpointUtils.java index 1a2e2b813..449b0a661 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/util/EndpointUtils.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/util/EndpointUtils.java @@ -68,8 +68,7 @@ public static void addEndpointSecurity(RequestContext requestContext) { * @param requestContext request Context */ public static void updateClusterHeaderAndCheckEnv(RequestContext requestContext) { - requestContext.addOrModifyHeaders(AdapterConstants.CLUSTER_HEADER, - requestContext.getClusterHeader()); + requestContext.addOrModifyHeaders(AdapterConstants.CLUSTER_HEADER, requestContext.getClusterHeader()); requestContext.getRemoveHeaders().remove(AdapterConstants.CLUSTER_HEADER); addRouterHttpHeaders(requestContext); addEndpointSecurity(requestContext); diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/test/java/org/wso2/apk/enforcer/jwt/JWTValidatorTest.java b/gateway/enforcer/org.wso2.apk.enforcer/src/test/java/org/wso2/apk/enforcer/jwt/JWTValidatorTest.java index 3e4b44b90..b4ceb92cf 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/test/java/org/wso2/apk/enforcer/jwt/JWTValidatorTest.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/test/java/org/wso2/apk/enforcer/jwt/JWTValidatorTest.java @@ -164,7 +164,6 @@ public void testJWTValidator() throws APISecurityException, EnforcerException { @Test public void testRevokedToken() throws APISecurityException, EnforcerException { - HashSet revokedTokens = new HashSet<>(); String revokedTokenJTI = "b8938768-23fd-4dec-8b70-bed45eb7c33d"; revokedTokens.add(revokedTokenJTI); diff --git a/helm-charts/crds/dp.wso2.com_apis.yaml b/helm-charts/crds/dp.wso2.com_apis.yaml index 1da03bb32..fc4e0a2a9 100644 --- a/helm-charts/crds/dp.wso2.com_apis.yaml +++ b/helm-charts/crds/dp.wso2.com_apis.yaml @@ -247,6 +247,7 @@ spec: could be REST, GraphQL, Async enum: - REST + - GraphQL type: string apiVersion: description: APIVersion is the version number of the API. @@ -259,8 +260,8 @@ spec: pattern: ^[/][a-zA-Z0-9~/_.-]*$ type: string definitionFileRef: - description: DefinitionFileRef contains the OpenAPI 3 or Swagger definition - of the API in a ConfigMap. + description: DefinitionFileRef contains the OpenAPI 3 or SDL file + in gzipped format. definition of the API in a ConfigMap. type: string definitionPath: default: /api-definition diff --git a/helm-charts/crds/dp.wso2.com_gqlroutes.yaml b/helm-charts/crds/dp.wso2.com_gqlroutes.yaml new file mode 100644 index 000000000..557436f2d --- /dev/null +++ b/helm-charts/crds/dp.wso2.com_gqlroutes.yaml @@ -0,0 +1,890 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: gqlroutes.dp.wso2.com +spec: + group: dp.wso2.com + names: + kind: GQLRoute + listKind: GQLRouteList + plural: gqlroutes + singular: gqlroute + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: GQLRoute is the Schema for the gqlroutes API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GQLRouteSpec defines the desired state of GQLRoute + properties: + backendRefs: + description: BackendRefs defines the backend(s) where matching requests + should be sent. + items: + description: HTTPBackendRef defines how a HTTPRoute should forward + an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed + if and only if the request is being forwarded to the backend + defined here. \n Support: Implementation-specific (For broader + support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that + must be completed during the request or response lifecycle. + HTTPRouteFilters are meant as an extension point to express + processing that may be done in Gateway implementations. + Some examples include request or response modification, + implementing authentication strategies, rate-limiting, and + traffic shaping. API guarantee/conformance is defined based + on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for + a filter that modifies request headers. \n Support: + Core" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter + that mirrors requests. Requests are sent to the specified + destination, but responses from that destination are + ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where + mirrored requests are sent. \n If the referent cannot + be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure + the \"ResolvedRefs\" condition on the Route status + is set to `status: False` and not configure this + backend in the underlying implementation. \n If + there is a cross-namespace reference to an *existing* + object that is not allowed by a ReferenceGrant, + the controller must ensure the \"ResolvedRefs\" + \ condition on the Route is set to `status: False`, + with the \"RefNotPermitted\" reason and not configure + this backend in the underlying implementation. \n + In either error case, the Message of the `ResolvedRefs` + Condition should be used to provide more detail + about the problem. \n Support: Extended for Kubernetes + Service \n Support: Implementation-specific for + any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". When + unspecified or empty string, core API group + is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to CNAME + DNS records that may live outside of the cluster + and as such are difficult to reason about in + terms of conformance. They also may not be safe + to forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a + type other than ExternalName) \n Support: Implementation-specific + (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the + backend. When unspecified, the local namespace + is inferred. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept the + reference. See the ReferenceGrant documentation + for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port + number to use for this resource. Port is required + when the referent is a Kubernetes Service. In + this case, the port number is the service port + number, not the target port. For other resources, + destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter + that responds to the request with an HTTP redirection. + \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used + in the value of the `Location` header in the response. + When empty, the hostname in the `Host` header of + the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify + the path of the incoming request. The modified path + is then used to construct the `Location` header. + When empty, the request path is used as-is. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not." + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value + of the `Location` header in the response. \n If + no port is specified, the redirect port MUST be + derived using the following rules: \n * If redirect + scheme is not-empty, the redirect port MUST be the + well-known port associated with the redirect scheme. + Specifically \"http\" to port 80 and \"https\" to + port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway + SHOULD be used. * If redirect scheme is empty, the + redirect port MUST be the Gateway Listener port. + \n Implementations SHOULD NOT add the port number + in the 'Location' header in the following cases: + \n * A Location header that will use HTTP (whether + that is determined via the Listener protocol or + the Scheme field) _and_ use port 80. * A Location + header that will use HTTPS (whether that is determined + via the Listener protocol or the Scheme field) _and_ + use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the + value of the `Location` header in the response. + When empty, the scheme of the request is used. \n + Scheme redirects can affect the port of the redirect, + for more information, refer to the documentation + for the port field of this filter. \n Note that + values may be added to this enum, implementations + must ensure that unknown values will not cause a + crash. \n Unknown values here must result in the + implementation setting the Accepted Condition for + the Route to `status: False`, with a Reason of `UnsupportedValue`. + \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to + be used in response. \n Note that values may be + added to this enum, implementations must ensure + that unknown values will not cause a crash. \n Unknown + values here must result in the implementation setting + the Accepted Condition for the Route to `status: + False`, with a Reason of `UnsupportedValue`. \n + Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n Support: + Extended" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. + As with other API fields, types are classified into + three conformance levels: \n - Core: Filter types and + their corresponding configuration defined by \"Support: + Core\" in this package, e.g. \"RequestHeaderModifier\". + All implementations must support core filters. \n - + Extended: Filter types and their corresponding configuration + defined by \"Support: Extended\" in this package, e.g. + \"RequestMirror\". Implementers are encouraged to support + extended filters. \n - Implementation-specific: Filters + that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior + across multiple implementations will be considered for + inclusion in extended or core conformance levels. Filter-specific + configuration for such filters is specified using the + ExtensionRef field. `Type` should be set to \"ExtensionRef\" + for custom filters. \n Implementers are encouraged to + define custom implementation types to extend the core + API with implementation-specific behavior. \n If a reference + to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have + been processed by that filter MUST receive a HTTP error + response. \n Note that values may be added to this enum, + implementations must ensure that unknown values will + not cause a crash. \n Unknown values here must result + in the implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter + that modifies a request during forwarding. \n Support: + Extended" + properties: + hostname: + description: "Hostname is the value to be used to + replace the Host header value during forwarding. + \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" would be modified to \"/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not." + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty string, + core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. + For example \"Service\". \n Defaults to \"Service\" when not + specified. \n ExternalName services can refer to CNAME DNS + records that may live outside of the cluster and as such are + difficult to reason about in terms of conformance. They also + may not be safe to forward to (see CVE-2021-25740 for more + information). Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a type other than + ExternalName) \n Support: Implementation-specific (Services + with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When + unspecified, the local namespace is inferred. \n Note that + when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace + to allow that namespace's owner to accept the reference. See + the ReferenceGrant documentation for details. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use + for this resource. Port is required when the referent is a + Kubernetes Service. In this case, the port number is the service + port number, not the target port. For other resources, destination + port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded + to the referenced backend. This is computed as weight/(sum + of all weights in this BackendRefs list). For non-zero values, + there may be some epsilon from the exact proportion defined + here depending on the precision an implementation supports. + Weight is not a percentage and the sum of weights does not + need to equal 100. \n If only one backend is specified and + it has a weight greater than 0, 100% of the traffic is forwarded + to that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight defaults + to 1. \n Support for this field varies based on the context + where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + type: array + hostnames: + description: Hostnames defines a set of hostname that should match + against the HTTP Host header to select a GQLRoute used to process + the request. + items: + description: "Hostname is the fully qualified domain name of a network + host. This matches the RFC 1123 definition of a hostname with + 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname + may be prefixed with a wildcard label (`*.`). The wildcard label + must appear by itself as the first label. \n Hostname can be \"precise\" + which is a domain name without the terminating dot of a network + host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain + name prefixed with a single wildcard label (e.g. `*.example.com`). + \n Note that as per RFC1035 and RFC1123, a *label* must consist + of lower case alphanumeric characters or '-', and must start and + end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) + that a Route wants to be attached to. Note that the referenced parent + resource needs to allow this for the attachment to be complete. + For Gateways, that means the Gateway needs to allow attachment from + Routes of this kind and namespace. \n The only kind of parent resource + with \"Core\" support is Gateway. This API may be extended in the + future to support additional kinds of parent resources such as one + of the route kinds. \n It is invalid to reference an identical parent + more than once. It is valid to reference multiple distinct sections + within the same parent resource, such as 2 Listeners within a Gateway. + \n It is possible to separately reference multiple distinct objects + that may be collapsed by an implementation. For example, some implementations + may choose to merge compatible Gateway Listeners together. If that + is the case, the list of routes attached to those resources should + also be merged. \n Note that for ParentRefs that cross namespace + boundaries, there are specific rules. Cross-namespace references + are only valid if they are explicitly allowed by something in the + namespace they are referring to. For example, Gateway has the AllowedRoutes + field, and ReferenceGrant provides a generic way to enable any other + kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). The only kind of parent resource with \"Core\" support + is Gateway. This API may be extended in the future to support + additional kinds of parent resources, such as HTTPRoute. \n The + API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: + Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core + (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document how/if + Port is interpreted. \n For the purpose of status, an attachment + is considered successful as long as the parent resource accepts + it partially. For example, Gateway listeners can restrict + which Routes can attach to them by Route kind, namespace, + or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this + Route, the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. \n Implementations MAY choose to support + attaching Routes to other resources. If that is the case, + they MUST clearly document how SectionName is interpreted. + \n When unspecified (empty string), this will reference the + entire resource. For the purpose of status, an attachment + is considered successful if at least one section in the parent + resource accepts it. For example, Gateway listeners can restrict + which Routes can attach to them by Route kind, namespace, + or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this + Route, the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of GraphQL resources, filters and actions. + items: + description: GQLRouteRules defines semantics for matching an GraphQL + request based on conditions (matches), processing it (filters), + and forwarding the request to an API object (backendRefs). + properties: + filters: + description: Filters define the filters that are applied to + requests that match this rule. + items: + description: GQLRouteFilter defines the filter to be applied + to a request. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + maxItems: 16 + type: array + matches: + description: Matches define conditions used for matching the + rule against incoming graphQL requests. Each match is independent, + i.e. this rule will be matched if **any** one of the matches + is satisfied. + items: + description: GQLRouteMatch defines the predicate used to match + requests to a given action. + properties: + path: + description: Path specifies a GQL request resource matcher. + type: string + type: + description: "Type specifies GQL typematcher. When specified, + this route will be matched only if the request has the + specified method. \n Support: Extended" + enum: + - QUERY + - MUTATION + type: string + type: object + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: GQLRouteStatus defines the observed state of GQLRoute + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm-charts/templates/serviceAccount/apk-cluster-role.yaml b/helm-charts/templates/serviceAccount/apk-cluster-role.yaml index 069d9c640..438240db4 100644 --- a/helm-charts/templates/serviceAccount/apk-cluster-role.yaml +++ b/helm-charts/templates/serviceAccount/apk-cluster-role.yaml @@ -113,6 +113,15 @@ rules: - apiGroups: ["dp.wso2.com"] resources: ["backendjwts/status"] verbs: ["get","patch","update"] + - apiGroups: ["dp.wso2.com"] + resources: ["gqlroutes"] + verbs: ["get","list","watch","update","delete","create"] + - apiGroups: ["dp.wso2.com"] + resources: ["gqlroutes/finalizers"] + verbs: ["update"] + - apiGroups: ["dp.wso2.com"] + resources: ["gqlroutes/status"] + verbs: ["get","patch","update"] - apiGroups: ["cp.wso2.com"] resources: ["applications"] verbs: ["get","list","watch","update","delete","create"]