Skip to content
4 changes: 4 additions & 0 deletions src/serviceconnector-passwordless/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Release History
===============
0.3.1
++++++
* Support User-Assigned Managed Identity and Service Principal.

0.3.0
++++++
* Add extension information in API request.
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
)
from azure.cli.command_modules.serviceconnector._resource_config import (
SOURCE_RESOURCES_PARAMS,
AUTH_TYPE_PARAMS,
AUTH_TYPE,
RESOURCE
)
from ._resource_config import (
AUTH_TYPE_PARAMS,
SUPPORTED_AUTH_TYPE,
TARGET_RESOURCES_PARAMS,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,27 @@

from azure.cli.command_modules.serviceconnector._resource_config import (
RESOURCE,
AUTH_TYPE
AUTH_TYPE,
)
passwordless_target_resources = [
from azure.cli.command_modules.serviceconnector.action import (
AddSecretAuthInfo,
AddSecretAuthInfoAuto,
)
from .action import (
AddSystemAssignedIdentityAuthInfo,
AddUserAssignedIdentityAuthInfo,
AddServicePrincipalAuthInfo,
AddUserAccountAuthInfo,
)

PASSWORDLESS_TARGET_RESOURCES = [
RESOURCE.Postgres,
RESOURCE.PostgresFlexible,
RESOURCE.MysqlFlexible,
RESOURCE.Sql
]

# pylint: disable=line-too-long
SUPPORTED_AUTH_TYPE = {
RESOURCE.Local: {
RESOURCE.Postgres: [AUTH_TYPE.Secret, AUTH_TYPE.UserAccount],
Expand All @@ -22,16 +34,16 @@
RESOURCE.Sql: [AUTH_TYPE.Secret, AUTH_TYPE.UserAccount],
},
RESOURCE.WebApp: {
RESOURCE.Postgres: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity],
RESOURCE.PostgresFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity],
RESOURCE.MysqlFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity],
RESOURCE.Sql: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity],
RESOURCE.Postgres: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity, AUTH_TYPE.UserIdentity, AUTH_TYPE.ServicePrincipalSecret],
RESOURCE.PostgresFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity, AUTH_TYPE.UserIdentity, AUTH_TYPE.ServicePrincipalSecret],
RESOURCE.MysqlFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity, AUTH_TYPE.UserIdentity, AUTH_TYPE.ServicePrincipalSecret],
RESOURCE.Sql: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity, AUTH_TYPE.UserIdentity, AUTH_TYPE.ServicePrincipalSecret],
},
RESOURCE.SpringCloud: {
RESOURCE.Postgres: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity],
RESOURCE.PostgresFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity],
RESOURCE.MysqlFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity],
RESOURCE.Sql: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity],
RESOURCE.Postgres: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity, AUTH_TYPE.UserIdentity, AUTH_TYPE.ServicePrincipalSecret],
RESOURCE.PostgresFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity, AUTH_TYPE.UserIdentity, AUTH_TYPE.ServicePrincipalSecret],
RESOURCE.MysqlFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity, AUTH_TYPE.UserIdentity, AUTH_TYPE.ServicePrincipalSecret],
RESOURCE.Sql: [AUTH_TYPE.Secret, AUTH_TYPE.SystemIdentity, AUTH_TYPE.UserIdentity, AUTH_TYPE.ServicePrincipalSecret],
},
RESOURCE.KubernetesCluster: {
RESOURCE.Postgres: [AUTH_TYPE.Secret],
Expand Down Expand Up @@ -115,3 +127,48 @@
}
},
}

