Added new authentication keyword to support AAD Service Principal (#1199)

This commit is contained in:
Jenny Tam 2020-10-08 12:30:04 -07:00 committed by GitHub
parent e0a6b52f7a
commit 142629a1cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 284 additions and 26 deletions

View file

@ -1,7 +1,7 @@
sudo: required
os: linux
dist: trusty
dist: bionic
group: edge

View file

@ -13,6 +13,9 @@ variables:
trigger:
- dev
pr:
- dev
jobs:
- job: macOS
pool:
@ -30,8 +33,8 @@ jobs:
- script: |
brew tap
brew tap homebrew/core
brew install autoconf automake libtool
brew install php@$(phpVersion)
brew reinstall autoconf automake libtool
brew reinstall php@$(phpVersion)
php -v
displayName: 'Install PHP'

View file

@ -187,7 +187,7 @@ void conn_string_parser::add_key_value_pair( _In_reads_(len) const char* value,
memcpy_s( option, len + 1, value, len );
option[len] = '\0';
valid = core_is_authentication_option_valid( option, len );
valid = AzureADOptions::isAuthValid(option, len);
}
}
if( !valid ) {

View file

@ -379,7 +379,7 @@ pdo_error PDO_ERRORS[] = {
},
{
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.", -73, false }
{ IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectorySPA is supported.", -73, false }
},
{
SQLSRV_ERROR_CE_DRIVER_REQUIRED,

View file

@ -709,18 +709,38 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v
return true;
}
// core_is_authentication_option_valid
// if the option for the authentication is valid, returns true. This returns false otherwise.
bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t value_len)
{
if (value_len <= 0)
return false;
namespace AzureADOptions {
enum AAD_AUTH_TYPE {
MIN_AAD_AUTH_TYPE = 0,
SQL_PASSWORD = 0,
AAD_PASSWORD,
AAD_MSI,
AAD_SPA,
MAX_AAD_AUTH_TYPE
};
if (!stricmp(value, AzureADOptions::AZURE_AUTH_SQL_PASSWORD) || !stricmp(value, AzureADOptions::AZURE_AUTH_AD_PASSWORD) || !stricmp(value, AzureADOptions::AZURE_AUTH_AD_MSI)) {
return true;
const char *AADAuths[] = { "SqlPassword", "ActiveDirectoryPassword", "ActiveDirectoryMsi", "ActiveDirectorySPA" };
bool isAuthValid(_In_z_ const char* value, _In_ size_t value_len)
{
if (value_len <= 0)
return false;
bool isValid = false;
for (short i = MIN_AAD_AUTH_TYPE; i < MAX_AAD_AUTH_TYPE && !isValid; i++)
{
if (!stricmp(value, AADAuths[i])) {
isValid = true;
}
}
return isValid;
}
return false;
bool isAADMsi(_In_z_ const char* value)
{
return (value != NULL && !stricmp(value, AADAuths[AAD_MSI]));
}
}
@ -789,9 +809,9 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
option = Z_STRVAL_P(auth_option);
}
if (option != NULL && !stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) {
activeDirectoryMSI = true;
//if (option != NULL && !stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) {
activeDirectoryMSI = AzureADOptions::isAADMsi(option);
if (activeDirectoryMSI) {
// There are two types of managed identities:
// (1) A system-assigned managed identity: UID must be NULL
// (2) A user-assigned managed identity: UID defined but must not be an empty string

View file

@ -191,9 +191,8 @@ const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34;
const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7;
namespace AzureADOptions {
const char AZURE_AUTH_SQL_PASSWORD[] = "SqlPassword";
const char AZURE_AUTH_AD_PASSWORD[] = "ActiveDirectoryPassword";
const char AZURE_AUTH_AD_MSI[] = "ActiveDirectoryMsi";
bool isAuthValid(_In_z_ const char* value, _In_ size_t value_len);
bool isAADMsi(_In_z_ const char* value);
}
// the message returned by ODBC Driver for SQL Server
@ -1288,7 +1287,6 @@ void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval *se
void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info );
bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t value_len );
size_t core_str_zval_is_true( _Inout_ zval* str_zval );
bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t value_len );
bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version );
bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN r, _In_ const char* error_state );

View file

