diff --git a/adapter/internal/controlplane/eventPublisher.go b/adapter/internal/controlplane/eventPublisher.go index 23352a484..3f760656b 100644 --- a/adapter/internal/controlplane/eventPublisher.go +++ b/adapter/internal/controlplane/eventPublisher.go @@ -100,11 +100,30 @@ type API struct { APIHash string `json:"-"` } +// Headers contains the request and response header modifier information +type Headers struct { + RequestHeaders HeaderModifier `json:"requestHeaders"` + ResponseHeaders HeaderModifier `json:"responseHeaders"` +} + +// HeaderModifier contains header modifier values +type HeaderModifier struct { + AddHeaders []Header `json:"addHeaders"` + RemoveHeaders []string `json:"removeHeaders"` +} + +// Header contains the header information +type Header struct { + Name string `json:"headerName"` + Value string `json:"headerValue,omitempty"` +} + // Operation holds the path, verb, throttling and interceptor policy type Operation struct { - Path string `json:"path"` - Verb string `json:"verb"` - Scopes []string `json:"scopes"` + Path string `json:"path"` + Verb string `json:"verb"` + Scopes []string `json:"scopes"` + Headers Headers `json:"headers"` } // CORSPolicy hold cors configs diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index 02238d41c..8766f81c3 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -2672,6 +2672,10 @@ func prepareOperations(apiState *synchronizer.APIState) []controlplane.Operation if apiState.ProdHTTPRoute != nil && apiState.ProdHTTPRoute.HTTPRouteCombined != nil { for _, rule := range apiState.ProdHTTPRoute.HTTPRouteCombined.Spec.Rules { scopes := []string{} + requestAddHeaders := []controlplane.Header{} + responseAddHeaders := []controlplane.Header{} + requestRemoveHeaders := []string{} + responseRemoveHeaders := []string{} for _, filter := range rule.Filters { if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == "Scope" { scope, found := apiState.ProdHTTPRoute.Scopes[types.NamespacedName{Namespace: apiState.APIDefinition.ObjectMeta.Namespace, Name: string(filter.ExtensionRef.Name)}.String()] @@ -2679,7 +2683,34 @@ func prepareOperations(apiState *synchronizer.APIState) []controlplane.Operation scopes = append(scopes, scope.Spec.Names...) } } + + if filter.RequestHeaderModifier != nil { + requestHeaderModifier := filter.RequestHeaderModifier + for _, addHeader := range requestHeaderModifier.Add { + requestAddHeaders = append(requestAddHeaders, controlplane.Header{Name: string(addHeader.Name), Value: string(addHeader.Value)}) + } + for _, setHeader := range requestHeaderModifier.Set { + requestAddHeaders = append(requestAddHeaders, controlplane.Header{Name: string(setHeader.Name), Value: string(setHeader.Value)}) + } + for _, removeHeader := range requestHeaderModifier.Remove { + requestRemoveHeaders = append(requestRemoveHeaders, removeHeader) + } + } + + if filter.ResponseHeaderModifier != nil { + responseHeaderModifier := filter.ResponseHeaderModifier + for _, addHeader := range responseHeaderModifier.Add { + responseAddHeaders = append(responseAddHeaders, controlplane.Header{Name: string(addHeader.Name), Value: string(addHeader.Value)}) + } + for _, setHeader := range responseHeaderModifier.Set { + responseAddHeaders = append(responseAddHeaders, controlplane.Header{Name: string(setHeader.Name), Value: string(setHeader.Value)}) + } + for _, removeHeader := range responseHeaderModifier.Remove { + responseRemoveHeaders = append(responseRemoveHeaders, removeHeader) + } + } } + for _, match := range rule.Matches { path := "/" verb := "GET" @@ -2693,7 +2724,21 @@ func prepareOperations(apiState *synchronizer.APIState) []controlplane.Operation path = path + "*" } path = "^" + path + "$" - operations = append(operations, controlplane.Operation{Path: path, Verb: verb, Scopes: scopes}) + operations = append(operations, controlplane.Operation{ + Path: path, + Verb: verb, + Scopes: scopes, + Headers: controlplane.Headers{ + RequestHeaders: controlplane.HeaderModifier{ + AddHeaders: requestAddHeaders, + RemoveHeaders: requestRemoveHeaders, + }, + ResponseHeaders: controlplane.HeaderModifier{ + AddHeaders: responseAddHeaders, + RemoveHeaders: responseRemoveHeaders, + }, + }, + }) } } } diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index 5a18714d6..0a6d3d8a5 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -448,7 +448,7 @@ public class APIClient { sandboxRoutes.push(gqlRoute.metadata.name); } } - } else { + } else if apkConf.'type == API_TYPE_REST { foreach model:HTTPRoute httpRoute in apiArtifact.productionHttpRoutes { if httpRoute.spec.rules.length() > 0 { productionRoutes.push(httpRoute.metadata.name); @@ -538,7 +538,7 @@ public class APIClient { apiArtifact.sandboxGqlRoutes.push(gqlRoute); } } - } else { + } else if apkConf.'type == API_TYPE_REST { model:HTTPRoute httpRoute = { metadata: { @@ -558,6 +558,8 @@ public class APIClient { apiArtifact.sandboxHttpRoutes.push(httpRoute); } } + } else { + return e909018("Invalid API Type specified"); } return; @@ -784,14 +786,23 @@ public class APIClient { } else { return e909022("Provided Type currently not supported for GraphQL APIs.", error("Provided Type currently not supported for GraphQL APIs.")); } + } else if apkConf.'type == API_TYPE_REST { + { + model:HTTPRouteRule httpRouteRule = { + matches: self.retrieveHTTPMatches(apkConf, operation, organization), + backendRefs: self.retrieveGeneratedBackend(apkConf, endpointToUse, endpointType), + filters: self.generateFilters(apiArtifact, apkConf, endpointToUse, operation, endpointType, organization) + }; + return httpRouteRule; + } } else { - model:HTTPRouteRule httpRouteRule = {matches: self.retrieveHTTPMatches(apkConf, operation, organization), backendRefs: self.retrieveGeneratedBackend(apkConf, endpointToUse, endpointType), filters: self.generateFilters(apiArtifact, apkConf, endpointToUse, operation, endpointType, organization)}; - return httpRouteRule; + return e909018("Invalid API Type specified"); } } else { return (); } - } on fail var e { + } + on fail var e { log:printError("Internal Error occured", e); return e909022("Internal Error occured", e); } @@ -800,46 +811,80 @@ public class APIClient { private isolated function generateFilters(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint endpoint, APKOperations operation, string endpointType, commons:Organization organization) returns model:HTTPRouteFilter[] { model:HTTPRouteFilter[] routeFilters = []; string generatedPath = self.generatePrefixMatch(endpoint, operation); - model:HTTPRouteFilter replacePathFilter = {'type: "URLRewrite", urlRewrite: {path: {'type: "ReplaceFullPath", replaceFullPath: generatedPath}}}; + model:HTTPRouteFilter replacePathFilter = { + 'type: "URLRewrite", + urlRewrite: { + path: { + 'type: "ReplaceFullPath", + replaceFullPath: generatedPath + } + } + }; routeFilters.push(replacePathFilter); APIOperationPolicies? operationPoliciesToUse = (); - if (apkConf.apiPolicies is APIOperationPolicies) { - operationPoliciesToUse = apkConf.apiPolicies; + APIOperationPolicies? operationPolicies = apkConf.apiPolicies; + if (operationPolicies is APIOperationPolicies && operationPolicies != {}) { + if operationPolicies.request is APKOperationPolicy[] || operationPolicies.response is APKOperationPolicy[] { + operationPoliciesToUse = apkConf.apiPolicies; + } } else { operationPoliciesToUse = operation.operationPolicies; } if operationPoliciesToUse is APIOperationPolicies { - APKOperationPolicy[]? request = operationPoliciesToUse.request; + APKOperationPolicy[]? requestPolicies = operationPoliciesToUse.request; + APKOperationPolicy[]? responsePolicies = operationPoliciesToUse.response; + + if requestPolicies is APKOperationPolicy[] && requestPolicies.length() > 0 { + model:HTTPRouteFilter headerModifierFilter = {'type: "RequestHeaderModifier"}; + headerModifierFilter.requestHeaderModifier = self.extractHttpHeaderFilterData(requestPolicies, organization); + routeFilters.push(headerModifierFilter); + } + if responsePolicies is APKOperationPolicy[] && responsePolicies.length() > 0 { + model:HTTPRouteFilter headerModifierFilter = {'type: "ResponseHeaderModifier"}; + headerModifierFilter.responseHeaderModifier = self.extractHttpHeaderFilterData(responsePolicies, organization); + routeFilters.push(headerModifierFilter); + } } return routeFilters; } isolated function extractHttpHeaderFilterData(APKOperationPolicy[] operationPolicy, commons:Organization organization) returns model:HTTPHeaderFilter { + model:HTTPHeader[] addPolicies = []; model:HTTPHeader[] setPolicies = []; string[] removePolicies = []; foreach APKOperationPolicy policy in operationPolicy { - string policyName = policy.policyName; - - record {}? policyParameters = policy.parameters; - if (policyParameters is record {}) { - if (policyName == "addHeader") { - model:HTTPHeader httpHeader = { - name: policyParameters.get("headerName"), - value: policyParameters.get("headerValue") - }; - setPolicies.push(httpHeader); - } - if (policyName == "removeHeader") { - string httpHeader = policyParameters.get("headerName"); - removePolicies.push(httpHeader); + if policy is HeaderModifierPolicy { + HeaderModifierPolicyParameters policyParameters = policy.parameters; + match policy.policyName { + AddHeaders => { + ModifierHeader[] addHeaders = policyParameters.headers; + foreach ModifierHeader header in addHeaders { + addPolicies.push(header); + } + } + SetHeaders => { + ModifierHeader[] setHeaders = policyParameters.headers; + foreach ModifierHeader header in setHeaders { + setPolicies.push(header); + } + } + RemoveHeaders => { + string[] removeHeaders = policyParameters.headers; + foreach string header in removeHeaders { + removePolicies.push(header); + } + } } } } model:HTTPHeaderFilter headerModifier = {}; - if (setPolicies != []) { + if addPolicies != [] { + headerModifier.add = addPolicies; + } + if setPolicies != [] { headerModifier.set = setPolicies; } - if (removePolicies != []) { + if removePolicies != [] { headerModifier.remove = removePolicies; } return headerModifier; @@ -1204,7 +1249,7 @@ public class APIClient { foreach APKOperationPolicy policy in policies { string policyName = policy.policyName; if policy.parameters is record {} { - if (policyName == "Interceptor") { + if (policyName == Interceptor) { InterceptorPolicy interceptorPolicy = check policy.cloneWithType(InterceptorPolicy); InterceptorPolicy_parameters parameters = interceptorPolicy?.parameters; EndpointConfiguration endpointConfig = {endpoint: parameters.backendUrl ?: "", certificate: {secretName: parameters.tlsSecretName, secretKey: parameters.tlsSecretKey}}; @@ -1223,12 +1268,12 @@ public class APIClient { }; } policyReferences.push(interceptorReference); - } else if (policyName == "BackendJwt") { + } else if (policyName == BackendJwt) { BackendJWTPolicy backendJWTPolicy = check policy.cloneWithType(BackendJWTPolicy); model:BackendJWT backendJwt = self.retrieveBackendJWTPolicy(apkConf, apiArtifact, backendJWTPolicy, operations, organization); apiArtifact.backendJwt = backendJwt; policyReferences.push({name: backendJwt.metadata.name}); - } else { + } else if policyName != AddHeaders && policyName != SetHeaders && policyName != RemoveHeaders { return e909052(error("Incorrect API Policy name provided.")); } } @@ -1593,7 +1638,6 @@ public class APIClient { private isolated function validateAndRetrieveAPKConfiguration(json apkconfJson) returns APKConf|commons:APKError? { do { runtimeapi:APKConfValidationResponse validationResponse = check apkConfValidator.validate(apkconfJson.toJsonString()); - if validationResponse.isValidated() { APKConf apkConf = check apkconfJson.cloneWithType(APKConf); map errors = {}; diff --git a/runtime/config-deployer-service/ballerina/resources/apk-conf-schema.yaml b/runtime/config-deployer-service/ballerina/resources/apk-conf-schema.yaml index 9c9847c50..1a8814ad9 100644 --- a/runtime/config-deployer-service/ballerina/resources/apk-conf-schema.yaml +++ b/runtime/config-deployer-service/ballerina/resources/apk-conf-schema.yaml @@ -256,11 +256,15 @@ components: oneOf: - $ref: "#/components/schemas/InterceptorPolicy" - $ref: "#/components/schemas/BackendJWTPolicy" + - $ref: "#/components/schemas/HeaderModifierPolicy" discriminator: propertyName: "policyName" mapping: BackendJwt: "#/components/schemas/BackendJWTPolicy" Interceptor: "#/components/schemas/InterceptorPolicy" + AddHeaders: "#/components/schemas/HeaderModifierPolicy" + SetHeaders: "#/components/schemas/HeaderModifierPolicy" + RemoveHeadersHeaders: "#/components/schemas/HeaderModifierPolicy" BaseOperationPolicy: title: API Operation Policy required: @@ -269,6 +273,12 @@ components: properties: policyName: type: string + enum: + - AddHeaders + - RemoveHeaders + - SetHeaders + - Interceptor + - BackendJwt policyVersion: type: string default: "v1" @@ -493,6 +503,24 @@ components: required: - enabled additionalProperties: false + HeaderModifierPolicy: + title: Header Modifier Parameters + type: object + properties: + headers: + type: array + items: + oneOf: + - $ref: "#/components/schemas/Header" + - type: string + additionalProperties: false + Header: + type: object + properties: + name: + type: string + value: + type: string CustomClaims: type: object required: diff --git a/runtime/config-deployer-service/ballerina/tests/resources/apk-schema.json b/runtime/config-deployer-service/ballerina/tests/resources/apk-schema.json index 9eb12e129..d7c57998f 100644 --- a/runtime/config-deployer-service/ballerina/tests/resources/apk-schema.json +++ b/runtime/config-deployer-service/ballerina/tests/resources/apk-schema.json @@ -311,7 +311,14 @@ "properties": { "policyName": { "type": "string", - "description": "The name of the operation policy." + "description": "The name of the operation policy.", + "enum": [ + "AddHeaders", + "RemoveHeaders", + "SetHeaders", + "Interceptor", + "BackendJwt" + ] }, "policyVersion": { "type": "string", @@ -330,6 +337,9 @@ }, { "$ref": "#/schemas/BackendJWTProperties" + }, + { + "$ref": "#/schemas/HeaderModifierProperties" } ] } @@ -652,6 +662,35 @@ }, "additionalProperties": false }, + "HeaderModifierProperties": { + "title": "Header Modifier Parameters", + "type": "object", + "properties": { + "headers": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/schemas/Header" + }, + { + "type": "string" + } + ] + } + } + }, + "additionalProperties": false + }, + "Header": { + "type": "object", + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, "CustomClaims": { "type": "object", "required": [ diff --git a/runtime/config-deployer-service/ballerina/types.bal b/runtime/config-deployer-service/ballerina/types.bal index 41fa29730..1f59fd5d3 100644 --- a/runtime/config-deployer-service/ballerina/types.bal +++ b/runtime/config-deployer-service/ballerina/types.bal @@ -1,53 +1,85 @@ +import ballerina/constraint; // AUTO-GENERATED FILE. // This file is auto-generated by the Ballerina OpenAPI tool. - import ballerina/http; -import ballerina/constraint; +# Configuration for a successful response. +# +# + body - The body of the response containing any data. public type OkAnydata record {| *http:Ok; anydata body; |}; +# Configuration for a Not Found error response. +# +# + body - The body of the response containing error details. public type NotFoundError record {| *http:NotFound; Error body; |}; +# Configuration for a Bad Request error response. +# +# + body - The body of the response containing error details. public type BadRequestError record {| *http:BadRequest; Error body; |}; +# Configuration for an Accepted response. +# +# + body - The body of the response containing a string. public type AcceptedString record {| *http:Accepted; string body; |}; +# Configuration for Internal Server Error response. +# +# + body - The body of the response containing error details. public type InternalServerErrorError record {| *http:InternalServerError; Error body; |}; +# Configuration for a list of errors. +# +# + code - The code representing the error. +# + message - Description about individual errors occurred. +# + description - A detailed description about the error message. public type ErrorListItem record { string code; - # Description about individual errors occurred string message; - # A detail description about the error message. string description?; }; +# Configuration for Endpoint Security. +# +# + enabled - Indicates whether the endpoint security is enabled. +# + securityType - Configuration for the basic endpoint security. public type EndpointSecurity record { boolean enabled?; BasicEndpointSecurity securityType?; }; +# Configuration for Custom Claims. +# +# + claim - The name of the custom claim. +# + value - The value of the custom claim. +# + type - The type of the custom claim. public type CustomClaims record { string claim; string value; string 'type = "string"; }; +# Configuration for a K8s Service. +# +# + name - The name of the K8s service. +# + namespace - The namespace in which the service is defined. +# + port - The port on which the service is exposed. +# + protocol - The protocol used by the service (e.g., "TCP", "UDP"). public type K8sService record { string name?; string namespace?; @@ -55,23 +87,38 @@ public type K8sService record { string protocol?; }; +# Configuration for Rate Limiting. +# +# + requestsPerUnit - Number of requests allowed per specified unit of time. +# + unit - Unit of time for the rate limit. public type RateLimit record { - # Number of requests allowed per specified unit of time int requestsPerUnit; - # Unit of time string unit; }; +# Configuration for Basic Endpoint Security. +# +# + secretName - The name of the secret containing the credentials. +# + userNameKey - The key to retrieve the username from the secret. +# + passwordKey - The key to retrieve the password from the secret. public type BasicEndpointSecurity record { string secretName?; string userNameKey?; string passwordKey?; }; +# Configuration for APK Operations. +# +# + target - The target endpoint for the operation. +# + verb - The HTTP verb for the operation. +# + secured - Indicates if authentication is applied to the operation(true/false). +# + endpointConfigurations - Configuration for the endpoint. +# + operationPolicies - Policies to be applied for the operation. +# + rateLimit - Rate limiting configuration for the operation. +# + scopes - Scopes required for the operation. public type APKOperations record { string target?; string verb?; - # Authentication mode for resource (true/false) boolean secured?; EndpointConfigurations endpointConfigurations?; APIOperationPolicies operationPolicies?; @@ -79,31 +126,54 @@ public type APKOperations record { string[] scopes?; }; -public type APKOperationPolicy InterceptorPolicy|BackendJWTPolicy; +# Common type for operation policies. +public type APKOperationPolicy InterceptorPolicy|BackendJWTPolicy|HeaderModifierPolicy; +# Configuration for API deployment using the apk-conf file. +# +# + apkConfiguration - APK Configuration (apk-conf) file. +# + definitionFile - API definition (OAS/Graphql/WebSocket). public type DeployApiBody record { - # apk-configuration file record {byte[] fileContent; string fileName;} apkConfiguration?; - # api definition (OAS/Graphql/WebSocket) record {byte[] fileContent; string fileName;} definitionFile?; }; +# Configuration of APK Operation Policies. +# +# + request - List of policies to be applied on the request. +# + response - List of policies to be applied on the response. public type APIOperationPolicies record { APKOperationPolicy[] request?; APKOperationPolicy[] response?; }; +# Additional properties for APK configuration. +# +# + name - The name of the additional property. +# + value - The value of the additional property. public type APKConf_additionalProperties record { string name?; string value?; }; +# Configuration of Resiliency settings. +# +# + circuitBreaker - Configuration for the CircuitBreaker. +# + timeout - Configuration for the Timeout. +# + retryPolicy - Configuration for the RetryPolicy. public type Resiliency record { CircuitBreaker circuitBreaker?; Timeout timeout?; RetryPolicy retryPolicy?; }; +# Configuration of CircuitBreaker settings. +# +# + maxConnectionPools - The maximum number of connection pools allowed. +# + maxConnections - The maximum number of connections allowed. +# + maxPendingRequests - The maximum number of pending requests allowed. +# + maxRequests - The maximum number of requests allowed. +# + maxRetries - The maximum number of retries allowed. public type CircuitBreaker record { int maxConnectionPools?; int maxConnections?; @@ -112,13 +182,37 @@ public type CircuitBreaker record { int maxRetries?; }; +# Common type for all authentication types. public type AuthenticationRequest OAuth2Authentication|APIKeyAuthentication|MTLSAuthentication|JWTAuthentication; +# Configuration for production and sandbox endpoints. +# +# + production - Production endpoint. +# + sandbox - Sandbox endpoint. public type EndpointConfigurations record { EndpointConfiguration production?; EndpointConfiguration sandbox?; }; +# Configuration for production and sandbox endpoints. +# +# + endpoint - The endpoint which can be a string or a Kubernetes service. +# + endpointSecurity - The security configuration for the endpoint. +# + certificate - The certificate configuration for the endpoint. +# + resiliency - The resiliency configuration for the endpoint. +public type EndpointConfiguration record { + string|K8sService endpoint; + EndpointSecurity endpointSecurity?; + Certificate certificate?; + Resiliency resiliency?; +}; + +# Configuration of OAuth2 Authentication type. +# +# + required - Is OAuth2 authentication mandatory/optional. +# + sendTokenToUpstream - Enables sending the value to the upstream. +# + headerName - Header name for sending the OAuth2 value. +# + headerEnable - Enable sending OAuth2 as a header. public type OAuth2Authentication record {| *Authentication; string required = "mandatory"; @@ -127,6 +221,13 @@ public type OAuth2Authentication record {| boolean headerEnable = true; |}; +# Configuration of JWT Authentication type. +# +# + required - Is JWT authentication mandatory/optional. +# + sendTokenToUpstream - Enables sending the value to the upstream. +# + headerName - Header name for sending the JWT value. +# + headerEnable - Enable sending the JWT as a header. +# + audience - List of audiences for the JWT. public type JWTAuthentication record {| *Authentication; string required = "mandatory"; @@ -136,39 +237,49 @@ public type JWTAuthentication record {| string[] audience = []; |}; +# Configuration of timeout. +# +# + downstreamRequestIdleTimeout - field description +# + upstreamResponseTimeout - field description public type Timeout record { int downstreamRequestIdleTimeout?; int upstreamResponseTimeout?; }; +# Configuration needed to generate K8s resources. +# +# + apkConfiguration - APK Configuration (apk-conf) file. +# + definitionFile - API definition (OAS/Graphql/WebSocket) +# + apiType - Type of API. public type GenerateK8sResourcesBody record { - # apk-configuration file record {byte[] fileContent; string fileName;} apkConfiguration?; - # api definition (OAS/Graphql/WebSocket) record {byte[] fileContent; string fileName;} definitionFile?; - # Type of API string apiType?; }; +# Configuration of an error. +# +# + code - Error code. +# + message - Error message. +# + description - A detailed description about the error message. +# + moreInfo - Preferably a URL with more details about the error. +# + 'error - A list containing multiple errors if any. For example, list out validation errors by each field. public type Error record { int code; - # Error message. string message; - # A detail description about the error message. string description?; - # Preferably an url with more details about the error. string moreInfo?; - # If there are more than one error list them out. - # For example, list out validation errors by each field. ErrorListItem[] 'error?; }; +# Definition body of the API definition. +# +# + definition - API definition (OAS/Graphql/WebSocket). +# + url - URL of API definition. +# + apiType - Type of API. public type DefinitionBody record { - # api definition (OAS/Graphql/WebSocket) record {byte[] fileContent; string fileName;} definition?; - # url of api definition string url?; - # Type of API string apiType?; }; @@ -191,24 +302,70 @@ public type CORSConfiguration record { string[] accessControlExposeHeaders?; }; +# Common configuration of all policies. +# +# + policyName - Name of the policy. +# + policyVersion - Version of the policy. +# + policyId - ID of the policy. public type BaseOperationPolicy record { - string policyName; + PolicyName policyName; string policyVersion = "v1"; string policyId?; }; +# Enum for all possible policy types. +public enum PolicyName { + BackendJwt, + Interceptor, + AddHeaders, + SetHeaders, + RemoveHeaders +} + +# Configuration for authentication types. +# +# + authType - Type of authentication. +# + enabled - Enable/disable the specific authentication type. public type Authentication record {| string authType?; boolean enabled = true; |}; +# Header modification configuration for an operation. +# +# + parameters - Contains header name and value of the header. +public type HeaderModifierPolicy record { + *BaseOperationPolicy; + HeaderModifierPolicyParameters parameters; +}; + +# Interceptor policy configuration for an operation. +# +# + parameters - Contains interceptor policy parameters public type InterceptorPolicy record { *BaseOperationPolicy; InterceptorPolicy_parameters parameters?; }; +# APK configuration for a given API +# +# + id - UUID of the API. +# + name - Name of the API. +# + basePath - Context of the API. +# + version - Version of the API. +# + 'type - Type of the API. ex: REST, GraphQL +# + definitionPath - Endpoint to expose API Definition. +# + defaultVersion - Is this the default version of the API. +# + subscriptionValidation - Is subscription validation enabled for the API. +# + environment - Environment of the API. +# + endpointConfigurations - Sandbox and production endpoint configurations of the API +# + operations - List of operations for this API. +# + apiPolicies - Policies like interceptor to be added to the entire API. +# + rateLimit - Rate limiting configuration for the API. +# + authentication - Authentication configuration for the API. +# + additionalProperties - Map of custom properties of API +# + corsConfiguration - CORS Configuration of API public type APKConf record { - # UUID of the API string id?; @constraint:String {maxLength: 60, minLength: 1} string name; @@ -217,25 +374,55 @@ public type APKConf record { @constraint:String {maxLength: 30, minLength: 1} string version; string 'type = API_TYPE_REST; - # Endpoint to expose API Definition string definitionPath?; - # Is this the default version of the API boolean defaultVersion = false; - # Is subscription validation enabled for the API boolean subscriptionValidation = false; - # Environment of the API string environment?; EndpointConfigurations endpointConfigurations?; APKOperations[] operations?; APIOperationPolicies apiPolicies?; RateLimit rateLimit?; AuthenticationRequest[] authentication?; - # Map of custom properties of API APKConf_additionalProperties[] additionalProperties?; - # CORS Configuration of API CORSConfiguration corsConfiguration?; }; +# Configuration for header modifiers as received from the apk-conf file. +# +# + headers - Headers to be added, set or removed. +public type HeaderModifierPolicyParameters record {| + ModifierHeader[]|string[] headers; +|}; + +# Configuration containing the different headers. +# +# + addHeaders - Headers to be added. +# + setHeaders - Headers to be set. +# + removeHeaders - Headers to be removed. +public type HeaderModifierFilterParameters record {| + ModifierHeader[] addHeaders; + ModifierHeader[] setHeaders; + string[] removeHeaders; +|}; + +# Configuration for headers. +# +# + name - The name of the header. +# + value - The value of the header. +public type ModifierHeader record {| + string name; + string value; +|}; + +# Configuration for Interceptor Policy parameters. +# +# + backendUrl - Backend URL of the interceptor service. +# + headersEnabled - Indicates whether request/response headers should be sent to the interceptor service. +# + bodyEnabled - Indicates whether request/response body should be sent to the interceptor service. +# + trailersEnabled - Indicates whether request/response trailers should be sent to the interceptor service. +# + contextEnabled - Indicates whether context details should be sent to the interceptor service. +# + tlsSecretName - (Optional parameter) The reference name for K8s ConfigMap with TLS information. +# + tlsSecretKey - (Optional parameter)The TLS key name. public type InterceptorPolicy_parameters record {| string backendUrl?; boolean headersEnabled?; @@ -246,6 +433,13 @@ public type InterceptorPolicy_parameters record {| string tlsSecretKey?; |}; +# Configuration for Backend JWT Policy parameters. +# +# + encoding - The encoding method for the JWT. +# + signingAlgorithm - The algorithm used for signing the JWT. +# + header - The header used for the JWT. +# + tokenTTL - The time-to-live (TTL) for the token. +# + customClaims - Custom claims to be included in the JWT. public type BackendJWTPolicy_parameters record {| string encoding?; string signingAlgorithm?; @@ -254,24 +448,32 @@ public type BackendJWTPolicy_parameters record {| CustomClaims[] customClaims?; |}; -public type EndpointConfiguration record { - string|K8sService endpoint; - EndpointSecurity endpointSecurity?; - Certificate certificate?; - Resiliency resiliency?; -}; - +# Configuration for Backend JWT Policy. +# +# + parameters - The parameters for the backend JWT policy. public type BackendJWTPolicy record { *BaseOperationPolicy; BackendJWTPolicy_parameters parameters?; }; +# Configuration for Retry Policy. +# +# + count - The number of retry attempts. +# + baseIntervalMillis - The base interval between retries in milliseconds. +# + statusCodes - The status codes that trigger a retry. public type RetryPolicy record { int count?; int baseIntervalMillis?; int[] statusCodes?; }; +# Configuration for API Key Auth Type +# +# + sendTokenToUpstream - Enables sending the API Key to upstream. +# + headerName - Name of APIKey header. +# + queryParamName - Name of APIKey query parameter. +# + headerEnable - Enable sending API Key in header. +# + queryParamEnable - Enable sending API Key as a query param. public type APIKeyAuthentication record {| *Authentication; boolean sendTokenToUpstream = false; @@ -291,11 +493,19 @@ public type MTLSAuthentication record {| ConfigMapRef[] certificates; |}; +# Configuration for K8s Secret. +# +# + secretName - Name of Secret. +# + secretKey - Key containing the relevant value. public type Certificate record { string secretName?; string secretKey?; }; +# Configuration for K8s ConfigMap. +# +# + name - Name of ConfigMap. +# + key - Key containing the relevant value. public type ConfigMapRef record { string name; string key; diff --git a/runtime/config-deployer-service/docker/config-deployer/conf/apk-schema.json b/runtime/config-deployer-service/docker/config-deployer/conf/apk-schema.json index 870b4a389..09149e9f5 100644 --- a/runtime/config-deployer-service/docker/config-deployer/conf/apk-schema.json +++ b/runtime/config-deployer-service/docker/config-deployer/conf/apk-schema.json @@ -356,7 +356,14 @@ "properties": { "policyName": { "type": "string", - "description": "The name of the operation policy." + "description": "The name of the operation policy.", + "enum": [ + "AddHeaders", + "RemoveHeaders", + "SetHeaders", + "Interceptor", + "BackendJwt" + ] }, "policyVersion": { "type": "string", @@ -375,6 +382,9 @@ }, { "$ref": "#/schemas/BackendJWTProperties" + }, + { + "$ref": "#/schemas/HeaderModifierProperties" } ] } @@ -697,6 +707,35 @@ }, "additionalProperties": false }, + "HeaderModifierProperties": { + "title": "Header Modifier Parameters", + "type": "object", + "properties": { + "headers": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/schemas/Header" + }, + { + "type": "string" + } + ] + } + } + }, + "additionalProperties": false + }, + "Header": { + "type": "object", + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, "CustomClaims": { "type": "object", "required": [ diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/request_and_response_filters.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/request_and_response_filters.apk-conf new file mode 100644 index 000000000..a41f67efc --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/request_and_response_filters.apk-conf @@ -0,0 +1,64 @@ +--- +id: "api-with-request-and-response-filters" +name: "EmployeeServiceAPI" +basePath: "/request-and-response-filters" +version: "3.14" +type: "REST" +defaultVersion: false +endpointConfigurations: + production: + endpoint: "http://backend:80/anything" +operations: + - target: "/employee" + verb: "GET" + secured: false + scopes: [] + operationPolicies: + request: + - policyName: AddHeaders + policyVersion: v1 + parameters: + headers: + - name: "Test-Request-Header" + value: "Test-Value" + - policyName: SetHeaders + policyVersion: v1 + parameters: + headers: + - name: "Set-Request-Header" + value: "Test-Value" + - policyName: RemoveHeaders + policyVersion: v1 + parameters: + headers: + - "Authorization" + response: + - policyName: AddHeaders + policyVersion: v1 + parameters: + headers: + - name: "Test-Response-Header" + value: "Test-Value" + - policyName: SetHeaders + policyVersion: v1 + parameters: + headers: + - name: "Set-Response-Header" + value: "Test-Value" + - policyName: RemoveHeaders + policyVersion: v1 + parameters: + headers: + - "content-type" + - target: "/employee" + verb: "POST" + secured: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "PUT" + secured: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + secured: true + scopes: [] diff --git a/test/cucumber-tests/src/test/resources/tests/api/HTTPRouteFilters.feature b/test/cucumber-tests/src/test/resources/tests/api/HTTPRouteFilters.feature new file mode 100644 index 000000000..e8138c5b5 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/HTTPRouteFilters.feature @@ -0,0 +1,28 @@ +Feature: Test HTTPRoute Filter Header Modifier functionality + Scenario: Test request and response header modification functionality + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/request_and_response_filters.apk-conf" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + | Authorization | bearer ${accessToken} | + And I send "GET" request to "https://default.gw.wso2.com:9095/request-and-response-filters/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + | 401 | + And the response body should contain "\"Test-Request-Header\": \"Test-Value\"" + And the response body should contain "\"Set-Request-Header\": \"Test-Value\"" + And the response body should not contain "\"Authorization\"" + Then the response headers contains key "Set-Response-Header" and value "Test-Value" + Then the response headers contains key "Test-Response-Header" and value "Test-Value" + And the response headers should not contain + | content-type | + + Scenario: Undeploy the API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "api-with-request-and-response-filters" + Then the response status code should be 202 + + \ No newline at end of file