diff --git a/lib/ansible/module_utils/azure_rm_common.py b/lib/ansible/module_utils/azure_rm_common.py index e74cf8b0d9..bd52f8558e 100644 --- a/lib/ansible/module_utils/azure_rm_common.py +++ b/lib/ansible/module_utils/azure_rm_common.py @@ -35,7 +35,10 @@ except ImportError: ANSIBLE_VERSION = 'unknown' AZURE_COMMON_ARGS = dict( - cli_default_profile=dict(type='bool'), + auth_source=dict( + type='str', + choices=['auto', 'cli', 'env', 'credential_file'] + ), profile=dict(type='str'), subscription_id=dict(type='str', no_log=True), client_id=dict(type='str', no_log=True), @@ -49,7 +52,6 @@ AZURE_COMMON_ARGS = dict( ) AZURE_CREDENTIAL_ENV_MAPPING = dict( - cli_default_profile='AZURE_CLI_DEFAULT_PROFILE', profile='AZURE_PROFILE', subscription_id='AZURE_SUBSCRIPTION_ID', client_id='AZURE_CLIENT_ID', @@ -134,13 +136,13 @@ except ImportError as exc: HAS_AZURE_EXC = exc HAS_AZURE = False - try: from azure.cli.core.util import CLIError from azure.common.credentials import get_azure_cli_credentials, get_cli_profile from azure.common.cloud import get_cli_active_cloud except ImportError: HAS_AZURE_CLI_CORE = False + CLIError = Exception def azure_id_to_dict(id): @@ -254,8 +256,12 @@ class AzureRMModuleBase(object): # authenticate self.credentials = self._get_credentials(self.module.params) if not self.credentials: - self.fail("Failed to get credentials. Either pass as parameters, set environment variables, " - "or define a profile in ~/.azure/credentials or be logged using AzureCLI.") + if HAS_AZURE_CLI_CORE: + self.fail("Failed to get credentials. Either pass as parameters, set environment variables, " + "define a profile in ~/.azure/credentials, or log in with Azure CLI (`az login`).") + else: + self.fail("Failed to get credentials. Either pass as parameters, set environment variables, " + "define a profile in ~/.azure/credentials, or install Azure CLI and log in (`az login`).") # cert validation mode precedence: module-arg, credential profile, env, "validate" self._cert_validation_mode = self.module.params['cert_validation_mode'] or self.credentials.get('cert_validation_mode') or \ @@ -266,7 +272,9 @@ class AzureRMModuleBase(object): # if cloud_environment specified, look up/build Cloud object raw_cloud_env = self.credentials.get('cloud_environment') - if not raw_cloud_env: + if self.credentials.get('credentials') is not None and raw_cloud_env is not None: + self._cloud_environment = raw_cloud_env + elif not raw_cloud_env: self._cloud_environment = azure_cloud.AZURE_PUBLIC_CLOUD # SDK default else: # try to look up "well-known" values via the name attribute on azure_cloud members @@ -284,19 +292,22 @@ class AzureRMModuleBase(object): except Exception as e: self.fail("cloud_environment {0} could not be resolved: {1}".format(raw_cloud_env, e.message), exception=traceback.format_exc(e)) - if self.credentials.get('subscription_id', None) is None: + if self.credentials.get('subscription_id', None) is None and self.credentials.get('credentials') is None: self.fail("Credentials did not include a subscription_id value.") self.log("setting subscription_id") self.subscription_id = self.credentials['subscription_id'] - if self.credentials.get('client_id') is not None and \ - self.credentials.get('secret') is not None and \ - self.credentials.get('tenant') is not None: - self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'], - secret=self.credentials['secret'], - tenant=self.credentials['tenant'], - cloud_environment=self._cloud_environment, - verify=self._cert_validation_mode == 'validate') + if self.credentials.get('credentials') is not None: + # AzureCLI credentials + self.azure_credentials = self.credentials['credentials'] + elif self.credentials.get('client_id') is not None and \ + self.credentials.get('secret') is not None and \ + self.credentials.get('tenant') is not None: + self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'], + secret=self.credentials['secret'], + tenant=self.credentials['tenant'], + cloud_environment=self._cloud_environment, + verify=self._cert_validation_mode == 'validate') elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None: tenant = self.credentials.get('tenant') @@ -437,19 +448,6 @@ class AzureRMModuleBase(object): except Exception as exc: self.fail("Error retrieving resource group {0} - {1}".format(resource_group, str(exc))) - def _get_azure_cli_profile(self): - if not HAS_AZURE_CLI_CORE: - self.fail("Do you have azure-cli-core installed? Try `pip install 'azure-cli-core' --upgrade`") - try: - credentials, subscription_id = get_azure_cli_credentials() - self._cloud_environment = get_cli_active_cloud() - return { - 'credentials': credentials, - 'subscription_id': subscription_id - } - except CLIError as err: - self.fail("AzureCLI profile cannot be loaded - {0}".format(err)) - def _get_profile(self, profile="default"): path = expanduser("~/.azure/credentials") try: @@ -470,14 +468,22 @@ class AzureRMModuleBase(object): return None + def _get_azure_cli_credentials(self): + credentials, subscription_id = get_azure_cli_credentials() + cloud_environment = get_cli_active_cloud() + + cli_credentials = { + 'credentials': credentials, + 'subscription_id': subscription_id, + 'cloud_environment': cloud_environment + } + return cli_credentials + def _get_env_credentials(self): env_credentials = dict() for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): env_credentials[attribute] = os.environ.get(env_variable, None) - if (env_credentials['cli_default_profile'] or '').lower() in ["true", "yes", "1"]: - return self._get_azure_cli_profile() - if env_credentials['profile']: credentials = self._get_profile(env_credentials['profile']) return credentials @@ -489,18 +495,38 @@ class AzureRMModuleBase(object): def _get_credentials(self, params): # Get authentication credentials. - # Precedence: module parameters-> environment variables-> default profile in ~/.azure/credentials. - self.log('Getting credentials') arg_credentials = dict() for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): arg_credentials[attribute] = params.get(attribute, None) - if arg_credentials['cli_default_profile']: - self.log('Retrieving credentials from Azure CLI current profile') - return self._get_azure_cli_profile() + auth_source = params.get('auth_source', None) + if not auth_source: + auth_source = os.environ.get('ANSIBLE_AZURE_AUTH_SOURCE', 'auto') + if auth_source == 'cli': + if not HAS_AZURE_CLI_CORE: + self.fail("Azure auth_source is `cli`, but azure-cli package is not available. Try `pip install azure-cli --upgrade`") + try: + self.log('Retrieving credentials from Azure CLI profile') + cli_credentials = self._get_azure_cli_credentials() + return cli_credentials + except CLIError as err: + self.fail("Azure CLI profile cannot be loaded - {0}".format(err)) + + if auth_source == 'env': + self.log('Retrieving credentials from environment') + env_credentials = self._get_env_credentials() + return env_credentials + + if auth_source == 'credential_file': + self.log("Retrieving credentials from credential file") + profile = params.get('profile', 'default') + default_credentials = self._get_profile(profile) + return default_credentials + + # auto, precedence: module parameters -> environment variables -> default profile in ~/.azure/credentials # try module params if arg_credentials['profile'] is not None: self.log('Retrieving credentials with profile parameter.') @@ -523,6 +549,14 @@ class AzureRMModuleBase(object): self.log('Retrieved default profile credentials from ~/.azure/credentials.') return default_credentials + try: + if HAS_AZURE_CLI_CORE: + self.log('Retrieving credentials from AzureCLI profile') + cli_credentials = self._get_azure_cli_credentials() + return cli_credentials + except CLIError as ce: + self.log('Error getting AzureCLI profile credentials - {0}'.format(ce)) + return None def serialize_obj(self, obj, class_name, enum_modules=None): diff --git a/lib/ansible/utils/module_docs_fragments/azure.py b/lib/ansible/utils/module_docs_fragments/azure.py index eb2c1100bf..17177915be 100644 --- a/lib/ansible/utils/module_docs_fragments/azure.py +++ b/lib/ansible/utils/module_docs_fragments/azure.py @@ -76,6 +76,20 @@ options: choices: [validate, ignore] default: null version_added: 2.5 + auth_source: + description: + - Controls the source of the credentials to use for authentication. + - C(auto) will follow the default precedence of module parameters -> environment variables -> default profile in credential file + C(~/.azure/credentials). + - When set to C(cli), the credentials will be sources from the default Azure CLI profile. + - Can also be set via the C(ANSIBLE_AZURE_AUTH_SOURCE) environment variable. + choices: + - auto + - cli + - credential_file + - env + default: auto + version_added: 2.5 requirements: - "python >= 2.7" - "azure >= 2.0.0"