diff --git a/README.md b/README.md index cc6b8a9..d5f4d2a 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ To setup a connection, the driver requires a JDBC connection URL. The connection | logOutput | location where driver logs should be emitted | a valid file path | `null` (logs are disabled) | | logLevel | severity level for which driver logs should be emitted | in order from highest(least logging) to lowest(most logging): OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL | OFF (logs are disabled) | | auth | authentication mechanism to use | `NONE` (no auth), `BASIC` (HTTP Basic), `AWS_SIGV4` (AWS SIGV4) | `basic` if username and/or password is specified, `NONE` otherwise | + | awsCredentialsProvider | The AWS credential provider to be used when authentication mechanism is `AWS_SIGV4` (AWS SIGV4). If not set, the driver will use DefaultAWSCredentialsProviderChain to sign the request. Note that the driver renamed the namespaces of its dependencies, so the value has to be an instance of com.amazonaws.opendistro.elasticsearch.sql.jdbc.shadow.com.amazonaws.auth.AWSCredentialsProvider| Instance of an AWSCredentialProvider | DefaultAWSCredentialsProviderChain | | region | if authentication type is `aws_sigv4`, then this is the region value to use when signing requests. Only needed if the driver can not determine the region for the host endpoint. The driver will detect the region if the host endpoint matches a known url pattern. | a valid AWS region value e.g. us-east-1 | `null` (auto-detected if possible from the host endpoint) | | requestCompression | whether to indicate acceptance of compressed (gzip) responses when making server requests | `true` or `false` | `false` | | useSSL | whether to establish the connection over SSL/TLS | `true` or `false` | `false` if scheme is `http`, `true` if scheme is `https` | @@ -254,6 +255,28 @@ Statement st = con.createStatement(); con.close(); ``` +* Connect to a remote host on default SSL port with AWS Sig V4 authentication, explicitly specifying the AWSCredentialProvider to use + +``` +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; +. +. +String url = "jdbc:elasticsearch://https://remote-host-name"; + +Properties properties = new Properties(); +properties.put("awsCredentialsProvider", new EnvironmentVariableCredentialsProvider()); + +Connection con = DriverManager.getConnection(url, properties); +Statement st = con.createStatement(); +. +// use the connection +. +// close connection +con.close(); +``` + * Connect to a remote host on default SSL port with AWS Sig V4 authentication, explicitly specifying the region to use in the request signing. ``` @@ -401,6 +424,32 @@ Statement st = con.createStatement(); con.close(); ``` +* Connect to a remote host on default SSL port with AWS Sig V4 authentication, explicitly specifying the AWSCredentialProvider to use + +``` +import java.sql.Connection; +import java.sql.Statement; +import javax.sql.DataSource; + +import com.amazon.opendistroforelasticsearch.jdbc.ElasticsearchDataSource; + +. +. +String url = "jdbc:elasticsearch://https://remote-host-name?auth=aws_sigv4®ion=us-west-1"; + +ElasticsearchDataSource ds = new ElasticsearchDataSource(); +ds.setUrl(url); +ds.setAwsCredentialProvider(new EnvironmentVariableCredentialsProvider()); + +Connection con = ds.getConnection(url); +Statement st = con.createStatement(); +. +// use the connection +. +// close connection +con.close(); +``` + * Connect to a remote host on default SSL port with AWS Sig V4 authentication, explicitly specifying the region to use in the request signing. ``` diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ElasticsearchDataSource.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ElasticsearchDataSource.java index df6a7e5..5959324 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ElasticsearchDataSource.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ElasticsearchDataSource.java @@ -16,6 +16,7 @@ package com.amazon.opendistroforelasticsearch.jdbc; +import com.amazon.opendistroforelasticsearch.jdbc.config.AwsCredentialsProviderProperty; import com.amazon.opendistroforelasticsearch.jdbc.config.ConnectionConfig; import com.amazon.opendistroforelasticsearch.jdbc.config.LoginTimeoutConnectionProperty; import com.amazon.opendistroforelasticsearch.jdbc.config.PasswordConnectionProperty; @@ -23,6 +24,7 @@ import com.amazon.opendistroforelasticsearch.jdbc.internal.JdbcWrapper; import com.amazon.opendistroforelasticsearch.jdbc.internal.util.UrlParser; import com.amazon.opendistroforelasticsearch.jdbc.logging.LoggingSource; +import com.amazonaws.auth.AWSCredentialsProvider; import javax.sql.DataSource; import java.io.PrintWriter; @@ -122,6 +124,10 @@ public void setUrl(String url) throws SQLException { throw new SQLException("Invalid connection URL", e); } } + + public void setAwsCredentialProvider(AWSCredentialsProvider awsCredentialProvider) { + connectionProperties.put(AwsCredentialsProviderProperty.KEY, awsCredentialProvider); + } /** * Updates DataSource configuration properties from the specified diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/AwsCredentialsProviderProperty.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/AwsCredentialsProviderProperty.java new file mode 100644 index 0000000..08a178b --- /dev/null +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/AwsCredentialsProviderProperty.java @@ -0,0 +1,31 @@ +package com.amazon.opendistroforelasticsearch.jdbc.config; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; + +public class AwsCredentialsProviderProperty extends ConnectionProperty { + + public static final String KEY = "awsCredentialsProvider"; + + public AwsCredentialsProviderProperty() { + super(KEY); + } + + @Override + public AWSCredentialsProvider getDefault() { + return new DefaultAWSCredentialsProviderChain(); + } + + @Override + protected AWSCredentialsProvider parseValue(Object rawValue) throws ConnectionPropertyException { + if (null == rawValue) { + return null; + } else if (rawValue instanceof AWSCredentialsProvider) { + return (AWSCredentialsProvider) rawValue; + } + + throw new ConnectionPropertyException(getKey(), + String.format("Property \"%s\" requires a valid AWSCredentialsProvider instance. " + + "Invalid value of type: %s specified.", getKey(), rawValue.getClass().getName())); + } +} diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfig.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfig.java index e7443f3..78b5489 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfig.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfig.java @@ -20,6 +20,7 @@ import com.amazon.opendistroforelasticsearch.jdbc.logging.LogLevel; import com.amazon.opendistroforelasticsearch.jdbc.internal.util.AwsHostNameUtil; import com.amazon.opendistroforelasticsearch.jdbc.internal.util.UrlParser; +import com.amazonaws.auth.AWSCredentialsProvider; import java.io.PrintWriter; import java.net.URISyntaxException; @@ -42,6 +43,7 @@ public class ConnectionConfig { private String password; private boolean requestCompression; private AuthenticationType authenticationType; + private AWSCredentialsProvider awsCredentialsProvider; private String region; private LogLevel logLevel; @@ -72,6 +74,7 @@ private ConnectionConfig(Builder builder) { this.requestCompression = builder.getRequestCompressionProperty().getValue(); this.authenticationType = builder.getAuthConnectionProperty().getValue(); + this.awsCredentialsProvider = builder.getAwsCredentialProvider().getValue(); this.region = builder.getRegionConnectionProperty().getValue(); this.keyStoreLocation = builder.getKeyStoreLocationConnectionProperty().getValue(); @@ -139,6 +142,10 @@ public AuthenticationType getAuthenticationType() { return authenticationType; } + public AWSCredentialsProvider getAwsCredentialsProvider() { + return awsCredentialsProvider; + } + public String getRegion() { return region; } @@ -194,6 +201,7 @@ public String toString() { ", password='" + mask(password) + '\'' + ", requestCompression=" + requestCompression + ", authenticationType=" + authenticationType + + ", awsCredentialsProvider=" + awsCredentialsProvider + ", region='" + region + '\'' + ", logLevel=" + logLevel + ", keyStoreLocation='" + keyStoreLocation + '\'' + @@ -244,6 +252,9 @@ public static class Builder { private TrustSelfSignedConnectionProperty trustSelfSignedConnectionProperty = new TrustSelfSignedConnectionProperty(); + private AwsCredentialsProviderProperty awsCredentialsProviderProperty + = new AwsCredentialsProviderProperty(); + private HostnameVerificationConnectionProperty hostnameVerificationConnectionProperty = new HostnameVerificationConnectionProperty(); @@ -259,6 +270,7 @@ public static class Builder { passwordProperty, requestCompressionProperty, authConnectionProperty, + awsCredentialsProviderProperty, regionConnectionProperty, keyStoreLocationConnectionProperty, keyStorePasswordConnectionProperty, @@ -322,6 +334,10 @@ public AuthConnectionProperty getAuthConnectionProperty() { return authConnectionProperty; } + public AwsCredentialsProviderProperty getAwsCredentialProvider() { + return awsCredentialsProviderProperty; + } + public RegionConnectionProperty getRegionConnectionProperty() { return regionConnectionProperty; } diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/transport/http/ApacheHttpTransport.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/transport/http/ApacheHttpTransport.java index 36b5690..c2e8983 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/transport/http/ApacheHttpTransport.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/transport/http/ApacheHttpTransport.java @@ -23,6 +23,7 @@ import com.amazon.opendistroforelasticsearch.jdbc.transport.TransportException; import com.amazon.opendistroforelasticsearch.jdbc.transport.http.auth.aws.AWSRequestSigningApacheInterceptor; import com.amazonaws.auth.AWS4Signer; +import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import org.apache.http.Header; import org.apache.http.auth.AuthScope; @@ -73,7 +74,6 @@ public class ApacheHttpTransport implements HttpTransport, LoggingSource { private RequestConfig requestConfig; private CloseableHttpClient httpClient; - public ApacheHttpTransport(ConnectionConfig connectionConfig, Logger log, String userAgent) throws TransportException { this.host = connectionConfig.getHost(); this.port = connectionConfig.getPort(); @@ -122,11 +122,13 @@ public ApacheHttpTransport(ConnectionConfig connectionConfig, Logger log, String signer.setServiceName("es"); signer.setRegionName(connectionConfig.getRegion()); + AWSCredentialsProvider provider = connectionConfig.getAwsCredentialsProvider() != null ? + connectionConfig.getAwsCredentialsProvider() : new DefaultAWSCredentialsProviderChain(); httpClientBuilder.addInterceptorLast( new AWSRequestSigningApacheInterceptor( "es", signer, - new DefaultAWSCredentialsProviderChain())); + provider)); } // TODO - can apply settings retry & backoff diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/DataSourceTests.java b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/DataSourceTests.java index cf77a50..d0e8e46 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/DataSourceTests.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/DataSourceTests.java @@ -65,6 +65,7 @@ void testDataSourceConfig() throws SQLException { assertNull(config.getUser()); assertNull(config.getPassword()); Assertions.assertEquals(AuthenticationType.NONE, config.getAuthenticationType()); + assertNull(config.getAwsCredentialsProvider()); } @Test diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfigTests.java b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfigTests.java index bccb9cc..27de6de 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfigTests.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfigTests.java @@ -19,6 +19,7 @@ import com.amazon.opendistroforelasticsearch.jdbc.auth.AuthenticationType; import com.amazon.opendistroforelasticsearch.jdbc.internal.util.UrlParser; import com.amazon.opendistroforelasticsearch.jdbc.logging.LogLevel; +import com.amazonaws.auth.EnvironmentVariableCredentialsProvider; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -271,6 +272,17 @@ void testRegionConfig() { "ap-southeast-2"); } + @Test + void testAwsCredentialsProviderConfig() { + assertPropertyRejectsValue(AwsCredentialsProviderProperty.KEY, "Invalid AWS Credentials Provider"); + + // The property accepts null and valid AWSCredentialProvider + assertPropertyAcceptsValue(AwsCredentialsProviderProperty.KEY, ConnectionConfig::getAwsCredentialsProvider, + null); + assertPropertyAcceptsValue(AwsCredentialsProviderProperty.KEY, ConnectionConfig::getAwsCredentialsProvider, + new EnvironmentVariableCredentialsProvider()); + } + @Test void testHostnameVerificationConfig() { assertCommonBooleanPropertyTests(HostnameVerificationConnectionProperty.KEY, ConnectionConfig::hostnameVerification);