This is an automated email from the ASF dual-hosted git repository.

arshad pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/seatunnel-web.git


The following commit(s) were added to refs/heads/main by this push:
     new 46c8a841 [Feature] Add Support for encrypting config fields (#278)
46c8a841 is described below

commit 46c8a841ad29967fe670d77dc37ece40b467f23a
Author: Shashwat Tiwari <shati...@visa.com>
AuthorDate: Tue Feb 11 14:47:00 2025 +0530

    [Feature] Add Support for encrypting config fields (#278)
    
    Co-authored-by: BilwaST <stbi...@gmail.com>
---
 .../org/apache/seatunnel/app/common/Constants.java |  2 +
 .../seatunnel/app/config/EncryptionConfig.java     | 36 +++++++++
 .../app/service/impl/DatasourceServiceImpl.java    | 19 ++++-
 .../app/service/impl/JobInstanceServiceImpl.java   | 18 +++++
 .../seatunnel/app/utils/ConfigShadeUtil.java       | 89 ++++++++++++++++++++++
 .../src/main/resources/application.yml             |  8 +-
 .../src/test/resources/application.yml             |  8 ++
 7 files changed, 177 insertions(+), 3 deletions(-)

diff --git 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java
 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java
index 886c1f70..22c01834 100644
--- 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java
+++ 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java
@@ -215,6 +215,7 @@ public final class Constants {
     public static final String COMMON_TASK_TYPE = "common";
 
     public static final String DEFAULT = "default";
+    public static final String ENCRYPTION_TYPE_NONE = "none";
     public static final String PASSWORD = "password";
     public static final String XXXXXX = "******";
     public static final String NULL = "NULL";
@@ -658,4 +659,5 @@ public final class Constants {
 
     public static final String AUTHENTICATION_PROVIDER_LDAP = "LDAP";
     public static final String AUTHENTICATION_PROVIDER_DB = "DB";
+    public static final String ENCRYPTION_IDENTIFIER_KEY = "shade.identifier";
 }
diff --git 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/EncryptionConfig.java
 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/EncryptionConfig.java
new file mode 100644
index 00000000..4e887321
--- /dev/null
+++ 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/EncryptionConfig.java
@@ -0,0 +1,36 @@
+/*
+ * 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.seatunnel.app.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import lombok.Data;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.apache.seatunnel.app.common.Constants.ENCRYPTION_TYPE_NONE;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "seatunnel-web.datasource.encryption")
+public class EncryptionConfig {
+    private String type = ENCRYPTION_TYPE_NONE;
+    private Set<String> keysToEncrypt = new HashSet<>();
+}
diff --git 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java
 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java
index 12b1a4f1..ce9cbc65 100644
--- 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java
+++ 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java
@@ -37,6 +37,7 @@ import org.apache.seatunnel.app.service.IJobDefinitionService;
 import org.apache.seatunnel.app.service.ITableSchemaService;
 import org.apache.seatunnel.app.thirdparty.datasource.DataSourceClientFactory;
 import 
org.apache.seatunnel.app.thirdparty.framework.SeaTunnelOptionRuleWrapper;
+import org.apache.seatunnel.app.utils.ConfigShadeUtil;
 import org.apache.seatunnel.app.utils.ServletUtils;
 import org.apache.seatunnel.common.utils.JsonUtils;
 import org.apache.seatunnel.datasource.plugin.api.DataSourcePluginInfo;
@@ -94,6 +95,8 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
 
     protected static final String DEFAULT_DATASOURCE_PLUGIN_VERSION = "1.0.0";
 
+    @Autowired private ConfigShadeUtil configShadeUtil;
+
     @Override
     public String createDatasource(
             String datasourceName,
@@ -114,6 +117,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
             throw new SeatunnelException(
                     SeatunnelErrorEnum.DATASOURCE_PRAM_NOT_ALLOWED_NULL, 
"datasourceConfig");
         }
+        configShadeUtil.encryptData(datasourceConfig);
         String datasourceConfigStr = JsonUtils.toJsonString(datasourceConfig);
         Datasource datasource =
                 Datasource.builder()
@@ -171,6 +175,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
         datasource.setUpdateTime(new Date());
         datasource.setDescription(description);
         if (MapUtils.isNotEmpty(datasourceConfig)) {
+            configShadeUtil.encryptData(datasourceConfig);
             String configJson = JsonUtils.toJsonString(datasourceConfig);
             datasource.setDatasourceConfig(configJson);
         }
@@ -226,6 +231,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
         String configJson = datasource.getDatasourceConfig();
         Map<String, String> datasourceConfig =
                 JsonUtils.toMap(configJson, String.class, String.class);
+        configShadeUtil.decryptData(datasourceConfig);
         String pluginName = datasource.getPluginName();
         return DataSourceClientFactory.getDataSourceClient()
                 .checkDataSourceConnectivity(pluginName, datasourceConfig);
@@ -274,6 +280,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
             Map<String, String> datasourceConfig =
                     JsonUtils.toMap(config, String.class, String.class);
 
+            configShadeUtil.decryptData(datasourceConfig);
             return DataSourceClientFactory.getDataSourceClient()
                     .getDatabases(pluginName, datasourceConfig);
         }
@@ -305,6 +312,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
         options.put("filterName", filterName);
         String pluginName = datasource.getPluginName();
         if (BooleanUtils.isNotTrue(checkIsSupportVirtualTable(pluginName))) {
+            configShadeUtil.decryptData(datasourceConfig);
             return DataSourceClientFactory.getDataSourceClient()
                     .getTables(pluginName, databaseName, datasourceConfig, 
options);
         }
@@ -324,6 +332,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
         Map<String, String> options = new HashMap<>();
         String pluginName = datasource.getPluginName();
         if (BooleanUtils.isNotTrue(checkIsSupportVirtualTable(pluginName))) {
+            configShadeUtil.decryptData(datasourceConfig);
             return DataSourceClientFactory.getDataSourceClient()
                     .getTables(pluginName, databaseName, datasourceConfig, 
options);
         }
@@ -345,6 +354,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
         ITableSchemaService tableSchemaService =
                 (ITableSchemaService) 
applicationContext.getBean("tableSchemaServiceImpl");
         if (BooleanUtils.isNotTrue(checkIsSupportVirtualTable(pluginName))) {
+            configShadeUtil.decryptData(datasourceConfig);
             List<TableField> tableFields =
                     DataSourceClientFactory.getDataSourceClient()
                             .getTableFields(pluginName, datasourceConfig, 
databaseName, tableName);
@@ -434,6 +444,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
                                                     
datasource.getDatasourceConfig(),
                                                     String.class,
                                                     String.class);
+                                    
configShadeUtil.decryptData(datasourceConfig);
                                     
datasourceRes.setDatasourceConfig(datasourceConfig);
                                     
datasourceRes.setCreateUserId(datasource.getCreateUserId());
                                     
datasourceRes.setUpdateUserId(datasource.getUpdateUserId());
@@ -503,7 +514,10 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
             throw new 
SeatunnelException(SeatunnelErrorEnum.DATASOURCE_NOT_FOUND, datasourceId);
         }
         String configJson = datasource.getDatasourceConfig();
-        return JsonUtils.toMap(configJson, String.class, String.class);
+        Map<String, String> datasourceConfig =
+                JsonUtils.toMap(configJson, String.class, String.class);
+        configShadeUtil.decryptData(datasourceConfig);
+        return datasourceConfig;
     }
 
     @Override
@@ -591,7 +605,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
         return getDatasourceDetailRes(datasource);
     }
 
-    private static DatasourceDetailRes getDatasourceDetailRes(Datasource 
datasource) {
+    private DatasourceDetailRes getDatasourceDetailRes(Datasource datasource) {
         DatasourceDetailRes datasourceDetailRes = new DatasourceDetailRes();
         datasourceDetailRes.setId(datasource.getId().toString());
         datasourceDetailRes.setDatasourceName(datasource.getDatasourceName());
@@ -603,6 +617,7 @@ public class DatasourceServiceImpl extends 
SeatunnelBaseServiceImpl
 
         Map<String, String> datasourceConfig =
                 JsonUtils.toMap(datasource.getDatasourceConfig(), 
String.class, String.class);
+        configShadeUtil.decryptData(datasourceConfig);
         // convert option rule
         datasourceDetailRes.setDatasourceConfig(datasourceConfig);
         return datasourceDetailRes;
diff --git 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobInstanceServiceImpl.java
 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobInstanceServiceImpl.java
index e7883395..cd453048 100644
--- 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobInstanceServiceImpl.java
+++ 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobInstanceServiceImpl.java
@@ -31,6 +31,7 @@ import org.apache.seatunnel.api.configuration.util.OptionRule;
 import org.apache.seatunnel.api.env.ParsingMode;
 import org.apache.seatunnel.app.bean.connector.ConnectorCache;
 import org.apache.seatunnel.app.config.ConnectorDataSourceMapperConfig;
+import org.apache.seatunnel.app.config.EncryptionConfig;
 import org.apache.seatunnel.app.dal.dao.IJobDefinitionDao;
 import org.apache.seatunnel.app.dal.dao.IJobInstanceDao;
 import org.apache.seatunnel.app.dal.dao.IJobLineDao;
@@ -60,6 +61,7 @@ import org.apache.seatunnel.app.service.IJobMetricsService;
 import org.apache.seatunnel.app.service.IVirtualTableService;
 import 
org.apache.seatunnel.app.thirdparty.datasource.DataSourceConfigSwitcherUtils;
 import 
org.apache.seatunnel.app.thirdparty.transfrom.TransformConfigSwitcherUtils;
+import org.apache.seatunnel.app.utils.ConfigShadeUtil;
 import org.apache.seatunnel.app.utils.JobUtils;
 import org.apache.seatunnel.app.utils.SeaTunnelConfigUtil;
 import org.apache.seatunnel.app.utils.ServletUtils;
@@ -74,6 +76,7 @@ import org.apache.seatunnel.server.common.SeatunnelException;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -95,6 +98,8 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static 
org.apache.seatunnel.app.common.Constants.ENCRYPTION_IDENTIFIER_KEY;
+import static org.apache.seatunnel.app.common.Constants.ENCRYPTION_TYPE_NONE;
 import static 
org.apache.seatunnel.app.utils.TaskOptionUtils.getTransformOption;
 
 @Service
@@ -124,6 +129,10 @@ public class JobInstanceServiceImpl extends 
SeatunnelBaseServiceImpl
 
     @Resource private IJobMetricsService jobMetricsService;
 
+    @Autowired private ConfigShadeUtil configShadeUtil;
+
+    @Autowired private EncryptionConfig encryptionConfig;
+
     @Override
     public JobExecutorRes createExecuteResource(
             @NonNull Long jobDefineId, JobExecParam executeParam) {
@@ -324,6 +333,14 @@ public class JobInstanceServiceImpl extends 
SeatunnelBaseServiceImpl
         if (sinkMap.size() > 0) {
             sinks = getConnectorConfig(sinkMap);
         }
+
+        if (!encryptionConfig.getType().equals(ENCRYPTION_TYPE_NONE)) {
+            envConfig =
+                    envConfig.withValue(
+                            ENCRYPTION_IDENTIFIER_KEY,
+                            
ConfigValueFactory.fromAnyRef(encryptionConfig.getType()));
+        }
+
         String env =
                 envConfig
                         .root()
@@ -575,6 +592,7 @@ public class JobInstanceServiceImpl extends 
SeatunnelBaseServiceImpl
             String connectorType,
             Map<String, String> config,
             OptionRule optionRule) {
+        configShadeUtil.encryptData(config);
         return parseConfigWithOptionRule(
                 pluginType, connectorType, ConfigFactory.parseMap(config), 
optionRule);
     }
diff --git 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/ConfigShadeUtil.java
 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/ConfigShadeUtil.java
new file mode 100644
index 00000000..ae2db772
--- /dev/null
+++ 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/ConfigShadeUtil.java
@@ -0,0 +1,89 @@
+/*
+ * 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.seatunnel.app.utils;
+
+import org.apache.seatunnel.app.config.EncryptionConfig;
+import org.apache.seatunnel.core.starter.utils.ConfigShadeUtils;
+import org.apache.seatunnel.server.common.SeatunnelErrorEnum;
+import org.apache.seatunnel.server.common.SeatunnelException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+
+import static org.apache.seatunnel.app.common.Constants.ENCRYPTION_TYPE_NONE;
+
+@Slf4j
+@Component
+public class ConfigShadeUtil {
+
+    @Autowired private EncryptionConfig encryptionConfig;
+
+    public void encryptData(Map<String, String> datasourceConfig) {
+        if (encryptionConfig.getType().equals(ENCRYPTION_TYPE_NONE)) {
+            return;
+        }
+        for (String key : encryptionConfig.getKeysToEncrypt()) {
+            String value = datasourceConfig.get(key);
+            if (StringUtils.isNotEmpty(value)) {
+                try {
+                    String processedValue =
+                            
ConfigShadeUtils.encryptOption(encryptionConfig.getType(), value);
+                    datasourceConfig.replace(key, processedValue);
+                } catch (IllegalArgumentException ex) {
+                    log.error("encryption for key {} failed", key);
+                    throw new SeatunnelException(
+                            SeatunnelErrorEnum.ERROR_CONFIG,
+                            String.format(
+                                    "encryption failed for key: %s, check if 
the keys were persisted in expected format",
+                                    key),
+                            ex);
+                }
+            }
+        }
+    }
+
+    public void decryptData(Map<String, String> datasourceConfig) {
+        if (encryptionConfig.getType().equals(ENCRYPTION_TYPE_NONE)) {
+            return;
+        }
+        for (String key : encryptionConfig.getKeysToEncrypt()) {
+            String value = datasourceConfig.get(key);
+            if (StringUtils.isNotEmpty(value)) {
+                try {
+                    String processedValue =
+                            
ConfigShadeUtils.decryptOption(encryptionConfig.getType(), value);
+                    datasourceConfig.replace(key, processedValue);
+                } catch (IllegalArgumentException ex) {
+                    log.error("decryption for key {} failed", key);
+                    throw new SeatunnelException(
+                            SeatunnelErrorEnum.ERROR_CONFIG,
+                            String.format(
+                                    "decryption failed for key: %s, check if 
the keys were persisted in expected format",
+                                    key),
+                            ex);
+                }
+            }
+        }
+    }
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/application.yml 
b/seatunnel-server/seatunnel-app/src/main/resources/application.yml
index 5a6e3a06..a90a6366 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/application.yml
+++ b/seatunnel-server/seatunnel-app/src/main/resources/application.yml
@@ -52,7 +52,13 @@ jwt:
   secretKey:
   algorithm: HS256
 
-
+seatunnel-web:
+  datasource:
+    encryption:
+      type: none
+      keys-to-encrypt:
+        - password
+        - auth
 ---
 spring:
   config:
diff --git a/seatunnel-web-it/src/test/resources/application.yml 
b/seatunnel-web-it/src/test/resources/application.yml
index ef285655..752fbc7b 100644
--- a/seatunnel-web-it/src/test/resources/application.yml
+++ b/seatunnel-web-it/src/test/resources/application.yml
@@ -48,6 +48,14 @@ jwt:
   secretKey: https://github.com/apache/seatunnel
   algorithm: HS256
 
+seatunnel-web:
+  datasource:
+    encryption:
+      type: none
+      keys-to-encrypt:
+        - password
+        - auth
+
 ---
 spring:
   application:

Reply via email to