Skip to content

Commit

Permalink
add useDefaultGSSCredential property (#2177)
Browse files Browse the repository at this point in the history
* add useDefaultGSSCredential property

* Fix wrong expected useDefaultGSSCredential value

* use default GSS if useDefaultGssCredential true

* Fix awkward R_useDefaultGSSCredentialPropertyDescription SqlServerResource

Co-authored-by: Jeffery Wasty <v-jeffwasty@microsoft.com>

* Update ISQLServerDataSource useDefaultGSSCredential doc string

Co-authored-by: Jeffery Wasty <v-jeffwasty@microsoft.com>

* Revert white space changes

* Added tests

* Corrected credential cleanup; Addressed PR comments

---------

Co-authored-by: Tuan Pham <Tuan.Pham@wisetechglobal.com>
Co-authored-by: Jeffery Wasty <v-jeffwasty@microsoft.com>
Co-authored-by: Terry Chow <v-terrychow@microsoft.com>
  • Loading branch information
4 people committed Oct 16, 2023
1 parent a3178ba commit 3f0f120
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 46 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ allprojects {

test {
useJUnitPlatform {
excludeTags (hasProperty('excludedGroups') ? excludedGroups : 'xSQLv15','xGradle','reqExternalSetup','NTLM','MSI','clientCertAuth','fedAuth')
excludeTags (hasProperty('excludedGroups') ? excludedGroups : 'xSQLv15','xGradle','reqExternalSetup','NTLM','MSI','clientCertAuth','fedAuth','kerberos')
}
}

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
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)
Kerberos - - - - - For tests using Kerberos authentication (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) - - - - - - - - - - - - - - - - - - - - - - -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,17 +594,32 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
*/
String getServerSpn();

/**
* Sets the value to indicate whether useDefaultGSSCredential is enabled.
*
* @param enable
* true if useDefaultGSSCredential is enabled. Otherwise, false.
*/
void setUseDefaultGSSCredential(boolean enable);

/**
* Returns the useDefaultGSSCredential.
*
* @return if enabled, return true. Otherwise, false.
*/
boolean getUseDefaultGSSCredential();

/**
* Sets the GSSCredential.
*
*
* @param userCredential
* the credential
*/
void setGSSCredentials(GSSCredential userCredential);

/**
* Returns the GSSCredential.
*
*
* @return GSSCredential
*/
GSSCredential getGSSCredentials();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ final class KerbAuthentication extends SSPIAuthentication {
private LoginContext lc = null;
private boolean isUserCreatedCredential = false;
private GSSCredential peerCredentials = null;
private boolean useDefaultNativeGSSCredential = false;
private GSSContext peerContext = null;

static {
Expand All @@ -63,6 +64,10 @@ private void initAuthInit() throws SQLServerException {
// as it is.
GSSName remotePeerName = manager.createName(spn, null);

if (useDefaultNativeGSSCredential) {
peerCredentials = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, kerberos, GSSCredential.INITIATE_ONLY);
}

if (null != peerCredentials) {
peerContext = manager.createContext(remotePeerName, kerberos, peerCredentials,
GSSContext.DEFAULT_LIFETIME);
Expand Down Expand Up @@ -220,10 +225,11 @@ private byte[] initAuthHandShake(byte[] pin, boolean[] done) throws SQLServerExc
* @param impersonatedUserCred
*/
KerbAuthentication(SQLServerConnection con, String address, int port, GSSCredential impersonatedUserCred,
boolean isUserCreated) {
boolean isUserCreated, boolean useDefaultNativeGSSCredential) {
this(con, address, port);
this.peerCredentials = impersonatedUserCred;
this.isUserCreatedCredential = isUserCreated;
this.useDefaultNativeGSSCredential = useDefaultNativeGSSCredential;
}

byte[] generateClientContext(byte[] pin, boolean[] done) throws SQLServerException {
Expand All @@ -235,9 +241,9 @@ byte[] generateClientContext(byte[] pin, boolean[] done) throws SQLServerExcepti

void releaseClientContext() {
try {
if (null != peerCredentials && !isUserCreatedCredential) {
if (null != peerCredentials && !isUserCreatedCredential && !useDefaultNativeGSSCredential) {
peerCredentials.dispose();
} else if (null != peerCredentials && isUserCreatedCredential) {
} else if (null != peerCredentials && (isUserCreatedCredential || useDefaultNativeGSSCredential)) {
peerCredentials = null;
}
if (null != peerContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL
static final private java.util.logging.Logger windowsCertificateStoreLogger = java.util.logging.Logger
.getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionCertificateStoreProvider");

static boolean isWindows;

String name = "MSSQL_CERTIFICATE_STORE";

static final String LOCAL_MACHINE_DIRECTORY = "LocalMachine";
Expand All @@ -29,14 +27,6 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL

private static final Lock lock = new ReentrantLock();

static {
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) {
isWindows = true;
} else {
isWindows = false;
}
}

/**
* Constructs a SQLServerColumnEncryptionCertificateStoreProvider.
*/
Expand Down Expand Up @@ -67,7 +57,7 @@ public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryption
windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(),
"decryptColumnEncryptionKey", "Decrypting Column Encryption Key.");
byte[] plainCek;
if (isWindows) {
if (SQLServerConnection.isWindows) {
plainCek = decryptColumnEncryptionKeyWindows(masterKeyPath, encryptionAlgorithm,
encryptedColumnEncryptionKey);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -983,8 +983,10 @@ IdleConnectionResiliency getSessionRecovery() {
/** global system ColumnEncryptionKeyStoreProviders */
static Map<String, SQLServerColumnEncryptionKeyStoreProvider> globalSystemColumnEncryptionKeyStoreProviders = new HashMap<>();

static boolean isWindows = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows");

static {
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) {
if (isWindows) {
SQLServerColumnEncryptionCertificateStoreProvider provider = new SQLServerColumnEncryptionCertificateStoreProvider();
globalSystemColumnEncryptionKeyStoreProviders.put(provider.getName(), provider);
}
Expand Down Expand Up @@ -1426,6 +1428,9 @@ public static void clearUserTokenCache() {
/** integrated authentication scheme */
private AuthenticationScheme intAuthScheme = AuthenticationScheme.NATIVE_AUTHENTICATION;

/** use default native GSS-API Credential flag */
private boolean useDefaultGSSCredential = SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue();

/** impersonated user credential */
private transient GSSCredential impersonatedUserCred;

Expand Down Expand Up @@ -2480,6 +2485,11 @@ Connection connectInternal(Properties propsIn,
impersonatedUserCred = (GSSCredential) activeConnectionProperties.get(sPropKey);
isUserCreatedCredential = true;
}
sPropKey = SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString();
sPropValue = activeConnectionProperties.getProperty(sPropKey);
if(null != sPropValue && isWindows) {
useDefaultGSSCredential = isBooleanPropertyOn(sPropKey, sPropValue);
}
} else if (intAuthScheme == AuthenticationScheme.NTLM) {
String sPropKeyDomain = SQLServerDriverStringProperty.DOMAIN.toString();
String sPropValueDomain = activeConnectionProperties.getProperty(sPropKeyDomain);
Expand Down Expand Up @@ -5097,9 +5107,9 @@ private void logon(LogonCommand command) throws SQLServerException {
authentication = new AuthenticationJNI(this, currentConnectPlaceHolder.getServerName(),
currentConnectPlaceHolder.getPortNumber());
} else if (AuthenticationScheme.JAVA_KERBEROS == intAuthScheme) {
if (null != impersonatedUserCred) {
if (null != impersonatedUserCred || useDefaultGSSCredential) {
authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(),
currentConnectPlaceHolder.getPortNumber(), impersonatedUserCred, isUserCreatedCredential);
currentConnectPlaceHolder.getPortNumber(), impersonatedUserCred, isUserCreatedCredential, useDefaultGSSCredential);
} else {
authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(),
currentConnectPlaceHolder.getPortNumber());
Expand Down Expand Up @@ -5800,8 +5810,7 @@ private SqlAuthenticationToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throw
} else if (authenticationString
.equalsIgnoreCase(SqlAuthentication.ACTIVE_DIRECTORY_INTEGRATED.toString())) {
// If operating system is windows and mssql-jdbc_auth is loaded then choose the DLL authentication.
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")
&& AuthenticationJNI.isDllLoaded()) {
if (isWindows && AuthenticationJNI.isDllLoaded()) {
try {
FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(
fedAuthInfo.stsurl, fedAuthInfo.spn, clientConnectionId.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,17 @@ public GSSCredential getGSSCredentials() {
SQLServerDriverObjectProperty.GSS_CREDENTIAL.getDefaultValue());
}

@Override
public void setUseDefaultGSSCredential(boolean enable) {
setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), enable);
}

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

@Override
public void setAccessToken(String accessToken) {
setStringProperty(connectionProps, SQLServerDriverStringProperty.ACCESS_TOKEN.toString(), accessToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,8 @@ enum SQLServerDriverBooleanProperty {
USE_FMT_ONLY("useFmtOnly", false),
SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY("sendTemporalDataTypesAsStringForBulkCopy", true),
DELAY_LOADING_LOBS("delayLoadingLobs", true),
USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false);
USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false),
USE_DEFAULT_GSS_CREDENTIAL("useDefaultGSSCredential", false);

private final String name;
private final boolean defaultValue;
Expand Down Expand Up @@ -748,7 +749,7 @@ public final class SQLServerDriver implements java.sql.Driver {
SQLServerDriverStringProperty.DATABASE_NAME.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString(),
Boolean.toString(SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.getDefaultValue()), false,
new String[] {"true", "false"}),
TRUE_FALSE),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.ENCRYPT.toString(),
SQLServerDriverStringProperty.ENCRYPT.getDefaultValue(), false,
new String[] {EncryptOption.FALSE.toString(), EncryptOption.NO.toString(),
Expand All @@ -768,6 +769,9 @@ public final class SQLServerDriver implements java.sql.Driver {
new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.toString(),
Boolean.toString(SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.getDefaultValue()), false,
TRUE_FALSE),
new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(),
Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()), false,
TRUE_FALSE),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.toString(),
SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.getDefaultValue(), false,
new String[] {KeyStoreAuthentication.JAVA_KEYSTORE_PASSWORD.toString()}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ protected Object[][] getContents() {
{"R_lastUpdateCountPropertyDescription", "Ensures that only the last update count is returned from an SQL statement passed to the server."},
{"R_disableStatementPoolingPropertyDescription", "Disables the statement pooling feature."},
{"R_integratedSecurityPropertyDescription", "Indicates whether Windows authentication will be used to connect to SQL Server."},
{"R_useDefaultGSSCredentialPropertyDescription", "Indicates whether GSSCredential will be created using native GSS-API."},
{"R_authenticationSchemePropertyDescription", "The authentication scheme to be used for integrated authentication."},
{"R_lockTimeoutPropertyDescription", "The number of milliseconds to wait before the database reports a lock time-out."},
{"R_connectRetryCountPropertyDescription", "The number of reconnection attempts if there is a connection failure."},
Expand Down
55 changes: 35 additions & 20 deletions src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,65 +16,81 @@
import java.util.HashMap;
import java.util.Map;

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

private static String kerberosAuth = "KERBEROS";
private static String authSchemeQuery = "select auth_scheme from sys.dm_exec_connections where session_id=@@spid";

@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));
}
createKerberosConnection(connectionStringUseDefaultJaasConfig);

// 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));
}
createKerberosConnection(connectionStringUseDefaultJaasConfig);
}

@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));
}
createKerberosConnection(connectionStringKerberos);

// 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)) {
try {
createKerberosConnection(connectionStringKerberos);
Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown"));
} catch (SQLServerException e) {
Assertions.assertTrue(e.getMessage()
.contains(TestResource.getResource("R_noLoginModulesConfiguredForJdbcDriver")));
}
}

@Test
public void testUseDefaultNativeGSSCredential() throws Exception {
// This is a negative test. This test should fail as expected as the JVM arg "-Dsun.security.jgss.native=true"
// isn't provided.
String connectionString = connectionStringKerberos + ";useDefaultGSSCredential=true;";

try {
createKerberosConnection(connectionString);
Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown"));
} catch (SQLServerException e) {
Assertions.assertEquals(e.getCause().getMessage(), TestResource.getResource("R_kerberosNativeGSSFailure"));
}
}

@Test
public void testBasicKerberosAuth() throws Exception {
createKerberosConnection(connectionStringKerberos);
}

private static void createKerberosConnection(String connectionString) throws Exception {
try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionString)) {
ResultSet rs = conn.createStatement().executeQuery(authSchemeQuery);
rs.next();
Assertions.assertEquals(kerberosAuth, rs.getString(1));
}
}

/**
* Overwrites the default JAAS config. Call before making a connection.
*/
Expand All @@ -84,7 +100,7 @@ private static void overwriteJaasConfig() {
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
new HashMap<>());
Map<String, AppConfigurationEntry[]> configurationEntries = new HashMap<>();
configurationEntries.put("KAFKA_CLIENT_CONTEXT_NAME",
configurationEntries.put("CLIENT_CONTEXT_NAME",
new AppConfigurationEntry[] { kafkaClientConfigurationEntry });
Configuration.setConfiguration(new InternalConfiguration(configurationEntries));
}
Expand All @@ -100,6 +116,5 @@ private static class InternalConfiguration extends Configuration {
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
return this.configurationEntries.get(name);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ public void testDataSource() throws SQLServerException {
assertEquals(Boolean.toString(booleanPropValue), ds.getEncrypt(),
TestResource.getResource("R_valuesAreDifferent"));

ds.setUseDefaultGSSCredential(booleanPropValue);
assertEquals(booleanPropValue, ds.getUseDefaultGSSCredential(),
TestResource.getResource("R_valuesAreDifferent"));

ds.setServerCertificate(stringPropValue);
assertEquals(stringPropValue, ds.getServerCertificate(), TestResource.getResource("R_valuesAreDifferent"));

Expand Down
Loading

0 comments on commit 3f0f120

Please sign in to comment.