Skip to content

Commit

Permalink
Bugfix/196 reenable kerberos in import (#206)
Browse files Browse the repository at this point in the history
* #196: Reenabled Kerberos authentication in `IMPORT`
  • Loading branch information
redcatbear authored Jun 12, 2019
1 parent e03748c commit f823908
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 118 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ Running the Virtual Schema requires a Java Runtime version 8 or later.
| Dependency | Purpose | License |
|-------------------------------------------------------------------------------------|--------------------------------------------------------|-------------------------------|
| [Apache Derby](https://db.apache.org/derby/) | Pure-Java embedded database | Apache License 2.0 |
| [Apache HTTP Components](http://hc.apache.org/) | HTTP communication | Apache License 2.0 |
| [Apache Maven](https://maven.apache.org/) | Build tool | Apache License 2.0 |
| [Equals Verifier](https://jqno.nl/equalsverifier/) | Testing `equals(...)` and `hashCode()` contracts | Apache License 2.0 |
| [Java Hamcrest](http://hamcrest.org/JavaHamcrest/) | Checking for conditions in code via matchers | BSD License |
| [JSONassert](http://jsonassert.skyscreamer.org/) | Compare JSON documents for semantic equality | Apache License 2.0 |
| [JUnit](https://junit.org/junit5) | Unit testing framework | Eclipse Public License 1.0 |
| [Mockito](http://site.mockito.org/) | Mocking framework | MIT License |
| [JUnit 5 System Extensions](https://github.com/itsallcode/junit5-system-extensions) | Capturing `STDOUT` and `STDERR` | Eclipse Public License 2.0 |
| [Equals Verifier](https://jqno.nl/equalsverifier/) | Testing `equals(...)` and `hashCode()` contracts | Apache License 2.0 |
| [Mockito](http://site.mockito.org/) | Mocking framework | MIT License |
| [SnakeYaml](https://bitbucket.org/asomov/snakeyaml/src/default/) | YAML parsing | Apache License 2.0 |
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ public String buildConnectionDefinition(final AdapterProperties properties,
}
}

private String getNamedConnection(final AdapterProperties properties) {
return "AT " + properties.getConnectionName();
}

private String getConnectionFromPropertiesOnly(final AdapterProperties properties) {
warnConnectionPropertiesDeprecated();
return getConnectionDefinition(properties.getConnectionString(), properties.getUsername(),
Expand All @@ -72,4 +68,8 @@ protected boolean hasConflictingConnectionProperties(final AdapterProperties pro
return properties.hasConnectionName()
&& (properties.hasConnectionString() || properties.hasUsername() || properties.hasPassword());
}

private String getNamedConnection(final AdapterProperties properties) {
return "AT " + properties.getConnectionName();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.exasol.adapter.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.*;
import java.util.Properties;
import java.util.logging.Logger;

import com.exasol.ExaConnectionAccessException;
import com.exasol.ExaConnectionInformation;
import com.exasol.ExaMetadata;
import com.exasol.*;
import com.exasol.adapter.AdapterProperties;
import com.exasol.auth.kerberos.KerberosConfigurationCreator;

/**
* Factory that produces JDBC connections to remote data sources
Expand All @@ -19,12 +17,10 @@ public class RemoteConnectionFactory {
/**
* Create a JDBC connection to the remote data source
*
* @param exaMetadata Exasol metadata (contains information about stored *
* connection details)
* @param exaMetadata Exasol metadata (contains information about stored * connection details)
* @param properties user-defined adapter properties
* @return JDBC connection to remote data source
* @throws SQLException if the connection to the remote source could not be
* established
* @throws SQLException if the connection to the remote source could not be established
*/
public Connection createConnection(final ExaMetadata exaMetadata, final AdapterProperties properties)
throws SQLException {
Expand All @@ -45,8 +41,9 @@ private Connection createConnectionWithUserCredentials(final String username, fi
return connection;
}

protected void logConnectionAttempt(final String address, final String user) {
LOGGER.fine(() -> "Connecting to \"" + address + "\" as user \"" + user + "\"");
protected void logConnectionAttempt(final String address, final String username) {
LOGGER.fine(
() -> "Connecting to \"" + address + "\" as user \"" + username + "\" using password authentication.");
}

protected void logRemoteDatabaseDetails(final Connection connection) throws SQLException {
Expand All @@ -59,15 +56,43 @@ private Connection createConnection(final String connectionName, final ExaMetada
throws SQLException {
try {
final ExaConnectionInformation exaConnection = exaMetadata.getConnection(connectionName);
final String password = exaConnection.getPassword();
final String username = exaConnection.getUser();
final String address = exaConnection.getAddress();
logConnectionAttempt(address, username);
final Connection connection = DriverManager.getConnection(address, username, exaConnection.getPassword());
logRemoteDatabaseDetails(connection);
return connection;
if (KerberosConfigurationCreator.isKerberosAuthentication(password)) {
return establishConnectionWithKerberos(password, username, address);
} else {
return establishConnectionWithRegularCredentials(password, username, address);
}
} catch (final ExaConnectionAccessException exception) {
throw new RemoteConnectionException(
"Could not access the connection information of connection \"" + connectionName + "\"", exception);
"Could not access the connection information of connection \"" + connectionName + "\".", exception);
}
}

private Connection establishConnectionWithKerberos(final String password, final String username,
final String address) throws SQLException {
logConnectionAttemptWithKerberos(address, username);
final Properties jdbcProperties = new Properties();
jdbcProperties.put("user", username);
jdbcProperties.put("password", password);
final KerberosConfigurationCreator kerberosConfigurationCreator = new KerberosConfigurationCreator();
kerberosConfigurationCreator.writeKerberosConfigurationFiles(username, password);
final Connection connection = DriverManager.getConnection(address, jdbcProperties);
logRemoteDatabaseDetails(connection);
return connection;
}

private void logConnectionAttemptWithKerberos(final String address, final String username) {
LOGGER.fine(
() -> "Connecting to \"" + address + "\" as user \"" + username + "\" using Kerberos authentication.");
}

private Connection establishConnectionWithRegularCredentials(final String password, final String username,
final String address) throws SQLException {
logConnectionAttempt(address, username);
final Connection connection = DriverManager.getConnection(address, username, password);
logRemoteDatabaseDetails(connection);
return connection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.exasol.auth.kerberos;

import static javax.xml.bind.DatatypeConverter.parseBase64Binary;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.logging.Logger;

/**
* This class generates the necessary configuration for a successful Kerberos authentication.
*
* @see <a href="https://docs.exasol.com/sql/import.htm?Highlight=kerberos">Kerberos authentication for
* <code>IMPORT</code> (Exasol online documentation)</a>
*/
public class KerberosConfigurationCreator {
public static final String USE_SUBJECT_CREDENTIALS_ONLY_PROPERTY = "javax.security.auth.useSubjectCredsOnly";
public static final String KERBEROS_CONFIG_PROPERTY = "java.security.krb5.conf";
public static final String LOGIN_CONFIG_PROPERTY = "java.security.auth.login.config";
public static final String KERBEROS_AUTHENTICATION_PREAMBLE = "ExaAuthType=Kerberos";
private static final Logger LOGGER = Logger.getLogger(KerberosConfigurationCreator.class.getName());

/**
* Check whether the given password contains Kerberos credentials
*
* @param password password / credential string to be examined
* @return <code>true</code> if the password is a Kerberos credential string, <code>false</code> otherwise
*/
public static boolean isKerberosAuthentication(final String password) {
return password.startsWith(KERBEROS_AUTHENTICATION_PREAMBLE);
}

/**
* Create Kerberos configuration and system properties
*
* @param user Kerberos principal
* @param password connection password containing kerberos configuration and key tab
*/
public void writeKerberosConfigurationFiles(final String user, final String password) {
final String[] tokens = password.split(";");
final String preamble = tokens[0];
if ((tokens.length == 3) && KERBEROS_AUTHENTICATION_PREAMBLE.equals(preamble)) {
final String base64EncodedKerberosConfig = tokens[1];
final String base64EncodedKeyTab = tokens[2];
createKerberosConfiguration(user, base64EncodedKerberosConfig, base64EncodedKeyTab);
} else {
throw new KerberosConfigurationCreatorException("Syntax error in Kerberos password."
+ " Must conform to: 'ExaAuthType=Kerberos;<base 64 kerberos config>;<base 64 key tab>'");
}
}

private void createKerberosConfiguration(final String user, final String base64EncodedKerberosConfig,
final String base64EncodedKeyTab) {
try {
final Path temporaryDirectory = createCommonDirectoryForKerberosConfigurationFiles();
final Path kerberosConfigPath = createTemporaryKerberosConfigFile(base64EncodedKerberosConfig,
temporaryDirectory);
final Path keyTabPath = createTemporaryKeyTabFile(base64EncodedKeyTab, temporaryDirectory);
final Path jaasConfigPath = createTemporaryJaasConfig(temporaryDirectory, user, keyTabPath);
setKerberosSystemProperties(kerberosConfigPath, jaasConfigPath);
} catch (final IOException exception) {
throw new KerberosConfigurationCreatorException("Unable to create temporary Kerberos configuration file.",
exception);
}
}

private Path createCommonDirectoryForKerberosConfigurationFiles() throws IOException {
final Path temporaryDirectory = Files.createTempDirectory("kerberos_");
temporaryDirectory.toFile().deleteOnExit();
LOGGER.finer(() -> "Created temporary directory \"" + temporaryDirectory
+ "\" to contain Kerberos authentication files.");
return temporaryDirectory;
}

private Path createTemporaryKerberosConfigFile(final String base64EncodedKerberosConfig,
final Path temporaryDirectory) throws IOException {
return createTemporaryFile(temporaryDirectory, "krb_", ".conf", parseBase64Binary(base64EncodedKerberosConfig));
}

private Path createTemporaryKeyTabFile(final String base64EncodedKeyTab, final Path temporaryDirectory)
throws IOException {
return createTemporaryFile(temporaryDirectory, "kt_", ".keytab", parseBase64Binary(base64EncodedKeyTab));
}

private Path createTemporaryFile(final Path temporaryDirectory, final String prefix, final String suffix,
final byte[] content) throws IOException {
final Path temporaryFile = Files.createTempFile(temporaryDirectory, prefix, suffix);
temporaryFile.toFile().deleteOnExit();
Files.write(temporaryFile, content);
LOGGER.finer(
() -> "Wrote " + content.length + " bytes to Kerberos configuration file \"" + temporaryFile + "\".");
return temporaryFile;
}

private Path createTemporaryJaasConfig(final Path temporaryDirectory, final String user, final Path keyTabPath)
throws IOException {
final byte[] content = ("Client {\n" //
+ "com.sun.security.auth.module.Krb5LoginModule required\n" //
+ "principal=\"" + user + "\"\n" //
+ "useKeyTab=true\n" //
+ "keyTab=\"" + keyTabPath + "\"\n" //
+ "doNotPrompt=true\n" //
+ "useTicketCache=false;\n" //
+ "};\n" //
+ "com.sun.security.jgss.initiate {\n" //
+ "com.sun.security.auth.module.Krb5LoginModule required\n" //
+ "principal=\"" + user + "\"\n" //
+ "useKeyTab=true\n" //
+ "keyTab=\"" + keyTabPath + "\"\n" //
+ "doNotPrompt=true\n" //
+ "useTicketCache=false;\n" //
+ "};\n").getBytes();
return createTemporaryFile(temporaryDirectory, "jaas_", ".conf", content);
}

private void setKerberosSystemProperties(final Path kerberosConfigPath, final Path jaasConfigPath) {
System.setProperty(KERBEROS_CONFIG_PROPERTY, kerberosConfigPath.toString());
System.setProperty(LOGIN_CONFIG_PROPERTY, jaasConfigPath.toString());
System.setProperty(USE_SUBJECT_CREDENTIALS_ONLY_PROPERTY, "false");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.exasol.auth.kerberos;

/**
* Exception for errors occurring during creation of the Kerberos configuration or its files
*/
public class KerberosConfigurationCreatorException extends RuntimeException {
private static final long serialVersionUID = -7910268166902081246L;

/**
* Create a new instance of a {@link KerberosConfigurationCreatorException}
*
* @param message error message
*/
public KerberosConfigurationCreatorException(final String message) {
super(message);
}

/**
* Create a new instance of a {@link KerberosConfigurationCreatorException}
*
* @param message error message
* @param cause cause
*/
public KerberosConfigurationCreatorException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.exasol.adapter.dialects;

import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.text.MatchesPattern.matchesPattern;
import static org.junit.Assert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,16 @@ public DummyRemoteMetadataReader(final Connection connection, final AdapterPrope

@Override
protected ColumnMetadataReader createColumnMetadataReader() {
// TODO Auto-generated method stub
return null;
}

@Override
protected TableMetadataReader createTableMetadataReader() {
// TODO Auto-generated method stub
return null;
}

@Override
protected IdentifierConverter createIdentifierConverter() {
// TODO Auto-generated method stub
return null;
}

}
}
Loading

0 comments on commit f823908

Please sign in to comment.