Skip to content

🐛Destination-dynamodb: enforce ssl connection #18672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
- name: DynamoDB
destinationDefinitionId: 8ccd8909-4e99-4141-b48d-4984b70b2d89
dockerRepository: airbyte/destination-dynamodb
dockerImageTag: 0.1.5
dockerImageTag: 0.1.7
documentationUrl: https://docs.airbyte.com/integrations/destinations/dynamodb
icon: dynamodb.svg
releaseStage: alpha
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1460,7 +1460,7 @@
supported_destination_sync_modes:
- "overwrite"
- "append"
- dockerImage: "airbyte/destination-dynamodb:0.1.5"
- dockerImage: "airbyte/destination-dynamodb:0.1.7"
spec:
documentationUrl: "https://docs.airbyte.com/integrations/destinations/dynamodb"
connectionSpecification:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ ENV APPLICATION destination-dynamodb

COPY --from=build /airbyte /airbyte

LABEL io.airbyte.version=0.1.5
LABEL io.airbyte.version=0.1.7
LABEL io.airbyte.name=airbyte/destination-dynamodb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

application {
mainClass = 'io.airbyte.integrations.destination.dynamodb.DynamodbDestination'
mainClass = 'io.airbyte.integrations.destination.dynamodb.DynamodbDestinationRunner'
applicationDefaultJvmArgs = ['-XX:+ExitOnOutOfMemoryError', '-XX:MaxRAMPercentage=75.0']
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,18 @@ public static AmazonDynamoDB getAmazonDynamoDB(final DynamodbDestinationConfig d
}
}

/**
* Checks that DynamoDb custom endpoint uses a variant that only uses HTTPS
*
* @param endpoint URL string representing an accessible S3 bucket
*/
public static boolean testCustomEndpointSecured(final String endpoint) {
// if user does not use a custom endpoint, do not fail
if (endpoint == null || endpoint.length() == 0) {
return true;
} else {
return endpoint.startsWith("https://");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ public static void main(final String[] args) throws Exception {
@Override
public AirbyteConnectionStatus check(final JsonNode config) {
try {
DynamodbChecker.attemptDynamodbWriteAndDelete(DynamodbDestinationConfig.getDynamodbDestinationConfig(config));
final DynamodbDestinationConfig dynamodbDestinationConfig =
DynamodbDestinationConfig.getDynamodbDestinationConfig(config);

DynamodbChecker.attemptDynamodbWriteAndDelete(dynamodbDestinationConfig);
return new AirbyteConnectionStatus().withStatus(AirbyteConnectionStatus.Status.SUCCEEDED);
} catch (final Exception e) {
LOGGER.error("Exception attempting to access the DynamoDB table: ", e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2022 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.integrations.destination.dynamodb;

import io.airbyte.integrations.base.adaptive.AdaptiveDestinationRunner;

public class DynamodbDestinationRunner {

public static void main(final String[] args) throws Exception {
AdaptiveDestinationRunner.baseOnEnv()
.withOssDestination(DynamodbDestination::new)
.withCloudDestination(DynamodbDestinationStrictEncrypt::new)
.run(args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.integrations.destination.dynamodb;

import com.fasterxml.jackson.databind.JsonNode;
import io.airbyte.protocol.models.AirbyteConnectionStatus;

public class DynamodbDestinationStrictEncrypt extends DynamodbDestination {

protected static final String NON_SECURE_URL_ERR_MSG = "Server Endpoint requires HTTPS";

public DynamodbDestinationStrictEncrypt() {
super();
}

@Override
public AirbyteConnectionStatus check(final JsonNode config) {
final DynamodbDestinationConfig dynamodbDestinationConfig =
DynamodbDestinationConfig.getDynamodbDestinationConfig(config);

// enforce ssl connection
if (!DynamodbChecker.testCustomEndpointSecured(dynamodbDestinationConfig.getEndpoint())) {
return new AirbyteConnectionStatus()
.withStatus(AirbyteConnectionStatus.Status.FAILED)
.withMessage(NON_SECURE_URL_ERR_MSG);
}

return super.check(config);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2022 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.integrations.destination.dynamodb;

import static io.airbyte.integrations.destination.dynamodb.DynamodbDestinationStrictEncrypt.NON_SECURE_URL_ERR_MSG;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableMap;
import io.airbyte.commons.io.IOs;
import io.airbyte.commons.json.Jsons;
import io.airbyte.protocol.models.AirbyteConnectionStatus;
import io.airbyte.protocol.models.AirbyteConnectionStatus.Status;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;

public class DynamodbDestinationStrictEncryptTest {

protected static final Path secretFilePath = Path.of("secrets/config.json");

/**
* Test that check passes if user is using HTTPS connection
*/
@Test
public void checkPassCustomEndpointIsHttpsOnly() {
final DynamodbDestination destinationWithHttpsOnlyEndpoint = new DynamodbDestinationStrictEncrypt();
final AirbyteConnectionStatus status = destinationWithHttpsOnlyEndpoint.check(getBaseConfigJson());
assertEquals(Status.SUCCEEDED, status.getStatus());
}

/**
* Test that check fails if user is using a non-secure (http) connection
*/
@Test
public void checkFailCustomEndpointIsHttpsOnly() {
final DynamodbDestination destinationWithHttpsOnlyEndpoint = new DynamodbDestinationStrictEncrypt();
final AirbyteConnectionStatus status = destinationWithHttpsOnlyEndpoint.check(getUnsecureConfig());
assertEquals(AirbyteConnectionStatus.Status.FAILED, status.getStatus());
assertEquals(NON_SECURE_URL_ERR_MSG, status.getMessage());
}

protected JsonNode getBaseConfigJson() {
if (!Files.exists(secretFilePath)) {
throw new IllegalStateException("Secret config file doesn't exist. Get a valid secret (for airbyter: "
+ "get secret from GSM) and put to ../destination-dynamodb/secrets/secret.json file");
}
return Jsons.deserialize(IOs.readFile(secretFilePath));
}

protected JsonNode getUnsecureConfig() {
return Jsons.jsonNode(ImmutableMap.builder()
.put("dynamodb_endpoint", "http://testurl.com:9000")
.put("dynamodb_table_name_prefix", "integration-test")
.put("dynamodb_region", "us-east-2")
.put("access_key_id", "dummy_access_key_id")
.put("secret_access_key", "dummy_secret_access_key")
.build());
}

}
2 changes: 2 additions & 0 deletions docs/integrations/destinations/dynamodb.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ This connector by default uses 10 capacity units for both Read and Write in Dyna

| Version | Date | Pull Request | Subject |
| :--- | :--- | :--- | :--- |
| 0.1.7 | 2022-11-03 | [\#18672](https://github.com/airbytehq/airbyte/pull/18672) | Added strict-encrypt cloud runner |
| 0.1.6 | 2022-11-01 | [\#18672](https://github.com/airbytehq/airbyte/pull/18672) | Enforce to use ssl connection |
| 0.1.5 | 2022-08-05 | [\#15350](https://github.com/airbytehq/airbyte/pull/15350) | Added per-stream handling |
| 0.1.4 | 2022-06-16 | [\#13852](https://github.com/airbytehq/airbyte/pull/13852) | Updated stacktrace format for any trace message errors |
| 0.1.3 | 2022-05-17 | [12820](https://github.com/airbytehq/airbyte/pull/12820) | Improved 'check' operation performance |
Expand Down