Skip to content

Commit 2438abd

Browse files
authored
[Fix] Do not specify --tenant flag when fetching managed identity access token from the CLI (#748)
## Changes Ports databricks/databricks-sdk-go#1021 to the Python SDK. The Azure CLI's az account get-access-token command does not allow specifying --tenant flag if it is authenticated via the CLI. Fixes #742. ## Tests Unit tests ensure that all expected cases are treated as managed identities. - [ ] `make test` run locally - [ ] `make fmt` applied - [ ] relevant integration tests applied
1 parent f06bb27 commit 2438abd

File tree

4 files changed

+109
-7
lines changed

4 files changed

+109
-7
lines changed

databricks/sdk/credentials_provider.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -411,10 +411,7 @@ def _parse_expiry(expiry: str) -> datetime:
411411

412412
def refresh(self) -> Token:
413413
try:
414-
is_windows = sys.platform.startswith('win')
415-
# windows requires shell=True to be able to execute 'az login' or other commands
416-
# cannot use shell=True all the time, as it breaks macOS
417-
out = subprocess.run(self._cmd, capture_output=True, check=True, shell=is_windows)
414+
out = _run_subprocess(self._cmd, capture_output=True, check=True)
418415
it = json.loads(out.stdout.decode())
419416
expires_on = self._parse_expiry(it[self._expiry_field])
420417
return Token(access_token=it[self._access_token_field],
@@ -429,6 +426,26 @@ def refresh(self) -> Token:
429426
raise IOError(f'cannot get access token: {message}') from e
430427

431428

429+
def _run_subprocess(popenargs,
430+
input=None,
431+
capture_output=True,
432+
timeout=None,
433+
check=False,
434+
**kwargs) -> subprocess.CompletedProcess:
435+
"""Runs subprocess with given arguments.
436+
This handles OS-specific modifications that need to be made to the invocation of subprocess.run."""
437+
kwargs['shell'] = sys.platform.startswith('win')
438+
# windows requires shell=True to be able to execute 'az login' or other commands
439+
# cannot use shell=True all the time, as it breaks macOS
440+
logging.debug(f'Running command: {" ".join(popenargs)}')
441+
return subprocess.run(popenargs,
442+
input=input,
443+
capture_output=capture_output,
444+
timeout=timeout,
445+
check=check,
446+
**kwargs)
447+
448+
432449
class AzureCliTokenSource(CliTokenSource):
433450
""" Obtain the token granted by `az login` CLI command """
434451

@@ -437,13 +454,30 @@ def __init__(self, resource: str, subscription: Optional[str] = None, tenant: Op
437454
if subscription is not None:
438455
cmd.append("--subscription")
439456
cmd.append(subscription)
440-
if tenant:
457+
if tenant and not self.__is_cli_using_managed_identity():
441458
cmd.extend(["--tenant", tenant])
442459
super().__init__(cmd=cmd,
443460
token_type_field='tokenType',
444461
access_token_field='accessToken',
445462
expiry_field='expiresOn')
446463

464+
@staticmethod
465+
def __is_cli_using_managed_identity() -> bool:
466+
"""Checks whether the current CLI session is authenticated using managed identity."""
467+
try:
468+
cmd = ["az", "account", "show", "--output", "json"]
469+
out = _run_subprocess(cmd, capture_output=True, check=True)
470+
account = json.loads(out.stdout.decode())
471+
user = account.get("user")
472+
if user is None:
473+
return False
474+
return user.get("type") == "servicePrincipal" and user.get("name") in [
475+
'systemAssignedIdentity', 'userAssignedIdentity'
476+
]
477+
except subprocess.CalledProcessError as e:
478+
logger.debug("Failed to get account information from Azure CLI", exc_info=e)
479+
return False
480+
447481
def is_human_user(self) -> bool:
448482
"""The UPN claim is the username of the user, but not the Service Principal.
449483

tests/test_auth_manual_tests.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from databricks.sdk.core import Config
24

35
from .conftest import set_az_path, set_home
@@ -60,3 +62,13 @@ def test_azure_cli_with_warning_on_stderr(monkeypatch, mock_tenant):
6062
host='https://adb-123.4.azuredatabricks.net',
6163
azure_workspace_resource_id=resource_id)
6264
assert 'X-Databricks-Azure-SP-Management-Token' in cfg.authenticate()
65+
66+
67+
@pytest.mark.parametrize('username', ['systemAssignedIdentity', 'userAssignedIdentity'])
68+
def test_azure_cli_does_not_specify_tenant_id_with_msi(monkeypatch, username):
69+
set_home(monkeypatch, '/testdata/azure')
70+
set_az_path(monkeypatch)
71+
monkeypatch.setenv('FAIL_IF_TENANT_ID_SET', 'true')
72+
monkeypatch.setenv('AZ_USER_NAME', username)
73+
monkeypatch.setenv('AZ_USER_TYPE', 'servicePrincipal')
74+
cfg = Config(auth_type='azure-cli', host='https://adb-123.4.azuredatabricks.net', azure_tenant_id='abc')

tests/testdata/az

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
#!/bin/bash
22

3-
if [ -n "$WARN" ]; then
4-
>&2 /bin/echo "WARNING: ${WARN}"
3+
# If the arguments are "account show", return the account details.
4+
if [ "$1" == "account" ] && [ "$2" == "show" ]; then
5+
/bin/echo "{
6+
\"environmentName\": \"AzureCloud\",
7+
\"id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",
8+
\"isDefault\": true,
9+
\"name\": \"Pay-As-You-Go\",
10+
\"state\": \"Enabled\",
11+
\"tenantId\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",
12+
\"user\": {
13+
\"name\": \"${AZ_USER_NAME:-testuser@databricks.com}\",
14+
\"type\": \"${AZ_USER_TYPE:-user}\"
15+
}
16+
}"
17+
exit 0
518
fi
619

720
if [ "yes" == "$FAIL" ]; then
@@ -26,6 +39,21 @@ for arg in "$@"; do
2639
fi
2740
done
2841

42+
# Add character to file at $COUNT if it is defined.
43+
if [ -n "$COUNT" ]; then
44+
echo -n x >> "$COUNT"
45+
fi
46+
47+
# If FAIL_IF_TENANT_ID_SET is set & --tenant-id is passed, fail.
48+
if [ -n "$FAIL_IF_TENANT_ID_SET" ]; then
49+
for arg in "$@"; do
50+
if [[ "$arg" == "--tenant" ]]; then
51+
echo 1>&2 "ERROR: Tenant shouldn't be specified for managed identity account"
52+
exit 1
53+
fi
54+
done
55+
fi
56+
2957
# Macos
3058
EXP="$(/bin/date -v+${EXPIRE:=10S} +'%F %T' 2>/dev/null)"
3159
if [ -z "${EXP}" ]; then

tests/testdata/windows/az.ps1

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
#!/usr/bin/env pwsh
22

3+
# If the arguments are "account show", return the account details.
4+
if ($args[0] -eq "account" -and $args[1] -eq "show") {
5+
$output = @{
6+
environmentName = "AzureCloud"
7+
id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
8+
isDefault = $true
9+
name = "Pay-As-You-Go"
10+
state = "Enabled"
11+
tenantId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
12+
user = @{
13+
name = if ($env:AZ_USER_NAME) { $env:AZ_USER_NAME } else { "[email protected]" }
14+
type = if ($env:AZ_USER_TYPE) { $env:AZ_USER_TYPE } else { "user" }
15+
}
16+
}
17+
$output | ConvertTo-Json
18+
exit 0
19+
}
20+
321
if ($env:WARN) {
422
Write-Error "WARNING: $env:WARN"
523
}
@@ -30,6 +48,16 @@ foreach ($arg in $Args) {
3048
}
3149
}
3250

51+
# If FAIL_IF_TENANT_ID_SET is set & --tenant-id is passed, fail.
52+
if ($env:FAIL_IF_TENANT_ID_SET) {
53+
foreach ($arg in $args) {
54+
if ($arg -eq "--tenant-id" -or $arg -like "--tenant*") {
55+
Write-Error "ERROR: Tenant shouldn't be specified for managed identity account"
56+
exit 1
57+
}
58+
}
59+
}
60+
3361
try {
3462
$EXP = (Get-Date).AddSeconds($env:EXPIRE -as [int])
3563
} catch {

0 commit comments

Comments
 (0)