Skip to content

Commit f352f20

Browse files
author
wanqifei
committed
[Feature][Config] Support custom config keys for encrypt/decrypt
1 parent eb70674 commit f352f20

File tree

9 files changed

+117
-30
lines changed

9 files changed

+117
-30
lines changed

docs/en/connector-v2/Config-Encryption-Decryption.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@ In most production environments, sensitive configuration items such as passwords
88

99
SeaTunnel comes with the function of base64 encryption and decryption, but it is not recommended for production use, it is recommended that users implement custom encryption and decryption logic. You can refer to this chapter [How to implement user-defined encryption and decryption](#How to implement user-defined encryption and decryption) get more details about it.
1010

11-
Base64 encryption support encrypt the following parameters:
11+
Base64 encryption support encrypt the following parameters by default:
1212
- username
1313
- password
1414
- auth
1515
- token
1616
- access_key
1717
- secret_key
1818

19+
And users can add custom parameters to `shade.options` for encryption and decryption.
20+
1921
Next, I'll show how to quickly use SeaTunnel's own `base64` encryption:
2022

21-
1. And a new option `shade.identifier` in env block of config file, this option indicate what the encryption method that you want to use, in this example, we should add `shade.identifier = base64` in config as the following shown:
23+
1. And new option `shade.identifier` and `shade.options` in env block of config file, `shade.identifier` indicate what the encryption method that you want to use, while `shade.options` specifies which parameters should be encrypted/decrypted. In this example, we should add `shade.identifier = base64` in config as the following shown:
2224

2325
```hocon
2426
#
@@ -41,6 +43,7 @@ Next, I'll show how to quickly use SeaTunnel's own `base64` encryption:
4143
env {
4244
parallelism = 1
4345
shade.identifier = "base64"
46+
shade.options = ["username", "password", "f1", "config1.f1", "config2.list"]
4447
}
4548
4649
source {
@@ -55,6 +58,10 @@ Next, I'll show how to quickly use SeaTunnel's own `base64` encryption:
5558
database-name = "inventory_vwyw0n"
5659
table-name = "products"
5760
base-url = "jdbc:mysql://localhost:56725"
61+
f1 = "seatunnel"
62+
# custom shade options
63+
config1.f1 = "seatunnel"
64+
config2.list = ["seatunnel", "seatunnel", "seatunnel"]
5865
}
5966
}
6067
@@ -103,7 +110,9 @@ Next, I'll show how to quickly use SeaTunnel's own `base64` encryption:
103110
"table-name" : "products",
104111
"plugin_name" : "MySQL-CDC",
105112
"server-id" : 5656,
106-
"username" : "c2VhdHVubmVs"
113+
"username" : "c2VhdHVubmVs",
114+
"config1.f1": "c2VhdHVubmVs",
115+
"config2.list": ["c2VhdHVubmVs","c2VhdHVubmVs","c2VhdHVubmVs"]
107116
}
108117
],
109118
"transform" : [],

seatunnel-common/src/main/java/org/apache/seatunnel/common/config/TypesafeConfigUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public static <T> T getConfig(
7777
? (T) Boolean.valueOf(config.getString(configKey))
7878
: defaultValue;
7979
}
80-
if (defaultValue instanceof Map) {
80+
if (defaultValue instanceof Map || defaultValue instanceof List) {
8181
return config.hasPath(configKey) ? (T) config.getAnyRef(configKey) : defaultValue;
8282
}
8383
throw new RuntimeException("Unsupported config type, configKey: " + configKey);

seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/command/ConfDecryptCommand.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ public void execute() throws CommandExecuteException, ConfigCheckException {
5353
.resolveWith(
5454
ConfigFactory.systemProperties(),
5555
ConfigResolveOptions.defaults().setAllowUnresolved(true));
56-
Config encryptConfig = ConfigShadeUtils.decryptConfig(config);
56+
Config decryptConfig = ConfigShadeUtils.decryptConfig(config);
5757
log.info(
58-
"Encrypt config: \n{}",
59-
encryptConfig
58+
"Decrypt config: \n{}",
59+
decryptConfig
6060
.root()
6161
.render(ConfigRenderOptions.defaults().setOriginComments(false)));
6262
}

seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/utils/ConfigBuilder.java

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,17 @@
3737
import java.nio.file.Path;
3838
import java.nio.file.Paths;
3939
import java.util.ArrayList;
40-
import java.util.Arrays;
4140
import java.util.LinkedHashMap;
4241
import java.util.List;
4342
import java.util.Map;
4443
import java.util.Objects;
4544
import java.util.Optional;
45+
import java.util.Set;
4646
import java.util.regex.Matcher;
4747
import java.util.regex.Pattern;
4848
import java.util.stream.Collectors;
4949

5050
import static org.apache.seatunnel.common.utils.PlaceholderUtils.replacePlaceholders;
51-
import static org.apache.seatunnel.core.starter.utils.ConfigShadeUtils.DEFAULT_SENSITIVE_KEYWORDS;
5251

5352
/** Used to build the {@link Config} from config file. */
5453
@Slf4j
@@ -91,10 +90,12 @@ public static Config of(@NonNull Path filePath, List<String> variables) {
9190
adapterSupplier
9291
.map(adapter -> of(adapter, filePath, variables))
9392
.orElseGet(() -> ofInner(filePath, variables));
94-
boolean isJson = filePath.getFileName().toString().endsWith(".json");
9593
log.info(
9694
"Parsed config file: \n{}",
97-
mapToString(configDesensitization(config.root().unwrapped())));
95+
mapToString(
96+
configDesensitization(
97+
config.root().unwrapped(),
98+
ConfigShadeUtils.getSensitiveOptions(config))));
9899
return config;
99100
}
100101

@@ -116,23 +117,39 @@ public static Config of(
116117
}
117118
log.info(
118119
"Parsed config file: \n{}",
119-
mapToString(configDesensitization(config.root().unwrapped())));
120+
mapToString(
121+
configDesensitization(
122+
config.root().unwrapped(),
123+
ConfigShadeUtils.getSensitiveOptions(config))));
120124
return config;
121125
}
122126

123-
public static Map<String, Object> configDesensitization(Map<String, Object> configMap) {
127+
public static Map<String, Object> configDesensitization(
128+
Map<String, Object> configMap, Set<String> sensitiveKeywords) {
124129
return configMap.entrySet().stream()
125130
.collect(
126131
LinkedHashMap::new,
127132
(m, p) -> {
128133
String key = p.getKey();
129134
Object value = p.getValue();
130-
if (Arrays.asList(DEFAULT_SENSITIVE_KEYWORDS)
131-
.contains(key.toLowerCase())) {
132-
m.put(key, "******");
135+
if (sensitiveKeywords.contains(key.toLowerCase())) {
136+
if (value instanceof List<?>) {
137+
List<Object> maskedList =
138+
((List<?>) value)
139+
.stream()
140+
.map(v -> "******")
141+
.collect(Collectors.toList());
142+
m.put(key, maskedList);
143+
} else {
144+
m.put(key, "******");
145+
}
133146
} else {
134147
if (value instanceof Map<?, ?>) {
135-
m.put(key, configDesensitization((Map<String, Object>) value));
148+
m.put(
149+
key,
150+
configDesensitization(
151+
(Map<String, Object>) value,
152+
sensitiveKeywords));
136153
} else if (value instanceof List<?>) {
137154
List<?> listValue = (List<?>) value;
138155
List<Object> newList =
@@ -141,8 +158,8 @@ public static Map<String, Object> configDesensitization(Map<String, Object> conf
141158
v -> {
142159
if (v instanceof Map<?, ?>) {
143160
return configDesensitization(
144-
(Map<String, Object>)
145-
v);
161+
(Map<String, Object>) v,
162+
sensitiveKeywords);
146163
} else {
147164
return v;
148165
}

seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/utils/ConfigShadeUtils.java

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@
3535
import java.util.Arrays;
3636
import java.util.Base64;
3737
import java.util.HashMap;
38+
import java.util.HashSet;
3839
import java.util.Iterator;
3940
import java.util.List;
4041
import java.util.Map;
4142
import java.util.ServiceLoader;
43+
import java.util.Set;
4244
import java.util.function.BiFunction;
4345

4446
/** Config shade utilities */
@@ -47,6 +49,7 @@ public final class ConfigShadeUtils {
4749

4850
private static final String SHADE_IDENTIFIER_OPTION = "shade.identifier";
4951
private static final String SHADE_PROPS_OPTION = "shade.properties";
52+
private static final String SHADE_OPTIONS_OPTION = "shade.options";
5053

5154
public static final String[] DEFAULT_SENSITIVE_KEYWORDS =
5255
new String[] {"password", "username", "auth", "token", "access_key", "secret_key"};
@@ -147,14 +150,24 @@ private static Config processConfig(
147150
// call open method before the encrypt/decrypt
148151
configShade.open(props);
149152

150-
List<String> sensitiveOptions = new ArrayList<>(Arrays.asList(DEFAULT_SENSITIVE_KEYWORDS));
153+
Set<String> sensitiveOptions = new HashSet<>(getSensitiveOptions(config));
151154
sensitiveOptions.addAll(Arrays.asList(configShade.sensitiveOptions()));
152-
BiFunction<String, Object, String> processFunction =
155+
BiFunction<String, Object, Object> processFunction =
153156
(key, value) -> {
154-
if (isDecrypted) {
155-
return configShade.decrypt(value.toString());
157+
if (value instanceof List) {
158+
List<String> list = (List<String>) value;
159+
List<String> processedList = new ArrayList<>();
160+
for (String element : list) {
161+
processedList.add(
162+
isDecrypted
163+
? configShade.decrypt(element)
164+
: configShade.encrypt(element));
165+
}
166+
return processedList;
156167
} else {
157-
return configShade.encrypt(value.toString());
168+
return isDecrypted
169+
? configShade.decrypt((String) value)
170+
: configShade.encrypt((String) value);
158171
}
159172
};
160173
String jsonString = config.root().render(ConfigRenderOptions.concise());
@@ -185,6 +198,19 @@ private static Config processConfig(
185198
return ConfigFactory.parseMap(configMap);
186199
}
187200

201+
public static Set<String> getSensitiveOptions(Config config) {
202+
Set<String> sensitiveOptions =
203+
new HashSet<>(
204+
TypesafeConfigUtils.getConfig(
205+
config != null && config.hasPath(Constants.ENV)
206+
? config.getConfig(Constants.ENV)
207+
: ConfigFactory.empty(),
208+
SHADE_OPTIONS_OPTION,
209+
new ArrayList<>()));
210+
sensitiveOptions.addAll(new ArrayList<>(Arrays.asList(DEFAULT_SENSITIVE_KEYWORDS)));
211+
return sensitiveOptions;
212+
}
213+
188214
public static class Base64ConfigShade implements ConfigShade {
189215

190216
private static final Base64.Encoder ENCODER = Base64.getEncoder();

seatunnel-core/seatunnel-core-starter/src/test/java/org/apache/seatunnel/core/starter/utils/ConfigBuilderTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ public void testConfigDesensitizationSort() {
3838
config.put("e", "1");
3939
config.put("f", "1");
4040

41-
Map<String, Object> desensitizationConfig = ConfigBuilder.configDesensitization(config);
41+
Map<String, Object> desensitizationConfig =
42+
ConfigBuilder.configDesensitization(
43+
config, ConfigShadeUtils.getSensitiveOptions(null));
4244
List<String> keys = new ArrayList<>(desensitizationConfig.keySet());
4345
Assertions.assertIterableEquals(Arrays.asList("a", "b", "c", "d", "e", "f"), keys);
4446
}

seatunnel-core/seatunnel-core-starter/src/test/java/org/apache/seatunnel/core/starter/utils/ConfigShadeTest.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.nio.charset.StandardCharsets;
4040
import java.nio.file.Paths;
4141
import java.util.ArrayList;
42+
import java.util.Arrays;
4243
import java.util.Base64;
4344
import java.util.List;
4445
import java.util.Map;
@@ -88,7 +89,9 @@ public void testUsePrivacyHandlerHocon() throws URISyntaxException {
8889
Config config = ConfigBuilder.of(Paths.get(resource.toURI()), Lists.newArrayList());
8990
config =
9091
ConfigFactory.parseMap(
91-
ConfigBuilder.configDesensitization(config.root().unwrapped()))
92+
ConfigBuilder.configDesensitization(
93+
config.root().unwrapped(),
94+
ConfigShadeUtils.getSensitiveOptions(config)))
9295
.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true))
9396
.resolveWith(
9497
ConfigFactory.systemProperties(),
@@ -101,6 +104,12 @@ public void testUsePrivacyHandlerHocon() throws URISyntaxException {
101104
config.getConfigList("source").get(0).getString("access_key"), "******");
102105
Assertions.assertEquals(
103106
config.getConfigList("source").get(0).getString("secret_key"), "******");
107+
Assertions.assertEquals(config.getConfigList("source").get(0).getString("f1"), "******");
108+
Assertions.assertEquals(
109+
config.getConfigList("source").get(0).getString("config1.f1"), "******");
110+
Assertions.assertEquals(
111+
config.getConfigList("source").get(0).getStringList("config2.list"),
112+
Arrays.asList("******", "******", "******"));
104113
String conf = ConfigBuilder.mapToString(config.root().unwrapped());
105114
Assertions.assertTrue(conf.contains("\"password\" : \"******\""));
106115
}
@@ -112,7 +121,9 @@ public void testUsePrivacyHandlerJson() throws URISyntaxException {
112121
Config config = ConfigBuilder.of(Paths.get(resource.toURI()), Lists.newArrayList());
113122
config =
114123
ConfigFactory.parseMap(
115-
ConfigBuilder.configDesensitization(config.root().unwrapped()))
124+
ConfigBuilder.configDesensitization(
125+
config.root().unwrapped(),
126+
ConfigShadeUtils.getSensitiveOptions(config)))
116127
.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true))
117128
.resolveWith(
118129
ConfigFactory.systemProperties(),
@@ -121,6 +132,13 @@ public void testUsePrivacyHandlerJson() throws URISyntaxException {
121132
config.getConfigList("source").get(0).getString("username"), "******");
122133
Assertions.assertEquals(
123134
config.getConfigList("source").get(0).getString("password"), "******");
135+
Assertions.assertEquals(config.getConfigList("source").get(0).getString("f1"), "******");
136+
Assertions.assertEquals(
137+
config.getConfigList("source").get(0).getString("config1.f1"), "******");
138+
Assertions.assertEquals(
139+
config.getConfigList("source").get(0).getStringList("config2.list"),
140+
Arrays.asList("******", "******", "******"));
141+
String conf = ConfigBuilder.mapToString(config.root().unwrapped());
124142
String json = ConfigBuilder.mapToString(config.root().unwrapped());
125143
Assertions.assertTrue(json.contains("\"password\" : \"******\""));
126144
}
@@ -132,7 +150,9 @@ public void testConfNull() throws URISyntaxException {
132150
Config config = ConfigBuilder.of(Paths.get(resource.toURI()), Lists.newArrayList());
133151
config =
134152
ConfigFactory.parseMap(
135-
ConfigBuilder.configDesensitization(config.root().unwrapped()))
153+
ConfigBuilder.configDesensitization(
154+
config.root().unwrapped(),
155+
ConfigShadeUtils.getSensitiveOptions(config)))
136156
.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true))
137157
.resolveWith(
138158
ConfigFactory.systemProperties(),

seatunnel-core/seatunnel-core-starter/src/test/resources/config.shade.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
env {
1919
parallelism = 1
2020
shade.identifier = "base64"
21+
shade.options = ["username", "password", "f1", "config1.f1", "config2.list", "f2"]
2122
}
2223

2324
source {
@@ -43,6 +44,11 @@ source {
4344
# test properties
4445
access_key = "YWNjZXNzX2tleQ=="
4546
secret_key = "c2VjcmV0X2tleQ=="
47+
48+
# test shade options
49+
f1 = "c2VhdHVubmVs"
50+
config1.f1 = "c2VhdHVubmVs"
51+
config2.list = ["c2VhdHVubmVsX3Bhc3N3b3Jk", "c2VhdHVubmVsX3Bhc3N3b3Jk", "c2VhdHVubmVsX3Bhc3N3b3Jk"]
4652
}
4753
}
4854

seatunnel-core/seatunnel-core-starter/src/test/resources/config.shade.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"env" : {
33
"shade.identifier" : "base64",
4-
"parallelism" : 1
4+
"parallelism" : 1,
5+
"shade.options": ["username", "password", "f1", "config1.f1", "config2.list", "f2"]
56
},
67
"source" : [
78
{
@@ -22,7 +23,13 @@
2223
"sex" : "boolean"
2324
}
2425
},
25-
"plugin_output" : "fake"
26+
"plugin_output" : "fake",
27+
"f1": "c2VhdHVubmVs",
28+
"config1.f1": "c2VhdHVubmVs",
29+
"config2.list": ["c2VhdHVubmVsX3Bhc3N3b3Jk", "c2VhdHVubmVsX3Bhc3N3b3Jk", "c2VhdHVubmVsX3Bhc3N3b3Jk"],
30+
"config3": {
31+
"f2": "c2VhdHVubmVs"
32+
}
2633
}
2734
],
2835
"transform" : [],

0 commit comments

Comments
 (0)