Skip to content

Commit d626e74

Browse files
authored
[Iot Hub] Generate SAS token for Iot Hub, device or module (#11628)
* Generate SAS token for Iot Hub, device or module * record test session
1 parent 5247512 commit d626e74

File tree

11 files changed

+2612
-967
lines changed

11 files changed

+2612
-967
lines changed

src/IotHub/IotHub.Test/ScenarioTests/IotHubDPDeviceTests.ps1

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function Test-AzureRmIotHubDeviceLifecycle
2727
$IotHubName = getAssetName
2828
$ResourceGroupName = getAssetName
2929
$Sku = "S1"
30+
$SasTokenPrefix = 'SharedAccessSignature'
3031
$device1 = getAssetName
3132
$device2 = getAssetName
3233
$device3 = getAssetName
@@ -42,6 +43,10 @@ function Test-AzureRmIotHubDeviceLifecycle
4243
# Create Iot Hub
4344
$iothub = New-AzIotHub -Name $IotHubName -ResourceGroupName $ResourceGroupName -Location $Location -SkuName $Sku -Units 1
4445

46+
# Generate SAS token for IotHub
47+
$token = New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName
48+
Assert-StartsWith $SasTokenPrefix $token
49+
4550
# Get all devices
4651
$devices = Get-AzIotHubDevice -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName
4752
Assert-True { $devices.Count -eq 0 }
@@ -70,6 +75,18 @@ function Test-AzureRmIotHubDeviceLifecycle
7075
Assert-True { $newDevice6.Authentication.Type -eq 'Sas' }
7176
Assert-True { $newDevice6.Capabilities.IotEdge }
7277

78+
# Generate SAS token for device
79+
$deviceToken = New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1
80+
Assert-StartsWith $SasTokenPrefix $deviceToken
81+
82+
# Expected error while generating SAS token for device
83+
$errorMessage = "This device does not support SAS auth."
84+
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device3 } $errorMessage
85+
86+
# Invoke direct method on device
87+
$errorMessage = "The entered device ""fakeDevice"" doesn't exist."
88+
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId "fakeDevice" } $errorMessage
89+
7390
# Count devices
7491
$totalDevices = Invoke-AzIotHubQuery -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -Query "select * from devices"
7592
Assert-True { $totalDevices.Count -eq 4}

src/IotHub/IotHub.Test/ScenarioTests/IotHubDPModuleTests.ps1

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function Test-AzureRmIotHubModuleLifecycle
2727
$IotHubName = getAssetName
2828
$ResourceGroupName = getAssetName
2929
$Sku = "S1"
30+
$SasTokenPrefix = 'SharedAccessSignature'
3031
$device1 = getAssetName
3132
$module1 = getAssetName
3233
$module2 = getAssetName
@@ -39,6 +40,10 @@ function Test-AzureRmIotHubModuleLifecycle
3940
# Create Iot Hub
4041
$iothub = New-AzIotHub -Name $IotHubName -ResourceGroupName $ResourceGroupName -Location $Location -SkuName $Sku -Units 1
4142

43+
# Generate SAS token for IotHub
44+
$token = New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName
45+
Assert-StartsWith $SasTokenPrefix $token
46+
4247
# Add iot device with symmetric authentication
4348
$newDevice1 = Add-AzIotHubDevice -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -AuthMethod 'shared_private_key'
4449
Assert-True { $newDevice1.Id -eq $device1 }
@@ -61,6 +66,22 @@ function Test-AzureRmIotHubModuleLifecycle
6166
Assert-True { $newModule2.DeviceId -eq $device1 }
6267
Assert-True { $newModule2.Authentication.Type -eq 'SelfSigned' }
6368

