Load certificate from ConfigMap in OpenShift to call external service using ApacheHttpClient - java

I am building an adapter application that should sent the data to an external service using HTTPS. While testing locally (against a mock server), I am saving the certificate in a local truststore and loading it using loadTrustMaterial(file, password) method. The difference in productive environment is that the application is running in OpenShift and the certificate is saved in ConfigMap.
Question:
How can I retrieve that certificate from ConfigMap in OpenShift (is there any url to the cert?), so my application can call external service?
Here are some code snippets for clarity:
private HttpResponse connectToExternalService(String xml) {
StringEntity stringEntity = null;
try {
stringEntity = new StringEntity(xml);
SSLContext sslContext = this.createSSLContext();
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(factory).build();
HttpPost post = new HttpPost(url);
post.setEntity(stringEntity);
post.setHeader("Content-Type", "text/xml");
return client.execute(post);
} catch (IOException e) {
}
}
private SSLContext createSSLContext() {
File file = new File("path to the truststore");
try {
char[] password = "password".toCharArray();
return SSLContexts.custom().loadTrustMaterial(file, password).build();
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
}
}

As config map being used to hold the certificate, the same can be mounted as file inside the container. Please refer k8s documentation to load as file. Post that add it trust store during container start.

Related

Cyberark retrieve password from vault using JAVA

