Spring Cloud Config with SSL handshake - spring

I'm trying to authenticate my Spring Boot application (Client) to my Spring Cloud Config Server. This works well with basic username/password using Spring Security, but I want to use SSL with an x509 certificate.
I understand that for this you need to bootstrap your own custom RestTemplate with your own custom HttpComponentsClientHttpRequestFactory in your Client that overwrites the default behaviour and teaches your Client how to contact the Server using its own truststore.jks (https://piotrminkowski.com/2019/12/03/secure-spring-cloud-config/).
To be able to force the bootstrap I understand that we need to add a spring.factories file under META_INF with something like:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.andrelcode.myspringcloudconfigclient.config.SSLConfigServiceBootstrapConfiguration
(Note: Spring clearly recognizes my spring.factories file, as it automatically assigns a "spring factories registration gutter icon to it)
Of course, it's not the end of the Story as we also need to specifically enable bootstrap since spring-cloud.version 2020.0:
Processing of Bootstrap configuration is disabled by default in Spring
Cloud 2020.0 and later. To re-enable it, set
spring.cloud.bootstrap.enabled=true (from: Spring Boot application does not load spring.factories)
So I did this as well, by adding a bootstrap.properties file to my project, with the given line:
spring.cloud.bootstrap.enabled=true
So at this point I have the followings:
src
|main
| |java
| | |com.andrelcode.myspringcloudconfigclient
| | | |config
| | | | |SSLConfigServiceBootstrapConfiguration()
| | |MyspringcloudconfigclientApplication() -> #SpringBootApplication, #EnableWebMvc
|resources
| |META_INF
| | |spring.factories -> org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.andrelcode.myspringcloudconfigclient.config.SSLConfigServiceBootstrapConfiguration
| |application.yml
| |bootstrap.properties -> spring.cloud.bootstrap.enabled=true
And yet, when I launch the application it clearly ignores all my bootstrap config and simply fails to start because it cannot pull its configuration from the Server:
Caused by:
org.springframework.web.client.HttpClientErrorException$BadRequest:
400 : "Bad RequestThis combination of host and port requires
TLS."
In the Server log I see:
java.io.IOException: Found an plain text HTTP request on what should
be an encrypted TLS connection
So again, it seems that Spring Boot in my Client completely ignores my efforts to bootstrap my own RestTemplate using my custom HttpComponentsClientHttpRequestFactory that it should use to talk to the Config Server. I'm not sure how DEBUG this any further, as I don't get any additional logs even if I set TRACE.
Here is the implementation of the Configuration I'd like to bootstrap to my Client (although it's the same as in the example link from Piotr above):
package com.andrelcode.myspringcloudconfigclient.config;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
#Configuration
public class SSLConfigServiceBootstrapConfiguration {
#Autowired
ConfigClientProperties configClientProperties;
#Bean
public ConfigServicePropertySourceLocator configServerPropertySourceLocator() throws Exception {
final char[] password = "testtest".toCharArray();
final FileSystemResource resource = new FileSystemResource("C:\\******truststore.jks");
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(resource.getFile(), password, password)
.loadTrustMaterial(resource.getFile(), password, new TrustSelfSignedStrategy()).build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier((s, sslSession) -> true)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(configClientProperties);
configServicePropertySourceLocator.setRestTemplate(new RestTemplate(requestFactory));
return configServicePropertySourceLocator;
}
}
So what's missing or what's preventing the properly bootstrapping my custom configuration at startup in my Client?
Thanks!

I had the same problem, but only with newer verions of spring cloud. With older ones, it worked as expected.
It turned out, that it was connected to the use of the import: 'configserver:' settings. I had all my config in the application.yml:
spring:
config:
import: 'configserver:'
application:
name: my-app
cloud:
config:
uri: https://localhost:1234
name: my-app
label: ${profile}
and no bootstrap.yml at all. I just provided the spring.cloud.bootstrap.enabled=true as an environment variable so that the SSLConfigServiceBootstrapConfiguration was read in.
After I moved the config-client settings (see code-snippet above) into the bootstrap.yml and removed the spring.config.import: 'configserver', it works as expected:
bootstrap.yml:
spring:
application:
name: my-app
cloud:
config:
uri: https://localhost:1234
name: my-app
label: ${profile}
Cheers.

Related

Integration Test with Spring Cloud Config Server

It's my first time implementing a Spring application using Spring Cloud Config framework. The application is working fine, getting configuration properties from repository through the server application. So, at the moment I must write the integration tests to test connection between client application and server and repository. To do it I mean get a value from repository over properties and check the value, also make a request to the server, or if exists, other method inside spring cloud config library to make it.
The problem now, when executing the integrationTest the application can't read the properties from remote. I've created a bootstrap.yml on the integrationTest context but not works. So, I got the errors like: Could not resolve placeholder.
MyIntegrationTest
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest()
#ActiveProfiles("integrationTest")
public class MyIntegrationTest {
#Value("${throttling.request.rate.minute}")
private int MAX_REQUEST_PER_MINUTE;
#Test
public void validateGetRequestRateLimitFromRepository() {
Assert.assertNotEquals(MAX_REQUEST_PER_MINUTE,1L);
}
}
bootstrap.yml
spring:
application:
name: sample-name
---
spring:
profiles: local #also tried integrationTest
cloud:
config:
enabled: true
uri: https://endpoint-uri:443
skipSslValidation: true
name: ${spring.application.name},${spring.application.name}-${spring.profiles}
Also tried:
Set application-integrationTest.yml
Set application.yml
Set bootstrap-integrationTest.yml
Create a *-integrationTest.yml at the repo.
Set #ActiveProfiles("local") on integrationTest class.
Nothing works.

Spring Boot log file can be set by application.properties but not by System.setProperty("logging.file",name)

I have a Spring boot application which can read an hard coded log file name from application.properties like this:
Option 1: ( working fine )
logging.file=C\\outputFolder\\fileName3.log
Option 2: ( not working )
For some reason if i set it as
System.setProperty("logging.file", "C:\\outputFolder\\fileName2.log");
it doesn't work, i have a very similar Spring boot application which reads the file name using Option 2, any idea what is missing here ?
I'm using slf4j in the following way:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger log = LoggerFactory.getLogger(ClassName.class);
you are setting the property value after logging configuration is done by spring boot, calling System.setProperty("logging.file", "C:\\outputFolder\\fileName2.log") won't take effect.
you can read the file location from the second app using System.getProperty("logging.file")

How to use a Spring profile expression?

Spring Boot: 2.0.4.RELEASE
I have specified a Spring Boot multi-profile YAML configuration file:
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production | eu-central
server:
address: 192.168.1.120
According to the reference guide, if the production or eu-central profile is active, the server.address property is 192.168.1.120. But when I run this test
#ActiveProfiles({"production"})
#RunWith(SpringRunner.class)
#SpringBootTest
public class ProfileTest {
#Autowired
private Environment environment;
#Value("${server.address}")
private String serverAddress;
#Test
public void testProfile() {
assertThat(environment.getActiveProfiles(), is(new String[] {"production"} ));
assertThat(serverAddress, is("192.168.1.120"));
}
}
it fails:
java.lang.AssertionError:
Expected: is "192.168.1.120"
but: was "192.168.1.100"
at com.example.demo.ProfileTest.testProfile(ProfileTest.java:27)
Why does the test fail and how do I use a Spring profile expression correctly?
By the way, if I remove | eu-central from the spring.profiles key the test passes!
Boris. I've studied your problem. And I think that I've found a bug in Spring-boot. I've reported an issue so you can check it. The problem is SpringBoot always use default value for ${server.address} because framework couldn't recognise profiles: production | eu-central as a List of profiles. If I use
#ActiveProfiles(value = "production | eu-central")
All is ok.
The link on Spring-boot issue here https://github.com/spring-projects/spring-boot/issues/14314
So, you could see the discussion.
Thank you!
UPDATED
I've got an answer from Spring Team on my Issue.
This format is a Spring Framework 5.1 (and therefore a Spring Boot 2.1
feature).
So the answer on your question is
This feature will be available in Spring Boot 2.1.

#ConfigurationProperties not pulling the external properties file

I've created a personal repository on Git where I have kept my application.properties file.
I've created a cloud config server ('my-config-server') and used the git repository url.
I have bound my spring-boot application that is supposed to access the external properties file with Git repository.
#javax.jws.WebService(
serviceName = "myService",
portName = "my_service",
targetNamespace = "urn://vdc.com/xmlmessaging/SD",
wsdlLocation = "classpath:myService.wsdl",
endpointInterface = "com.my.service.SDType")
#PropertySource("application.properties")
#ConfigurationProperties
public class SDTypeImpl implements SDType {
/*It has various services implementation that use following method**/
private SDObj getObj (BigDecimal value) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(
SDTypeImpl.class);
SDObj obj = context.getBean(SDPropertiesUtil.class).getObj(value);
context.close();
return obj;
}
}
Another Class:
public class SDPropertiesUtil {
#Autowired
public Environment env;
public SDObj getObj(BigDecimal value) {
String valueStr = env.getProperty(value.toString());
/*do logic*/
}
My application starts but fails to load properties file from my git repository.
I believe I should have an application.properties at src/main/resources in my application but since I'm using
#PropertySource("application.properties")
#ConfigurationProperties
I'm telling my application to use the application.properties from an external location and do not use internal properties file. But this is not happening. My application is still using the internal properties file.
The source you included doesn't show your app configuration settings to connect to the Config server. Do you mind sharing it?
This is how the config server could be queried from a client app:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
Let's say a Config server points to a Git repo which includes this file: demo-config-client-development.properties
You should be able to query the Config Server as:
curl http://localhost:8101/demo-config-client-development.properties
Assuming Config Server is running in locally and listening on 8181.
Let's also say you have a client app named: demo-config-client that connects to the Config server and runs using the development Spring profile, this app would now be able to read remote properties hosted in a Git repo through a Config server.
A detailed tutorial could be found at my blog at: http://tech.asimio.net/2016/12/09/Centralized-and-Versioned-Configuration-using-Spring-Cloud-Config-Server-and-Git.html

servicemix: on which port is my osgi bundle listening?

As a newby to servicemix/karaf, I'm trying to create a very simple program that accepts and returns a REST-request. The class I have is:
package (....)
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import (...).model.RSDocument;
import (...).model.RSDocumentResponse;
#RestController
public class DocumentService {
#RequestMapping(value = "/rest/document", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ResponseEntity<RSDocumentResponse> printDocument(
#RequestBody RSDocument documentRequest)
{
System.out.println(documentRequest.getContent());
RSDocumentResponse response = new RSDocumentResponse();
response.setSuccess(true);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
I've got something similar working in Tomcat. In Tomcat, you would specify the port where it will listen to incoming requests by doubleclicking on the server in eclipse and setting a value in the "Ports" section. How do I set the port in Servicemix, or even figure out which port it's currently listening to? I have the bundle succesfully starting from the command line in Servicemix. My application doesn't seem to be listening on 80 (Apache), 8080 (None), or 8181 (Servicemix console)
First of all make sure you have the web-container deployed. For this make sure the war feature is installed.
feature:list | grep war
if it's not installed, install it by issueing:
feature:install war
Now, make sure your jar/war contains the Web-ContextPath Manifest entry telling the web-container which context path to look for. If you have all other required bundles installed and running, including all dependencies are set you should be able to navigate to your rest service on:
localhost:8181/myContextPath/rest/document

Resources