How to set valid certification path when connecting to redis using SSL in Spring Boot? - spring

I want to connect to redis by using SSL. I set up host, port etc. but when i'm setting...
spring.redis.ssl=true
and when i run the application i got following error:
org.springframework.data.redis.RedisConnectionFailureException:
Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to
XXX:XXX at
org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$ExceptionTranslatingConnectionProvider.translateException(LettuceConnectionFactory.java:1689)
~[spring-data-redis-2.5.7.jar:2.5.7]
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target at
java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
~[na:na] at
java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:349)
~[na:na]
Actually my certificate (test.pem) is in resources folder in spring boot apllication project.
Where should i put the certificate file or how to set the path to this file?
I want to set it by application.yml or by java code.

This configuration works in my case:
#Configuration
#RequiredArgsConstructor
public class RedisSSLConfiguration {
#Value("${spring.redis.host}")
private String host;
#Value("${spring.redis.port}")
private int port;
#Value("${spring.redis.password}")
private String password;
#Value("${spring.redis.ssl:false}")
private boolean sslEnabled;
private final ResourceLoader resourceLoader;
#Bean
RedisConnectionFactory redisConnectionFactory() throws IOException {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(password);
LettuceClientConfiguration.LettuceClientConfigurationBuilder lettuceClientConfigurationBuilder =
LettuceClientConfiguration.builder();
if (sslEnabled){
SslOptions sslOptions = SslOptions.builder()
.trustManager(resourceLoader.getResource("classpath:redis.pem").getFile())
.build();
ClientOptions clientOptions = ClientOptions
.builder()
.sslOptions(sslOptions)
.protocolVersion(ProtocolVersion.RESP3)
.build();
lettuceClientConfigurationBuilder
.clientOptions(clientOptions)
.useSsl();
}
LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfigurationBuilder.build();
return new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
}
}

Related

Verify Certificate and Hostname for an HTTPS REST call with Spring RestTemplate

I have a Spring Boot Microservice where I am trying to invoke an external server which exposes an HTTPS REST Endpoint (TLS v1.2). I have been provided the server side certificate in .pem format.
I would like to implement this call using RestTemplate and use the provided certificate and verify the host name during the call.
I have tried to Google this and all the search results are trying to ignore the certificate and host name.
Can I have an example code snippet to implement this properly?
After some digging with different blogs and stackoverflow threads, following worked for me:
Create Rest Template:
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream(ResourceUtils.getFile(clientKeyPath)), "".toCharArray());
SSLContext sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(keyStore, null)
.loadTrustMaterial(ResourceUtils.getFile(keystorePath), keystorePassword.toCharArray())
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new CustomHostnameVerifier());
HttpClient client = HttpClients
.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(client);
RestTemplate sslRestTemplate = new RestTemplate(requestFactory);
Implementation of CustomHostnameVerifier:
#Component
public class CustomHostnameVerifier implements HostnameVerifier {
#Value("${dns.name}")
private String dnsName;
#Override
public boolean verify(String hostname, SSLSession session) {
return hostname.equals(dnsName);
}
}

keystore.jks and truststore.jks can't be found in Spring Boot App

