Customization¶
To make new microservices accessible via the Uniport-Gateway, a dedicated configuration must be created and integrated. The following sections describe the procedure for this.
Creating a Custom Configuration¶
The configuration of the Uniport-Gateway is twofold:
- Static
- Dynamic
The static configuration is provided in the form of a JSON file. It defines the objects for entrypoints and providers.
The second level depends on the providers from the static configuration. The dynamic configuration defines how the Uniport-Gateway processes incoming requests.
Static Configuration¶
The JSON file for the static configuration is searched for at startup in the specified order:
- File specified via the Environment Variable
UNIPORT_GATEWAY_JSON - File specified via the System Property
UNIPORT_GATEWAY_JSON - File
uniport-gateway.jsonin the/etc/uniport-gateway/default/directory - File
uniport-gateway.jsonin the current directory (Run Configuration "UniportGateway" := ./server/uniport-gateway)
Example of a static configuration
Additional configuration files can be referenced from this JSON file. For example, this configuration file uses the file Provider for dynamic configuration, with further files contained in the subdirectory ./dynamic-config/.
{
"entrypoints": [
{
"name": "http20000",
"port": 20000,
"middlewares": [
{
"name": "customHeader",
"type": "headers",
"options": {
"customResponseHeaders": {
"customResponseHeader": "customResponseValue"
},
"customRequestHeaders": {
"customRequestHeader": "customRequestValue"
}
}
}]
}
],
"providers": [
{
"name": "file",
"directory": "./dynamic-config",
"watch": false
}
]
}
In this case, the referenced directory ./dynamic-config/ must contain at least one subdirectory (with any name). This subdirectory contains the other JSON files for configuring routers, middlewares, and services, e.g.:
uniport-gateway/server/src/main/resources/
|-- uniport-gateway
| |-- dynamic-config
| | |-- auth
| | | `-- config.json
| | `-- general
| | `-- config.json
| `-- uniport-gateway.json
If there are multiple subdirectories with JSON files, JSON arrays under the same key are concatenated instead of overwritten. This allows configurations to be clearly structured. With the folder structure from the example above, routers can be defined and used simultaneously in auth/config.json and general/config.json.
Entrypoints¶
Entrypoints are the network entry points of Uniport-Gateway. They define the port where packets are received. Entrypoints are part of the Static Configuration.
| Variable | Required | Type | Description |
|---|---|---|---|
name |
Yes | String | Name of the entrypoint |
port |
Yes | Integer | Port number |
sessionDisabled |
No | Boolean | Disables session handling (Default: false). As of version 8.0.0, this variable MUST NOT be set anymore. Session is not active by default, it can only be activated if the Session Middleware is explicitly declared. |
tls |
No | Object | Configure paths to files with a certificate and key with certFile and keyFile. |
middlewares |
No | List of middlewares | Middlewares can be attached to each entrypoint, which are first processed before a request is forwarded to the route-specific middlewares. |
Providers¶
The Uniport-Gateway can be configured via Providers.
The following Provider types are supported:
- File (name =
file) - Docker Container (name =
docker)
File Provider¶
The Uniport-Gateway can be configured using a JSON file via the File Provider. Configuration via a single file or multiple files is supported.
Keep it DRY
The File Provider can be used for the reuse of configurations.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
name |
Yes | String | - | Type of the Provider, here file |
filename |
Yes or directory |
String | - | Defines the path to a configuration file |
directory |
Yes or filename |
String | - | Defines the path to the directory which contains the configuration files. It is important to understand how multiple configuration files are merged: In general, with a deep-merge (recursive) JSON objects are matched within the existing structure and all matching entries are replaced. JsonArrays are treated like any other entry, i.e., completely replaced. This pattern is applied to all files that are in the same directory. For more complex configurations, we offer a merge mechanism over subdirectories. Subdirectories are largely merged in the same way as described above, with the exception of JsonArrays. JsonArrays are concatenated without duplicates. The names of the subdirectories do not matter and can be used for organizational purposes. |
watch |
Yes | Boolean | - | Set watch option to true to automatically react to file changes. |
Docker Provider¶
With Docker, Container Labels can be used to configure routing in the Uniport-Gateway.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
endpoint |
No | String | unix:///var/run/docker.sock |
The Uniport-Gateway needs access to the Docker Socket to read the dynamic configuration. The Docker API endpoint can be defined via this variable. |
exposedByDefault |
No | Boolean | true |
Exposes the container by default via the Uniport-Gateway. If set to false, containers without the portal.enable=true label are ignored. |
network |
No | String | "" |
Defines the Default Network that is used for connecting with the containers. |
defaultRule |
No | String | Host('${name}') |
Defines which routing rule is applied to the container if the container does not define one. The rule must be a valid StringSubstitutor. The Container Service Name can be retrieved via the ${name} variable and the StringSubstitutor has access to any Labels that are defined for this container. |
IP/Port Detection¶
The private IP and port of a container are queried from the Docker API.
Network and IP selection works as follows:
The port is chosen as follows:
- If the container exposes no port, the container is ignored.
- If the container exposes one port, this port is used.
- If the container exposes multiple ports, the port must be manually set with the label
portal.http.service.<service-name>.server.port.
Security Notice
Access to the Docker API without restrictions is not recommended according to OWASP. If the Uniport-Gateway is attacked, the attacker can gain access to the underlying host:
[...] only trusted users shouls be allowed to control your Docker dameon [...]
Solution
The Docker Socket can also be exposed via SSH. SSH is supported with Docker > 18.09.
Configuration Interval¶
Status: Functionality is available, but cannot yet be configured (the Default is used).
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
providersThrottleDuration |
No | Integer | 2000 |
Interval in milliseconds in which the configuration should be re-read from the Providers. |
In some cases, some Providers can publish many configuration changes at once. This would generate more change events in the Uniport-Gateway than necessary. To circumvent this problem, this option can be set. It defines how long the Uniport-Gateway waits after a re-configuration before it makes new changes. If multiple change events are registered during this time, only the most current one is considered and the rest ist ignored. This option can only be set globally for all Providers, but is applied individually for each Provider.
Provider Namespace¶
When specific resources are declared in the dynamic configuration, e.g., Middlewares and Services, they are located in their Provider's namespace. For example, if a Middleware is declared with a Docker label, the Middleware is in the Docker Provider's namespace.
If multiple Providers are used and such a resource is to be referenced that was declared by another Provider (e.g., a cross-Provider resource like a Middleware), the resource should be provided with the separator "@" and the Provider name.
<resource-name>@<provider-name>
Dynamic Configuration¶
Dynamic configuration contains all the definitions for how the Uniport-Gateway processes requests. This configuration can be changed during runtime.
Routers¶
A Router is responsible for forwarding incoming requests to services that can process them further. During this process, routers can use Middlewares to update or modify requests before forwarding them to the Service.
| Variable | Required | Type | Description |
|---|---|---|---|
name |
Yes | String | Name of the router. |
entrypoints |
No | List of Strings | If not specified, the router will accept requests from all defined entrypoints. If the router scope should be limited to a set of entrypoints, set the Entrypoint option. |
rule |
No | Path('/example')PathPrefix('/example')PathRegex('/(de\|en)/example')Host('example.com')HostRegex('foo(bar)?') |
A rule is a matcher configured with values. These values determine whether a specific request matches a specific criterion. Once the rule is verified, the router becomes active. It notifies middlewares and forwards the request to the services. |
priority |
No | Integer | Routers are sorted by default to avoid path overlaps. They are sorted in descending order by the length of the rules. The priority is therefore equal to the length of the rules, so the longest rule has the highest priority. |
middlewares |
No | List of Strings | A router can have a list of middlewares. Middlewares become active only if the rule matches and before the request is forwarded to the services. Middlewares are activated in the same order they're listed in the router configuration (Forced Ranking). |
service |
Yes | String | Every request must ultimately be processed by a service. Therefore, every router definition should include a service target, which essentially describes where the request is delivered. Generally, a service assigned to a router should be defined. However, there are exceptions for label-based providers. See the specific Docker documentation. |
Example
{
"name": "testRouter",
"middlewares": ["md1", "md2", "md3"],
"rule": "Path('/')",
"priority": 42,
"service": "testService"
}
Warning
The character @ is not allowed in a middleware's name.
Middlewares¶
Middlewares are attached to Routers and optimize requests before they're sent to a Service (or before the response from a Service is sent back to the Client).
Middlewares can be chained together, allowing for any scenario.
A Middleware's configuration always consists of a name, a type, and an optional options field. The options field is always an object type, and its structure depends on the specific Middleware Type.
Example
{
"name": "testMiddleware",
"type": "authorizationBearer",
"options": { "sessionScope": "testScope" }
}
authorizationBearer¶
The Authorization Bearer sets a token in the HTTP header Authorization: Bearer <token> depending on the Session Scope. It's tightly coupled with the OAuth2 Middleware as it uses tokens acquired through the authentication process.
| Variable | Required | Type | Description |
|---|---|---|---|
sessionScope |
Yes | id (referencing a session scope defined by an OAuth2 middleware) | The Session Scope determines which token should be set in the Auth Bearer Header. This can be either an ID token or an Access token. Per user, there is one ID token and zero or more Access tokens. |
backchannellogout¶
This middleware implements the OIDC back-channel logout. The IAm may send a request to the router this middleware is configured for to logout a specific user i.e. destroy its session. This covers the use-case of an admin clearing a session in the IAM that also clears the corresponding session in the Uniport-Gateway.
The middleware answers invalid back-channel logout requests with a 400 Bad Request.
| Variable | Required | Type | Description |
|---|---|---|---|
publicKeys |
Yes | Array | An array of Public Keys. Each element (Public Key) in the array is an object with the fields publicKey and optionally publicKeyAlgorithm. The publicKey value is a public key in PEM format (without header/footer) or a URL for retrieving the public key from Portal-IAM (e.g., http://portal-iam:8080/auth/realms/portal). The OIDC Discovery mechanism is used for this, and all public keys found under the jwks_uri URL that are used to create a signature are loaded. If the publicKey contains a public key in PEM format, the publicKeyAlgorithm value can also be set; otherwise, the default RS256 is used. |
issuer |
Yes | String | Value that the sub claim in the JWT must possess. |
additionalIssuers |
No | Array | An array of additional Issuers. If other Issuers besides the main issuer are relevant, they can be declared in this array. |
audience |
Yes | String | Value that the aud claim in the JWT must possess. |
claims |
No | Array | An array of requirements that each JWT must fulfill. Each element (requirement) in the array is an object with the fields claimPath, operator, and value. The value at the path claimPath, specified as JSONPath (https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/) in bracket-notation, is compared with the claim in the JWT at that path according to the operator. The following operators are currently supported:EQUALS: The value in the JWT claim must match value.CONTAINS: The value in the JWT claim must be contained in value. value must necessarily be specified as a list!EQUALS_SUBSTRING_WHITESPACE and CONTAINS_SUBSTRING_WHITESPACE: Instead of specifying value as a list, elements can be separated by whitespace. |
publicKeysReconciliation |
No | Object | An object with the two fields enabled and intervalMs. By default, enabled is true and intervalMs is 600000 (1h). Basically, the bearerOnly middleware loads the current Public Keys used by Portal-IAM to sign JWTs from Portal-IAM when the Uniport-Gateway starts. The Public Key Reconciler can automatically update the Public Keys at a defined interval. This ensures that in the event of a key rollover, the Uniport-Gateway does not need to be restarted, but automatically knows and accepts the new Public Keys. Additionally, in the case of an Unauthorized Request (401), the Public Keys are automatically updated and the Request is repeated once. This ensures that JWTs signed with a new Public Key, but where the reconciliation interval has not yet expired, can still be verified. |
Example
{
"name": "backChannelLogout",
"type": "backChannelLogout",
"options": {
"publicKeys": [
{
"publicKey": "${UNIPORT_GATEWAY_BEARER_TOKEN_PUBLIC_KEY}"
}
],
"audience": [
"Uniport-Gateway"
],
"issuer": "${UNIPORT_GATEWAY_BEARER_TOKEN_ISSUER}",
"publicKeysReconciliation": {
"enabled": true,
"intervalMs": 3600000
}
}
}
bearerOnly¶
This Middleware checks every incoming request for an HTTP Authorization header containing a valid JWT. For validity, the signature and the claims for Issuer and Audience are checked. The values used for this are defined via options:
| Variable | Required | Type | Description |
|---|---|---|---|
publicKeys |
Yes | Array | An array of Public Keys. Each element (Public Key) in the array is an object with the fields publicKey and optionally publicKeyAlgorithm. The publicKey value is a public key in PEM format (without header/footer) or a URL for retrieving the public key from Portal-IAM (e.g., http://portal-iam:8080/auth/realms/portal). The OIDC Discovery mechanism is used for this, and all public keys found under the jwks_uri URL that are used to create a signature are loaded. If the publicKey contains a public key in PEM format, the publicKeyAlgorithm value can also be set; otherwise, the default RS256 is used. |
issuer |
Yes | String | Value that the sub claim in the JWT must possess. |
additionalIssuers |
No | Array | An array of additional Issuers. If other Issuers besides the main issuer are relevant, they can be declared in this array. |
audience |
Yes | String | Value that the aud claim in the JWT must possess. |
claims |
No | Array | An array of requirements that each JWT must fulfill. Each element (requirement) in the array is an object with the fields claimPath, operator, and value. The value at the path claimPath, specified as JSONPath (https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/) in bracket-notation, is compared with the claim in the JWT at that path according to the operator. The following operators are currently supported:EQUALS: The value in the JWT claim must match value.CONTAINS: The value in the JWT claim must be contained in value. value must necessarily be specified as a list!EQUALS_SUBSTRING_WHITESPACE and CONTAINS_SUBSTRING_WHITESPACE: Instead of specifying value as a list, elements can be separated by whitespace. |
publicKeysReconciliation |
No | Object | An object with the two fields enabled and intervalMs. By default, enabled is true and intervalMs is 600000 (1h). Basically, the bearerOnly middleware loads the current Public Keys used by Portal-IAM to sign JWTs from Portal-IAM when the Uniport-Gateway starts. The Public Key Reconciler can automatically update the Public Keys at a defined interval. This ensures that in the event of a key rollover, the Uniport-Gateway does not need to be restarted, but automatically knows and accepts the new Public Keys. Additionally, in the case of an Unauthorized Request (401), the Public Keys are automatically updated and the Request is repeated once. This ensures that JWTs signed with a new Public Key, but where the reconciliation interval has not yet expired, can still be verified. |
Additionally:
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
optional |
No | Boolean | false | Switch whether the Authorization header in the request is mandatory. |
Tip
Specifying issuer and audience is optional. If the Authorization header as a whole should be optional, this can be defined in the optional field with true.
Example
{
"name": "bearerOnly",
"type": "bearerOnly",
"options": {
"publicKey": "https://portal-iam:8080/auth/realms/portal",
"publicKeyAlgorithm": "RS256",
"issuer": "${PROXY_BEARER_TOKEN_ISSUER}",
"audience": ["Uniport-Gateway", "Navigation"],
"optional": false,
"claims": [
{
"claimPath": "$['organisation']",
"operator": "EQUALS",
"value": "portal"
},
{
"claimPath": "$['resource_access']['Organisation']['roles']",
"operator": "CONTAINS",
"value": ["ADMINISTRATOR", "TENANT"]
},
{
"claimPath": "$['https://hasura.io/jwt/claims']",
"operator": "EQUALS",
"value": {
"x-hasura-allowed-roles": ["KEYCLOAK", "portaluser"],
"x-hasura-portaluser-id": "1234"
}
}
],
"publicKeysReconciliation": {
"enabled": "true",
"intervalMs": "600000"
}
}
}
bodyHandler¶
The Bodyhandler Middleware is currently only used/needed in the context of CSRF and should only be set if necessary.
checkJwt¶
This Middleware is similar to the bearerOnly Middleware, but loads the id/access token associated with the configured session scope, instead of using the bearer token in the authorization header. It validates and checks the token according to the configured JWT options.
| Variable | Required | Type | Description |
|---|---|---|---|
publicKeys |
Yes | Array | An array of Public Keys. Each element (Public Key) in the array is an object with the fields publicKey and optionally publicKeyAlgorithm. The publicKey value is a public key in PEM format (without header/footer) or a URL for retrieving the public key from Portal-IAM (e.g., http://portal-iam:8080/auth/realms/portal). The OIDC Discovery mechanism is used for this, and all public keys found under the jwks_uri URL that are used to create a signature are loaded. If the publicKey contains a public key in PEM format, the publicKeyAlgorithm value can also be set; otherwise, the default RS256 is used. |
issuer |
Yes | String | Value that the sub claim in the JWT must possess. |
additionalIssuers |
No | Array | An array of additional Issuers. If other Issuers besides the main issuer are relevant, they can be declared in this array. |
audience |
Yes | String | Value that the aud claim in the JWT must possess. |
claims |
No | Array | An array of requirements that each JWT must fulfill. Each element (requirement) in the array is an object with the fields claimPath, operator, and value. The value at the path claimPath, specified as JSONPath (https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/) in bracket-notation, is compared with the claim in the JWT at that path according to the operator. The following operators are currently supported:EQUALS: The value in the JWT claim must match value.CONTAINS: The value in the JWT claim must be contained in value. value must necessarily be specified as a list!EQUALS_SUBSTRING_WHITESPACE and CONTAINS_SUBSTRING_WHITESPACE: Instead of specifying value as a list, elements can be separated by whitespace. |
publicKeysReconciliation |
No | Object | An object with the two fields enabled and intervalMs. By default, enabled is true and intervalMs is 600000 (1h). Basically, the bearerOnly middleware loads the current Public Keys used by Portal-IAM to sign JWTs from Portal-IAM when the Uniport-Gateway starts. The Public Key Reconciler can automatically update the Public Keys at a defined interval. This ensures that in the event of a key rollover, the Uniport-Gateway does not need to be restarted, but automatically knows and accepts the new Public Keys. Additionally, in the case of an Unauthorized Request (401), the Public Keys are automatically updated and the Request is repeated once. This ensures that JWTs signed with a new Public Key, but where the reconciliation interval has not yet expired, can still be verified. |
| Variable | Required | Type | Description |
|---|---|---|---|
sessionScope |
Yes | id (referencing a session scope defined by an OAuth2 middleware) | The Session Scope determines which token should be set in the Auth Bearer Header. This can be either an ID token or an Access token. Per user, there is one ID token and zero or more Access tokens. |
checkRoute¶
The checkRoute Middleware of type checkRoute returns a 202 status code directly for a request if the value _check-route_ is found in the request's URI. Otherwise, this Middleware ignores the request.
Example
{
"name": "CheckAuthMiddleware",
"type": "checkRoute"
}
If this Middleware is placed in a route that requires authentication, it can be used on the client side to check if a valid session exists. This is because if the client receives a 200 status code, this could also be the result of the forwarded login page. Typically, this route is called when the client receives an error status code (e.g., 405) for a POST request. After checking the session with checkRoute, it can be decided whether the POST request should be called again.
claimToHeader¶
The ClaimToHeader Middleware can set the value of a JWT Claim as an HTTP Header, allowing other components to read the value directly from the header.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
claimPath |
Yes | JsonPath | - | Path to the claim. |
headerName |
Yes | String | - | Name of the header. |
Example
{
"name": "tenantFromJwtToHeader",
"type": "claimToHeader",
"options": {
"claimPath": "tenant",
"headerName": "X-Uniport-Tenant"
}
}
cors¶
The CORS Middleware implements the CORS protocol. CORS is a secure mechanism for sharing resources that are requested from one host but provided by another. This middleware also deduplicates headers if they are present multiple times.
Further information on CORS can be found here:
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
allowedOrigins |
No | String Array | - | Set the list of allowed origins. An origin follows rfc6454#section-7 and is expected to have the format: <scheme> "://" <hostname> [ ":" <port> ]. |
allowedOriginPatterns |
No | Regex String Array | - | Set the list of allowed relative origins. A relative origin is a regex that should match the format <scheme> "://" <hostname> [ ":" <port> ]. |
allowedMethods |
No | String Array | - | Set the list of allowed methods. A method is one of GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS, TRACE, CONNECT. |
allowedHeaders |
No | String Array | - | Set the list of allowed headers. |
exposedHeaders |
No | String Array | - | Set the list of exposed headers. |
maxAgeSeconds |
No | Int | - | Set how long the browser should cache the information. The default implemented by the browser is 5 seconds if there's no max age header. The maximum is browser-specific (Firefox: 24h, Chromium: 2h). |
allowCredentials |
No | Boolean | false | Set whether credentials are allowed. Note that user agents will block requests that use a wildcard as origin and include credentials. |
allowPrivateNetwork |
No | Boolean | false | Set whether access from public to private networks is allowed. |
If neither allowedOrigins nor allowedOriginPatterns is configured, this implies a */Wildcard (all Origins are allowed) configuration.
Example
{
"name": "cors",
"type": "cors",
"options": {
"allowedOrigins": [
"https://example.com"
],
"allowedOriginPatterns": [
"https://.*\\.example.com"
],
"allowedMethods": [
"GET",
"POST",
"PUT",
"DELETE"
],
"allowedHeaders": [
"foo"
],
"exposedHeaders": [
"bar"
],
"maxAgeSeconds": 42,
"allowCredentials": true,
"allowPrivateNetwork": true
}
}
cspViolationReportingServer¶
The Content Security Policy supports Reporting Directives for reporting CSP Violations. The cspViolationReportingServer Middleware can be used as a Reporting Server, which can be set via the report-uri or report-to Directive. It logs all CSP Violation Reports at the configured Log Level.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
logLevel |
No | TRACE, DEBUG, INFO, WARN, ERROR |
WARN |
Log Level for CSP Violation Reports. |
Example
With the following configuration, a CSP Violation Reporting Server is created at the path /csp-reports.
{
"http": {
"routers": [
{
"name": "csp-violation-reporting-server",
"middlewares": [
"cspViolationReportingServer"
],
"rule": "Path('/csp-reports')",
"service": "dashboard-proxy"
}
],
"middlewares": [
{
"name": "cspViolationReportingServer",
"type": "cspViolationReportingServer",
"options": {
"logLevel": "ERROR"
}
}
],
"services": []
}
}
This can be used together with a csp Middleware:
{
"name": "csp",
"type": "csp",
"options": {
"policyDirectives": [
{
"directive": "report-uri",
"values": [
"/csp-reports"
]
},
{
"directive": "report-to",
"values": [
"/csp-reports"
]
},
[...]
]
}
}
Difference between report-uri and report-to Directives
There are report-uri and report-to as Reporting Directives available for reporting CSP Violations. report-uri is deprecated and should no longer be used. However, not all browsers (especially Firefox) support the report-to Reporting Directive yet, and therefore both Reporting Directives can/must/should be set to cover all browsers. Browsers that already support report-to ignore report-uri, and browsers that do not yet support report-to use report-uri and may log a warning that an unknown Directive report-to is being used.
csrf¶
The CSRF Middleware implements the Double Submit Cookie strategy to counteract Cross-Site Request Forgery attacks. The Middleware generates a CSRF cookie with every GET request, whose value must then be set in POST, PUT, PATCH, DELETE requests, either in the header or in the body.
Bug
When using the CSRF middleware, it's assumed that the Session middleware and Bodyhandler middleware are processed before the CSRF middleware. However, the Bodyhandler middleware currently doesn't work and shouldn't be used; this results in a 500 HTTP error being returned instead of a 403 when a CSRF token is missing.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
timeoutInMinute |
No | Int | 15 |
CSRF cookie timeout in minutes. |
nagHttps |
No | Boolean | true |
If the request comes over HTTP (instead of HTTPS), it should be logged. |
headerName |
No | String | X-XSRF-TOKEN |
Name of the header where the value of the CSRF cookie is expected. |
origin |
No | String | Sets the origin for this server. If this value is set, an additional check takes place. The request must match the origin server, port, and protocol. | |
cookie |
No | Object | CSRF cookie configuration. | |
cookie.name |
No | String | XSRF-TOKEN |
Name of the CSRF cookie. |
cookie.path |
No | String | / |
Path of the CSRF cookie. |
cookie.secure |
No | Boolean | true |
If this flag is set, browsers are instructed to send the cookie only over HTTPS. Note that this will likely cause sessions to stop working if used without HTTPS (e.g., in development). |
Example
{
"name": "csrf",
"type": "csrf",
"options": {
"timeoutInMinute": 15,
"nagHttps": true,
"headerName": "X-Uniport-XSRF-TOKEN",
"cookie": {
"name": "uniport.xsrf-token",
"secure": false
}
}
}
customResponse¶
This Middleware allows hardcoded responses to be returned to the client. The following options are supported:
| Variable | Required | Type | Description |
|---|---|---|---|
content |
Yes | String | - |
statusCode |
Yes | Integer | - |
headers |
No | Pairs of header name and value | - |
headers¶
The Headers Middleware manages the headers of incoming requests and outgoing responses.
| Variable | Required | Type | Description |
|---|---|---|---|
customRequestHeaders |
No | Pairs of header name and value | - |
customResponseHeaders |
No | Pairs of header name and value | - |
languageCookie¶
The Language Cookie Middleware adds the value (ISO code) of the (by default: uniport.language) cookie to the Accept-Language header. This overwrites the previous Accept-Language header. If no Language Cookie is present in the request, nothing is changed.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
name |
No | String | uniport.language |
Name of the Language Cookie. |
Example
{
"name": "languageCookie",
"type": "languageCookie"
}
matomo¶
The Matomo Middleware should be used exclusively with the Analytics microservie. In combination with our AutoLogin Feature for Matomo, it enables automatic login based on request header values. The Middleware reads the Access Token from the Authorization Header and then sets the required values in the Request Header. The path to the values must be specified as a JsonPath.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
pathRoles |
No | JsonPath | $.resource_access.Analytics.roles |
Path to the roles. |
pathGroup |
No | JsonPath | $.tenant |
Path to the group name. |
pathUsername |
No | JsonPath | $.preferred_username |
Path to the username. |
pathEmail |
No | JsonPath | $.email |
Path to the email address. |
oauth2¶
This Middleware enables user authentication through a Keycloak server instance. It intercepts all requests and redirects them for authentication if necessary. After successful authentication, it stores the ID token and access token under the given session scope.
Specifications:
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
clientId |
Yes | String | Provider client ID. | |
clientSecret |
Yes | String | Provider client secret. | |
discoveryUrl |
Yes | URL | Provider discovery URL. For Keycloak, usually https://keycloak.ch/auth/realms/<your-realm>. |
|
sessionScope |
Yes | String | - | |
additionalScopes |
No | Array | empty | Additional OAuth2 scopes. |
responseMode |
No | query, fragment, form_post | form_post | Default is form_post. |
proxyAuthenticationFlow |
No | boolean | true | If set, the authorization_path and issuer are patched with the Uniport-Gateway's public URL (see publicUrl). This ensures that the authentication process is always handled via the Uniport-Gateway. |
publicUrl |
No | URL | empty | To correctly set the redirect_uri for the OAuth2 callback, the middleware needs to know the URL under which the Uniport-Gateway is accessible. This setting is also used to patch OIDC URLs, e.g. authorization_endpoint and issuer, to ensure all Keycloak related traffic is routed through the Gateway. Generally, the environment variables UNIPORT_GATEWAY_PUBLIC_PROTOCOL, UNIPORT_GATEWAY_PUBLIC_HOSTNAME, and UNIPORT_GATEWAY_PUBLIC_PORT are used for this. However, these can be overridden per middleware with this property. This is necessary, if oauth2 middlewares are used under different hosts. |
callbackOrigin |
No | URL | publicUrl |
By default, the callback URL points to the same host as specified with the publicUrl. However, in the scenario where there should be a shared SSO session with Keycloak (e.g. auth.uniport.ch) in the Gateway for multiple hosts routed through the Gateway, e.g. uniport.ch and inventage.com, the callback URL needs to be configurable to point to the corresponding domain. This requires responseMode to be set to something else than form_post (e.g.query), as the session cookie would otherwise not be sent with the callback request (due to its SameSite attribute st to STRICT). Alternatively, the session cookie can be configured with SameSite=NONE and secure |
jwtExpirationLeeway |
No | Integer | 0 | To allow a grace period in seconds if the clocks are not reliable. |
additionalParameters |
No | Map | empty | Additional OAuth2 authentication request parameters. |
passthroughParameters |
No | Array | empty | This list is a whitelist for parameter keys that are added to the OAuth2 Auth Request Parameters if they are present in the initial request that triggered the Auth Request. If a parameter key appears multiple times in the initial request, only the first parameter value is added. The passthroughParameters overwrite parameters added by additionalParameters. This option is useful if dynamic information needs to be passed to the Authorization Provider, such as kc_idp_hint. |
Warning
It's recommended to use responseMode=form_post only when it's guaranteed that the first loaded resource has the MIME type text/html.
Note
The oauth2 Middleware generates an internal callback URL in the format /callback/<sessionScope> to obtain the Authorization Code from Keycloak.
oauth2registration¶
This Middleware enables the direct invocation of user self-registration at a Keycloak server instance. It intercepts all requests and redirects them for registration if necessary. After successful registration, it stores the ID token and access token under the given session scope.
The oauth2registration accepts the same configuration parameters as the oauth2 Middleware.
passAuthorization¶
This Middleware is a combination of authorization and bearerOnly Middlewares and accepts a superset of both Middleware configurations. It checks whether the incoming request has an Access Token in its session under the configured sessionScope (authorization) that fulfills the configured JWT claims (bearerOnly).
The name already indicates that the value in the Authorization header of the incoming request is not modified but passed through as is. This is necessary if a frontend already sends JWTs that need to reach the backend, but the Uniport-Gateway still wants to enforce authN/authZ.
| Variable | Required | Type | Description |
|---|---|---|---|
publicKeys |
Yes | Array | An array of Public Keys. Each element (Public Key) in the array is an object with the fields publicKey and optionally publicKeyAlgorithm. The publicKey value is a public key in PEM format (without header/footer) or a URL for retrieving the public key from Portal-IAM (e.g., http://portal-iam:8080/auth/realms/portal). The OIDC Discovery mechanism is used for this, and all public keys found under the jwks_uri URL that are used to create a signature are loaded. If the publicKey contains a public key in PEM format, the publicKeyAlgorithm value can also be set; otherwise, the default RS256 is used. |
issuer |
Yes | String | Value that the sub claim in the JWT must possess. |
additionalIssuers |
No | Array | An array of additional Issuers. If other Issuers besides the main issuer are relevant, they can be declared in this array. |
audience |
Yes | String | Value that the aud claim in the JWT must possess. |
claims |
No | Array | An array of requirements that each JWT must fulfill. Each element (requirement) in the array is an object with the fields claimPath, operator, and value. The value at the path claimPath, specified as JSONPath (https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/) in bracket-notation, is compared with the claim in the JWT at that path according to the operator. The following operators are currently supported:EQUALS: The value in the JWT claim must match value.CONTAINS: The value in the JWT claim must be contained in value. value must necessarily be specified as a list!EQUALS_SUBSTRING_WHITESPACE and CONTAINS_SUBSTRING_WHITESPACE: Instead of specifying value as a list, elements can be separated by whitespace. |
publicKeysReconciliation |
No | Object | An object with the two fields enabled and intervalMs. By default, enabled is true and intervalMs is 600000 (1h). Basically, the bearerOnly middleware loads the current Public Keys used by Portal-IAM to sign JWTs from Portal-IAM when the Uniport-Gateway starts. The Public Key Reconciler can automatically update the Public Keys at a defined interval. This ensures that in the event of a key rollover, the Uniport-Gateway does not need to be restarted, but automatically knows and accepts the new Public Keys. Additionally, in the case of an Unauthorized Request (401), the Public Keys are automatically updated and the Request is repeated once. This ensures that JWTs signed with a new Public Key, but where the reconciliation interval has not yet expired, can still be verified. |
| Variable | Required | Type | Description |
|---|---|---|---|
sessionScope |
Yes | id (referencing a session scope defined by an OAuth2 middleware) | The Session Scope determines which token should be set in the Auth Bearer Header. This can be either an ID token or an Access token. Per user, there is one ID token and zero or more Access tokens. |
redirectRegex¶
Redirect Regex redirects a request using regex matching and replacement.
| Variable | Required | Type | Description |
|---|---|---|---|
regex |
Yes | String | The regex option is the regular expression used to match and capture elements from the request URL. |
replacement |
Yes | String | The Replacement option defines how the URL should be changed to obtain the new target URL. |
replacePathRegex¶
Replace Path Regex replaces the path of a URL using regex matching and replacement.
| Variable | Required | Type | Description |
|---|---|---|---|
regex |
Yes | String | The regex option is the regular expression used to match and capture the path from the request URL. |
replacement |
Yes | String | The replacement option defines the format of the replacement path, which can contain captured variables. |
responseHeadersOnStatus¶
This middleware modifies response headers only when the response status code matches the configured value.
| Variable | Required | Type | Description |
|---|---|---|---|
statusCode |
Yes | Integer | Status code when the header should be modified |
setResponseHeaders |
No | Object | Configuration for setting a header |
rewriteResponseHeaders |
No | Object | Configuration for rewriting a reader |
The following example rewrites the header WWW-Authenticate by the given regex.
{
"name": "wwwAuth",
"type": "responseHeadersOnStatus",
"options": {
"statusCode": 401,
"rewriteResponseHeaders": {
"WWW-Authenticate": {
"regex": "(resource_metadata=\"[^\"]*\\.well-known/oauth-protected-resource)(\")",
"replacement": "$1/organisation/mcp$2"
}
}
}
}
_session_¶
This Middleware is intended for development only. It displays the current session content, including the Session ID, every cookie, and, if logged in, the ID token (encoded and decoded), and every Access token (encoded and decoded).
Example
{
"name": "ShowSessionContentMiddleware",
"type": "_session_"
}
Entry-Middlewares¶
The following Entry-Middlewares are typically attached to Entrypoints instead of Routers. However, it's also possible to use Entry-Middlewares as regular (Route-)Middlewares and vice versa. Entry-Middlewares are defined in the static configuration.
csp¶
Bug
Directives without values (e.g., sandbox) are only supported from Uniport-Gateway Version 8.2.0+. Microservices (e.g., Portal-Monitoring) that return such directives as CSP-Policies will not function, meaning the Gateway cannot load the resources on that path.
With the Content Security Policy Middleware, we can define which resources the browser is allowed to load. As of Uniport-Gateway Version 8.1.0+, it is now possible to define multiple consecutive CSP-Middlewares for a route: e.g., general CSP-Policies on the Entry-Middleware and specific/restrictive CSP-Policies on each specific route. The union of all CSP-Policies will then be enforced.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
reportOnly |
No | Boolean | false | Activates Content-Security-Policy-Report-Only. If active, report-to must also be present as a directive. |
policyDirectives |
Yes | Array | - | An array of Policy Directives. Each element (Directive) in the array is an object with the fields directive and values, where values is an array of multiple or single values. |
mergeStrategy |
No | UNION, INTERNAL, EXTERNAL |
UNION |
The mergeStrategy determines how external CSP-Policies (e.g., set by Portal IAM) are used. External CSP-Policies are read from the Content-Security-Policy and Content-Security-Policy-Report-Only headers and combined/union. With UNION, the union of the Middleware CSP Policies and the external CSP Policies is enforced as the final CSP Policy. With EXTERNAL, the external CSP-Policy is enforced, and with INTERNAL, the Middleware-Policy. |
Warning
In the case of multiple CSP-Middlewares for a route, the last middleware processed determines the mergeStrategy between external and internal CSP-Policies. In the schema below, that is the CSP2 Middleware or Resp2.

Example
{
"name": "csp",
"type": "csp",
"options": {
"policyDirectives": [
{
"directive": "default-src",
"values": ["self"]
},
{
"directive": "style-src",
"values": ["https://fonts.googleapis.com", "https://fonts.gstatic.com", "self", "unsafe-inline"]
}
]
}
}
Note
A keen eye might have observed that the CSP-Policies are already configured and combined on the Request (CSP1/Req1 and CSP2/Req2 in the schema above). This ensures that CSP-Policies are enforced even if a Request is terminated by any Middleware and does not reach the Backend.
openTelemetry¶
This Middleware ensures that the keys traceId and sessionId are available in the logging context. These are then used in the configuration of the Log Pattern via %vcl{traceId} so that every log output contains these two values.
Additionally, the Middleware sets the HTTP Header X-Uniport-Trace-Id in the HTTP Response, which also contains the value of the traceId.
replacedSessionCookieDetection¶
This Middleware processes all requests from the browser that are sent during the session ID regeneration (see figure). These requests are recognized by the Middleware using a "detection" cookie, whose name is configurable ("name"), and then sent back as a Redirect (=retry) Response to the same URL (no new session ID is included). However, the Redirect Response is only sent back after a configurable timeout ('waitTimeInMillisecond'), in the hope that the new session ID has arrived at the browser in the meantime.

| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
name |
No | String | uniport.state |
Name of the "detection" cookie. |
waitTimeInMillisecond |
No | String | 50 |
Timeout for the Redirect Response in milliseconds. |
maxRedirectRetries |
No | Int | 5 |
Maximum number of redirect attempts before requests are let through. |
responseSessionCookieRemoval¶
This Middleware is (as of today: October 19, 2022) only used in combination with the replacedSessionCookieDetection Middleware. If requests are sent during session ID regeneration, they are sent back by the replacedSessionCookieDetection Middleware as a Redirect (=retry) Response to the same URL. This Middleware removes the "outdated" session cookie during the Redirect Response, as the browser could otherwise overwrite the new session cookie with the old one.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
name |
No | String | uniport.session |
Name of the session cookie. Must match cookie.name. |
requestResponseLogger¶
Logs each request and/or response and adds the requestId and sessionId to the contextual data. Depending on the configured Log-Level, more or fewer details of the Request/Response are logged.
Tip
Only on the TRACE Log-Level will the Body be logged.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
contentTypes |
No | String Array | [] | Logs the Body of the specified Content-Types. |
loggingRequestEnabled |
No | Boolean | True | An array of Policy Directives. Each element (Directive) in the array is an object with the fields directive and values, where values is an array of multiple or single values. |
loggingResponseEnabled |
No | Boolean | True | An array of Policy Directives. Each element (Directive) in the array is an object with the fields directive and values, where values is an array of multiple or single values. |
session¶
The Session Middleware manages browser sessions. Each request can be associated with a session based on the session cookie, which is also issued by this middleware. Details on session management can be configured.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
idleTimeoutInMinutes |
No | Int | 15 |
Session timeout in minutes. |
idMinimumLength |
No | Int | 32 |
Minimum length of the session ID. |
nagHttps |
No | Boolean | true |
If the request comes over HTTP (instead of HTTPS), it should be logged. |
lifetimeHeader |
No | Boolean | false |
If this flag is set, the x-uniport-session-lifetime header with the session expiration will be returned in the response. |
lifetimeCookie |
No | Boolean | false |
If this flag is set, the uniport.session-lifetime cookie with the session expiration will be returned in the response. |
uriWithoutSessionTimeoutReset |
No | Regex | Regex for URIs for which requests should not trigger a session timeout reset (e.g., *._polling_.*). |
|
cookie |
No | Object | Session cookie configuration. | |
cookie.name |
No | String | uniport.session |
Name of the session cookie. Must match name. |
cookie.httpOnly |
No | Boolean | true |
If this flag is set, browsers are instructed to prevent Javascript access to the cookie. This serves as protection against the most common XSS attacks. |
cookie.secure |
No | Boolean | false |
If this flag is set, browsers are instructed to send the cookie only over HTTPS. Note that this will likely cause sessions to stop working if used without HTTPS (e.g., in development). |
cookie.sameSite |
No | String | STRICT |
SameSite policy for the session cookie. Possible values: NONE: The browser sends cookies for both cross-site and same-site requests, STRICT: The browser only sends cookies for same-site requests. If the request originates from a different URL than the current location's URL, none of the cookies marked with the Strict attribute are sent., LAX: Same-site cookies are not sent with cross-site subrequests, e.g., requests for loading images or frames. But they are sent when a user navigates to the URL from an external site, e.g., by following a link. |
clusteredSessionStoreRetryTimeoutInMilliseconds |
No | Int | 5000 |
Retry Timeout, in milliseconds, if a session is not found in the clustered session store. |
Example
{
"name": "session",
"type": "session",
"options": {
"idleTimeoutInMinutes": 15,
"idMinimumLength": 32,
"nagHttps": true,
"cookie": {
"name": "uniport.session",
"httpOnly": true,
"secure": false,
"sameSite": "STRICT"
},
"clusteredSessionStoreRetryTimeoutInMilliseconds": 5000
}
}
sessionBag¶
The Session Bag is implemented as a Middleware. It should be set as the last Entry-Middleware in the chain and is responsible for cookie handling. The User-Agent generally sees no other cookie than the Vert.x session cookie. The Session Bag manages all cookies related to this session. It intercepts responses from services, deletes, and stores them. These cookies are then re-added to future requests from the same session, so that services do not notice a difference.
Exceptions can be configured so that cookies are also returned to the User-Agent.
| Variable | Required | Type | Description |
|---|---|---|---|
whitelistedCookies |
No | Array | Each element in the array is an object with the fields name and path. The name field defines the name and path defines the path of the cookie. If there is a match, the cookie is returned to the User-Agent. |
An exception to this rule is the Keycloak session cookie for the Master Realm. This is the only cookie, apart from the Vert.x session cookie, that is passed to the User-Agent. This is required for some Keycloak pages.

Services¶
Services are responsible for configuring how the actual services are reached, which then process incoming requests.
| Variable | Required | Type | Default | Description |
|---|---|---|---|---|
name |
Yes | String | - | Name of the service. |
servers |
Yes | List of tuple of protocol (optional http (=default) or https) host (String), port (Integer) and httpsOptions (Object) |
- | Servers specify a single instance of your program. The Host, Port, and Https options point to a specific instance. |
verbose |
No | Boolean | false | If enabled, all outgoing requests and incoming responses are logged. A log level of INFO or higher is required for this (DEBUG/TRACE). |
httpsOptions |
No | Object | Defines the outgoing HTTPS connection. The httpOptions only take effect if the protocol is set to https. |
|
httpsOptions.verifyHostname |
No | Boolean | true | Checks whether the hostname in the server's certificate matches the hostname of the server the client is connecting to. |
httpsOptions.trustAll |
No | Boolean | true | Should all server certificates be trusted? |
httpsOptions.trustStorePath |
No | String | - | Path to the truststore. (If not all server certificates should be trusted after all). |
httpsOptions.trustStorePassword |
No | String | - | Password for the truststore. |
Service Name
The character @ is not allowed in the Service Name.
Example
{
"name": "testService",
"servers": [
{
"protocol": "http",
"host": "example.com",
"port": 4242
}
]
}
Implementation of Dynamic Configuration¶
File¶
General¶
The good old configuration file. This is the least magical way to configure the Uniport-Gateway. Nothing is done automatically here, and everything must be defined manually.
The basic structure of the configuration file is:
{
"http": {
"routers": [],
"middlewares": [],
"services": []
}
}
Environment variables can also be used in all parts of the configuration file.
Example of a configuration file for the Organization Microservice
{
"http": {
"routers": [
{
"name": "organisation-graphql",
"rule": "PathPrefix('/v1/')",
"middlewares": ["bearerOnly"],
"service": "organisation-graphql"
},
{
"name": "organisation-frontend",
"rule": "PathPrefix('/')",
"middlewares": ["bearerOnly"],
"service": "organisation-frontend"
}
],
"middlewares": [
{
"name": "bearerOnly",
"type": "bearerOnly",
"options": {
"publicKey": "${PROXY_BEARER_TOKEN_PUBLIC_KEY}",
"publicKeyAlgorithm": "RS256",
"optional": "${PROXY_BEARER_TOKEN_OPTIONAL}"
}
}
],
"services": [
{
"name": "organisation-graphql",
"servers": [
{
"host": "organisation-graphql",
"port": "20031"
}
]
},
{
"name": "organisation-frontend",
"servers": [
{
"host": "organisation-frontend",
"port": "20035"
}
]
}
]
}
}
Routers¶
{
"name": "testRouter",
"middlewares": ["md1", "md2", "md3"],
"rule": "Path('/')",
"priority": 42,
"service": "testService"
}
Middlewares¶
{
"name": "testMiddleware",
"type": "authorizationBearer",
"options": {
"sessionScope": "testScope"
}
}
Services¶
{
"name": "testService",
"servers": [
{
"host": "example.com",
"port": 4242
}
]
}
Docker¶
General¶
The Uniport-Gateway creates a corresponding Router and Service for each container.
A server instance is automatically attached to the Service, and the Default Rule is assigned to the Router if no Routing Rule has been defined in the Labels.
Docker Service Discovery
For Docker Container Service Discovery to work, /var/run/docker.sock must be mounted in the Uniport-Gateway. It is important that docker.sock has permission 666 (sudo chmod 666 /var/run/docker.sock). In this regard, there are some security aspects to consider.
Example of a Dockerfile for the Organization Microservice
FROM ${docker.pull.registry}/ch.uniport.gateway.uniport-gateway:${uniport-gateway.version}
COPY target/docker-context/organisation-proxy-config/ /etc/uniport-gateway/organisation/
# labels used for the service discovery by the uniport-gateway
LABEL portal.enable="true"
LABEL portal.http.routers.organisation-proxy.rule="PathPrefix('/organisation')"
LABEL portal.http.routers.organisation-proxy.middlewares="organisationRedirectRegex, organisationOauth2@file, organisationAuthBearer@file, organisationReplacePath"
LABEL portal.http.middlewares.organisationRedirectRegex.redirectRegex.regex="^(/organisation)\$"
LABEL portal.http.middlewares.organisationRedirectRegex.redirectRegex.replacement="\$1/"
LABEL portal.http.middlewares.organisationReplacePath.replacePathRegex.regex="/organisation/(.*)"
LABEL portal.http.middlewares.organisationReplacePath.replacePathRegex.replacement="/\$1"
LABEL portal.http.services.organisation-proxy.servers.port="20030"
Service Definition¶
In general, when configuring a Service that is assigned to one (or more) Router(s), it must also be defined. However, when using label-based configurations, there are some exceptions:
- If a Label defines a Router (e.g., through a Router Rule) and a Label defines a Service (e.g., through a Server Port), but the Router does not specify a Service, then this Service is automatically assigned to the Router.
- If a Label defines a Router (e.g., through a Router Rule), but no Service is defined, then a Service is automatically created and assigned to the Router.
Routers¶
To update the configuration of the Router automatically attached to the container, add Labels starting with portal.http.routers.<name-of-your-router>., followed by the option to be changed. Available options are rule, priority, entrypoints, middlewares, and service.
Example
Add this Label portal.http.routers.test-router.rule=Host('example.com') to change the rule.
Middlewares¶
Middlewares can be declared by using Labels starting with portal.http.middlewares.<name-of-your-middleware>., followed by the Middleware Type/Options. Examples and detailed explanations are available under "Customization > Middlewares".
Services¶
To update the configuration of the Service automatically attached to the container, add Labels starting with portal.http.services.<name-of-your-service>., followed by the option to be changed. Available options are server.host and server.port.
Specific Provider Options¶
You can instruct Uniport-Gateway to consider (or not consider) the container by setting portal.enable to true or false. This option overrides the value of exposedByDefault.
labels:
- "portal.enable=true"
Overrides the default Docker network used for connections with the container. If a container is connected to multiple networks, make sure the correct network name is set, otherwise the container will be ignored.
labels:
- "portal.docker.network=test-network"
JSON Schema¶
For both static and dynamic configurations, there are JSON schemas that define the structure of the configuration. Modern IDEs (e.g., IntelliJ) can use the JSON schema to assist developers in writing and validating the configuration.
Further information on using JSON schemas can be found in the following chapter.
Adding Microservices¶
It is recommended to store your own configuration files under a different path in the Docker Image than /etc/uniport-gateway/default/. Then, set the Environment Variable to the used value, e.g., UNIPORT_GATEWAY_JSON=/etc/uniport-gateway/example/uniport-gateway.json, in the uniport-gateway.common.env file under ./uniport-gateway/docker-compose/src/main/resources/uniport-gateway.common.env for Docker, and in the values.dev.yaml file under uniport-gateway/helm/src/main/resources/values.dev.yaml for Kubernetes.