From f0dca7914e80eed69d97e0b22244eaccded59d22 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?brother-=E6=88=8E?= <767817253@qq.com>
Date: Thu, 9 Nov 2023 13:43:03 +0800
Subject: [PATCH] support kmsv3 (#8)
* support kmsv3
* tiny fix
* fix #6
* tiny fix
* version 1.0.3
* add log keyId
* add key deletion protection
* fix
* add no-args constructor
* fix up
* fix up mvn dependencies bug
* add exception infomation
* add exception infomation
* throw nacos exception
* throw nacos exception
* ca is not necessary
* change log level
* ca is not necessary
---
.gitignore | 2 +-
pom.xml | 35 +-
.../client/aliyun/AliyunConfigFilter.java | 410 ++++++++++++++++--
.../nacos/client/aliyun/AliyunConst.java | 47 ++
.../nacos/client/aliyun/AsyncProcessor.java | 71 +++
.../client/aliyun/AliyunConfigFilterTest.java | 97 +++++
src/test/resources/aliyun-kms.properties | 19 +
src/test/resources/ca.pem | 2 +
src/test/resources/client_key.json | 4 +
9 files changed, 642 insertions(+), 45 deletions(-)
create mode 100644 src/main/java/com/alibaba/nacos/client/aliyun/AsyncProcessor.java
create mode 100644 src/test/java/com/alibaba/nacos/client/aliyun/AliyunConfigFilterTest.java
create mode 100644 src/test/resources/aliyun-kms.properties
create mode 100644 src/test/resources/ca.pem
create mode 100644 src/test/resources/client_key.json
diff --git a/.gitignore b/.gitignore
index 70afd1b..0b144da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,4 +27,4 @@ hs_err_pid*
.project
.settings/
*.iml
-target/
+target/
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index cc2c12f..598944e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
2018
com.alibaba.nacos
nacos-client-mse-extension
- 1.0.2-beta
+ 1.0.3
jar
@@ -51,26 +51,39 @@
UTF-8
- 1.4.2-SNAPSHOT
+ 2.2.4
com.aliyun
aliyun-java-sdk-core
- 4.5.20
+ 4.5.17
com.aliyun
aliyun-java-sdk-kms
- 2.14.0
+ 2.16.3
+
+
+
+ com.aliyun.kms
+ kms-transfer-client
+
+
+ com.aliyun
+ aliyun-java-sdk-kms
+
+
+ 0.1.0
com.alibaba.nacos
nacos-api
${nacos.version}
+ true
@@ -79,6 +92,20 @@
1.15
+
+
+ org.junit.jupiter
+ junit-jupiter
+ RELEASE
+ test
+
+
+ com.alibaba.nacos
+ nacos-client
+ ${nacos.version}
+ test
+
+
diff --git a/src/main/java/com/alibaba/nacos/client/aliyun/AliyunConfigFilter.java b/src/main/java/com/alibaba/nacos/client/aliyun/AliyunConfigFilter.java
index c94639a..3e391ad 100644
--- a/src/main/java/com/alibaba/nacos/client/aliyun/AliyunConfigFilter.java
+++ b/src/main/java/com/alibaba/nacos/client/aliyun/AliyunConfigFilter.java
@@ -7,21 +7,37 @@
import com.alibaba.nacos.api.config.filter.IConfigResponse;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.utils.StringUtils;
-import com.aliyuncs.DefaultAcsClient;
+import com.aliyun.dkms.gcs.openapi.models.Config;
+import com.aliyun.kms.KmsTransferAcsClient;
+import com.aliyuncs.IAcsClient;
import com.aliyuncs.auth.AlibabaCloudCredentialsProvider;
import com.aliyuncs.auth.InstanceProfileCredentialsProvider;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.FormatType;
+import com.aliyuncs.http.HttpClientConfig;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
-import com.aliyuncs.kms.model.v20160120.DecryptRequest;
-import com.aliyuncs.kms.model.v20160120.EncryptRequest;
+import com.aliyuncs.kms.model.v20160120.DescribeKeyRequest;
+import com.aliyuncs.kms.model.v20160120.DescribeKeyResponse;
import com.aliyuncs.kms.model.v20160120.GenerateDataKeyRequest;
import com.aliyuncs.kms.model.v20160120.GenerateDataKeyResponse;
+import com.aliyuncs.kms.model.v20160120.DecryptRequest;
+import com.aliyuncs.kms.model.v20160120.EncryptRequest;
+import com.aliyuncs.kms.model.v20160120.SetDeletionProtectionRequest;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
import java.util.Properties;
+import java.util.Set;
/**
* the IConfigFilter of Aliyun.
@@ -30,6 +46,8 @@
*/
public class AliyunConfigFilter extends AbstractConfigFilter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AliyunConfigFilter.class);
+
private static final String GROUP = "group";
private static final String DATA_ID = "dataId";
@@ -46,6 +64,8 @@ public class AliyunConfigFilter extends AbstractConfigFilter {
private static final String CIPHER_PREFIX = "cipher-";
+ private static AliyunConst.KmsVersion kmsVersion;
+
public static final String CIPHER_KMS_AES_128_PREFIX = "cipher-kms-aes-128-";
public static final String CIPHER_KMS_AES_256_PREFIX = "cipher-kms-aes-256-";
@@ -54,39 +74,226 @@ public class AliyunConfigFilter extends AbstractConfigFilter {
public static final String KMS_KEY_SPEC_AES_256 = "AES_256";
- private DefaultAcsClient kmsClient;
+ private IAcsClient kmsClient;
private String keyId;
+ private final Set addedKeys = new HashSet();
+
+ private AsyncProcessor asyncProcessor;
+
+ private Exception localInitException;
+
@Override
public void init(Properties properties) {
- keyId = properties.getProperty(KEY_ID);
- String regionId = properties.getProperty(REGION_ID);
- String ramRoleName = properties.getProperty(PropertyKeyConst.RAM_ROLE_NAME);
- String accessKey = properties.getProperty(PropertyKeyConst.ACCESS_KEY);
- String secretKey = properties.getProperty(PropertyKeyConst.SECRET_KEY);
- String kmsEndpoint = properties.getProperty(AliyunConst.KMS_ENDPOINT);
-
- if(StringUtils.isEmpty(regionId)){
- regionId = System.getProperty(KMS_REGION_ID, System.getenv(KMS_REGION_ID));
+ LOGGER.info("init ConfigFilter: {}, for more information, please check: {}",
+ this.getFilterName(), AliyunConst.MSE_ENCRYPTED_CONFIG_USAGE_DOCUMENT_URL);
+ // get kms version, default using kms v1
+ String kv = properties.getProperty(AliyunConst.KMS_VERSION_KEY,
+ System.getProperty(AliyunConst.KMS_VERSION_KEY, System.getenv(AliyunConst.KMS_VERSION_KEY)));
+ if (StringUtils.isBlank(kv)) {
+ LOGGER.warn("kms version is not set, using kms v1 version.");
+ kmsVersion = AliyunConst.KmsVersion.Kmsv1;
+ } else {
+ kmsVersion = AliyunConst.KmsVersion.fromValue(kv);
+ if (kmsVersion == AliyunConst.KmsVersion.UNKNOWN_VERSION) {
+ LOGGER.warn("kms version is not supported, using kms v1 version.");
+ kmsVersion = AliyunConst.KmsVersion.Kmsv1;
+ } else {
+ LOGGER.info("using kms version {}.", kmsVersion.getValue());
+ }
+ }
+
+ //keyId corresponding to the id/alias of KMS's secret key, using mseServiceKeyId by default
+ keyId = properties.getProperty(KEY_ID, System.getProperty(KEY_ID, System.getenv(KEY_ID)));
+ if (StringUtils.isBlank(keyId)) {
+ if (kmsVersion == AliyunConst.KmsVersion.Kmsv1) {
+ keyId = AliyunConst.KMS_DEFAULT_KEY_ID_VALUE;
+ LOGGER.info("using default keyId {}.", keyId);
+ } else {
+ String errorMsg = "keyId is not set up yet, unable to encrypt the configuration.";
+ localInitException = new RuntimeException(errorMsg);
+ LOGGER.error(AliyunConst.formatHelpMessage(errorMsg), localInitException);
+ return;
+ }
+ } else {
+ LOGGER.info("using keyId {}.", keyId);
+ }
+
+ try {
+ if (kmsVersion == AliyunConst.KmsVersion.Kmsv1) {
+ kmsClient = createKmsV1Client(properties);
+ } else if (kmsVersion == AliyunConst.KmsVersion.Kmsv3) {
+ kmsClient = createKmsV3Client(properties);
+ }
+ } catch (ClientException e) {
+ LOGGER.error(AliyunConst.formatHelpMessage("kms init failed."), e);
+ localInitException = e;
+ } catch (Exception e) {
+ LOGGER.error(AliyunConst.formatHelpMessage("create kms client failed."), e);
+ localInitException = e;
}
-
- if (System.getProperties().containsKey(AliyunConst.KMS_ENDPOINT)) {
- kmsEndpoint = System.getProperty(AliyunConst.KMS_ENDPOINT);
+ try {
+ asyncProcessor = new AsyncProcessor();
+ } catch (Exception e) {
+ LOGGER.error("init async processor failed.", e);
+ }
+ }
+
+ /**
+ * init kms v1 client, accessing the KMS service through a shared gateway.
+ *
+ * @date 2023/9/19
+ * @description
+ * @param properties
+ * @return com.aliyuncs.IAcsClient
+ * @throws
+ */
+ private IAcsClient createKmsV1Client(Properties properties) {
+ String regionId = properties.getProperty(REGION_ID, System.getProperty(REGION_ID, System.getenv(REGION_ID)));
+ String kmsRegionId = properties.getProperty(KMS_REGION_ID, System.getProperty(KMS_REGION_ID, System.getenv(KMS_REGION_ID)));
+ if (StringUtils.isBlank(regionId)) {
+ regionId = kmsRegionId;
+ }
+ LOGGER.info("using regionId {}.", regionId);
+ if (StringUtils.isBlank(kmsRegionId)) {
+ kmsRegionId = regionId;
}
+ LOGGER.info("using kms regionId {}.", kmsRegionId);
+
+ if (StringUtils.isBlank(kmsRegionId) && StringUtils.isBlank(regionId)) {
+ String errorMsg = "region is not set up yet";
+ LOGGER.error(AliyunConst.formatHelpMessage(errorMsg));
+ localInitException = new RuntimeException(errorMsg);
+ return null;
+ }
+
+ String ramRoleName= properties.getProperty(PropertyKeyConst.RAM_ROLE_NAME,
+ System.getProperty(PropertyKeyConst.RAM_ROLE_NAME, System.getenv(PropertyKeyConst.RAM_ROLE_NAME)));
+ LOGGER.info("using ramRoleName {}.", ramRoleName);
+
+ String accessKey = properties.getProperty(PropertyKeyConst.ACCESS_KEY,
+ System.getProperty(PropertyKeyConst.ACCESS_KEY, System.getenv(PropertyKeyConst.ACCESS_KEY)));
+ LOGGER.info("using accessKey {}.", accessKey);
+
+ String secretKey = properties.getProperty(PropertyKeyConst.SECRET_KEY,
+ System.getProperty(PropertyKeyConst.SECRET_KEY, System.getenv(PropertyKeyConst.SECRET_KEY)));
+
+ String kmsEndpoint = properties.getProperty(AliyunConst.KMS_ENDPOINT,
+ System.getProperty(AliyunConst.KMS_ENDPOINT, System.getenv(AliyunConst.KMS_ENDPOINT)));
if (!StringUtils.isBlank(kmsEndpoint)) {
DefaultProfile.addEndpoint(regionId, "kms", kmsEndpoint);
}
+ LOGGER.info("using kmsEndpoint {}.", kmsEndpoint);
+
IClientProfile profile = null;
+ IAcsClient kmsClient = null;
if (!StringUtils.isBlank(ramRoleName)) {
profile = DefaultProfile.getProfile(regionId);
AlibabaCloudCredentialsProvider alibabaCloudCredentialsProvider = new InstanceProfileCredentialsProvider(
ramRoleName);
- kmsClient = new DefaultAcsClient(profile, alibabaCloudCredentialsProvider);
+ kmsClient = new KmsTransferAcsClient(profile, alibabaCloudCredentialsProvider);
+ LOGGER.info("successfully create kms client by using RAM role.");
} else {
profile = DefaultProfile.getProfile(regionId, accessKey, secretKey);
- kmsClient = new DefaultAcsClient(profile);
+ kmsClient = new KmsTransferAcsClient(profile);
+ LOGGER.info("successfully create kms client by using ak/sk.");
}
+ return kmsClient;
+ }
+
+ /**
+ * init kms v3 client, accessing the KMS service through the KMS instance gateway.
+ *
+ * @date 2023/9/19
+ * @description
+ * @param properties
+ * @return
+ * @throws
+ */
+ private IAcsClient createKmsV3Client(Properties properties) throws ClientException {
+ Config config = new Config();
+ config.setProtocol("https");
+ IClientProfile profile = null;
+
+ String kmsClientKeyContent = properties.getProperty(AliyunConst.KMS_CLIENT_KEY_CONTENT_KEY,
+ System.getProperty(AliyunConst.KMS_CLIENT_KEY_CONTENT_KEY, System.getenv(AliyunConst.KMS_CLIENT_KEY_CONTENT_KEY)));
+ if (!StringUtils.isBlank(kmsClientKeyContent)) {
+ LOGGER.info("using {}: {}.", AliyunConst.KMS_CLIENT_KEY_CONTENT_KEY, kmsClientKeyContent);
+ config.setClientKeyContent(kmsClientKeyContent);
+ } else {
+ String errorMsg = null;
+ LOGGER.info("{} is empty, will read from file.", AliyunConst.KMS_CLIENT_KEY_CONTENT_KEY);
+ String kmsClientKeyFilePath = properties.getProperty(AliyunConst.KMS_CLIENT_KEY_FILE_PATH_KEY,
+ System.getProperty(AliyunConst.KMS_CLIENT_KEY_FILE_PATH_KEY, System.getenv(AliyunConst.KMS_CLIENT_KEY_FILE_PATH_KEY)));
+ if (!StringUtils.isBlank(kmsClientKeyFilePath)) {
+ String s = readFileToString(kmsClientKeyFilePath);
+ if (!StringUtils.isBlank(s)) {
+ LOGGER.info("using kmsClientKeyFilePath: {}.", kmsClientKeyFilePath);
+ config.setClientKeyFile(kmsClientKeyFilePath);
+ } else {
+ errorMsg = "both config from kmsClientKeyContent and kmsClientKeyFilePath is empty";
+ }
+ } else {
+ errorMsg = "kmsClientKeyFilePath is empty";
+ }
+ if (!StringUtils.isBlank(errorMsg)) {
+ localInitException = new RuntimeException(errorMsg);
+ return null;
+ }
+ }
+
+ String kmsEndpoint = properties.getProperty(AliyunConst.KMS_ENDPOINT,
+ System.getProperty(AliyunConst.KMS_ENDPOINT, System.getenv(AliyunConst.KMS_ENDPOINT)));
+ if (StringUtils.isBlank(kmsEndpoint)) {
+ String errorMsg = String.format("%s is empty", AliyunConst.KMS_ENDPOINT);
+ localInitException = new RuntimeException(errorMsg);
+ return null;
+ } else {
+ LOGGER.info("using kmsEndpoint: {}.", kmsEndpoint);
+ config.setEndpoint(kmsEndpoint);
+ }
+
+ String kmsPassword = properties.getProperty(AliyunConst.KMS_PASSWORD_KEY,
+ System.getProperty(AliyunConst.KMS_PASSWORD_KEY, System.getenv(AliyunConst.KMS_PASSWORD_KEY)));
+ if (StringUtils.isBlank(kmsPassword)) {
+ String errorMsg = String.format("%s is empty", AliyunConst.KMS_PASSWORD_KEY);
+ localInitException = new RuntimeException(errorMsg);
+ return null;
+ } else {
+ LOGGER.info("using kmsPassword prefix: {}.", kmsPassword.substring(kmsPassword.length() / 8));
+ config.setPassword(kmsPassword);
+ }
+
+ String kmsCaFileContent = properties.getProperty(AliyunConst.KMS_CA_FILE_CONTENT,
+ System.getProperty(AliyunConst.KMS_CA_FILE_CONTENT, System.getenv(AliyunConst.KMS_CA_FILE_CONTENT)));
+ if (!StringUtils.isBlank(kmsCaFileContent)) {
+ LOGGER.info("using {}: {}.", AliyunConst.KMS_CA_FILE_CONTENT, kmsCaFileContent);
+ config.setCa(kmsCaFileContent);
+ } else {
+ String errorMsg = null;
+ LOGGER.info("{} is empty, will read from file.", AliyunConst.KMS_CA_FILE_CONTENT);
+ String kmsCaFilePath = properties.getProperty(AliyunConst.KMS_CA_FILE_PATH_KEY,
+ System.getProperty(AliyunConst.KMS_CA_FILE_PATH_KEY, System.getenv(AliyunConst.KMS_CA_FILE_PATH_KEY)));
+ if (!StringUtils.isBlank(kmsCaFilePath)) {
+ config.setCaFilePath(kmsCaFilePath);
+ } else {
+ errorMsg = "kmsCaFilePath is empty";
+ config.setCaFilePath(null);
+ }
+ if (!StringUtils.isBlank(errorMsg)) {
+ LOGGER.warn(AliyunConst.formatHelpMessage(errorMsg));
+ profile = DefaultProfile.getProfile(config.getRegionId(), "ak", "sk", "sts");
+ HttpClientConfig httpClientConfig = HttpClientConfig.getDefault();
+ httpClientConfig.setIgnoreSSLCerts(true);
+ profile.setHttpClientConfig(httpClientConfig);
+ }
+ }
+
+ if (profile == null) {
+ return new KmsTransferAcsClient(config);
+ }
+ return new KmsTransferAcsClient(profile, config);
}
@Override
@@ -99,7 +306,7 @@ public void doFilter(IConfigRequest request, IConfigResponse response, IConfigFi
dataId = (String) request.getParameter(DATA_ID);
group = (String) request.getParameter(GROUP);
if (dataId.startsWith(CIPHER_PREFIX)) {
- if (request.getParameter(CONTENT) != null) {
+ if (!StringUtils.isBlank((String)request.getParameter(CONTENT))) {
request.putParameter(CONTENT, encrypt(keyId, request));
}
}
@@ -107,39 +314,63 @@ public void doFilter(IConfigRequest request, IConfigResponse response, IConfigFi
filterChain.doFilter(request, response);
}
if (response != null) {
- dataId = (String) response.getParameter("dataId");
- group = (String) response.getParameter("group");
+ dataId = (String) response.getParameter(DATA_ID);
+ group = (String) response.getParameter(GROUP);
if (dataId.startsWith(CIPHER_PREFIX)) {
- response.putParameter("content", decrypt(response));
+ if (!StringUtils.isBlank((String)response.getParameter(CONTENT))) {
+ response.putParameter(CONTENT, decrypt(response));
+ }
}
}
} catch (ClientException e) {
- e.printStackTrace();
- String message = String.format("KMS error, dataId: %s, groupId: %s", dataId, group);
- throw new NacosException(NacosException.HTTP_CLIENT_ERROR_CODE, message, e);
+ String message = String.format("KMS message:[%s], error message:[%s], dataId: %s, groupId: %s", e.getMessage(), e.getErrMsg(), dataId, group);
+ throw new NacosException(NacosException.HTTP_CLIENT_ERROR_CODE, AliyunConst.formatHelpMessage(message), e);
} catch (Exception e) {
- NacosException ee = new NacosException();
- ee.setCauseThrowable(e);
+ StringBuilder stringBuilder = new StringBuilder();
+ for (StackTraceElement ste : e.getStackTrace()) {
+ stringBuilder.append(ste.toString()).append("\n");
+ }
+ NacosException ee = new NacosException(NacosException.INVALID_PARAM, AliyunConst.formatHelpMessage(stringBuilder.toString()), e);
throw ee;
}
}
private String decrypt(IConfigResponse response) throws Exception {
- String dataId = (String) response.getParameter("dataId");
- String content = (String) response.getParameter("content");
+ String dataId = (String) response.getParameter(DATA_ID);
+ String content = (String) response.getParameter(CONTENT);
+ String result;
if (dataId.startsWith(CIPHER_KMS_AES_128_PREFIX) || dataId.startsWith(CIPHER_KMS_AES_256_PREFIX)) {
- String encryptedDataKey = (String) response.getParameter("encryptedDataKey");
+ String encryptedDataKey = (String) response.getParameter(ENCRYPTED_DATA_KEY);
if (!StringUtils.isBlank(encryptedDataKey)) {
String dataKey = decrypt(encryptedDataKey);
- return AesUtils.decrypt((String) response.getParameter("content"), dataKey, "UTF-8");
+ if (StringUtils.isBlank(dataKey)) {
+ throw new RuntimeException("failed to decrypt encryptedDataKey with empty value");
+ }
+ result = AesUtils.decrypt((String) response.getParameter(CONTENT), dataKey, "UTF-8");
+ if (StringUtils.isBlank(result)) {
+ throw new RuntimeException("failed to decrypt content with empty value");
+ }
+ } else {
+ throw new RuntimeException("encrypted failed encryptedDataKey is empty");
}
- return "";
} else {
- return decrypt(content);
+ result = decrypt(content);
+ if (StringUtils.isBlank(result)) {
+ throw new RuntimeException("failed to decrypt content with empty value");
+ }
}
+ return result;
}
- private String decrypt(String content) throws ClientException {
+ private String decrypt(String content) throws Exception {
+ if (kmsClient == null) {
+ if (localInitException != null) {
+ throw localInitException;
+ } else {
+ throw new RuntimeException("kms client isn't initialized. " +
+ "For more information, please check: " + AliyunConst.MSE_ENCRYPTED_CONFIG_USAGE_DOCUMENT_URL);
+ }
+ }
final DecryptRequest decReq = new DecryptRequest();
decReq.setSysProtocol(ProtocolType.HTTPS);
decReq.setSysMethod(MethodType.POST);
@@ -149,6 +380,20 @@ private String decrypt(String content) throws ClientException {
}
private String encrypt(String keyId, IConfigRequest configRequest) throws Exception {
+ if (kmsClient == null) {
+ if (localInitException != null) {
+ throw localInitException;
+ } else {
+ throw new RuntimeException("kms client isn't initialized. " +
+ "For more information, please check: " + AliyunConst.MSE_ENCRYPTED_CONFIG_USAGE_DOCUMENT_URL);
+ }
+ }
+ if (StringUtils.isBlank(keyId)) {
+ throw new RuntimeException("keyId is not set up yet, unable to encrypt the configuration. " +
+ "For more information, please check: " + AliyunConst.MSE_ENCRYPTED_CONFIG_USAGE_DOCUMENT_URL);
+ }
+ String result;
+ protectKeyId(keyId);
String dataId = (String) configRequest.getParameter(DATA_ID);
if (dataId.startsWith(CIPHER_KMS_AES_128_PREFIX) || dataId.startsWith(CIPHER_KMS_AES_256_PREFIX)) {
@@ -159,15 +404,24 @@ private String encrypt(String keyId, IConfigRequest configRequest) throws Except
keySpec = KMS_KEY_SPEC_AES_256;
}
GenerateDataKeyResponse generateDataKeyResponse = generateDataKey(keyId, keySpec);
- configRequest.putParameter(ENCRYPTED_DATA_KEY, generateDataKeyResponse.getCiphertextBlob());
String dataKey = generateDataKeyResponse.getPlaintext();
- return AesUtils.encrypt((String) configRequest.getParameter(CONTENT), dataKey, "UTF-8");
+ if (StringUtils.isBlank(dataKey.trim())) {
+ throw new RuntimeException("get generateDataKey failed with empty content. " +
+ "For more information, please check: " + AliyunConst.MSE_ENCRYPTED_CONFIG_USAGE_DOCUMENT_URL);
+ }
+ configRequest.putParameter(ENCRYPTED_DATA_KEY, generateDataKeyResponse.getCiphertextBlob());
+ result = AesUtils.encrypt((String) configRequest.getParameter(CONTENT), dataKey, "UTF-8");
+ } else {
+ result = encrypt(keyId, (String) configRequest.getParameter(CONTENT));
}
- return encrypt(keyId, (String) configRequest.getParameter(CONTENT));
+ if (StringUtils.isBlank(result)) {
+ throw new RuntimeException("encrypt failed with empty result.");
+ }
+ return result;
}
- private String encrypt(String keyId, String plainText) throws Exception {
+ public String encrypt(String keyId, String plainText) throws Exception {
final EncryptRequest encReq = new EncryptRequest();
encReq.setProtocol(ProtocolType.HTTPS);
encReq.setAcceptFormat(FormatType.JSON);
@@ -177,7 +431,7 @@ private String encrypt(String keyId, String plainText) throws Exception {
return kmsClient.getAcsResponse(encReq).getCiphertextBlob();
}
- private GenerateDataKeyResponse generateDataKey(String keyId, String keySpec) throws ClientException {
+ public GenerateDataKeyResponse generateDataKey(String keyId, String keySpec) throws ClientException {
GenerateDataKeyRequest generateDataKeyRequest = new GenerateDataKeyRequest();
generateDataKeyRequest.setAcceptFormat(FormatType.JSON);
@@ -187,9 +441,85 @@ private GenerateDataKeyResponse generateDataKey(String keyId, String keySpec) th
return kmsClient.getAcsResponse(generateDataKeyRequest);
}
+ private void protectKeyId(String keyId) {
+ if (!addedKeys.contains(keyId)) {
+ synchronized (addedKeys) {
+ addedKeys.add(keyId);
+ asyncProcessor.addTack(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (kmsClient == null) {
+ LOGGER.error("kms client hasn't initiated.");
+ return;
+ }
+ DescribeKeyRequest describeKeyRequest = new DescribeKeyRequest();
+ describeKeyRequest.setKeyId(keyId);
+ try {
+ DescribeKeyResponse describeKeyResponse = kmsClient.getAcsResponse(describeKeyRequest);
+ if (describeKeyResponse.getKeyMetadata()!= null) {
+ String arn = describeKeyResponse.getKeyMetadata().getArn();
+ LOGGER.info("set deletion protection for keyId[{}], arn[{}]", keyId, arn);
+
+ SetDeletionProtectionRequest setDeletionProtectionRequest = new SetDeletionProtectionRequest();
+ setDeletionProtectionRequest.setProtectedResourceArn(arn);
+ setDeletionProtectionRequest.setEnableDeletionProtection(true);
+ setDeletionProtectionRequest.setDeletionProtectionDescription("key is used by nacos-client");
+ try {
+ kmsClient.getAcsResponse(setDeletionProtectionRequest);
+ } catch (ClientException e) {
+ LOGGER.error("set deletion protect failed, keyId: {}.", keyId);
+ throw e;
+ }
+ } else {
+ addedKeys.remove(keyId);
+ LOGGER.warn("keyId meta is null, cannot set key protection");
+ }
+ } catch (ClientException e) {
+ LOGGER.error("describe key failed, keyId: {}.", keyId);
+ throw e;
+ }
+ } catch (Exception e) {
+ addedKeys.remove(keyId);
+ LOGGER.error("execute async task failed", e);
+ }
+
+ }
+ });
+ }
+ }
+ }
+
+ private static String readFileToString(String filePath) {
+ File file = getFileByPath(filePath);
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ try {
+ Path path = Paths.get(file.getAbsolutePath());
+ byte[] fileContent = Files.readAllBytes(path);
+ return new String(fileContent, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static File getFileByPath(String filePath) {
+ File file = new File(filePath);
+ if (!file.exists()) {
+ String path = AliyunConfigFilter.class.getClassLoader().getResource("").getPath();
+ if (!(file = new File(path + filePath)).exists()) {
+ path = Paths.get(filePath).toAbsolutePath().toString();
+ if (!(file = new File(path)).exists()) {
+ return null;
+ }
+ }
+ }
+ return file;
+ }
@Override
public int getOrder() {
- return 0;
+ return 1;
}
@Override
diff --git a/src/main/java/com/alibaba/nacos/client/aliyun/AliyunConst.java b/src/main/java/com/alibaba/nacos/client/aliyun/AliyunConst.java
index 6379031..694d8ca 100644
--- a/src/main/java/com/alibaba/nacos/client/aliyun/AliyunConst.java
+++ b/src/main/java/com/alibaba/nacos/client/aliyun/AliyunConst.java
@@ -7,4 +7,51 @@
*/
public class AliyunConst {
public static final String KMS_ENDPOINT = "kmsEndpoint";
+
+ public static final String KMS_VERSION_KEY = "kmsVersion";
+
+ public static final String KMS_DEFAULT_KEY_ID_VALUE = "alias/acs/mse";
+
+ public static final String KMS_CLIENT_KEY_FILE_PATH_KEY = "kmsClientKeyFilePath";
+
+ public static final String KMS_CLIENT_KEY_CONTENT_KEY = "kmsClientKeyContent";
+
+ public static final String KMS_PASSWORD_KEY = "kmsPasswordKey";
+
+ public static final String KMS_CA_FILE_PATH_KEY = "kmsCaFilePath";
+
+ public static final String KMS_CA_FILE_CONTENT = "kmsCaFileContent";
+
+ public static final String MSE_ENCRYPTED_CONFIG_USAGE_DOCUMENT_URL = "https://help.aliyun.com/zh/mse/user-guide/create-and-use-encrypted-configurations?spm=a2c4g.11186623.0.0.55587becdOW3jf";
+
+ public static String formatHelpMessage(String errorMessage) {
+ return String.format("%s, for more information, please check: %s",
+ errorMessage, AliyunConst.MSE_ENCRYPTED_CONFIG_USAGE_DOCUMENT_URL);
+ }
+
+ public enum KmsVersion {
+ Kmsv1("v1.0"),
+ Kmsv3("v3.0"),
+ UNKNOWN_VERSION("unknown version");
+
+ private String value;
+
+ KmsVersion(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static KmsVersion fromValue(String value) {
+ for (KmsVersion version : values()) {
+ if (version.getValue().equals(value)) {
+ return version;
+ }
+ }
+ return UNKNOWN_VERSION;
+ }
+
+ }
}
diff --git a/src/main/java/com/alibaba/nacos/client/aliyun/AsyncProcessor.java b/src/main/java/com/alibaba/nacos/client/aliyun/AsyncProcessor.java
new file mode 100644
index 0000000..d428b60
--- /dev/null
+++ b/src/main/java/com/alibaba/nacos/client/aliyun/AsyncProcessor.java
@@ -0,0 +1,71 @@
+package com.alibaba.nacos.client.aliyun;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class AsyncProcessor {
+
+ private static final int QUEUE_INITIAL_CAPACITY = 8;
+
+ private static final String DEFAULT_PROCESSOR_NAME = "asyncProcessor";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AsyncProcessor.class);
+
+ private final BlockingQueue queue;
+
+ private final AtomicBoolean closed;
+
+ private final String name;
+
+ public AsyncProcessor() {
+ this(QUEUE_INITIAL_CAPACITY, DEFAULT_PROCESSOR_NAME);
+ }
+
+ public AsyncProcessor(int queueSize, String name) {
+ this.queue = new ArrayBlockingQueue(queueSize);
+ this.closed = new AtomicBoolean(false);
+ this.name = name;
+ (new InnerWorker(name)).start();
+ }
+
+ public void addTack(Runnable task) {
+ try {
+ queue.put(task);
+ } catch (InterruptedException e) {
+ LOGGER.error(e.toString(), e);
+ }
+ }
+
+ public void shutdown() {
+ queue.clear();
+ closed.compareAndSet(false, true);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ private class InnerWorker extends Thread {
+ InnerWorker(String name) {
+ super(name);
+ }
+ @Override
+ public void run() {
+ while (!closed.get()) {
+ try {
+ Runnable task = queue.take();
+ long begin = System.currentTimeMillis();
+ task.run();
+ long duration = System.currentTimeMillis();
+ LOGGER.info("runner[{}] executed task {} cost {} ms", getName(), task, duration - begin);
+ } catch (InterruptedException e) {
+ LOGGER.error(e.toString(), e);
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/alibaba/nacos/client/aliyun/AliyunConfigFilterTest.java b/src/test/java/com/alibaba/nacos/client/aliyun/AliyunConfigFilterTest.java
new file mode 100644
index 0000000..7f96521
--- /dev/null
+++ b/src/test/java/com/alibaba/nacos/client/aliyun/AliyunConfigFilterTest.java
@@ -0,0 +1,97 @@
+package com.alibaba.nacos.client.aliyun;
+
+import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.api.utils.StringUtils;
+import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager;
+import com.alibaba.nacos.client.config.filter.impl.ConfigRequest;
+import com.alibaba.nacos.client.config.filter.impl.ConfigResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+public class AliyunConfigFilterTest {
+ private static final String ENCRYPTED_DATA_KEY = "encryptedDataKey";
+ public static Properties properties;
+ public static final List dataIdList = new ArrayList(){{
+ add("cipher-crypt");
+ add("cipher-kms-aes-256-crypt");
+ add("cipher-kms-aes-128-crypt");
+ }};
+
+ public static final String content = "crypt";
+
+ public static final String group = "default";
+
+ @BeforeEach
+ public void preset() {
+ try {
+ properties = new Properties();
+ properties.load(this.getClass().getResourceAsStream("/aliyun-kms.properties"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ @Test
+ public void testAliyunConfigFilterWithKmsV1() {
+ properties.setProperty(AliyunConst.KMS_VERSION_KEY, AliyunConst.KmsVersion.Kmsv1.getValue());
+ //ignore kmsEndpoint
+ properties.setProperty("kmsEndpoint", "");
+ properties.setProperty("regionId", "cn-beijing");
+ properties.setProperty("kms_region_id", "cn-beijing");
+ properties.setProperty("accessKey", "LTAxxxx1E6");
+ properties.setProperty("secretKey", "kr6JxxxsD6");
+ properties.setProperty("keyId", "alias/acs/mse");
+ executeConfigFilter();
+ }
+
+ // must be running in vpc
+// @Test
+// public void testAliyunConfigFilterWithKmsV3() {
+// properties.setProperty(AliyunConst.KMS_VERSION_KEY, AliyunConst.KmsVersion.Kmsv3.getValue());
+// properties.setProperty("keyId", "alias/chasu");
+// properties.setProperty("kmsEndpoint", "kst-bjxxxxxxxxxc.cryptoservice.kms.aliyuncs.com");
+// properties.setProperty("kmsClientKeyFilePath", "/client_key.json");
+// properties.setProperty("kmsPasswordKey", "19axxx213");
+// properties.setProperty("kmsCaFilePath", "/ca.pem");
+// executeConfigFilter();
+// }
+
+ private void executeConfigFilter() {
+ for (String dataId : dataIdList) {
+ ConfigFilterChainManager configFilterChainManager = new ConfigFilterChainManager(properties);
+ AliyunConfigFilter aliyunConfigFilter = new AliyunConfigFilter();
+ configFilterChainManager.addFilter(aliyunConfigFilter);
+
+ ConfigRequest configRequest = new ConfigRequest();
+ configRequest.setGroup(group);
+ configRequest.setDataId(dataId);
+ configRequest.setContent(content);
+ String encryptedContent = null;
+ try {
+ configFilterChainManager.doFilter(configRequest, null);
+ encryptedContent = configRequest.getContent();
+ Assertions.assertFalse(StringUtils.isBlank(encryptedContent));
+ } catch (NacosException e) {
+ e.printStackTrace();
+ }
+
+ ConfigResponse configResponse = new ConfigResponse();
+ configResponse.setGroup(group);
+ configResponse.setDataId(dataId);
+ configResponse.setEncryptedDataKey((String) configRequest.getParameter(ENCRYPTED_DATA_KEY));
+ configResponse.setContent(encryptedContent);
+ try {
+ configFilterChainManager.doFilter(null, configResponse);
+ Assertions.assertEquals(content, configResponse.getContent());
+ } catch (NacosException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/src/test/resources/aliyun-kms.properties b/src/test/resources/aliyun-kms.properties
new file mode 100644
index 0000000..62f4a1a
--- /dev/null
+++ b/src/test/resources/aliyun-kms.properties
@@ -0,0 +1,19 @@
+# v1 configuration
+regionId=cn-beijing
+kms_region_id=cn-beijing
+accessKey=LTAxxx1E6
+secretKey=kr6xxxsD6
+kmsVersion=v1.0
+keyId=alias/kk
+
+# v3 configuration
+# regionId=cn-beijing
+# kms_region_id=cn-beijing
+# accessKey=LTAxxx1E6
+# secretKey=kr6xxxsD6
+# keyId=alias/kk
+# kmsVersion=v3.0
+kmsEndpoint=kst-xxxygc.cryptoservice.kms.aliyuncs.com
+kmsClientKeyFilePath=client_key.json
+kmsPasswordKey=19axxx213
+kmsCaFilePath=ca.pem
diff --git a/src/test/resources/ca.pem b/src/test/resources/ca.pem
new file mode 100644
index 0000000..c412709
--- /dev/null
+++ b/src/test/resources/ca.pem
@@ -0,0 +1,2 @@
+-----BEGIN CERTIFICATE-----
+-----END CERTIFICATE-----
diff --git a/src/test/resources/client_key.json b/src/test/resources/client_key.json
new file mode 100644
index 0000000..6c37c9c
--- /dev/null
+++ b/src/test/resources/client_key.json
@@ -0,0 +1,4 @@
+{
+ "KeyId": "KAAxxxe74",
+ "PrivateKeyData": "MIIxxxA=="
+}
\ No newline at end of file