I wrote a 2-way authetication restful service cleint to consume a secured restful web service on port 8443 over https. Here are parts of application.properties:
trust.store=classpath:truststore.jks
trust.store.password=xyz123
key.store=classpath:keystore.jks
key.store.password=xyz123
Below are two ways to configure RestTemplate
#Configuration
public class SSLTemplate {
#Value("${key.store}")
private String keyStore;
#Value("${key.store.password}")
private String keyStorePassword;
#Value("${trust.store}")
private String trustStore;
#Value("${trust.store.password}")
private String trustStorePassword;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(ResourceUtils.getFile(keyStore), keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile(trustStoreF), trustStorePassword.toCharArray())
.build();
...
}
#Configuration
public class SSLTemplate {
#Value("${key.store}")
private Resource keyStoreR;
#Value("${key.store.password}")
private String keyStorePassword;
#Value("${trust.store}")
private Resource trustStoreR;
#Value("${trust.store.password}")
private String trustStorePassword;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(keyStoreR.getURL(), keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(trustStoreR.getURL(), trustStorePassword.toCharArray())
.build();
...
}
When I run the app via bootRun or within Eclipse, both of them work.
But when I use jar launcer
java -jar app.jar
I got below exception.
Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiation
Exception: Failed to instantiate [org.springframework.web.client.RestTemplate]: Factory method 'restTemplate' threw exception;
nested exception is java.io.FileNotFoundException: URL cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/C:/build/libs/app.jar!/BOOT-INF/classes!/truststore.jks
I have also tried
java -Djavax.net.ssl.trustStore=truststore.jks -Djavax.net.ssl.trustStorePassword=xyz123 -Djavax.net.ssl.keyStore=keystore.jks -Djavax.net.ssl.keyStorePassword=xyz123 -jar app.jar
and got the same exception. Any help will be really appreciated.
Try using resource.getInputStream() instead of resource.getFile() as, resource.getFile() in Spring tries to access a file system path but it can not access a path in your JAR
This link has a rich content and do take a look at Andy's answer here
Sample example:
Resource resource = resourceLoader.getResource("classpath:GeoLite2-Country.mmdb");
InputStream dbAsStream = resource.getInputStream();
and do you have use full path
-Djavax.net.ssl.trustStore=something/like/this/truststore.jks

Camel MQTT Configuration options DSL route URL parameter with bean value not working

I ma trying to test pub/sub from an external broker ( AWs IoT ); started off of the camel-example-spring-boot example project and added thecamel-mqtt-starter. Everything seems to work fine until I try to define the mqtt routes. I am having issues with configuring the sslContext url parameter :
#Configuration
public class AppConfig {
#Bean(name="awsiotsslcontext")
SSLContext awsiotsslcontext(){
SSLContext sslContext = null;
try{
ClassLoader cl = this.getClass().getClassLoader();
InputStream is = cl.getResourceAsStream("/cert/myApp.cert.pem");
// You could get a resource as a stream instead.
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(is);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
} catch (Exception e){
}
return sslContext;
}
}
And then in my route builder :
#Component
public class SampleCamelRouter extends RouteBuilder {
#Autowired
SSLContext awsiotsslcontext;
#Override
public void configure() throws Exception {
from("timer://foo?repeatCount=0&delay=5000&fixedRate=true&period=10s")
.setBody(simple("TEST MESSAGE"))
.to("mqtt:awsiot?host=ssl://{{aws.iot.host}}:8883&publishTopicName={{aws.iot.sub.topic}}&sslContext=#awsiotsslcontext").log("Sent :"+body().convertToString().toString());
from("mqtt:awsiot?host=ssl://{{aws.iot.host}}:8883&subscribeTopicName={{aws.iot.sub.topic}}&sslContext=#awsiotsslcontext").log("Recieved : "+body().convertToString().toString());
}
}
getting the following error :
java.lang.IllegalArgumentException: Could not find a suitable setter
for property: sslContext as there isn't a setter method with same
type: java.lang.String nor type conversion possible: No type converter
available to convert from type: java.lang.String to the required type:
javax.net.ssl.SSLContext with value #awsiotsslcontext
I believe this is a simple endpoint configuration issue, but tried various things and nothing seems to work. Having the # with the bean name should have camel to look up in the registry for the bean but here it recognizes it as String ? Any workaround here ?
This was an issue on camel route configuration; when I configured my route under a #Configuration instead of having under #Component as suggested by the documentation, it doesn't complain about bean definition with '#' in the URI; I would expect in a Spring Boot Application the beans would load before the routes by default:
#Bean
RouteBuilder awsIoTRoute() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("timer://foo?repeatCount=0&delay=5000&fixedRate=true&period=17s")
.setBody(simple("TEST MESSAGE"))
.to("mqtt:awsIoTPublisher?host=ssl://{{aws.iot.host}}:8883&publishTopicName={{aws.iot.pub.topic}}&clientId={{aws.iot.pub.clientId}}&sslContext=#sslContext")
.log("Sent :"+body().convertToString().toString());
from("mqtt:awsIoTReciever?host=ssl://{{aws.iot.host}}:8883&subscribeTopicName={{aws.iot.sub.topic}}&clientId={{aws.iot.sub.clientId}}&sslContext=#sslContext").log("Recieved : "+body().convertToString());
}
};
}