69+
# Generate SAS token for module
70+
$moduleToken = New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId $module1
71+
Assert-StartsWith $SasTokenPrefix $moduleToken
72+
73+
# Expected error while generating SAS token for module
74+
$errorMessage = "You are unable to get sas token for module without device information."
75+
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -ModuleId $module1 } $errorMessage
76+
77+
# Expected error while generating SAS token for module
78+
$errorMessage = "This module does not support SAS auth."
79+
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId $module2 } $errorMessage
80+
81+
# Expected error while generating SAS token for module
82+
$errorMessage = "The entered module ""fakeModule"" doesn't exist."
83+
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId "fakeModule" } $errorMessage
84+
6485
# Count device modules
6586
$totalModules = Invoke-AzIotHubQuery -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -Query "select * from devices.modules where devices.Id='$device1'"
6687
Assert-True { $totalModules.Count -eq 2}

src/IotHub/IotHub.Test/SessionRecords/Microsoft.Azure.Commands.IotHub.Test.ScenarioTests.IotHubDPDeviceTests/TestAzureIotHubDeviceLifecycle.json

Lines changed: 1143 additions & 582 deletions
Large diffs are not rendered by default.

src/IotHub/IotHub.Test/SessionRecords/Microsoft.Azure.Commands.IotHub.Test.ScenarioTests.IotHubDPModuleTests/TestAzureIotHubModuleLifecycle.json

Lines changed: 951 additions & 384 deletions
Large diffs are not rendered by default.

src/IotHub/IotHub/Az.IotHub.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ CmdletsToExport = 'Add-AzIotHubKey', 'Get-AzIotHubEventHubConsumerGroup',
105105
'Get-AzIotHubModuleTwin', 'Update-AzIotHubModuleTwin',
106106
'Add-AzIotHubConfiguration', 'Get-AzIotHubConfiguration',
107107
'Remove-AzIotHubConfiguration', 'Set-AzIotHubConfiguration',
108-
'Invoke-AzIotHubModuleMethod', 'Invoke-AzIotHubQuery'
108+
'Invoke-AzIotHubModuleMethod', 'Invoke-AzIotHubQuery', 'New-AzIotHubSasToken'
109109
# Variables to export from this module
110110
# VariablesToExport = @()
111111

src/IotHub/IotHub/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
* Added cmdlet to invoke an edge module method in an Iot Hub.
3636
* Added cmdlet to invoke a query in an IoT hub to retrieve information using a SQL-like language.
3737
* Fix #11597: Add-AzIotHubDevice fails to create Edge Enabled Device without child devices.
38+
* Added cmdlet to generate SAS token for Iot Hub, device or module.
3839

