How to hit Secure Elastic Search through Java High Level Rest Client - java

I'm new to Elastic search. Integrated my Spring boot application with Elastic search through Java High Level Rest Client.
I've configured JHLRC bean as below and it worked fine:
#Bean(destroyMethod = "close")
public RestHighLevelClient client() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));
return client;
}
Started exploring the security for Elasticsearch, after setup certificate and passwords, I've enabled security by providing below properties :
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
I'm able to login in kibana by using a created username and password but getting 401 Unauthorized while hitting any Elastic search API through JHLRC.
Can someone please help me on what further changes I've to make while configuring Java High Level Rest Client to hit secure Elastic search?

It worked after making below changes in JHLRC:
#Bean(destroyMethod = "close")
public RestHighLevelClient client() {
final BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
basicCredentialsProvider
.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "password_generated_by_elastic_search"));
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
.setHttpClientConfigCallback(new HttpClientConfigCallback() {
#Override
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
httpClientBuilder.disableAuthCaching();
return httpClientBuilder.setDefaultCredentialsProvider(basicCredentialsProvider);
}
})
);
return restHighLevelClient;
}

You need to include the Basic credentials which you are giving while accessing the kibana, below code shows you can pass the username and password in JHLRC.
First, create the encoded string from your username and password, you can use the superuser elastic which has all the access by using the below code.
private String getEncodedString(String username, String password) {
return HEADER_PREFIX + Base64.getEncoder().encodeToString(
(username + ":" + password)
.getBytes());
}
Now in your request option, you pass the auth header which will include the base 64 encoded string which you will get from the above method.
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder()
.addHeader(AUTH_HEADER_NAME, getEncodedString(basicCredentials));
Last, you just need to build the object of above requestion options builder and pass it to your client in any request like below:
GetResponse getResponse = restHighLevelClient.get(getRequest, builder.build());

Related

How to connect Elasticsearch(8.x) using Java low level client configuration as RestHightLevelClient is deprecated

I am new to ElasticSearch and trying to establish connection to elasticsearch using Java Lowlevel rest client configuration as RestHightLevelClient is deprecated.The documentation says to add below code but I am not sure where the code needs to be added.
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 9201, "http")).build();
The below RestHightLevelClient configuration works fine but it is deprecated.
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.demo.elasticsearch.repositories")
#ComponentScan(basePackages = {"com.demo.elasticsearch"})
public class ElasticSearchConfiguration
extends AbstractElasticsearchConfiguration
{
#Override
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration clientConfiguration =
ClientConfiguration
.builder()
.connectedTo("host:port")
.usingSsl()
.withBasicAuth("username","password")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
Can someone please help me with java low level rest client config
EDIT:
Try replacing your second code-block with this one:
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.demo.elasticsearch.repositories")
#ComponentScan(basePackages = {"com.demo.elasticsearch"})
public class ElasticSearchConfiguration
extends AbstractElasticsearchConfiguration
{
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 9201, "http")).build();
// Create the transport with a Jackson mapper
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
// And create the API client
ElasticsearchClient esClient = new
ElasticsearchClient(transport);
return esClient;
}
This worked for me to get the connection working with a local elasticsearch.
Old post:
I am struggeling with this too. I also try to connect with BasicAuth to https URL.
I found the following documentation: https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/_basic_authentication.html
Maybe it is helpful for you. Let me know in case you managed to solve this problem.
I currently replaced your block with
#Bean
public ElasticsearchClient elasticsearchClient() {
final CredentialsProvider credentialsProvider =
new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(user, password));
RestClientBuilder builder = RestClient.builder(
new HttpHost("mycustomurlHidden.com", 9243))
.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider);
}
});
I am still getting an error, but maybe it works for you

https proxy using okhttp3