Spring Boot Using Embedded Tomcat with JNDI

I am using Spring Boot with Embedded Tomcat and attempting to use JNDI but getting the following error:
javax.naming.NameNotFoundException: Name [jdbc/dataSource]
Any tips would be greatly appreciated.
Here is my code:
#Configuration
public class TomcatJndiConfiguration{
#Value("${database.driver}")
private String driverClassName;
#Value("${database.url}")
private String databaseUrl;
#Value("${database.username}")
private String databaseUsername;
#Value("${database.password}")
private String databasePassword;
#Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
ContextResource resource = new ContextResource();
resource.setName("jdbc/dataSource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", driverClassName);
resource.setProperty("url", databaseUrl);
resource.setProperty("password", databaseUsername);
resource.setProperty("username", databasePassword);
context.getNamingResources().addResource(resource);
}
};
}
#Bean
public DataSource dataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("jdbc/dataSource");
bean.setLookupOnStartup(true);
bean.setProxyInterface(DataSource.class);
bean.setResourceRef(true);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
Stacktrace is:
Caused by: javax.naming.NameNotFoundException: Name [jdbc/dataSource] is not bound in this Context. Unable to find [jdbc].
at org.apache.naming.NamingContext.lookup(NamingContext.java:818)
at org.apache.naming.NamingContext.lookup(NamingContext.java:166)
at org.apache.naming.SelectorContext.lookup(SelectorContext.java:157)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:155)
at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:87)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179)
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95)
at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106)
at org.springframework.jndi.JndiObjectTargetSource.afterPropertiesSet(JndiObjectTargetSource.java:97)
at org.springframework.jndi.JndiObjectFactoryBean$JndiObjectProxyFactory.createJndiObjectProxy(JndiObjectFactoryBean.java:318)
at org.springframework.jndi.JndiObjectFactoryBean$JndiObjectProxyFactory.access$000(JndiObjectFactoryBean.java:307)
at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:200)
at com.kronos.daas.configuration.TomcatJndiConfiguration.dataSource(TomcatJndiConfiguration.java:72)
You need to set lookupOnStartup to false on the JndiObjectFactoryBean.
Alternatively, if you need the lookup to work during startup, then this answer may be of interest.
Edit: you've also set the JNDI name on your JndiObjectFactory bean incorrectly. It needs to be java:comp/env/jdbc/myDataSource not jdbc/dataSource.
You use a different name when you're looking up the resource versus when you registered it as the registration automatically places the resource beneath java:comp/env/.
If you are using spring boot, no need for all of that class.
It is already configured in #EnableAutoConfiguration or
#SpringBootApplication
Just put the following in your application.properties file or equivalent in application.yml file
spring.datasource.driverClassName=JDBCDriverClassName
spring.datasource.url=databaseUrl
spring.datasource.username=databaseUsername
spring.datasource.password=databasePassword
spring.datasource.jndi-name=java:jdbc/dataSource

Spring WS and HTTPS?

I use Spring WS to send a request to web service (DentalXChange).
private static final String MESSAGE = <xml request/>
private final WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
public void customSendAndReceive() {
StreamSource source = new StreamSource(new StringReader(MESSAGE));
StreamResult result = new StreamResult(System.out);
webServiceTemplate.sendSourceAndReceiveToResult("https://prelive2.dentalxchange.com/dws/DwsService",
source, result);
}
And here is error
Exception in thread "main" org.springframework.ws.client.WebServiceIOException: I/O error: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: sun.security.ssl.SSLContextImpl$DefaultSSLContext); nested exception is java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: sun.security.ssl.SSLContextImpl$DefaultSSLContext)
How to solve it. Thanks

Resources