How to secure spring cloud eureka service with basic auth? - spring

I set up multiple instances of eureka servers on the same host. They are using host names eureka-primary, secondary and tertiary which are defined as the localhost aliases in the hosts file and everything is working just fine - they are all visible and available to each other as different instances.
The problem starts when I try to secure eureka instances with basic auth and this. The idea is add spring security dependency, to specify the security user and password on eureka servers, and to put these credentials in the defaultZone urls (configs are below), but this does not seem to work.
Eureka instances can't even register to each other and when I try to access eureka web portal I am prompted with login form and then redirected to dashboard. All dashboards are working fine and need credentials to be accessed.
I am using spring cloud Finchley.RC1 with spring boot 2.0.1.RELEASE and the same version of spring-boot-starter-security and spring-cloud-starter-netflix-eureka-server.
Eureka server pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>rs.microservices</groupId>
<artifactId>eurekaServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eurekaServer</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RC1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
Eureka server application.yml
---
spring:
security:
user:
name: admin
password: admin
profiles: primary
application:
name: eureka-server-clustered
server:
port: 8011
eureka:
instance:
hostname: eureka-primary
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://admin:admin#eureka-secondary:8012/eureka/,http://admin:admin#eureka-tertiary:8013/eureka/
---
spring:
security:
user:
name: admin
password: admin
profiles: secondary
application:
name: eureka-server-clustered
server:
port: 8012
eureka:
instance:
hostname: eureka-secondary
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://admin:admin#eureka-primary:8013/eureka/,http://admin:admin#eureka-tertiary:8011/eureka/
---
spring:
security:
user:
name: admin
password: admin
profiles: tertiary
application:
name: eureka-server-clustered
server:
port: 8013
eureka:
instance:
hostname: eureka-tertiary
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://admin:admin#eureka-primary:8011/eureka/,http://admin:admin#eureka-secondary:8012/eureka/
Microservice bootstrap.yml
spring:
application:
name: someService
server:
port: 0
eureka:
client:
registerWithEureka: true
fetchRegistry: true
service-url:
defaultZone: http://admin:admin#localhost:8011/eureka/,http://admin:admin#localhost:8012/eureka/,http://admin:admin#localhost:8013/eureka/
What am I doing wrong?
*EDIT
I already found multiple solutions like this one Securing Eureka in Spring cloud, but none of them really fixed my problem - as you can see our configurations are identical.

Solved!
TL;DR
The problem was the CSRF and for some reason spring couldn't authenticate user configured in application.yml
So I had to override configure methods from WebSecurityConfigurerAdapter to disable csrf and create inMemory user. Also removed spring.security.user attributes from application.yml.
Eureka server application.yml now looks like:
---
spring:
profiles: primary
application:
name: eureka-server-clustered
server:
port: 8011
eureka:
instance:
hostname: eureka-primary
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://admin:admin#eureka-secondary:8012/eureka,http://admin:admin#eureka-tertiary:8013/eureka
---
spring:
profiles: secondary
application:
name: eureka-server-clustered
server:
port: 8012
eureka:
instance:
hostname: eureka-secondary
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://admin:admin#eureka-primary:8013/eureka,http://admin:admin#eureka-tertiary:8011/eureka
---
spring:
profiles: tertiary
application:
name: eureka-server-clustered
server:
port: 8013
eureka:
instance:
hostname: eureka-tertiary
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://admin:admin#eureka-primary:8011/eureka,http://admin:admin#eureka-secondary:8012/eureka
Newly created WebSecurityConfig class:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("admin").password("admin")
.authorities("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}

I had similar issue with Greenwich.SR1.
Enabled security with spring.security.user.name allowed me to login with username/password but my services could't register with Eureka service.
I made it work, and as mentioned above, the cause was indeed CSRF
This fixes it:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}

