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

[Iot Hub] Generate SAS token for Iot Hub, device or module #11628

Merged
merged 3 commits into from
Apr 20, 2020
Merged
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
17 changes: 17 additions & 0 deletions src/IotHub/IotHub.Test/ScenarioTests/IotHubDPDeviceTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function Test-AzureRmIotHubDeviceLifecycle
$IotHubName = getAssetName
$ResourceGroupName = getAssetName
$Sku = "S1"
$SasTokenPrefix = 'SharedAccessSignature'
$device1 = getAssetName
$device2 = getAssetName
$device3 = getAssetName
Expand All @@ -42,6 +43,10 @@ function Test-AzureRmIotHubDeviceLifecycle
# Create Iot Hub
$iothub = New-AzIotHub -Name $IotHubName -ResourceGroupName $ResourceGroupName -Location $Location -SkuName $Sku -Units 1

# Generate SAS token for IotHub
$token = New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName
Assert-StartsWith $SasTokenPrefix $token

# Get all devices
$devices = Get-AzIotHubDevice -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName
Assert-True { $devices.Count -eq 0 }
Expand Down Expand Up @@ -70,6 +75,18 @@ function Test-AzureRmIotHubDeviceLifecycle
Assert-True { $newDevice6.Authentication.Type -eq 'Sas' }
Assert-True { $newDevice6.Capabilities.IotEdge }

# Generate SAS token for device
$deviceToken = New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1
Assert-StartsWith $SasTokenPrefix $deviceToken

# Expected error while generating SAS token for device
$errorMessage = "This device does not support SAS auth."
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device3 } $errorMessage

# Invoke direct method on device
$errorMessage = "The entered device ""fakeDevice"" doesn't exist."
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId "fakeDevice" } $errorMessage

# Count devices
$totalDevices = Invoke-AzIotHubQuery -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -Query "select * from devices"
Assert-True { $totalDevices.Count -eq 4}
Expand Down
21 changes: 21 additions & 0 deletions src/IotHub/IotHub.Test/ScenarioTests/IotHubDPModuleTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function Test-AzureRmIotHubModuleLifecycle
$IotHubName = getAssetName
$ResourceGroupName = getAssetName
$Sku = "S1"
$SasTokenPrefix = 'SharedAccessSignature'
$device1 = getAssetName
$module1 = getAssetName
$module2 = getAssetName
Expand All @@ -39,6 +40,10 @@ function Test-AzureRmIotHubModuleLifecycle
# Create Iot Hub
$iothub = New-AzIotHub -Name $IotHubName -ResourceGroupName $ResourceGroupName -Location $Location -SkuName $Sku -Units 1

# Generate SAS token for IotHub
$token = New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName
Assert-StartsWith $SasTokenPrefix $token

# Add iot device with symmetric authentication
$newDevice1 = Add-AzIotHubDevice -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -AuthMethod 'shared_private_key'
Assert-True { $newDevice1.Id -eq $device1 }
Expand All @@ -61,6 +66,22 @@ function Test-AzureRmIotHubModuleLifecycle
Assert-True { $newModule2.DeviceId -eq $device1 }
Assert-True { $newModule2.Authentication.Type -eq 'SelfSigned' }

# Generate SAS token for module
$moduleToken = New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId $module1
Assert-StartsWith $SasTokenPrefix $moduleToken

# Expected error while generating SAS token for module
$errorMessage = "You are unable to get sas token for module without device information."
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -ModuleId $module1 } $errorMessage

# Expected error while generating SAS token for module
$errorMessage = "This module does not support SAS auth."
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId $module2 } $errorMessage

# Expected error while generating SAS token for module
$errorMessage = "The entered module ""fakeModule"" doesn't exist."
Assert-ThrowsContains { New-AzIotHubSasToken -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId "fakeModule" } $errorMessage

# Count device modules
$totalModules = Invoke-AzIotHubQuery -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -Query "select * from devices.modules where devices.Id='$device1'"
Assert-True { $totalModules.Count -eq 2}
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/IotHub/IotHub/Az.IotHub.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ CmdletsToExport = 'Add-AzIotHubKey', 'Get-AzIotHubEventHubConsumerGroup',
'Get-AzIotHubModuleTwin', 'Update-AzIotHubModuleTwin',
'Add-AzIotHubConfiguration', 'Get-AzIotHubConfiguration',
'Remove-AzIotHubConfiguration', 'Set-AzIotHubConfiguration',
'Invoke-AzIotHubModuleMethod', 'Invoke-AzIotHubQuery'
'Invoke-AzIotHubModuleMethod', 'Invoke-AzIotHubQuery', 'New-AzIotHubSasToken'
# Variables to export from this module
# VariablesToExport = @()

