----初始化项目

This commit is contained in:
2025-09-19 20:49:14 +08:00
parent b345d2828d
commit df7765c400
2867 changed files with 359313 additions and 89 deletions

40
plugin/encryption/pom.xml Normal file
View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 1999-2021 Alibaba Group Holding Ltd.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>nacos-plugin</artifactId>
<groupId>com.alibaba.nacos</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nacos-encryption-plugin</artifactId>
<name>nacos-encryption-plugin ${project.version}</name>
<url>https://nacos.io</url>
<description>Nacos encryption plugin pom.xml file</description>
<dependencies>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-common</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,98 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.nacos.plugin.encryption;
import com.alibaba.nacos.common.spi.NacosServiceLoader;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* Encryption Plugin Management.
*
* @author lixiaoshuang
*/
public class EncryptionPluginManager {
private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionPluginManager.class);
private static final Map<String, EncryptionPluginService> ENCRYPTION_SPI_MAP = new ConcurrentHashMap<>();
private static final EncryptionPluginManager INSTANCE = new EncryptionPluginManager();
private EncryptionPluginManager() {
loadInitial();
}
/**
* Load initial.
*/
private void loadInitial() {
Collection<EncryptionPluginService> encryptionPluginServices = NacosServiceLoader.load(
EncryptionPluginService.class);
for (EncryptionPluginService encryptionPluginService : encryptionPluginServices) {
if (StringUtils.isBlank(encryptionPluginService.algorithmName())) {
LOGGER.warn("[EncryptionPluginManager] Load EncryptionPluginService({}) algorithmName(null/empty) fail."
+ " Please Add algorithmName to resolve.", encryptionPluginService.getClass());
continue;
}
ENCRYPTION_SPI_MAP.put(encryptionPluginService.algorithmName(), encryptionPluginService);
LOGGER.info("[EncryptionPluginManager] Load EncryptionPluginService({}) algorithmName({}) successfully.",
encryptionPluginService.getClass(), encryptionPluginService.algorithmName());
}
}
/**
* Get EncryptionPluginManager instance.
*
* @return EncryptionPluginManager
*/
public static EncryptionPluginManager instance() {
return INSTANCE;
}
/**
* get EncryptionPluginService instance.
*
* @param algorithmName algorithmName, mark a EncryptionPluginService instance.
* @return EncryptionPluginService instance.
*/
public Optional<EncryptionPluginService> findEncryptionService(String algorithmName) {
return Optional.ofNullable(ENCRYPTION_SPI_MAP.get(algorithmName));
}
/**
* Injection realization.
*
* @param encryptionPluginService Encryption implementation
*/
public static synchronized void join(EncryptionPluginService encryptionPluginService) {
if (Objects.isNull(encryptionPluginService)) {
return;
}
ENCRYPTION_SPI_MAP.put(encryptionPluginService.algorithmName(), encryptionPluginService);
LOGGER.info("[EncryptionPluginManager] join successfully.");
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.nacos.plugin.encryption.handler;
import com.alibaba.nacos.common.utils.Pair;
import com.alibaba.nacos.plugin.encryption.EncryptionPluginManager;
import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Optional;
import java.util.stream.Stream;
/**
* EncryptionHandler.
*
* @author lixiaoshuang
*/
public class EncryptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionHandler.class);
/**
* For examplecipher-AES-dataId.
*/
private static final String PREFIX = "cipher-";
/**
* Execute encryption.
*
* @param dataId dataId
* @param content Content that needs to be encrypted.
* @return Return key and ciphertext.
*/
public static Pair<String, String> encryptHandler(String dataId, String content) {
if (!checkCipher(dataId)) {
return Pair.with("", content);
}
Optional<String> algorithmName = parseAlgorithmName(dataId);
Optional<EncryptionPluginService> optional = algorithmName.flatMap(
EncryptionPluginManager.instance()::findEncryptionService);
if (!optional.isPresent()) {
LOGGER.warn("[EncryptionHandler] [encryptHandler] No encryption program with the corresponding name found");
return Pair.with("", content);
}
EncryptionPluginService encryptionPluginService = optional.get();
String secretKey = encryptionPluginService.generateSecretKey();
String encryptContent = encryptionPluginService.encrypt(secretKey, content);
return Pair.with(encryptionPluginService.encryptSecretKey(secretKey), encryptContent);
}
/**
* Execute decryption.
*
* @param dataId dataId
* @param secretKey Decryption key.
* @param content Content that needs to be decrypted.
* @return Return key and plaintext.
*/
public static Pair<String, String> decryptHandler(String dataId, String secretKey, String content) {
if (!checkCipher(dataId)) {
return Pair.with(secretKey, content);
}
Optional<String> algorithmName = parseAlgorithmName(dataId);
Optional<EncryptionPluginService> optional = algorithmName.flatMap(
EncryptionPluginManager.instance()::findEncryptionService);
if (!optional.isPresent()) {
LOGGER.warn("[EncryptionHandler] [decryptHandler] No encryption program with the corresponding name found");
return Pair.with(secretKey, content);
}
EncryptionPluginService encryptionPluginService = optional.get();
String decryptSecretKey = encryptionPluginService.decryptSecretKey(secretKey);
String decryptContent = encryptionPluginService.decrypt(decryptSecretKey, content);
return Pair.with(decryptSecretKey, decryptContent);
}
/**
* Parse encryption algorithm name.
*
* @param dataId dataId
* @return algorithm name
*/
private static Optional<String> parseAlgorithmName(String dataId) {
return Stream.of(dataId.split("-")).skip(1).findFirst();
}
/**
* Check if encryption and decryption is needed.
*
* @param dataId dataId
* @return boolean whether data id needs encrypt
*/
private static boolean checkCipher(String dataId) {
return dataId.startsWith(PREFIX) && !PREFIX.equals(dataId);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.nacos.plugin.encryption.spi;
/**
* Encryption and decryption spi.
*
* @author lixiaoshuang
*/
public interface EncryptionPluginService {
/**
* Encrypted interface.
*
* @param secretKey secret key
* @param content content unencrypted
* @return encrypt value
*/
String encrypt(String secretKey, String content);
/**
* Decryption interface.
*
* @param secretKey secret key
* @param content encrypted
* @return decrypt value
*/
String decrypt(String secretKey, String content);
/**
* Generate Secret key.
*
* @return Secret key
*/
String generateSecretKey();
/**
* Algorithm naming.
*
* @return name
*/
String algorithmName();
/**
* encrypt secretKey.
* @param secretKey secretKey
* @return encrypted secretKey
*/
String encryptSecretKey(String secretKey);
/**
* decrypt secretKey.
* @param secretKey secretKey
* @return decrypted secretKey
*/
String decryptSecretKey(String secretKey);
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.nacos.plugin.encryption;
import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService;
import org.junit.Assert;
import org.junit.Test;
import java.util.Optional;
/**
* EncryptionPluginManagerTest.
*
* @author lixiaoshuang
*/
public class EncryptionPluginManagerTest {
@Test
public void testInstance() {
EncryptionPluginManager instance = EncryptionPluginManager.instance();
Assert.assertNotNull(instance);
}
@Test
public void testJoin() {
EncryptionPluginManager.join(new EncryptionPluginService() {
@Override
public String encrypt(String secretKey, String content) {
return content;
}
@Override
public String decrypt(String secretKey, String content) {
return content;
}
@Override
public String generateSecretKey() {
return "12345678";
}
@Override
public String algorithmName() {
return "aes";
}
@Override
public String encryptSecretKey(String secretKey) {
return secretKey;
}
@Override
public String decryptSecretKey(String secretKey) {
return secretKey;
}
});
Assert.assertNotNull(EncryptionPluginManager.instance().findEncryptionService("aes"));
}
@Test
public void testFindEncryptionService() {
EncryptionPluginManager instance = EncryptionPluginManager.instance();
Optional<EncryptionPluginService> optional = instance.findEncryptionService("aes");
Assert.assertTrue(optional.isPresent());
}
}

View File

@ -0,0 +1,235 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.nacos.plugin.encryption.handler;
import com.alibaba.nacos.common.utils.Pair;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.encryption.EncryptionPluginManager;
import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* AES encryption algorithm testing dataId with prefix cipher.
*
* @Author shiwenyu
* @Date 2023/12/22 6:07 PM
* @Version 1.0
*/
public class EncryptionAesHandlerTest {
private EncryptionPluginService mockEncryptionPluginService;
@Before
public void setUp() {
mockEncryptionPluginService = new EncryptionPluginService() {
private static final String ALGORITHM = "AES";
private static final String AES_PKCS5P = "AES/ECB/PKCS5Padding";
// 随机生成密钥-用来加密数据内容
private final String contentKey = generateKey();
// 随机生成密钥-用来加密密钥
private final String theKeyOfContentKey = generateKey();
private String generateKey() {
SecureRandom secureRandom = new SecureRandom();
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
keyGenerator.init(128, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyBytes = secretKey.getEncoded();
return Base64.encodeBase64String(keyBytes);
}
@Override
public String encrypt(String secretKey, String content) {
return Base64.encodeBase64String(aes(Cipher.ENCRYPT_MODE, content, secretKey));
}
@Override
public String decrypt(String secretKey, String content) {
if (StringUtils.isBlank(secretKey)) {
return null;
}
return aesDecrypt(content, secretKey);
}
@Override
public String generateSecretKey() {
return contentKey;
}
@Override
public String algorithmName() {
return ALGORITHM.toLowerCase();
}
@Override
public String encryptSecretKey(String secretKey) {
return Base64.encodeBase64String(aes(Cipher.ENCRYPT_MODE, generateSecretKey(), theKeyOfContentKey));
}
@Override
public String decryptSecretKey(String secretKey) {
if (StringUtils.isBlank(secretKey)) {
return null;
}
return aesDecrypt(secretKey, theKeyOfContentKey);
}
private byte[] aes(int mode, String content, String key) {
try {
return aesBytes(mode, content.getBytes(StandardCharsets.UTF_8), key);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private byte[] aesBytes(int mode, byte[] content, String key) {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
Cipher cipher = null;
try {
cipher = Cipher.getInstance(AES_PKCS5P);
cipher.init(mode, keySpec);
return cipher.doFinal(content);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String aesDecrypt(String content, String key) {
byte[] bytes = aesBytes(Cipher.DECRYPT_MODE, Base64.decodeBase64(content), key);
return new String(bytes, StandardCharsets.UTF_8);
}
};
EncryptionPluginManager.join(mockEncryptionPluginService);
}
@Test
public void testEncrypt() {
String content = "content";
String contentKey = mockEncryptionPluginService.generateSecretKey();
Pair<String, String> pair = EncryptionHandler.encryptHandler("cipher-aes-dataId", content);
Assert.assertEquals("should return the encryption secret key if algorithm defined.", mockEncryptionPluginService.encryptSecretKey(contentKey),
pair.getFirst());
Assert.assertEquals("should return the encryption content if algorithm defined.", mockEncryptionPluginService.encrypt(contentKey, content),
pair.getSecond());
}
@Test
public void testDecrypt() {
String content = "content";
String contentKey = mockEncryptionPluginService.generateSecretKey();
String encryptionSecretKey = mockEncryptionPluginService.encryptSecretKey(contentKey);
String encryptionContent = mockEncryptionPluginService.encrypt(contentKey, content);
Pair<String, String> pair = EncryptionHandler.decryptHandler("cipher-aes-dataId", encryptionSecretKey, encryptionContent);
Assert.assertEquals("should return the original secret key if algorithm defined.", mockEncryptionPluginService.generateSecretKey(),
pair.getFirst());
Assert.assertEquals("should return the original content if algorithm defined.", content, pair.getSecond());
}
@Test
public void testEncryptAndDecrypt() {
String dataId = "cipher-aes-dataId";
String content = "content";
String contentKey = mockEncryptionPluginService.generateSecretKey();
Pair<String, String> encryptPair = EncryptionHandler.encryptHandler(dataId, content);
String encryptionSecretKey = encryptPair.getFirst();
String encryptionContent = encryptPair.getSecond();
Assert.assertNotNull(encryptPair);
Assert.assertEquals("should return the encryption secret key if algorithm defined.", mockEncryptionPluginService.encryptSecretKey(contentKey),
encryptionSecretKey);
Assert.assertEquals("should return the encryption content if algorithm defined.", mockEncryptionPluginService.encrypt(contentKey, content),
encryptionContent);
Pair<String, String> decryptPair = EncryptionHandler.decryptHandler(dataId, encryptionSecretKey, encryptionContent);
Assert.assertNotNull(decryptPair);
Assert.assertEquals("should return the original secret key if algorithm defined.", mockEncryptionPluginService.generateSecretKey(),
decryptPair.getFirst());
Assert.assertEquals("should return the original content if algorithm defined.", content, decryptPair.getSecond());
}
@Test
public void testPrefixNotCipherEncrypt() {
String content = "content";
Pair<String, String> pair = EncryptionHandler.encryptHandler("test-dataId", content);
Assert.assertNotNull(pair);
Assert.assertEquals(pair.getFirst(), "");
Assert.assertEquals(pair.getSecond(), content);
}
@Test
public void testPrefixNotCipherDecrypt() {
String content = "content";
Pair<String, String> pair = EncryptionHandler.decryptHandler("test-dataId", "", content);
Assert.assertNotNull(pair);
Assert.assertEquals(pair.getFirst(), "");
Assert.assertEquals(pair.getSecond(), content);
}
@Test
public void testAlgorithmEmpty() {
String dataId = "cipher-";
String content = "content";
Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, content);
Assert.assertNotNull("should not throw exception when parsing enc algo for dataId '" + dataId + "'", pair);
Assert.assertEquals(pair.getFirst(), "");
Assert.assertEquals(pair.getSecond(), content);
}
@Test
public void testUnknownAlgorithmNameEncrypt() {
String dataId = "cipher-mySM4-application";
String content = "content";
Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, content);
Assert.assertNotNull(pair);
Assert.assertEquals(pair.getFirst(), "");
Assert.assertEquals("should return original content if algorithm is not defined.", content, pair.getSecond());
}
@Test
public void testUnknownAlgorithmNameDecrypt() {
String dataId = "cipher-mySM4-application";
String content = "content";
Pair<String, String> pair = EncryptionHandler.decryptHandler(dataId, "", content);
Assert.assertNotNull(pair);
Assert.assertEquals(pair.getFirst(), "");
Assert.assertEquals("should return original content if algorithm is not defined.", content, pair.getSecond());
}
}

View File

@ -0,0 +1,133 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.nacos.plugin.encryption.handler;
import com.alibaba.nacos.common.utils.Pair;
import com.alibaba.nacos.plugin.encryption.EncryptionPluginManager;
import com.alibaba.nacos.plugin.encryption.spi.EncryptionPluginService;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* EncryptionHandlerTest.
*
* @author lixiaoshuang
*/
public class EncryptionHandlerTest {
private EncryptionPluginService mockEncryptionPluginService;
@Before
public void setUp() {
mockEncryptionPluginService = new EncryptionPluginService() {
@Override
public String encrypt(String secretKey, String content) {
return secretKey + content;
}
@Override
public String decrypt(String secretKey, String content) {
return content.replaceFirst(secretKey, "");
}
@Override
public String generateSecretKey() {
return "12345678";
}
@Override
public String algorithmName() {
return "mockAlgo";
}
@Override
public String encryptSecretKey(String secretKey) {
return secretKey + secretKey;
}
@Override
public String decryptSecretKey(String secretKey) {
return generateSecretKey();
}
};
EncryptionPluginManager.join(mockEncryptionPluginService);
}
@Test
public void testEncryptHandler() {
Pair<String, String> pair = EncryptionHandler.encryptHandler("test-dataId", "content");
Assert.assertNotNull(pair);
}
@Test
public void testDecryptHandler() {
Pair<String, String> pair = EncryptionHandler.decryptHandler("test-dataId", "12345678", "content");
Assert.assertNotNull(pair);
}
@Test
public void testCornerCaseDataIdAlgoParse() {
String dataId = "cipher-";
Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, "content");
Assert.assertNotNull("should not throw exception when parsing enc algo for dataId '" + dataId + "'", pair);
}
@Test
public void testUnknownAlgorithmNameEnc() {
String dataId = "cipher-mySM4-application";
String content = "content";
Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, content);
Assert.assertNotNull(pair);
Assert.assertEquals("should return original content if algorithm is not defined.", content, pair.getSecond());
}
@Test
public void testUnknownAlgorithmNameDecrypt() {
String dataId = "cipher-mySM4-application";
String content = "content";
Pair<String, String> pair = EncryptionHandler.decryptHandler(dataId, "", content);
Assert.assertNotNull(pair);
Assert.assertEquals("should return original content if algorithm is not defined.", content, pair.getSecond());
}
@Test
public void testEncrypt() {
String dataId = "cipher-mockAlgo-application";
String content = "content";
String sec = mockEncryptionPluginService.generateSecretKey();
Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, content);
Assert.assertNotNull(pair);
Assert.assertEquals("should return encrypted content.",
mockEncryptionPluginService.encrypt(sec, content), pair.getSecond());
Assert.assertEquals("should return encrypted secret key.",
mockEncryptionPluginService.encryptSecretKey(sec), pair.getFirst());
}
@Test
public void testDecrypt() {
String dataId = "cipher-mockAlgo-application";
String oContent = "content";
String oSec = mockEncryptionPluginService.generateSecretKey();
String content = mockEncryptionPluginService.encrypt(oSec, oContent);
String sec = mockEncryptionPluginService.encryptSecretKey(oSec);
Pair<String, String> pair = EncryptionHandler.decryptHandler(dataId, sec, content);
Assert.assertNotNull(pair);
Assert.assertEquals("should return original content.", oContent, pair.getSecond());
Assert.assertEquals("should return original secret key.", oSec, pair.getFirst());
}
}