This is an automated email from the ASF dual-hosted git repository. healchow pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/inlong.git
commit a2145ce4c85142313637eef41559068f975cd5ce Author: woofyzhao <[email protected]> AuthorDate: Sat Jul 2 17:19:39 2022 +0800 [INLONG-4774][Manager] Support auth and encryption key user config (#4775) * Support auth and encryption key user config * Support both encrypted and unencrypted data store * Update some classes name and property files Co-authored-by: healchow <[email protected]> --- .../inlong/manager/common/pojo/user/UserInfo.java | 12 +- .../inlong/manager/common/util/AESUtils.java | 192 +++++++++++++++++++++ .../inlong/manager/common/util/RSAUtils.java | 122 +++++++++++++ .../inlong/manager/common/util/AESUtilsTest.java | 61 +++++++ .../src/test/resources/application.properties | 24 +++ .../inlong/manager/dao/entity/UserEntity.java | 7 +- .../main/resources/mappers/UserEntityMapper.xml | 78 +++++++-- .../manager/service/core/impl/UserServiceImpl.java | 48 +++++- .../main/resources/h2/apache_inlong_manager.sql | 22 ++- .../manager-web/sql/apache_inlong_manager.sql | 22 ++- .../src/main/resources/application.properties | 4 + .../manager/web/controller/AnnoControllerTest.java | 5 +- .../resources/application.properties | 4 + 13 files changed, 555 insertions(+), 46 deletions(-) diff --git a/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/pojo/user/UserInfo.java b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/pojo/user/UserInfo.java index cc6f9dadc..d4d5ada15 100644 --- a/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/pojo/user/UserInfo.java +++ b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/pojo/user/UserInfo.java @@ -43,8 +43,7 @@ public class UserInfo { private Integer id; /** - * user type - * {@link UserTypeEnum} + * user type {@link UserTypeEnum} */ @NotNull @InEnumInt(UserTypeEnum.class) @@ -59,6 +58,15 @@ public class UserInfo { @ApiModelProperty(value = "password", required = true) private String password; + @ApiModelProperty("secret key") + private String secretKey; + + @ApiModelProperty("public key") + private String publicKey; + + @ApiModelProperty("private key") + private String privateKey; + @NotNull @Min(1) @ApiModelProperty(value = "valid days", required = true) diff --git a/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/AESUtils.java b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/AESUtils.java new file mode 100644 index 000000000..a3676421f --- /dev/null +++ b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/AESUtils.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.inlong.manager.common.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.SecureRandom; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +/** + * AES encryption and decryption utils. + */ +@Slf4j +public class AESUtils { + + private static final int DEFAULT_VERSION = 1; + private static final int KEY_SIZE = 128; + private static final String ALGORITHM = "AES"; + private static final String RNG_ALGORITHM = "SHA1PRNG"; + + private static final String CONFIG_FILE = "application.properties"; + private static final String CONFIG_ITEM_ENCRYPT_KEY_PREFIX = "inlong.encrypt.key.value"; + private static final String CONFIG_ITEM_ENCRYPT_VERSION = "inlong.encrypt.version"; + + public static Map<Integer, String> AES_KEY_MAP = new ConcurrentHashMap<>(); + public static Integer CURRENT_VERSION; + + /** + * Load the application properties configuration + */ + private static Properties getApplicationProperties() throws IOException { + Properties properties = new Properties(); + String path = Thread.currentThread().getContextClassLoader().getResource("").getPath() + CONFIG_FILE; + InputStream inputStream = new BufferedInputStream(Files.newInputStream(Paths.get(path))); + properties.load(inputStream); + return properties; + } + + /** + * Get the current aes key version + */ + public static Integer getCurrentVersion(Properties properties) throws IOException { + if (CURRENT_VERSION != null) { + return CURRENT_VERSION; + } + + if (properties == null) { + properties = getApplicationProperties(); + } + String verStr = properties.getProperty(CONFIG_ITEM_ENCRYPT_VERSION); + if (StringUtils.isNotEmpty(verStr)) { + CURRENT_VERSION = Integer.valueOf(verStr); + } else { + CURRENT_VERSION = DEFAULT_VERSION; + } + log.debug("Crypto CURRENT_VERSION = {}", CURRENT_VERSION); + return CURRENT_VERSION; + } + + /** + * Get aes key from config file + */ + public static String getAesKeyByConfig(Integer version) throws Exception { + Properties properties = getApplicationProperties(); + Integer targetVersion = (version == null ? getCurrentVersion(properties) : version); + if (StringUtils.isNotEmpty(AES_KEY_MAP.get(targetVersion))) { + return AES_KEY_MAP.get(targetVersion); + } + + // get aes key under specified version + String keyName = CONFIG_ITEM_ENCRYPT_KEY_PREFIX + targetVersion; + String aesKey = properties.getProperty(keyName); + if (StringUtils.isEmpty(aesKey)) { + throw new RuntimeException(String.format("cannot find encryption key %s in application config", keyName)); + } + AES_KEY_MAP.put(targetVersion, aesKey); + return aesKey; + } + + /** + * Generate key + */ + private static SecretKey generateKey(byte[] aesKey) throws Exception { + SecureRandom random = SecureRandom.getInstance(RNG_ALGORITHM); + random.setSeed(aesKey); + KeyGenerator gen = KeyGenerator.getInstance(ALGORITHM); + gen.init(KEY_SIZE, random); + return gen.generateKey(); + } + + /** + * Encrypt by key + */ + public static byte[] encrypt(byte[] plainBytes, byte[] key) throws Exception { + SecretKey secKey = generateKey(key); + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secKey); + return cipher.doFinal(plainBytes); + } + + /** + * Encrypt by key and current version + */ + public static String encryptToString(byte[] plainBytes, Integer version) throws Exception { + if (version == null) { + // no encryption + return new String(plainBytes, StandardCharsets.UTF_8); + } + byte[] keyBytes = getAesKeyByConfig(version).getBytes(StandardCharsets.UTF_8); + return parseByte2HexStr(encrypt(plainBytes, keyBytes)); + } + + /** + * Decrypt by key and specified version + */ + public static byte[] decrypt(byte[] cipherBytes, byte[] key) throws Exception { + SecretKey secKey = generateKey(key); + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secKey); + return cipher.doFinal(cipherBytes); + } + + /** + * Encrypt by property key + */ + public static byte[] decryptAsString(String cipherText, Integer version) throws Exception { + if (version == null) { + // No decryption: treated as plain text + return cipherText.getBytes(StandardCharsets.UTF_8); + } + byte[] keyBytes = getAesKeyByConfig(version).getBytes(StandardCharsets.UTF_8); + return decrypt(parseHexStr2Byte(cipherText), keyBytes); + } + + /** + * Parse byte to String in Hex type + */ + public static String parseByte2HexStr(byte[] buf) { + StringBuilder strBuf = new StringBuilder(); + for (byte b : buf) { + String hex = Integer.toHexString(b & 0xFF); + if (hex.length() == 1) { + hex = '0' + hex; + } + strBuf.append(hex.toUpperCase()); + } + return strBuf.toString(); + } + + /** + * Parse String to byte as Hex type + */ + public static byte[] parseHexStr2Byte(String hexStr) { + if (hexStr.length() < 1) { + return null; + } + byte[] result = new byte[hexStr.length() / 2]; + for (int i = 0; i < hexStr.length() / 2; i++) { + int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16); + int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16); + result[i] = (byte) (high * 16 + low); + } + return result; + } +} \ No newline at end of file diff --git a/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/RSAUtils.java b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/RSAUtils.java new file mode 100644 index 000000000..cf3bf9635 --- /dev/null +++ b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/RSAUtils.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.inlong.manager.common.util; + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.HashMap; +import java.util.Map; + +/** + * RSA encryption and decryption utils. + */ +public class RSAUtils { + + public static final String PUBLIC_KEY = "RSAPublicKey"; + public static final String PRIVATE_KEY = "RSAPrivateKey"; + private static final String KEY_ALGORITHM = "RSA"; + private static final String SIGNATURE_ALGORITHM = "SHA1PRNG"; + private static final int MAX_ENCRYPT_BLOCK = 117; + private static final int MAX_DECRYPT_BLOCK = 128; + private static SecureRandom random; + + static { + try { + random = SecureRandom.getInstance(SIGNATURE_ALGORITHM); + } catch (NoSuchAlgorithmException ignored) { + // impossible + } + } + + /** + * Generate RSA key pairs + */ + public static Map<String, String> generateRSAKeyPairs() throws NoSuchAlgorithmException { + Map<String, String> keyPairMap = new HashMap<>(); + KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM); + generator.initialize(1024, random); + KeyPair keyPair = generator.genKeyPair(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + keyPairMap.put(PUBLIC_KEY, Base64.encodeBase64String(publicKey.getEncoded())); + keyPairMap.put(PRIVATE_KEY, Base64.encodeBase64String(privateKey.getEncoded())); + return keyPairMap; + } + + /** + * Encryption by public key + */ + public static byte[] encryptByPublicKey(byte[] data, RSAPublicKey publicKey) throws Exception { + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // encrypt data by block + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + } + + /** + * Decryption by private key + */ + public static byte[] decryptByPrivateKey(byte[] encryptedData, RSAPrivateKey privateKey) throws Exception { + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + int inputLen = encryptedData.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // decrypt data by block + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + return decryptedData; + } +} diff --git a/inlong-manager/manager-common/src/test/java/org/apache/inlong/manager/common/util/AESUtilsTest.java b/inlong-manager/manager-common/src/test/java/org/apache/inlong/manager/common/util/AESUtilsTest.java new file mode 100644 index 000000000..82388272e --- /dev/null +++ b/inlong-manager/manager-common/src/test/java/org/apache/inlong/manager/common/util/AESUtilsTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.inlong.manager.common.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +/** + * AES encryption and decryption util test. + */ +public class AESUtilsTest { + + @Test + public void testEncryptDecryptDirectly() throws Exception { + byte[] key = "key-123".getBytes(StandardCharsets.UTF_8); + String plainText = "hello, inlong"; + byte[] cipheredBytes = AESUtils.encrypt(plainText.getBytes(StandardCharsets.UTF_8), key); + byte[] decipheredBytes = AESUtils.decrypt(cipheredBytes, key); + Assertions.assertEquals(plainText, new String(decipheredBytes, StandardCharsets.UTF_8)); + } + + @Test + void testEncryptDecryptByConfigVersion() throws Exception { + String plainText = "hello, inlong again"; + Integer version = AESUtils.getCurrentVersion(null); + String cipheredText = AESUtils.encryptToString(plainText.getBytes(StandardCharsets.UTF_8), version); + byte[] decipheredBytes = AESUtils.decryptAsString(cipheredText, version); + Assertions.assertEquals(plainText, new String(decipheredBytes, StandardCharsets.UTF_8)); + } + + @Test + void testEncryptDecryptByNullVersion() throws Exception { + String plainText = "hello, inlong again"; + + // when version is null no encryption is performed + String cipheredText = AESUtils.encryptToString(plainText.getBytes(StandardCharsets.UTF_8), null); + Assertions.assertEquals(plainText, cipheredText); + + // when version is null no decryption is performed + byte[] decipheredBytes = AESUtils.decryptAsString(cipheredText, null); + Assertions.assertEquals(plainText, new String(decipheredBytes, StandardCharsets.UTF_8)); + } +} diff --git a/inlong-manager/manager-common/src/test/resources/application.properties b/inlong-manager/manager-common/src/test/resources/application.properties new file mode 100644 index 000000000..0481c7a54 --- /dev/null +++ b/inlong-manager/manager-common/src/test/resources/application.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +# Configure auth plugin +#inlong.auth.type=default +# Encryption config, the suffix of value must be the same as the version. +inlong.encrypt.version=1 +inlong.encrypt.key.value1="I!N@L#O$N%G^" diff --git a/inlong-manager/manager-dao/src/main/java/org/apache/inlong/manager/dao/entity/UserEntity.java b/inlong-manager/manager-dao/src/main/java/org/apache/inlong/manager/dao/entity/UserEntity.java index 52ccba49d..b4c6d181a 100644 --- a/inlong-manager/manager-dao/src/main/java/org/apache/inlong/manager/dao/entity/UserEntity.java +++ b/inlong-manager/manager-dao/src/main/java/org/apache/inlong/manager/dao/entity/UserEntity.java @@ -17,9 +17,10 @@ package org.apache.inlong.manager.dao.entity; +import lombok.Data; + import java.io.Serializable; import java.util.Date; -import lombok.Data; /** * User entity, including username, password, etc. @@ -31,6 +32,10 @@ public class UserEntity implements Serializable { private Integer id; private String name; private String password; + private String secretKey; + private String publicKey; + private String privateKey; + private Integer encryptVersion; private Integer accountType; private Date dueDate; private Date createTime; diff --git a/inlong-manager/manager-dao/src/main/resources/mappers/UserEntityMapper.xml b/inlong-manager/manager-dao/src/main/resources/mappers/UserEntityMapper.xml index 3ecda201f..84c3e3b1d 100644 --- a/inlong-manager/manager-dao/src/main/resources/mappers/UserEntityMapper.xml +++ b/inlong-manager/manager-dao/src/main/resources/mappers/UserEntityMapper.xml @@ -24,6 +24,10 @@ <id column="id" jdbcType="INTEGER" property="id"/> <result column="name" jdbcType="VARCHAR" property="name"/> <result column="password" jdbcType="VARCHAR" property="password"/> + <result column="secret_key" jdbcType="VARCHAR" property="secretKey"/> + <result column="public_key" jdbcType="VARCHAR" property="publicKey"/> + <result column="private_key" jdbcType="VARCHAR" property="privateKey"/> + <result column="encrypt_version" jdbcType="INTEGER" property="encryptVersion"/> <result column="account_type" jdbcType="INTEGER" property="accountType"/> <result column="due_date" jdbcType="TIMESTAMP" property="dueDate"/> <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/> @@ -62,8 +66,8 @@ </where> </sql> <sql id="Base_Column_List"> - id, name, password, account_type, due_date, create_time, update_time, create_by, - update_by + id, name, password, secret_key, public_key, private_key, encrypt_version, + account_type, due_date, create_time, update_time, create_by, update_by </sql> <select id="selectByExample" parameterType="org.apache.inlong.manager.dao.entity.UserEntityExample" resultMap="BaseResultMap"> @@ -93,11 +97,15 @@ </delete> <insert id="insert" parameterType="org.apache.inlong.manager.dao.entity.UserEntity"> insert into user (id, name, password, - account_type, due_date, create_time, - update_time, create_by, update_by) + secret_key, public_key, private_key, + encrypt_version, account_type, due_date, + create_time, update_time, + create_by, update_by) values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, - #{accountType,jdbcType=INTEGER}, #{dueDate,jdbcType=TIMESTAMP}, #{createTime,jdbcType=TIMESTAMP}, - #{updateTime,jdbcType=TIMESTAMP}, #{createBy,jdbcType=VARCHAR}, #{updateBy,jdbcType=VARCHAR}) + #{secretKey,jdbcType=VARCHAR}, #{publicKey,jdbcType=VARCHAR}, #{privateKey,jdbcType=VARCHAR}, + #{encryptVersion,jdbcType=INTEGER}, #{accountType,jdbcType=INTEGER}, #{dueDate,jdbcType=TIMESTAMP}, + #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{createBy,jdbcType=VARCHAR}, + #{updateBy,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" parameterType="org.apache.inlong.manager.dao.entity.UserEntity"> insert into user @@ -111,6 +119,18 @@ <if test="password != null"> password, </if> + <if test="secretKey != null"> + secret_key, + </if> + <if test="publicKey != null"> + public_key, + </if> + <if test="privateKey != null"> + private_key, + </if> + <if test="encryptVersion != null"> + encrypt_version, + </if> <if test="accountType != null"> account_type, </if> @@ -140,6 +160,18 @@ <if test="password != null"> #{password,jdbcType=VARCHAR}, </if> + <if test="secretKey != null"> + #{secretKey,jdbcType=VARCHAR}, + </if> + <if test="publicKey != null"> + #{publicKey,jdbcType=VARCHAR}, + </if> + <if test="privateKey != null"> + #{privateKey,jdbcType=VARCHAR}, + </if> + <if test="encryptVersion != null"> + #{encryptVersion,jdbcType=INTEGER}, + </if> <if test="accountType != null"> #{accountType,jdbcType=INTEGER}, </if> @@ -176,6 +208,18 @@ <if test="password != null"> password = #{password,jdbcType=VARCHAR}, </if> + <if test="secretKey != null"> + secret_key = #{secretKey,jdbcType=VARCHAR}, + </if> + <if test="publicKey != null"> + public_key = #{publicKey,jdbcType=VARCHAR}, + </if> + <if test="privateKey != null"> + private_key = #{privateKey,jdbcType=VARCHAR}, + </if> + <if test="encryptVersion != null"> + encrypt_version = #{encryptVersion,jdbcType=INTEGER}, + </if> <if test="accountType != null"> account_type = #{accountType,jdbcType=INTEGER}, </if> @@ -199,14 +243,18 @@ </update> <update id="updateByPrimaryKey" parameterType="org.apache.inlong.manager.dao.entity.UserEntity"> update user - set name = #{name,jdbcType=VARCHAR}, - password = #{password,jdbcType=VARCHAR}, - account_type = #{accountType,jdbcType=INTEGER}, - due_date = #{dueDate,jdbcType=TIMESTAMP}, - create_time = #{createTime,jdbcType=TIMESTAMP}, - update_time = #{updateTime,jdbcType=TIMESTAMP}, - create_by = #{createBy,jdbcType=VARCHAR}, - update_by = #{updateBy,jdbcType=VARCHAR} + set name = #{name,jdbcType=VARCHAR}, + password = #{password,jdbcType=VARCHAR}, + secret_key = #{secretKey,jdbcType=VARCHAR}, + public_key = #{publicKey,jdbcType=VARCHAR}, + private_key = #{privateKey,jdbcType=VARCHAR}, + encrypt_version = #{encryptVersion,jdbcType=INTEGER}, + account_type = #{accountType,jdbcType=INTEGER}, + due_date = #{dueDate,jdbcType=TIMESTAMP}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + update_time = #{updateTime,jdbcType=TIMESTAMP}, + create_by = #{createBy,jdbcType=VARCHAR}, + update_by = #{updateBy,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> -</mapper> \ No newline at end of file +</mapper> diff --git a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/core/impl/UserServiceImpl.java b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/core/impl/UserServiceImpl.java index 727b0617e..46916d48b 100644 --- a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/core/impl/UserServiceImpl.java +++ b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/core/impl/UserServiceImpl.java @@ -21,14 +21,18 @@ import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.inlong.manager.common.enums.UserTypeEnum; +import org.apache.inlong.manager.common.exceptions.BusinessException; import org.apache.inlong.manager.common.pojo.user.PasswordChangeRequest; import org.apache.inlong.manager.common.pojo.user.UserDetailListVO; import org.apache.inlong.manager.common.pojo.user.UserDetailPageRequest; import org.apache.inlong.manager.common.pojo.user.UserInfo; +import org.apache.inlong.manager.common.util.AESUtils; import org.apache.inlong.manager.common.util.CommonBeanUtils; import org.apache.inlong.manager.common.util.LoginUserUtils; import org.apache.inlong.manager.common.util.Preconditions; +import org.apache.inlong.manager.common.util.RSAUtils; import org.apache.inlong.manager.common.util.SmallTools; import org.apache.inlong.manager.dao.entity.UserEntity; import org.apache.inlong.manager.dao.entity.UserEntityExample; @@ -38,8 +42,10 @@ import org.apache.inlong.manager.service.core.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.List; +import java.util.Map; import static org.apache.inlong.manager.common.util.SmallTools.getOverDueDate; @@ -64,7 +70,6 @@ public class UserServiceImpl implements UserService { @Override public UserInfo getById(Integer userId) { Preconditions.checkNotNull(userId, "User id should not be empty"); - UserEntity entity = userMapper.selectByPrimaryKey(userId); Preconditions.checkNotNull(entity, "User not exists with id " + userId); @@ -74,6 +79,20 @@ public class UserServiceImpl implements UserService { result.setValidDays(SmallTools.getValidDays(entity.getCreateTime(), entity.getDueDate())); result.setType(entity.getAccountType()); + try { + // decipher according to stored key version + // note that if the version is null then the string is treated as unencrypted plain text + Integer version = entity.getEncryptVersion(); + byte[] secretKeyBytes = AESUtils.decryptAsString(entity.getSecretKey(), version); + byte[] publicKeyBytes = AESUtils.decryptAsString(entity.getPublicKey(), version); + result.setSecretKey(new String(secretKeyBytes, StandardCharsets.UTF_8)); + result.setPublicKey(new String(publicKeyBytes, StandardCharsets.UTF_8)); + } catch (Exception e) { + String errMsg = String.format("decryption error: %s", e.getMessage()); + log.error(errMsg, e); + throw new BusinessException(errMsg); + } + log.debug("success to get user info by id={}", userId); return result; } @@ -82,7 +101,7 @@ public class UserServiceImpl implements UserService { public boolean create(UserInfo userInfo) { String username = userInfo.getUsername(); UserEntity userExists = getByName(username); - Preconditions.checkNull(userExists, "User [" + username + "] already exists"); + Preconditions.checkNull(userExists, "username [" + username + "] already exists"); UserEntity entity = new UserEntity(); entity.setAccountType(userInfo.getType()); @@ -90,6 +109,22 @@ public class UserServiceImpl implements UserService { entity.setDueDate(getOverDueDate(userInfo.getValidDays())); entity.setCreateBy(LoginUserUtils.getLoginUserDetail().getUsername()); entity.setName(username); + try { + Map<String, String> keyPairs = RSAUtils.generateRSAKeyPairs(); + String publicKey = keyPairs.get(RSAUtils.PUBLIC_KEY); + String privateKey = keyPairs.get(RSAUtils.PRIVATE_KEY); + String secretKey = RandomStringUtils.randomAlphanumeric(8); + Integer encryptVersion = AESUtils.getCurrentVersion(null); + entity.setEncryptVersion(encryptVersion); + entity.setPublicKey(AESUtils.encryptToString(publicKey.getBytes(StandardCharsets.UTF_8), encryptVersion)); + entity.setPrivateKey(AESUtils.encryptToString(privateKey.getBytes(StandardCharsets.UTF_8), encryptVersion)); + entity.setSecretKey(AESUtils.encryptToString(secretKey.getBytes(StandardCharsets.UTF_8), encryptVersion)); + } catch (Exception e) { + String errMsg = String.format("generate rsa key error: %s", e.getMessage()); + log.error(errMsg, e); + throw new BusinessException(errMsg); + } + entity.setCreateTime(new Date()); Preconditions.checkTrue(userMapper.insert(entity) > 0, "Create user failed"); @@ -99,13 +134,13 @@ public class UserServiceImpl implements UserService { @Override public int update(UserInfo userInfo, String currentUser) { - Preconditions.checkNotNull(userInfo, "User info should not be null"); - Preconditions.checkNotNull(userInfo.getId(), "User id should not be null"); + Preconditions.checkNotNull(userInfo, "user info should not be null"); + Preconditions.checkNotNull(userInfo.getId(), "user id should not be null"); // Whether the current user is an administrator UserEntity currentUserEntity = getByName(currentUser); Preconditions.checkTrue(currentUserEntity.getAccountType().equals(UserTypeEnum.ADMIN.getCode()), - "The current user is not a manager and does not have permission to update users"); + "current user is not a manager and does not have permission to update users"); UserEntity entity = userMapper.selectByPrimaryKey(userInfo.getId()); Preconditions.checkNotNull(entity, "User not exists with id " + userInfo.getId()); @@ -128,7 +163,6 @@ public class UserServiceImpl implements UserService { String oldPassword = request.getOldPassword(); String oldPasswordMd = SmallTools.passwordMd5(oldPassword); Preconditions.checkTrue(oldPasswordMd.equals(entity.getPassword()), "Old password is wrong"); - String newPasswordMd5 = SmallTools.passwordMd5(request.getNewPassword()); entity.setPassword(newPasswordMd5); @@ -143,7 +177,7 @@ public class UserServiceImpl implements UserService { // Whether the current user is an administrator UserEntity entity = getByName(currentUser); Preconditions.checkTrue(entity.getAccountType().equals(UserTypeEnum.ADMIN.getCode()), - "The current user is not a manager and does not have permission to delete users"); + "current user is not a manager and does not have permission to delete users"); userMapper.deleteByPrimaryKey(userId); log.debug("success to delete user by id={}, current user={}", userId, currentUser); diff --git a/inlong-manager/manager-test/src/main/resources/h2/apache_inlong_manager.sql b/inlong-manager/manager-test/src/main/resources/h2/apache_inlong_manager.sql index 23491c6bc..d91c8be18 100644 --- a/inlong-manager/manager-test/src/main/resources/h2/apache_inlong_manager.sql +++ b/inlong-manager/manager-test/src/main/resources/h2/apache_inlong_manager.sql @@ -558,15 +558,19 @@ CREATE TABLE IF NOT EXISTS `stream_sink_field` -- ---------------------------- CREATE TABLE IF NOT EXISTS `user` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(256) NOT NULL COMMENT 'account name', - `password` varchar(64) NOT NULL COMMENT 'password md5', - `account_type` int(11) NOT NULL DEFAULT '1' COMMENT 'account type, 0-manager 1-normal', - `due_date` datetime DEFAULT NULL COMMENT 'due date for account', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', - `create_by` varchar(256) NOT NULL COMMENT 'create by sb.', - `update_by` varchar(256) DEFAULT NULL COMMENT 'update by sb.', + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Incremental primary key', + `name` varchar(256) NOT NULL COMMENT 'Username', + `password` varchar(64) NOT NULL COMMENT 'Password md5', + `secret_key` varchar(256) DEFAULT NULL COMMENT 'Auth key for public network access', + `public_key` text DEFAULT NULL COMMENT 'Public key for asymmetric data encryption', + `private_key` text DEFAULT NULL COMMENT 'Private key for asymmetric data encryption', + `encrypt_version` int(11) DEFAULT NULL COMMENT 'Encryption key version', + `account_type` int(11) NOT NULL DEFAULT '1' COMMENT 'Account type, 0-manager 1-normal', + `due_date` datetime DEFAULT NULL COMMENT 'Due date for account', + `create_by` varchar(256) NOT NULL COMMENT 'Creator name', + `update_by` varchar(256) DEFAULT NULL COMMENT 'Modifier name', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Modify time', PRIMARY KEY (`id`), UNIQUE KEY `unique_user_name` (`name`) ); diff --git a/inlong-manager/manager-web/sql/apache_inlong_manager.sql b/inlong-manager/manager-web/sql/apache_inlong_manager.sql index d5a94d94f..295f62081 100644 --- a/inlong-manager/manager-web/sql/apache_inlong_manager.sql +++ b/inlong-manager/manager-web/sql/apache_inlong_manager.sql @@ -587,15 +587,19 @@ CREATE TABLE IF NOT EXISTS `stream_sink_field` -- ---------------------------- CREATE TABLE IF NOT EXISTS `user` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(256) NOT NULL COMMENT 'account name', - `password` varchar(64) NOT NULL COMMENT 'password md5', - `account_type` int(11) NOT NULL DEFAULT '1' COMMENT 'account type, 0-manager 1-normal', - `due_date` datetime DEFAULT NULL COMMENT 'due date for account', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', - `create_by` varchar(256) NOT NULL COMMENT 'create by sb.', - `update_by` varchar(256) DEFAULT NULL COMMENT 'update by sb.', + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Incremental primary key', + `name` varchar(256) NOT NULL COMMENT 'Username', + `password` varchar(64) NOT NULL COMMENT 'Password md5', + `secret_key` varchar(256) DEFAULT NULL COMMENT 'Auth key for public network access', + `public_key` text DEFAULT NULL COMMENT 'Public key for asymmetric data encryption', + `private_key` text DEFAULT NULL COMMENT 'Private key for asymmetric data encryption', + `encrypt_version` int(11) DEFAULT NULL COMMENT 'Encryption key version', + `account_type` int(11) NOT NULL DEFAULT '1' COMMENT 'Account type, 0-manager 1-normal', + `due_date` datetime DEFAULT NULL COMMENT 'Due date for account', + `create_by` varchar(256) NOT NULL COMMENT 'Creator name', + `update_by` varchar(256) DEFAULT NULL COMMENT 'Modifier name', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Modify time', PRIMARY KEY (`id`), UNIQUE KEY `unique_user_name` (`name`) ) ENGINE = InnoDB diff --git a/inlong-manager/manager-web/src/main/resources/application.properties b/inlong-manager/manager-web/src/main/resources/application.properties index 6d01dc8cf..a05144673 100644 --- a/inlong-manager/manager-web/src/main/resources/application.properties +++ b/inlong-manager/manager-web/src/main/resources/application.properties @@ -54,3 +54,7 @@ common.http-client.connectionRequestTimeout=3000 # Configure auth plugin inlong.auth.type=default + +# Encryption config, the suffix of value must be the same as the version. +inlong.encrypt.version=1 +inlong.encrypt.key.value1="I!N@L#O$N%G^" diff --git a/inlong-manager/manager-web/src/test/java/org/apache/inlong/manager/web/controller/AnnoControllerTest.java b/inlong-manager/manager-web/src/test/java/org/apache/inlong/manager/web/controller/AnnoControllerTest.java index 4983fd76b..a779ba72c 100644 --- a/inlong-manager/manager-web/src/test/java/org/apache/inlong/manager/web/controller/AnnoControllerTest.java +++ b/inlong-manager/manager-web/src/test/java/org/apache/inlong/manager/web/controller/AnnoControllerTest.java @@ -70,8 +70,7 @@ class AnnoControllerTest extends WebBaseTest { Response<String> response = getResBody(mvcResult, String.class); Assertions.assertFalse(response.isSuccess()); - Assertions.assertEquals("Username or password was incorrect, or the account has expired", - response.getErrMsg()); + Assertions.assertTrue(response.getErrMsg().contains("incorrect")); } @Test @@ -117,7 +116,7 @@ class AnnoControllerTest extends WebBaseTest { Response<Boolean> resBody = getResBody(mvcResult, Boolean.class); Assertions.assertFalse(resBody.isSuccess()); - Assertions.assertEquals("User [admin] already exists", resBody.getErrMsg()); + Assertions.assertTrue(resBody.getErrMsg().contains("already exists")); } @Test diff --git a/inlong-manager/manager-web/src/main/resources/application.properties b/inlong-manager/manager-web/src/test/resources/application.properties similarity index 93% copy from inlong-manager/manager-web/src/main/resources/application.properties copy to inlong-manager/manager-web/src/test/resources/application.properties index 6d01dc8cf..a05144673 100644 --- a/inlong-manager/manager-web/src/main/resources/application.properties +++ b/inlong-manager/manager-web/src/test/resources/application.properties @@ -54,3 +54,7 @@ common.http-client.connectionRequestTimeout=3000 # Configure auth plugin inlong.auth.type=default + +# Encryption config, the suffix of value must be the same as the version. +inlong.encrypt.version=1 +inlong.encrypt.key.value1="I!N@L#O$N%G^"