Expand Down
1 change: 1 addition & 0 deletions src/IotHub/IotHub/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
* Added cmdlet to invoke an edge module method in an Iot Hub.
* Added cmdlet to invoke a query in an IoT hub to retrieve information using a SQL-like language.
* Fix #11597: Add-AzIotHubDevice fails to create Edge Enabled Device without child devices.
* Added cmdlet to generate SAS token for Iot Hub, device or module.

## Version 2.3.0
* Added support to manage distributed settings per-device. New Cmdlets are:
Expand Down
193 changes: 193 additions & 0 deletions src/IotHub/IotHub/IotHub/DataPlane/NewAzIotHubSasToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

namespace Microsoft.Azure.Commands.Management.IotHub
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Management.Automation;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Microsoft.Azure.Commands.Management.IotHub.Common;
using Microsoft.Azure.Commands.Management.IotHub.Models;
using Microsoft.Azure.Devices;
using Microsoft.Azure.Management.IotHub;
using Microsoft.Azure.Management.IotHub.Models;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using ResourceManager.Common.ArgumentCompleters;

[Cmdlet("New", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "IotHubSasToken", DefaultParameterSetName = ResourceParameterSet, SupportsShouldProcess = true)]
[OutputType(typeof(string))]
public class NewAzIotHubSasToken : IotHubBaseCmdlet
{
private const string ResourceIdParameterSet = "ResourceIdSet";
private const string ResourceParameterSet = "ResourceSet";
private const string InputObjectParameterSet = "InputObjectSet";

[Parameter(Position = 0, Mandatory = true, ParameterSetName = InputObjectParameterSet, ValueFromPipeline = true, HelpMessage = "IotHub object")]
[ValidateNotNullOrEmpty]
public PSIotHub InputObject { get; set; }

[Parameter(Position = 0, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Name of the Resource Group")]
[ValidateNotNullOrEmpty]
[ResourceGroupCompleter]
public string ResourceGroupName { get; set; }

[Parameter(Position = 0, Mandatory = true, ParameterSetName = ResourceIdParameterSet, ValueFromPipelineByPropertyName = true, HelpMessage = "IotHub Resource Id")]
[ValidateNotNullOrEmpty]
[ResourceIdCompleter("Microsoft.Devices/IotHubs")]
public string ResourceId { get; set; }

[Parameter(Position = 1, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Name of the Iot Hub")]
[ValidateNotNullOrEmpty]
public string IotHubName { get; set; }

[Parameter(Mandatory = false, HelpMessage = "Target Device Id.")]
[ValidateNotNullOrEmpty]
public string DeviceId { get; set; }

[Parameter(Mandatory = false, HelpMessage = "Target Module Id.")]
[ValidateNotNullOrEmpty]
public string ModuleId { get; set; }

[Parameter(Mandatory = false, HelpMessage = "Access key name.")]
[ValidateNotNullOrEmpty]
public string KeyName { get; set; }

[Parameter(Mandatory = false, HelpMessage = "Access key type.")]
[ValidateNotNullOrEmpty]
public PSKeyType KeyType { get; set; }

[Parameter(Mandatory = false, HelpMessage = "Future expiry (in seconds) of the token to be generated. Default is 3600.")]
[ValidateNotNullOrEmpty]
public int Duration { get; set; }

public override void ExecuteCmdlet()
{
if (ShouldProcess(this.IotHubName, Properties.Resources.NewIotHubSasToken))
{
IotHubDescription iotHubDescription;
if (ParameterSetName.Equals(InputObjectParameterSet))
{
this.ResourceGroupName = this.InputObject.Resourcegroup;
this.IotHubName = this.InputObject.Name;
iotHubDescription = IotHubUtils.ConvertObject<PSIotHub, IotHubDescription>(this.InputObject);
}
else
{
if (ParameterSetName.Equals(ResourceIdParameterSet))
{
this.ResourceGroupName = IotHubUtils.GetResourceGroupName(this.ResourceId);
this.IotHubName = IotHubUtils.GetIotHubName(this.ResourceId);
}

iotHubDescription = this.IotHubClient.IotHubResource.Get(this.ResourceGroupName, this.IotHubName);
}

if (this.IsParameterBound(c => c.ModuleId) && !this.IsParameterBound(c => c.DeviceId))
{
throw new ArgumentException("You are unable to get sas token for module without device information.");
}

if (!this.IsParameterBound(c => c.Duration))
{
this.Duration = 3600;
}

string resourceUri = string.Empty;
string keyName = string.Empty;
string key = string.Empty;

if (this.IsParameterBound(c => c.DeviceId))
{
IEnumerable<SharedAccessSignatureAuthorizationRule> authPolicies = this.IotHubClient.IotHubResource.ListKeys(this.ResourceGroupName, this.IotHubName);
SharedAccessSignatureAuthorizationRule policy = IotHubUtils.GetPolicy(authPolicies, PSAccessRights.RegistryRead);
PSIotHubConnectionString psIotHubConnectionString = IotHubUtils.ToPSIotHubConnectionString(policy, iotHubDescription.Properties.HostName);
RegistryManager registryManager = RegistryManager.CreateFromConnectionString(psIotHubConnectionString.PrimaryConnectionString);

if (this.IsParameterBound(c => c.ModuleId))
{
Module module = registryManager.GetModuleAsync(this.DeviceId, this.ModuleId).GetAwaiter().GetResult();
if (module != null)
{
if (module.Authentication.Type.Equals(AuthenticationType.Sas))
{
resourceUri = string.Format("{0}/devices/{1}/modules/{2}", iotHubDescription.Properties.HostName, this.DeviceId, this.ModuleId);
key = this.KeyType.Equals(PSKeyType.primary) ? module.Authentication.SymmetricKey.PrimaryKey : module.Authentication.SymmetricKey.SecondaryKey;
}
else
{
throw new ArgumentException("This module does not support SAS auth.");
}
}
else
{
throw new ArgumentException($"The entered module \"{this.ModuleId}\" doesn't exist.");
}
}
else
{
Device device = registryManager.GetDeviceAsync(this.DeviceId).GetAwaiter().GetResult();
if (device != null)
{
if (device.Authentication.Type.Equals(AuthenticationType.Sas))
{
resourceUri = string.Format("{0}/devices/{1}", iotHubDescription.Properties.HostName, this.DeviceId);
key = this.KeyType.Equals(PSKeyType.primary) ? device.Authentication.SymmetricKey.PrimaryKey : device.Authentication.SymmetricKey.SecondaryKey;
}
else
{
throw new ArgumentException("This device does not support SAS auth.");
}
}
else
{
throw new ArgumentException($"The entered device \"{this.DeviceId}\" doesn't exist.");
}
}
}
else
{
if (!this.IsParameterBound(c => c.KeyName))
{
this.KeyName = "iothubowner";
}
SharedAccessSignatureAuthorizationRule authPolicy = this.IotHubClient.IotHubResource.GetKeysForKeyName(this.ResourceGroupName, this.IotHubName, this.KeyName);
resourceUri = iotHubDescription.Properties.HostName;
keyName = authPolicy.KeyName;
key = this.KeyType.Equals(PSKeyType.primary) ? authPolicy.PrimaryKey : authPolicy.SecondaryKey;
}

this.WriteObject(this.createToken(resourceUri, keyName, key, this.Duration));
}
}

private string createToken(string resourceUri, string keyName, string key, int duration)
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + duration);
string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry);
if (!string.IsNullOrEmpty(keyName))
{
sasToken += String.Format(CultureInfo.InvariantCulture, "&skn={0}", keyName);
}
return sasToken;
}
}
}
9 changes: 9 additions & 0 deletions src/IotHub/IotHub/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/IotHub/IotHub/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,7 @@
<data name="InvokeIotHubQuery" xml:space="preserve">
<value>Query an Iot Hub</value>
</data>
<data name="NewIotHubSasToken" xml:space="preserve">
<value>Generate Sas Token</value>
</data>
</root>
3 changes: 3 additions & 0 deletions src/IotHub/IotHub/help/Az.IotHub.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ Creates a new import devices job.
### [New-AzIotHubKey](New-AzIotHubKey.md)
Generate an Azure IoT Hub key.

### [New-AzIotHubSasToken](New-AzIotHubSasToken.md)
Generate a SAS token for a target IoT Hub, device or module.

### [Remove-AzIotHub](Remove-AzIotHub.md)
Deletes an IotHub.

Expand Down
Loading