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.
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.
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"
deletionPolicy: Merge
removes keys but will keep the kubernetes secret around for the gateway to usedataFrom
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.
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.For the example SpringCloudGateway
configuration above, to ensure access to the Vault path, you need to 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
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.
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.
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
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.
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 secretsNote 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.
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 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 activated.secretsProviderName
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 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}
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.
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:
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 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.
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 modeThe token relay feature is supported when running Spring Cloud Gateway in standalone mode outside of Kubernetes. See TokenRelay Standalone Configuration