Spring cloud gateway refresh with RewritePath fails - spring-boot

I'm running a spring cloud gateway instance that relies on spring cloud configuration server. My application starts up with the given following configuration.
cloud:
gateway:
routes:
- id: route-foo
uri: lb://foo
predicates:
- Path=/api/foo/**
filters:
- name: RewritePath
args:
regexp: "/api/foo/(?<remaining>.*)"
replacement: "/${remaining}"
Say I make a modification to my configuration and add an additional route below
- id: route-bar
uri: lb://bar
predicates:
- Path=/api/bar/**
filters:
- name: RewritePath
args:
regexp: "/api/bar/(?<remaining>.*)"
replacement: "/${remaining}"
Executing a POST to http://localhost:8080/actuator/refresh returns the following error.
500 Server Error for HTTP POST "/actuator/refresh" (Encoded)
java.lang.IllegalArgumentException: Could not resolve placeholder 'remaining' in value "/${remaining}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:178)
It appears that spring is trying to parse my RewritePath replacement and substitute it with environment variables. What are my options?

You're running into spring trying to replace variables in your configuration.
There are two solutions
Escape the replacement syntax (Preferred)
change replacement: "/${remaining}"
to replacement: "/$\\{remaining}"
The RewriteGatewayFilterFactory does a replace on the configuration before running the filter.
Enable ignoreUnresolvableNestedPlaceholders
#Bean
#Primary
public StandardReactiveWebEnvironment standardReactiveWebEnvironmentCustomizer(StandardReactiveWebEnvironment environment) {
environment.setIgnoreUnresolvableNestedPlaceholders(true);
return environment;
}
This will leave all unresolvable placeholders alone in your configuration and not fail during the refresh. I would recommend against this approach as it may end up delaying some issues into runtime if you're relying on a field to be populated correctly.
Theoretically you should be able to accomplish this same functionality with the PropertySourcesPlaceholderConfigurer, I had a hard time getting that to cooperate.

Related

Quarkus application - add base path to OpenAPI definition

I'm currently working on a Quarkus based API that is due to be hosted on an API gateway that requires our application to be running with a base-path and I found out that by setting quarkus.resteasy.path in application.properties I can run the application with a base-path and it is being added to the specification automatically.
I am generating an OpenAPI 3 specification by using org.eclipse.microprofile.openapi.annotations. My problem is that in the specification this base-path is added to every single operation. I am instead trying to only apply the base-path within the servers declaration basically like the equivalent of what basePath used to be in OpenAPI 2.0.
Current outcome:
servers:
- url: https://dev.example.com
description: Development
- url: https://example.com
description: Production
security:
- jwt: []
paths:
/api/capacity/availability:
get:
...
/api/capacity/consume:
post:
...
Desired outcome:
servers:
- url: https://dev.example.com/api
description: Development
- url: https://example.com/api
description: Production
security:
- jwt: []
paths:
/capacity/availability:
get:
...
/capacity/consume:
post:
...
Any help would be appreciated.
You can also use config to set the servers: quarkus.smallrye-openapi.servers
See https://quarkus.io/guides/openapi-swaggerui#quarkus-smallrye-openapi_quarkus.smallrye-openapi.servers

How to inject deployment yaml env variable in springboot application yaml

I am trying to read environment variable declared in deployment yaml of Kubernetes into springboot application.yaml
Below is sample in deployment.yaml
spec:
containers:
env:
- name: SECRET_IN
value: dev
Below is sample in application.yaml
innovation:
in: ${SECRET_IN:demo}
But on localhost when I try to print innovation.in (#Configuration is created correctly) I am not getting "dev" in output, it always prints demo, it appears the link between deployment, application yaml is not happening ,could someone please help.
You can store the whole application.YAML config file into the config map or secret and inject it with the deployment only
For example :
kind: ConfigMap
apiVersion: v1
metadata:
name: demo
data:
application.yaml: |-
pool:
size:
core: 1
max:16
if your application.properties is something like
example:
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/dbname
spring.datasource.username=user
spring.datasource.password=password
You can replace it with
jdbc:mysql://${MYSQL_HOST:localhost}:3306/dbname
Deployment.yaml will be something like
spec:
containers:
- name: demowebapp
image: registry.gitlab.com/unicorn/unicornapp:1.0
ports:
- containerPort: 8080
imagePullPolicy: Always
env:
- name: MYSQL_HOST
value: mysql-prod
You can save more config into the config map & secret also based on the requirement.
Read more at : https://pushbuildtestdeploy.com/spring-boot-application.properties-in-kubernetes/
I think you did everything right, I have a similar working setup, although without a default 'demo'.
A couple of clarification from the spring boot's standpoint that might help.
application.yml can contain placeholders that can be resolved from the environment variables indeed.
Make sure that this application.yml is not "changed" (rewritten, filtered by maven whatever) during the compilation of the spring boot application artifact.
The most important: spring boot knows nothing about the k8s setup. If the environment variable exists - it will pick it. So the same could be checked even locally - define the env. variable on your local machine and run the spring boot application.
The chances are that somehow when the application runs (with the user/group) the environment variables are not accessible - check it by printing the environment variables (or this specific one) right before starting the spring boot application. Or you can do it in java in the main method:
Map<String, String> env = System.getenv();
env.entrySet().forEach(System.out::println);

What's the use of matchOptionalTrailingSeparator in Spring Cloud Gateway Predicate

From the doc about Spring Cloud that Spring.io given:
The Path Route Predicate Factory takes two parameters: a list of Spring PathMatcher patterns and an optional flag called matchOptionalTrailingSeparator.
It mentions an optional flag matchOptionalTrailingSeparator without more description.
What is the use of this flag and how to use this flag?
Thanks
The parameter matchOptionalTrailingSeparator is used to determine whether the given Path predicate should also match the requests with the trailing slash / too.
By default, the value of this is true.
For e.g. below route
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Path=/foo/{segment}
will match both the request /foo/{segment} and /foo/{segment}/
But if it's written as:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Path=/foo/{segment},false
It will not match the requests with trailing slash / i.e. it will only match /foo/{segment} and not match /foo/{segment}/

Spring Cloud Gateway and TokenRelay Filter

I’m trying to migrate JHipster from using Zuul to Spring Cloud Gateway. JHipster uses Eureka to look up routes and I believe I’ve configured Spring Cloud Gateway correctly to look up routes and propagate the access token to them. Here’s my config:
spring:
cloud:
gateway:
default-filters:
- TokenRelay
discovery:
locator:
enabled: true
lower-case-service-id: true
route-id-prefix: /services/
httpclient:
pool:
max-connections: 1000
The problem I’m experiencing is the access token is not sending an Authorization header to the downstream services.
Here's how things were configured with Zuul in my application.yml:
zuul: # those values must be configured depending on the application specific needs
sensitive-headers: Cookie,Set-Cookie #see https://github.com/spring-cloud/spring-cloud-netflix/issues/3126
host:
max-total-connections: 1000
max-per-route-connections: 100
prefix: /services
semaphore:
max-semaphores: 500
I created a pull request to show what's changed after integrating Spring Cloud Gateway.
https://github.com/mraible/jhipster-reactive-microservices-oauth2/pull/4
Steps to reproduce the issue:
git clone -b reactive git#github.com:mraible/jhipster-reactive-microservices-oauth2.git
Start JHipster Registry, Keycloak, and the gateway app:
cd jhipster-reactive-microservices-oauth2/gateway
docker-compose -f src/main/docker/jhipster-registry.yml up -d
docker-compose -f src/main/docker/keycloak.yml up -d
./mvnw
Start MongoDB and the blog app:
cd ../blog
docker-compose -f src/main/docker/mongodb.yml up -d
./mvnw
Navigate to http://localhost:8080 in your browser, log in with admin/admin, and try to go to Entities > Blog. You will get a 403 access denied error. If you look in Chrome Developer Tools at the network traffic, you'll see the access token isn't included in any headers.
I was able to solve this using this answer.
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
predicates:
- name: Path
args:
pattern: "'/services/'+serviceId.toLowerCase()+'/**'"
filters:
- name: RewritePath
args:
regexp: "'/services/' + serviceId.toLowerCase() + '/(?<remaining>.*)'"
replacement: "'/${remaining}'"
I also had to add .pathMatchers("/services/**").authenticated() to my security config, which wasn't needed for Zuul. You can see my commit here.

Spring cloud config environment variable interpolation

I'm using spring-cloud-config in my Spring Boot project and I don't understand how to interpolate environment variables.
For example I have MYSQL_PASSWORD variable set in the config server (as environment variable) and I want to get it from other clients, without redefining the variable inside all of them. This is my config:
service.yml (in config-server)
spring:
datasource:
url: jdbc:mysql://mysql:3306/${MYSQL_DATABASE}?autoReconnect=true&useSSL=false
username: ${MYSQL_USER}
password: ${MYSQL_PASSWORD}
bootstrap.yml (into the client)
spring:
application:
name: event-service
cloud:
config:
uri: http://config-service:8888
fail-fast: true
If I start my services with this configuration, placeholder ${MYSQL_***} is not interpolated and I cannot connect to the database obviously.
What I have to do to make it work is to define environment variables like MYSQL_*** also in the client.
This is weird for me as I want config-server to be the central repository for everything. Do you have any advice?
I suggest you try to replace ${MYSQL_USER} and ${MYSQL_PASSWORD} with ${mysql.user} and ${mysql.password} - that way you'll be relying on Spring Boot's default property replacement.
Although I'm not sure that configuration server supports what you're trying to use, I've never tried a similar use case, please write back with a solution if the approach I suggested didn't work :)

Resources