Skip to content

Commit

Permalink
Adding a new connection property "useDefaultJaasConfig" to coexist wi…
Browse files Browse the repository at this point in the history
…th solutions that overwrite the system JAAS (#2147)

* Added a new connection property "useDefaultJaasConfig"

* New option to allow the JDBC driver to perform Kerberos authentication using its builtin JaasConfiguration(), to easily coexist with an external JAAS configuration that does not provide a SQLJDBCDriver Login module configuration.

* Warning is printed if the jaasConfigurationName is non-default at the same time as useDefaultJaasConfig, as the jaasConfigurationName will not be used.

* Added tests for useDefaultJaasConfig

* PR comments

---------

Co-authored-by: Terry Chow <[email protected]>
Co-authored-by: Terry Chow <[email protected]>
  • Loading branch information
3 people authored Oct 11, 2023
1 parent 5ac4e61 commit 3a7daf3
Show file tree
Hide file tree
Showing 18 changed files with 197 additions and 12 deletions.
7 changes: 4 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@
xAzureSQLDB - - - - For tests not compatible with Azure SQL Database - -
xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse -
xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance
NTLM - - - - - - For tests using NTLM Authentication mode (excluded by default)
reqExternalSetup - For tests requiring external setup (excluded by default)
NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by default)
Kerberos - - - - - For tests using Kerberos authentication (excluded by default)
reqExternalSetup - For tests requiring external setup (excluded by default)
clientCertAuth - - For tests requiring client certificate authentication
setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Default testing enabled with SQL Server 2019 (SQLv15) -->
<excludedGroups>xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth</excludedGroups>
<excludedGroups>xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth,kerberos</excludedGroups>
<!-- Use -preview for preview release, leave empty for official release. -->
<releaseExt>-preview</releaseExt>
<!-- Driver Dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
int getSocketTimeout();

/**
* Sets the login configuration file for Kerberos authentication. This overrides the default configuration <i>
* Sets the login configuration name for Kerberos authentication. This overrides the default configuration <i>
* SQLJDBCDriver </i>
*
* @param configurationName
Expand All @@ -814,7 +814,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
void setJASSConfigurationName(String configurationName);

/**
* Returns the login configuration file for Kerberos authentication.
* Returns the login configuration name for Kerberos authentication.
*
*
* @return login configuration file name
Expand All @@ -825,7 +825,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
String getJASSConfigurationName();

/**
* Sets the login configuration file for Kerberos authentication. This overrides the default configuration <i>
* Sets the login configuration name for Kerberos authentication. This overrides the default configuration <i>
* SQLJDBCDriver </i>
*
*
Expand All @@ -835,12 +835,27 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
void setJAASConfigurationName(String configurationName);

/**
* Returns the login configuration file for Kerberos authentication.
* Returns the login configuration name for Kerberos authentication.
*
* @return login configuration file name
* @return login configuration name
*/
String getJAASConfigurationName();

/**
* Returns whether the default JAAS Configuration should be used
*
* @return useDefaultJaasConfig boolean value
*/
boolean getUseDefaultJaasConfig();

