@@ -6785,6 +6785,13 @@ def upload_ssl_cert(cmd, resource_group_name,
67856785 # Check if this is a Flex Consumption function app
67866786 is_flex = is_flex_functionapp(cmd.cli_ctx, resource_group_name, name)
67876787
6788+ # Validate parameter usage
6789+ if is_flex and slot:
6790+ raise ArgumentUsageError("--slot is not supported for Flex Consumption function apps. "
6791+ "Site-scoped certificates apply to the parent app.")
6792+ if load_to_code is not None and not is_flex:
6793+ raise ArgumentUsageError("--load-to-code is only supported for Flex Consumption function apps.")
6794+
67886795 # For Flex Consumption apps, use the site-scoped certificates endpoint (slots not supported)
67896796 if is_flex:
67906797 # Build certificate envelope as dict to include loadCertificateToWebsitesSettings
@@ -6830,6 +6837,9 @@ def list_ssl_certs(cmd, resource_group_name, name=None):
68306837 # Check if this is a Flex Consumption function app
68316838 if name and is_flex_functionapp(cmd.cli_ctx, resource_group_name, name):
68326839 return client.site_certificates.list(resource_group_name=resource_group_name, name=name)
6840+ if name:
6841+ raise ArgumentUsageError("--name is only supported for Flex Consumption function apps. "
6842+ "For other app types, certificates are managed at the resource group level.")
68336843 return client.certificates.list_by_resource_group(resource_group_name)
68346844
68356845
@@ -6843,6 +6853,9 @@ def show_ssl_cert(cmd, resource_group_name, certificate_name, name=None):
68436853 name=name,
68446854 certificate_name=certificate_name
68456855 )
6856+ if name:
6857+ raise ArgumentUsageError("--name is only supported for Flex Consumption function apps. "
6858+ "For other app types, certificates are managed at the resource group level.")
68466859 return client.certificates.get(resource_group_name, certificate_name)
68476860
68486861
@@ -6861,15 +6874,30 @@ def delete_ssl_cert(cmd, resource_group_name, certificate_thumbprint, name=None)
68616874 )
68626875 raise ResourceNotFoundError("Certificate for thumbprint '{}' not found".format(certificate_thumbprint))
68636876
6877+ if name:
6878+ raise ArgumentUsageError("--name is only supported for Flex Consumption function apps. "
6879+ "For other app types, certificates are managed at the resource group level.")
68646880 webapp_certs = client.certificates.list_by_resource_group(resource_group_name)
68656881 for webapp_cert in webapp_certs:
68666882 if webapp_cert.thumbprint == certificate_thumbprint:
68676883 return client.certificates.delete(resource_group_name, webapp_cert.name)
68686884 raise ResourceNotFoundError("Certificate for thumbprint '{}' not found".format(certificate_thumbprint))
68696885
68706886
6887+ def _validate_flex_ssl_params(is_flex, load_to_code, enable_using_msi, webapp, resource_group_name, name):
6888+ """Validate SSL import parameters for Flex Consumption apps."""
6889+ if load_to_code is not None and not is_flex:
6890+ raise ArgumentUsageError("--load-to-code is only supported for Flex Consumption function apps.")
6891+ if enable_using_msi is not None and not is_flex:
6892+ raise ArgumentUsageError("--enable-using-msi is only supported for Flex Consumption function apps.")
6893+ if enable_using_msi and (not webapp.identity or not webapp.identity.type):
6894+ raise ArgumentUsageError(
6895+ "--enable-using-msi requires a managed identity assigned to the function app. "
6896+ "Assign one with: az functionapp identity assign -g {} -n {}".format(resource_group_name, name))
6897+
6898+
68716899def import_ssl_cert(cmd, resource_group_name, key_vault, key_vault_certificate_name, name=None, certificate_name=None,
6872- load_to_code=None, enable_using_msi=None): # pylint: disable=too-many-branches
6900+ load_to_code=None, enable_using_msi=None):
68736901 Certificate = cmd.get_models('Certificate')
68746902 client = web_client_factory(cmd.cli_ctx)
68756903
@@ -6931,33 +6959,49 @@ def import_ssl_cert(cmd, resource_group_name, key_vault, key_vault_certificate_n
69316959 if asc.name == key_vault_certificate_name:
69326960 kv_secret_name = asc.certificates[key_vault_certificate_name].key_vault_secret_name
69336961 break
6962+ else:
6963+ logger.warning("Unable to check App Service Certificate orders. "
6964+ "If '%s' is an App Service Certificate, the import may not resolve "
6965+ "the correct Key Vault secret name.", key_vault_certificate_name)
69346966
69356967 # if kv_secret_name is not populated, it is not an appservice certificate, proceed for KV certificates
69366968 if not kv_secret_name:
69376969 kv_secret_name = key_vault_certificate_name
69386970
69396971 cert_name = certificate_name or '{}-{}-{}'.format(resource_group_name, kv_name, key_vault_certificate_name)
69406972
6941- lnk = 'https://azure.github.io/AppService/2016/05/24/Deploying-Azure-Web-App-Certificate-through-Key-Vault.html'
6942- lnk_msg = 'Find more details here: {}'.format(lnk)
6943- if not _check_service_principal_permissions(cmd, kv_resource_group_name, kv_name, kv_subscription):
6944- logger.warning('Unable to verify Key Vault permissions.')
6945- logger.warning('You may need to grant Microsoft.Azure.WebSites service principal the Secret:Get permission')
6946- logger.warning(lnk_msg)
6973+ # When using MSI, the app's managed identity accesses Key Vault, not the service principal
6974+ if enable_using_msi:
6975+ logger.warning('Using managed identity to access Key Vault. '
6976+ 'Ensure the app\'s managed identity has the permission on the Key Vault.')
6977+ else:
6978+ lnk = 'https://azure.github.io/AppService/2016/05/24/Deploying-Azure-Web-App-Certificate-through-Key-Vault.html'
6979+ lnk_msg = 'Find more details here: {}'.format(lnk)
6980+ if not _check_service_principal_permissions(cmd, kv_resource_group_name, kv_name, kv_subscription):
6981+ logger.warning('Unable to verify Key Vault permissions.')
6982+ logger.warning('You may need to grant Microsoft.Azure.WebSites service principal the Secret:Get permission')
6983+ logger.warning(lnk_msg)
69476984
69486985 kv_cert_def = Certificate(location=location, key_vault_id=kv_id, password='',
69496986 key_vault_secret_name=kv_secret_name)
69506987
69516988 # Check if this is a Flex Consumption function app
6952- if name and is_flex_functionapp(cmd.cli_ctx, resource_group_name, name):
6989+ is_flex = name and is_flex_functionapp(cmd.cli_ctx, resource_group_name, name)
6990+
6991+ # Validate parameter usage
6992+ _validate_flex_ssl_params(is_flex, load_to_code, enable_using_msi, webapp if name else None,
6993+ resource_group_name, name)
6994+
6995+ if is_flex:
69536996 # Build certificate envelope as dict to include loadCertificateToWebsitesSettings
69546997 # (not in SDK model yet)
69556998 cert_envelope = {
69566999 "location": location,
69577000 "properties": {
69587001 "keyVaultId": kv_id,
69597002 "password": "",
6960- "keyVaultSecretName": kv_secret_name
7003+ "keyVaultSecretName": kv_secret_name,
7004+ "serverFarmId": get_site_server_farm_id(webapp)
69617005 }
69627006 }
69637007 if load_to_code is not None:
@@ -7007,6 +7051,11 @@ def create_managed_ssl_cert(cmd, resource_group_name, name, hostname, slot=None,
70077051 # Check if this is a Flex Consumption function app
70087052 is_flex = is_flex_functionapp(cmd.cli_ctx, resource_group_name, name)
70097053
7054+ # Validate parameter usage
7055+ if is_flex and slot:
7056+ raise ArgumentUsageError("--slot is not supported for Flex Consumption function apps. "
7057+ "Site-scoped certificates apply to the parent app.")
7058+
70107059 # Default certificate_name to hostname if not provided
70117060 if not certificate_name:
70127061 certificate_name = hostname
@@ -7091,6 +7140,11 @@ def _update_ssl_binding(cmd, resource_group_name, name, certificate_thumbprint,
70917140 # Check if this is a Flex Consumption function app
70927141 is_flex = is_flex_functionapp(cmd.cli_ctx, resource_group_name, name)
70937142
7143+ # Validate parameter usage for Flex apps
7144+ if is_flex and slot:
7145+ raise ArgumentUsageError("--slot is not supported for Flex Consumption function apps. "
7146+ "Site-scoped certificates apply to the parent app.")
7147+
70947148 found_cert = None
70957149
70967150 # For Flex Consumption apps, search in site-scoped certificates
@@ -7121,6 +7175,12 @@ def _update_ssl_binding(cmd, resource_group_name, name, certificate_thumbprint,
71217175
71227176 if found_cert:
71237177 if not hostname:
7178+ # For Flex Consumption apps, site-scoped certificates may not populate host_names
7179+ # Require --hostname to be specified explicitly in this case
7180+ if is_flex and (not found_cert.host_names or len(found_cert.host_names) == 0):
7181+ raise ArgumentUsageError(
7182+ "The site-scoped certificate does not have associated host names. "
7183+ "Please specify the hostname explicitly using --hostname.")
71247184 if len(found_cert.host_names) == 1 and not found_cert.host_names[0].startswith('*'):
71257185 return _update_host_name_ssl_state(cmd, resource_group_name, name, webapp,
71267186 found_cert.host_names[0], ssl_type,
0 commit comments