The open-source Spring Cloud Gateway project includes a number of built-in filters for use in Gateway routes. The following commercial filters provided by VMware Spring Cloud Gateway for Kubernetes can be used in addition to those included in the OSS project.

ApiKey

Note This filter is currently not supported when running the Gateway in standalone mode.

The ApiKey filter validates API keys generated by API portal for VMware Tanzu. It is expected that every request has a X-API-Key header specified, which the filter will validate against the hashed value stored in Hashicorp Vault.

Spring Cloud Gateway can be configured for the ApiKey filter in two ways:

If you are using HashiCorp Vault to store your secrets, you can configure SpringCloudGateway using either approach. If you are using a different provider, you must use the kubernetes secret approach.

Configuring Spring Cloud Gateway for ApiKey using External Secrets Operator

Using External Secrets Operator (ESO), you can create a kubernetes secret with all the ApiKey keys secrets from your secret provider. While it is possible to create and manage the secret manually, ESO will automatically update the kubernetes secret when the secret provider is updated.

Here is a sample SpringCloudGateway configured to use a kubernetes secret for the ApiKey filter:

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
  name: my-gateway
spec:
  api:
    groupId: accounting
  extensions:
    filters:
      apiKey:
        enabled: true
        secretName: "api-key-secrets"

The kubernetes secret, api-key-secrets, will be created by the scg-operator if it does not exist because the secret is required for the gateway pod to start.

