Spring JAVA SSL - No name matching localhost found - spring

I have two SSL end-points that share the same application.properties and key store file.
I want to have one end-point call the other, but getting an error No name matching localhost found
How can I adjust this to allow one microservice to call the other(s) as intended below?
I have played with the following to attempt a solution to no avail:
javax.net.ssl.HostnameVerifier()
Created a localhost certificate and added it to the keystore
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
public class submitapplicationcontroller {
#Bean
public WebClient.Builder getWebClientBuilder(){
return WebClient.builder();
}
#Autowired private WebClient.Builder webClientBuilder;
#PostMapping("/submitapplication")
public String submitapplication() {
/*** Returns Error Found Below ***/
String response = webClientBuilder.build()
.post()
.uri("https://localhost:8080/validateaddress")
.retrieve()
.bodyToMono(String.class)
.block();
return response;
}
}
javax.net.ssl.SSLHandshakeException: No name matching localhost found
at java.base/sun.security.ssl.Alert.createSSLException
Error has been observed at the following site(s):
|_ checkpoint ⇢ Request to POST https://localhost:8080/v1/validateaddress
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
public class validateaddresscontroller {
#PostMapping("/validateaddress")
public String validateaddress() {
return "response";
}
}
server.ssl.key-alias=server
server.ssl.key-password=asensitivesecret
server.ssl.key-store=classpath:server.jks
server.ssl.key-store-provider=SUN
server.ssl.key-store-type=JKS
server.ssl.key-store-password=asensitivesecret

The problem here was the way I went about creating and implementing the certificates. I had 2 separate keystores and certificates; one named "server", and one named "localhost". I added the localhost certificate to the server keystore, and applied the server keystore and certificate to the springboot application / application.properties.
What you have to do is create just one certificate and keystore dubbed "localhost" and you have to use that to apply to the application / application.properties.
What you should have after creating the localhost JKS and certificate
server.ssl.key-alias=localhost
server.ssl.key-password=asensitivesecret
server.ssl.key-store=classpath:localhost.jks
server.ssl.key-store-provider=SUN
server.ssl.key-store-type=JKS
server.ssl.key-store-password=asensitivesecret
Note: I don't believe you actually have to create a JKS named "localhost", just the certificate. I just did for testing purposes.

Related

Avoid basic auth when using x.509 authentication

I have created an REST API based on Spring WebFlux that is protected through X.509 authentication. I followed this guide https://www.baeldung.com/x-509-authentication-in-spring-security to create all the certificates.
The router implementation:
#Configuration
class LogRouter {
#Bean
fun functionalRoutes(handler: LogHandler): RouterFunction<ServerResponse> =
route()
.route(RequestPredicates.path("/")) {
ServerResponse.ok().body(Mono.just("I am alive"))
}
.nest(RequestPredicates.path("/api").and(RequestPredicates.accept(MediaType.APPLICATION_JSON))) { builder ->
builder.GET("/fn/mono", handler::monoMessage)
.POST("/fn/mono", handler::monoPostMessage)
}
.build()
}
and app implementation:
#SpringBootApplication
#EnableWebFluxSecurity
class RestplayApplication {
#Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
val principalExtractor = SubjectDnX509PrincipalExtractor()
principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)")
val authenticationManager = ReactiveAuthenticationManager { authentication: Authentication ->
authentication.isAuthenticated = "Trusted Org Unit" == authentication.name
Mono.just(authentication)
}
http
.x509 { x509 ->
x509
.principalExtractor(principalExtractor)
.authenticationManager(authenticationManager)
}
.authorizeExchange { exchanges ->
exchanges
.anyExchange().authenticated()
}
return http.build()
}
}
fun main(args: Array<String>) {
runApplication<RestplayApplication>( *args)
}
I use Firefox browser to test the x.509 authentication and I have added the self signed certificate(rootCA.crt) to the Firefox:
included client certificate(clientBob.p12).
When calling the link in the browser it shows basic authentication form:
However, I expect the authentication form not to be appeared because I have provided a valid client certificate in the browser.
Why the basic form appears every time?
The code is hosted on https://github.com/softshipper/restplay. The password for certificates are always changeit.
I debugged it and the problem seems to be that your client certificate clientBob.crt does not contain the field for Organization Unit of the subject and your principalExtractor is set to extract this field. As a result, your principalExtractor fails, and so it calls authenticationFailureHandler, which is set to prompt for basic authentication by default.
Possible solutions could be:
Use a client certificate that includes the Organization Unit of the subject, and set it to "Trusted Org Unit".
Alter the principalExtractor regex so that it uses a different field. The default one uses the common name (CN). If you do edit this, then remember to also update your authenticationManager to check for "Bob" instead of "Trusted Org Unit"

