From a51498a46f2c53db07e9180ad0279efefd55cc19 Mon Sep 17 00:00:00 2001 From: yugangw-msft Date: Thu, 17 May 2018 09:39:32 -0700 Subject: [PATCH 1/3] vnet peering --- src/azure-cli-core/HISTORY.rst | 3 + src/azure-cli-core/azure/cli/core/_profile.py | 20 +++++- .../azure/cli/core/adal_authentication.py | 10 ++- .../azure/cli/core/commands/client_factory.py | 14 +++- .../azure/cli/core/tests/test_profile.py | 67 +++++++++++++------ .../azure-cli-network/HISTORY.rst | 3 + .../network/_client_factory.py | 5 +- .../cli/command_modules/network/commands.py | 4 +- .../cli/command_modules/network/custom.py | 12 +++- .../azure-cli-network/setup.py | 2 +- 10 files changed, 107 insertions(+), 33 deletions(-) diff --git a/src/azure-cli-core/HISTORY.rst b/src/azure-cli-core/HISTORY.rst index 10109ff1ca2..11ac5e8afb4 100644 --- a/src/azure-cli-core/HISTORY.rst +++ b/src/azure-cli-core/HISTORY.rst @@ -2,6 +2,9 @@ Release History =============== +2.0.34 +++++++ +* core: support cross tenant resource referencing 2.0.34 ++++++ diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index 511cdbd5c27..47084647d15 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -439,14 +439,20 @@ 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 = [] + for s in [x for x in (aux_subscriptions or []) if x != subscription_id]: + a = self.get_subscription(s) + if a[_TENANT_ID] != account[_TENANT_ID]: + external_tenants_info.append((a[_USER_ENTITY][_USER_NAME], a[_TENANT_ID])) + if identity_type is None: def _retrieve_token(): if in_cloud_console() and account[_USER_ENTITY].get(_CLOUD_SHELL_ID): @@ -455,8 +461,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) diff --git a/src/azure-cli-core/azure/cli/core/adal_authentication.py b/src/azure-cli-core/azure/cli/core/adal_authentication.py index 223fb479baa..4dc0c88873b 100644 --- a/src/azure-cli-core/azure/cli/core/adal_authentication.py +++ b/src/azure-cli-core/azure/cli/core/adal_authentication.py @@ -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() @@ -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 diff --git a/src/azure-cli-core/azure/cli/core/commands/client_factory.py b/src/azure-cli-core/azure/cli/core/commands/client_factory.py index 961adb89afb..eb4ef9f7e8a 100644 --- a/src/azure-cli-core/azure/cli/core/commands/client_factory.py +++ b/src/azure-cli-core/azure/cli/core/commands/client_factory.py @@ -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 @@ -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 @@ -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: diff --git a/src/azure-cli-core/azure/cli/core/tests/test_profile.py b/src/azure-cli-core/azure/cli/core/tests/test_profile.py index 64bec82e825..3858d1bae56 100644 --- a/src/azure-cli-core/azure/cli/core/tests/test_profile.py +++ b/src/azure-cli-core/azure/cli/core/tests/test_profile.py @@ -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} @@ -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): diff --git a/src/command_modules/azure-cli-network/HISTORY.rst b/src/command_modules/azure-cli-network/HISTORY.rst index 6ae1a7956ca..04e7c3ca63a 100644 --- a/src/command_modules/azure-cli-network/HISTORY.rst +++ b/src/command_modules/azure-cli-network/HISTORY.rst @@ -2,6 +2,9 @@ Release History =============== +2.1.3 +++++++ +* `network vnet peering`: a few improvements 2.1.2 ++++++ diff --git a/src/command_modules/azure-cli-network/azure/cli/command_modules/network/_client_factory.py b/src/command_modules/azure-cli-network/azure/cli/command_modules/network/_client_factory.py index 3dc503a0b0d..7ca3ec8ab12 100644 --- a/src/command_modules/azure-cli-network/azure/cli/command_modules/network/_client_factory.py +++ b/src/command_modules/azure-cli-network/azure/cli/command_modules/network/_client_factory.py @@ -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, **_): diff --git a/src/command_modules/azure-cli-network/azure/cli/command_modules/network/commands.py b/src/command_modules/azure-cli-network/azure/cli/command_modules/network/commands.py index a54c37cd21c..f53df2f1574 100644 --- a/src/command_modules/azure-cli-network/azure/cli/command_modules/network/commands.py +++ b/src/command_modules/azure-cli-network/azure/cli/command_modules/network/commands.py @@ -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 @@ -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') diff --git a/src/command_modules/azure-cli-network/azure/cli/command_modules/network/custom.py b/src/command_modules/azure-cli-network/azure/cli/command_modules/network/custom.py index b6ab0d2c8bd..94fef6eeddd 100644 --- a/src/command_modules/azure-cli-network/azure/cli/command_modules/network/custom.py +++ b/src/command_modules/azure-cli-network/azure/cli/command_modules/network/custom.py @@ -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 diff --git a/src/command_modules/azure-cli-network/setup.py b/src/command_modules/azure-cli-network/setup.py index 55a451b8a25..83a8f0e5b07 100644 --- a/src/command_modules/azure-cli-network/setup.py +++ b/src/command_modules/azure-cli-network/setup.py @@ -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', From a77e5ac0c4c66fcf9037df5f16dc9e3b14308f3e Mon Sep 17 00:00:00 2001 From: Yugang Wang Date: Thu, 31 May 2018 09:37:59 -0700 Subject: [PATCH 2/3] fix history doc --- src/azure-cli-core/HISTORY.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/azure-cli-core/HISTORY.rst b/src/azure-cli-core/HISTORY.rst index 11ac5e8afb4..79e46df4a1a 100644 --- a/src/azure-cli-core/HISTORY.rst +++ b/src/azure-cli-core/HISTORY.rst @@ -5,10 +5,6 @@ Release History 2.0.34 ++++++ * core: support cross tenant resource referencing - -2.0.34 -++++++ -* Minor fixes. * 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. From fadc4e21966815c7a50e2b7fb0ddce4636a9dcd1 Mon Sep 17 00:00:00 2001 From: Yugang Wang Date: Thu, 31 May 2018 11:53:41 -0700 Subject: [PATCH 3/3] address review feedback --- src/azure-cli-core/azure/cli/core/_profile.py | 9 +++++---- .../azure/cli/core/commands/client_factory.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index 47084647d15..18f25933ead 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -448,10 +448,11 @@ def get_login_credentials(self, resource=None, subscription_id=None, aux_subscri identity_type, identity_id = Profile._try_parse_msi_account_name(account) external_tenants_info = [] - for s in [x for x in (aux_subscriptions or []) if x != subscription_id]: - a = self.get_subscription(s) - if a[_TENANT_ID] != account[_TENANT_ID]: - external_tenants_info.append((a[_USER_ENTITY][_USER_NAME], a[_TENANT_ID])) + 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(): diff --git a/src/azure-cli-core/azure/cli/core/commands/client_factory.py b/src/azure-cli-core/azure/cli/core/commands/client_factory.py index eb4ef9f7e8a..9aaf86d4ac5 100644 --- a/src/azure-cli-core/azure/cli/core/commands/client_factory.py +++ b/src/azure-cli-core/azure/cli/core/commands/client_factory.py @@ -45,10 +45,10 @@ def resolve_client_arg_name(operation, kwargs): def get_mgmt_service_client(cli_ctx, client_or_resource_type, subscription_id=None, api_version=None, 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