File CVE-2024-43591.patch of Package azure-cli.37684

From 57b9d2c5d319f0854fdc234b756bb7416bd85c54 Mon Sep 17 00:00:00 2001
From: xfz11 <81600993+xfz11@users.noreply.github.com>
Date: Wed, 4 Sep 2024 10:40:44 +0800
Subject: [PATCH] {Service Connector} Add input validation for `run_cli_cmd` 
 (#29798)

* fix run_cli_cmd

* add input validator

* fix no attribute error

* fix add_target_resource_block

* fix no attribute

* update confluent

* lint

* update is_valid_resource_id

* update

* update validate_source_id

* lint

* remove msrestazure
---
 .../serviceconnector/_addon_factory.py        | 25 +++----
 .../serviceconnector/_params.py               | 21 +++---
 .../serviceconnector/_transformers.py         |  4 +-
 .../serviceconnector/_utils.py                | 25 ++++---
 .../serviceconnector/_validators.py           | 66 ++++++++++++++++---
 .../serviceconnector/action.py                | 36 +++++++++-
 6 files changed, 132 insertions(+), 45 deletions(-)

diff --git a/src/azure-cli/azure/cli/command_modules/serviceconnector/_addon_factory.py b/src/azure-cli/azure/cli/command_modules/serviceconnector/_addon_factory.py
index 71c595a980..dd33f3a0f3 100644
--- a/src/azure-cli/azure/cli/command_modules/serviceconnector/_addon_factory.py
+++ b/src/azure-cli/azure/cli/command_modules/serviceconnector/_addon_factory.py
@@ -5,9 +5,8 @@
 
 import re
 from knack.log import get_logger
-from msrestazure.tools import (
+from azure.mgmt.core.tools import (
     parse_resource_id,
-    is_valid_resource_id
 )
 from azure.cli.core import telemetry
 from azure.cli.core.commands.client_factory import get_subscription_id
@@ -17,7 +16,8 @@ from azure.cli.core.azclierror import (
 )
 from ._utils import (
     generate_random_string,
-    run_cli_cmd
+    run_cli_cmd,
+    is_valid_resource_id
 )
 from ._resource_config import (
     RESOURCE,
@@ -42,12 +42,13 @@ logger = get_logger(__name__)
 AddonConfig = {
     RESOURCE.Postgres: {
         'create': [
-            'az postgres server create -g {target_resource_group} -n {server} -l {location} -u {user} -p {password}',
-            'az postgres db create -g {target_resource_group} -s {server} -n {database}'
+            'az postgres server create -g "{target_resource_group}" -n "{server}" -l "{location}" -u "{user}" \
+                -p "{password}"',
+            'az postgres db create -g "{target_resource_group}" -s "{server}" -n {database}'
         ],
         'delete': [
-            'az postgres server delete -g {target_resource_group} -n {server} --yes',
-            'az postgres db delete -g {target_resource_group} -s {server} -n {database} --yes'
+            'az postgres server delete -g "{target_resource_group}" -n "{server}" --yes',
+            'az postgres db delete -g "{target_resource_group}" -s "{server}" -n "{database}" --yes'
         ],
         'params': {
             'target_resource_group': '_retrive_source_rg',
@@ -59,8 +60,8 @@ AddonConfig = {
         }
     },
     RESOURCE.KeyVault: {
-        'create': ['az keyvault create -g {target_resource_group} -n {vault} -l {location}'],
-        'delete': ['az keyvault delete -g {target_resource_group} -n {vault} --yes'],
+        'create': ['az keyvault create -g "{target_resource_group}" -n "{vault}" -l "{location}"'],
+        'delete': ['az keyvault delete -g "{target_resource_group}" -n "{vault}" --yes'],
         'params': {
             'target_resource_group': '_retrive_source_rg',
             'location': '_retrive_source_loc',
@@ -68,8 +69,8 @@ AddonConfig = {
         }
     },
     RESOURCE.StorageBlob: {
-        'create': ['az storage account create -g {target_resource_group} -n {account} -l {location}'],
-        'delete': ['az storage account delete -g {target_resource_group} -n {account} --yes'],
+        'create': ['az storage account create -g "{target_resource_group}" -n "{account}" -l "{location}"'],
+        'delete': ['az storage account delete -g "{target_resource_group}" -n "{account}" --yes'],
         'params': {
             'target_resource_group': '_retrive_source_rg',
             'location': '_retrive_source_loc',
@@ -186,7 +187,7 @@ class AddonBase:
         '''Retrieve the location of source resource group
         '''
         rg = self._retrive_source_rg()
-        output = run_cli_cmd('az group show -n {} -o json'.format(rg))
+        output = run_cli_cmd('az group show -n "{}" -o json'.format(rg))
         return output.get('location')
 
     def _get_source_type(self):
diff --git a/src/azure-cli/azure/cli/command_modules/serviceconnector/_params.py b/src/azure-cli/azure/cli/command_modules/serviceconnector/_params.py
index b2aab50403..0f2cecb5bd 100644
--- a/src/azure-cli/azure/cli/command_modules/serviceconnector/_params.py
+++ b/src/azure-cli/azure/cli/command_modules/serviceconnector/_params.py
@@ -37,7 +37,7 @@ from knack.arguments import CLIArgumentType
 from .action import AddCustomizedKeys
 
 
-def add_source_resource_block(context, source, enable_id=True, validate_source_id=False):
+def add_source_resource_block(context, source, enable_id=True):
     source_args = SOURCE_RESOURCES_PARAMS.get(source)
     for resource, args in SOURCE_RESOURCES_PARAMS.items():
         if resource != source:
@@ -57,7 +57,7 @@ def add_source_resource_block(context, source, enable_id=True, validate_source_i
         required_args.append(content.get('options')[0])
 
     validator_kwargs = {
-        'validator': validate_params} if validate_source_id else {}
+        'validator': validate_params}
     if not enable_id:
         context.argument('source_id', options_list=['--source-id'], type=str,
                          help="The resource id of a {source}. Required if {required_args} "
@@ -140,14 +140,15 @@ def add_target_resource_block(context, target):
                     context.ignore(arg)
 
     required_args = []
-    for arg, content in TARGET_RESOURCES_PARAMS.get(target).items():
-        context.argument(arg, options_list=content.get('options'), type=str,
-                         help='{}. Required if \'--target-id\' is not specified.'.format(content.get('help')))
-        required_args.append(content.get('options')[0])
+    if target in TARGET_RESOURCES_PARAMS:
+        for arg, content in TARGET_RESOURCES_PARAMS.get(target).items():
+            context.argument(arg, options_list=content.get('options'), type=str,
+                             help='{}. Required if \'--target-id\' is not specified.'.format(content.get('help')))
+            required_args.append(content.get('options')[0])
 
-    context.argument('target_id', type=str,
-                     help='The resource id of target service. Required if {required_args} '
-                     'are not specified.'.format(required_args=str(required_args)))
+        context.argument('target_id', type=str,
+                         help='The resource id of target service. Required if {required_args} '
+                         'are not specified.'.format(required_args=str(required_args)))
 
     if target != RESOURCE.KeyVault:
         context.ignore('enable_csi')
@@ -253,7 +254,7 @@ def load_arguments(self, _):  # pylint: disable=too-many-statements
 
         with self.argument_context('{} connection list'.format(source.value)) as c:
             add_source_resource_block(
-                c, source, enable_id=False, validate_source_id=True)
+                c, source, enable_id=False)
 
         with self.argument_context('{} connection show'.format(source.value)) as c:
             add_source_resource_block(c, source)
diff --git a/src/azure-cli/azure/cli/command_modules/serviceconnector/_transformers.py b/src/azure-cli/azure/cli/command_modules/serviceconnector/_transformers.py
index 4125908b25..0b0e658f4b 100644
--- a/src/azure-cli/azure/cli/command_modules/serviceconnector/_transformers.py
+++ b/src/azure-cli/azure/cli/command_modules/serviceconnector/_transformers.py
@@ -34,7 +34,7 @@ def transform_linker_properties(result):
     result = todict(result)
     resource_id = result.get('id')
     try:
-        output = run_cli_cmd('az webapp connection list-configuration --id {} -o json'.format(resource_id))
+        output = run_cli_cmd('az webapp connection list-configuration --id "{}" -o json'.format(resource_id))
         result['configurations'] = output.get('configurations')
     except CLIInternalError:
         pass
@@ -54,7 +54,7 @@ def transform_local_linker_properties(result):
     result = todict(result)
     resource_id = result.get('id')
     try:
-        output = run_cli_cmd('az connection generate-configuration --id {} -o json'.format(resource_id))
+        output = run_cli_cmd('az connection generate-configuration --id "{}" -o json'.format(resource_id))
         result['configurations'] = output.get('configurations')
     except CLIInternalError:
         pass
diff --git a/src/azure-cli/azure/cli/command_modules/serviceconnector/_utils.py b/src/azure-cli/azure/cli/command_modules/serviceconnector/_utils.py
index c1ece98dbb..50d39a6d2e 100644
--- a/src/azure-cli/azure/cli/command_modules/serviceconnector/_utils.py
+++ b/src/azure-cli/azure/cli/command_modules/serviceconnector/_utils.py
@@ -6,7 +6,6 @@
 import time
 from knack.log import get_logger
 from knack.util import todict, CLIError
-from msrestazure.tools import parse_resource_id
 from azure.cli.core.azclierror import (
     ValidationError,
     CLIInternalError
@@ -17,11 +16,20 @@ from ._resource_config import (
     TARGET_RESOURCES_USERTOKEN,
     RESOURCE
 )
-
+from azure.mgmt.core.tools import (
+    parse_resource_id,
+    is_valid_resource_id as is_valid_resource_id_sdk
+)
 
 logger = get_logger(__name__)
 
 
+def is_valid_resource_id(value):
+    if re.search('[\"\'|]', value):
+        return False
+    return is_valid_resource_id_sdk(value)
+
+
 def should_load_source(source):
     '''Check whether to load `az {source} connection`
     If {source} is an extension (e.g, spring-cloud), load the command group only when {source} is installed
@@ -123,7 +131,7 @@ def provider_is_registered(subscription=None):
     # register the provider
     subs_arg = ''
     if subscription:
-        subs_arg = '--subscription {}'.format(subscription)
+        subs_arg = '--subscription "{}"'.format(subscription)
     output = run_cli_cmd(
         'az provider show -n Microsoft.ServiceLinker {}'.format(subs_arg))
     if output.get('registrationState') == 'NotRegistered':
@@ -137,7 +145,7 @@ def register_provider(subscription=None):
 
     subs_arg = ''
     if subscription:
-        subs_arg = '--subscription {}'.format(subscription)
+        subs_arg = '--subscription "{}"'.format(subscription)
 
     # register the provider
     run_cli_cmd(
@@ -274,13 +282,10 @@ def get_auth_if_no_valid_key_vault_connection(source_name, source_id, key_vault_
 
 # https://docs.microsoft.com/azure/app-service/app-service-key-vault-references
 def get_auth_if_no_valid_key_vault_connection_for_webapp(source_id, key_vault_connections):
-    from msrestazure.tools import (
-        is_valid_resource_id
-    )
 
     try:
         webapp = run_cli_cmd(
-            'az rest -u {}?api-version=2020-09-01 -o json'.format(source_id))
+            'az rest -u "{}?api-version=2020-09-01" -o json'.format(source_id))
         reference_identity = webapp.get(
             'properties').get('keyVaultReferenceIdentity')
     except Exception as e:
@@ -298,7 +303,7 @@ def get_auth_if_no_valid_key_vault_connection_for_webapp(source_id, key_vault_co
         except Exception:  # pylint: disable=broad-except
             try:
                 identity = run_cli_cmd(
-                    'az identity show --ids {} -o json'.format(reference_identity))
+                    'az identity show --ids "{}" -o json'.format(reference_identity))
                 client_id = identity.get('clientId')
             except Exception:  # pylint: disable=broad-except
                 pass
@@ -353,7 +358,7 @@ def get_object_id_of_current_user():
             return user_object_id
         if user_type == 'servicePrincipal':
             user_info = run_cli_cmd(
-                f'az ad sp show --id {signed_in_user.get("name")}')
+                f'az ad sp show --id "{signed_in_user.get("name")}" -o json')
             user_object_id = user_info.get('id')
             return user_object_id
     except CLIInternalError as e:
diff --git a/src/azure-cli/azure/cli/command_modules/serviceconnector/_validators.py b/src/azure-cli/azure/cli/command_modules/serviceconnector/_validators.py
index 465ba0f5bb..9e537dfe91 100644
--- a/src/azure-cli/azure/cli/command_modules/serviceconnector/_validators.py
+++ b/src/azure-cli/azure/cli/command_modules/serviceconnector/_validators.py
@@ -12,9 +12,8 @@ from knack.prompting import (
     prompt,
     prompt_pass
 )
-from msrestazure.tools import (
+from azure.mgmt.core.tools import (
     parse_resource_id,
-    is_valid_resource_id
 )
 from azure.cli.core import telemetry
 from azure.cli.core.commands.client_factory import get_subscription_id
@@ -26,7 +25,8 @@ from azure.cli.core.azclierror import (
 
 from ._utils import (
     run_cli_cmd,
-    get_object_id_of_current_user
+    get_object_id_of_current_user,
+    is_valid_resource_id
 )
 from ._resource_config import (
     CLIENT_TYPE,
@@ -146,7 +146,7 @@ def get_client_type(cmd, namespace):
 
         client_type = None
         try:
-            output = run_cli_cmd('az webapp show --id {} -o json'.format(source_id))
+            output = run_cli_cmd('az webapp show --id "{}" -o json'.format(source_id))
             prop = output.get('siteConfig').get('linuxFxVersion', None) or\
                 output.get('siteConfig').get('windowsFxVersion', None)
             # use 'linuxFxVersion' and 'windowsFxVersion' property to decide
@@ -168,7 +168,7 @@ def get_client_type(cmd, namespace):
         client_type = CLIENT_TYPE.SpringBoot
         try:
             segments = parse_resource_id(source_id)
-            output = run_cli_cmd('az spring app show -g {} -s {} -n {}'
+            output = run_cli_cmd('az spring app show -g "{}" -s "{}" -n "{}"'
                                  ' -o json'.format(segments.get('resource_group'), segments.get('name'),
                                                    segments.get('child_name_1')))
             prop_val = output.get('properties')\
@@ -466,8 +466,9 @@ def get_missing_target_args(cmd):
     target = get_target_resource_name(cmd)
     missing_args = dict()
 
-    for arg, content in TARGET_RESOURCES_PARAMS.get(target).items():
-        missing_args[arg] = content
+    if target in TARGET_RESOURCES_PARAMS:
+        for arg, content in TARGET_RESOURCES_PARAMS.get(target).items():
+            missing_args[arg] = content
 
     return missing_args
 
@@ -495,6 +496,32 @@ def get_missing_auth_args(cmd, namespace):
                 auth_param_exist = True
                 break
 
+    if target == RESOURCE.ConfluentKafka:
+        return missing_args
+    # when keyvault csi is enabled, auth_type is userIdentity without subs_id and client_id
+    if source == RESOURCE.KubernetesCluster and target == RESOURCE.KeyVault:
+        if getattr(namespace, 'enable_csi', None):
+            if auth_param_exist:
+                logger.warning('When CSI driver is enabled (--enable-csi), \
+                               Service Connector uses the user assigned managed identity generated by AKS \
+                               azure-keyvault-secrets-provider add-on to authenticate. \
+                               Additional auth info is ignored.')
+            setattr(namespace, 'user_identity_auth_info', {
+                'auth_type': 'userAssignedIdentity'
+            })
+            return missing_args
+        if not auth_param_exist:
+            setattr(namespace, 'enable_csi', True)
+            setattr(namespace, 'user_identity_auth_info', {
+                'auth_type': 'userAssignedIdentity'
+            })
+            logger.warning('Auth info is not specified, use secrets store csi driver as default: --enable-csi')
+            return missing_args
+
+    # ACA as target use null auth
+    if target == RESOURCE.ContainerApp:
+        return missing_args
+
     if source and target and not auth_param_exist:
         default_auth_type = SUPPORTED_AUTH_TYPE.get(source, {}).get(target, {})[0]
 
@@ -619,7 +646,7 @@ def validate_update_params(cmd, namespace):
     '''Get missing args of update command
     '''
     missing_args = dict()
-    if not validate_connection_id(namespace):
+    if not validate_connection_id(namespace) and not validate_source_resource_id(cmd, namespace):
         missing_args.update(get_missing_source_args(cmd, namespace))
     missing_args.update(get_missing_auth_args(cmd, namespace))
     missing_args.update(get_missing_connection_name(namespace))
@@ -720,6 +747,25 @@ def apply_auth_args(cmd, namespace, arg_values):
             for arg in AUTH_TYPE_PARAMS.get(auth_type):
                 if arg in arg_values:
                     setattr(namespace, arg, arg_values.get(arg, None))
+                    if arg == 'workload_identity_auth_info':
+                        apply_workload_identity(namespace, arg_values)
+
+
+def apply_workload_identity(namespace, arg_values):
+    output = run_cli_cmd('az identity show --ids "{}"'.format(
+        arg_values.get('workload_identity_auth_info')
+    ))
+    if output:
+        client_id = output.get('clientId')
+        subs_id = arg_values.get('workload_identity_auth_info').split('/')[2]
+    else:
+        raise ValidationError('Invalid user identity resource ID for workload identity.')
+    setattr(namespace, 'user_identity_auth_info',
+            {
+                'client_id': client_id,
+                'subscription_id': subs_id,
+                'auth_type': 'userAssignedIdentity'
+            })
 
 
 def apply_connection_name(namespace, arg_values):
@@ -840,7 +886,7 @@ def validate_params(cmd, namespace):
             namespace.connection_name = generate_connection_name(cmd)
         else:
             validate_connection_name(namespace.connection_name)
-        if getattr(namespace, 'new_addon'):
+        if getattr(namespace, 'new_addon', None):
             _validate_and_apply(validate_addon_params, apply_addon_params)
         else:
             _validate_and_apply(validate_create_params, apply_create_params)
@@ -892,7 +938,7 @@ def validate_service_state(linker_parameters):
         if not rg or not name:
             return
 
-        output = run_cli_cmd('az appconfig show -g {} -n {}'.format(rg, name))
+        output = run_cli_cmd('az appconfig show -g "{}" -n "{}"'.format(rg, name))
         if output and output.get('disableLocalAuth') is True:
             raise ValidationError('Secret as auth type is not allowed when local auth is disabled for the '
                                   'specified appconfig, you may use service principal or managed identity.')
diff --git a/src/azure-cli/azure/cli/command_modules/serviceconnector/action.py b/src/azure-cli/azure/cli/command_modules/serviceconnector/action.py
index aa46532c95..8ce5f3b8f9 100644
--- a/src/azure-cli/azure/cli/command_modules/serviceconnector/action.py
+++ b/src/azure-cli/azure/cli/command_modules/serviceconnector/action.py
@@ -214,7 +214,7 @@ class AddServicePrincipalAuthInfo(argparse.Action):
                                   'Required keys are: client-id, secret')
         if 'principal_id' not in d:
             from ._utils import run_cli_cmd
-            output = run_cli_cmd('az ad sp show --id {}'.format(d['client_id']))
+            output = run_cli_cmd('az ad sp show --id "{}"'.format(d['client_id']))
             if output:
                 d['principal_id'] = output.get('id')
             else:
@@ -225,3 +225,37 @@ class AddServicePrincipalAuthInfo(argparse.Action):
 
         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)
+        # convert into user identity
+        namespace.user_identity_auth_info = action
+
+    def get_action(self, values, option_string):  # pylint: disable=no-self-use
+        try:
+            # --workload-identity <user-identity-resource-id>
+            properties = defaultdict(list)
+            if len(values) != 1:
+                raise ValidationError(
+                    'Invalid number of values for --workload-identity <USER-IDENTITY-RESOURCE-ID>')
+            properties['user-identity-resource-id'] = values[0]
+        except ValueError:
+            raise ValidationError('Usage error: {} [VALUE]'.format(option_string))
+
+        d = {}
+        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
-- 
2.47.1

openSUSE Build Service is sponsored by