I am working on a Java application, trying to retrieve the password from a Cyberark Vault using Rest API call. I get the data back for the API call through the browser (Edge/Chrome)after importing the client certificate. I tried adding the same cert into java truststore "C:\jdk1.8.0_77\jre\lib\security\cacerts" but when making the call, I get the below error
403 - Forbidden: Access is denied. You do not have permission to view
this directory or page using the credentials that you supplied.
I used to get this error in the browser as well until I imported the client cert. So what am I missing now? Are there any attributes/variables that needs to be set to make the call? I am using Apache HttpClient. I am passing the truststore, password as VM arguments in Eclipse.
KeyStore keyStore = null;
String baseUrl = "https://cyberarkservices:23456/api/Accounts?AppID=myapp&Safe=Test&Object=testobject";
try {
keyStore = KeyStore.getInstance("JKS");
} catch (KeyStoreException e) {
System.out.println(e.getStackTrace());
}
FileInputStream instream = null;
try {
instream = new FileInputStream(new File(System.getProperty("javax.net.ssl.trustStore")));
keyStore.load(instream, System.getProperty("javax.net.ssl.trustStorePassword").toCharArray());
} catch (Exception e) {
System.out.println("Exception occured loading cacerts: " + e);
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = null;
try {
sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, System.getProperty("javax.net.ssl.trustStorePassword").toCharArray())
.build();
} catch (KeyManagementException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
System.out.println("Exception occured loading SSL key material: " + e);
}
HttpClientBuilder builder = HttpClientBuilder.create();
SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(sslcontext,
new String[] { "TLSv1.1", "TLSv1.2" }, null,
NoopHostnameVerifier.INSTANCE);
builder.setSSLSocketFactory(sslConnectionFactory);
builder.setSSLSocketFactory(sslConnectionFactory);
CloseableHttpClient httpclient = builder.build();
CloseableHttpResponse response = null;
try {
HttpGet httpget = new HttpGet(baseUrl);
// CALL API
String reply = "";
response = httpclient.execute(httpget);
String res_xml = EntityUtils.toString(response.getEntity());
if(res_xml!=null && !res_xml.isEmpty())
{
reply = res_xml;
System.out.println(reply);
}
I got it to work. Just read in/loaded the client certificate(.p12) directly from java while making the API call without importing it into trust store or keystore file. Passed in the cert location/password as VM arguments and it worked just fine.

Adding SSLContext in CloseableHttpAsyncClient at Runtime

We have a generic application which delivers message to different POST endpoints. And we are using
CloseableHttpAsyncClient for this purpose. Its been built/initialized as follows,
private static CloseableHttpAsyncClient get() {
CloseableHttpAsyncClient lInstance;
IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
.setIoThreadCount(100)
.setConnectTimeout(10000)
.setSoTimeout(10000).build();
ConnectingIOReactor ioReactor = null;
try {
ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
} catch (IOReactorException e) {
logger_.logIfEnabled(Level.ERROR, e);
}
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
connManager.setDefaultMaxPerRoute(50);
connManager.setMaxTotal(5000);
connManager.closeIdleConnections(10000, TimeUnit.MILLISECONDS);
baseRequestConfig = RequestConfig.custom().setConnectTimeout(10000)
.setConnectionRequestTimeout(10000)
.setSocketTimeout(10000).build();
lInstance = HttpAsyncClients.custom().setDefaultRequestConfig(baseRequestConfig)
.setConnectionManager(connManager).build();
lInstance.start();
return lInstance;
}
This is prebuilt and initialized. As an when a new request arrives to our application, based on message, authentication type, a new postRequest is built httpPost = new HttpPost(builder.build());
After setting the required header, payload etc. exiting httpClient is used to send the request.
httpClient.execute(httpPost, httpContext, null);
Now, the question is based on the our new requirement to support client certificate based authentication. And since our current approach is to create httpClient in the beginning, the question is how to change the behaviour of httpClient to send client certificate to some endpoints and work as it is for other endpoints which doesn't require certificate to be send?
I know I can introduce SSLContext to CloseableHttpAsyncClient while creating, but at the time of creating I don't have any information that we have any endpoint which requires certificate based authentication. And we can have many endpoints which would be supporting client certificate and that would be known at runtime.

Http call in java not sending client certificate

I'm using the Apache HTTP client (version 4.5.13) in Java 8 to perform a POST call that requires the client to authenticate using a certificate certificate, that I have stored in a .PFX file.
This is the code I'm using:
public static void performClientRequest() throws Exception {
//Trust Strategy to accept any server certificate
TrustStrategy trustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
};
//Load PFX client certificate
KeyStore clientStore = KeyStore.getInstance("PKCS12");
InputStream instream = new FileInputStream("C:\\client.pfx");
try {
clientStore.load(instream, null);
} finally {
instream.close();
}
//Create ssl context with key store and trust strategy
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(clientStore, null)
.loadTrustMaterial(trustStrategy)
.build();
//Create ssl socket factory from context
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
//Create HTTP client
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.build();
//Perform call
URI url = new URI("https://mysite.foo");
HttpPost request = new HttpPost(url);
request.setHeader("Content-Type","application/json");
request.setHeader("Accept", "application/json");
String body = "...";
StringEntity bodyEntity = new StringEntity(body);
request.setEntity(bodyEntity);
HttpResponse response = httpClient.execute(request);
HttpEntity entity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
EntityUtils.consume(entity);
}
I've used this code in in the past and it worked perfectly back then, but now I'm trying to reuse it and it just doesn't send the certificate, the server replies with:
HTTP/1.1 403 No client certificate supplied
How can I debug this and discover why the certificate is not being sent?
Note: I implemented a similar call both in C# and using Postman, and in both cases it works perfectly, so the client certificate authentication to the server is working, it's just not working in my Java implementation.
So, I don't know if this is a bug or intended behavior (if so, why?), but apparently the PFX file must be password-protected, then it gets sent correctly. I could not make this work with a non-protected PFX file and passing null as the password like I was doing in the code I posted in the question.
So the problem is solved, but I would be curious if anyone could comment on WHY this happens.

Apache HttpClient and remote files in URL with https scheme