/**
* Sets whether the default JAAS Configuration will be used. This means the system-wide JAAS configuration
* is ignored to avoid conflicts with libraries that override the JAAS configuration.
*
* @param useDefaultJaasConfig
* boolean property to use the default JAAS configuration
*/
void setUseDefaultJaasConfig(boolean useDefaultJaasConfig);
/**
* Sets whether Fips Mode should be enabled/disabled on the connection. For FIPS enabled JVM this property should be
* true.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,31 @@ private void initAuthInit() throws SQLServerException {
String configName = con.activeConnectionProperties.getProperty(
SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(),
SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue());
boolean useDefaultJaas = Boolean.parseBoolean(con.activeConnectionProperties.getProperty(
SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue())));

if (!configName.equals(
SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue()) && useDefaultJaas) {
// Reset configName to default -- useDefaultJaas setting takes priority over jaasConfigName
if (authLogger.isLoggable(Level.WARNING)) {
authLogger.warning(toString() + String.format(
"Using default JAAS configuration, configured %s=%s will not be used.",
SQLServerDriverStringProperty.JAAS_CONFIG_NAME, configName));
}
configName = SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue();
}
Subject currentSubject;
KerbCallback callback = new KerbCallback(con);
try {
AccessControlContext context = AccessController.getContext();
currentSubject = Subject.getSubject(context);
if (null == currentSubject) {
lc = new LoginContext(configName, callback);
if (useDefaultJaas) {
lc = new LoginContext(configName, null, callback, new JaasConfiguration(null));
} else {
lc = new LoginContext(configName, callback);
}
lc.login();
// per documentation LoginContext will instantiate a new subject.
currentSubject = lc.getSubject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,18 @@ public String getJAASConfigurationName() {
SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue());
}

@Override
public boolean getUseDefaultJaasConfig() {
return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue());
}

@Override
public void setUseDefaultJaasConfig(boolean useDefaultJaasConfig) {
setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
useDefaultJaasConfig);
}

/**
* @deprecated This method is deprecated. Use {@link SQLServerDataSource#setUser(String user)} instead.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,8 @@ enum SQLServerDriverBooleanProperty {
USE_BULK_COPY_FOR_BATCH_INSERT("useBulkCopyForBatchInsert", false),
USE_FMT_ONLY("useFmtOnly", false),
SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY("sendTemporalDataTypesAsStringForBulkCopy", true),
DELAY_LOADING_LOBS("delayLoadingLobs", true);
DELAY_LOADING_LOBS("delayLoadingLobs", true),
USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false);

private final String name;
private final boolean defaultValue;
Expand Down Expand Up @@ -892,6 +893,9 @@ public final class SQLServerDriver implements java.sql.Driver {
null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(),
SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue()), false,
TRUE_FALSE),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SSL_PROTOCOL.toString(),
SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue(), false,
new String[] {SSLProtocol.TLS.toString(), SSLProtocol.TLS_V10.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,8 @@ protected Object[][] getContents() {
{"R_kerberosLoginFailedForUsername", "Cannot login with Kerberos principal {0}, check your credentials. {1}"},
{"R_kerberosLoginFailed", "Kerberos Login failed: {0} due to {1} ({2})"},
{"R_StoredProcedureNotFound", "Could not find stored procedure ''{0}''."},
{"R_jaasConfigurationNamePropertyDescription", "Login configuration file for Kerberos authentication."},
{"R_jaasConfigurationNamePropertyDescription", "Login configuration name for Kerberos authentication."},
{"R_useDefaultJaasConfigPropertyDescription", "Use the default JAAS configuration for Kerberos authentication."},
{"R_AKVKeyNotFound", "Key not found: {0}"},
{"R_SQLVariantSupport", "SQL_VARIANT is not supported in versions of SQL Server before 2008."},
{"R_invalidProbbytes", "SQL_VARIANT: invalid probBytes for {0} type."},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
@Tag(Constants.xSQLv12)
@Tag(Constants.xAzureSQLDB)
@Tag(Constants.xAzureSQLDW)
@Tag(Constants.reqExternalSetup)
public class BulkCopySendTemporalDataTypesAsStringAETest extends AESetup {
static String inputFile = "BulkCopyCSVSendTemporalDataTypesAsStringForBulkCopy.csv";
static String encoding = "UTF-8";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
@Tag(Constants.xSQLv12)
@Tag(Constants.xAzureSQLDW)
@Tag(Constants.xAzureSQLDB)
@Tag(Constants.reqExternalSetup)
public class CallableStatementTest extends AESetup {

private static String multiStatementsProcedure = AbstractSQLGenerator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
@Tag(Constants.xSQLv12)
@Tag(Constants.xAzureSQLDW)
@Tag(Constants.xAzureSQLDB)
@Tag(Constants.reqExternalSetup)
public class JDBCEncryptionDecryptionTest extends AESetup {
private boolean nullable = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
@Tag(Constants.xSQLv12)
@Tag(Constants.xAzureSQLDW)
@Tag(Constants.xAzureSQLDB)
@Tag(Constants.reqExternalSetup)
public class MultiUserAKVTest extends AESetup {

private static Map<String, SQLServerColumnEncryptionKeyStoreProvider> requiredKeyStoreProvider = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
@Tag(Constants.xSQLv11)
@Tag(Constants.xSQLv12)
@Tag(Constants.xSQLv14)
@Tag(Constants.reqExternalSetup)
public class ParameterMetaDataCacheTest extends AESetup {

@BeforeAll
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
@Tag(Constants.xSQLv12)
@Tag(Constants.xAzureSQLDW)
@Tag(Constants.xAzureSQLDB)
@Tag(Constants.reqExternalSetup)
public class PrecisionScaleTest extends AESetup {
private static java.util.Date date = null;
private static int offsetFromGMT = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
@Tag(Constants.xSQLv12)
@Tag(Constants.xAzureSQLDW)
@Tag(Constants.xAzureSQLDB)
@Tag(Constants.reqExternalSetup)
public class RegressionAlwaysEncryptedTest extends AESetup {
static String numericTable[][] = {{"Bit", "bit"}, {"Tinyint", "tinyint"}, {"Smallint", "smallint"},};

Expand Down
105 changes: 105 additions & 0 deletions src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.microsoft.sqlserver.jdbc;

import com.microsoft.sqlserver.testframework.AbstractTest;
import com.microsoft.sqlserver.testframework.Constants;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;

@RunWith(JUnitPlatform.class)
public class KerberosTest extends AbstractTest {

private static String kerberosAuth = "KERBEROS";

@BeforeAll
public static void setupTests() throws Exception {
setConnection();
}

@Tag(Constants.Kerberos)
@Test
public void testUseDefaultJaasConfigConnectionStringPropertyTrue() throws Exception {
String connectionStringUseDefaultJaasConfig = connectionStringKerberos + ";useDefaultJaasConfig=true;";

// Initial connection should succeed with default JAAS config
try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionStringUseDefaultJaasConfig)) {
ResultSet rs = conn.createStatement().executeQuery("select auth_scheme from sys.dm_exec_connections where session_id=@@spid");
rs.next();
Assertions.assertEquals(kerberosAuth, rs.getString(1));
}

// Attempt to overwrite JAAS config. Since useDefaultJaasConfig=true, this should have no effect
// and subsequent connections should succeed.
overwriteJaasConfig();

// New connection should successfully connect and continue to use the default JAAS config.
try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionStringUseDefaultJaasConfig)) {
ResultSet rs = conn.createStatement().executeQuery("select auth_scheme from sys.dm_exec_connections where session_id=@@spid");
rs.next();
Assertions.assertEquals(kerberosAuth, rs.getString(1));
}
}

@Tag(Constants.Kerberos)
@Test
public void testUseDefaultJaasConfigConnectionStringPropertyFalse() throws Exception {

// useDefaultJaasConfig=false by default
// Initial connection should succeed with default JAAS config
try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionStringKerberos)) {
ResultSet rs = conn.createStatement().executeQuery("select auth_scheme from sys.dm_exec_connections where session_id=@@spid");
rs.next();
Assertions.assertEquals(kerberosAuth, rs.getString(1));
}

// Overwrite JAAS config. Since useDefaultJaasConfig=false, overwriting should succeed and have an effect.
// Subsequent connections will fail.
overwriteJaasConfig();

// New connection should fail as it is attempting to connect using an overwritten JAAS config.
try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionStringKerberos)) {
Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown"));
} catch (SQLServerException e) {
Assertions.assertTrue(e.getMessage()
.contains(TestResource.getResource("R_noLoginModulesConfiguredForJdbcDriver")));
}
}

/**
* Overwrites the default JAAS config. Call before making a connection.
*/
private static void overwriteJaasConfig() {
AppConfigurationEntry kafkaClientConfigurationEntry = new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
new HashMap<>());
Map<String, AppConfigurationEntry[]> configurationEntries = new HashMap<>();
configurationEntries.put("KAFKA_CLIENT_CONTEXT_NAME",
new AppConfigurationEntry[] { kafkaClientConfigurationEntry });
Configuration.setConfiguration(new InternalConfiguration(configurationEntries));
}