Import spring-security in your pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
And put this in your application.yaml :
security:
user:
name: admin
password: password
And it should work !
EDIT : I already made this, here the simpliest code that allow you to achieve this, I seggest you start from here :
pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>vg.step</groupId>
<artifactId>eureka.server</artifactId>
<version>0.0.1</version>
<name>vgstepeurekaserver</name>
<properties>
<org.springframework.cloud-version>1.4.4.RELEASE</org.springframework.cloud-version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>${org.springframework.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>application*.yaml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
src/main/resources/application.yaml :
spring:
application:
name: #project.name#
eureka:
instance:
hostname: eureka-server
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
server:
port: 8002
security:
user:
name: admin
password: password
src/main/java/vg/step/eureka/server/Application.java :
package vg.step.eureka.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
#SpringBootApplication
#EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Related

Error with Eureka Server Cluster (peer awareness)

My question is about Spring Cloud Eureka: I'm trying to run Eureka Cluster (2 nodes, localhost:8761, localhost:8762),
but some errors appear:
RedirectingEurekaHttpClient : Request execution error: endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}
RetryableEurekaHttpClient :
Request execution failed with message: java.net.ConnectException: Connection refused
DiscoveryClient : DiscoveryClient_UNKNOWN/192.168.1.4:8761 - was unable to refresh its cache! status = Cannot execute request on any known server
Here are application.yml files:
Server 1:
spring:
profiles: eureka-peer1
server:
port: 8761
eureka:
instance:
hostname: eureka-peer1
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://eureka-peer2:8762/eureka/
Server 2:
spring:
profiles: eureka-peer2
server:
port: 8762
eureka:
instance:
hostname: eureka-peer2
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://eureka-peer1:8761/eureka/
File /etc/hosts:
127.0.0.1 localhost
127.0.0.1 eureka-peer1
127.0.0.1 eureka-peer2
ErekaService1Application.java , ErekaService2Application.java:
package com.example.eurekaservice1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
#SpringBootApplication
#EnableEurekaServer
#EnableEurekaClient
public class EurekaService1Application {
public static void main(String[] args) {
SpringApplication.run(EurekaService1Application.class, args);
}
}
Removing #EnableEurekaClient and setting "registerWithEureka" and "fetchRegistry" to "false" gives the same results.
Moreover, standalone mode with these properties leads to the same error:
spring:
profiles: eureka-peer1
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
Here is pom.xml file, automatically generated using start.spring.io:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>eureka-service-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-service-1</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Remove #EnableEurekaClient & dependency as well
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Properties are not read from Vault on Integrating Vault with Spring Cloud Config Server

