Conversation
//cc: @lmazuel @jianghaolu |
program_files_folder = os.environ.get('ProgramFiles(x86)') or os.environ.get('ProgramFiles') | ||
probing_paths = [os.path.join(program_files_folder, 'Microsoft SDKs', 'Azure', 'CLI2', 'wbin', 'az.cmd')] | ||
else: | ||
probing_paths = ['/usr/bin/az', '/usr/local/bin/az'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't work if CLI was installed by pip. You could subprocess "which az", or use "shutil.which('az')" starting Python 3 (probably the right approach is "shutil.which('az')" on Python 3 and fallback to subprocess otherwise).
https://docs.python.org/3/library/shutil.html?highlight=.which#shutil.which
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use the presence of a ${HOME}/.azure directory as the hint to try the CLI creds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...or, alternatively, use the ${PATH} environment variable...
|
||
def signed_session(self, session=None): | ||
# Token cache is handled by the VM extension, call each time to avoid expiration | ||
self.set_token() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should, the CLI be called each request to refresh the token?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you meant sub-shelling is kind of slow? Yes, I can optimize by leveraging the expiresOn
information returned from the get-access-token
command
tests/test_local_creds_prober.py
Outdated
from msrestazure.azure_local_creds_prober import AzureLocalCredentialProber | ||
|
||
prober = AzureLocalCredentialProber() | ||
client = StorageManagementClient(prober, prober.subscription_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to use the factory style I have right now:
from azure.common.client_factory import get_client_from_cli_profile
from azure.mgmt.compute import ComputeManagementClient
client = get_client_from_cli_profile(ComputeManagementClient)
but something more generic like:
from azure.common.client_factory import get_client
from azure.mgmt.compute import ComputeManagementClient
client = get_client(ComputeManagementClient)
class AzureLocalCredentialProber(object): | ||
''' | ||
Probing logics: | ||
1. Managed service identity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The progression should be from more specific to less specific. This is the way I'd define the specificity of the different sources of information:
Environment variables > local file > local endpoint available.
Following this, does this: 2, 3, 1, 4 make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started with the most secure option, then follow up with SDK home made ones. But you have a point, so I will adjust. Also it is unlikely people would set env variable in a VM with identity
Explicit environment variables should come before managed identity like @johanste says cause we should prefer explicit intent. That's what the specs say too :). Rather than a global "Prober", could each credentials type include a static Can we document the names of keys in the connection string and names of environment variables? We should use the same names in all SDKs. |
We will need to get tokens for resources other than AzureRM, e.g. Storage and Key Vault. Can the constructor be parameterized to take a resource URL and default to "https://management.azure.com"? |
Each specific SDK has the resource uri defined inside, e.g. go-sdk , so this is not a concern. |
Hi folks, i have addressed all feedback, please take another look. |
def probe(self): | ||
creds = None | ||
if self.enabled and os.environ.get('AZURE_CONNECTION_STRING'): | ||
auth_info = json.loads(os.environ.get('AZURE_CONNECTION_STRING')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the connection string JSON valid? I thought it was something like:
AZURE_CONNECTION_STRING="client_id=1234;client_secret=2345;tenant_id=3456;subscription_id=4567;"
return None | ||
|
||
def probe_subscription(self, creds): | ||
return json.loads(os.environ.get('AZURE_CONNECTION_STRING'))['subscriptionId'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question about JSON, and I thought too it should be subscription_id
else: | ||
cloud_environment = AZURE_PUBLIC_CLOUD | ||
resource = self.resource or cloud_environment.endpoints.management | ||
creds = ServicePrincipalCredentials(client_id=auth_info['clientId'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
client_id?
try: | ||
creds = MSIAuthentication() | ||
# MSI is not yet supported in sovereigns | ||
setattr(creds, 'cloud_environment', AZURE_PUBLIC_CLOUD) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1/ PUBLIC AZURE is the default anyway 2/ we read cloud_environemnent from the kwargs, so should be passed to the init if we want to
creds = MSIAuthentication() | ||
# MSI is not yet supported in sovereigns | ||
setattr(creds, 'cloud_environment', AZURE_PUBLIC_CLOUD) | ||
creds.resource = self.resource or AZURE_PUBLIC_CLOUD.endpoints.management |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently default is:
self.resource = kwargs.get('resource', self.cloud_environment.endpoints.active_directory_resource_id)
Is it wrong?
def probe_subscription(self, creds): # pylint: disable=no-self-use | ||
subscription_id = None | ||
try: | ||
from azure.mgmt.resource.subscriptions import SubscriptionClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to assume resource client is available. Especially the code here will use latest API version, which is a bad idea for stack.
I would prefer to hard code this one with a requests call, like I did for auto-registration of RP
_LOGGER.warning('You also have accesses to a few other subscriptions "%S".' | ||
' You can supply subscription_id on invoking probers') | ||
except ImportError: # should be rare | ||
_LOGGER.warning('Failed to load azure.mgmt.resource.subscriptions to find the default' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This problem will disapear with a direct call to azure using requests
else: | ||
cloud_environment = AZURE_PUBLIC_CLOUD | ||
|
||
resource = self.resource or cloud_environment.endpoints.management |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now I use
resource = self._cloud_environment.endpoints.active_directory_resource_id
Is it wrong?
process = Popen(['which', 'az'], stdout=PIPE, stderr=PIPE) | ||
stdout, stderr = process.communicate() | ||
process.wait() | ||
if not stderr: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If stderr, log it as warning
_LOGGER.warning('More than one Azure CLI are installed at "%s"' | ||
' Pick the 1st one.', ', '.join(installed_clis)) | ||
|
||
if not cli_path: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should provide a cli_path optional kwargs to cover weird scenario
Detect CLI installations | ||
''' | ||
def probe(self): # pylint: disable=no-self-use | ||
uname = platform.uname() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would take a different approach:
- Let try to call "az"
- If it fails, try to find it and retry
It's more Pythonic in the design (EAFP), and if "az" is in the Path it might work out of the box anyway.
a. app service | ||
b. virtual machine | ||
4. Azure CLI, through "az account get-access-token" | ||
''' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the authentication file:
https://docs.microsoft.com/en-us/python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file
|
||
def get_creds_through_local_probing(**kwargs): | ||
''' | ||
Probing logics: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
Will be done in azure-identity but not msrestazure |
For #118.
The probe sequence:
only "system assigned identity" is probed, as user assigned identity would require extra arguments, and under that context users are better off using strong type credentials
AZURE_CONNECTION_STRING
with sdk auth file content in json, which is proposed in the design review meeting in June.e.g.
{AZURE_CLIENT_ID='...', AZURE_CLIENT_SECRET='...', AZURE_TENANT_ID='...', AZURE_CLOUD_NAME='...'}
AZURE_CLOUD_NAME
is optional. We default to public cloudLike 2, rather using individual environment variables
Subscription id will be optional as the chainer will find them
Per suggestion, the example code includes both management plane and data plane client codes