Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/azure-cli-core/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

Release History
===============

2.0.34
++++++
* Minor fixes.
* core: support cross tenant resource referencing
* Improve telemetry upload reliability
1. Remove retry. Once failed stop uploading.
2. Update the process start configuration to prevent upload process from blocking the CLI process.
Expand Down
21 changes: 18 additions & 3 deletions src/azure-cli-core/azure/cli/core/_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,14 +439,21 @@ def _try_parse_msi_account_name(account):
return parts[0], (None if len(parts) <= 1 else parts[1])
return None, None

def get_login_credentials(self, resource=None,
subscription_id=None):
def get_login_credentials(self, resource=None, subscription_id=None, aux_subscriptions=None):
account = self.get_subscription(subscription_id)
user_type = account[_USER_ENTITY][_USER_TYPE]
username_or_sp_id = account[_USER_ENTITY][_USER_NAME]
resource = resource or self.cli_ctx.cloud.endpoints.active_directory_resource_id

identity_type, identity_id = Profile._try_parse_msi_account_name(account)

external_tenants_info = []
ext_subs = [aux_sub for aux_sub in (aux_subscriptions or []) if aux_sub != subscription_id]
for ext_sub in ext_subs:
sub = self.get_subscription(ext_sub)
if sub[_TENANT_ID] != account[_TENANT_ID]:
external_tenants_info.append((sub[_USER_ENTITY][_USER_NAME], sub[_TENANT_ID]))

if identity_type is None:
def _retrieve_token():
if in_cloud_console() and account[_USER_ENTITY].get(_CLOUD_SHELL_ID):
Expand All @@ -455,8 +462,16 @@ def _retrieve_token():
return self._creds_cache.retrieve_token_for_user(username_or_sp_id,
account[_TENANT_ID], resource)
return self._creds_cache.retrieve_token_for_service_principal(username_or_sp_id, resource)

def _retrieve_tokens_from_external_tenants():
external_tokens = []
for u, t in external_tenants_info:
external_tokens.append(self._creds_cache.retrieve_token_for_user(u, t, resource))
return external_tokens

from azure.cli.core.adal_authentication import AdalAuthentication
auth_object = AdalAuthentication(_retrieve_token)
auth_object = AdalAuthentication(_retrieve_token,
_retrieve_tokens_from_external_tenants if external_tenants_info else None)
else:
if self._msi_creds is None:
self._msi_creds = MsiAccountTypes.msi_auth_factory(identity_type, identity_id, resource)
Expand Down
10 changes: 8 additions & 2 deletions src/azure-cli-core/azure/cli/core/adal_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@

class AdalAuthentication(Authentication): # pylint: disable=too-few-public-methods

def __init__(self, token_retriever):
def __init__(self, token_retriever, external_tenant_token_retriever=None):
self._token_retriever = token_retriever
self._external_tenant_token_retriever = external_tenant_token_retriever

def signed_session(self, session=None):
session = session or super(AdalAuthentication, self).signed_session()

external_tenant_tokens = None
try:
scheme, token, _ = self._token_retriever()
if self._external_tenant_token_retriever:
external_tenant_tokens = self._external_tenant_token_retriever()
except CLIError as err:
if in_cloud_console():
AdalAuthentication._log_hostname()
Expand All @@ -40,6 +43,9 @@ def signed_session(self, session=None):

header = "{} {}".format(scheme, token)
session.headers['Authorization'] = header
if external_tenant_tokens:
aux_tokens = ';'.join(['{} {}'.format(scheme2, tokens2) for scheme2, tokens2, _ in external_tenant_tokens])
session.headers['x-ms-authorization-auxiliary'] = aux_tokens
return session

@staticmethod
Expand Down
14 changes: 11 additions & 3 deletions src/azure-cli-core/azure/cli/core/commands/client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ def resolve_client_arg_name(operation, kwargs):