Invalid Certification with Amazon S3 Presigned URL

I am trying to download an object from an S3 bucket using a presigned url via the following configuration & code:
public void getDocumentFromPresignedUrl(final String presignedUrl, final String id) {
PresignedUrlDownload transfer = null;
try {
File file = File.createTempFile(id, ".pdf");
//errors out on this line
transfer = getTransferMgr().download(new PresignedUrlDownloadRequest(new URL(presignedUrl)), file);
transfer.waitForCompletion();
}
}
Which is configured via the following:
private ClientConfiguration getClientConfiguration() {
ClientConfiguration clientConfig = new ClientConfiguration();
clientConfig.setProtocol(Protocol.HTTPS);
return clientConfig;
}
public TransferManager getTransferMgr() {
return TransferManagerBuilder.standard().withS3Client(getS3Client()).build();
}
public AmazonS3 getS3Client() {
return AmazonS3ClientBuilder.standard().withRegion(region)
.withClientConfiguration(getClientConfiguration()).build();
}
However, the following error is thrown each time:
com.amazonaws.SdkClientException:
Unable to execute HTTP request: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
WHAT I HAVE TRIED:
I tried to take the AWS cert from the presigned url location in-browser, discussed here
I tried to use the traditional RestTemplate provided by Spring, with no luck
I AM able to retrieve the object from S3 both in Postman and my browser, but not via my Spring app
How does one circumvent this sdkClientException and GET their S3 object?
The solution to this problem resides in cacerts. If you are running on a proxy (such as Zscaler), the certification will need to be added to the /cacerts file.
In the case of this question, I was adding the Zscaler cert to the WRONG JRE. Because I was using SpringToolSuite, I needed to add the cert to Spring's JRE, which in my case was:
keytool -import -alias Z_Root -keystore "C:\Program Files\sts-4.8.1.RELEASE\plugins\org.eclipse.justj.openjdk.hotspot.jre.full.win32.x86_64_1.v20201010-1246\jre\lib\security\cacerts" -storepass changeit -file "C:\Users\myUser\Downloads\MyZscalerCert.crt"
and NOT the typical %JAVA_HOME%/jre path.

Wrong "Generated server url" in springdoc-openapi-ui (Swagger UI) deployed behind proxy

Spring Boot 2.2 application with springdoc-openapi-ui (Swagger UI) runs HTTP port.
The application is deployed to Kubernetes with Ingress routing HTTPS requests from outside the cluster to the service.
In this case Swagger UI available at https://example.com/api/swagger-ui.html has wrong "Generated server url" - http://example.com/api. While it should be https://example.com/api.
While Swagger UI is accessed by HTTPS, the generated server URL still uses HTTP.
I had same problem. Below worked for me.
#OpenAPIDefinition(
servers = {
#Server(url = "/", description = "Default Server URL")
}
)
#SpringBootApplication
public class App {
// ...
}
If the accepted solution doesn't work for you then you can always set the url manually by defining a bean.
#Bean
public OpenAPI customOpenAPI() {
Server server = new Server();
server.setUrl("https://example.com/api");
return new OpenAPI().servers(List.of(server));
}
And the url can be defined via a property and injected here.
springdoc-openapi FAQ has a section How can I deploy the Doploy springdoc-openapi-ui, behind a reverse proxy?.
The FAQ section can be extended.
Make sure X-Forwarded headers are sent by your proxy (X-Forwarded-For, X-Forwarded-Proto and others).
If you are using Undertow (spring-boot-starter-undertow), set property server.forward-headers-strategy=NATIVE to make a Web server natively handle X-Forwarded headers. Also, consider switching to Undertow if you are not using it.
If you are using Tomcat (spring-boot-starter-tomcat), set property server.forward-headers-strategy=NATIVE and make sure to list IP addresses of all internal proxies to trust in the property server.tomcat.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}. By default, IP addresses in 10/8, 192.168/16, 169.254/16 and 127/8 are trusted.
Alternatively, for Tomcat set property server.forward-headers-strategy=FRAMEWORK.
Useful links:
Running Behind a Front-end Proxy Server
Customize Tomcat’s Proxy Configuration
ServerProperties.ForwardHeadersStrategy
In case you have non-default context path
#Configuration
public class SwaggerConfig {
#Bean
public OpenAPI openAPI(ServletContext servletContext) {
Server server = new Server().url(servletContext.getContextPath());
return new OpenAPI()
.servers(List.of(server))
// ...
}
}
Below worked for me.
#OpenAPIDefinition(servers = {#server(url = "/", description = "Default Server URL")})
#SpringBootApplication
class App{
// ...
}
or
#OpenAPIDefinition(servers = {#server(url = "/", description = "Default Server URL")})
#Configuration
public class OpenAPIConfig {
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info().title("App name")
.termsOfService("http://swagger.io/terms/")
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
}
}
Generated server url is HHTP - issue