I am using okhttp3 and trying to see how do I pass userId & pswd to authenticate with proxy server that accepts only HTTPS protocol. I already saw exmaple over SO & on other sites(link below) but they don't have HTTPS protocol.
https://botproxy.net/docs/how-to/okhttpclient-proxy-authentication-how-to/
Can anyone please tell me how to use HTTPS protocol to call proxy server?
It is not officially supported but there is a workaround.
https://github.com/square/okhttp/issues/6561
Authenticator proxyAuthenticator = new Authenticator() {
#Override public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(username, password);
return response.request().newBuilder().header("Proxy-Authorization", credential).build();
}
};
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)))
.proxyAuthenticator(proxyAuthenticator);
.socketFactory(new DelegatingSocketFactory(SSLSocketFactory.getDefault()))
.build();

OAuth 2.0 Access Tokens and Client Certificate

So I'm currently developing a Spring boot MS that needs to connect to an external API which has OAuth 2.0 implemented.
The API Store uses a custom version of a grant type called a Client Certificate.
This grant type uses a combination of Mutual SSL and Application level credentials.
It requires two identity factors:
Identity Factor 1 – Mutual SSL: Certificate created by me signed by the API store owner
Identity Factor 2 – Application Level Credentials: {consumerKey:consumerSecret}
The curl command for obtaining this token is:
curl -k -d "grant_type=client_cert" --basic -u "{consumer key}:{consumer secret}" -H "Content-Type: application/x-www-form-urlencoded" --cert {Certificate Pem} https://api.examplestore.com/token
How can I translate this to my Spring boot application?
I've currently written this piece of code, but I think I'm far off.
public void TokenRequest() {
ResponseEntity<String> response = null;
RestTemplate restTemplate = new RestTemplate();
String credentials = String.format("%s:%s", consumerKey, consumerSecret);
String encodedCredentials = new String(Base64.getEncoder().encodeToString(credentials.getBytes()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//headers.setCertificate??
headers.add("Authorization", "Basic " + encodedCredentials);
HttpEntity<String> request = new HttpEntity<String>(headers);
response = restTemplate.exchange(tokenUrl, HttpMethod.POST, request, String.class);
}
Any help is welcome. Thank you :)
I think you are not that far off.
You defenitely need to include the body:
HttpEntity<String> request = new HttpEntity<String>("grant_type=client_cert", headers);
Also you need to include the certificate, maybe like this:
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial(new URL("/path/to/your/cert"), "certpassword".toCharArray())
.setProtocol("yourProtocol")
.build();
final HttpClient httpClient = HttpClientBuilder.create()
.setSSLContext(sslContext)
.build();
final ClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
...

Springboot SLL API consume

I am trying to consume an API from a 3rd party server. The 3rd party sent me an SSL certificated named certificate.p12 which is the cert file which I use to do the handshake. I have created a custom RestTemplate with SSL as follows:
#Configuration
public class CustomRestTemplate {
private static final String PASSWORD = "fake_password";
private static final String RESOURCE_PATH = "keystore/certificate.p12";
private static final String KEY_TYPE = "PKCS12";
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
char[] password = PASSWORD.toCharArray();
SSLContext sslContext = SSLContextBuilder
.create()
// .loadKeyMaterial(keyStore(RESOURCE_PATH, password), password)
.loadKeyMaterial(keyStore(getClass().getClassLoader().getResource(RESOURCE_PATH).getFile(), password), password)
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.build();
HttpClient client = HttpClients
.custom()
.setSSLContext(sslContext)
.build();
return builder
.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(client))
.build();
}
private KeyStore keyStore(String file, char[] password) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KEY_TYPE);
File key = ResourceUtils.getFile(file);
try (InputStream in = new FileInputStream(key)) {
keyStore.load(in, password);
}
return keyStore;
}
}
I then call the endpoint using the following code:
#Component
#Service
public class TransactionService implements TransactionInterface {
#Autowired
private CustomRestTemplate restTemplate = new CustomRestTemplate();
private static final String BASE_URL = "https://41.x.x.x:xxxx/";
#Override
public List<Transaction> getUnsentTransactions(int connectionId) throws Exception {
HttpEntity<?> httpEntity = new HttpEntity<>(null, new HttpHeaders());
ResponseEntity<Transaction[]> resp = restTemplate
.restTemplate(new RestTemplateBuilder())
.exchange(BASE_URL + "path/end_point/" + connectionId, HttpMethod.GET, httpEntity, Transaction[].class);
return Arrays.asList(resp.getBody());
}
}
I get an the following stacktrace when trying to consume the api:
org.springframework.web.client.ResourceAccessException: I/O error on GET request for \"https://41.x.x.x:xxxx/path/endpoint/parameters\": Certificate for <41.x.x.x> doesn't match any of the subject alternative names: [some_name_here, some_name_here];
I do not have much experience with TLS or SSL certificates. I am really stuck at the moment and hoping I can get some help here. The 3rd party provided me with a testing site where I can test the endpoints and after importing the certificate.p12 file into my browser I can reach the endpoints using their testing site but my Springboot application still does not reach the endpoint.
Do I need to copy the cert into a specific folder? This does not seem like the case because I get a FileNotFoundException if I change the path or filename and I get a password incorrect error if I enter the wrong password for the certificate.p12 file. I tried using Postman to test it but Postman returns the same stacktrace as my web application.
Looking at the information above, am I missing something? Is the keystore not being created during runtime? Do I need to bind the certificate to the JVM or my outgoing request?
Any help would be appreciated.
Thanks
It looks like you are trying to connect to a server which doesn't have a valid name in the certificate. For example, if you are connecting to "stackoverflow.com", the certificate needs that domain in the "subject" or the "subject alternative names" field.
Even a testing site should have a valid certificate, but if that's not possible (as it's a third party site and you can't change it yourself), you can disable the verification using this question
Of course, this should only be done for testing.

