Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[App Service] Fix #20983: az webapp config ssl import: Make web app a non-required parameter #30958

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/azure-cli/azure/cli/command_modules/appservice/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1537,10 +1537,12 @@
type: command
short-summary: Import an SSL or App Service Certificate to a web app from Key Vault.
examples:
- name: Import an SSL or App Service Certificate certificate to a web app from Key Vault.
- name: Import an SSL or App Service Certificate certificate to a web app from Key Vault. Note that all webapps in the webspace will also be able to use the certificate.
text: az webapp config ssl import --resource-group MyResourceGroup --name MyWebapp --key-vault MyKeyVault --key-vault-certificate-name MyCertificateName
- name: Import an SSL or App Service Certificate to a web app from Key Vault using resource id (typically if Key Vault is in another subscription).
- name: Import an SSL or App Service Certificate to a web app from Key Vault using resource id (typically if Key Vault is in another subscription). Note that all webapps in the webspace will also be able to use the certificate.
text: az webapp config ssl import --resource-group MyResourceGroup --name MyWebapp --key-vault '/subscriptions/[sub id]/resourceGroups/[rg]/providers/Microsoft.KeyVault/vaults/[vault name]' --key-vault-certificate-name MyCertificateName
- name: Import an SSL or App Service Certificate certificate to a webspace from Key Vault. Note that all webapps in the webspace will also be able to use the certificate.
text: az webapp config ssl import --resource-group MyResourceGroup --key-vault MyKeyVault --key-vault-certificate-name MyCertificateName
"""

helps['webapp config ssl create'] = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ def load_arguments(self, _):
with self.argument_context(scope + ' config ssl import') as c:
c.argument('key_vault', help='The name or resource ID of the Key Vault')
c.argument('key_vault_certificate_name', help='The name of the certificate in Key Vault')
c.argument('name', help='Name of the web app. This is used to set the location of the webspace for the certificate import. If not specified, the location of the resource group will be used. If you have apps in multiple regions/webspaces, you must specify the name of the app to set the location of the webspace for the certificate import.')
with self.argument_context(scope + ' config ssl create') as c:
c.argument('hostname', help='The custom domain name')
c.argument('name', options_list=['--name', '-n'], help='Name of the web app.')
Expand Down
26 changes: 17 additions & 9 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -3956,14 +3956,22 @@ def delete_ssl_cert(cmd, resource_group_name, certificate_thumbprint):
raise ResourceNotFoundError("Certificate for thumbprint '{}' not found".format(certificate_thumbprint))


def import_ssl_cert(cmd, resource_group_name, name, key_vault, key_vault_certificate_name, certificate_name=None):
def import_ssl_cert(cmd, resource_group_name, key_vault, key_vault_certificate_name, name=None, certificate_name=None):
Certificate = cmd.get_models('Certificate')
client = web_client_factory(cmd.cli_ctx)
webapp = client.web_apps.get(resource_group_name, name)
if not webapp:
raise ResourceNotFoundError("'{}' app doesn't exist in resource group {}".format(name, resource_group_name))
server_farm_id = webapp.server_farm_id
location = webapp.location

# Webapp name is not required for this command, but the location of the webspace is required since the certificate
# is associated with the webspace, not the app. All apps and plans in the same webspace will share the same
# certificates. If the app is not provided, the location of the resource group is used.
if name:
webapp = client.web_apps.get(resource_group_name, name)
if not webapp:
raise ResourceNotFoundError("'{}' app doesn't exist in resource group {}".format(name, resource_group_name))
location = webapp.location
else:
rg_client = get_mgmt_service_client(cmd.cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES)
location = rg_client.resource_groups.get(resource_group_name).location

kv_id = None
if not is_valid_resource_id(key_vault):
kv_client = get_mgmt_service_client(cmd.cli_ctx, ResourceType.MGMT_KEYVAULT)
Expand All @@ -3978,9 +3986,9 @@ def import_ssl_cert(cmd, resource_group_name, name, key_vault, key_vault_certifi
if kv_id is None:
kv_msg = 'The Key Vault {0} was not found in the subscription in context. ' \
'If your Key Vault is in a different subscription, please specify the full Resource ID: ' \
'\naz .. ssl import -n {1} -g {2} --key-vault-certificate-name {3} ' \
'\naz .. ssl import -g {1} --key-vault-certificate-name {2} ' \
'--key-vault /subscriptions/[sub id]/resourceGroups/[rg]/providers/Microsoft.KeyVault/' \
'vaults/{0}'.format(key_vault, name, resource_group_name, key_vault_certificate_name)
'vaults/{0}'.format(key_vault, resource_group_name, key_vault_certificate_name)
logger.warning(kv_msg)
return

Expand Down Expand Up @@ -4025,7 +4033,7 @@ def import_ssl_cert(cmd, resource_group_name, name, key_vault, key_vault_certifi
logger.warning(lnk_msg)

kv_cert_def = Certificate(location=location, key_vault_id=kv_id, password='',
key_vault_secret_name=kv_secret_name, server_farm_id=server_farm_id)
key_vault_secret_name=kv_secret_name)

return client.certificates.create_or_update(name=cert_name, resource_group_name=resource_group_name,
certificate_envelope=kv_cert_def)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
CommandName:
- keyvault set-policy
Connection:
- keep-alive
ParameterSetName:
- -g --name --spn --secret-permissions
User-Agent:
- python/3.12.9 (Windows-11-10.0.26100-SP0) AZURECLI/2.70.0
method: GET
uri: https://graph.microsoft.com/v1.0/servicePrincipals?$filter=servicePrincipalNames%2Fany%28c%3Ac%20eq%20%27Microsoft.Azure.WebSites%27%29
response:
body:
string: '{"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#servicePrincipals","value":[{"id":"f8daea97-62e7-4026-becf-13c2ea98e8b4","deletedDateTime":null,"accountEnabled":true,"alternativeNames":[],"appDisplayName":"Microsoft
Azure App Service","appDescription":null,"appId":"abfa0a7c-a6b6-4736-8310-5855508787cd","applicationTemplateId":null,"appOwnerOrganizationId":"f8cdef31-a31e-4b4a-93e4-5f571e91255a","appRoleAssignmentRequired":false,"createdDateTime":null,"description":null,"disabledByMicrosoftStatus":null,"displayName":"Microsoft
Azure App Service","homepage":null,"loginUrl":null,"logoutUrl":null,"notes":null,"notificationEmailAddresses":[],"preferredSingleSignOnMode":null,"preferredTokenSigningKeyThumbprint":null,"replyUrls":["https://msftintch1.sso.azurewebsites.windows.net/","https://jinc.sso.azurewebsites.windows.net/","https://jinw.sso.azurewebsites.windows.net/","https://sec.sso.azurewebsites.windows.net/","https://qac.sso.azurewebsites.windows.net/","https://ses.sso.azurewebsites.windows.net/","https://plc.sso.azurewebsites.windows.net/","https://itn.sso.azurewebsites.windows.net/","https://ilc.sso.azurewebsites.windows.net/","https://esc.sso.azurewebsites.windows.net/","https://mxc.sso.azurewebsites.windows.net/","https://twn.sso.azurewebsites.windows.net/","https://twnw.sso.azurewebsites.windows.net/","https://hel.sso.azurewebsites.windows.net/","https://ate.sso.azurewebsites.windows.net/","https://kul.sso.azurewebsites.windows.net/","https://lo.sso.azurewebsites.windows.net/","https://cnn10.sso.azurewebsites.windows.net/","https://krs2.sso.azurewebsites.windows.net/","https://brne.sso.azurewebsites.windows.net/","https://clc.sso.azurewebsites.windows.net/","https://clnc.sso.azurewebsites.windows.net/","https://myw.sso.azurewebsites.windows.net/","https://nzn.sso.azurewebsites.windows.net/","https://bec.sso.azurewebsites.windows.net/","https://mys.sso.azurewebsites.windows.net/","https://insc.sso.azurewebsites.windows.net/","https://idc.sso.azurewebsites.windows.net/","https://flc.sso.azurewebsites.windows.net/","https://ilnw.sso.azurewebsites.windows.net/","https://dke.sso.azurewebsites.windows.net/","https://usw3.sso.azurewebsites.windows.net/","https://dxb.sso.azurewebsites.windows.net/","https://brse.sso.azurewebsites.windows.net/","https://chw.sso.azurewebsites.windows.net/","https://trydiagnosticsmesh.azure.com/","https://zrh.sso.azurewebsites.windows.net/","https://yt1.sso.azurewebsites.windows.net/","https://yq1.sso.azurewebsites.windows.net/","https://sy3.sso.azurewebsites.windows.net/","https://svg.sso.azurewebsites.windows.net/","https://sn1.sso.azurewebsites.windows.net/","https://sg1.sso.azurewebsites.windows.net/","https://se1.sso.azurewebsites.windows.net/","https://ps1.sso.azurewebsites.windows.net/","https://pn1.sso.azurewebsites.windows.net/","https://par.sso.azurewebsites.windows.net/","https://osl.sso.azurewebsites.windows.net/","https://os1.sso.azurewebsites.windows.net/","https://mwh.sso.azurewebsites.windows.net/","https://msftintsg1.sso.azurewebsites.windows.net/","https://msftintdm3.sso.azurewebsites.windows.net/","https://mrs.sso.azurewebsites.windows.net/","https://ml1.sso.azurewebsites.windows.net/","https://ma1.sso.azurewebsites.windows.net/","https://ln1.sso.azurewebsites.windows.net/","https://kw1.sso.azurewebsites.windows.net/","https://jnb21.sso.azurewebsites.windows.net/","https://hk1.sso.azurewebsites.windows.net/","https://fra.sso.azurewebsites.windows.net/","https://dm1.sso.azurewebsites.windows.net/","https://db3.sso.azurewebsites.windows.net/","https://cy4.sso.azurewebsites.windows.net/","https://cw1.sso.azurewebsites.windows.net/","https://cq1.sso.azurewebsites.windows.net/","https://cpt20.sso.azurewebsites.windows.net/","https://ch1.sso.azurewebsites.windows.net/","https://cbr21.sso.azurewebsites.windows.net/","https://cbr20.sso.azurewebsites.windows.net/","https://bn1.sso.azurewebsites.windows.net/","https://bm1.sso.azurewebsites.windows.net/","https://blu.sso.azurewebsites.windows.net/","https://ber.sso.azurewebsites.windows.net/","https://bay.sso.azurewebsites.windows.net/","https://auh.sso.azurewebsites.windows.net/","https://am2.sso.azurewebsites.windows.net/","https://euapdm1.sso.azurewebsites.windows.net/","https://euapbn1.sso.azurewebsites.windows.net/","https://msftinthk1.sso.azurewebsites.windows.net/","https://deploy-staging.azure.com","https://functions.azure.com","https://functions-staging.azure.com","https://functions-next.azure.com","https://functions-release.azure.com"],"servicePrincipalNames":["https://appservice.azure.com","Microsoft.Azure.WebSites","abfa0a7c-a6b6-4736-8310-5855508787cd"],"servicePrincipalType":"Application","signInAudience":"AzureADMultipleOrgs","tags":["disableAcceptingTenantedPassthroughTokens","disableRequestingTenantedPassthroughTokens","disableLegacyUserImpersonationResource","disableLegacyUserImpersonationClient"],"tokenEncryptionKeyId":null,"samlSingleSignOnSettings":null,"addIns":[],"appRoles":[],"info":{"logoUrl":null,"marketingUrl":null,"privacyStatementUrl":null,"supportUrl":null,"termsOfServiceUrl":null},"keyCredentials":[],"oauth2PermissionScopes":[{"adminConsentDescription":"Allow
the application to access all the APIs registered with App Service","adminConsentDisplayName":"Access
APIs registered with App Service","id":"e0ea806b-d128-49dc-ac08-2bf18f7874d8","isEnabled":true,"type":"User","userConsentDescription":"Allow
the application to access all the APIs registered with App Service","userConsentDisplayName":"Access
APIs registered with App Service","value":"user_impersonation"}],"passwordCredentials":[],"resourceSpecificApplicationPermissions":[],"verifiedPublisher":{"displayName":null,"verifiedPublisherId":null,"addedDateTime":null}}]}'
headers:
cache-control:
- no-cache
content-length:
- '5754'
content-type:
- application/json; odata.metadata=minimal; odata.streaming=true; IEEE754Compatible=false;
charset=utf-8
date:
- Wed, 05 Mar 2025 21:00:51 GMT
odata-version:
- '4.0'
request-id:
- 1c1c2816-f859-44c9-a943-29e634ead8a7
strict-transport-security:
- max-age=31536000
transfer-encoding:
- chunked
vary:
- Accept-Encoding
x-ms-ags-diagnostic:
- '{"ServerInfo":{"DataCenter":"East US 2","Slice":"E","Ring":"5","ScaleUnit":"002","RoleInstance":"BN2PEPF00003EA9"}}'
x-ms-resource-unit:
- '1'
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
CommandName:
- keyvault set-policy
Connection:
- keep-alive
ParameterSetName:
- -g --name --spn --secret-permissions
User-Agent:
- AZURECLI/2.70.0 azsdk-python-core/1.31.0 Python/3.12.9 (Windows-11-10.0.26100-SP0)
method: GET
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.KeyVault/vaults/kv-ssl-test000002?api-version=2023-02-01
response:
body:
string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest.rg000001/providers/Microsoft.KeyVault/vaults/kv-ssl-test000002","name":"kv-ssl-test000002","type":"Microsoft.KeyVault/vaults","location":"westeurope","tags":{},"systemData":{"createdBy":"[email protected]","createdByType":"User","createdAt":"2025-03-05T21:00:15.656Z","lastModifiedBy":"[email protected]","lastModifiedByType":"User","lastModifiedAt":"2025-03-05T21:00:15.656Z"},"properties":{"sku":{"family":"A","name":"standard"},"tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","accessPolicies":[],"enabledForDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":7,"enableRbacAuthorization":true,"vaultUri":"https://kv-ssl-test000002.vault.azure.net/","provisioningState":"Succeeded","publicNetworkAccess":"Enabled"}}'
headers:
cache-control:
- no-cache
content-length:
- '834'
content-type:
- application/json; charset=utf-8
date:
- Wed, 05 Mar 2025 21:00:52 GMT
expires:
- '-1'
pragma:
- no-cache
strict-transport-security:
- max-age=31536000; includeSubDomains
x-aspnet-version:
- 4.0.30319
x-cache:
- CONFIG_NOCACHE
x-content-type-options:
- nosniff
x-ms-keyvault-service-version:
- 1.5.1491.0
x-ms-ratelimit-remaining-subscription-global-reads:
- '16499'
x-msedge-ref:
- 'Ref A: A373C60BBBF64BA3B5A74C6EE6A7B06B Ref B: BL2AA2030101019 Ref C: 2025-03-05T21:00:52Z'
status:
code: 200
message: OK
version: 1
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,26 @@ def test_webapp_ssl_import(self, resource_group, key_vault):
webapp_name), cert_thumbprint)
])

@unittest.skip("Flaky Test")
@ResourceGroupPreparer(location=WINDOWS_ASP_LOCATION_WEBAPP)
@KeyVaultPreparer(location=WINDOWS_ASP_LOCATION_WEBAPP, name_prefix='kv-ssl-test', name_len=20)
def test_webapp_ssl_import_no_app(self, resource_group, key_vault):
# Cert Generated using
# https://learn.microsoft.com/azure/app-service-web/web-sites-configure-ssl-certificate#bkmk_ssopenssl
pfx_file = os.path.join(TEST_DIR, 'server.pfx')
cert_password = 'test'
cert_thumbprint = '9E9735C45C792B03B3FFCCA614852B32EE71AD6B'
cert_name = 'test-cert'
self.cmd('keyvault set-policy -g {} --name {} --spn {} --secret-permissions get'.format(
resource_group, key_vault, 'Microsoft.Azure.WebSites'))
self.cmd('keyvault certificate import --name {} --vault-name {} --file "{}" --password {}'.format(
cert_name, key_vault, pfx_file, cert_password))

self.cmd('webapp config ssl import --resource-group {} --key-vault {} --key-vault-certificate-name {} --certificate-name {}'.format(resource_group, key_vault, cert_name, "test123"), checks=[
JMESPathCheck('thumbprint', cert_thumbprint),
JMESPathCheck('test123')
])

@unittest.skip("Flaky Test")
@ResourceGroupPreparer(parameter_name='kv_resource_group', location=WINDOWS_ASP_LOCATION_WEBAPP)
@ResourceGroupPreparer(location=WINDOWS_ASP_LOCATION_WEBAPP)
Expand Down