def get_mgmt_service_client(cli_ctx, client_or_resource_type, subscription_id=None, api_version=None,
**kwargs):
aux_subscriptions=None, **kwargs):
"""
:params subscription_id: the current account's subscription
:param aux_subscriptions: mainly for cross tenant scenarios, say vnet peering.
"""
sdk_profile = None
if isinstance(client_or_resource_type, (ResourceType, CustomResourceType)):
# Get the versioned client
Expand All @@ -57,7 +61,9 @@ def get_mgmt_service_client(cli_ctx, client_or_resource_type, subscription_id=No
# Get the non-versioned client
client_type = client_or_resource_type
client, _ = _get_mgmt_service_client(cli_ctx, client_type, subscription_id=subscription_id,
api_version=api_version, sdk_profile=sdk_profile, **kwargs)
api_version=api_version, sdk_profile=sdk_profile,
aux_subscriptions=aux_subscriptions,
**kwargs)
return client


Expand Down Expand Up @@ -104,12 +110,14 @@ def _get_mgmt_service_client(cli_ctx,
base_url_bound=True,
resource=None,
sdk_profile=None,
aux_subscriptions=None,
**kwargs):
from azure.cli.core._profile import Profile
logger.debug('Getting management service client client_type=%s', client_type.__name__)
resource = resource or cli_ctx.cloud.endpoints.active_directory_resource_id
profile = Profile(cli_ctx=cli_ctx)
cred, subscription_id, _ = profile.get_login_credentials(subscription_id=subscription_id, resource=resource)
cred, subscription_id, _ = profile.get_login_credentials(subscription_id=subscription_id, resource=resource,
aux_subscriptions=aux_subscriptions)

client_kwargs = {}
if base_url_bound:
Expand Down
67 changes: 47 additions & 20 deletions src/azure-cli-core/azure/cli/core/tests/test_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,26 +82,6 @@ def setUpClass(cls):
'Q8U2g9kXHrbYFeY2gJxF_hnfLvNKxUKUBnftmyYxZwKi0GDS0BvdJnJnsqSRSpxUx__Ra9QJkG1IaDzj'
'ZcSZPHK45T6ohK9Hk9ktZo0crVl7Tmw')

