Websphere - Spring Integration SSL issue - spring

Our Spring Integration application runs on Websphere. It is a client to an SSL external service.
I've imported a certificate using Retrive from port [into default trust store], giving host and 443 port. Enabled tracing on WebSphere and it seems it is looking at cacert file and not trust.p12.
[18-2-19 13:44:59:154 CET] 00000063 SystemOut O 2019-02-18 13:44:59.153 INFO 30426 --- [ver.startup : 0] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [public org.springframework.http.ResponseEntity<springfox.documentation.spring.web.json.Json> springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation(java.lang.String,javax.servlet.http.HttpServletRequest)]
[18-2-19 13:44:59:826 CET] 00000063 SystemOut O keyStore is: /srv/opt/IBM/WebSphere/AppServer/java/8.0/jre/lib/security/cacerts
Code:
public class PreemptiveMessageSender extends HttpComponentsMessageSender {
#Autowired
private Environment env;
private String host;
private String userId;
private String password;
public PreemptiveMessageSender() {
super();
}
public PreemptiveMessageSender(HttpClient httpClient) {
super(httpClient);
}
#Override
protected HttpContext createContext(URI uri) {
HttpHost targetHost = new HttpHost(host, 443, "https");
String decryptedPassword = getDecryptedPassword();
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(userId, decryptedPassword));
AuthCache authCache = new BasicAuthCache();
authCache.put(targetHost, new BasicScheme());
// Add AuthCache to the execution context
final HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
return context;
}
private String getDecryptedPassword() {
BasicTextEncryptor textEncrypt = new BasicTextEncryptor();
textEncrypt.setPassword(env.getProperty("KEY_PASSWORD"));
return textEncrypt.decrypt(password);
}
#Override
public WebServiceConnection createConnection(URI uri) throws IOException {
HttpPost httpPost = new HttpPost(uri);
if (isAcceptGzipEncoding()) {
httpPost.addHeader(HttpTransportConstants.HEADER_ACCEPT_ENCODING,
HttpTransportConstants.CONTENT_ENCODING_GZIP);
}
HttpContext httpContext = createContext(uri);
return new CustomHttpComponentsConnection(getHttpClient(), httpPost, httpContext);
}
...
}
Error:
"exception": "org.springframework.ws.client.WebServiceIOException",
"message": "I/O error: com.ibm.jsse2.util.h: PKIX path building failed: java.security.cert.CertPathBuilderException:
PKIXCertPathBuilderImpl could not build a valid CertPath.; internal
cause is: \n\tjava.security.cert.CertPathValidatorException: The
certificate issued by CN=ODC Test Root CA - G1, O=ODC Test, C=TU is
not trusted; internal cause is:
\n\tjava.security.cert.CertPathValidatorException: Certificate
chaining error; nested exception is
javax.net.ssl.SSLHandshakeException: com.ibm.jsse2.util.h: PKIX path
building failed: java.security.cert.CertPathBuilderException:
PKIXCertPathBuilderImpl could not build a valid CertPath.; internal
cause is: \n\tjava.security.cert.CertPathValidatorException: The
certificate issued by CN=ODC Test Root CA - G1, O=ODC Test, C=TU is
not trusted; internal cause is:
\n\tjava.security.cert.CertPathValidatorException: Certificate
chaining error",
Question:
Is this problem with Spring Integration using the java cacert? How to make it use the trust store of WebSphere?

I'll start with I don't know anything about Spring. But given the behavior you talk about it must be creating its own instance of the SSLContext. This will cause it to by pass WebSphere SSL settings. It must be doing something like SSLContext.getInstance() to create its own instance or it could be doing something like SSLContext.getDefault() which returns you the JDK's default SSLContext. Both will not get you a WebSphere SSLContext.

https://developer.ibm.com/answers/questions/394270/im-using-an-apache-httpclient-to-make-an-outbound/
HttpClient theClient =
HttpClientBuilder.create().useSystemProperties().addInterceptorFirst(new
RemoveSoapHeadersInterceptor()).build();
private static class RemoveSoapHeadersInterceptor implements HttpRequestInterceptor {
#Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
if (request instanceof HttpEntityEnclosingRequest) {
if (request.containsHeader(HTTP.TRANSFER_ENCODING)) {
request.removeHeaders(HTTP.TRANSFER_ENCODING);
}
if (request.containsHeader(HTTP.CONTENT_LEN)) {
request.removeHeaders(HTTP.CONTENT_LEN);
}
}
}
}

