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 validates 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.
Using External Secrets Operator (ESO), you can create a Kubernetes secret with all the ApiKey secrets from your secret provider. While it is possible to create and manage the secret manually, ESO automatically updates 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
, is 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"
deletionPolicy: Merge
removes keys but keeps the Kubernetes secret for the gateway to use.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 1 MiB, there is a limit to the number of keys you can have. Assuming a key/value pair is 256 bytes long, you could have 4096 entries.
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 instance 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
Where:
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.For the example SpringCloudGateway
configuration above, to ensure access to the Vault path, you must configure your Hashicorp Vault instance as follows:
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
This sample command uses scg-policy
as the policy name, but any name can be chosen here. This name will be required in the next step.
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 must be set to the name of the namespace where you create your Spring Cloud Gateway instance, and the bound_service_account_names
value must refer to a service account in the same namespace.
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, use the relevant command:
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
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}"
You can 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 supports 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.
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. If the secret doesn't exist, the gateway pod errors out looking for the secret.
The ExternalSecret
in the example below returns secrets matching the regex jwt-keys/.*
. It is recommended that you have a common prefix in your secrets to make filtering easier. Also check that the generated Kubernetes secret includes only the JWT key secrets you intend 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 similar like this:
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
Where:
filters.jwtKey
describes the JwtKey
filter configuration:
enabled
indicates that the JWT key validation on all requests is activatedsecretName
is the name of the Kubernetes secret with all the JwtKey secretsNote As the Kubernetes secret has a limit of 1 MiB, 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. The RSA algorithm uses more bytes per key/value pair.
Spring Cloud Gateway for Kubernetes integrates with Vault on Kubernetes, and assumes that 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 instance 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
Where:
serviceAccount.name
is the name of the ServiceAccount used by the gateway instancesextensions.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 activatedsecretsProviderName
is the Vault secrets provider name defined previouslyWhen 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 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}
In addition to the JWT key structure above, the secrets in 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 algorithm:
vault kv put jwt-keys-for-vmware-tanzu/customnamespace-mygateway/client_0 \
kid="client_0" \
alg="RSA" \
key="-----BEGIN CERTIFICATE-----\
MIIBIyEpEBgkqhkiG9w ..."
HMAC algorithm
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 updates the keys in the Gateway after no more than 5 minutes.
After 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 by applying 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 checks 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
In addition to supporting 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
contains only one value (for example, 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 for development purposes only. It accepts a list of roles or scopes (separated by commas) to inject into 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, so additional configuration is required to reduce the risk that it is accidentally activated in production environments:
JAVA_OPTS
property) com.vmware.tanzu.springcloudgateway.dev.mode.enabled
must be set to true
.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 must create a placeholder configuration that activates an SSO profile and sets 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.
Important TokenRelay
is set using boolean fields spec.service.tokenRelay
and/or spec.routes.tokenRelay
. If you add it to the filters
array it is ignored. This helps ensure that these are applied to the Gateway in the correct order.
To do this, use:
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 updates 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 are asked to authenticate again using the Authorization Flow. The later re-authentication happens automatically if the user session is still active in the Identity Provider. Users may report seeing the page reload.
Important You cannot use the TokenRelay
filter with the BasicAuth
filter, because both filters use the Authorization
header.
TokenRelay
in standalone modeThe token relay feature is supported when running Spring Cloud Gateway in standalone mode outside of Kubernetes. See TokenRelay Standalone Configuration