Skip to content

Commit 4b578ee

Browse files
#10518-webapp: Support the new appservice Managed certificates (#13441)
* #10518-webapp: Support the new appservice Managed certificates * updated online version for help files * fixed static analysis SignatureIssues. * Review comments * Review comments addressed * Addressed Review comments * Modified tests are rerecorded * Fixed build issue * addressed Code review comments * updated conflicts * Updated code to support 202 status * Updated ParametreSet positions * Update src/Websites/Websites/ChangeLog.md Co-authored-by: Yeming Liu <[email protected]>
1 parent 39a1633 commit 4b578ee

15 files changed

+5698
-5
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Microsoft.Azure.ServiceManagement.Common.Models;
16+
using Microsoft.WindowsAzure.Commands.ScenarioTest;
17+
using Microsoft.WindowsAzure.Commands.Test.Utilities.Common;
18+
using Xunit;
19+
using Xunit.Abstractions;
20+
21+
namespace Microsoft.Azure.Commands.Websites.Test.ScenarioTests
22+
{
23+
public class CertificatesTests : RMTestBase
24+
{
25+
public XunitTracingInterceptor _logger;
26+
public CertificatesTests(ITestOutputHelper output)
27+
{
28+
_logger = new XunitTracingInterceptor(output);
29+
XunitTracingInterceptor.AddToContext(_logger);
30+
}
31+
32+
[Fact]
33+
[Trait(Category.AcceptanceType, Category.CheckIn)]
34+
public void TestNewAzWebAppCertificate()
35+
{
36+
WebsitesController.NewInstance.RunPsTest(_logger, "Test-NewAzWebAppCertificate");
37+
}
38+
[Fact]
39+
[Trait(Category.AcceptanceType, Category.CheckIn)]
40+
public void TestNewAzWebAppCertificateWithSSLBinding()
41+
{
42+
WebsitesController.NewInstance.RunPsTest(_logger, "Test-NewAzWebAppCertificateWithSSLBinding");
43+
}
44+
[Fact]
45+
[Trait(Category.AcceptanceType, Category.CheckIn)]
46+
public void TestNewAzWebAppCertificateForSlot()
47+
{
48+
WebsitesController.NewInstance.RunPsTest(_logger, "Test-NewAzWebAppCertificateForSlot");
49+
}
50+
[Fact]
51+
[Trait(Category.AcceptanceType, Category.CheckIn)]
52+
public void TestRemoveAzWebAppCertificate()
53+
{
54+
WebsitesController.NewInstance.RunPsTest(_logger, "Test-RemoveAzWebAppCertificate");
55+
}
56+
}
57+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# ----------------------------------------------------------------------------------
2+
#
3+
# Copyright Microsoft Corporation
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
# ----------------------------------------------------------------------------------
14+
15+
<#
16+
.SYNOPSIS
17+
Tests creating a new managed cert for app.
18+
#>
19+
function Test-NewAzWebAppCertificate
20+
{
21+
$rgname = "lketmtestantps10"
22+
$appname = "lketmtestantps10"
23+
$certName = "adorenowcert"
24+
$prodHostname = "www.adorenow.net"
25+
$thumbprint=""
26+
27+
try{
28+
29+
$cert=New-AzWebAppCertificate -ResourceGroupName $rgname -Name $certName -WebAppName $appname -HostName $prodHostname
30+
$thumbprint=$cert.ThumbPrint
31+
32+
# Assert
33+
Assert-AreEqual $prodHostname $cert.SubjectName
34+
35+
}
36+
finally{
37+
38+
# Cleanup
39+
Remove-AzWebAppCertificate -ResourceGroupName $rgname -ThumbPrint $thumbprint
40+
}
41+
}
42+
43+
<#
44+
.SYNOPSIS
45+
Tests creating a new managed cert for app with SSL binding.
46+
#>
47+
function Test-NewAzWebAppCertificateWithSSLBinding
48+
{
49+
$rgname = "lketmtestantps10"
50+
$appname = "lketmtestantps10"
51+
$prodHostname = "www.adorenow.net"
52+
$certName = "adorenowcert"
53+
$thumbprint=""
54+
55+
try{
56+
57+
$cert=New-AzWebAppCertificate -ResourceGroupName $rgname -Name $certName -WebAppName $appname -HostName $prodHostname -AddBinding
58+
$thumbprint=$cert.ThumbPrint
59+
60+
# Assert
61+
Assert-AreEqual $prodHostname $cert.SubjectName
62+
63+
#Assert
64+
$getResult = Get-AzWebAppSSLBinding -ResourceGroupName $rgname -WebAppName $appname
65+
Assert-AreEqual 1 $getResult.Count
66+
$currentHostNames = $getResult | Select -expand Name
67+
Assert-True { $currentHostNames -contains $prodHostname }
68+
$getResult = Get-AzWebAppSSLBinding -ResourceGroupName $rgname -WebAppName $appname -Name $prodHostname
69+
Assert-AreEqual $getResult.Name $prodHostname
70+
71+
}
72+
finally{
73+
74+
# Cleanup
75+
Remove-AzWebAppSSLBinding -ResourceGroupName $rgname -WebAppName $appname -Name $prodHostname -Force
76+
Remove-AzWebAppCertificate -ResourceGroupName $rgname -ThumbPrint $thumbprint
77+
}
78+
}
79+
80+
<#
81+
.SYNOPSIS
82+
Tests creating a new managed certfor slot.
83+
#>
84+
function Test-NewAzWebAppCertificateForSlot
85+
{
86+
87+
$rgname = "lketmtestantps10"
88+
$appname = "lketmtestantps10"
89+
$slot = "testslot"
90+
$slotHostname = "testslot.adorenow.net"
91+
$certName = "adorenowcert"
92+
$thumbprint=""
93+
94+
try{
95+
96+
$cert=New-AzWebAppCertificate -ResourceGroupName $rgname -Name $certName -WebAppName $appname -HostName $slotHostname -Slot $slot
97+
$thumbprint=$cert.ThumbPrint
98+
99+
# Assert
100+
Assert-AreEqual $slotHostname $cert.SubjectName
101+
102+
}
103+
finally{
104+
105+
# Cleanup
106+
Remove-AzWebAppCertificate -ResourceGroupName $rgname -ThumbPrint $thumbprint
107+
}
108+
}
109+
110+
<#
111+
.SYNOPSIS
112+
Tests removing a managed cert.
113+
#>
114+
function Test-RemoveAzWebAppCertificate
115+
{
116+
117+
$rgname = "lketmtestantps10"
118+
$appname = "lketmtestantps10"
119+
$prodHostname = "www.adorenow.net"
120+
$certName = "adorenowcert"
121+
$thumbprint=""
122+
123+
try{
124+
125+
$cert=New-AzWebAppCertificate -ResourceGroupName $rgname -Name $certName -WebAppName $appname -HostName $prodHostname
126+
$thumbprint=$cert.ThumbPrint
127+
128+
# Assert
129+
Assert-AreEqual $prodHostname $cert.SubjectName
130+
131+
Remove-AzWebAppCertificate -ResourceGroupName $rgname -ThumbPrint $thumbprint
132+
133+
$certificate = Get-AzWebAppCertificate -Thumbprint $thumbprint
134+
135+
#Assert
136+
$certificate.count -eq 0
137+
138+
}
139+
finally{
140+
141+
}
142+
}

src/Websites/Websites.Test/SessionRecords/Microsoft.Azure.Commands.Websites.Test.ScenarioTests.CertificatesTests/TestNewAzWebAppCertificate.json

Lines changed: 563 additions & 0 deletions
Large diffs are not rendered by default.

src/Websites/Websites.Test/SessionRecords/Microsoft.Azure.Commands.Websites.Test.ScenarioTests.CertificatesTests/TestNewAzWebAppCertificateForSlot.json

Lines changed: 563 additions & 0 deletions
Large diffs are not rendered by default.

src/Websites/Websites.Test/SessionRecords/Microsoft.Azure.Commands.Websites.Test.ScenarioTests.CertificatesTests/TestNewAzWebAppCertificateWithSSLBinding.json

Lines changed: 2654 additions & 0 deletions
Large diffs are not rendered by default.

src/Websites/Websites.Test/SessionRecords/Microsoft.Azure.Commands.Websites.Test.ScenarioTests.CertificatesTests/TestRemoveAzWebAppCertificate.json

Lines changed: 1175 additions & 0 deletions
Large diffs are not rendered by default.

src/Websites/Websites/Az.Websites.psd1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ CmdletsToExport = 'Get-AzAppServicePlan', 'Set-AzAppServicePlan',
100100
'Remove-AzWebAppAccessRestrictionRule',
101101
'Update-AzWebAppAccessRestrictionConfig',
102102
'Add-AzWebAppTrafficRouting', 'Remove-AzWebAppTrafficRouting',
103-
'Get-AzWebAppTrafficRouting', 'Update-AzWebAppTrafficRouting'
103+
'Get-AzWebAppTrafficRouting', 'Update-AzWebAppTrafficRouting',
104+
'New-AzWebAppCertificate','Remove-AzWebAppCertificate'
104105

105106
# Variables to export from this module
106107
# VariablesToExport = @()

src/Websites/Websites/ChangeLog.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
- Additional information about change #1
1919
-->
2020
## Upcoming Release
21-
21+
* Added support for App Service Managed certificates
22+
- New Cmdlets
23+
- New-AzWebAppCertificate
24+
- Remove-AzWebAppCertificate
25+
2226
## Version 2.1.1
2327
* Prevent duplicate access restriction rules
2428

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
16+
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
17+
using Microsoft.Azure.Commands.WebApps.Models;
18+
using Microsoft.Azure.Commands.WebApps.Models.WebApp;
19+
using Microsoft.Azure.Commands.WebApps.Utilities;
20+
using Microsoft.Azure.Management.WebSites.Models;
21+
using System;
22+
using System.Linq;
23+
using System.Management.Automation;
24+
using System.Net;
25+
using System.Net.Http;
26+
using System.Text;
27+
using System.Threading;
28+
29+
namespace Microsoft.Azure.Commands.WebApps.Cmdlets.Certificates
30+
{
31+
32+
/// <summary>
33+
/// This commandlet will let you create a new managed certificate
34+
/// </summary>
35+
[Cmdlet("New", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "WebAppCertificate", SupportsShouldProcess = true), OutputType(typeof(PSCertificate))]
36+
public class NewAzureWebAppCertificate : WebAppBaseClientCmdLet
37+
{
38+
// Poll status for a maximum of 6 minutes (360 seconds / 2 seconds per status check)
39+
private const int NumStatusChecks = 72;
40+
const string CertNamePostFixSeparator = "_";
41+
const string ParameterSet1Name = "S1";
42+
43+
[Parameter(ParameterSetName = ParameterSet1Name, Position = 0, Mandatory = true, HelpMessage = "The name of the resource group.")]
44+
[ResourceGroupCompleter]
45+
[ValidateNotNullOrEmpty]
46+
public string ResourceGroupName { get; set; }
47+
48+
[Parameter(ParameterSetName = ParameterSet1Name, Position = 1, Mandatory = true, HelpMessage = "The name of the web app.")]
49+
[ResourceNameCompleter("Microsoft.Web/sites", "ResourceGroupName")]
50+
[ValidateNotNullOrEmpty]
51+
public string WebAppName { get; set; }
52+
53+
[Parameter(ParameterSetName = ParameterSet1Name, Mandatory = false, HelpMessage = "The name of the certificate")]
54+
[ValidateNotNullOrEmpty]
55+
public string Name { get; set; }
56+
57+
[Parameter(ParameterSetName = ParameterSet1Name, Position = 2, Mandatory = false, HelpMessage = "The name of the web app slot.")]
58+
[ResourceNameCompleter("Microsoft.Web/sites/slots", "ResourceGroupName", "WebAppName")]
59+
[ValidateNotNullOrEmpty]
60+
public string Slot { get; set; }
61+
62+
[Parameter(ParameterSetName = ParameterSet1Name, Position = 3, Mandatory = true, HelpMessage = "Custom hostnames associated with web app/slot.")]
63+
[ValidateNotNullOrEmpty]
64+
public string HostName { get; set; }
65+
66+
[Parameter(ParameterSetName = ParameterSet1Name, Mandatory = false, HelpMessage = "To add the created certificate to WebApp/slot.")]
67+
[ValidateNotNullOrEmpty]
68+
public SwitchParameter AddBinding { get; set; }
69+
70+
[Parameter(ParameterSetName = ParameterSet1Name, Mandatory = false, HelpMessage = "Ssl state option. Use either 'SniEnabled' or 'IpBasedEnabled'. Default option is 'SniEnabled'.")]
71+
[ValidateNotNullOrEmpty]
72+
public SslState? SslState { get; set; }
73+
74+
public override void ExecuteCmdlet()
75+
{
76+
if (!string.IsNullOrWhiteSpace(ResourceGroupName) && !string.IsNullOrWhiteSpace(WebAppName))
77+
{
78+
string certName = null;
79+
HttpStatusCode statusCode = HttpStatusCode.OK;
80+
var webApp = new PSSite(WebsitesClient.GetWebApp(ResourceGroupName, WebAppName, Slot));
81+
var location = webApp.Location;
82+
83+
Certificate createdCertdetails = new Certificate();
84+
85+
var certificate = new Certificate(
86+
webApp.Location,
87+
type: "Microsoft.Web/certificates",
88+
canonicalName: HostName,
89+
password: "",
90+
serverFarmId: webApp.ServerFarmId);
91+
if (this.ShouldProcess(this.WebAppName, string.Format($"Creating an App service managed certificate for Web App '{WebAppName}'")))
92+
{
93+
try
94+
{
95+
//Default certName is HostName
96+
certName = Name != null ? Name : HostName;
97+
createdCertdetails = (PSCertificate)WebsitesClient.CreateCertificate(ResourceGroupName, certName, certificate);
98+
}
99+
catch (DefaultErrorResponseException e)
100+
{
101+
statusCode = e.Response.StatusCode;
102+
// 'Conflict' exception is thrown when certificate already exists. Let's swallow it and continue.
103+
//'Accepted' exception is thrown by default for create cert method.
104+
if (e.Response.StatusCode != HttpStatusCode.Conflict &&
105+
e.Response.StatusCode != HttpStatusCode.Accepted)
106+
{
107+
throw;
108+
}
109+
if (e.Response.StatusCode == HttpStatusCode.Accepted)
110+
{
111+
var poll_url = e.Response.Headers["Location"].FirstOrDefault();
112+
var token=WebsitesClient.GetAccessToken(DefaultContext);
113+
HttpClient client = new HttpClient();
114+
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.AccessToken);
115+
116+
HttpResponseMessage r;
117+
int numChecks = 0;
118+
do
119+
{
120+
Thread.Sleep(TimeSpan.FromSeconds(5));
121+
r = client.GetAsync(poll_url).Result;
122+
numChecks++;
123+
} while (r.StatusCode == HttpStatusCode.Accepted && numChecks < NumStatusChecks);
124+
125+
if (r.StatusCode == HttpStatusCode.Accepted && numChecks >= NumStatusChecks)
126+
{
127+
var rec = new ErrorRecord(new Exception(string.Format($"The creation of the managed certificate '{this.HostName}' is taking longer than expected." +
128+
$" Please re-try the operation '{CreateInputCommand()}'")),
129+
string.Empty, ErrorCategory.OperationTimeout, null);
130+
WriteError(rec);
131+
}
132+
133+
134+
}
135+
}
136+
createdCertdetails = WebsitesClient.GetCertificate(ResourceGroupName, certName);
137+
138+
//Add only when user is opted for Binding
139+
if (AddBinding)
140+
{
141+
142+
WebsitesClient.UpdateHostNameSslState(ResourceGroupName,
143+
WebAppName,
144+
Slot,
145+
webApp.Location,
146+
HostName, SslState.HasValue ? SslState.Value : Management.WebSites.Models.SslState.SniEnabled,
147+
createdCertdetails.Thumbprint);
148+
}
149+
WriteObject(createdCertdetails);
150+
}
151+
152+
}
153+
154+
}
155+
private string CreateInputCommand()
156+
{
157+
StringBuilder command = new StringBuilder("New-AzWebAppCertificate ");
158+
command.Append($"-ResourceGroupName {this.ResourceGroupName} -WebAppName {this.WebAppName} -HostName {this.HostName} ");
159+
if (Slot != null)
160+
command.Append($"-Slot {this.Slot} ");
161+
if (AddBinding)
162+
command.Append($"-AddBinding ");
163+
if (SslState != null)
164+
command.Append($"-SslState {this.SslState} ");
165+
return command.ToString(); ;
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)