Commit 7f9e5473 authored by Rustam Lotsmanenko (EPAM)'s avatar Rustam Lotsmanenko (EPAM) Committed by Riabokon Stanislav(EPAM)[GCP]
Browse files

Keep Elasticsearch credentials encrypted in Redis cache(GONRG-3021) & refactor...

Keep Elasticsearch credentials encrypted in Redis cache(GONRG-3021) & refactor kms client & refactor dependencies
parent 7ee3ab5d
This diff is collapsed.
......@@ -37,80 +37,36 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<failOnMissingWebXml>false</failOnMissingWebXml>
<project.main.basedir>${project.parent.basedir}</project.main.basedir>
<osdu.oscorecommon.version>0.11.0</osdu.oscorecommon.version>
</properties>
<dependencies>
<dependency>
<groupId>org.opengroup.osdu</groupId>
<artifactId>search-core</artifactId>
<version>0.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.opengroup.osdu</groupId>
<artifactId>core-lib-gcp</artifactId>
<version>0.10.0</version>
<version>0.12.0-rc3</version>
</dependency>
<dependency>
<groupId>org.opengroup.osdu</groupId>
<artifactId>os-core-common</artifactId>
<version>${osdu.oscorecommon.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-core</artifactId>
<version>1.38.1</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-datastore</artifactId>
<version>1.72.0</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-logging</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.28.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava-jdk5</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-storage</artifactId>
<version>v1-rev150-1.25.0</version>
<artifactId>search-core</artifactId>
<version>0.12.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava-jdk5</artifactId>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-iam</artifactId>
<version>v1-rev281-1.25.0</version>
<groupId>org.opengroup.osdu</groupId>
<artifactId>os-core-common</artifactId>
<version>${osdu.oscorecommon.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava-jdk5</artifactId>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-cloudkms</artifactId>
<version>v1-rev81-1.25.0</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
......
......@@ -17,21 +17,72 @@
package org.opengroup.osdu.search.provider.gcp.cache;
import com.google.gson.Gson;
import java.io.IOException;
import java.util.Objects;
import javax.inject.Inject;
import org.apache.http.HttpStatus;
import org.opengroup.osdu.core.common.cache.RedisCache;
import org.opengroup.osdu.core.common.model.http.AppException;
import org.opengroup.osdu.core.common.model.search.ClusterSettings;
import org.opengroup.osdu.core.common.provider.interfaces.IElasticCredentialsCache;
import org.opengroup.osdu.core.common.provider.interfaces.IKmsClient;
import org.opengroup.osdu.search.config.SearchConfigurationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ElasticCredentialsCache extends RedisCache<String, ClusterSettings> {
@Autowired
public ElasticCredentialsCache(final SearchConfigurationProperties configurationProperties) {
super(configurationProperties.getRedisSearchHost(),
Integer.parseInt(configurationProperties.getRedisSearchPort()),
configurationProperties.getElasticCacheExpiration() * 60,
String.class,
ClusterSettings.class);
public class ElasticCredentialsCache implements IElasticCredentialsCache<String, ClusterSettings>, AutoCloseable {
private IKmsClient kmsClient;
private RedisCache<String, String> cache;
@Inject
public ElasticCredentialsCache(final SearchConfigurationProperties configurationProperties, final IKmsClient kmsClient) {
this.cache = new RedisCache<>(configurationProperties.getRedisSearchHost(),
Integer.parseInt(configurationProperties.getRedisSearchPort()),
configurationProperties.getElasticCacheExpiration() * 60,
String.class,
String.class);
this.kmsClient = kmsClient;
}
@Override
public void put(String key, ClusterSettings value) {
try {
String jsonSettings = new Gson().toJson(value);
String encryptString = kmsClient.encryptString(jsonSettings);
this.cache.put(key, encryptString);
} catch (IOException e) {
throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal server error", "Unable to encrypt settings before putting in cache", e);
}
}
@Override
public ClusterSettings get(String key) {
try {
String encryptedSettings = this.cache.get(key);
if (Objects.isNull(encryptedSettings) || encryptedSettings.isEmpty()) {
return null;
}
String jsonSettings = this.kmsClient.decryptString(encryptedSettings);
return new Gson().fromJson(jsonSettings, ClusterSettings.class);
} catch (IOException e) {
throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal server error", "Unable to decrypt settings from cache", e);
}
}
@Override
public void delete(String s) {
this.cache.delete(s);
}
@Override
public void clearAll() {
this.cache.clearAll();
}
@Override
public void close() throws Exception {
this.cache.close();
}
}
// Copyright 2017-2019, Schlumberger
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.opengroup.osdu.search.provider.gcp.provider.kms;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.cloudkms.v1.CloudKMS;
import com.google.api.services.cloudkms.v1.CloudKMSScopes;
import com.google.api.services.cloudkms.v1.model.DecryptRequest;
import com.google.api.services.cloudkms.v1.model.DecryptResponse;
import com.google.api.services.cloudkms.v1.model.EncryptRequest;
import com.google.api.services.cloudkms.v1.model.EncryptResponse;
import javax.inject.Inject;
import org.opengroup.osdu.core.common.search.Preconditions;
import org.opengroup.osdu.search.config.SearchConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component("searchKmsClient")
@RequestScope
public class KmsClient {
@Inject
private SearchConfigurationProperties properties;
private static final String KEY_NAME = "projects/%s/locations/global/keyRings/%s/cryptoKeys/%s";
/**
* Encrypts the given plaintext using the specified crypto key.
* Google KMS automatically uses the new primary key version to encrypt data, so this could be directly used for key rotation
*/
public String encryptString(String textToBeEncrypted) throws IOException {
Preconditions.checkNotNullOrEmpty(textToBeEncrypted, "textToBeEncrypted cannot be null");
byte[] plaintext = textToBeEncrypted.getBytes(StandardCharsets.UTF_8);
String resourceName = String.format(KEY_NAME, properties.getGoogleCloudProject(), properties.getKeyRing(), properties.getKmsKey());
CloudKMS kms = createAuthorizedClient();
EncryptRequest request = new EncryptRequest().encodePlaintext(plaintext);
EncryptResponse response = kms.projects().locations().keyRings().cryptoKeys()
.encrypt(resourceName, request)
.execute();
return response.getCiphertext();
}
/**
* Decrypts the provided ciphertext with the specified crypto key.
* Google KMS automatically uses the correct key version to decrypt data, as long as the key version is not disabled
*/
public String decryptString(String textToBeDecrypted) throws IOException {
Preconditions.checkNotNullOrEmpty(textToBeDecrypted, "textToBeDecrypted cannot be null");
CloudKMS kms = createAuthorizedClient();
String cryptoKeyName = String.format(KEY_NAME, properties.getGoogleCloudProject(), properties.getKeyRing(), properties.getKmsKey());
DecryptRequest request = new DecryptRequest().setCiphertext(textToBeDecrypted);
DecryptResponse response = kms.projects().locations().keyRings().cryptoKeys()
.decrypt(cryptoKeyName, request)
.execute();
return new String(response.decodePlaintext(), StandardCharsets.UTF_8).trim();
}
/**
* Creates an authorized CloudKMS client service using Application Default Credentials.
*
* @return an authorized CloudKMS client
* @throws IOException if there's an error getting the default credentials.
*/
private CloudKMS createAuthorizedClient() throws IOException {
HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = GoogleCredential.getApplicationDefault();
if (credential.createScopedRequired()) {
credential = credential.createScoped(CloudKMSScopes.all());
}
return new CloudKMS.Builder(transport, jsonFactory, credential)
.setApplicationName("CloudKMS snippets")
.build();
}
}
\ No newline at end of file
......@@ -19,19 +19,18 @@ import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.Key;
import javax.inject.Inject;
import org.apache.http.HttpStatus;
import org.opengroup.osdu.core.common.model.tenant.TenantInfo;
import org.opengroup.osdu.core.common.model.search.ClusterSettings;
import org.opengroup.osdu.core.common.model.http.AppException;
import org.opengroup.osdu.core.common.model.search.ClusterSettings;
import org.opengroup.osdu.core.common.model.tenant.TenantInfo;
import org.opengroup.osdu.core.common.provider.interfaces.IElasticRepository;
import org.opengroup.osdu.core.common.provider.interfaces.IKmsClient;
import org.opengroup.osdu.core.common.search.Preconditions;
import org.opengroup.osdu.search.config.SearchConfigurationProperties;
import org.opengroup.osdu.search.provider.gcp.provider.kms.KmsClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
@Component
public class ElasticRepositoryDatastore implements IElasticRepository {
......@@ -40,7 +39,7 @@ public class ElasticRepositoryDatastore implements IElasticRepository {
static final String XPACK_RESTCLIENT_CONFIGURATION = "configuration";
@Inject
private KmsClient kmsClient;
private IKmsClient kmsClient;
@Inject
private DatastoreFactory datastoreFactory;
......@@ -56,7 +55,8 @@ public class ElasticRepositoryDatastore implements IElasticRepository {
Entity datastoreEntity = googleDatastore.get(key);
if (datastoreEntity == null) {
throw new AppException(HttpStatus.SC_NOT_FOUND, "Cluster setting not found", "The requested cluster setting was not found in datastore.", String.format("Cluster setting with key: '%s' does not exist in datastore.", key.getName()));
throw new AppException(HttpStatus.SC_NOT_FOUND, "Cluster setting not found", "The requested cluster setting was not found in datastore.",
String.format("Cluster setting with key: '%s' does not exist in datastore.", key.getName()));
}
String encryptedHost = null;
......@@ -81,9 +81,11 @@ public class ElasticRepositoryDatastore implements IElasticRepository {
return new ClusterSettings(host, port, usernameAndPassword);
} catch (GoogleJsonResponseException e) {
String debuggingInfo = String.format("Host: %s | port: %s | configuration: %s", encryptedHost, encryptedPort, encryptedConfiguration);
throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cluster setting decryption error", "An error has occurred decrypting cluster settings.", debuggingInfo, e);
throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cluster setting decryption error",
"An error has occurred decrypting cluster settings.", debuggingInfo, e);
} catch (Exception e) {
throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cluster setting fetch error", "An error has occurred fetching cluster settings from the datastore.", e);
throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cluster setting fetch error",
"An error has occurred fetching cluster settings from the datastore.", e);
}
}
}
\ No newline at end of file
......@@ -43,4 +43,6 @@ service.policy.id=${POLICY_ID}
POLICY_API=http://localhost:8081/police/api/policy/v1
PARTITION_API=${partition_service_endpoint}
partition_service_endpoint=https://os-partition-jvmvia5dea-uc.a.run.app/api/partition/v1
service.policy.endpoint=${POLICY_API}
\ No newline at end of file
service.policy.endpoint=${POLICY_API}
KEY_RING=${key-ring}
KMS_KEY=${kms-key}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment