Chapter 11. The 3scale API Management WebAssembly module
Chapter 11. The 3scale API Management WebAssembly module The threescale-wasm-auth module is a WebAssembly module that plugs into Service Mesh and enables it to authorize the incoming requests with Red Hat 3scale API Management. It expands on Service Mesh capabilities and offers full API management capabilities, including authentication, analytics, and billing for your microservices. Service Mesh focuses on the infrastructure layer with features like traffic management, service discovery, load balancing, and security. API management focuses on creating, publishing, and managing APIs. Together, Service Mesh and 3scale can improve the reliability, scalability, security, and performance of your microservices and APIs. Note The threescale-wasm-auth module runs on integrations of 3scale 2.11 or later with Red Hat OpenShift Service Mesh 2.1.0 or later. Prerequisites A 3scale account with administrator privileges. A Service Mesh 2.4 or later installation. Service Mesh 2.3 currently does not work due to OSSM-3647 . For Service Mesh 2.1 and 2.2, refer to Using the 3scale API Management WebAssembly module . An application running within Service Mesh. Use the Bookinfo example application . Cluster administrators on OpenShift Container Platform (OCP) can configure the threescale-wasm-auth module to authorize HTTP requests to 3scale through the WasmPlugin custom resource. The Service Mesh then injects the module into sidecars, exposing the host services and allows you to use the module to process proxy requests. From a 3scale perspective, the threescale-wasm-auth module serves as a gateway and replaces APIcast when integrating with Service Mesh. This means some of the APIcast features cannot be used, notably policies and staging and production environments. 11.1. Deploying the Bookinfo application to Service Mesh You can use the example Bookinfo application from Service Mesh to demonstrate the procedure of configuring Service Mesh with 3scale. Procedure Deploy Bookinfo application: See Bookinfo example application . Verify that the application is available: USD export GATEWAY_URL=USD(oc -n istio-system get route istio-ingressgateway -o jsonpath='{.spec.host}') USD curl -I "http://USDGATEWAY_URL/productpage" HTTP/1.1 200 OK 11.2. Creating a product in 3scale API Management A product is a customer-facing API that can redirect or use multiple internal APIs, called backends. When using 3scale with Service Mesh, backends are not used. The link between a product and the private base URL is made in the mesh. For this reason, only the product is needed. Procedure Create a new product, application plan, and application. See Creating new products to test API calls . Change deployment to Istio : Navigate to [Your_product_name] > Integration > Settings . Change Deployment to Istio . Click Update Product to update configuration. Promote the configuration: Navigate to [Your_product_name] > Integration > Configuration . Click Update Configuration . 11.3. Connecting 3scale API Management with Service Mesh Important Create the ServiceEntry custom resource (CR) and DestinationRule CR in the service-mesh/istio-system namespace or the bookinfo namespace. It should be in a namespace containing ServiceMeshControlPlane. To reach 3scale from Service Mesh you must configure both tenant and backend URLs as an external service through the ServiceEntry CR and the DestinationRule CR. This enables the threescale-wasm-auth module to access both backend, which handles request authorization, and system, from which the product configuration is fetched. 11.3.1. Adding 3scale API Management URLs to Service Mesh ServiceEntry is needed to allow requests to the service from within the Service Mesh, and DestinationRule is there to configure a secure connection for 3scale services. 11.3.1.1. Adding a tenant URL to Service Mesh Procedure Collect system tenant URLs: This is a URL of the 3scale Admin Portal you used to create the product. Create ServiceEntry for system: oc apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: <service_entry_threescale_system> spec: hosts: - <system_hostname> ports: - number: 443 name: https protocol: HTTPS location: MESH_EXTERNAL resolution: DNS EOF Create DestinationRule for system: oc apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: <destination_rule_threescale_system> spec: host: <system_hostname> trafficPolicy: tls: mode: SIMPLE sni: <system_hostname> EOF Additional resources Understanding service entries Understanding destination rules 11.4. Adding backend URL to Service Mesh By incorporating the 3scale backend URL into your Service Mesh setup, you can establish a secure communication channel between your microservices and the 3scale backend. The integration enables the implementation of authentication, analytics, and billing features for managing APIs in the Service Mesh environment. The backend can be accessed externally using the exposed route and internally using the OpenShift service. 11.4.1. Using 3scale API Management on a different cluster from Service Mesh Procedure Collect backend URLs: For 3scale Hosted, the backend URL is: su1.3scale.net For 3scale On-premises, fetch the URL using the following command: USD oc get -n <3scale_namespace> route backend --template="{{.spec.host}}" Create ServiceEntry for backend: oc apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: <service_entry_threescale_backend> spec: hosts: - <backend_hostname> ports: - number: 443 name: https protocol: HTTPS location: MESH_EXTERNAL resolution: DNS EOF Create DestinationRule for backend: oc apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: <destination_rule_threescale_backend> spec: host: <backend_hostname> trafficPolicy: tls: mode: SIMPLE sni: <backend_hostname> EOF 11.5. Using 3scale API Management on the same cluster as Service Mesh Note The following procedure is an alternative to Adding backend URL to service mesh . To have the threescale-wasm-auth module authorize requests against 3scale, the module must have access to 3scale services. You can do this within Red Hat OpenShift Service Mesh by applying an external ServiceEntry object and a corresponding DestinationRule object for TLS configuration to use the HTTPS protocol. The custom resources (CRs) set up the service entries and destination rules for secure access from within Service Mesh to 3scale for the backend and system components of the Service Management API and the Account Management API. The Service Management API receives queries for the authorization status of each request. The Account Management API provides API management configuration settings for your services. Procedure Create ServiceEntry for Backend: oc apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: <service_entry_threescale_backend> spec: hosts: - backend-listener.<3scale_namespace>.svc.cluster.local ports: - number: 80 name: http protocol: HTTP location: MESH_EXTERNAL resolution: DNS EOF Create DestinationRule for Backend: oc apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: <destination_rule_threescale_backend> spec: host: backend-listener.<3scale_namespace>.svc.cluster.local EOF 11.6. Creating a WasmPlugin custom resource Service Mesh provides a custom resource definition (CRD) to specify and apply Proxy-WASM extensions to sidecar proxies, known as the WasmPlugin . Service Mesh applies the custom resource (CR) to the set of workloads that require HTTP API management with 3scale. Procedure Identify the OpenShift Container Platform (OCP) namespace, for example the bookinfo project, on your Service Mesh deployment that you will apply this module to. Obtain pull secret with registry.redhat.io credentials. Create the new pull secret resource in same namespace as the WasmPlugin . You must declare the namespace where the threescale-wasm-auth module is deployed, alongside a selector to identify the set of applications the module will apply to. The following example is the YAML format for the CR for threescale-wasm-auth module: apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> namespace: <namespace> spec: url: oci://registry.redhat.io/3scale-amp2/3scale-auth-wasm-rhel8:0.0.3 imagePullSecret: <pull_secret_resource> phase: AUTHZ priority: 100 match: - mode: CLIENT selector: matchLabels: app: <selector> pluginConfig: api: v1 system: name: system upstream: name: outbound|443||<system_host> url: <system_url> timeout: 5000 token: <access_token> backend: name: backend upstream: name: outbound|<backend_port>||<backend_host> url: <backend_url> timeout: 5000 extensions: - no_body services: - id: '<product_id>' authorities: - "*" credentials: user_key: - query_string: keys: - user_key - header: keys: - user_key The spec.pluginConfig field varies depending on the application. All other fields persist across multiple instances of this custom resource. This particular WasmPlugin spec.pluginConfig is configured with user_key authentication provided in a query string. Explanations: name Specifies the unique name or identifier for the WasmPlugin within 3scale. namespace Namespace of the workload. imagePullSecret The name of the pull secret you created in step 2. selector Workload label selector. Use the productpage of the bookinfo project. backend-port Depends on which 3scale you are using. See Adding 3scale URLs to Service Mesh . For example, internal 3scale uses port 80 and the external 3scale uses port 443. backend-host and system-host Use the same hosts you used in Adding 3scale URLs to Service Mesh . system-url and backend-url Use their respective hosts and add a protocol. For example, https://<system-host> . access-token Access token to the system tenant. product_id The ID of the product you would like to use. If you want multiple products, define multiple products under the services section. After you have the module configuration in spec.pluginConfig and the rest of the custom resource, apply them with the oc apply command: USD oc apply -f threescale-wasm-auth-bookinfo.yaml 11.6.1. 3scale API Management WasmPlugin authentication options These are examples of configuration for 3scale User key (App id/App key) authentication. User key apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: ... pluginConfig: ... services: - id: '<service_id>' authorities: - "*" credentials: user_key: - query_string: keys: - user_key - header: keys: - user_key App Id and App key apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: ... pluginConfig: ... services: - id: '<service_id>' authorities: - "*" credentials: app_id: - query_string: keys: - app_id - header: keys: - app_id app_key: - query_string: keys: - app_key - header: keys: - app_key OIDC Apart from the WasmPlugin itself, for OpenID Connect (OIDC) to work, you also need an additional custom resource called RequestAuthentication . When you apply the RequestAuthentication , it configures Envoy with a native plugin to validate JWT tokens. The proxy validates everything before running the module, so any requests that fail do not make it to the 3scale WebAssembly module. apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: jwt-example namespace: <bookinfo> spec: selector: matchLabels: app: <productpage> jwtRules: - issuer: >- "<url>/auth/realms/<realm_name>" jwksUri: >- "<url>/auth/realms/<realm_name>/protocol/openid-connect/certs" Explanation <url> : The URL of and OIDC instance, when configured with keycloak, is used to specify the keycloak OIDC provider's metadata endpoint for authentication configuration. <realm_name> : The name of the realm used in OIDC. apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: ... pluginConfig: ... services: - id: '<service_id>' authorities: - "*" credentials: app_id: - filter: path: - envoy.filters.http.jwt_authn - "0" keys: - azp - aud ops: - take: head: 1 Additional resources Restrict access with JSON Web Token Wasm Plugin 11.7. Testing the configured API You can verify the effectiveness of your API configuration by conducting an authentication check when making calls to your application. By thoroughly testing the authentication mechanism, you can ensure that only authorized requests are processed, maintaining the security and integrity of your application. Procedure Try a call to the Bookinfo application with the WasmPlugin applied. It should be rejected as we did not include any authentication: USD export GATEWAY_URL=USD(oc -n istio-system get route istio-ingressgateway -o jsonpath='{.spec.host}') USD curl -I "http://USDGATEWAY_URL/productpage" HTTP/1.1 403 Retrieve user key for authentication: Navigate to [Your_product_name] > Applications > Listings . Select your application. Look for Authentication > User Key . Try the call again with user key present. USD curl -I "http://USDGATEWAY_URL/productpage?user_key=USDUSER_KEY" HTTP/1.1 200 OK Verify that the hit was registered in metrics. Navigate to [Your_product_name] > Analytics > Traffic . You should see your calls registered. 11.8. The 3scale API Management WebAssembly module configuration The WasmPlugin custom resource spec provides the configuration that the Proxy-WASM module reads from. The spec is embedded in the host and read by the Proxy-WASM module. Typically, the configurations are in the JSON file format for the modules to parse. However, the WasmPlugin resource can interpret the spec value as YAML and convert it to JSON for consumption by the module. If you use the Proxy-WASM module in stand-alone mode, you must write the configuration using the JSON format. Using the JSON format means using escaping and quoting where needed within the host configuration files, for example Envoy . When you use the WebAssembly module with the WasmPlugin resource, the configuration is in the YAML format. In this case, an invalid configuration forces the module to show diagnostics based on its JSON representation to a sidecar's logging stream. Important The EnvoyFilter custom resource (CR) is not a supported API, although it can be used in some 3scale Istio adapter or Service Mesh releases. Using the EnvoyFilter CR is not recommended. Use the WasmPlugin API instead of the EnvoyFilter CR. If you must use the EnvoyFilter CR, you must specify the spec in JSON format. 11.8.1. Configuring the 3scale API Management WebAssembly module The architecture of the 3scale WebAssembly module configuration depends on the 3scale account and authorization service, and the list of services to handle. Prerequisites The prerequisites are a set of minimum mandatory fields in all cases: For the 3scale account and authorization service: the backend-listener URL. For the list of services to handle: the service IDs and at least one credential look up method and where to find it. You will find examples for dealing with userkey , appid with appkey , and OpenID Connect (OIDC) patterns. The WebAssembly module uses the settings you specified in the static configuration. For example, if you add a mapping rule configuration to the module, it will always apply, even when the 3scale Admin Portal has no such mapping rule. The rest of the WasmPlugin resource exists around the spec.pluginConfig YAML entry. 11.8.2. The 3scale API Management WebAssembly module api object The api top-level string from the 3scale WebAssembly module defines which version of the configuration the module will use. Note A non-existent or unsupported version of the api object renders the 3scale WebAssembly module inoperable. The api top-level string example apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> namespace: <bookinfo> spec: pluginConfig: api: v1 ... The api entry defines the rest of the values for the configuration. The only accepted value is v1 . New settings that break compatibility with the current configuration or need more logic that modules using v1 cannot handle will require different values. 11.8.3. The 3scale API Management WebAssembly module system object The system top-level object specifies how to access the 3scale Account Management API for a specific account. The upstream field is the most important part of the object. The system object is optional, but recommended unless you are providing a fully static configuration for the 3scale WebAssembly module. The latter is an option if you do not want to provide connectivity to the system component of 3scale. When you provide static configuration objects in addition to the system object, the static ones always take precedence. apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: system: name: <saas_porta> upstream: <object> token: <my_account_token> ttl: 300 ... Table 11.1. system object fields Name Description Required name An identifier for the 3scale service, currently not referenced elsewhere. Optional upstream The details about a network host to be contacted. upstream refers to the 3scale Account Management API host known as system. Yes token A 3scale personal access token with read permissions. Yes ttl The minimum amount of seconds to consider a configuration retrieved from this host as valid before trying to fetch new changes. The default is 600 seconds (10 minutes). Note: there is no maximum amount, but the module will generally fetch any configuration within a reasonable amount of time after this TTL elapses. Optional 11.8.4. The 3scale API Management WebAssembly module upstream object The upstream object describes an external host to which the proxy can perform calls. apiVersion: maistra.io/v1 upstream: name: outbound|443||multitenant.3scale.net url: "https://myaccount-admin.3scale.net/" timeout: 5000 ... Table 11.2. upstream object fields Name Description Required name name is not a free-form identifier. It is the identifier for the external host as defined by the proxy configuration. In the case of stand-alone Envoy configurations, it maps to the name of a Cluster , also known as upstream in other proxies. Note: the value of this field, because the Service Mesh and 3scale Istio adapter control plane configure the name according to a format using a vertical bar (|) as the separator of multiple fields. For the purposes of this integration, always use the format: outbound|<port>||<hostname> . Yes url The complete URL to access the described service. Unless implied by the scheme, you must include the TCP port. Yes Timeout Timeout in milliseconds so that connections to this service that take more than the amount of time to respond will be considered errors. Default is 1000 seconds. Optional 11.8.5. The 3scale API Management WebAssembly module backend object The backend top-level object specifies how to access the 3scale Service Management API for authorizing and reporting HTTP requests. This service is provided by the Backend component of 3scale. apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: ... backend: name: backend upstream: <object> ... Table 11.3. backend object fields Name Description Required name An identifier for the 3scale backend, currently not referenced elsewhere. Optional upstream The details about a network host to be contacted. This must refer to the 3scale Account Management API host, known, system. Yes. The most important and required field. 11.8.6. The 3scale API Management WebAssembly module services object The services top-level object specifies which service identifiers are handled by this particular instance of the module . You must specify which ones are handled because accounts have multiple services. The rest of the configuration revolves around how to configure services. The services field is required. It is an array that must contain at least one service to be useful. apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: ... services: - id: "2555417834789" token: service_token authorities: - "*.app" - 0.0.0.0 - "0.0.0.0:8443" credentials: <object> mapping_rules: <object> ... Each element in the services array represents a 3scale service. Table 11.4. services object fields Name Description Required id An identifier for this 3scale service, currently not referenced elsewhere. Yes token This token can be found in the proxy configuration for your service in System or you can retrieve the it from System with following curl command: curl "\https://<system_host>/admin/api/services/<service_id>/proxy/configs/production/latest.json?access_token=<access_token>" | jq '.proxy_config.content.backend_authentication_value' Optional authorities An array of strings, each one representing the Authority of a URL to match. These strings accept glob patterns supporting the asterisk ( * ), plus sign ( + ), and question mark ( ? ) matchers. Yes credentials An object defining which kind of credentials to look for and where. Yes mapping_rules An array of objects representing mapping rules and 3scale methods to hit. Optional 11.8.7. The 3scale API Management WebAssembly module credentials object The credentials object is a component of the service object. credentials specifies which kind of credentials to be looked up and the steps to perform this action. All fields are optional, but you must specify at least one, user_key or app_id . The order in which you specify each credential is irrelevant because it is pre-established by the module. Only specify one instance of each credential. apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: ... services: - credentials: user_key: <array_of_lookup_queries> app_id: <array_of_lookup_queries> app_key: <array_of_lookup_queries> ... Table 11.5. credentials object fields Name Description Required user_key This is an array of lookup queries that defines a 3scale user key. A user key is commonly known as an API key. Optional app_id This is an array of lookup queries that define a 3scale application identifier. Application identifiers are provided by 3scale or by using an identity provider like Red Hat single sign-on , Red Hat build of Keycloak or OpenID Connect (OIDC). The resolution of the lookup queries specified here, whenever it is successful and resolves to two values, it sets up the app_id and the app_key . Optional app_key This is an array of lookup queries that define a 3scale application key. Application keys without a resolved app_id are useless, so only specify this field when app_id has been specified. Optional 11.8.8. The 3scale API Management WebAssembly module lookup queries The lookup query object is part of any of the fields in the credentials object. It specifies how a given credential field should be found and processed. When evaluated, a successful resolution means that one or more values were found. A failed resolution means that no values were found. Arrays of lookup queries describe a short-circuit or relationship: a successful resolution of one of the queries stops the evaluation of any remaining queries and assigns the value or values to the specified credential-type. Each query in the array is independent of each other. A lookup query is made up of a single field, a source object, which can be one of a number of source types. See the following example: apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: ... services: - credentials: user_key: - <source_type>: <object> - <source_type>: <object> ... app_id: - <source_type>: <object> ... app_key: - <source_type>: <object> ... ... A source object exists as part of an array of sources within any of the credentials object fields. The object field name, referred to as a source -type is any one of the following: header : The lookup query receives HTTP request headers as input. query_string : The lookup query receives the URL query string parameters as input. filter : The lookup query receives filter metadata as input. All source -type objects have at least the following two fields: Table 11.6. source-type object fields Name Description Required keys An array of strings, each one a key , referring to entries found in the input data. Yes ops An array of operations that perform a key entry match. The array is a pipeline where operations receive inputs and generate outputs on the operation. An operation failing to provide an output resolves the lookup query as failed. The pipeline order of the operations determines the evaluation order. Optional path Shows the path in the metadata used to look up data. However, it is not required when header or query_string source type is used, but it is required when the filter source-type is used. Optional When a key matches the input data, the rest of the keys are not evaluated and the source resolution algorithm jumps to executing the operations ( ops ) specified, if any. If no ops are specified, the result value of the matching key , if any, is returned. Operations provide a way to specify certain conditions and transformations for inputs you have after the first phase looks up a key . Use operations when you need to transform, decode, and assert properties, however they do not provide a mature language to deal with all needs and lack Turing-completeness . A stack stored the outputs of operations . When evaluated, the lookup query finishes by assigning the value or values at the bottom of the stack, depending on how many values the credential consumes. 11.8.9. The 3scale WebAssembly module operations object Each element in the ops array belonging to a specific source type is an operation object that either applies transformations to values or performs tests. The field name to use for such an object is the name of the operation itself, and any values are the parameters to the operation , which could be structure objects, for example, maps with fields and values, lists, or strings. Most operations attend to one or more inputs, and produce one or more outputs. When they consume inputs or produce outputs, they work with a stack of values: each value consumed by the operations is popped from the stack of values and initially populated with any source matches. The values outputted by them are pushed to the stack. Other operations do not consume or produce outputs other than asserting certain properties, but they inspect a stack of values. Note When resolution finishes, the values picked up by the step, such as assigning the values to be an app_id , app_key , or user_key , are taken from the bottom values of the stack. There are a few different operations categories: decode These transform an input value by decoding it to get a different format. string These take a string value as input and perform transformations and checks on it. stack These take a set of values in the input and perform multiple stack transformations and selection of specific positions in the stack. check These assert properties about sets of operations in a side-effect free way. control These perform operations that allow for modifying the evaluation flow. format These parse the format-specific structure of input values and look up values in it. All operations are specified by the name identifiers as strings. Additional resources Available operations 11.8.10. The 3scale API Management WebAssembly module mapping_rules object The mapping_rules object is part of the service object. It specifies a set of REST path patterns and related 3scale metrics and count increments to use when the patterns match. You need the value if no dynamic configuration is provided in the system top-level object. If the object is provided in addition to the system top-level entry, then the mapping_rules object is evaluated first. mapping_rules is an array object. Each element of that array is a mapping_rule object. The evaluated matching mapping rules on an incoming request provide the set of 3scale methods for authorization and reporting to the APIManager . When multiple matching rules refer to the same methods , there is a summation of deltas when calling into 3scale. For example, if two rules increase the Hits method twice with deltas of 1 and 3, a single method entry for Hits reporting to 3scale has a delta of 4. 11.8.11. The 3scale API Management WebAssembly module mapping_rule object The mapping_rule object is part of an array in the mapping_rules object. The mapping_rule object fields specify the following information: The HTTP request method to match. A pattern to match the path against. The 3scale methods to report along with the amount to report. The order in which you specify the fields determines the evaluation order. Table 11.7. mapping_rule object fields Name Description Required method Specifies a string representing an HTTP request method, also known as verb. Values accepted match the any one of the accepted HTTP method names, case-insensitive. A special value of any matches any method. Yes pattern The pattern to match the HTTP request's URI path component. This pattern follows the same syntax as documented by 3scale. It allows wildcards, use of the asterisk (*) character, using any sequence of characters between braces such as {this} . Yes usages A list of usage objects. When the rule matches, all methods with their deltas are added to the list of methods sent to 3scale for authorization and reporting. Embed the usages object with the following required fields: name : The method system name to report. Note: name is case sensitive. delta : For how much to increase that method by. Yes last Whether the successful matching of this rule should stop the evaluation of more mapping rules. Optional Boolean. The default is false The following example is independent of existing hierarchies between methods in 3scale. That is, anything run on the 3scale side will not affect this. For example, the Hits metric might be a parent of them all, so it stores 4 hits due to the sum of all reported methods in the authorized request and calls the 3scale Authrep API endpoint. The example below uses a GET request to a path, /products/1/sold , that matches all the rules. mapping_rules GET request example apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: ... mapping_rules: - method: GET pattern: / usages: - name: hits delta: 1 - method: GET pattern: /products/ usages: - name: products delta: 1 - method: ANY pattern: /products/{id}/sold usages: - name: sales delta: 1 - name: products delta: 1 ... All usages get added to the request the module performs to 3scale with usage data as follows: Hits: 1 products: 2 sales: 1 11.9. The 3scale API Management WebAssembly module examples for credentials use cases You will spend most of your time applying configuration steps to obtain credentials in the requests to your services. The following are credentials examples, which you can modify to adapt to specific use cases. You can combine them all, although when you specify multiple source objects with their own lookup queries , they are evaluated in order until one of them successfully resolves. 11.9.1. API key (user_key) in query string parameters The following example looks up a user_key in a query string parameter or header of the same name: credentials: user_key: - query_string: keys: - user_key - header: keys: - user_key 11.9.2. Application ID and key The following example looks up app_key and app_id credentials in a query or headers. credentials: app_id: - header: keys: - app_id - query_string: keys: - app_id app_key: - header: keys: - app_key - query_string: keys: - app_key 11.9.3. Authorization header A request includes an app_id and app_key in an authorization header. If there is at least one or two values outputted at the end, then you can assign the app_key . The resolution here assigns the app_key if there is one or two outputted at the end. The authorization header specifies a value with the type of authorization and its value is encoded as Base64 . This means you can split the value by a space character, take the second output and then split it again using a colon (:) as the separator. For example, if you use this format app_id:app_key , the header looks like the following example for credential : You must use lowercase header field names as shown in the following example: credentials: app_id: - header: keys: - authorization ops: - split: separator: " " max: 2 - length: min: 2 - drop: head: 1 - base64_urlsafe - split: max: 2 app_key: - header: keys: - app_key The example use case looks at the headers for an authorization : It takes its string value and split it by a space, checking that it generates at least two values of a credential -type and the credential itself, then dropping the credential -type. It then decodes the second value containing the data it needs, and splits it by using a colon (:) character to have an operations stack including first the app_id , then the app_key , if it exists. If app_key does not exist in the authorization header then its specific sources are checked. For example, the header with the key app_key in this case. To add extra conditions to credentials , allow Basic authorizations, where app_id is either aladdin or admin , or any app_id being at least 8 characters in length. app_key must contain a value and have a minimum of 64 characters as shown in the following example: credentials: app_id: - header: keys: - authorization ops: - split: separator: " " max: 2 - length: min: 2 - reverse - glob: - Basic - drop: tail: 1 - base64_urlsafe - split: max: 2 - test: if: length: min: 2 then: - strlen: max: 63 - or: - strlen: min: 1 - drop: tail: 1 - assert: - and: - reverse - or: - strlen: min: 8 - glob: - aladdin - admin After picking up the authorization header value, you get a Basic credential -type by reversing the stack so that the type is placed on top. Run a glob match on it. When it validates, and the credential is decoded and split, you get the app_id at the bottom of the stack, and potentially the app_key at the top. Run a test: if there are two values in the stack, meaning an app_key was acquired. Ensure the string length is between 1 and 63, including app_id and app_key . If the key's length is zero, drop it and continue as if no key exists. If there was only an app_id and no app_key , the missing else branch indicates a successful test and evaluation continues. The last operation, assert , indicates that no side-effects make it into the stack. You can then modify the stack: Reverse the stack to have the app_id at the top. Whether or not an app_key is present, reversing the stack ensures app_id is at the top. Use and to preserve the contents of the stack across tests. Then use one of the following possibilities: Make sure app_id has a string length of at least 8. Make sure app_id matches either aladdin or admin . 11.9.4. OpenID Connect (OIDC) use case For Service Mesh and the 3scale Istio adapter, you must deploy a RequestAuthentication as shown in the following example, filling in your own workload data and jwtRules : apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: jwt-example namespace: <bookinfo> spec: selector: matchLabels: app: <productpage> jwtRules: - issuer: >- "<url>/auth/realms/<realm_name>" jwksUri: >- "<url>/auth/realms/<realm_name>/protocol/openid-connect/certs" When you apply the RequestAuthentication , it configures Envoy with a native plugin to validate JWT tokens. The proxy validates everything before running the module, so any requests that fail do not make it to the 3scale WebAssembly module. When a JWT token is validated, the proxy stores its contents in an internal metadata object, with an entry whose key depends on the specific configuration of the plugin. This use case gives you the ability to look up structure objects with a single entry containing an unknown key name. The 3scale app_id for OIDC matches the OAuth client_id . This is found in the azp or aud fields of JWT tokens. To get app_id field from Envoy's native JWT authentication filter, see the following example: credentials: app_id: - filter: path: - envoy.filters.http.jwt_authn - "0" keys: - azp - aud ops: - take: head: 1 The example instructs the module to use the filter source type to look up filter metadata for an object from the Envoy -specific JWT authentication native plugin. This plugin includes the JWT token as part of a structure object with a single entry and a pre-configured name. Use 0 to specify that you will only access the single entry. The resulting value is a structure for which you will resolve two fields: azp : The value where app_id is found. aud : The value where this information can also be found. The operation ensures only one value is held for assignment. 11.9.5. Picking up the JWT token from a header Some setups might have validation processes for JWT tokens, where the validated token would reach this module via a header in JSON format. To get the app_id , see the following example: credentials: app_id: - header: keys: - x-jwt-payload ops: - base64_urlsafe - json: - keys: - azp - aud - take: head: 1 11.10. 3scale API Management WebAssembly module minimal working configuration The following is an example of a 3scale WebAssembly module minimal working configuration. You can copy and paste this and edit it to work with your own configuration. apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: url: oci://registry.redhat.io/3scale-amp2/3scale-auth-wasm-rhel8:0.0.3 imagePullSecret: <pull_secret_resource> phase: AUTHZ match: - mode: SERVER priority: 100 selector: matchLabels: app: <productpage> pluginConfig: api: v1 system: name: <system_name> upstream: name: outbound|443||multitenant.3scale.net url: https://istiodevel-admin.3scale.net/ timeout: 5000 token: <token> backend: name: <backend_name> upstream: name: outbound|443||su1.3scale.net url: https://su1.3scale.net/ timeout: 5000 extensions: - no_body services: - id: '2555417834780' authorities: - "*" credentials: user_key: - query_string: keys: - <user_key> - header: keys: - <user_key> app_id: - query_string: keys: - <app_id> - header: keys: - <app_id> app_key: - query_string: keys: - <app_key> - header: keys: - <app_key> Additional resources Migrating from ServiceMeshExtension to WasmPlugin resources Kubernetes Custom Resources Wasm Plugin
export GATEWAY_URL=USD(oc -n istio-system get route istio-ingressgateway -o jsonpath='{.spec.host}') curl -I "http://USDGATEWAY_URL/productpage" HTTP/1.1 200 OK apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: <service_entry_threescale_system> spec: hosts: - <system_hostname> ports: - number: 443 name: https protocol: HTTPS location: MESH_EXTERNAL resolution: DNS EOF apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: <destination_rule_threescale_system> spec: host: <system_hostname> trafficPolicy: tls: mode: SIMPLE sni: <system_hostname> EOF oc get -n <3scale_namespace> route backend --template="{{.spec.host}}" apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: <service_entry_threescale_backend> spec: hosts: - <backend_hostname> ports: - number: 443 name: https protocol: HTTPS location: MESH_EXTERNAL resolution: DNS EOF apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: <destination_rule_threescale_backend> spec: host: <backend_hostname> trafficPolicy: tls: mode: SIMPLE sni: <backend_hostname> EOF apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: <service_entry_threescale_backend> spec: hosts: - backend-listener.<3scale_namespace>.svc.cluster.local ports: - number: 80 name: http protocol: HTTP location: MESH_EXTERNAL resolution: DNS EOF apply -n <bookinfo> -f -<<EOF apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: <destination_rule_threescale_backend> spec: host: backend-listener.<3scale_namespace>.svc.cluster.local EOF apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> namespace: <namespace> spec: url: oci://registry.redhat.io/3scale-amp2/3scale-auth-wasm-rhel8:0.0.3 imagePullSecret: <pull_secret_resource> phase: AUTHZ priority: 100 match: - mode: CLIENT selector: matchLabels: app: <selector> pluginConfig: api: v1 system: name: system upstream: name: outbound|443||<system_host> url: <system_url> timeout: 5000 token: <access_token> backend: name: backend upstream: name: outbound|<backend_port>||<backend_host> url: <backend_url> timeout: 5000 extensions: - no_body services: - id: '<product_id>' authorities: - "*" credentials: user_key: - query_string: keys: - user_key - header: keys: - user_key oc apply -f threescale-wasm-auth-bookinfo.yaml apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: services: - id: '<service_id>' authorities: - "*" credentials: user_key: - query_string: keys: - user_key - header: keys: - user_key apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: services: - id: '<service_id>' authorities: - "*" credentials: app_id: - query_string: keys: - app_id - header: keys: - app_id app_key: - query_string: keys: - app_key - header: keys: - app_key apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: jwt-example namespace: <bookinfo> spec: selector: matchLabels: app: <productpage> jwtRules: - issuer: >- "<url>/auth/realms/<realm_name>" jwksUri: >- "<url>/auth/realms/<realm_name>/protocol/openid-connect/certs" apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: services: - id: '<service_id>' authorities: - "*" credentials: app_id: - filter: path: - envoy.filters.http.jwt_authn - "0" keys: - azp - aud ops: - take: head: 1 export GATEWAY_URL=USD(oc -n istio-system get route istio-ingressgateway -o jsonpath='{.spec.host}') curl -I "http://USDGATEWAY_URL/productpage" HTTP/1.1 403 curl -I "http://USDGATEWAY_URL/productpage?user_key=USDUSER_KEY" HTTP/1.1 200 OK apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> namespace: <bookinfo> spec: pluginConfig: api: v1 apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: system: name: <saas_porta> upstream: <object> token: <my_account_token> ttl: 300 apiVersion: maistra.io/v1 upstream: name: outbound|443||multitenant.3scale.net url: "https://myaccount-admin.3scale.net/" timeout: 5000 apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: backend: name: backend upstream: <object> apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: services: - id: "2555417834789" token: service_token authorities: - "*.app" - 0.0.0.0 - "0.0.0.0:8443" credentials: <object> mapping_rules: <object> apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: services: - credentials: user_key: <array_of_lookup_queries> app_id: <array_of_lookup_queries> app_key: <array_of_lookup_queries> apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: services: - credentials: user_key: - <source_type>: <object> - <source_type>: <object> app_id: - <source_type>: <object> app_key: - <source_type>: <object> apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: pluginConfig: mapping_rules: - method: GET pattern: / usages: - name: hits delta: 1 - method: GET pattern: /products/ usages: - name: products delta: 1 - method: ANY pattern: /products/{id}/sold usages: - name: sales delta: 1 - name: products delta: 1 credentials: user_key: - query_string: keys: - user_key - header: keys: - user_key credentials: app_id: - header: keys: - app_id - query_string: keys: - app_id app_key: - header: keys: - app_key - query_string: keys: - app_key aladdin:opensesame: Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l credentials: app_id: - header: keys: - authorization ops: - split: separator: " " max: 2 - length: min: 2 - drop: head: 1 - base64_urlsafe - split: max: 2 app_key: - header: keys: - app_key credentials: app_id: - header: keys: - authorization ops: - split: separator: " " max: 2 - length: min: 2 - reverse - glob: - Basic - drop: tail: 1 - base64_urlsafe - split: max: 2 - test: if: length: min: 2 then: - strlen: max: 63 - or: - strlen: min: 1 - drop: tail: 1 - assert: - and: - reverse - or: - strlen: min: 8 - glob: - aladdin - admin apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: jwt-example namespace: <bookinfo> spec: selector: matchLabels: app: <productpage> jwtRules: - issuer: >- "<url>/auth/realms/<realm_name>" jwksUri: >- "<url>/auth/realms/<realm_name>/protocol/openid-connect/certs" credentials: app_id: - filter: path: - envoy.filters.http.jwt_authn - "0" keys: - azp - aud ops: - take: head: 1 credentials: app_id: - header: keys: - x-jwt-payload ops: - base64_urlsafe - json: - keys: - azp - aud - take: head: 1 apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: <threescale_wasm_plugin_name> spec: url: oci://registry.redhat.io/3scale-amp2/3scale-auth-wasm-rhel8:0.0.3 imagePullSecret: <pull_secret_resource> phase: AUTHZ match: - mode: SERVER priority: 100 selector: matchLabels: app: <productpage> pluginConfig: api: v1 system: name: <system_name> upstream: name: outbound|443||multitenant.3scale.net url: https://istiodevel-admin.3scale.net/ timeout: 5000 token: <token> backend: name: <backend_name> upstream: name: outbound|443||su1.3scale.net url: https://su1.3scale.net/ timeout: 5000 extensions: - no_body services: - id: '2555417834780' authorities: - "*" credentials: user_key: - query_string: keys: - <user_key> - header: keys: - <user_key> app_id: - query_string: keys: - <app_id> - header: keys: - <app_id> app_key: - query_string: keys: - <app_key> - header: keys: - <app_key>