diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/PEMCertificate.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/PEMCertificate.java index f24fe18bd3..1fd4fafb13 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/PEMCertificate.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/PEMCertificate.java @@ -1,5 +1,6 @@ package com.appsmith.external.models; +import com.appsmith.external.annotations.encryption.Encrypted; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -16,10 +17,11 @@ import org.springframework.data.mongodb.core.mapping.Document; @NoArgsConstructor @AllArgsConstructor @Document -public class PEMCertificate { +public class PEMCertificate implements AppsmithDomain { UploadedFile file; + @Encrypted @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String password; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSHConnection.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSHConnection.java index 2639f000b1..0b83620f91 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSHConnection.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSHConnection.java @@ -11,7 +11,7 @@ import lombok.ToString; @ToString @NoArgsConstructor @AllArgsConstructor -public class SSHConnection { +public class SSHConnection implements AppsmithDomain { public enum AuthType { IDENTITY_FILE, PASSWORD diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSHPrivateKey.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSHPrivateKey.java index 41544a7ed4..b209efe7b7 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSHPrivateKey.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSHPrivateKey.java @@ -1,5 +1,7 @@ package com.appsmith.external.models; +import com.appsmith.external.annotations.encryption.Encrypted; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,10 +13,12 @@ import lombok.ToString; @ToString @NoArgsConstructor @AllArgsConstructor -public class SSHPrivateKey { +public class SSHPrivateKey implements AppsmithDomain { UploadedFile keyFile; + @Encrypted + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String password; } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSLDetails.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSLDetails.java index 326a23538e..efb05715f3 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSLDetails.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/SSLDetails.java @@ -15,7 +15,7 @@ import org.springframework.data.mongodb.core.mapping.Document; @AllArgsConstructor @NoArgsConstructor @Document -public class SSLDetails { +public class SSLDetails implements AppsmithDomain { public enum AuthType { // Default driver configurations diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/UploadedFile.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/UploadedFile.java index 93cc661edc..91f494484d 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/UploadedFile.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/UploadedFile.java @@ -1,5 +1,6 @@ package com.appsmith.external.models; +import com.appsmith.external.annotations.encryption.Encrypted; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -17,12 +18,13 @@ import java.util.Base64; @EqualsAndHashCode @NoArgsConstructor @AllArgsConstructor -public class UploadedFile { +public class UploadedFile implements AppsmithDomain { private static final String BASE64_DELIMITER = ";base64,"; String name; + @Encrypted @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String base64Content; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java index 4bb8e7249d..4870b459ee 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java @@ -57,9 +57,12 @@ import com.mongodb.MongoException; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import com.mongodb.client.model.Filters; +import com.mysema.commons.lang.Pair; import lombok.extern.slf4j.Slf4j; import net.minidev.json.JSONObject; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.StringUtils; import org.bson.Document; import org.bson.types.ObjectId; import org.springframework.core.io.DefaultResourceLoader; @@ -2834,4 +2837,113 @@ public class DatabaseChangelog { mongoTemplate.save(plugin); } } + + private Document getDocumentFromPath(Document document, String path) { + String[] pathKeys = path.split("\\."); + Document documentPtr = document; + + /** + * - Traverse document one key at a time. + * - Forced to traverse document one key at a time for the lack of a better API that allows traversal for + * chained keys or key list. + */ + for (int i=0; i encryptionService.encryptString((String) v) + ); + } + } + + private void encryptRawValues(Document document, List pathList, EncryptionService encryptionService) { + pathList.stream() + .forEach(path -> encryptPathValueIfExists(document, path, encryptionService)); + } + + @ChangeSet(order = "080", id = "encrypt-certificate", author = "") + public void encryptCertificateAndPassword(MongockTemplate mongoTemplate, EncryptionService encryptionService) { + + /** + * - List of attributes that need to be encoded. + * - Each path represents where the attribute exists in mongo db document. + */ + List pathList = new ArrayList<>(); + pathList.add("datasourceConfiguration.connection.ssl.keyFile.base64Content"); + pathList.add("datasourceConfiguration.connection.ssl.certificateFile.base64Content"); + pathList.add("datasourceConfiguration.connection.ssl.caCertificateFile.base64Content"); + pathList.add("datasourceConfiguration.connection.ssl.pemCertificate.file.base64Content"); + pathList.add("datasourceConfiguration.connection.ssl.pemCertificate.password"); + pathList.add("datasourceConfiguration.sshProxy.privateKey.keyFile.base64Content"); + pathList.add("datasourceConfiguration.sshProxy.privateKey.password"); + + mongoTemplate.execute("datasource", new CollectionCallback() { + @Override + public String doInCollection(MongoCollection collection) { + MongoCursor cursor = collection.find( + Filters.or( + Filters.exists(pathList.get(0)), + Filters.exists(pathList.get(1)), + Filters.exists(pathList.get(2)), + Filters.exists(pathList.get(3)), + Filters.exists(pathList.get(4)), + Filters.exists(pathList.get(5)), + Filters.exists(pathList.get(6)) + ) + ).cursor(); + + List> documentPairList = new ArrayList<>(); + while (cursor.hasNext()) { + Document old = (Document) cursor.next(); + // This document will have the encrypted values. + Document updated = Document.parse(old.toJson()); + // Encrypt attributes + encryptRawValues(updated, pathList, encryptionService); + documentPairList.add(new Pair(old, updated)); + } + + /** + * - Replace old document with the updated document that has encrypted values. + * - Replacing here instead of the while loop above makes sure that we attempt replacement only if + * the encryption step succeeded without error for each selected document. + */ + documentPairList.stream() + .forEach(docPair -> collection.findOneAndReplace(docPair.getFirst(), docPair.getSecond())); + + return null; + } + }); + } }