Related

Micrometer with Elasticsearch over SSL

I'm trying to use Micrometer with Elasticsearch over SSL.
I use Micrometer in version 1.8.0, Elasticsearch in version 7.16.3 and OpenJDK 11.0.2 .
Because I know that it's not possible to use a built-in configuration (link) I tried to inject a custom HttpUrlConnectionSender as in the following class SecureHttpSender:
public class SecureHttpSender extends HttpUrlConnectionSender {
...
public SecureHttpSender(ElasticProperties properties, SecureElasticProperties secureElasticProperties) {
super(properties.getConnectTimeout(), properties.getReadTimeout());
this.secureElasticProperties = secureElasticProperties;
this.sslSocketFactory = buildSslSocketFactory();
}
#Override
public Response send(Request request) throws IOException {
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = (HttpURLConnection) request.getUrl().openConnection();
// if the connection is an instance of the HttpsURLConnection class, the ssl configuration will always been applied.
if (httpURLConnection instanceof HttpsURLConnection) {
// - hostname verifier
if (!secureElasticProperties.isVerifyHostname()) {
logger.debug("setting the hostname verifier to: {}", NoopHostnameVerifier.INSTANCE);
((HttpsURLConnection) httpURLConnection).setHostnameVerifier(NoopHostnameVerifier.INSTANCE);
}
// - trust store configuration
((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslSocketFactory);
}
return super.send(request);
} finally {
try {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
} catch (Exception ignore) {
}
}
}
private SSLSocketFactory buildSslSocketFactory() {
SSLSocketFactory sslSocketFactory;
try (InputStream is = getInputStream(secureElasticProperties.getTrustStorePath())) {
KeyStore truststore = KeyStore.getInstance(secureElasticProperties.getTrustStoreType());
truststore.load(is, secureElasticProperties.getTrustStorePassword().toCharArray());
SSLContextBuilder sslBuilder = SSLContexts.custom().loadTrustMaterial(truststore, null);
final SSLContext sslContext = sslBuilder.build();
sslSocketFactory = sslContext.getSocketFactory();
} catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) {
String message = String.format("error while loading the security configuration from: %s", secureElasticProperties);
logger.error(message, e);
throw new RuntimeException("management.metrics.export.elastic.ssl");
}
return sslSocketFactory;
}
private InputStream getInputStream(String trustStorePathString) throws IOException {
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource resource = pathMatchingResourcePatternResolver.getResource(trustStorePathString);
return resource.getInputStream();
}
}
that I injected with Spring Boot so I can apply the desired configuration, but I got the following error:
ERROR 10912 --- [trics-publisher] i.m.elastic.ElasticMeterRegistry : failed to send metrics to elastic
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...
The server certificate and the client truststore are valid because I already used them with success.
I also tried to force a specific version of the TLS protocol during the handshake phase: TLSv1.3 and TLSv1.2 but the error still occurs.
Anyone have any suggestions on how to fix it? thanks
Check what super.send does, it creates a new connection without using the one you created. I'm not recommending using a self-signed cert and a custom truststore but you can set a default HostnameVerifier using
HttpsURLConnection.setDefaultHostnameVerifier.
Since this is static, it will work for all HttpsURLConnection instances so you don't need to inject anything into Micrometer.
The right solution would be either using a non-self-signed cert or a proper truststore (e.g.: via javax.net.ssl.trustStore).
I did a test with a simple change to the code I had posted and I solved it:
I copied all code of the super.send() method, adding the additional code to set the custom SslSocketFactory and all was OK!
so the reason was that
it creates a new connection without using the one you created
as Jonatan said... a my trivial mistake. :)

How to use Resilience4j Circuit Breaker with WebFlux in Spring Boot