SpringBoot: LoadBalancer [server]: Error choosing server for key default

I'm creating a load balance feature on my project in which I have three server that will simultaneously ping for 15 seconds. However, when I already run my client-side, it always goes to the fallback page and received an error of "LoadBalancer [server]: Error choosing server for key default" even if the servers are already running.
Here are the codes in my project:
app.properties
server.port=8788
server.ribbon.eureka.enabled=false
server.ribbon.listOfServers=localhost:8787,localhost:8789,localhost:8790
#every 15 seconds
server.ribbon.ServerListRefreshInterval=15000
client service (wherein it is my fallback method)
private LoadBalancerClient loadBalancer;
private RestTemplate restTemplate;
public ClientService(RestTemplate rest) {
this.restTemplate = rest;
}
#HystrixCommand(fallbackMethod = "reliable")
public String login() {
ServiceInstance instance = loadBalancer.choose("server");
URI uri = URI.create(String.format("http://%s:%s/admin/ping", instance.getHost(), instance.getPort()));
//URI uri = URI.create("http://localhost:8787/admin/ping");
return this.restTemplate.getForObject(uri, String.class);
}
MainController
public class MainController{
private final static Logger LOGGER = LoggerFactory.getLogger(MainController.class);
#Autowired
private ClientService clientService;
#LoadBalanced
#Bean
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
#Autowired
RestTemplate restTemplate;
...
Client client = new Client();
WebResource resource = client.resource("http://%s:%s/auth/loginvalidate");
ClientResponse response = resource.type(MediaType.APPLICATION_JSON)
.header("Authorization", "Basic " + encodePw)
.get(ClientResponse.class);
I got rid of that error by doing two things:
1) Add the following properties to the remote service:
management.endpoints.web.exposure.include: "*"
management.endpoint.health.enabled: "true"
management.endpoint.restart.enabled: "true"
management.endpoint.info.enabled: "true"
2) Make sure that there is a ping endpoint in the remote service:
public class MainController{
#RequestMapping("/")
public String ribbonPing() {
return this.hostName;
}
}
I added a few amendments to the example provided by Kubernetes Circuit Breaker & Load Balancer Example to test this scenario and put in here.
I suggest that you follow those links as a kind of "best practises" guide in order to build your Hystrix/Ribbon solution. Pay special attention to:
the starters/dependencies added to the pom files
the structure of the Java classes (how and where each bean is declared and injected)
how you configure your (micro-)services (in this case with K8s ConfigMaps)

Spring : Unable to send mail- Could not connect to SMTP host: smtp.gmail.com, port: 465, response: -1

I am trying to send an email through my spring boot app. However, I am getting the following exception:
Could not connect to SMTP host: smtp.gmail.com, port: 465, response: -1
The controller class is as follows :
#RestController
#RequestMapping("/services")
public class MyController {
#Autowired
private MailSender mailSender;
#RequestMapping(value = "/my/mail", method = RequestMethod.GET)
#ResponseBody
public String sendmymail() {
System.out.println("Starting send");
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("Hello");
mailMessage.setTo("myemailid#gmail.com");
mailMessage.setFrom("myemailid#gmail.com");
SimpleMailMessage message = new SimpleMailMessage(mailMessage);
message.setText("Hello");
try {
this.mailSender.send(message);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
System.out.println("Finished send");
return "OK";
}
}
I have configured the properties in application.properties as follows :
spring.mail.host=smtp.gmail.com
spring.mail.port=465
spring.mail.username=<myemailid>
spring.mail.password=<mypassword>
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.transport.protocol=smtps
spring.mail.properties.mail.smtps.quitwait=false
spring.mail.properties.mail.smtp.socketFactory=25
I have added the folllowing dependecy in pom.xml for autowiring MailSender:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
Is there anything that I am doing wrong. The email id and password is correct, as I have checked it multiple times.
Port number for google smtp server is 587:
spring.mail.port=587
Maybe add this too:
spring.mail.properties.mail.smtp.starttls.enable = true
I managed to send emails with my gmail account without these three config values:
spring.mail.properties.mail.transport.protocol=smtps
spring.mail.properties.mail.smtps.quitwait=false
spring.mail.properties.mail.smtp.socketFactory=25
I have no idea what are they but guess you should be ok without them.
I faced similar issue. I followed below 2 steps & it worked -
In gmail settings, Turn ON access for less secure app.
Antivirus settings. Turn off web-shield and mail-shield.

Resources