@ -1383,7 +1383,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In
bool valid = true;
if( stricmp( SS_CONN_OPTS[i].sqlsrv_name, SSConnOptionNames::Authentication ) == 0 ) {
valid = core_is_authentication_option_valid( value, value_len );
valid = AzureADOptions::isAuthValid(value, value_len);
}
CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[i].sqlsrv_name ) {

View file

@ -365,7 +365,7 @@ ss_error SS_ERRORS[] = {
},
{
SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.", -62, false }
{ IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectorySPA is supported.", -62, false }
},
{
SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED,

View file

@ -24,6 +24,8 @@ $adServer = 'TARGET_AD_SERVER';
$adDatabase = 'TARGET_AD_DATABASE';
$adUser = 'TARGET_AD_USERNAME';
$adPassword = 'TARGET_AD_PASSWORD';
$adSPClientId = 'TARGET_ADSP_CLIENT_ID';
$adSPClientSecret = 'TARGET_ADSP_CLIENT_SECRET';
$driverType = true;
$driver = "ODBC Driver 17 for SQL Server";

View file

@ -96,5 +96,5 @@ if ($azureServer != 'TARGET_AD_SERVER') {
Connected successfully with Authentication=SqlPassword.
string(1) "%d"
Could not connect with Authentication=ActiveDirectoryIntegrated.
SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.
SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectorySPA is supported.
%s with Authentication=ActiveDirectoryPassword.

View file

@ -0,0 +1,117 @@
--TEST--
Test some basics of Azure AD service principal support
--SKIPIF--
<?php
require_once('skipif.inc');
require_once('MsSetup.inc');
try {
$conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd);
$msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"];
$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0];
$msodbcsqlMin = explode(".", $msodbcsqlVer)[1];
if ($msodbcsqlMaj < 17 || $msodbcsqlMin < 7) {
die("skip: Requires ODBC driver 17.7 or above");
}
} catch (PDOException $e) {
die("skip: Failed to connect in skipif.");
}
?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function simpleTest($conn)
{
// Create table
$tableName = 'pdoTestSPA';
$col1 = 'Testing service principal with pdo';
dropTable($conn, $tableName);
$query = "CREATE TABLE $tableName(ID INT IDENTITY(1,1), COL1 VARCHAR(50))";
$stmt = $conn->query($query);
// Insert one row
$query = "INSERT INTO $tableName VALUES ('$col1')";
$stmt = $conn->query($query);
// Fetch data
$query = "SELECT * FROM $tableName";
$stmt = $conn->query($query);
$result = $stmt->fetch(PDO::FETCH_NUM);
$id = $result[0];
if ($id != 1) {
echo "AzureAD service principal test: fetched id $id unexpected\n";
}
$field = $result[1];
if ($field !== $col1) {
echo "AzureAD service principal test: fetched value $field unexpected\n";
}
dropTable($conn, $tableName);
}
function connectAzureDB($showException)
{
global $adServer, $adDatabase, $adSPClientId, $adSPClientSecret, $maxAttempts;
$conn = false;
try {
$connectionInfo = "Database = $adDatabase; Authentication = ActiveDirectorySPA;";
$conn = new PDO("sqlsrv:server = $adServer; $connectionInfo", $adSPClientId, $adSPClientSecret);
} catch (PDOException $e) {
if ($showException) {
echo "Could not connect with Azure AD Service Principal after $maxAttempts retries.\n";
print_r($e->getMessage());
echo PHP_EOL;
}
}
return $conn;
}
// First test connecting to regular sql server
require_once('MsSetup.inc');
try {
$conn = new PDO("sqlsrv:server = $server; Authentication = ActiveDirectorySPA;", $uid, $pwd);
echo "Expect regular connection to fail\n";
} catch(PDOException $e) {
// do nothing
}
// Next, test connecting with a valid service principal and perform some simple tasks
$maxAttempts = 3;
try {
if ($adServer != 'TARGET_AD_SERVER' && $adSPClientId != 'TARGET_ADSP_CLIENT_ID') {
$conn = false;
$numAttempts = 0;
do {
$conn = connectAzureDB($numAttempts == ($maxAttempts - 1));
if ($conn === false) {
$numAttempts++;
sleep(10);
}
} while ($conn === false && $numAttempts < $maxAttempts);
// Proceed when successfully connected
if ($conn) {
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true);
simpleTest($conn);
unset($conn);
}
}
} catch(PDOException $e) {
print_r($e->getMessage());
echo PHP_EOL;
}
echo "Done\n";
?>
--EXPECT--
Done

View file

@ -31,6 +31,8 @@ $adServer = 'TARGET_AD_SERVER';
$adDatabase = 'TARGET_AD_DATABASE';
$adUser = 'TARGET_AD_USERNAME';
$adPassword = 'TARGET_AD_PASSWORD';
$adSPClientId = 'TARGET_ADSP_CLIENT_ID';
$adSPClientSecret = 'TARGET_ADSP_CLIENT_SECRET';
if (isset($_ENV['MSSQL_SERVER']) || isset($_ENV['MSSQL_USER']) || isset($_ENV['MSSQL_PASSWORD'])) {
$server = $_ENV['MSSQL_SERVER'];

View file

@ -106,7 +106,7 @@ Array
[SQLSTATE] => IMSSP
[1] => -62
[code] => -62
[2] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.
[message] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.
[2] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectorySPA is supported.
[message] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectorySPA is supported.
)
%s with Authentication=ActiveDirectoryPassword.

View file

@ -0,0 +1,116 @@
--TEST--
Test some basics of Azure AD Service Principal support
--SKIPIF--
<?php
require_once('skipif.inc');
require_once('MsSetup.inc');
$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver);
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
die("skip: Failed to connect in skipif.");
}
$msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer'];
$version = explode(".", $msodbcsqlVer);
if ($version[0] < 17 || $version[1] < 7) {
die("skip: Requires ODBC driver 17.7 or above");
}
?>
--FILE--
<?php
require_once('MsCommon.inc');
function simpleTest($conn)
{
// Create table
$tableName = 'testSPA';
$col1 = 'Testing service principal';
dropTable($conn, $tableName);
$query = "CREATE TABLE $tableName(ID INT IDENTITY(1,1), COL1 VARCHAR(50))";
$stmt = sqlsrv_query($conn, $query);
if (!$stmt) {
fatalError("AzureAD service principal test: failed to create a table\n");
}
// Insert one row
$query = "INSERT INTO $tableName VALUES ('$col1')";
$stmt = sqlsrv_query($conn, $query);
if (!$stmt) {
fatalError("AzureAD service principal test: failed to insert a row\n");
}
// Fetch data
$query = "SELECT * FROM $tableName";
$stmt = sqlsrv_query($conn, $query);
if (!$stmt) {
fatalError("AzureAD service principal test: failed to fetch a table\n");
}
while (sqlsrv_fetch($stmt)) {
$id = sqlsrv_get_field($stmt, 0);
if ($id != 1) {
fatalError("AzureAD service principal test: fetched id $id unexpected\n");
}
$field = sqlsrv_get_field($stmt, 1);
if ($field !== $col1) {
fatalError("AzureAD service principal test: fetched value $field unexpected\n");
}
}
dropTable($conn, $tableName);
}
function connectAzureDB($showException)
{
global $adServer, $adDatabase, $adSPClientId, $adSPClientSecret, $maxAttempts;
$conn = false;
$connectionInfo = array("Database"=>$adDatabase,
"Authentication"=>"ActiveDirectorySPA",
"UID"=>$adSPClientId,
"PWD"=>$adSPClientSecret);
$conn = sqlsrv_connect($adServer, $connectionInfo);
if ($conn === false) {
if ($showException) {
fatalError("Could not connect with Azure AD Service Principal after $maxAttempts retries.\n");
}
} else {
simpleTest($conn);
sqlsrv_close($conn);
}
return $conn;
}
// Try connecting to an invalid server. Expect this to fail.
$connectionInfo = array("Authentication"=>"ActiveDirectorySPA");
$conn = sqlsrv_connect('invalidServer', $connectionInfo);
if ($conn) {
fatalError("AzureAD Service Principal test: expected to fail with invalidServer\n");
}
// Next, test connecting with Service Principal
$maxAttempts = 3;
if ($adServer != 'TARGET_AD_SERVER' && $adSPClientId != 'TARGET_ADSP_CLIENT_ID') {
$conn = false;
$numAttempts = 0;
do {
$conn = connectAzureDB($numAttempts == ($maxAttempts - 1));
if ($conn === false) {
$numAttempts++;
sleep(10);
}
} while ($conn === false && $numAttempts < $maxAttempts);
}
echo "Done\n";
?>
--EXPECT--
Done