How to resolve "SSLV3_ALERT_BAD_CERTIFICATE" error in Spring Boot - spring

I have the following rest end point exposed protected by SSL (Spring Boot)
#RestController
public class TestController {
#RequestMapping(value = "/data", method = RequestMethod.GET)
public String getData() {
return "Hello World";
}
In YML I have the following properties
server:
ssl:
enabled: true
client-auth: need
key-store: {keystore-path}
key-store-password: {keystore-password}
key-alias: alias-name
key-store-type: JKS
Now I am trying to call the above rest end point from another app with the following code
URL obj = new URL(GET_URL);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
System.out.println("GET Response Code :: " + responseCode);
But I am getting the following error :
Error: write EPROTO 2771201016:error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_BAD_CERTIFICATE:../../third_party/boringssl/src/ssl/tls_record.cc:587:SSL alert number 42`
How to resolve this error?

Related

Spring JAVA SSL - No name matching localhost found

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.

Retaining the Request's Path During Spring Cloud Gateway Failover

Is there a way to externally configure Spring Cloud Gateway to failover to another data center? I'm thinking of something like this:
spring:
cloud:
gateway:
routes:
- id: test-service
uri: lb://test-service:8085/
predicates:
- Path=/test-service/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: fallback
fallbackUri: forward:/fallback
#fallbackUri: forward:/fallback/test-service
- id: fallback
uri: http://${fallback_data_center}
predicates:
- Path=/fallback/**
---
spring:
config:
activate:
on-profile: data_center_1
fallback_data_center: dc2.com
---
spring:
config:
activate:
on-profile: data_center_2
fallback_data_center: dc1.com
The problem I run into is that the CircuitBreaker filter's fallbackUri parameter only supports forward schemed URIs. However, the path part of the request URL is overridden with the path in the forward URL. So there does not appear to be a way to failover with the path from the original request such as if this configuration had received a request of http://dc1.com/test-service/some/path without creating a configuration for every possible path.
At the time of writing this answer there is still now official way of doing a failover to another host.
What we are trying to achieve in our team is to have routes with Retry and CircuitBreaker filters which can fallback to another host keeping the original request unmodified ( request payload, header, query params and most importantly the API context path ) and just replacing the host so we can fallback to another datacenter.
We archived this by using the default Gateway Retry and CircuitBreaker filters and developing a custom FallbackController which just replaces the host with a configured property and keeps the rest of the request unmodified including the request context path:
#RestController
#RequestMapping("/fallback")
#ConditionalOnProperty(value="gateway.fallback.enabled", havingValue = "true")
public class FallbackController {
private final GatewayFallbackConfig gatewayFallbackConfig;
private final WebClient webClient;
public FallbackController(GatewayFallbackConfig gatewayFallbackConfig) {
this.gatewayFallbackConfig = gatewayFallbackConfig;
this.webClient = WebClient.create();
}
#PostMapping
Mono<ResponseEntity<String>> postFallback(#RequestBody(required = false) String body,
ServerWebExchangeDecorator serverWebExchangeDecorator) {
return fallback(body, serverWebExchangeDecorator);
}
#GetMapping
Mono<ResponseEntity<String>> getFallback(#RequestBody(required = false) String body,
ServerWebExchangeDecorator serverWebExchangeDecorator) {
return fallback(body, serverWebExchangeDecorator);
}
#PatchMapping
Mono<ResponseEntity<String>> patchFallback(#RequestBody(required = false) String body,
ServerWebExchangeDecorator serverWebExchangeDecorator) {
return fallback(body, serverWebExchangeDecorator);
}
#DeleteMapping
Mono<ResponseEntity<String>> deleteFallback(#RequestBody(required = false) String body,
ServerWebExchangeDecorator serverWebExchangeDecorator) {
return fallback(body, serverWebExchangeDecorator);
}
private Mono<ResponseEntity<String>> fallback(String body, ServerWebExchangeDecorator serverWebExchangeDecorator) {
ServerHttpRequest originalRequest = serverWebExchangeDecorator.getDelegate().getRequest();
WebClient.RequestBodySpec request = webClient.method(originalRequest.getMethod())
.uri(buildFallbackURI(originalRequest));
Optional.ofNullable(body)
.ifPresent(request::bodyValue);
return request.exchangeToMono(response -> response.toEntity(String.class));
}
private URI buildFallbackURI(ServerHttpRequest originalRequest) {
return UriComponentsBuilder.fromHttpRequest(originalRequest)
.scheme(gatewayFallbackConfig.getScheme())
.host(gatewayFallbackConfig.getHost())
.port(gatewayFallbackConfig.getPort())
.build(ServerWebExchangeUtils.containsEncodedParts(originalRequest.getURI()))
.toUri();
}
With an additional property configuration holder:
#Getter
#Component
#RefreshScope
#ConditionalOnProperty(value="gateway.fallback.enabled", havingValue = "true")
public class GatewayFallbackConfig {
private final String scheme;
private final String host;
private final String port;
private final Set<String> excludedHeaders;
public GatewayFallbackConfig(
#Value("${gateway.fallback.scheme:https}") String scheme,
#Value("${gateway.fallback.host}") String host,
#Value("${gateway.fallback.port:#{null}}") String port,
#Value("${gateway.fallback.headers.exclude}") Set<String> excludedHeaders) {
this.scheme = scheme;
this.host = host;
this.port = port;
this.excludedHeaders = excludedHeaders;
}
And we are using it with a route configuration like that:
- id: example-route
uri: http://localhost:8080
predicates:
- Path=/foo/bar/**
filters:
- name: CircuitBreaker
args:
name: exampleCircuitBreaker
fallbackUri: forward:/fallback
statusCodes:
- INTERNAL_SERVER_ERROR
- BAD_GATEWAY
- SERVICE_UNAVAILABLE
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE,GATEWAY_TIMEOUT
series: SERVER_ERROR
methods: GET,POST,PUT,DELETE
exceptions: org.springframework.cloud.gateway.support.NotFoundException,javax.security.auth.login.LoginException
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
gateway:
fallback:
scheme: https
host: some.other.host.com
enabled: true

MockMVC multipart returns 404 with context-path

I am using MockMvc multipart in spring boot to test a RestController which accepts a multipart file.
The test runs fine when I don't set context-path in application.yml but as soon as I set it to something the mockMvc returns 404.
Following config works:
Application.yml
server:
port: 8080
servlet:
context-path: /
use-forward-headers: true
forward-headers-strategy: FRAMEWORK
Test.java
// setup
String templateUploadUrl = "http://localhost:8080/v1/file/upload?name=%s";
byte[] templateContent = DataFactory.getFileAsByteArray();
String contentParameterName = "file";
String fileName = "fileName";
MockMultipartFile multipartFile = new MockMultipartFile(contentParameterName, templateContent);
templateUploadUrl = String.format(templateUploadUrl, fileName);
this.mvc.perform(multipart(templateUploadUrl).file(multipartFile));
But following config gives 404:
Application.yml
server:
port: 8080
servlet:
context-path: /context
use-forward-headers: true
forward-headers-strategy: FRAMEWORK
Test.java
// setup
String templateUploadUrl = "http://localhost:8080/context/v1/file/upload?name=%s";
byte[] templateContent = DataFactory.getFileAsByteArray();
String contentParameterName = "file";
String fileName = "fileName";
MockMultipartFile multipartFile = new MockMultipartFile(contentParameterName, templateContent);
templateUploadUrl = String.format(templateUploadUrl, fileName);
this.mvc.perform(multipart(templateUploadUrl).file(multipartFile));

SpringBoot + Keycloak adapter setup failed

I try to secure a SpringBoot backend with Keycloak adapter.
SprinBoot 2.1.9
Keycloak 6.0.1
I'm just scratching my head over lot of bug and missing documentation. Currently, i try to make the keycloak adapter to correctly respond with Cors Header for a 401 WWW-authenticate error.
I've investigated and found that securityContext is null in the class org.keycloak.adapters.AuthenticatedActionsHandler, resulting in header not set in response.
protected boolean corsRequest() {
if (!deployment.isCors()) return false;
KeycloakSecurityContext securityContext = facade.getSecurityContext(); // This return null
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
String exposeHeaders = deployment.getCorsExposedHeaders();
if (deployment.getPolicyEnforcer() != null) {
if (exposeHeaders != null) {
exposeHeaders += ",";
} else {
exposeHeaders = "";
}
exposeHeaders += "WWW-Authenticate";
}
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
Following the code, i found that :
public class OIDCCatalinaHttpFacade extends CatalinaHttpFacade implements OIDCHttpFacade{
public OIDCCatalinaHttpFacade(org.apache.catalina.connector.Request request, HttpServletResponse response) {
super(response, request);
}
#Override
public KeycloakSecurityContext getSecurityContext() {
return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
}
}
getSecurityContext return null.
So what i'm missing to make it work?
This is my springboot application.propertie
server.port = 8081
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.main.allow-bean-definition-overriding=true
logging.level.org.springframework.security=DEBUG
logging.level.org.keycloak=TRACE
keycloak.realm = spring
keycloak.bearer-only = true
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required = none
keycloak.resource = spring-boot-elide
keycloak.credentials.secret = *********************
keycloak.confidential-port = 0
keycloak.enabled = true
keycloak.cors = true
You can use keycloak adaptor with keycloak spring starter and spring security. And use a WebSecurityConfigurerAdapter Config to handle CORS in the overriding configure method.
Refer this doc : https://www.keycloak.org/docs/6.0/securing_apps/index.html#_spring_boot_adapter

Feign Client ignoring request params

I created Feign Client:
#FeignClient(name = "yandex",url="${yandex.ribbon.listOfServers}")
public interface YandexMapsRestApiServiceClient {
#RequestMapping(method = RequestMethod.GET, value = "{geoParam}")
String getCountryInfo(#Param("geoParam") String geoParam);
}
In controller I have been wrote:
#Autowired
private YandexMapsRestApiServiceClient client;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String test() {
return client.getCountryInfo("Moscow");
}
My Applicaton.yml look this:
yandex:
ribbon:
listOfServers: https://geocode-maps.yandex.ru/1.x/?format=json&geocode=
ConnectTimeout: 20000
ReadTimeout: 20000
IsSecure: true
hystrix.command.default.execution:
timeout.enabled: true
isolation.thread.timeoutInMilliseconds: 50000
When I try to get some result, in return I get 404 error:
feign.FeignException: status 404 reading YandexMapsRestApiServiceClient#getCountryInfo(String); content:
In this case, I see in the debugger that he feign not set my geoParam:
Why does this happen and how to solve this problem?
As Musaddique has stated, you are mixing Feign and Spring annotations. When using Spring Cloud Feign(OpenFeign), you must use the Spring annotation RequestParam. Feign annotations will not be processed.
Update
To achieve what you are looking for, you will need to change your configuration. The of url should be a url or service name only. Using query string or other extensions to the url will have unexpected results.
Move the path information to the RequestMapping annotation and specify the query parameter there.
#FeignClient(name = "yandex", url="${yandex.ribbon.listOfServers}")
public interface YandexMapsRestApiServiceClient {
#RequestMapping(method = RequestMethod.GET, value = "/1.x?format=json&geocode={geoParam}")
String getCountryInfo(#RequestParam("geoParam") String geoParam);
}
Where your ribbon configuration looks like this:
yandex:
ribbon:
listOfServers: "https://geocode-maps.yandex.ru"
ConnectTimeout: 20000
ReadTimeout: 20000
IsSecure: true
Now, using your example of client.getCountryInfo("moscow") will result in the final url of https://geocode-maps.yandex.ru/1.x?format=json&geocode=moscow.

Resources