cls.test_cloud_shell_msi_access_token = (
'yJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IlNTUWRoSTFjS3ZoUUVEU0p4RTJnR1lzNDBRMCIsImtpZCI6IlNTUWRoSTFjS3'
'ZoUUVEU0p4RTJnR1lzNDBRMCJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwcz'
'ovL3N0cy53aW5kb3dzLm5ldC81NDgyNmIyMi0zOGQ2LTRmYjItYmFkOS1iN2I5M2EzZTljNWEvIiwiaWF0IjoxNTIwMjgzODI3LCJuYmY'
'iOjE1MjAyODM4MjcsImV4cCI6MTUyMDI4ODAyNiwiYWNyIjoiMSIsImFpbyI6IkFWUUFxLzhHQUFBQXppd1c2VE1heElJeGxxVkR3TnAx'
'MkZvNG5IeVc3NnFXd0ZlS2VlanlYTmdrRUFlckNBM1JoQ0ZLU3VMOGRaQXVBQnd6cTErOTgzdlRoK1dHMTdqa0NWSWVtN1JwYXU5M3Zla'
'2RVbkxxdVpRPSIsImFsdHNlY2lkIjoiNTo6MTAwMzAwMDA4MDFDNDREMyIsImFtciI6WyJyc2EiXSwiYXBwaWQiOiJiNjc3YzI5MC1jZj'
'RiLTRhOGUtYTYwZS05MWJhNjUwYTRhYmUiLCJhcHBpZGFjciI6IjIiLCJlX2V4cCI6MjYzMDk5LCJlbWFpbCI6Inl1Z2FuZ3dAbWljcm9'
'zb2Z0LmNvbSIsImZhbWlseV9uYW1lIjoiV2FuZyIsImdpdmVuX25hbWUiOiJZdWdhbmciLCJncm91cHMiOlsiZTRiYjBiNTYtMTAxNC00'
'MGY4LTg4YWItM2Q4YThjYjBlMDg2Il0sImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiL'
'TJkN2NkMDExZGI0Ny8iLCJpcGFkZHIiOiIxNjcuMjIwLjEuMjM0IiwibmFtZSI6Ill1Z2FuZyBXYW5nIiwib2lkIjoiODllZDViZTgtZm'
'Y5Ny00MWI1LWFiMTEtMDU1ZTFlM2NjMzRiIiwicHVpZCI6IjEwMDNCRkZEOTU5Rjg5NTUiLCJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24'
'iLCJzdWIiOiIyRFhuT05jNUVBcjZhXzNVcmtSUmJRQXZHbnh6cUFhLUhMVnMxcld3Z3RJIiwidGlkIjoiNTQ4MjZiMjItMzhkNi00ZmIy'
'LWJhZDktYjdiOTNhM2U5YzVhIiwidW5pcXVlX25hbWUiOiJ5dWdhbmd3QG1pY3Jvc29mdC5jb20iLCJ1dGkiOiJESGNDOFQwYkJrLTh5W'
'VB2cjlBQ0FBIiwidmVyIjoiMS4wIiwid2lkcyI6WyI2MmU5MDM5NC02OWY1LTQyMzctOTE5MC0wMTIxNzcxNDVlMTAiXX0.U5rdKCPd_3'
'EsleHmZhWaYe19I3jNzFSwvzn84f8cExXbgxkK-X8ejkE_J4A_SufHnaI1x_QHgEIpbIz6RD99tUyccI-emNCpJpM7Ucfhl779gAOdVzy'
'75Nc87RhXOXVObNlfvay_BKJ3bDEcayXeoRcPRa2uJ-4c8t6rAqFAHi8UrxOOo2lTTJqhWWlLJ00qY3y31MJQqR_ThwMyaHrORgrnMS6_'
'2if0WIg9-BMDbZYiSOIHKJApZNBi2W1Bl-S4FIkh_e70QWQn1h5p1D8eGmnI1vSyCwb6PpIYW93vldYe0Q4hketRlDXyGlOmRZywN7eHZ'
'qUGFKxJnyEx9rKrvg')

def test_normalize(self):
cli = TestCli()
storage_mock = {'subscriptions': None}
Expand Down Expand Up @@ -469,6 +449,53 @@ def test_get_login_credentials(self, mock_get_token, mock_read_cred_file):
'https://management.core.windows.net/')
self.assertEqual(mock_get_token.call_count, 1)

@mock.patch('azure.cli.core._profile._load_tokens_from_file', autospec=True)
@mock.patch('azure.cli.core._profile.CredsCache.retrieve_token_for_user', autospec=True)
def test_get_login_credentials_aux_subscriptions(self, mock_get_token, mock_read_cred_file):
cli = TestCli()
raw_token2 = 'some...secrets2'
token_entry2 = {
"resource": "https://management.core.windows.net/",
"tokenType": "Bearer",
"_authority": "https://login.microsoftonline.com/common",
"accessToken": raw_token2,
}
some_token_type = 'Bearer'
mock_read_cred_file.return_value = [TestProfile.token_entry1, token_entry2]
mock_get_token.side_effect = [(some_token_type, TestProfile.raw_token1), (some_token_type, raw_token2)]
# setup
storage_mock = {'subscriptions': None}
profile = Profile(cli_ctx=cli, storage=storage_mock, use_global_creds_cache=False, async_persist=False)
test_subscription_id = '12345678-1bf0-4dda-aec3-cb9272f09590'
test_subscription_id2 = '12345678-1bf0-4dda-aec3-cb9272f09591'
test_tenant_id = '12345678-38d6-4fb2-bad9-b7b93a3e1234'
test_tenant_id2 = '12345678-38d6-4fb2-bad9-b7b93a3e4321'
test_subscription = SubscriptionStub('/subscriptions/{}'.format(test_subscription_id),
'MSI-DEV-INC', self.state1, test_tenant_id)
test_subscription2 = SubscriptionStub('/subscriptions/{}'.format(test_subscription_id2),
'MSI-DEV-INC2', self.state1, test_tenant_id2)
consolidated = profile._normalize_properties(self.user1,
[test_subscription, test_subscription2],
False)
profile._set_subscriptions(consolidated)
# action
cred, subscription_id, _ = profile.get_login_credentials(subscription_id=test_subscription_id,
aux_subscriptions=[test_subscription_id2])

