Encrypt attributes (#6508)

* encrypt attributes
This commit is contained in:
Sumit Kumar 2021-08-11 11:43:57 +05:30 committed by GitHub
parent b45d24da9b
commit 15061765da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 125 additions and 5 deletions

View File

@ -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;

View File

@ -11,7 +11,7 @@ import lombok.ToString;
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class SSHConnection {
public class SSHConnection implements AppsmithDomain {
public enum AuthType {
IDENTITY_FILE, PASSWORD

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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<pathKeys.length; i++) {
if (documentPtr.containsKey(pathKeys[i])) {
try {
documentPtr = documentPtr.get(pathKeys[i], Document.class);
} catch (ClassCastException e) {
System.out.println("Failed to cast document for path: " + path);
e.printStackTrace();
return null;
}
}
else {
return null;
}
}
return documentPtr;
}
private void encryptPathValueIfExists(Document document, String path, EncryptionService encryptionService) {
String[] pathKeys = path.split("\\.");
/**
* - For attribute path "datasourceConfiguration.connection.ssl.keyFile.base64Content", first get the parent
* document that contains the attribute 'base64Content' i.e. fetch the document corresponding to path
* "datasourceConfiguration.connection.ssl.keyFile"
*/
String parentDocumentPath = StringUtils.join(ArrayUtils.subarray(pathKeys, 0, pathKeys.length - 1), ".");
Document parentDocument = getDocumentFromPath(document, parentDocumentPath);
if (parentDocument != null) {
/**
* - Replace old value with new encrypted value if the key exists and is non-null.
* - Use the last element in pathKeys since it the key that names the attribute that needs to be encrypted
* e.g. 'base64Content' in "datasourceConfiguration.connection.ssl.keyFile.base64Content"
*/
parentDocument.computeIfPresent(
pathKeys[pathKeys.length - 1],
(k, v) -> encryptionService.encryptString((String) v)
);
}
}
private void encryptRawValues(Document document, List<String> 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<String> 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<String>() {
@Override
public String doInCollection(MongoCollection<Document> 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<Pair<Document, Document>> 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;
}
});
}
}