I have service A that calls downstream service B.
Service A code
#RestController
#RequestMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService){
this.greetingService = greetingService;
}
#GetMapping(value = "/greetings")
public Mono<String> getGreetings() {
return greetingService.callServiceB();
}
}
#Component
#RequiredArgsConstructor
public class GreetingService {
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("greetingService");
Callable<Mono<String>> callable = CircuitBreaker.decorateCallable(circuitBreaker, this::clientCall);
Future<Mono<String>> future = Executors.newSingleThreadExecutor().submit(callable);
public Mono<String> callServiceB() {
try {
return future.get();
} catch (CircuitBreakerOpenException | InterruptedException | ExecutionException ex){
return Mono.just("Service is down!");
}
}
private final String url = "/v1/holidaysgreetings";
private Mono<String> clientCall(){
WebClient client = WebClient.builder().baseUrl("http://localhost:8080").build();
return client
.get()
.uri(url)
.retrieve()
.bodyToMono(String.class);
}
when i shut down downstream service B(running on localhost:8080) and hit /greetings endpoint in GreetingsController class to see if my circuit breaker is working properly or not, i get very this nasty error
2021-06-28 21:27:31.431 ERROR 10285 --- [nio-8081-exec-7] o.a.c.c.C.[.[.[.[dispatcherServlet]: Servlet.service() for servlet [dispatcherServlet] in context with path [/v1/holidaysgreetings]
threw exception [Request processing failed; nested exception is org.springframework.web.reactive.function.client.WebClientRequestException: Connection refused: localhost/127.0.0.1:8080;
nested exception is io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8080] with root cause
java.net.ConnectException: Connection refused
Anyone knows why i am getting this? What i am missing here? Am i implementing circuit breaker correctly?
You are mixing reactive libraries with regular non-reactive libraries. If you aim to use spring-webflux it is better to use the reactor-resilience4j together with the regular reactor-adapter library.
Use these imports:
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
implementation "io.projectreactor.addons:reactor-adapter:${reactorVersion}"
You are also not creating the circuit-breaker service that you can rely on. After creating it you can call the " Mono run(Mono toRun, Function<Throwable, Mono> fallback)" (to the one that return a Flux if you want) to execute your service and provide a fallback.
Here is one example from a demo code.
#RestController
public class CompletableFutureDemoController {
Logger LOG = LoggerFactory.getLogger(CompletableFutureDemoController.class);
private CompletableFutureHttpBinService httpBin;
private ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory;
public CompletableFutureDemoController(CompletableFutureHttpBinService httpBin, ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory) {
this.httpBin = httpBin;
this.reactiveCircuitBreakerFactory = reactiveCircuitBreakerFactory;
}
#GetMapping("/completablefuture/delay/{seconds}")
public Mono<Map> delay(#PathVariable int seconds) {
return reactiveCircuitBreakerFactory.create("completablefuturedelay")
.run(Mono.fromFuture(httpBin.delay(seconds)), t -> {
LOG.warn("delay call failed error", t);
Map<String, String> fallback = new HashMap();
fallback.put("hello", "world");
return Mono.just(fallback);
}
);
}
}

EncryptedData was not recognized, apache cxf with spring

my error is this:
org.apache.cxf.interceptor.Fault: Message part
{http://www.w3.org/2001/04/xmlenc#}EncryptedData was not recognized.
(Does it exist in service WSDL?)
Which is due to setting properties for decoding ecrypted data. My issue is that I am having problems how to do that with apache cxf (Timestamp and Signature works ok).
Here is my part of code:
public WSS4JStaxInInterceptor wss4JStaxInInterceptor() throws Exception {
WSSSecurityProperties inProperties = new WSSSecurityProperties();
//inProperties.addAction(WSSConstants.USERNAMETOKEN);
inProperties.addAction(WSSConstants.TIMESTAMP);
inProperties.addAction(WSSConstants.SIGNATURE);
inProperties.addAction(WSSConstants.ENCRYPTION);
inProperties.setEncryptionUser("xxx");
inProperties.loadDecryptionKeystore(this.getClass().getClassLoader().getResource("\"C:\\\\Users\\\\miha_\\\\OneDrive\\\\Dokumenti\\\\Job\\\\Lj\\\\Spring\\\\demo\\\\src\\\\main\\\\resources\\\\xxxx.jks"),"xxx".toCharArray());;
inProperties.setMustUnderstand(false);
inProperties.loadSignatureKeyStore(this.getClass().getClassLoader().getResource("\"C:\\\\Users\\\\miha_\\\\OneDrive\\\\Dokumenti\\\\Job\\\\Lj\\\\Spring\\\\demo\\\\src\\\\main\\\\resources\\\\xxxx.jks"),"xxx".toCharArray());
inProperties.setSignatureUser("cbd");
//inProperties.setSignatureVerificationCryptoProperties(wss4jInProperties());
//inProperties.setUsernameTokenPasswordType(WSSConstants.UsernameTokenPasswordType.PASSWORD_DIGEST);
inProperties.setCallbackHandler(new ClientKeystorePasswordCallback());
WSS4JStaxInInterceptor wss4JStaxInInterceptor = new WSS4JStaxInInterceptor(inProperties);
return wss4JStaxInInterceptor;
}
So I define "loadDecryptionKeystore" in which I get keystore. But where do I define which certificate to take (with setEncryptionUser("xxx"); ?) and where password to access private key in certificate?
Should I define also something else, how ?
ps.: this is configuration for server part when receiving request
thank you
You define which certificate to take by calling setEncryptionUser.
The password for the private key should by supplied by the CallbackHandler that you define by calling setCallbackHandler. When the password for the private key will be needed, the framework will request it by calling the callback handler with an instance of WSPasswordCallback (see the documentation section about WSPasswordCallback identifiers for details).
A simple example of a callback handler:
/**
* #see ClientKeystorePasswordCallback
*/
public class ClientKeystorePasswordCallback implements CallbackHandler {
private Map<String, String> passwords =
new HashMap<String, String>();
public ClientKeystorePasswordCallback() {
passwords.put("myclientkey", "ckpass");
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
String pass = passwords.get(pc.getIdentifier());
if (pass != null) {
pc.setPassword(pass);
return;
}
}
}
}

Spring boot AzureAD AADAuthenticationFilter checks for invalid issuer how do I overwrite or config this to different issuer

Using azure ad spring starter 2.1.6. with the AADAuthenticationFilter https://github.com/Microsoft/azure-spring-boot, the code goes to buildUserPrincipal within this there is a verify issuer call. The default appears below. My issuer is https://login.microsoftonline.com/uuid/v2.0 not the default thus it throws an error, how can I overwrite or config this to check against my issuer?
private ConfigurableJWTProcessor<SecurityContext> getAadJwtTokenValidator(JWSAlgorithm jwsAlgorithm) {
final ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
final JWSKeySelector<SecurityContext> keySelector =
new JWSVerificationKeySelector<>(jwsAlgorithm, keySource);
jwtProcessor.setJWSKeySelector(keySelector);
jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<SecurityContext>() {
#Override
public void verify(JWTClaimsSet claimsSet, SecurityContext ctx) throws BadJWTException {
super.verify(claimsSet, ctx);
final String issuer = claimsSet.getIssuer();
if (issuer == null || !issuer.contains("https://sts.windows.net/")
&& !issuer.contains("https://sts.chinacloudapi.cn/")) {
throw new BadJWTException("Invalid token issuer");
}
}
});
return jwtProcessor;
}

Error creating WebSocket connection using AsyncHttpClient behind Squid Proxy

Library in use:
AsyncHtpClient Library:
Version : 1.9.32
Location: https://github.com/AsyncHttpClient/async-http-client
Netty Version : 3.10.3.Final
Proxy: Squid Proxy
I am trying to create a websocket connection using AsyncHttpClinet library. It works fine when using without the proxy.
But when I start a proxy and pass in the Host, port, username and password , I am unable to create a websocket connection.
It get a stack trace which says Invalid Status Code 400:
Caused by: java.lang.IllegalStateException: Invalid Status Code 400
at com.ning.http.client.ws.WebSocketUpgradeHandler.onCompleted(WebSocketUpgradeHandler.java:76)
at com.ning.http.client.ws.WebSocketUpgradeHandler.onCompleted(WebSocketUpgradeHandler.java:29)
at com.ning.http.client.providers.netty.future.NettyResponseFuture.getContent(NettyResponseFuture.java:177)
at com.ning.http.client.providers.netty.future.NettyResponseFuture.done(NettyResponseFuture.java:214)
... 35 more
I am setting the proxy object like this:
ProxyServer ps = new ProxyServer("host-name",portNo,"user_name","password");
AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder().setProxyServer(ps).build();
WebSocket websocket = c.prepareGet(url)
.execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(
new WebSocketTextListener() {
#Override
public void onMessage(String message) {
}
#Override
public void onFragment(String s, boolean b) {
}
#Override
public void onOpen(WebSocket websocket) {
}
#Override
public void onClose(WebSocket websocket) {
}
#Override
public void onError(Throwable t) {
}
}
).build()
).get();
Are there any other steps to configure a proxy for websocket connections?
I have also tried configuring the ProxyServer object like this:
ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS,"host-name",portNo,"user_name","password");

Resources