ReadyApi why does authorization header not work, but basic auth tab does

I'm attempting to re-create a REST call I use in Ready-API from java but having issues.
If I make a GET request in ReadyAPI with and I use the AUTH tab in the UI, and set it to Basic, with a username and password and I check "USE GLOBAL PREFERENCE" it works without issue. However if I select "Authenticate pre-emptively" it fails.
Also in readyAPI if I insert an Authorization header with the base64 encoded string, instead of using the "Auth" tab, it also fails. This works for other servers I attempt to talk to, but not this one.
I'm trying to find out why it fails with the Authorization Header. As I'm attempting to make the same call from java with restTemplate.
Something like:
String plainCreds = username + ":" + password;
byte[] plainCredsBytes = StringUtils.getBytesUtf8(plainCreds);
String base64Creds = Base64.encodeBase64String(plainCredsBytes);
httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.AUTHORIZATION, "Basic " + base64Creds);
What is ReadyAPI doing differently when using the Auth Tab with "Use Global Preferences" that makes it succeed? How can I do this in Java?
The authentication scheme for "basic" needs to be passed with the appropriate capitalization of the scheme name:
httpHeaders.add(HttpHeaders.AUTHORIZATION, "Basic " + base64Creds);
https://www.rfc-editor.org/rfc/rfc7617 Section 2. The 'Basic' Authentication Scheme
I know this is an old question, but I hope this helps somebody.
I had this same scenario and I found the solution here: https://www.baeldung.com/resttemplate-digest-authentication
Basically, you have to create your own RestTemplate bean so the Authorization is by Digest and not Basic:
#Bean(name = "myRestTemplate")
public RestTemplate myRestTemplate(
final String username,
final String password) {
CloseableHttpClient client = HttpClientBuilder.create().
setDefaultCredentialsProvider(provider(username,password)).useSystemProperties().build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client);
return new RestTemplate(requestFactory);
}
private CredentialsProvider provider(String username, String password) {
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
provider.setCredentials(AuthScope.ANY, credentials);
return provider;
}
Then when you want to use the bean
private String getQueryOutput(String query) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
ResponseEntity<String> resp = restTemplate.exchange(
"https://myURL/to/accept/post",
HttpMethod.POST,
new HttpEntity<>(query, httpHeaders),
String.class);
return resp.getBody();
}

Categories