Apply policies to APIs

To control traffic behavior to your API products, you can use Gloo policies. Generally, policies work the same for API products that you expose in the developer portal as described in the following docs:

Sometimes, the way you configure routing and policies differs when your API products are exposed with Portal. Portal provides extra flexibility to describe your API products that subsequent policies can take advantage of. Refer to the following guides for these special use cases:

Add custom metadata from API products to requests and responses

You can set custom metadata for your API products when you bundle your APIs in a route table. Then, the custom metadata is available for your developer portal and analytics. You can also add the custom metadata in subsequent requests and responses to your APIs by using a header manipulation policy.

  1. Install or upgrade Portal. In the istioInstallations section of the gloo-platform Helm release, enable the following access logs for the ingress gateway. The Monitor Portal analytics guide includes an example of enabling access logs in the ingress gateway. For more information about access logs, see Access logs.

    • The user_id, api_id, api_product_id, api_product_name, usage_plan, and custom_metadata come from the Portal metadata that you configure later in the route table for your API products.
    • The "x_tenant_id": "%REQ(X-TENANT-ID)%" is a request header that you append later in the header manipulation policy.
    istioInstallations:
      controlPlane:
        enabled: true
        installations:
          - istioOperatorSpec:
              meshConfig:
                # Enable access logging to /dev/stdout
                accessLogFile: /dev/stdout
                # Encoding for the access log (TEXT or JSON). Default value is TEXT.
                accessLogEncoding: JSON
                # If empty, the default log format is used.
                # See the default log format at https://istio.io/latest/docs/tasks/observability/logs/access-log/#default-access-log-format
                # To change the format, see https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-rules
                accessLogFormat: |
                  {
                    "timestamp": "%START_TIME%",
                    "server_name": "%REQ(:AUTHORITY)%",
                    "response_duration": "%DURATION%",
                    "request_command": "%REQ(:METHOD)%",
                    "request_uri": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%",
                    "request_protocol": "%PROTOCOL%",
                    "status_code": "%RESPONSE_CODE%",
                    "client_address": "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%",
                    "x_forwarded_for": "%REQ(X-FORWARDED-FOR)%",
                    "bytes_sent": "%BYTES_SENT%",
                    "bytes_received": "%BYTES_RECEIVED%",
                    "user_agent": "%REQ(USER-AGENT)%",
                    "downstream_local_address": "%DOWNSTREAM_LOCAL_ADDRESS%",
                    "requested_server_name": "%REQUESTED_SERVER_NAME%",
                    "request_id": "%REQ(X-REQUEST-ID)%",
                    "response_flags": "%RESPONSE_FLAGS%",
                    "route_name": "%ROUTE_NAME%",
                    "upstream_cluster": "%UPSTREAM_CLUSTER%",
                    "upstream_host": "%UPSTREAM_HOST%",
                    "upstream_local_address": "%UPSTREAM_LOCAL_ADDRESS%",
                    "upstream_service_time": "%REQ(x-envoy-upstream-service-time)%",
                    "upstream_transport_failure_reason": "%UPSTREAM_TRANSPORT_FAILURE_REASON%",
                    "correlation_id": "%REQ(X-CORRELATION-ID)%",
                    "user_id": "%DYNAMIC_METADATA(envoy.filters.http.ext_authz:userId)%",
                    "api_id": "%DYNAMIC_METADATA(io.solo.gloo.apimanagement:api_id)%",
                    "api_product_id": "%DYNAMIC_METADATA(io.solo.gloo.apimanagement:api_product_id)%",
                    "api_product_name": "%DYNAMIC_METADATA(io.solo.gloo.apimanagement:api_product_name)%",
                    "usage_plan": "%DYNAMIC_METADATA(envoy.filters.http.ext_authz:usagePlan)%",
                    "custom_metadata": "%DYNAMIC_METADATA(io.solo.gloo.apimanagement.custom_metadata)%",
                    "x_tenant_id": "%REQ(X-TENANT-ID)%"
                  }              
            revision: auto   
    
  2. Create your APIs.

  3. Bundle your APIs into API products by creating a route table. In the portalMetadata section of the route table, make sure to your custom metadata, such as compatability and tenant in the following example.

      portalMetadata:
        apiProductId: "tracks"
        apiProductDisplayName: "Catstronauts Course Tracks"
        apiVersion: "v1"
        title: "Catstronauts REST API"
        description: "REST API for Catstronauts to retrieve data for tracks, authors and modules."
        termsOfService: "You must authenticate to use this API! And other Terms of Service."
        contact: "support@example.com"
        license: "License info, such as MIT"
        lifecycle: "Supported"
        customMetadata:
          compatibility: "None"
          tenantId: "tenant-us-east2" 
    
  4. Apply a header manipulation policy that appends the custom metadata from the API product to the request header. The policy gets the custom metadata from the %DYNAMIC_METADATA% in the access logs of the ingress gateway. For more information and other options such as removing request or response headers, see the Header manipulation guide or API reference docs. In the following example, the policy:

    • Appends the tenantId as a request header so that subsequent processing can take place based on the tenant ID.
    • Appends the compatibility as a response header so that the client can tell whether the API product is backwards compatible.
    kubectl apply -f - << EOF
    apiVersion: trafficcontrol.policy.gloo.solo.io/v2
    kind: HeaderManipulationPolicy
    metadata:
      name: portal-custom-metadata
      namespace: tracks
    spec:
      applyToRoutes:
      - route:
          labels:
            api: tracks
      config:
        appendRequestHeaders:
          x-tenant-id: "%DYNAMIC_METADATA(io.solo.gloo.apimanagement.custom_metadata:tenantId)%"
        appendResponseHeaders:
          x-compatibility: "%DYNAMIC_METADATA(io.solo.gloo.apimanagement.custom_metadata:compatibility)%"
    EOF
    
  5. Get the external address of the ingress gateway. If you deployed your ingress gateway in a different namespace or with a different version, update the command.

    export INGRESS_GW_IP=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    echo $INGRESS_GW_IP
    
    export INGRESS_GW_IP=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
    echo $INGRESS_GW_IP
    
  6. Send a request to the Tracks app. Note that if you already included the Tracks app in a usage plan, you might need to authenticate such as with an API key.

    curl -vik http://$INGRESS_GW_ADDRESS:80/trackapi/tracks -H "host: api.example.com:80"
    

    In the reponse, note that the x-compatibility response header is returned.

    x-compatibility: None
    
  7. Check the ingress gateway logs for the x_tenant_id request header that you appended to requests sent to the upstream service.

    kubectl logs -n gloo-mesh-gateways -l app=istio-ingressgateway
    

    Example output:

    {
      "api_product_id": "tracks",
      "request_command": "GET",
      "api_product_name": "Catstronauts Course",
      "user_agent": "curl/8.1.2",
      "server_name": "api.example.com",
      "custom_metadata": {
        "tenantId": "tenant-us-east2",
        "compatibility": "None"
      },
      "response_duration": 11,
      "x_forwarded_for": "10.0.11.54",
      "status_code": 200,
      "usage_plan": "gold",
      "user_id": "user1",
      "api_id": "tracks",
      "request_uri": "/trackapi/tracks",
      "x_tenant_id": "tenant-us-east2"
      ...
    }
       

  8. Optional: Clean up the policy if you no longer need to add custom metadata to requests and responses.

    kubectl delete HeaderManipulationPolicy portal-custom-metadata -n tracks