summaryrefslogtreecommitdiff
path: root/groot-bootstrap
diff options
context:
space:
mode:
authordoufenghu <[email protected]>2024-01-30 19:54:02 +0800
committerdoufenghu <[email protected]>2024-01-30 19:54:02 +0800
commitcfb60f30354f6ee1919b7606118b824b5b6ed0c5 (patch)
tree68e0ea868688794c61912978fd34ba9014b98303 /groot-bootstrap
parent4576db8dbff7754abdbbd88d592a6b8a48a921d7 (diff)
[Feature][bootstrap] Add ConfigShade interface and implement the AESConfigShade class for encrypting and decrypting sensitive configuration information, such as usename and password.
Diffstat (limited to 'groot-bootstrap')
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/AESConfigShade.java36
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/Base64ConfigShade.java28
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/CommandArgs.java18
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfDecryptCommand.java42
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfEncryptCommand.java42
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfValidateCommand.java24
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommand.java2
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommandArgs.java11
-rw-r--r--groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/utils/ConfigShadeUtils.java137
-rw-r--r--groot-bootstrap/src/main/resources/META-INF/services/com.geedgenetworks.common.config.ConfigShade2
-rw-r--r--groot-bootstrap/src/test/java/com/geedgenetworks/bootstrap/utils/ConfigShadeTest.java71
-rw-r--r--groot-bootstrap/src/test/resources/META-INF/services/com.geedgenetworks.common.config.ConfigShade2
-rw-r--r--groot-bootstrap/src/test/resources/inline_to_clickhouse.yaml55
13 files changed, 465 insertions, 5 deletions
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/AESConfigShade.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/AESConfigShade.java
new file mode 100644
index 0000000..2b5d0a4
--- /dev/null
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/AESConfigShade.java
@@ -0,0 +1,36 @@
+package com.geedgenetworks.bootstrap.command;
+
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
+import com.geedgenetworks.common.config.ConfigShade;
+import java.nio.charset.StandardCharsets;
+
+public class AESConfigShade implements ConfigShade {
+ private static final String IDENTIFIER = "aes";
+
+ private static final byte[] SECURITY_KEY =
+ SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), ".geedgenetworks.".getBytes(StandardCharsets.UTF_8)).getEncoded() ;
+
+ private static final String[] SENSITIVE_OPTIONS =
+ new String[] {"connection.user", "connection.password", "kafka.sasl.jaas.config"};
+
+ @Override
+ public String[] sensitiveOptions() {
+ return SENSITIVE_OPTIONS;
+ }
+
+ @Override
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ @Override
+ public String encrypt(String content) {
+ return SecureUtil.aes(SECURITY_KEY).encryptHex(content, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public String decrypt(String content) {
+ return SecureUtil.aes(SECURITY_KEY).decryptStr(content, StandardCharsets.UTF_8);
+ }
+}
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/Base64ConfigShade.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/Base64ConfigShade.java
new file mode 100644
index 0000000..4ae2b5c
--- /dev/null
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/Base64ConfigShade.java
@@ -0,0 +1,28 @@
+package com.geedgenetworks.bootstrap.command;
+
+import com.geedgenetworks.common.config.ConfigShade;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+public class Base64ConfigShade implements ConfigShade {
+ private static final Base64.Encoder ENCODER = Base64.getEncoder();
+
+ private static final Base64.Decoder DECODER = Base64.getDecoder();
+
+ private static final String IDENTIFIER = "base64";
+
+ @Override
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ @Override
+ public String encrypt(String content) {
+ return ENCODER.encodeToString(content.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public String decrypt(String content) {
+ return new String(DECODER.decode(content));
+ }
+}
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/CommandArgs.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/CommandArgs.java
index 2a00474..f83183c 100644
--- a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/CommandArgs.java
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/CommandArgs.java
@@ -30,16 +30,26 @@ public abstract class CommandArgs {
names = {"--check"},
description = "Whether check config")
protected boolean checkConfig = false;
+
+
@Parameter(names = {"-i", "--variable"},
- splitter = ParameterSplitter.class,
- description = "user-defined parameters , such as -i data_center=bj " +
- "We use ',' as separator, when inside \"\", ',' are treated as normal characters instead of delimiters.")
+ splitter = ParameterSplitter.class,
+ description = "user-defined parameters , such as -i data_center=bj "
+ + "We use ',' as separator, when inside \"\", ',' are treated as normal characters instead of delimiters.")
protected List<String> variables = Collections.emptyList();
@Parameter(names = {"-n", "--name"},
- description = "job name")
+ description = "job name")
protected String jobName = Constants.DEFAULT_JOB_NAME;
+ @Parameter(names = {"--encrypt"},
+ description = "Encrypt config file, when both --decrypt and --encrypt are specified, only --encrypt will take effect")
+ protected boolean encrypt = false;
+
+ @Parameter(names = {"--decrypt"},
+ description = "Decrypt config file, When both --decrypt and --encrypt are specified, only --encrypt will take effect")
+ protected boolean decrypt = false;
+
protected List<String> originalParameters = Collections.emptyList();
public abstract Command buildCommand();
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfDecryptCommand.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfDecryptCommand.java
new file mode 100644
index 0000000..1476d2e
--- /dev/null
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfDecryptCommand.java
@@ -0,0 +1,42 @@
+package com.geedgenetworks.bootstrap.command;
+
+import cn.hutool.setting.yaml.YamlUtil;
+import com.geedgenetworks.bootstrap.exception.CommandExecuteException;
+import com.geedgenetworks.bootstrap.exception.ConfigCheckException;
+import com.geedgenetworks.bootstrap.utils.ConfigShadeUtils;
+import com.typesafe.config.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+
+
+import static com.geedgenetworks.bootstrap.utils.ConfigFileUtils.checkConfigExist;
+@Slf4j
+public class ConfDecryptCommand implements Command<ExecuteCommandArgs>{
+
+ private final ExecuteCommandArgs executeCommandArgs;
+
+ public ConfDecryptCommand(ExecuteCommandArgs executeCommandArgs) {
+ this.executeCommandArgs = executeCommandArgs;
+ }
+
+ @Override
+ public void execute() throws CommandExecuteException, ConfigCheckException {
+ String decryptConfigFile = executeCommandArgs.getConfigFile();
+ Path configPath = Paths.get(decryptConfigFile);
+ checkConfigExist(configPath);
+ Map<String, Object> configMap = YamlUtil.loadByPath(configPath.toString());
+ ConfigObject configObject = ConfigValueFactory.fromMap(configMap);
+ Config config = configObject.toConfig();
+ Config decryptConfig = ConfigShadeUtils.decryptConfig(config);
+ log.info(
+ "Encrypt config: \n{}",
+ decryptConfig
+ .root()
+ .render(ConfigRenderOptions.defaults().setOriginComments(false)));
+
+
+ }
+}
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfEncryptCommand.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfEncryptCommand.java
new file mode 100644
index 0000000..dfefbca
--- /dev/null
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfEncryptCommand.java
@@ -0,0 +1,42 @@
+package com.geedgenetworks.bootstrap.command;
+
+import cn.hutool.setting.yaml.YamlUtil;
+import com.geedgenetworks.bootstrap.exception.CommandExecuteException;
+import com.geedgenetworks.bootstrap.exception.ConfigCheckException;
+import com.geedgenetworks.bootstrap.utils.ConfigShadeUtils;
+import com.typesafe.config.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import static com.geedgenetworks.bootstrap.utils.ConfigFileUtils.checkConfigExist;
+
+@Slf4j
+public class ConfEncryptCommand implements Command<ExecuteCommandArgs>{
+ private final ExecuteCommandArgs executeCommandArgs;
+
+ public ConfEncryptCommand(ExecuteCommandArgs executeCommandArgs) {
+ this.executeCommandArgs = executeCommandArgs;
+ }
+ @Override
+ public void execute() throws CommandExecuteException, ConfigCheckException {
+ if (executeCommandArgs.isDecrypt()) {
+ log.warn(
+ "When both --decrypt and --encrypt are specified, only --encrypt will take effect");
+ }
+ String encryptConfigFile = executeCommandArgs.getConfigFile();
+ Path configPath = Paths.get(encryptConfigFile);
+ Map<String, Object> configMap = YamlUtil.loadByPath(configPath.toString());
+ ConfigObject configObject = ConfigValueFactory.fromMap(configMap);
+ Config config = configObject.toConfig();
+ Config encryptConfig = ConfigShadeUtils.encryptConfig(config);
+ log.info(
+ "Encrypt config: \n{}",
+ encryptConfig
+ .root()
+ .render(ConfigRenderOptions.defaults().setOriginComments(false)));
+
+ }
+}
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfValidateCommand.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfValidateCommand.java
new file mode 100644
index 0000000..e6a9da4
--- /dev/null
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ConfValidateCommand.java
@@ -0,0 +1,24 @@
+package com.geedgenetworks.bootstrap.command;
+
+import com.geedgenetworks.bootstrap.exception.CommandExecuteException;
+import com.geedgenetworks.bootstrap.exception.ConfigCheckException;
+import com.geedgenetworks.bootstrap.utils.ConfigFileUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.file.Path;
+
+@Slf4j
+public class ConfValidateCommand implements Command<ExecuteCommandArgs> {
+ private final ExecuteCommandArgs executeCommandArgs;
+
+ public ConfValidateCommand(ExecuteCommandArgs executeCommandArgs) {
+ this.executeCommandArgs = executeCommandArgs;
+ }
+
+
+ @Override
+ public void execute() throws CommandExecuteException, ConfigCheckException {
+ Path configFile = ConfigFileUtils.getConfigPath(executeCommandArgs);
+ // todo validate config file
+ }
+}
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommand.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommand.java
index dc73bd1..17bc278 100644
--- a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommand.java
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommand.java
@@ -7,6 +7,7 @@ import com.geedgenetworks.bootstrap.exception.JobExecuteException;
import com.geedgenetworks.bootstrap.execution.ExecutionConfigKeyName;
import com.geedgenetworks.bootstrap.execution.JobExecution;
import com.geedgenetworks.bootstrap.utils.ConfigFileUtils;
+import com.geedgenetworks.bootstrap.utils.ConfigShadeUtils;
import com.geedgenetworks.common.Constants;
import com.geedgenetworks.common.config.ConfigProvider;
import com.geedgenetworks.common.config.GrootStreamConfig;
@@ -35,6 +36,7 @@ public class ExecuteCommand implements Command<ExecuteCommandArgs> {
Map<String, Object> configMap = YamlUtil.loadByPath(configFile.toString());
ConfigObject configObject = ConfigValueFactory.fromMap(configMap);
Config config = configObject.toConfig();
+ config = ConfigShadeUtils.decryptConfig(config);
// if user specified job name using command line arguments, override config option
if (!executeCommandArgs.getJobName().equals(Constants.DEFAULT_JOB_NAME)) {
config = config.withValue(
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommandArgs.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommandArgs.java
index a6b2dd6..a6ccadc 100644
--- a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommandArgs.java
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/command/ExecuteCommandArgs.java
@@ -35,8 +35,17 @@ public class ExecuteCommandArgs extends CommandArgs {
StartBuilder.setDeployMode(getDeployMode());
userParamsToSysEnv();
if(checkConfig) {
- // todo check job config
+ return new ConfValidateCommand(this);
}
+
+ if (encrypt) {
+ return new ConfEncryptCommand(this);
+ }
+
+ if (decrypt) {
+ return new ConfDecryptCommand(this);
+ }
+
return new ExecuteCommand(this);
}
diff --git a/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/utils/ConfigShadeUtils.java b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/utils/ConfigShadeUtils.java
new file mode 100644
index 0000000..3e6663e
--- /dev/null
+++ b/groot-bootstrap/src/main/java/com/geedgenetworks/bootstrap/utils/ConfigShadeUtils.java
@@ -0,0 +1,137 @@
+package com.geedgenetworks.bootstrap.utils;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.geedgenetworks.common.Constants;
+import com.geedgenetworks.common.config.ConfigShade;
+import com.geedgenetworks.common.config.TypesafeConfigUtils;
+import com.google.common.base.Preconditions;
+import com.typesafe.config.*;
+import lombok.extern.slf4j.Slf4j;
+import java.util.*;
+import java.util.function.BiFunction;
+
+/** Config shade utilities */
+@Slf4j
+public final class ConfigShadeUtils {
+
+ private static final String SHADE_IDENTIFIER_OPTION = "shade.identifier";
+
+ private static final String[] DEFAULT_SENSITIVE_OPTIONS =
+ new String[] {"password", "username", "auth"};
+
+ private static final Map<String, ConfigShade> CONFIG_SHADES = new HashMap<>();
+
+ private static final ConfigShade DEFAULT_SHADE = new DefaultConfigShade();
+
+ static {
+ ServiceLoader<ConfigShade> serviceLoader = ServiceLoader.load(ConfigShade.class);
+ Iterator<ConfigShade> it = serviceLoader.iterator();
+ it.forEachRemaining(
+ configShade -> {
+ CONFIG_SHADES.put(configShade.getIdentifier(), configShade);
+ });
+ log.info("Load config shade: {}", CONFIG_SHADES.keySet());
+ }
+
+ public static String encryptOption(String identifier, String content) {
+ ConfigShade configShade = CONFIG_SHADES.getOrDefault(identifier, DEFAULT_SHADE);
+ return configShade.encrypt(content);
+ }
+
+ public static String decryptOption(String identifier, String content) {
+ ConfigShade configShade = CONFIG_SHADES.getOrDefault(identifier, DEFAULT_SHADE);
+ return configShade.decrypt(content);
+ }
+
+ public static Config decryptConfig(Config config) {
+ String identifier =
+ TypesafeConfigUtils.getConfig(
+ config.hasPath(ConfigUtil.joinPath(Constants.APPLICATION, Constants.APPLICATION_ENV))
+ ? config.getConfig(ConfigUtil.joinPath(Constants.APPLICATION, Constants.APPLICATION_ENV))
+ : ConfigFactory.empty(),
+ ConfigUtil.joinPath(SHADE_IDENTIFIER_OPTION),
+ DEFAULT_SHADE.getIdentifier());
+ return decryptConfig(identifier, config);
+ }
+
+ public static Config encryptConfig(Config config) {
+ String identifier =
+ TypesafeConfigUtils.getConfig(
+ config.hasPath(ConfigUtil.joinPath(Constants.APPLICATION, Constants.APPLICATION_ENV))
+ ? config.getConfig(ConfigUtil.joinPath(Constants.APPLICATION, Constants.APPLICATION_ENV))
+ : ConfigFactory.empty(),
+ ConfigUtil.joinPath(SHADE_IDENTIFIER_OPTION),
+ DEFAULT_SHADE.getIdentifier());
+ return encryptConfig(identifier, config);
+ }
+
+ public static Config decryptConfig(String identifier, Config config) {
+ return processConfig(identifier, config, true);
+ }
+
+ public static Config encryptConfig(String identifier, Config config) {
+ return processConfig(identifier, config, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Config processConfig(String identifier, Config config, boolean isDecrypted) {
+ ConfigShade configShade = CONFIG_SHADES.getOrDefault(identifier, DEFAULT_SHADE);
+ List<String> sensitiveOptions = new ArrayList<>(Arrays.asList(DEFAULT_SENSITIVE_OPTIONS));
+ sensitiveOptions.addAll(Arrays.asList(configShade.sensitiveOptions()));
+ BiFunction<String, Object, String> processFunction =
+ (key, value) -> {
+ if (isDecrypted) {
+ return configShade.decrypt(value.toString());
+ } else {
+ return configShade.encrypt(value.toString());
+ }
+ };
+ String jsonString = config.root().render(ConfigRenderOptions.concise());
+ JSONObject configMap = JSON.parseObject(jsonString);
+ JSONObject sources = configMap.getJSONObject(Constants.SOURCES);
+ JSONObject sinks = configMap.getJSONObject(Constants.SINKS);
+ Preconditions.checkArgument(
+ !sources.isEmpty(), "Miss <Source> config! Please check the config file.");
+ Preconditions.checkArgument(
+ !sinks.isEmpty(), "Miss <Sink> config! Please check the config file.");
+
+ sources.values().forEach((value) -> {
+ JSONObject source = (JSONObject) value;
+ JSONObject propertiesObject = source.getJSONObject("properties");
+ for (String sensitiveOption : sensitiveOptions) {
+ propertiesObject.computeIfPresent(sensitiveOption, processFunction);
+ }
+ });
+
+ sinks.values().forEach((value) -> {
+ JSONObject sink = (JSONObject) value;
+ JSONObject propertiesObject = sink.getJSONObject("properties");
+ for (String sensitiveOption : sensitiveOptions) {
+ propertiesObject.computeIfPresent(sensitiveOption, processFunction);
+ }
+ });
+ return ConfigValueFactory.fromMap(configMap).toConfig();
+ }
+
+
+ private static class DefaultConfigShade implements ConfigShade {
+ private static final String IDENTIFIER = "default";
+
+ @Override
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ @Override
+ public String encrypt(String content) {
+ return content;
+ }
+
+ @Override
+ public String decrypt(String content) {
+ return content;
+ }
+ }
+
+}
diff --git a/groot-bootstrap/src/main/resources/META-INF/services/com.geedgenetworks.common.config.ConfigShade b/groot-bootstrap/src/main/resources/META-INF/services/com.geedgenetworks.common.config.ConfigShade
new file mode 100644
index 0000000..6654db5
--- /dev/null
+++ b/groot-bootstrap/src/main/resources/META-INF/services/com.geedgenetworks.common.config.ConfigShade
@@ -0,0 +1,2 @@
+com.geedgenetworks.bootstrap.command.Base64ConfigShade
+com.geedgenetworks.bootstrap.command.AESConfigShade \ No newline at end of file
diff --git a/groot-bootstrap/src/test/java/com/geedgenetworks/bootstrap/utils/ConfigShadeTest.java b/groot-bootstrap/src/test/java/com/geedgenetworks/bootstrap/utils/ConfigShadeTest.java
new file mode 100644
index 0000000..ccdd224
--- /dev/null
+++ b/groot-bootstrap/src/test/java/com/geedgenetworks/bootstrap/utils/ConfigShadeTest.java
@@ -0,0 +1,71 @@
+package com.geedgenetworks.bootstrap.utils;
+
+import cn.hutool.setting.yaml.YamlUtil;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.geedgenetworks.common.Constants;
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigObject;
+import com.typesafe.config.ConfigRenderOptions;
+import com.typesafe.config.ConfigValueFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.Map;
+
+@Slf4j
+public class ConfigShadeTest {
+
+ private static final String USERNAME = "grootstream";
+
+ private static final String PASSWORD = "grootstream_password";
+
+ @Test
+ public void testParseConfig() throws URISyntaxException {
+ URL resource = ConfigShadeTest.class.getResource("/inline_to_clickhouse.yaml");
+ Assertions.assertNotNull(resource);
+ Map<String, Object> configMap = YamlUtil.loadByPath(Paths.get(resource.toURI()).toString());
+ ConfigObject configObject = ConfigValueFactory.fromMap(configMap);
+ Config decryptConfig = ConfigShadeUtils.decryptConfig(configObject.toConfig());
+ String jsonString = decryptConfig.root().render(ConfigRenderOptions.concise());
+ JSONObject jsonObject = JSON.parseObject(jsonString);
+ JSONObject sinkObject = jsonObject.getJSONObject(Constants.SINKS);
+ log.info("Decrypt config: {}", decryptConfig.root().render(ConfigRenderOptions.concise()));
+ Assertions.assertEquals(sinkObject.keySet().size(), 1);
+ Assertions.assertNotNull(sinkObject);
+ Assertions.assertNotNull(sinkObject.getJSONObject("clickhouse_sink"));
+ Assertions.assertNotNull(sinkObject.getJSONObject("clickhouse_sink")
+ .getJSONObject("properties"));
+ Assertions.assertEquals(sinkObject.getJSONObject("clickhouse_sink")
+ .getJSONObject("properties").isEmpty(), false);
+ Assertions.assertEquals(sinkObject.getJSONObject("clickhouse_sink")
+ .getJSONObject("properties").get("connection.user"),USERNAME);
+ Assertions.assertNotNull(sinkObject.getJSONObject("clickhouse_sink")
+ .getJSONObject("properties").get("connection.password"), PASSWORD);
+ }
+
+ @Test
+ public void testDecryptAndEncrypt() {
+ String encryptUsername = ConfigShadeUtils.encryptOption("base64", USERNAME);
+ String decryptUsername = ConfigShadeUtils.decryptOption("base64", encryptUsername);
+ String encryptPassword = ConfigShadeUtils.encryptOption("base64", PASSWORD);
+ String decryptPassword = ConfigShadeUtils.decryptOption("base64", encryptPassword);
+ Assertions.assertEquals("Z3Jvb3RzdHJlYW0=", encryptUsername);
+ Assertions.assertEquals("Z3Jvb3RzdHJlYW1fcGFzc3dvcmQ=", encryptPassword);
+ Assertions.assertEquals(decryptUsername, USERNAME);
+ Assertions.assertEquals(decryptPassword, PASSWORD);
+ encryptUsername = ConfigShadeUtils.encryptOption("aes", USERNAME);
+ decryptUsername = ConfigShadeUtils.decryptOption("aes", encryptUsername);
+ encryptPassword = ConfigShadeUtils.encryptOption("aes", PASSWORD);
+ decryptPassword = ConfigShadeUtils.decryptOption("aes", encryptPassword);
+ Assertions.assertEquals("ed986337dfdbe341be1d29702e6ae619", encryptUsername);
+ Assertions.assertEquals("159c7da83d988a9ec041d10a6bfbe221bcbaed6b62d9cc1b04ff51e633ebd105", encryptPassword);
+ Assertions.assertEquals(decryptUsername, USERNAME);
+ Assertions.assertEquals(decryptPassword, PASSWORD);
+ System.out.println( ConfigShadeUtils.encryptOption("aes", "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"admin\" password=\"galaxy2019\";"));
+ System.out.println( ConfigShadeUtils.decryptOption("aes", "454f65ea6eef1256e3067104f82730e737b68959560966b811e7ff364116b03124917eb2b0f3596f14733aa29ebad9352644ce1a5c85991c6f01ba8a5e8f177a7ff0b2d3889a424249967b3870b50993d9644f239f0de82cdb13bdb502959e16afadffa49ef1e1d2b9c9b5113e619817"));
+ }
+}
diff --git a/groot-bootstrap/src/test/resources/META-INF/services/com.geedgenetworks.common.config.ConfigShade b/groot-bootstrap/src/test/resources/META-INF/services/com.geedgenetworks.common.config.ConfigShade
new file mode 100644
index 0000000..6654db5
--- /dev/null
+++ b/groot-bootstrap/src/test/resources/META-INF/services/com.geedgenetworks.common.config.ConfigShade
@@ -0,0 +1,2 @@
+com.geedgenetworks.bootstrap.command.Base64ConfigShade
+com.geedgenetworks.bootstrap.command.AESConfigShade \ No newline at end of file
diff --git a/groot-bootstrap/src/test/resources/inline_to_clickhouse.yaml b/groot-bootstrap/src/test/resources/inline_to_clickhouse.yaml
new file mode 100644
index 0000000..9038cc8
--- /dev/null
+++ b/groot-bootstrap/src/test/resources/inline_to_clickhouse.yaml
@@ -0,0 +1,55 @@
+sources: # [object] Define connector source
+ inline_source:
+ type: inline
+ fields: # [array of object] Schema field projection, support read data only from specified fields.
+ - name: log_id
+ type: bigint
+ - name: recv_time
+ type: bigint
+ - name: server_fqdn
+ type: string
+ - name: server_domain
+ type: string
+ - name: client_ip
+ type: string
+ - name: server_ip
+ type: string
+ - name: server_asn
+ type: string
+ - name: decoded_as
+ type: string
+ - name: device_group
+ type: string
+ - name: device_tag
+ type: string
+ properties:
+ #
+ # [string] Event Data, it will be parsed to Map<String, Object> by the specified format.
+ #
+ data: '{"recv_time": 1705565615, "log_id":206211012872372224, "tcp_rtt_ms":128,"decoded_as":"HTTP", "http_version":"http1","http_request_line":"GET / HTTP/1.1","http_host":"www.ct.cn","http_url":"www.ct.cn/","http_user_agent":"curl/8.0.1","http_status_code":200,"http_response_line":"HTTP/1.1 200 OK","http_response_content_type":"text/html; charset=UTF-8","http_response_latency_ms":31,"http_session_duration_ms":5451,"in_src_mac":"ba:bb:a7:3c:67:1c","in_dest_mac":"86:dd:7a:8f:ae:e2","out_src_mac":"86:dd:7a:8f:ae:e2","out_dest_mac":"ba:bb:a7:3c:67:1c","tcp_client_isn":678677906,"tcp_server_isn":1006700307,"address_type":4,"client_ip":"192.11.22.22","server_ip":"8.8.8.8","client_port":42751,"server_port":80,"in_link_id":65535,"out_link_id":65535,"start_timestamp_ms":1703646546127,"end_timestamp_ms":1703646551702,"duration_ms":5575,"sent_pkts":97,"sent_bytes":5892,"received_pkts":250,"received_bytes":333931,"tcp_c2s_ip_fragments":0,"tcp_s2c_ip_fragments":0,"tcp_c2s_rtx_pkts":0,"tcp_c2s_rtx_bytes":0,"tcp_s2c_rtx_pkts":0,"tcp_s2c_rtx_bytes":0,"tcp_c2s_o3_pkts":0,"tcp_s2c_o3_pkts":0,"tcp_c2s_lost_bytes":0,"tcp_s2c_lost_bytes":0,"flags":26418,"flags_identify_info":[100,1,100,60,150,100,1,2],"app_transition":"http.1111.test_1_1","decoded_as":"HTTP","server_fqdn":"www.ct.cn","app":"test_1_1","decoded_path":"ETHERNET.IPv4.TCP.http","fqdn_category_list":[1767],"t_vsys_id":1,"vsys_id":1,"session_id":290538039798223400,"tcp_handshake_latency_ms":41,"client_os_desc":"Windows","server_os_desc":"Linux","data_center":"center-xxg-tsgx","device_group":"group-xxg-tsgx","device_tag":"{\"tags\":[{\"tag\":\"data_center\",\"value\":\"center-xxg-tsgx\"},{\"tag\":\"device_group\",\"value\":\"group-xxg-tsgx\"}]}","device_id":"9800165603247024","sled_ip":"192.168.40.39","dup_traffic_flag":0}'
+ format: json
+ json.ignore.parse.errors: false
+
+sinks:
+ clickhouse_sink:
+ type: clickhouse
+ properties:
+ host: 192.168.44.12:9001
+ table: tsg_galaxy_v3.inline_source_test_local
+ batch.size: 10
+ batch.interval: 1s
+ connection.user: ed986337dfdbe341be1d29702e6ae619
+ connection.password: 159c7da83d988a9ec041d10a6bfbe221bcbaed6b62d9cc1b04ff51e633ebd105
+
+application: # [object] Define job configuration
+ env:
+ name: example-inline-to-clickhouse
+ parallelism: 3
+ shade.identifier: aes
+ pipeline:
+ object-reuse: true
+ topology:
+ - name: inline_source
+ downstream: [clickhouse_sink]
+ - name: clickhouse_sink
+ downstream: [] \ No newline at end of file