From 250c0c28503ac1ffca644bfbd5ce633051e65d95 Mon Sep 17 00:00:00 2001 From: Xiaofan Zhou Date: Fri, 13 Sep 2024 12:58:08 +0800 Subject: [PATCH] add `az aks connection create` --- src/serviceconnector-passwordless/HISTORY.rst | 4 ++ .../_credential_free.py | 12 +++++ .../_help.py | 26 +++++++++++ .../_resource_config.py | 28 +++++++++--- .../action.py | 44 +++++++++++++++++++ .../config.py | 2 +- .../custom.py | 4 +- src/serviceconnector-passwordless/setup.py | 2 +- 8 files changed, 113 insertions(+), 9 deletions(-) diff --git a/src/serviceconnector-passwordless/HISTORY.rst b/src/serviceconnector-passwordless/HISTORY.rst index 5be3238b915..27f25bcbbf5 100644 --- a/src/serviceconnector-passwordless/HISTORY.rst +++ b/src/serviceconnector-passwordless/HISTORY.rst @@ -2,6 +2,10 @@ Release History =============== +3.1.0 +++++++ +* Add `az aks connection create` + 3.0.2 ++++++ * Some improvements and security issue fixes. diff --git a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_credential_free.py b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_credential_free.py index 5d37e0b809d..ffd4c5bd7ea 100644 --- a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_credential_free.py +++ b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_credential_free.py @@ -963,6 +963,8 @@ def get_create_query(self): def getSourceHandler(source_id, source_type): if source_type in {RESOURCE.WebApp, RESOURCE.FunctionApp}: return WebappHandler(source_id, source_type) + if source_type in {RESOURCE.KubernetesCluster}: + return KubernetesHandler(source_id, source_type) if source_type in {RESOURCE.ContainerApp}: return ContainerappHandler(source_id, source_type) if source_type in {RESOURCE.SpringCloud, RESOURCE.SpringCloudDeprecated}: @@ -995,6 +997,16 @@ def get_identity_pid(self): pass +class KubernetesHandler(SourceHandler): + def get_identity_name(self): + raise CLIInternalError( + "System Identity is not supported for Kubernetes cluster.") + + def get_identity_pid(self): + raise CLIInternalError( + "System Identity is not supported for Kubernetes cluster.") + + class SpringHandler(SourceHandler): def get_identity_name(self): segments = parse_resource_id(self.source_id) diff --git a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_help.py b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_help.py index fa2ee2fe69b..9b9f2e66bad 100644 --- a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_help.py +++ b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_help.py @@ -286,6 +286,28 @@ def get_source_display_name(sourcename): client-id : Required. Client id of the user assigned identity. subs-id : Required. Subscription id of the user assigned identity. ''' + workload_identity_param = '' + if AUTH_TYPE.WorkloadIdentity in auth_types: + if target in {RESOURCE.MysqlFlexible}: + workload_identity_param = ''' + - name: --workload-identity + short-summary: The user-assigned managed identity used to create workload identity federation. + long-summary: | + Usage: --workload-identity mysql-identity-id= + + user-identity-resource-id: Required. The resource id of the user assigned identity. Please DO NOT use AKS control plane identity and kubelet identity which is not supported by federated identity credential. + mysql-identity-id : Optional. ID of identity used for MySQL flexible server Microsoft Entra Authentication. Ignore it if you are the server Microsoft Entra administrator. + ''' + else: + workload_identity_param = ''' + - name: --workload-identity + short-summary: The user-assigned managed identity used to create workload identity federation. + long-summary: | + Usage: --workload-identity + + user-identity-resource-id: Required. The resource id of the user assigned identity. + Please DO NOT use AKS control plane identity and kubelet identity which is not supported by federated identity credential. + ''' service_principal_param = '' if AUTH_TYPE.ServicePrincipalSecret in auth_types: if target in {RESOURCE.MysqlFlexible}: @@ -334,6 +356,7 @@ def get_source_display_name(sourcename): {secret_auto_param} {system_identity_param} {user_identity_param} + {workload_identity_param} {service_principal_param} examples: - name: Create a connection between {source_display_name} and {target} interactively @@ -355,6 +378,7 @@ def get_source_display_name(sourcename): secret_auto_param=secret_auto_param, system_identity_param=system_identity_param, user_identity_param=user_identity_param, + workload_identity_param=workload_identity_param, service_principal_param=service_principal_param, source_params=source_params, target_params=target_params, @@ -370,6 +394,7 @@ def get_source_display_name(sourcename): {secret_auto_param} {system_identity_param} {user_identity_param} + {workload_identity_param} {service_principal_param} examples: - name: Update the client type of a connection with resource name @@ -385,6 +410,7 @@ def get_source_display_name(sourcename): secret_auto_param=secret_auto_param, system_identity_param=system_identity_param, user_identity_param=user_identity_param, + workload_identity_param=workload_identity_param, service_principal_param=service_principal_param, source_params=source_params, connection_id=connection_id, diff --git a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_resource_config.py b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_resource_config.py index 05459ac6a08..4d77d342ef6 100644 --- a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_resource_config.py +++ b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/_resource_config.py @@ -17,6 +17,7 @@ AddUserAssignedIdentityAuthInfo, AddServicePrincipalAuthInfo, AddUserAccountAuthInfo, + AddWorkloadIdentityAuthInfo ) EX_SUPPORTED_AUTH_TYPE = SUPPORTED_AUTH_TYPE.copy() @@ -26,6 +27,7 @@ RESOURCE.SpringCloud, RESOURCE.SpringCloudDeprecated, RESOURCE.FunctionApp, + RESOURCE.KubernetesCluster, ] PASSWORDLESS_TARGET_RESOURCES = [ @@ -44,12 +46,19 @@ } for resourceType in PASSWORDLESS_SOURCE_RESOURCES: - EX_SUPPORTED_AUTH_TYPE[resourceType] = { - # 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], - } + if RESOURCE.KubernetesCluster == resourceType: + EX_SUPPORTED_AUTH_TYPE[resourceType] = { + RESOURCE.PostgresFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.WorkloadIdentity, AUTH_TYPE.ServicePrincipalSecret], + RESOURCE.MysqlFlexible: [AUTH_TYPE.Secret, AUTH_TYPE.WorkloadIdentity, AUTH_TYPE.ServicePrincipalSecret], + RESOURCE.Sql: [AUTH_TYPE.Secret, AUTH_TYPE.WorkloadIdentity, AUTH_TYPE.ServicePrincipalSecret], + } + else: + EX_SUPPORTED_AUTH_TYPE[resourceType] = { + # 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], + } TARGET_RESOURCES_PARAMS = { # RESOURCE.Postgres: { @@ -152,6 +161,13 @@ 'action': AddUserAssignedIdentityAuthInfo } }, + AUTH_TYPE.WorkloadIdentity: { + 'workload_identity_auth_info': { + 'options': ['--workload-identity'], + 'help': 'The workload identity auth info', + 'action': AddWorkloadIdentityAuthInfo + } + }, AUTH_TYPE.ServicePrincipalSecret: { 'service_principal_auth_info_secret': { 'options': ['--service-principal'], diff --git a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/action.py b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/action.py index 1ecdb2713b7..227877460e8 100644 --- a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/action.py +++ b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/action.py @@ -159,3 +159,47 @@ def get_action(self, values, option_string, command_name): d['auth_type'] = 'servicePrincipalSecret' return d + + +class AddWorkloadIdentityAuthInfo(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + action = self.get_action(values, option_string, namespace.command) + # convert into user identity + namespace.user_identity_auth_info = action + + def get_action(self, values, option_string, command_name): # pylint: disable=no-self-use + try: + # --workload-identity + properties = defaultdict(list) + if is_mysql_target(command_name): + for val in values: + if '=' in val: + k, v = val.split('=', 1) + properties[k] = v + else: + properties['user-identity-resource-id'] = val + else: + if len(values) != 1: + raise ValidationError( + 'Invalid number of values for --workload-identity ') + properties['user-identity-resource-id'] = values[0] + except ValueError: + raise ValidationError('Usage error: {} [VALUE]'.format(option_string)) + + d = {} + if is_mysql_target(command_name) and 'mysql-identity-id' in properties: + d['mysql-identity-id'] = properties['mysql-identity-id'] + if 'user-identity-resource-id' in properties: + from ._utils import run_cli_cmd + output = run_cli_cmd('az identity show --ids "{}"'.format(properties['user-identity-resource-id'])) + if output: + d['client_id'] = output.get('clientId') + d['subscription_id'] = properties['user-identity-resource-id'].split('/')[2] + else: + raise ValidationError('Invalid user identity resource ID.') + else: + raise ValidationError('Required values missing for parameter --workload-identity: \ + user-identity-resource-id') + + d['auth_type'] = 'userAssignedIdentity' + return d diff --git a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/config.py b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/config.py index 0ccc97ec0be..51aa1c50df8 100644 --- a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/config.py +++ b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/config.py @@ -4,5 +4,5 @@ # -------------------------------------------------------------------------------------------- -VERSION = '3.0.2' +VERSION = '3.1.0' NAME = 'serviceconnector-passwordless' diff --git a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/custom.py b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/custom.py index 05080347409..5c4572c32f8 100644 --- a/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/custom.py +++ b/src/serviceconnector-passwordless/azext_serviceconnector_passwordless/custom.py @@ -4,12 +4,14 @@ # -------------------------------------------------------------------------------------------- -def connection_create_ext(cmd, client, # pylint: disable=too-many-locals,too-many-statements +# pylint: disable=too-many-locals,too-many-statements,unused-argument +def connection_create_ext(cmd, client, connection_name=None, client_type=None, source_resource_group=None, source_id=None, target_resource_group=None, target_id=None, secret_auth_info=None, secret_auth_info_auto=None, user_identity_auth_info=None, system_identity_auth_info=None, + workload_identity_auth_info=None, # only used as arg service_principal_auth_info_secret=None, key_vault_id=None, app_config_id=None, diff --git a/src/serviceconnector-passwordless/setup.py b/src/serviceconnector-passwordless/setup.py index c1627565b5d..0de1af393a9 100644 --- a/src/serviceconnector-passwordless/setup.py +++ b/src/serviceconnector-passwordless/setup.py @@ -15,7 +15,7 @@ logger.warn("Wheel is not available, disabling bdist_wheel hook") -VERSION = '3.0.2' +VERSION = '3.1.0' try: from azext_serviceconnector_passwordless.config import VERSION except ImportError: