Skip to content
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

🎉 Create a secure-only Oracle Source #6898

Merged
merged 18 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!Dockerfile
!build
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM airbyte/integration-base-java:dev

WORKDIR /airbyte

ENV APPLICATION source-oracle-strict-encrypt
ENV TZ UTC

COPY build/distributions/${APPLICATION}*.tar ${APPLICATION}.tar

RUN tar xf ${APPLICATION}.tar --strip-components=1

LABEL io.airbyte.version=0.1.0
LABEL io.airbyte.name=airbyte/source-oracle-strict-encrypt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference)
# for more information about how to configure these tests
connector_image: airbyte/source-oracle-strict-encrypt:dev
tests:
spec:
- spec_path: "src/test/resources/expected_spec.json"
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
plugins {
id 'application'
id 'airbyte-docker'
id 'airbyte-integration-test-java'
}

application {
mainClass = 'io.airbyte.integrations.source.oracle_strict_encrypt.OracleStrictEncryptSource'
applicationDefaultJvmArgs = ['-XX:MaxRAMPercentage=75.0']
}

dependencies {

// required so that log4j uses a standard xml parser instead of an oracle one (that gets pulled in by the oracle driver)
implementation group: 'xerces', name: 'xercesImpl', version: '2.12.1'

implementation project(':airbyte-db:lib')
implementation project(':airbyte-integrations:bases:base-java')
implementation project(':airbyte-integrations:connectors:source-oracle')
implementation project(':airbyte-protocol:models')
implementation project(':airbyte-integrations:connectors:source-jdbc')
implementation project(':airbyte-integrations:connectors:source-relational-db')

implementation "com.oracle.database.jdbc:ojdbc8-production:19.7.0.0"

testImplementation testFixtures(project(':airbyte-integrations:connectors:source-jdbc'))
testImplementation project(':airbyte-test-utils')

testImplementation 'org.apache.commons:commons-lang3:3.11'
testImplementation 'org.testcontainers:oracle-xe:1.15.2'

integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-source-test')

implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs)
integrationTestJavaImplementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.integrations.source.oracle_strict_encrypt;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.airbyte.commons.json.Jsons;
import io.airbyte.integrations.base.IntegrationRunner;
import io.airbyte.integrations.base.Source;
import io.airbyte.integrations.base.spec_modification.SpecModifyingSource;
import io.airbyte.integrations.source.oracle.OracleSource;
import io.airbyte.protocol.models.ConnectorSpecification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OracleStrictEncryptSource extends SpecModifyingSource implements Source {

private static final Logger LOGGER = LoggerFactory.getLogger(OracleStrictEncryptSource.class);

OracleStrictEncryptSource() {
super(OracleSource.sshWrappedSource());
}

@Override
public ConnectorSpecification modifySpec(final ConnectorSpecification originalSpec) {
final ConnectorSpecification spec = Jsons.clone(originalSpec);
((ArrayNode) spec.getConnectionSpecification().get("required")).add("encryption");
// We need to remove the first item from one Of, which is responsible for connecting to the source without encrypted.
((ArrayNode) spec.getConnectionSpecification().get("properties").get("encryption").get("oneOf")).remove(0);
andriikorotkov marked this conversation as resolved.
Show resolved Hide resolved
return spec;
}

public static void main(final String[] args) throws Exception {
final Source source = new OracleStrictEncryptSource();
LOGGER.info("starting source: {}", OracleStrictEncryptSource.class);
new IntegrationRunner(source).run(args);
LOGGER.info("completed source: {}", OracleStrictEncryptSource.class);
}

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

package io.airbyte.integrations.source.oracle_strict_encrypt;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap;
import io.airbyte.commons.json.Jsons;
import io.airbyte.db.Databases;
import io.airbyte.db.jdbc.JdbcDatabase;
import org.junit.jupiter.api.Test;

import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class OracleSourceNneAcceptanceTest extends OracleStrictEncryptSourceAcceptanceTest {

@Test
public void testEncrytion() throws SQLException {
final JsonNode clone = Jsons.clone(getConfig());
((ObjectNode) clone).put("encryption", Jsons.jsonNode(ImmutableMap.builder()
.put("encryption_method", "client_nne")
.put("encryption_algorithm", "3DES168")
.build()));

String algorithm = clone.get("encryption")
.get("encryption_algorithm").asText();

JdbcDatabase database = Databases.createJdbcDatabase(clone.get("username").asText(),
clone.get("password").asText(),
String.format("jdbc:oracle:thin:@//%s:%s/%s",
clone.get("host").asText(),
clone.get("port").asText(),
clone.get("sid").asText()),
"oracle.jdbc.driver.OracleDriver",
"oracle.net.encryption_client=REQUIRED;" +
"oracle.net.encryption_types_client=( "
+ algorithm + " )");

String network_service_banner = "select network_service_banner from v$session_connect_info where sid in (select distinct sid from v$mystat)";
List<JsonNode> collect = database.query(network_service_banner).collect(Collectors.toList());

assertTrue(collect.get(2).get("NETWORK_SERVICE_BANNER").asText()
.contains("Oracle Advanced Security: " + algorithm + " encryption"));
}

@Test
public void testCheckProtocol() throws SQLException {
final JsonNode clone = Jsons.clone(getConfig());
((ObjectNode) clone).put("encryption", Jsons.jsonNode(ImmutableMap.builder()
.put("encryption_method", "client_nne")
.put("encryption_algorithm", "AES256")
.build()));

String algorithm = clone.get("encryption")
.get("encryption_algorithm").asText();

JdbcDatabase database = Databases.createJdbcDatabase(clone.get("username").asText(),
clone.get("password").asText(),
String.format("jdbc:oracle:thin:@//%s:%s/%s",
clone.get("host").asText(),
clone.get("port").asText(),
clone.get("sid").asText()),
"oracle.jdbc.driver.OracleDriver",
"oracle.net.encryption_client=REQUIRED;" +
"oracle.net.encryption_types_client=( "
+ algorithm + " )");

String network_service_banner = "SELECT sys_context('USERENV', 'NETWORK_PROTOCOL') as network_protocol FROM dual";
List<JsonNode> collect = database.query(network_service_banner).collect(Collectors.toList());

assertEquals("tcp", collect.get(0).get("NETWORK_PROTOCOL").asText());
}

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

package io.airbyte.integrations.source.oracle_strict_encrypt;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import io.airbyte.commons.json.Jsons;
import io.airbyte.commons.resources.MoreResources;
import io.airbyte.db.Databases;
import io.airbyte.db.jdbc.JdbcDatabase;
import io.airbyte.integrations.base.ssh.SshHelpers;
import io.airbyte.integrations.standardtest.source.SourceAcceptanceTest;
import io.airbyte.integrations.standardtest.source.TestDestinationEnv;
import io.airbyte.protocol.models.CatalogHelpers;
import io.airbyte.protocol.models.ConfiguredAirbyteCatalog;
import io.airbyte.protocol.models.ConfiguredAirbyteStream;
import io.airbyte.protocol.models.ConnectorSpecification;
import io.airbyte.protocol.models.Field;
import io.airbyte.protocol.models.JsonSchemaPrimitive;
import io.airbyte.protocol.models.SyncMode;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.testcontainers.containers.OracleContainer;

public class OracleStrictEncryptSourceAcceptanceTest extends SourceAcceptanceTest {

private static final String STREAM_NAME = "JDBC_SPACE.ID_AND_NAME";
private static final String STREAM_NAME2 = "JDBC_SPACE.STARSHIPS";

protected OracleContainer container;
protected JsonNode config;

@Override
protected void setupEnvironment(TestDestinationEnv environment) throws Exception {
container = new OracleContainer("epiclabs/docker-oracle-xe-11g");
container.start();

config = Jsons.jsonNode(ImmutableMap.builder()
.put("host", container.getHost())
.put("port", container.getFirstMappedPort())
.put("sid", container.getSid())
.put("username", container.getUsername())
.put("password", container.getPassword())
.put("schemas", List.of("JDBC_SPACE"))
.put("encryption", Jsons.jsonNode(ImmutableMap.builder()
.put("encryption_method", "client_nne")
.put("encryption_algorithm", "3DES168")
.build()))
.build());

JdbcDatabase database = Databases.createJdbcDatabase(config.get("username").asText(),
config.get("password").asText(),
String.format("jdbc:oracle:thin:@//%s:%s/%s",
config.get("host").asText(),
config.get("port").asText(),
config.get("sid").asText()),
"oracle.jdbc.driver.OracleDriver",
"oracle.net.encryption_client=REQUIRED;" +
"oracle.net.encryption_types_client=( 3DES168 )");

database.execute(connection -> {
connection.createStatement().execute("CREATE USER JDBC_SPACE IDENTIFIED BY JDBC_SPACE DEFAULT TABLESPACE USERS QUOTA UNLIMITED ON USERS");
connection.createStatement().execute("CREATE TABLE jdbc_space.id_and_name(id NUMERIC(20, 10), name VARCHAR(200), power BINARY_DOUBLE)");
connection.createStatement().execute("INSERT INTO jdbc_space.id_and_name (id, name, power) VALUES (1,'goku', BINARY_DOUBLE_INFINITY)");
connection.createStatement().execute("INSERT INTO jdbc_space.id_and_name (id, name, power) VALUES (2, 'vegeta', 9000.1)");
connection.createStatement().execute("INSERT INTO jdbc_space.id_and_name (id, name, power) VALUES (NULL, 'piccolo', -BINARY_DOUBLE_INFINITY)");
connection.createStatement().execute("CREATE TABLE jdbc_space.starships(id INTEGER, name VARCHAR(200))");
connection.createStatement().execute("INSERT INTO jdbc_space.starships (id, name) VALUES (1,'enterprise-d')");
connection.createStatement().execute("INSERT INTO jdbc_space.starships (id, name) VALUES (2, 'defiant')");
connection.createStatement().execute("INSERT INTO jdbc_space.starships (id, name) VALUES (3, 'yamato')");
});

database.close();
}

@Override
protected void tearDown(TestDestinationEnv testEnv) {
container.close();
}

@Override
protected String getImageName() {
return "airbyte/source-oracle-strict-encrypt:dev";
}

@Override
protected ConnectorSpecification getSpec() throws Exception {
return SshHelpers.injectSshIntoSpec(Jsons.deserialize(MoreResources.readResource("expected_spec.json"), ConnectorSpecification.class));
}

@Override
protected JsonNode getConfig() {
return config;
}

@Override
protected ConfiguredAirbyteCatalog getConfiguredCatalog() {
return new ConfiguredAirbyteCatalog().withStreams(Lists.newArrayList(
new ConfiguredAirbyteStream()
.withSyncMode(SyncMode.INCREMENTAL)
.withCursorField(Lists.newArrayList("ID"))
.withStream(CatalogHelpers.createAirbyteStream(
STREAM_NAME,
Field.of("ID", JsonSchemaPrimitive.NUMBER),
Field.of("NAME", JsonSchemaPrimitive.STRING),
Field.of("POWER", JsonSchemaPrimitive.NUMBER))
.withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH, SyncMode.INCREMENTAL))),
new ConfiguredAirbyteStream()
.withSyncMode(SyncMode.INCREMENTAL)
.withCursorField(Lists.newArrayList("ID"))
.withStream(CatalogHelpers.createAirbyteStream(
STREAM_NAME2,
Field.of("ID", JsonSchemaPrimitive.NUMBER),
Field.of("NAME", JsonSchemaPrimitive.STRING))
.withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH, SyncMode.INCREMENTAL)))));
}

@Override
protected List<String> getRegexTests() {
return Collections.emptyList();
}

@Override
protected JsonNode getState() {
return Jsons.jsonNode(new HashMap<>());
}

}
Loading