# verify
self.assertEqual(subscription_id, test_subscription_id)

# verify the cred._tokenRetriever is a working lambda
token_type, token = cred._token_retriever()
self.assertEqual(token, self.raw_token1)
self.assertEqual(some_token_type, token_type)

token2 = cred._external_tenant_token_retriever()
self.assertEqual(len(token2), 1)
self.assertEqual(token2[0][1], raw_token2)

self.assertEqual(mock_get_token.call_count, 2)

@mock.patch('azure.cli.core._profile._load_tokens_from_file', autospec=True)
@mock.patch('msrestazure.azure_active_directory.MSIAuthentication', autospec=True)
def test_get_login_credentials_msi_system_assigned(self, mock_msi_auth, mock_read_cred_file):
Expand Down
3 changes: 3 additions & 0 deletions src/command_modules/azure-cli-network/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Release History
===============
2.1.3
++++++
* `network vnet peering`: a few improvements

2.1.2
++++++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
# --------------------------------------------------------------------------------------------


def network_client_factory(cli_ctx, **_):
def network_client_factory(cli_ctx, aux_subscriptions=None, **_):
from azure.cli.core.profiles import ResourceType
from azure.cli.core.commands.client_factory import get_mgmt_service_client
return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_NETWORK)
return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_NETWORK,
aux_subscriptions=aux_subscriptions)


def resource_client_factory(cli_ctx, **_):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ def load_command_table(self, _):
client_factory=cf_packet_capture
)

network_custom = CliCommandType(operations_tmpl='azure.cli.command_modules.network.custom#{}')

# endregion

# region NetworkRoot
Expand Down Expand Up @@ -624,7 +626,7 @@ def _make_singular(value):
g.command('show', 'get', exception_handler=empty_on_404)
g.command('list', 'list')
g.command('delete', 'delete')
g.generic_update_command('update', setter_arg_name='virtual_network_peering_parameters')
g.generic_update_command('update', setter_name='update_vnet_peering', setter_type=network_custom)

with self.command_group('network vnet subnet', network_subnet_sdk) as g:
g.command('delete', 'delete')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2918,9 +2918,19 @@ def create_vnet_peering(cmd, resource_group_name, virtual_network_name, virtual_
allow_gateway_transit=allow_gateway_transit,
allow_forwarded_traffic=allow_forwarded_traffic,
use_remote_gateways=use_remote_gateways)
ncf = network_client_factory(cmd.cli_ctx)
aux_subscription = parse_resource_id(remote_virtual_network)['subscription']
ncf = network_client_factory(cmd.cli_ctx, aux_subscriptions=[aux_subscription])
return ncf.virtual_network_peerings.create_or_update(
resource_group_name, virtual_network_name, virtual_network_peering_name, peering)


def update_vnet_peering(cmd, resource_group_name, virtual_network_name, virtual_network_peering_name, **kwargs):
peering = kwargs['parameters']
aux_subscription = parse_resource_id(peering.remote_virtual_network.id)['subscription']
ncf = network_client_factory(cmd.cli_ctx, aux_subscriptions=[aux_subscription])
return ncf.virtual_network_peerings.create_or_update(
resource_group_name, virtual_network_name, virtual_network_peering_name, peering)

# endregion


Expand Down
2 changes: 1 addition & 1 deletion src/command_modules/azure-cli-network/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
logger.warn("Wheel is not available, disabling bdist_wheel hook")
cmdclass = {}

VERSION = "2.1.2"
VERSION = "2.1.3"
CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
Expand Down