private static class InternalConfiguration extends Configuration {
private final Map<String, AppConfigurationEntry[]> configurationEntries;

InternalConfiguration(Map<String, AppConfigurationEntry[]> configurationEntries) {
this.configurationEntries = configurationEntries;
}

@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
return this.configurationEntries.get(name);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ public void testDataSource() throws SQLServerException {
ds.setJAASConfigurationName(stringPropValue);
assertEquals(stringPropValue, ds.getJAASConfigurationName(), TestResource.getResource("R_valuesAreDifferent"));

ds.setUseDefaultJaasConfig(booleanPropValue);
assertEquals(booleanPropValue, ds.getUseDefaultJaasConfig(), TestResource.getResource("R_valuesAreDifferent"));

ds.setMSIClientId(stringPropValue);
assertEquals(stringPropValue, ds.getMSIClientId(), TestResource.getResource("R_valuesAreDifferent"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,5 +203,6 @@ protected Object[][] getContents() {
{"R_connectTimedOut", "connect timed out"},
{"R_sessionKilled", "Cannot continue the execution because the session is in the kill state"},
{"R_failedFedauth", "Failed to acquire fedauth token: "},
{"R_noLoginModulesConfiguredForJdbcDriver", "javax.security.auth.login.LoginException (No LoginModules configured for SQLJDBCDriver)"},
{"R_unexpectedThreadCount", "Thread count is higher than expected."}};
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ public abstract class AbstractTest {
protected static String[] enclaveAttestationUrl = null;
protected static String[] enclaveAttestationProtocol = null;


protected static String kerberosServer = null;
protected static String kerberosServerPort = null;

protected static String clientCertificate = null;
protected static String clientKey = null;
protected static String clientKeyPassword = "";
Expand Down Expand Up @@ -103,6 +107,7 @@ public abstract class AbstractTest {
protected static Connection connectionAzure = null;
protected static String connectionString = null;
protected static String connectionStringNTLM;
protected static String connectionStringKerberos;

protected static ConfidentialClientApplication fedauthClientApp = null;

Expand Down Expand Up @@ -193,6 +198,9 @@ public static void setup() throws Exception {

clientKeyPassword = getConfiguredProperty("clientKeyPassword", "");

kerberosServer = getConfiguredProperty("kerberosServer", null);
kerberosServerPort = getConfiguredProperty("kerberosServerPort", null);

trustStore = getConfiguredProperty("trustStore", "");
if (!trustStore.trim().isEmpty()) {
connectionString = TestUtils.addOrOverrideProperty(connectionString, "trustStore", trustStore);
Expand Down Expand Up @@ -250,7 +258,7 @@ public static void setup() throws Exception {
protected static void setupConnectionString() {
connectionStringNTLM = connectionString;

// if these properties are defined then NTLM is desired, modify connection string accordingly
// If these properties are defined then NTLM is desired, modify connection string accordingly
String domain = getConfiguredProperty("domainNTLM");
String user = getConfiguredProperty("userNTLM");
String password = getConfiguredProperty("passwordNTLM");
Expand All @@ -273,6 +281,14 @@ protected static void setupConnectionString() {
connectionStringNTLM = TestUtils.addOrOverrideProperty(connectionStringNTLM, "integratedSecurity", "true");
}

if (null != kerberosServer && null != kerberosServerPort) {
connectionStringKerberos = "jdbc:sqlserver://" + kerberosServer + ":" + kerberosServerPort + ";";
connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "authenticationScheme", "JavaKerberos");
connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "integratedSecurity", "true");
connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "trustServerCertificate", "true");
connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "encrypt", "false");
}

ds = updateDataSource(connectionString, new SQLServerDataSource());
dsXA = updateDataSource(connectionString, new SQLServerXADataSource());
dsPool = updateDataSource(connectionString, new SQLServerConnectionPoolDataSource());
Expand Down
Loading

0 comments on commit 3a7daf3

Please sign in to comment.