I am trying use Spring boot config server with git and vault and all my spring boot client application will retrieve the vault properties via the config server by passing the vault config token.
I am using the spring boot 2.1.8.RELEASE and below is the POM.xml file for my spring boot config server.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.ps.psc</groupId>
<artifactId>psc-config-server</artifactId>
<version>0.0.1</version>
<name>psc-config-server</name>
<description>Spring configuration server</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-monitor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The bootstrap.yml file
spring:
profiles:
active:
- git
- vault
cloud:
config:
enabled: true
server:
git:
order: 2
username: ********
password: ********
uri: https://*******#bitbucket.org/krushna/configuration.git
search-paths:
- payment*
vault:
host: 127.0.0.1
port: 8200
scheme: http
order: 1
skip-ssl-validation: true
kv-version: 1
vault:
authentication: TOKEN
token: s.PB5cAJ9WhOuWamIOuFVkzpbl
scheme: http
host: 127.0.0.1
port: 8200
config:
order: 1
My application.yml file
server:
port: 7000
spring:
application:
name: configserver
With the above configuartion my config server is able read the properties only from the GIT not from the vault.
In the vault I have written a properties like below.
vault write secret/payment password=test#123
If I make curl call like below
curl -X "GET" "http://127.0.0.1:7000/payment/default" -H "X-Config-Token: s.PB5cAJ9WhOuWamIOuFVkzpbl"
I am geeting properties from git only, response below.
{
"name": "payment",
"profiles": ["default"],
"label": null,
"version": "e9b941d22f6b7cd3083a731d168f78fa4ec0fc42",
"state": null,
"propertySources": [{
"name": "https://******#bitbucket.org/krushna/configuration.git/application.properties",
"source": {
"foofromGit": "bar"
}
}]
}
What I am doing worng here? I have tried multiple option like differnt KV version, only configuring spring cloude config vault etc.
Edit:
I have used the vault conf like below.
backend "file" {
path = "vault"
}
listener "tcp" {
tls_disable = 1
}
and doing curl to vault driectly I am able to read the value now.
curl -X GET -H "X-Vault-Token:s.PB5cAJ9WhOuWamIOuFVkzpbl" http://127.0.0.1:8200/v1/secret/payment/
response:
{
"request_id": "35c8793e-3530-81c1-7917-3e922ef4065b",
"lease_id": "",
"renewable": false,
"lease_duration": 2764800,
"data": {
"password": "test#123"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
I am able to fix the issue by moving the git and spring cloude config vault configuration details from bootstrap.yml to application.yml like below.
bootstrap.yml
spring:
application:
name: configserver
cloud:
vault:
authentication: TOKEN
token: s.jyFarEyroi5pJNOxPnhT4f3D
scheme: http
host: 127.0.0.1
port: 8200
config:
order: 1
Application.yml
server:
port: 7000
spring:
profiles:
active: git, vault
cloud:
config:
server:
git:
uri: https://krushna#bitbucket.org/krushna/configuration.git
search-paths:
- payment*
vault:
port: 8200
host: 127.0.01
skip-ssl-validation: true
scheme: http
I am still not clear how this fix the issues?, only thing I know that bootstrap will load first, and I am reading the git credential from vault and then application.yml has the other details for the spring cloud config vault and git.
Any explanation on this will be really welcome

How to set up Spring Cloud Gateway application so it can use the Service Discovery of Spring Cloud Kubernetes?

I created two Spring Boot applications which both will be deployed in a Kubernetes cluster. One of those apps will act as a gateway and therefore uses Spring Cloud Gateway as a dependency. Also I want to integrate service discovery with Spring Cloud Kubernetes and that the gateway uses the service discovery to automatically generate corresponding routes. But when I expose the gateway application, which is running in an local Minikube cluster, and invoke the second app/service I get a 503 error with following message: Unable to find instance for ...-service
Currently I have installed following:
Minikube
VirtualBox
Docker Toolbox
I have created a Gradle project with two subprojects (gateway and another service). All projects will be build/deployed locally. The default Service Account has permission to read the Kubernetes API. After deployment of those services I expose the gateway service externally. In the gateway service I have some endpoints implemented, which
provide a list of all services in the cluster vie the DiscoveryClient.
on application layer invoke the other service based on the URI provided by the DiscoveryClient.
Everything seems to work but when I invoke the other service with URI/serviceId I get the 503 error...
Following Spring Cloud versions are used:
spring-cloud-starter-kubernetes 1.0.1.RELEASE
spring-cloud-starter-gateway 2.1.1.RELEASE
My demo app is available at https://github.com/nmaoez/spring-cloud-gateway-kubernetes and the README.md provides steps to get both services deployed in a local Minikube cluster. Also all available endpoints are shown. But the interessing part are the application.yaml and the application class of the gateway.
application.yaml:
spring:
application:
name: gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
management:
endpoints:
web:
exposure:
include: '*'
GatewayApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#RestController
public class GatewayApplication {
#Autowired
RestTemplate restTemplate;
#Autowired
private DiscoveryClient discoveryClient;
#GetMapping("/")
#ResponseBody
public String hello() {
return "GatewayApplication says hello!";
}
#GetMapping("/test")
#ResponseBody
public String invokeTestService() {
List<ServiceInstance> testServiceInstances = this.discoveryClient.getInstances("test-service");
return restTemplate.getForObject(testServiceInstances.get(0).getUri(), String.class);
}
#GetMapping("/services")
public List<String> services() {
return this.discoveryClient.getServices();
}
#GetMapping("/services/{serviceId}")
public List<ServiceInstance> servicesById(#PathVariable("serviceId") String serviceId) {
return this.discoveryClient.getInstances(serviceId);
}
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
I got it some how running after I overwrote the url-expression field in the gateway-service/application.yaml to
url-expression: "uri+'/'"
After that I got a correct response after I invoked gateway-uri/another-service/. But my wish is to not explicitly replace the default of lb://serviceid. How can I do that?
I expect that if I invoke another service in the cluster though the gateway, I get a 200 response and the correct answer based in the rest controller of the application.
You have to add the dependency to spring-cloud-starter-kubernetes-ribbon as well.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
Then it will work without any rewrites, just with the spring.cloud.gateway.discovery.locator.enabled: true
I just implemented an example application using Spring Cloud Gateway and Kubernetes, that works like a charm in Docker Desktop. And no extra nor funny configurations were needed.
If it may help this was my build.gradle:
plugins {
id 'org.springframework.boot' version '2.4.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.1.0-SNAPSHOT'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
ext {
set('springCloudVersion', "2020.0.0")
set('springCloudKubernetesVersion', '1.1.7.RELEASE')
set('springCloudVersion', '2020.0.0')
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-sleuth'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation "org.springframework.cloud:spring-cloud-starter-kubernetes:$springCloudKubernetesVersion"
implementation "org.springframework.cloud:spring-cloud-starter-kubernetes-config:$springCloudKubernetesVersion"
implementation "org.springframework.cloud:spring-cloud-starter-kubernetes-ribbon:$springCloudKubernetesVersion"
implementation "org.springframework.cloud:spring-cloud-starter-kubernetes-loadbalancer:$springCloudKubernetesVersion"
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"
}
}
test {
useJUnitPlatform()
}
This is the configuration from the application.yaml:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
And finally, the DiscoveryClient is enabled in the app:
#SpringBootApplication
#EnableDiscoveryClient // So services can be discovered
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
Note as Jakub Kubrynski said, you must include the Ribbon dependency.
Also beware routing only works when the gateway is deployed to the K8s cluster. Outside it, it can see the K8s services but cannot route to them since they are using IP addresses in the K8s network.
I'm able to setup spring cloud gateway with discovery of spring-cloud-kubernetes dependency version 1.1.10.RELASE and Spring-boot: 2.5.7 Spring cloud gateway: 3.0.4
Pom file looks like below:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.7</version>
<relativePath/>
</parent>
<groupId>com.csg.cro.rccs</groupId>
<artifactId>api-gateway</artifactId>
<version>${revision}</version>
<name>RCC-APIGateway</name>
<description>Enable Proxy and verify user token project for Risk 360</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>11</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
<revision>21.7.0-SNAPSHOT</revision>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<version>1.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
<version>1.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
<version>1.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-loadbalancer</artifactId>
<version>1.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.1.0</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
My discovery config look like:
spring:
application.name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
url-expression: "'http://'+serviceId+':'+getPort()"
lower-case-service-id: true

Not able to read configuration from Consul in spring-boot application

I am creating a Spring Boot application, which will read configuration like DB properties from Consul. But I am not able to read the key value from Consul using my application. Following is, what I am trying to do.
**pom.xml**
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tuturself</groupId>
<artifactId>spring-boot-consul</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.retry.version>1.2.1.RELEASE</spring.retry.version>
<consul.version>1.1.2.RELEASE</consul.version>
<consul.discovery.version>1.1.2.RELEASE</consul.discovery.version>
<jackson.version>2.8.1</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-consul-discovery</artifactId>
<version>${consul.discovery.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${spring.retry.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-consul-dependencies</artifactId>
<version>1.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
And Following is my Main class:
#EnableRetry
#RefreshScope
#EnableDiscoveryClient
#SpringBootApplication
#ComponentScan("com.test.*")
public class SpringBootConsulApplication {
private static ConsulConfiguration consulConfiguration;
public static void main(String[] args) {
try {
String consulHost = System.getProperty("spring.cloud.consul.host");
System.out.println("consulHost ::" + consulHost);
String consulPort = System.getProperty("spring.cloud.consul.port");
System.out.println("consulPort ::" + consulPort);
String consulPrefix = System.getProperty("spring.cloud.consul.config.prefix");
System.out.println("consulPrefix ::" + consulPrefix);
new SpringApplicationBuilder(SpringBootConsulApplication.class).web(true).run(args);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
And I am reading the consul properties using the #Value annotation:
#Configuration
#EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class ConsulConfiguration {
#Value("${cassandra.host}")
private String cassandraHost;
#Value("${cassandra.user}")
private String userName;
#Value("${cassandra.password}")
private String password;
}
I have my bootstrap.yml in resources folder:
spring:
cloud:
consul:
host: localhost
port: 8500
enabled: true
config:
enabled: true
prefix: config/application
defaultContext: apps
profileSeparator: '::'
application:
name: spring-boot-consul
Consul is up and running in my local system on localhost:8500 where I have the file config/application/spring-boot-consul.yml file;
spring:
application:
name: spring-boot-consul
cassandra:
host: 127.0.0.1:9042,127.0.0.2:9042
user: my_user
password: my_pass
pooling:
maxThread: 10
timeout: 50
keyspace:
name: test_keyspace
readConsistency: ONE
writeConsistency: ONE
When I am strating the application, it is showing not able to bind cassandra.host in my ConsulConfiguration class. Thus stopping the application. Any hints , What I am doing wrong here?
You can find a working example here.
Consul Configuration KV Store
You need to store your properties in the Consul KV store either from Consul UI or from the command line. The Consul Agent will not load your properties from the file system. To load the properties from the command line, you can use the following command once the Consul Agent is up and running. The YAML data can be read from a file by prefixing the file name with the # symbol.
./consul kv put config/application/data #spring-boot-consul.yml
where config/application/data is the key name.
If the data is successfully written in the KV, you should get the following response,
Success! Data written to: config/application/data
You can also fetch the properties from the KV by using the following command,
$ ./consul kv get config/application/data
cassandra:
host: 127.0.0.1:9042,127.0.0.2:9042
user: my_user
password: my_pass
You can also view the properties from the Consul Web UI,
Changes to bootstrap.yml
You need to modify your bootstrap.yml slightly. Here are the changes:
prefix value to config
defaultContext value to application
Added format to yaml
Added data-key by the name of data to fetch the YAML blob.
spring:
profiles: default
cloud:
consul:
host: localhost
port: 8500
config:
enabled: true
prefix: config
defaultContext: application
data-key: data
profileSeparator: '::'
format: yaml
application:
name: spring-boot-consul
Changes to ConsulConfiguration
#Configuration
#RefreshScope
public class ConsulConfiguration {
#Value("${cassandra.host}")
private String cassandraHost;
#Value("${cassandra.user}")
private String userName;
#Value("${cassandra.password}")
private String password;
#PostConstruct
public void postConstruct() {
// to validate if properties are loaded
System.out.println("** cassandra.host: " + cassandraHost);
System.out.println("** cassandra.user: " + userName);
System.out.println("** cassandra.password: " + password);
}
}
Changes to Application class,
#EnableRetry
#RefreshScope
#EnableDiscoveryClient
#EnableAutoConfiguration
#EnableConfigurationProperties
#SpringBootApplication
#ComponentScan("com.test.*")
public class SpringBootConsulApplication {
public static void main(String[] args) {
...
}
}

Micro-services: Zuul & consul in Spring cloud application

I'm trying to create a Spring cloud microservice application using Zuul and Consul.
I have 2 components in my project:
api-gateway microservice using Zuul
Hello world microservice (a simple hello world Rest Webservice)
Here is the code of The api-gateway:
#SpringBootApplication
#EnableZuulProxy
#EnableDiscoveryClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
The pom.xml
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Brixton.M3</version>
</parent>
<properties>
<java.version>1.8</java.version>
<spring.cloud.consul.version>1.0.0.M4</spring.cloud.consul.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<!-- Setup Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<!-- Setup Spring MVC & REST, use Embedded Tomcat -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<!-- Spring Cloud starter -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-all</artifactId>
<version>${spring.cloud.consul.version}</version>
</dependency>
</dependencies>
application.yml
zuul:
routes:
hello1:
path: /hello1/**
serviceId: microservice-example
logging:
level:
org.springframework: INFO
com.netflix: DEBUG
bootstrap.yml
spring:
application:
name: edge-server
cloud:
consul:
config:
enabled: true
host: localhost
port: 8500
Here is the code of hello microservice:
#SpringBootApplication
#EnableConfigServer
#EnableDiscoveryClient
#RestController
public class Application {
#RequestMapping(value="/hello1",method = RequestMethod.GET)
public String hello() {
System.out.print("hello1");
return "Hello1";
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
bootstrap.yml:
spring:
application:
name: microservice-example
profiles:
active: native
cloud:
consul:
config:
enabled: true
host: localhost
port: 8500
But, when I start the api-gateway I got the following exception:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.cloud.netflix.zuul.filters.RouteLocator]: Factory method 'routeLocator' threw exception; nested exception is java.lang.IllegalStateException: Unable to locate service in consul agent: edge-server
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
... 69 common frames omitted
Caused by: java.lang.IllegalStateException: Unable to locate service in consul agent: edge-server
at org.springframework.cloud.consul.discovery.ConsulDiscoveryClient.getLocalServiceInstance(ConsulDiscoveryClient.java:66) ~[spring-cloud-consul-discovery-1.0.0.M4.jar:1.0.0.M4]
This issue is fixed in Brixton.M3 (1.0.0.M5). As mentioned above this was an issue with spring-cloud-consul. The new version is working fine

Resources