You can configure an ExternalSecret to merge its found secrets to the existing kubernetes secret using creationPolicy: Merge as shown below:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: vault-example
spec:
  refreshInterval: "5s"
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: api-key-secrets
    creationPolicy: Merge
    deletionPolicy: Merge
  dataFrom:
    - find:
        conversionStrategy: Default
        decodingStrategy: None
        name:
          regexp: "api-portal-path/accounting/.*"
      rewrite:
      - regexp:
          source: "api-portal-path/accounting/(.*)"
          target: "accounting_$1"
  • The deletionPolicy: Merge removes keys but will keep the kubernetes secret around for the gateway to use
  • The dataFrom spec finds secrets stored under api-portal-path/accounting/* and rewrites the key to accounting_{file-name}

Note As the kubernetes secret has a limit of 1MiB, there is a limit to the number of keys you can have. Assuming a key/value pair is 256 bytes, you could have 4096 entries.

Configuring Spring Cloud Gateway for ApiKey using Vault

Spring Cloud Gateway for Kubernetes integrates with Vault on Kubernetes, and assumes that a Vault Agent Injector has been deployed to the cluster. The ApiKey filter requires additional Vault integration parameters to be configured in the SpringCloudGateway resource for each Gateway instances that uses it:

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
  name: mygateway
spec:
  api:
    groupId: my-group-id

  serviceAccount:
    name: scg-service-account

  extensions:
    secretsProviders:
      - name: vault-api-keys
        vault:
          roleName: api-key-role
          path: api-portal-keys
    filters:
      apiKey:
        enabled: true
        secretsProviderName: vault-api-keys
  • serviceAccount.name is the name of the ServiceAccount used by the Gateway instance.
  • extensions.secretsProviders describes the location from which keys will be obtained:
    • name is the unique name by which the Vault secrets provider can be referenced in the apiKey.secretsProviderName property.
    • vault.roleName is the Vault role with read access to the secrets.
    • vault.path (optional) is the same Vault path you configured when setting up API key management in API portal. If not set, the value will default to api-portal-for-vmware-tanzu.
  • filters.apiKey describes the ApiKey filter configuration:
    • enabled is the flag indicating that API key validation on all requests is activated.
    • secretsProviderName is the Vault secrets provider name defined previously.

Configuring HashiCorp Vault

For the example SpringCloudGateway configuration above, to ensure access to the Vault path, you need to configure your Hashicorp Vault instance as follows:

  1. Create a Vault access policy to the API portal path for the Gateway, including your Gateway groupId (see the SpringCloudGateway resource specification for more details)

    vault policy write scg-policy - <<EOF
    path "api-portal-keys/data/my-group-id" {
      capabilities = ["read"]
    }
    path "api-portal-keys/metadata/my-group-id" {
      capabilities = ["list"]
    }
    EOF
    

    The sample command above uses scg-policy as the policy name, although any name can be chosen here. This name will be required in the next step.

  2. Create a role that binds a namespaced service account to the policy, following the Kubernetes Auth Method:

    vault write auth/kubernetes/role/api-key-role \
      bound_service_account_names=scg-service-account \
      bound_service_account_namespaces=scg-namespace \
      policies=scg-policy \
      ttl=24h
    

    The bound_service_account_namespaces value needs to be set to the name of namespace where you create your Spring Cloud Gateway instance, and the bound_service_account_names value needs to refer to a service account in the same namespace.

Validating the configuration

After applying the configuration above, all routes served by the Gateway instance will require the X-API-Key header in order to be accessed.

To test this using an HTTP client such as httpie or cURL:

http GET my-gateway.my-example-domain.com/github X-API-Key:{my-api-key}
curl -X GET my-gateway.my-example-domain.com/github --header "X-API-key:{my-api-key}"

If you want to check that API key management is activated and that keys have been loaded on any Gateway instance, you can visit its /actuator/info endpoint, which should display:

apikey:
  enabled: true
  loaded: true
  headerName: X-Api-Key

Customizing apiKey HTTP header

X-API-Key is the default header used to send the key to the Gateway. But you can change it using the headerName property in the SpringCloudGateway resource.

spec:
  extensions:
    filters:
      apiKey:
        enabled: true
        secretsProviderName: vault-api-keys
        headerName: X-Custom-Header

You can test this configuration with cURL using the new header.

curl -X GET my-gateway.my-example-domain.com/github --header "X-Custom-Header:{my-api-key}"

And also, see the new value reflected in the /actuator/info endpoint.


JwtKey

Note This filter is currently not supported when running the Gateway in standalone mode.

The JwtKey filter validates JSON Web Tokens (JWT) generated by different providers with different signature keys. It is expected that every request has a key id that identifies the key that validates the token signature.

The JwtKey secret has the following structure:

  • kid is the key id to uniquely identify the public key (RSA) or the private key (HMAC). This key id should match the value obtained from the key id location.
  • alg is the algorithm used to encrypt the public key (currently supporting RSA only) or the private key (HS256, HS384, or HS512).
  • key is the public key in PEM format (supporting both CERTIFICATE and PUBLIC KEY formats), or private key (must be at least 32 bytes in length).

Spring Cloud Gateway can be configured for the JwtKey filter in two ways:

If you are using HashiCorp Vault to store your secrets, you can configure SpringCloudGateway using either approach. If you are using a different provider, you must use the kubernetes secret approach.

Configuring Spring Cloud Gateway for JwtKey using External Secrets Operator

Using External Secrets Operator (ESO), you can create a kubernetes secret with all the JWT keys secrets from your secret provider. While it is possible to create and manage the secret manually, ESO will automatically update the kubernetes secret when the secret provider is updated.

Important The secret must exist before the gateway pod starts, otherwise it will error out looking for the secret.

The ExternalSecret below returns secrets matching the regex jwt-keys/.*. It is recommended to have a common prefix in your secrets to filter by. Please also check that the generated kubernetes secret only has the JWT key secrets you intended to share.

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: vault-example
spec:
  refreshInterval: "15s"
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: jwt-key-secrets
  dataFrom:
    - find:
        name:
          regexp: "jwt-keys/.*"

If you stored a secret in your secret provider as:

{"alg":"HS256","key":"my-super-secure-hs256-secret-key","kid":"client_3"}

Then the kubernetes secret created by ESO, jwt-key-secrets, should look something like:

apiVersion: v1
kind: Secret
metadata:
  name: jwt-key-secrets
data:
  jwt-keys_client_3: eyJhbGciOiJIUzI1NiIsImtleSI6ImFub3RoZXItdWx0cmEtc2VjdXJlLXNlY3JldC0zMzMzIiwia2lkIjoiY2xpZW50XzMifQ==

You can then use the kubernetes secret jwt-key-secrets in your SpringCloudGateway resource:

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
  name: mygateway
spec:
  extensions:
    filters:
      jwtKey:
        enabled: true
        secretName: jwt-key-secrets
  • filters.jwtKey describes the JwtKey filter configuration:
    • enabled is the flag indicating that the JWT key validation on all requests is activated.
    • secretName is the name of the Kubernetes secret with all the JwtKey secrets

Note As the kubernetes secret has a limit of 1MiB, there is a limit to the number of keys you can have. Assuming 128 bytes for an HS256 key/value pair, you could have at most 8192 entries. Using the RSA algorithm will use more bytes per key/value pair.

Configuring Spring Cloud Gateway for JwtKey using Vault

Spring Cloud Gateway for Kubernetes integrates with Vault on Kubernetes, and assumes a Vault Agent Injector has been deployed to the cluster. The JwtKey filter requires additional Vault integration parameters to be configured in the SpringCloudGateway resource for each Gateway instances that uses it:

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
  name: mygateway
spec:
  serviceAccount:
    name: scg-service-account

  extensions:
    secretsProviders:
      - name: vault-jwt-keys
        vault:
          roleName: scg-role
    filters:
      jwtKey:
        enabled: true
        secretsProviderName: vault-jwt-keys
  • serviceAccount.name is the name of the ServiceAccount used by the gateway instances
  • extensions.secretsProviders describes the location from which keys will be obtained:
    • name is the unique name by which the Vault secrets provider can be referenced in the jwtKey.secretsProviderName property.
    • vault.roleName is the Vault role with read access to the secrets.
    • vault.path (optional) is the full path to the secrets in Vault. For example, for keys my-secrets/scg/keys/123... and my-secrets/scg/keys/456..., the vault.path setting would be my-secrets/scg/keys. If not set, the value will default to jwt-keys-for-vmware-tanzu/{namespace}-{gateway_name}
    • vault.authPath (optional) is the authentication path for Vault's Kubernetes auth method. For example, /auth/cluster-1-auth, /auth/cluster-2-auth.
  • filters.jwtKey describes the JwtKey filter configuration:
    • enabled is the flag indicating that the JWT key validation on all requests is activated.
    • secretsProviderName is the Vault secrets provider name defined previously

Configuring routes

When defining the routes to be protected in a SpringCloudGatewayRouteConfig, add the JwtKey filter by including it in the list of filters for the route.

The configuration provided to the JwtKey filter indicates the key id location. This key id location describes whether the key id is to found in an HTTP header, or in a JWT claim or JWT header, with the following syntax:

  • JwtKey={header:X-JWT-KEYID}: the key id location is expected to be in an HTTP header named X-JWT-KEYID
  • JwtKey={claim:kid}: the key id location is expected to be in a JWT claim or JWT header named kid

For example:

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGatewayRouteConfig
metadata:
  name: myapp-route-config
spec:
  service:
    name: myapp
  routes:
  - predicates:
      - Path=/api/**
    filters:
      - JwtKey={header:X-JWT-KEYID}

Configuring HashiCorp Vault

In addition to the JWT key structure above, the secrets within Vault must be stored in a specific location. Unless the path property is set in the secretsProvider it must be composed of jwt-keys-for-vmware-tanzu/{namespace}-{gateway_name}/{kid}.

RSA:

vault kv put jwt-keys-for-vmware-tanzu/customnamespace-mygateway/client_0 \
              kid="client_0" \
              alg="RSA" \
              key="-----BEGIN CERTIFICATE-----\
              MIIBIyEpEBgkqhkiG9w ..."

HMAC:

vault kv put jwt-keys-for-vmware-tanzu/customnamespace-mygateway/client_0 \
              kid="client_0" \
              alg="HS256" \
              key="Key-Must-Be-at-least-32-bytes-in-length!"

Note If you need to add, remove or update a key in Vault, you can use any of Vault's supported methods, such as the HTTP API or CLI. Every interaction will update the keys in the Gateway after no more than 5 minutes.

Validating the configuration

Once the Gateway is up and running you can see the loaded keys in its /actuator/info endpoint. Each key is shown with its id and the time when it was last modified.

"jwtkeys": [
  {
    "id": "client_0",
    "lastRefreshTime": "2021-09-07T07:57:01+0000"
  }
]

Roles

The Roles filter uses custom JWT claim properties to apply role-based access control (RBAC) to routes.

If you are not using the Single Sign-On feature, you can still use role-based control provided you apply the JwtKey filter.

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGatewayRouteConfig
metadata:
  name: my-gateway-routes
spec:
  service:
    name: myapp
  routes:
  - ssoEnabled: true
    predicates:
      - Path=/api/**
    filters:
      - Roles=role_01,role_02

By default, Spring Cloud Gateway for Kubernetes will check for role values under the roles claim, but you can change this by setting the spec.sso.roles-attribute-name property in your SpringCloudGateway resource.

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
  name: mygateway
spec:
  sso:
    roles-attribute-name: my-roles

As well as simple claim names, spec.sso.roles-attribute-name supports nested JSON values, such as custom-data.user.roles.

Spring Cloud Gateway expects the roles claim (or custom claim specified in spec.sso.roles-attribute-name) to contain an array (e.g. roles = ["user-role-1", "user-role-2"]), but a single string is also accepted when roles only contains one value (e.g. roles = "user-role").


Scopes

When Single Sign-On is activated, you can fine-tune route access control based on OpenID Connect scopes by adding the Scopes filter.

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGatewayRouteConfig
metadata:
  name: my-gateway-routes
spec:
  service:
    name: myapp
  routes:
    - ssoEnabled: true
      predicates:
        - Path=/api/**
      filters:
        - Scopes=api.read,api.write,user

SsoAutoAuthorize

Caution: This filter must be applied only for development purposes. It accepts a list of roles or scopes (separated by commas) to inject in a fake SSO authorization.

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGatewayRouteConfig
metadata:
  name: my-gateway-routes
spec:
  service:
    name: myapp
    filters:
      - SsoAutoAuthorize=SCOPE_test,ROLE_test

This filter is intended to be a convenience to aid development only, and so additional configuration is required to reduce the chance that it is accidentally activated in production environments:

  • System property (JAVA_OPTS property) com.vmware.tanzu.springcloudgateway.dev.mode.enabled must be set to true.
  • Configuration property com.vmware.tanzu.springcloudgateway.sso.auto.authorize.enabled must be set to true.
apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
  name: my-gateway
spec:
  env:
    - name: com.vmware.tanzu.springcloudgateway.sso.auto.authorize.enabled
      value: "true"
  java-opts: "-Dcom.vmware.tanzu.springcloudgateway.dev.mode.enabled=true"

If no SSO configuration is present you will need to create a placeholder configuration activating an SSO profile, and setting a valid issuer uri, for example the Google Issuer URL (https://accounts.google.com):

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGateway
metadata:
  name: my-gateway
spec:
  env:
    - name: spring.profiles.include
      value: "sso"
    - name: spring.security.oauth2.client.provider.sso.issuer-uri
      value: "https://accounts.google.com"
    - name: com.vmware.tanzu.springcloudgateway.sso.auto.authorize.enabled
      value: "true"
  java-opts: "-Dcom.vmware.tanzu.springcloudgateway.dev.mode.enabled=true"

TokenRelay

A Token Relay involves an OAuth2 or OpenID Connect consumer acting as a client, and forwarding the incoming token to outgoing resource requests. In the case of Spring Cloud Gateway, activating TokenRelay on a route instructs the Gateway to forward the currently authenticated user's identity token (ID Token) to the downstream routed service.

The client app and the web browser send auth details through SGG for K8x to the API and OIDC provider.

Important TokenRelay is set using boolean fields `spec.service.tokenRelay` and/or `spec.routes.tokenRelay`. If you add it to the `filters` array it will be ignored. This helps ensure it is applied to the Gateway in the correct order.

To do this

apiVersion: "tanzu.vmware.com/v1"
kind: SpringCloudGatewayRouteConfig
metadata:
  name: my-gateway-routes
spec:
  service:
    name: myapp
    tokenRelay: true
  routes:
  - ssoEnabled: true
    predicates:
      - Path=/api/**
   - ssoEnabled: true
     tokenRelay: false
     predicates:
       - Path=/login

Spring Cloud Gateway will update the token and internal SCG session automatically using the Refresh Token sent by the Identity Provider. Review your Provider documentation to see how to enable refresh for users.

If no Refresh Token is provided, users will be requested to authenticate again via Authorization Flow. The later re-authentication will happen automatically if the user session is still active in the Identity Provider. Users may report seeing the page reloading.

Important The TokenRelay filter will not work together with the BasicAuth filter, as both filters use the Authorization header.

TokenRelay in standalone mode

The token relay feature is supported when running Spring Cloud Gateway in standalone mode outside of Kubernetes. See TokenRelay Standalone Configuration

check-circle-line exclamation-circle-line close-line
Scroll to top icon