diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/integration/crypto/LegacyDecryptStringConverter.java b/app/proprietary/src/main/java/stirling/software/proprietary/integration/crypto/LegacyDecryptStringConverter.java new file mode 100644 index 0000000000..118608b925 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/integration/crypto/LegacyDecryptStringConverter.java @@ -0,0 +1,35 @@ +package stirling.software.proprietary.integration.crypto; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +/** + * For columns that were once whole-blob encrypted but no longer hold secrets. + * + *
Writes plaintext, so the value is readable by any instance regardless of the per-installation
+ * encryption key. Any value that isn't our ciphertext (already-plaintext JSON, or ciphertext from a
+ * key we don't hold) is returned as-is; the latter is the caller's to reject.
+ */
+@Converter
+public class LegacyDecryptStringConverter implements AttributeConverter Idempotent by construction: rewritten rows no longer embed credentials, so re-runs find
* nothing to do. Connections are deduplicated against both this run's extractions and existing S3
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/policy/source/SourceEntity.java b/app/proprietary/src/main/java/stirling/software/proprietary/policy/source/SourceEntity.java
index c94e4cb079..728612742e 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/policy/source/SourceEntity.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/policy/source/SourceEntity.java
@@ -12,7 +12,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
-import stirling.software.proprietary.integration.crypto.LenientEncryptedStringConverter;
+import stirling.software.proprietary.integration.crypto.LegacyDecryptStringConverter;
/**
* JPA row for a {@link Source}. The whole source lives as JSON in {@code sourceJson} (authoritative
@@ -48,9 +48,9 @@ public class SourceEntity implements Serializable {
@Column(name = "enabled")
private boolean enabled;
- // Encrypted at rest: source options carry user-supplied credentials (e.g. an S3 secret
- // access key). Lenient so rows written before encryption shipped still load.
- @Convert(converter = LenientEncryptedStringConverter.class)
+ // Plaintext at rest: the S3 credentials that used to live here now sit in a referenced
+ // IntegrationConfig connection (still encrypted). Decrypts legacy ciphertext on read.
+ @Convert(converter = LegacyDecryptStringConverter.class)
@Column(name = "source_json", columnDefinition = "text")
private String sourceJson;
}
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/policy/store/PolicyEntity.java b/app/proprietary/src/main/java/stirling/software/proprietary/policy/store/PolicyEntity.java
index e393e47833..c871267bc4 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/policy/store/PolicyEntity.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/policy/store/PolicyEntity.java
@@ -12,7 +12,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
-import stirling.software.proprietary.integration.crypto.LenientEncryptedStringConverter;
+import stirling.software.proprietary.integration.crypto.LegacyDecryptStringConverter;
/**
* JPA row for a {@link stirling.software.proprietary.policy.model.Policy}. The whole policy lives
@@ -58,9 +58,9 @@ public class PolicyEntity implements Serializable {
@Column(name = "sort_order")
private Integer sortOrder;
- // Encrypted at rest: output options carry user-supplied credentials (e.g. an S3 secret
- // access key). Lenient so rows written before encryption shipped still load.
- @Convert(converter = LenientEncryptedStringConverter.class)
+ // Plaintext at rest: the S3 credentials that used to live here now sit in a referenced
+ // IntegrationConfig connection (still encrypted). Decrypts legacy ciphertext on read.
+ @Convert(converter = LegacyDecryptStringConverter.class)
@Column(name = "policy_json", columnDefinition = "text")
private String policyJson;
}
diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/integration/crypto/LegacyDecryptStringConverterTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/integration/crypto/LegacyDecryptStringConverterTest.java
new file mode 100644
index 0000000000..11b913e9d7
--- /dev/null
+++ b/app/proprietary/src/test/java/stirling/software/proprietary/integration/crypto/LegacyDecryptStringConverterTest.java
@@ -0,0 +1,51 @@
+package stirling.software.proprietary.integration.crypto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+class LegacyDecryptStringConverterTest {
+
+ private final LegacyDecryptStringConverter converter = new LegacyDecryptStringConverter();
+
+ @BeforeAll
+ static void initKey() throws Exception {
+ KeyGenerator generator = KeyGenerator.getInstance("AES");
+ generator.init(256);
+ SecretKey key = generator.generateKey();
+ CredentialEncryption.initialiseForTesting(key);
+ }
+
+ @Test
+ void writesPlaintext() {
+ String json = "{\"bucket\":\"inbox\",\"mode\":\"consume\"}";
+
+ assertThat(converter.convertToDatabaseColumn(json)).isEqualTo(json);
+ }
+
+ @Test
+ void decryptsLegacyCiphertextOnRead() {
+ String json = "{\"bucket\":\"inbox\"}";
+ String legacyCiphertext = CredentialEncryption.encrypt(json);
+
+ assertThat(legacyCiphertext).isNotEqualTo(json);
+ assertThat(converter.convertToEntityAttribute(legacyCiphertext)).isEqualTo(json);
+ }
+
+ @Test
+ void passesPlaintextThroughOnRead() {
+ String json = "{\"bucket\":\"inbox\",\"mode\":\"consume\"}";
+
+ assertThat(converter.convertToEntityAttribute(json)).isEqualTo(json);
+ }
+
+ @Test
+ void nullsPassThrough() {
+ assertThat(converter.convertToDatabaseColumn(null)).isNull();
+ assertThat(converter.convertToEntityAttribute(null)).isNull();
+ }
+}
diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/integration/crypto/LenientEncryptedStringConverterTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/integration/crypto/LenientEncryptedStringConverterTest.java
deleted file mode 100644
index b61ded9708..0000000000
--- a/app/proprietary/src/test/java/stirling/software/proprietary/integration/crypto/LenientEncryptedStringConverterTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package stirling.software.proprietary.integration.crypto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-class LenientEncryptedStringConverterTest {
-
- private final LenientEncryptedStringConverter converter = new LenientEncryptedStringConverter();
-
- @BeforeAll
- static void initKey() throws Exception {
- KeyGenerator generator = KeyGenerator.getInstance("AES");
- generator.init(256);
- SecretKey key = generator.generateKey();
- CredentialEncryption.initialiseForTesting(key);
- }
-
- @Test
- void roundTripsThroughCiphertext() {
- String json = "{\"bucket\":\"inbox\",\"secretAccessKey\":\"shh\"}";
-
- String stored = converter.convertToDatabaseColumn(json);
-
- assertThat(stored).isNotEqualTo(json).doesNotContain("shh");
- assertThat(converter.convertToEntityAttribute(stored)).isEqualTo(json);
- }
-
- @Test
- void legacyPlaintextRowsPassThroughOnRead() {
- String legacy = "{\"bucket\":\"inbox\",\"mode\":\"consume\"}";
-
- assertThat(converter.convertToEntityAttribute(legacy)).isEqualTo(legacy);
- }
-
- @Test
- void nullsPassThrough() {
- assertThat(converter.convertToDatabaseColumn(null)).isNull();
- assertThat(converter.convertToEntityAttribute(null)).isNull();
- }
-}