Skip to content

Commit 2ef0691

Browse files
andriikorotkovoctavia-squidington-iii
authored andcommitted
🎉 Destination postgres: Add SSL certificates and update normalization (#14743)
* added ssl certificates for postgres source * added command for remove client private key after transformation to encrypted key with .pk8 extension * added connection with CA and client certificates for postgres destination * updated code style * moved common methods to the common class * moved common methods to the common class * fixed remarks * updated postgres source tests * added minor changes to spec and added fixes to password mechanism * updated postgres source tests * updated strict-encrypt postgres source and destination and added tests for SSL certificates for all postgres connectors * fixed check style * updated documentation and versions of connectors * updated ordrs in test spec * fixed minor remarks in specs and expected_specs * fixed minor remarks in specs and expected_specs * fixed Dockerfile * fixed remarks * fixed remarks * fixed remarks * fixed remarks * fixed remarks * fixed code style * fixed connectors version in definition file * updated postgres destination normalization * updated postgres destination tests * fixed code style for postgres source and destination * pulled master changes * removed allow mode for destination-postgres-strect-encrypt * updated connectors version * fixed custom DBT transformation and enabled test for it * updated normalization version * updated keystore password generation method * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III <[email protected]>
1 parent b80826b commit 2ef0691

File tree

17 files changed

+678
-35
lines changed

17 files changed

+678
-35
lines changed

airbyte-config/init/src/main/resources/seed/destination_definitions.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@
203203
- name: Postgres
204204
destinationDefinitionId: 25c5221d-dce2-4163-ade9-739ef790f503
205205
dockerRepository: airbyte/destination-postgres
206-
dockerImageTag: 0.3.21
206+
dockerImageTag: 0.3.22
207207
documentationUrl: https://docs.airbyte.io/integrations/destinations/postgres
208208
icon: postgresql.svg
209209
releaseStage: alpha

airbyte-config/init/src/main/resources/seed/destination_specs.yaml

Lines changed: 147 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3302,7 +3302,7 @@
33023302
supported_destination_sync_modes:
33033303
- "overwrite"
33043304
- "append"
3305-
- dockerImage: "airbyte/destination-postgres:0.3.21"
3305+
- dockerImage: "airbyte/destination-postgres:0.3.22"
33063306
spec:
33073307
documentationUrl: "https://docs.airbyte.io/integrations/destinations/postgres"
33083308
connectionSpecification:
@@ -3360,17 +3360,161 @@
33603360
order: 5
33613361
ssl:
33623362
title: "SSL Connection"
3363-
description: "Encrypt data using SSL."
3363+
description: "Encrypt data using SSL. When activating SSL, please select\
3364+
\ one of the connection modes."
33643365
type: "boolean"
33653366
default: false
33663367
order: 6
3368+
ssl_mode:
3369+
title: "SSL modes"
3370+
description: "SSL connection modes. \n <b>disable</b> - Chose this mode\
3371+
\ to disable encryption of communication between Airbyte and destination\
3372+
\ database\n <b>allow</b> - Chose this mode to enable encryption only\
3373+
\ when required by the source database\n <b>prefer</b> - Chose this mode\
3374+
\ to allow unencrypted connection only if the source database does not\
3375+
\ support encryption\n <b>require</b> - Chose this mode to always require\
3376+
\ encryption. If the source database server does not support encryption,\
3377+
\ connection will fail\n <b>verify-ca</b> - Chose this mode to always\
3378+
\ require encryption and to verify that the source database server has\
3379+
\ a valid SSL certificate\n <b>verify-full</b> - This is the most secure\
3380+
\ mode. Chose this mode to always require encryption and to verify the\
3381+
\ identity of the source database server\n See more information - <a href=\"\
3382+
https://jdbc.postgresql.org/documentation/head/ssl-client.html\"> in the\
3383+
\ docs</a>."
3384+
type: "object"
3385+
order: 7
3386+
oneOf:
3387+
- title: "disable"
3388+
additionalProperties: false
3389+
description: "Disable SSL."
3390+
required:
3391+
- "mode"
3392+
properties:
3393+
mode:
3394+
type: "string"
3395+
const: "disable"
3396+
enum:
3397+
- "disable"
3398+
default: "disable"
3399+
order: 0
3400+
- title: "allow"
3401+
additionalProperties: false
3402+
description: "Allow SSL mode."
3403+
required:
3404+
- "mode"
3405+
properties:
3406+
mode:
3407+
type: "string"
3408+
const: "allow"
3409+
enum:
3410+
- "allow"
3411+
default: "allow"
3412+
order: 0
3413+
- title: "prefer"
3414+
additionalProperties: false
3415+
description: "Prefer SSL mode."
3416+
required:
3417+
- "mode"
3418+
properties:
3419+
mode:
3420+
type: "string"
3421+
const: "prefer"
3422+
enum:
3423+
- "prefer"
3424+
default: "prefer"
3425+
order: 0
3426+
- title: "require"
3427+
additionalProperties: false
3428+
description: "Require SSL mode."
3429+
required:
3430+
- "mode"
3431+
properties:
3432+
mode:
3433+
type: "string"
3434+
const: "require"
3435+
enum:
3436+
- "require"
3437+
default: "require"
3438+
order: 0
3439+
- title: "verify-ca"
3440+
additionalProperties: false
3441+
description: "Verify-ca SSL mode."
3442+
required:
3443+
- "mode"
3444+
- "ca_certificate"
3445+
properties:
3446+
mode:
3447+
type: "string"
3448+
const: "verify-ca"
3449+
enum:
3450+
- "verify-ca"
3451+
default: "verify-ca"
3452+
order: 0
3453+
ca_certificate:
3454+
type: "string"
3455+
title: "CA certificate"
3456+
description: "CA certificate"
3457+
airbyte_secret: true
3458+
multiline: true
3459+
order: 1
3460+
client_key_password:
3461+
type: "string"
3462+
title: "Client key password (Optional)"
3463+
description: "Password for keystorage. This field is optional. If\
3464+
\ you do not add it - the password will be generated automatically."
3465+
airbyte_secret: true
3466+
order: 4
3467+
- title: "verify-full"
3468+
additionalProperties: false
3469+
description: "Verify-full SSL mode."
3470+
required:
3471+
- "mode"
3472+
- "ca_certificate"
3473+
- "client_certificate"
3474+
- "client_key"
3475+
properties:
3476+
mode:
3477+
type: "string"
3478+
const: "verify-full"
3479+
enum:
3480+
- "verify-full"
3481+
default: "verify-full"
3482+
order: 0
3483+
ca_certificate:
3484+
type: "string"
3485+
title: "CA certificate"
3486+
description: "CA certificate"
3487+
airbyte_secret: true
3488+
multiline: true
3489+
order: 1
3490+
client_certificate:
3491+
type: "string"
3492+
title: "Client certificate"
3493+
description: "Client certificate"
3494+
airbyte_secret: true
3495+
multiline: true
3496+
order: 2
3497+
client_key:
3498+
type: "string"
3499+
title: "Client key"
3500+
description: "Client key"
3501+
airbyte_secret: true
3502+
multiline: true
3503+
order: 3
3504+
client_key_password:
3505+
type: "string"
3506+
title: "Client key password (Optional)"
3507+
description: "Password for keystorage. This field is optional. If\
3508+
\ you do not add it - the password will be generated automatically."
3509+
airbyte_secret: true
3510+
order: 4
33673511
jdbc_url_params:
33683512
description: "Additional properties to pass to the JDBC URL string when\
33693513
\ connecting to the database formatted as 'key=value' pairs separated\
33703514
\ by the symbol '&'. (example: key1=value1&key2=value2&key3=value3)."
33713515
title: "JDBC URL Params"
33723516
type: "string"
3373-
order: 7
3517+
order: 8
33743518
tunnel_method:
33753519
type: "object"
33763520
title: "SSH Tunnel Method"

airbyte-db/db-lib/src/main/java/io/airbyte/db/PostgresUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static Certificate getCertificate(final PostgreSQLContainer<?> container)
3333
container.execInContainer("su", "-c", "psql -U test -c \"ALTER USER postgres WITH SUPERUSER;\"");
3434

3535
container.execInContainer("su", "-c", "openssl ecparam -name prime256v1 -genkey -noout -out ca.key");
36-
container.execInContainer("su", "-c", "openssl req -new -x509 -sha256 -key ca.key -out ca.crt -subj \"/CN=localhost\"");
36+
container.execInContainer("su", "-c", "openssl req -new -x509 -sha256 -key ca.key -out ca.crt -subj \"/CN=127.0.0.1\"");
3737
container.execInContainer("su", "-c", "openssl ecparam -name prime256v1 -genkey -noout -out server.key");
3838
container.execInContainer("su", "-c", "openssl req -new -sha256 -key server.key -out server.csr -subj \"/CN=localhost\"");
3939
container.execInContainer("su", "-c",

airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/JdbcUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class JdbcUtils {
2929
// NOTE: this is the plural version of SCHEMA_KEY
3030
public static final String SCHEMAS_KEY = "schemas";
3131
public static final String SSL_KEY = "ssl";
32+
public static final String SSL_MODE_KEY = "ssl_mode";
3233
public static final String TLS_KEY = "tls";
3334
public static final String USERNAME_KEY = "username";
3435
private static final JdbcSourceOperations defaultSourceOperations = new JdbcSourceOperations();

airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/util/PostgresSslConnectionUtils.java

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
package io.airbyte.integrations.util;
66

77
import com.fasterxml.jackson.databind.JsonNode;
8+
9+
import java.io.BufferedReader;
10+
import java.io.File;
11+
import java.io.FileReader;
812
import java.io.IOException;
913
import java.io.PrintWriter;
1014
import java.nio.charset.StandardCharsets;
@@ -18,7 +22,6 @@
1822
public class PostgresSslConnectionUtils {
1923

2024
private static final Logger LOGGER = LoggerFactory.getLogger(PostgresSslConnectionUtils.class);
21-
private static final String KEY_STORE_PASS = RandomStringUtils.randomAlphanumeric(10);
2225
private static final String CA_CERTIFICATE = "ca.crt";
2326
private static final String CLIENT_CERTIFICATE = "client.crt";
2427
private static final String CLIENT_KEY = "client.key";
@@ -37,17 +40,14 @@ public class PostgresSslConnectionUtils {
3740
public static final String VERIFY_FULL = "verify-full";
3841
public static final String DISABLE = "disable";
3942
public static final String TRUE_STRING_VALUE = "true";
43+
public static final String ENCRYPT_FILE_NAME = "encrypt";
4044
public static final String FACTORY_VALUE = "org.postgresql.ssl.DefaultJavaSSLFactory";
4145

4246
public static Map<String, String> obtainConnectionOptions(final JsonNode encryption) {
4347
final Map<String, String> additionalParameters = new HashMap<>();
4448
if (!encryption.isNull()) {
4549
final var method = encryption.get(PARAM_MODE).asText();
46-
String sslPassword = encryption.has(PARAM_CLIENT_KEY_PASSWORD) ? encryption.get(PARAM_CLIENT_KEY_PASSWORD).asText() : "";
47-
var keyStorePassword = KEY_STORE_PASS;
48-
if (!sslPassword.isEmpty()) {
49-
keyStorePassword = sslPassword;
50-
}
50+
var keyStorePassword = checkOrCreatePassword(encryption);
5151
switch (method) {
5252
case VERIFY_CA -> {
5353
additionalParameters.putAll(obtainConnectionCaOptions(encryption, method, keyStorePassword));
@@ -64,6 +64,37 @@ public static Map<String, String> obtainConnectionOptions(final JsonNode encrypt
6464
return additionalParameters;
6565
}
6666

67+
private static String checkOrCreatePassword(final JsonNode encryption) {
68+
String sslPassword = encryption.has(PARAM_CLIENT_KEY_PASSWORD) ? encryption.get(PARAM_CLIENT_KEY_PASSWORD).asText() : "";
69+
var keyStorePassword = RandomStringUtils.randomAlphanumeric(10);
70+
if (sslPassword.isEmpty()) {
71+
var file = new File(ENCRYPT_FILE_NAME);
72+
if (file.exists()) {
73+
keyStorePassword = readFile(file);
74+
} else {
75+
try {
76+
createCertificateFile(ENCRYPT_FILE_NAME, keyStorePassword);
77+
} catch (final IOException e) {
78+
throw new RuntimeException("Failed to create encryption file ");
79+
}
80+
}
81+
} else {
82+
keyStorePassword = sslPassword;
83+
}
84+
return keyStorePassword;
85+
}
86+
87+
private static String readFile(final File file) {
88+
try {
89+
BufferedReader reader = new BufferedReader(new FileReader(file));
90+
String currentLine = reader.readLine();
91+
reader.close();
92+
return currentLine;
93+
} catch (final IOException e) {
94+
throw new RuntimeException("Failed to read file with encryption");
95+
}
96+
}
97+
6798
private static Map<String, String> obtainConnectionFullOptions(final JsonNode encryption,
6899
final String method,
69100
final String clientKeyPassword) {

airbyte-integrations/connectors/destination-postgres-strict-encrypt/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ ENV APPLICATION destination-postgres-strict-encrypt
1616

1717
COPY --from=build /airbyte /airbyte
1818

19-
LABEL io.airbyte.version=0.3.21
19+
LABEL io.airbyte.version=0.3.22
2020
LABEL io.airbyte.name=airbyte/destination-postgres-strict-encrypt

airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncrypt.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package io.airbyte.integrations.destination.postgres;
66

7+
import com.fasterxml.jackson.databind.node.ArrayNode;
78
import com.fasterxml.jackson.databind.node.ObjectNode;
89
import io.airbyte.commons.json.Jsons;
910
import io.airbyte.db.jdbc.JdbcUtils;
@@ -17,6 +18,8 @@
1718
public class PostgresDestinationStrictEncrypt extends SpecModifyingDestination implements Destination {
1819

1920
private static final Logger LOGGER = LoggerFactory.getLogger(PostgresDestinationStrictEncrypt.class);
21+
private static final String PROPERTIES = "properties";
22+
private static final String ONE_OF_PROPERTY = "oneOf";
2023

2124
public PostgresDestinationStrictEncrypt() {
2225
super(PostgresDestination.sshWrappedDestination());
@@ -25,7 +28,14 @@ public PostgresDestinationStrictEncrypt() {
2528
@Override
2629
public ConnectorSpecification modifySpec(final ConnectorSpecification originalSpec) {
2730
final ConnectorSpecification spec = Jsons.clone(originalSpec);
28-
((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.SSL_KEY);
31+
((ObjectNode) spec.getConnectionSpecification().get(PROPERTIES)).remove(JdbcUtils.SSL_KEY);
32+
ArrayNode modifiedSslModes = spec.getConnectionSpecification().get(PROPERTIES).get(JdbcUtils.SSL_MODE_KEY).get(ONE_OF_PROPERTY).deepCopy();
33+
// Assume that the first item is the "allow" option; remove it
34+
modifiedSslModes.remove(1);
35+
// Assume that the first item is the "disable" option; remove it
36+
modifiedSslModes.remove(0);
37+
((ObjectNode) spec.getConnectionSpecification().get(PROPERTIES).get(JdbcUtils.SSL_MODE_KEY)).remove(ONE_OF_PROPERTY);
38+
((ObjectNode) spec.getConnectionSpecification().get(PROPERTIES).get(JdbcUtils.SSL_MODE_KEY)).put(ONE_OF_PROPERTY, modifiedSslModes);
2939
return spec;
3040
}
3141

airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncryptAcceptanceTest.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
package io.airbyte.integrations.destination.postgres;
66

7+
import static io.airbyte.db.PostgresUtils.getCertificate;
8+
79
import com.fasterxml.jackson.databind.JsonNode;
810
import com.google.common.collect.ImmutableMap;
911
import io.airbyte.commons.json.Jsons;
1012
import io.airbyte.db.Database;
13+
import io.airbyte.db.PostgresUtils;
1114
import io.airbyte.db.factory.DSLContextFactory;
1215
import io.airbyte.db.factory.DatabaseDriver;
1316
import io.airbyte.db.jdbc.JdbcUtils;
@@ -29,6 +32,9 @@ public class PostgresDestinationStrictEncryptAcceptanceTest extends DestinationA
2932
private PostgreSQLContainer<?> db;
3033
private final ExtendedNameTransformer namingResolver = new ExtendedNameTransformer();
3134

35+
protected static final String PASSWORD = "Passw0rd";
36+
protected static PostgresUtils.Certificate certs;
37+
3238
@Override
3339
protected String getImageName() {
3440
return "airbyte/destination-postgres-strict-encrypt:dev";
@@ -43,6 +49,13 @@ protected JsonNode getConfig() {
4349
.put(JdbcUtils.SCHEMA_KEY, "public")
4450
.put(JdbcUtils.PORT_KEY, db.getFirstMappedPort())
4551
.put(JdbcUtils.DATABASE_KEY, db.getDatabaseName())
52+
.put(JdbcUtils.SSL_MODE_KEY, ImmutableMap.builder()
53+
.put("mode", "verify-full")
54+
.put("ca_certificate", certs.getCaCertificate())
55+
.put("client_certificate", certs.getClientCertificate())
56+
.put("client_key", certs.getClientKey())
57+
.put("client_key_password", PASSWORD)
58+
.build())
4659
.build());
4760
}
4861

@@ -131,10 +144,11 @@ private List<JsonNode> retrieveRecordsFromTable(final String tableName, final St
131144
}
132145

133146
@Override
134-
protected void setup(final TestDestinationEnv testEnv) {
135-
db = new PostgreSQLContainer<>(DockerImageName.parse("marcosmarxm/postgres-ssl:dev").asCompatibleSubstituteFor("postgres"))
136-
.withCommand("postgres -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key");
147+
protected void setup(final TestDestinationEnv testEnv) throws Exception {
148+
db = new PostgreSQLContainer<>(DockerImageName.parse("postgres:bullseye")
149+
.asCompatibleSubstituteFor("postgres"));
137150
db.start();
151+
certs = getCertificate(db);
138152
}
139153

140154
@Override

0 commit comments

Comments
 (0)