I'm using version 4.2.5. of AutoRetryHttpClient from org.apache.httpcomponents to download a pdf file from an url whose scheme is https. The code is written in NetBeans 7.3 and uses JDK7.
Supposing that the imaginary pdf resource is at https://www.thedomain.with/my_resource.pdf, then I have the following code:
SchemeRegistry registry = new SchemeRegistry();
try {
final SSLSocketFactory sf = new SSLSocketFactory(new TrustStrategy() {
#Override
public boolean isTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
});
registry.register(new Scheme("https", 3920, sf));
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException ex) {
Logger.getLogger(HttpConnection.class.getName()).log(Level.SEVERE, null, ex);
}
//Here I create the client.
HttpClient client = new AutoRetryHttpClient(new DefaultHttpClient(new PoolingClientConnectionManager(registry)),
new DefaultServiceUnavailableRetryStrategy(5, //num of max retries
100//retry interval));
HttpResponse httpResponse = null;
try {
HttpGet httpget = new HttpGet("https://www.thedomain.with/my_resource.pdf");
//I set header and Mozilla User-Agent
httpResponse = client.execute(httpget);
} catch (IOException ex) {
}
... //other lines of code to get and save the file, not really important since the code is never reached
When I call client.execute the following exception is thrown
org.apache.http.conn.HttpHostConnectException: Connection to https://www.thedomain.with refused
What can I do to get that pdf resource?
PS: I can download it via browser, so exists a way to obtain that file.
There seem to be a couple of problems:
You registered the Scheme to use 3920 as the default port, which is a non-standard port number for HTTPS. If the server is actually running on that port, then you would have to access using this URL in the browser: https://www.thedomain.with:3920/my_resource.pdf. Since the URL that you use in the browser does not include the 3920 port, then the server will be running on the default port of 443, so you should use change new Scheme("https", 3920, sf) to new Scheme("https", 443, sf).
It appears that the CN in your server's certificate doesn't match its hostname, which is causing the SSLPeerUnverifiedException. In order for this to work, you would need to use the SSLSocketFactory(TrustStrategy, HostnameVerifier) constructor and pass a verifier that doesn't do this check. Apache provides the AllowAllHostnameVerifier for this purpose.
Note: You really shouldn't use the no-op TrustStrategy and HostnameVerifier in production code, as this essentially turns off all security checks in terms of authenticating the remote server and leaves you open to impersonation attacks.

Authenticating to sharepoint with kerberos from a java HttpClient

I have a linux\java6 client that will authenticate to sharepoint2010 with NTLM and then send HTTP REST web services using Apache Commons HttpClient.
I can do this with NTLM , but I want to use the same REST API to access sharepoint 2010 that uses kerberos auth.
Any examples how to authenticate and send REST over HTTP with a kerberos sharepoint?
(preferably using HttpClient)
p.s.
I dont have access to sharepoint code, but i do have access to sharepoint admin configurations.
This is roughly how I authenticate with NTLM:
HttpClient httpClient = new HttpClient(new SimpleHttpConnectionManager(true));
AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, JCIFS_NTLMScheme.class);
String localHostName = Inet4Address.getLocalHost().getHostName();
authscope = new AuthScope(uri.getHost(), AuthScope.ANY_PORT);
httpClient.getState().setCredentials(authscope,new NTCredentials(
getUsername(),getPassword(),localHostName,getDomain()));
// after the initial ntlm auth I can call my REST service with "httpClient.executeMethod"
int status = httpClient.executeMethod(new GetMethod(accessURI + "/sitecollection/info"));
Please confirm that your environment is correctly setup for Kerberos, this can be achieved by running kinit. If this fails you will need to ensure that your krb5.ini (windows) or krb5.conf (linux) are setup to point to your domain controller correctly.
Once you have confirmed that Kerberos is functional you can use the example code from HttpClient as pasted below.
Please note that there are many issues that can cause Kerberos to fail, such as time synchronisation, supported encryption types, trust relationships across domain forests and it's also worth ensuring that your client is on a seperate box to the server.
Here is the example code which is available in the HttpClient download, you will need to ensure your JAAS configuration and krb5.conf or ini are correct!
public class ClientKerberosAuthentication {
public static void main(String[] args) throws Exception {
System.setProperty("java.security.auth.login.config", "login.conf");
System.setProperty("java.security.krb5.conf", "krb5.conf");
System.setProperty("sun.security.krb5.debug", "true");
System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
DefaultHttpClient httpclient = new DefaultHttpClient();
try {
httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, new SPNegoSchemeFactory());
Credentials use_jaas_creds = new Credentials() {
public String getPassword() {
return null;
}
public Principal getUserPrincipal() {
return null;
}
};
httpclient.getCredentialsProvider().setCredentials(
new AuthScope(null, -1, null),
use_jaas_creds);
HttpUriRequest request = new HttpGet("http://kerberoshost/");
HttpResponse response = httpclient.execute(request);
HttpEntity entity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
System.out.println("----------------------------------------");
if (entity != null) {
System.out.println(EntityUtils.toString(entity));
}
System.out.println("----------------------------------------");
// This ensures the connection gets released back to the manager
EntityUtils.consume(entity);
} finally {
// When HttpClient instance is no longer needed,
// shut down the connection manager to ensure
// immediate deallocation of all system resources
httpclient.getConnectionManager().shutdown();
}
}
}

Categories