AUTH_TYPE_PARAMS = {
AUTH_TYPE.Secret: {
'secret_auth_info': {
'options': ['--secret'],
'help': 'The secret auth info',
'action': AddSecretAuthInfo
}
},
AUTH_TYPE.SecretAuto: {
'secret_auth_info_auto': {
'options': ['--secret'],
'help': 'The secret auth info',
'action': AddSecretAuthInfoAuto
}
},
AUTH_TYPE.SystemIdentity: {
'system_identity_auth_info': {
'options': ['--system-identity'],
'help': 'The system assigned identity auth info',
'action': AddSystemAssignedIdentityAuthInfo
}
},
AUTH_TYPE.UserIdentity: {
'user_identity_auth_info': {
'options': ['--user-identity'],
'help': 'The user assigned identity auth info',
'action': AddUserAssignedIdentityAuthInfo
}
},
AUTH_TYPE.ServicePrincipalSecret: {
'service_principal_auth_info_secret': {
'options': ['--service-principal'],
'help': 'The service principal auth info',
'action': AddServicePrincipalAuthInfo
}
},
AUTH_TYPE.UserAccount: {
'user_account_auth_info': {
'options': ['--user-account'],
'help': 'The local user account auth info',
'action': AddUserAccountAuthInfo
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import argparse
from collections import defaultdict
from azure.cli.core.azclierror import ValidationError
from azure.cli.command_modules.serviceconnector._resource_config import (
RESOURCE
)


# pylint: disable=consider-using-f-string, raise-missing-from
def is_mysql_target(command_name):
target_name = command_name.split(' ')[-1]
return target_name.lower() == RESOURCE.MysqlFlexible.value.lower()


class AddUserAssignedIdentityAuthInfo(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
action = self.get_action(values, option_string, namespace.command)
namespace.user_identity_auth_info = action

def get_action(self, values, option_string, command_name): # pylint: disable=no-self-use
try:
properties = defaultdict(list)
for (k, v) in (x.split('=', 1) for x in values):
properties[k].append(v)
properties = dict(properties)
except ValueError:
raise ValidationError(
'usage error: {} [KEY=VALUE ...]'.format(option_string))
d = {}
for k in properties:
kl = k.lower()
v = properties[k]
if kl == 'client-id':
d['client_id'] = v[0]
elif kl == 'subs-id':
d['subscription_id'] = v[0]
elif is_mysql_target(command_name) and kl == 'mysql-identity-id':
d['mysql-identity-id'] = v[0]
else:
raise ValidationError('Unsupported Key {} is provided for parameter --user-identity. All '
'possible keys are: client-id, subs-id{}'.format(
k, ', mysql-identity-id' if is_mysql_target(command_name) else ''))
if 'client_id' not in d or 'subscription_id' not in d:
raise ValidationError(
'Required keys missing for parameter --user-identity: client-id, subs-id')
d['auth_type'] = 'userAssignedIdentity'
return d


class AddSystemAssignedIdentityAuthInfo(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
action = self.get_action(values, option_string, namespace.command)
namespace.system_identity_auth_info = action

def get_action(self, values, option_string, command_name): # pylint: disable=no-self-use
try:
properties = defaultdict(list)
for (k, v) in (x.split('=', 1) for x in values):
properties[k].append(v)
properties = dict(properties)
except ValueError:
raise ValidationError(
'Usage error: {} [KEY=VALUE ...]'.format(option_string))
d = {}
for k in properties:
v = properties[k]
if is_mysql_target(command_name) and k.lower() == 'mysql-identity-id':
d['mysql-identity-id'] = v[0]
else:
raise ValidationError(
'Unsupported Key {} is provided for parameter --system-identity')
d['auth_type'] = 'systemAssignedIdentity'
return d


class AddUserAccountAuthInfo(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
action = self.get_action(values, option_string, namespace.command)
namespace.user_account_auth_info = action

def get_action(self, values, option_string, command_name): # pylint: disable=no-self-use
try:
properties = defaultdict(list)
for (k, v) in (x.split('=', 1) for x in values):
properties[k].append(v)
properties = dict(properties)
except ValueError:
raise ValidationError(
'usage error: {} [KEY=VALUE ...]'.format(option_string))
d = {}
for k in properties:
kl = k.lower()
v = properties[k]
if kl == 'object-id':
d['principal_id'] = v[0]
elif is_mysql_target(command_name) and kl == 'mysql-identity-id':
d['mysql-identity-id'] = v[0]
else:
raise ValidationError('Unsupported Key {} is provided for parameter --user-account. All '
'possible keys are: principal-id{}'.format(
k, ', mysql-identity-id' if is_mysql_target(command_name) else ''))
d['auth_type'] = 'userAccount'
return d


class AddServicePrincipalAuthInfo(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
action = self.get_action(values, option_string, namespace.command)
namespace.service_principal_auth_info_secret = action

def get_action(self, values, option_string, command_name): # pylint: disable=no-self-use
try:
properties = defaultdict(list)
for (k, v) in (x.split('=', 1) for x in values):
properties[k].append(v)
properties = dict(properties)
except ValueError:
raise ValidationError(
'Usage error: {} [KEY=VALUE ...]'.format(option_string))
d = {}
for k in properties:
kl = k.lower()
v = properties[k]
if kl == 'client-id':
d['client_id'] = v[0]
elif kl == 'object-id':
d['principal_id'] = v[0]
elif kl == 'secret':
d['secret'] = v[0]
elif is_mysql_target(command_name) and kl == 'mysql-identity-id':
d['mysql-identity-id'] = v[0]
else:
raise ValidationError('Unsupported Key {} is provided for parameter --service-principal. Possible '
'keys are: client-id, object-id, secret{}'.format(
k, ', mysql-identity-id' if is_mysql_target(command_name) else ''))
if 'client_id' not in d or 'secret' not in d:
raise ValidationError('Required keys missing for parameter --service-principal. '
'Required keys are: client-id, secret')
if 'principal_id' not in d:
from azure.cli.command_modules.serviceconnector._utils import run_cli_cmd
output = run_cli_cmd(
'az ad sp show --id {}'.format(d['client_id']))
if output:
d['principal_id'] = output.get('id')
else:
raise ValidationError('Could not resolve object-id from the given client-id: {}. Please '
'confirm the client-id and provide the object-id (Enterprise Application) '
'of the service principal, by using --service-principal client-id=XX '
'object-id=XX secret=XX'.format(d['client_id']))

d['auth_type'] = 'servicePrincipalSecret'
return d
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
)
from azure.cli.command_modules.serviceconnector._utils import should_load_source

from ._resource_config import passwordless_target_resources
from ._resource_config import PASSWORDLESS_TARGET_RESOURCES
from ._client_factory import (
cf_linker,
cf_connector
Expand All @@ -29,7 +29,7 @@ def load_command_table(self, _):
operations_tmpl='azure.mgmt.servicelinker.operations._connector_operations#ConnectorOperations.{}',
client_factory=cf_connector)

for target in passwordless_target_resources:
for target in PASSWORDLESS_TARGET_RESOURCES:
with self.command_group('connection create',
local_connection_type, client_factory=cf_connector) as ig:
ig.custom_command(target.value, 'local_connection_create_ext',
Expand All @@ -39,7 +39,7 @@ def load_command_table(self, _):
# if source resource is released as an extension, load our command groups
# only when the extension is installed
if should_load_source(source):
for target in passwordless_target_resources:
for target in PASSWORDLESS_TARGET_RESOURCES:
with self.command_group(f'{source.value} connection create',
connection_type, client_factory=cf_linker) as ig:
ig.custom_command(target.value, 'connection_create_ext',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
# --------------------------------------------------------------------------------------------


VERSION = '0.3.0'
VERSION = '0.3.1'
NAME = 'serviceconnector-passwordless'
2 changes: 1 addition & 1 deletion src/serviceconnector-passwordless/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
logger.warn("Wheel is not available, disabling bdist_wheel hook")


VERSION = '0.3.0'
VERSION = '0.3.1'
try:
from azext_serviceconnector_passwordless.config import VERSION
except ImportError:
Expand Down