3940
## Version 2.3.0
4041
* Added support to manage distributed settings per-device. New Cmdlets are:
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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+
namespace Microsoft.Azure.Commands.Management.IotHub
16+
{
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Globalization;
20+
using System.Management.Automation;
21+
using System.Security.Cryptography;
22+
using System.Text;
23+
using System.Web;
24+
using Microsoft.Azure.Commands.Management.IotHub.Common;
25+
using Microsoft.Azure.Commands.Management.IotHub.Models;
26+
using Microsoft.Azure.Devices;
27+
using Microsoft.Azure.Management.IotHub;
28+
using Microsoft.Azure.Management.IotHub.Models;
29+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
30+
using ResourceManager.Common.ArgumentCompleters;
31+
32+
[Cmdlet("New", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "IotHubSasToken", DefaultParameterSetName = ResourceParameterSet, SupportsShouldProcess = true)]
33+
[OutputType(typeof(string))]
34+
public class NewAzIotHubSasToken : IotHubBaseCmdlet
35+
{
36+
private const string ResourceIdParameterSet = "ResourceIdSet";
37+
private const string ResourceParameterSet = "ResourceSet";
38+
private const string InputObjectParameterSet = "InputObjectSet";
39+
40+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = InputObjectParameterSet, ValueFromPipeline = true, HelpMessage = "IotHub object")]
41+
[ValidateNotNullOrEmpty]
42+
public PSIotHub InputObject { get; set; }
43+
44+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Name of the Resource Group")]
45+
[ValidateNotNullOrEmpty]
46+
[ResourceGroupCompleter]
47+
public string ResourceGroupName { get; set; }
48+
49+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = ResourceIdParameterSet, ValueFromPipelineByPropertyName = true, HelpMessage = "IotHub Resource Id")]
50+
[ValidateNotNullOrEmpty]
51+
[ResourceIdCompleter("Microsoft.Devices/IotHubs")]
52+
public string ResourceId { get; set; }
53+
54+
[Parameter(Position = 1, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Name of the Iot Hub")]
55+
[ValidateNotNullOrEmpty]
56+
public string IotHubName { get; set; }
57+
58+
[Parameter(Mandatory = false, HelpMessage = "Target Device Id.")]
59+
[ValidateNotNullOrEmpty]
60+
public string DeviceId { get; set; }
61+
62+
[Parameter(Mandatory = false, HelpMessage = "Target Module Id.")]
63+
[ValidateNotNullOrEmpty]
64+
public string ModuleId { get; set; }
65+
66+
[Parameter(Mandatory = false, HelpMessage = "Access key name.")]
67+
[ValidateNotNullOrEmpty]
68+
public string KeyName { get; set; }
69+
70+
[Parameter(Mandatory = false, HelpMessage = "Access key type.")]
71+
[ValidateNotNullOrEmpty]
72+
public PSKeyType KeyType { get; set; }
73+
74+
[Parameter(Mandatory = false, HelpMessage = "Future expiry (in seconds) of the token to be generated. Default is 3600.")]
75+
[ValidateNotNullOrEmpty]
76+
public int Duration { get; set; }
77+
78+
public override void ExecuteCmdlet()
79+
{
80+
if (ShouldProcess(this.IotHubName, Properties.Resources.NewIotHubSasToken))
81+
{
82+
IotHubDescription iotHubDescription;
83+
if (ParameterSetName.Equals(InputObjectParameterSet))
84+
{
85+
this.ResourceGroupName = this.InputObject.Resourcegroup;
86+
this.IotHubName = this.InputObject.Name;
87+
iotHubDescription = IotHubUtils.ConvertObject<PSIotHub, IotHubDescription>(this.InputObject);
88+
}
89+
else
90+
{
91+
if (ParameterSetName.Equals(ResourceIdParameterSet))
92+
{
93+
this.ResourceGroupName = IotHubUtils.GetResourceGroupName(this.ResourceId);
94+
this.IotHubName = IotHubUtils.GetIotHubName(this.ResourceId);
95+
}
96+
97+
iotHubDescription = this.IotHubClient.IotHubResource.Get(this.ResourceGroupName, this.IotHubName);
98+
}
99+
100+
if (this.IsParameterBound(c => c.ModuleId) && !this.IsParameterBound(c => c.DeviceId))
101+
{
102+
throw new ArgumentException("You are unable to get sas token for module without device information.");
103+
}
104+
105+
if (!this.IsParameterBound(c => c.Duration))
106+
{
107+
this.Duration = 3600;
108+
}
109+
110+
string resourceUri = string.Empty;
111+
string keyName = string.Empty;
112+
string key = string.Empty;
113+
114+
if (this.IsParameterBound(c => c.DeviceId))
115+
{
116+
IEnumerable<SharedAccessSignatureAuthorizationRule> authPolicies = this.IotHubClient.IotHubResource.ListKeys(this.ResourceGroupName, this.IotHubName);
117+
SharedAccessSignatureAuthorizationRule policy = IotHubUtils.GetPolicy(authPolicies, PSAccessRights.RegistryRead);
118+
PSIotHubConnectionString psIotHubConnectionString = IotHubUtils.ToPSIotHubConnectionString(policy, iotHubDescription.Properties.HostName);
119+
RegistryManager registryManager = RegistryManager.CreateFromConnectionString(psIotHubConnectionString.PrimaryConnectionString);
120+
121+
if (this.IsParameterBound(c => c.ModuleId))
122+
{
123+
Module module = registryManager.GetModuleAsync(this.DeviceId, this.ModuleId).GetAwaiter().GetResult();
124+
if (module != null)
125+
{
126+
if (module.Authentication.Type.Equals(AuthenticationType.Sas))
127+
{
128+
resourceUri = string.Format("{0}/devices/{1}/modules/{2}", iotHubDescription.Properties.HostName, this.DeviceId, this.ModuleId);
129+
key = this.KeyType.Equals(PSKeyType.primary) ? module.Authentication.SymmetricKey.PrimaryKey : module.Authentication.SymmetricKey.SecondaryKey;
130+
}
131+
else
132+
{
133+
throw new ArgumentException("This module does not support SAS auth.");
134+
}
135+
}
136+
else
137+
{
138+
throw new ArgumentException($"The entered module \"{this.ModuleId}\" doesn't exist.");
139+
}
140+
}
141+
else
142+
{
143+
Device device = registryManager.GetDeviceAsync(this.DeviceId).GetAwaiter().GetResult();
144+
if (device != null)
145+
{
146+
if (device.Authentication.Type.Equals(AuthenticationType.Sas))
147+
{
148+
resourceUri = string.Format("{0}/devices/{1}", iotHubDescription.Properties.HostName, this.DeviceId);
149+
key = this.KeyType.Equals(PSKeyType.primary) ? device.Authentication.SymmetricKey.PrimaryKey : device.Authentication.SymmetricKey.SecondaryKey;
150+
}
151+
else
152+
{
153+
throw new ArgumentException("This device does not support SAS auth.");
154+
}
155+
}
156+
else
157+
{
158+
throw new ArgumentException($"The entered device \"{this.DeviceId}\" doesn't exist.");
159+
}
160+
}
161+
}
162+
else
163+
{
164+
if (!this.IsParameterBound(c => c.KeyName))
165+
{
166+
this.KeyName = "iothubowner";
167+
}
168+
SharedAccessSignatureAuthorizationRule authPolicy = this.IotHubClient.IotHubResource.GetKeysForKeyName(this.ResourceGroupName, this.IotHubName, this.KeyName);
169+
resourceUri = iotHubDescription.Properties.HostName;
170+
keyName = authPolicy.KeyName;
171+
key = this.KeyType.Equals(PSKeyType.primary) ? authPolicy.PrimaryKey : authPolicy.SecondaryKey;
172+
}
173+
174+
this.WriteObject(this.createToken(resourceUri, keyName, key, this.Duration));
175+
}
176+
}
177+
178+
private string createToken(string resourceUri, string keyName, string key, int duration)
179+
{
180+
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
181+
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + duration);
182+
string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
183+
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
184+
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
185+
var sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry);
186+
if (!string.IsNullOrEmpty(keyName))
187+
{
188+
sasToken += String.Format(CultureInfo.InvariantCulture, "&skn={0}", keyName);
189+
}
190+
return sasToken;
191+
}
192+
}
193+
}

src/IotHub/IotHub/Properties/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/IotHub/IotHub/Properties/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,7 @@
240240
<data name="InvokeIotHubQuery" xml:space="preserve">
241241
<value>Query an Iot Hub</value>
242242
</data>
243+
<data name="NewIotHubSasToken" xml:space="preserve">
244+
<value>Generate Sas Token</value>
245+
</data>
243246
</root>

src/IotHub/IotHub/help/Az.IotHub.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ Creates a new import devices job.
134134
### [New-AzIotHubKey](New-AzIotHubKey.md)
135135
Generate an Azure IoT Hub key.
136136

137+
### [New-AzIotHubSasToken](New-AzIotHubSasToken.md)
138+
Generate a SAS token for a target IoT Hub, device or module.
139+
137140
### [Remove-AzIotHub](Remove-AzIotHub.md)
138141
Deletes an IotHub.
139142

0 commit comments

Comments
 (0)