Skip to content

Commit

Permalink
Merge branch 'feature/dpsRbacSupport' of https://github.com/Azure/azu…
Browse files Browse the repository at this point in the history
…re-iot-sdk-csharp into feature/dpsRbacSupport
  • Loading branch information
dylanbulfinMS committed Feb 1, 2022
2 parents 7705a1f + 79d359d commit 00ab690
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 3 deletions.
70 changes: 69 additions & 1 deletion e2e/test/config/TestConfiguration.Provisioning.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.Azure.Devices.E2ETests.Provisioning;

#if !NET451

using Azure.Identity;

#endif

namespace Microsoft.Azure.Devices.E2ETests
{
Expand All @@ -14,6 +25,34 @@ public static partial class Provisioning

public static string ConnectionString => GetValue("PROVISIONING_CONNECTION_STRING");

public static string GetProvisioningHostName()
{
var connectionString = new ConnectionStringParser(ConnectionString);
return connectionString.ProvisioningHostName;
}

#if !NET451

public static ClientSecretCredential GetClientSecretCredential()
{
return new ClientSecretCredential(
GetValue("MSFT_TENANT_ID"),
GetValue("IOTHUB_CLIENT_ID"),
GetValue("IOTHUB_CLIENT_SECRET"));
}

#endif

public static string GetProvisioningSharedAccessSignature(TimeSpan timeToLive)
{
var connectionString = new ConnectionStringParser(ConnectionString);
return GenerateSasToken(
connectionString.ProvisioningHostName,
connectionString.SharedAccessKey,
timeToLive,
connectionString.SharedAccessKeyName);
}

public static string GlobalDeviceEndpoint =>
GetValue("DPS_GLOBALDEVICEENDPOINT", "global.azure-devices-provisioning.net");

Expand All @@ -38,6 +77,35 @@ public static X509Certificate2Collection GetGroupEnrollmentChain()
public static string FarAwayIotHubHostName => GetValue("FAR_AWAY_IOTHUB_HOSTNAME");

public static string CustomAllocationPolicyWebhook => GetValue("CUSTOM_ALLOCATION_POLICY_WEBHOOK");

private static string GenerateSasToken(string resourceUri, string sharedAccessKey, TimeSpan timeToLive, string policyName = default)
{
var epochTime = new DateTime(1970, 1, 1);
DateTime expiresOn = DateTime.UtcNow.Add(timeToLive);
TimeSpan secondsFromEpochTime = expiresOn.Subtract(epochTime);
long seconds = Convert.ToInt64(secondsFromEpochTime.TotalSeconds, CultureInfo.InvariantCulture);
string expiry = Convert.ToString(seconds, CultureInfo.InvariantCulture);

string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;

using var hmac = new HMACSHA256(Convert.FromBase64String(sharedAccessKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

// SharedAccessSignature sr=ENCODED(dh://myiothub.azure-devices.net/a/b/c?myvalue1=a)&sig=<Signature>&se=<ExpiresOnValue>[&skn=<KeyName>]
string token = string.Format(
CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}",
WebUtility.UrlEncode(resourceUri),
WebUtility.UrlEncode(signature),
expiry);

if (!string.IsNullOrWhiteSpace(policyName))
{
token += "&skn=" + policyName;
}

return token;
}
}
}
}
3 changes: 2 additions & 1 deletion e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -610,13 +610,14 @@ if ($certExists)
az iot dps certificate delete -g $ResourceGroup --dps-name $dpsName --name $uploadCertificateName --etag $etag
}
Write-Host "`nUploading new certificate to DPS."
az iot dps certificate create -g $ResourceGroup --path $rootCertPath --dps-name $dpsName --certificate-name $uploadCertificateName --output none
az iot dps certificate create -g $ResourceGroup --path $rootCertPath --dps-name $dpsName --certificate-name $uploadCertificateName

$isVerified = az iot dps certificate show -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName --query 'properties.isVerified' --output tsv
if ($isVerified -eq 'false')
{
Write-Host "`nVerifying certificate uploaded to DPS."
$etag = az iot dps certificate show -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName --query 'etag'
Write-Host "`n az iot dps certificate generate-verification-code -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName -e $etag --query 'properties.verificationCode'"
$requestedCommonName = az iot dps certificate generate-verification-code -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName -e $etag --query 'properties.verificationCode'
$verificationCertArgs = @{
"-DnsName" = $requestedCommonName;
Expand Down
59 changes: 59 additions & 0 deletions e2e/test/provisioning/ConnectionStringParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;

namespace Microsoft.Azure.Devices.E2ETests.Provisioning
{
internal class ConnectionStringParser
{
public string ProvisioningHostName { get; private set; }

public string DeviceId { get; private set; }

public string SharedAccessKey { get; private set; }

public string SharedAccessKeyName { get; private set; }

public ConnectionStringParser(string connectionString)
{
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new ArgumentException(nameof(connectionString), "Parameter cannot be null, empty, or whitespace.");
}

string[] parts = connectionString.Split(';');

foreach (string part in parts)
{
int separatorIndex = part.IndexOf('=');
if (separatorIndex < 0)
{
throw new ArgumentException($"Improperly formatted key/value pair: {part}.");
}

string key = part.Substring(0, separatorIndex);
string value = part.Substring(separatorIndex + 1);

switch (key.ToUpperInvariant())
{
case "HOSTNAME":
ProvisioningHostName = value;
break;

case "SHAREDACCESSKEY":
SharedAccessKey = value;
break;

case "DEVICEID":
DeviceId = value;
break;

case "SHAREDACCESSKEYNAME":
SharedAccessKeyName = value;
break;

default:
throw new NotSupportedException($"Unrecognized tag found in parameter {nameof(connectionString)}.");
}
}
}
}
}
59 changes: 59 additions & 0 deletions e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Azure;
using FluentAssertions;
using Microsoft.Azure.Devices.Provisioning.Security.Samples;
using Microsoft.Azure.Devices.Provisioning.Service;
using Microsoft.Azure.Devices.Shared;
Expand Down Expand Up @@ -110,6 +112,63 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation_Symmet
await ProvisioningServiceClient_GetEnrollmentGroupAttestation(AttestationMechanismType.SymmetricKey);
}

[LoggedTestMethod]
public async Task ProvisioningServiceClient_TokenCredentialAuth_Success()
{
// arrange
using var provisioningServiceClient = ProvisioningServiceClient.Create(
TestConfiguration.Provisioning.GetProvisioningHostName(),
TestConfiguration.Provisioning.GetClientSecretCredential());

IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(
provisioningServiceClient,
AttestationMechanismType.SymmetricKey,
null,
AllocationPolicy.Static,
null,
null,
null)
.ConfigureAwait(false);

// act
IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment);

// assert
individualEnrollmentResult.Should().NotBeNull();

// cleanup
await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId);
}

[LoggedTestMethod]
public async Task ProvisioningServiceClient_AzureSasCredentialAuth_Success()
{
// arrange
string signature = TestConfiguration.Provisioning.GetProvisioningSharedAccessSignature(TimeSpan.FromHours(1));
using var provisioningServiceClient = ProvisioningServiceClient.Create(
TestConfiguration.Provisioning.GetProvisioningHostName(),
new AzureSasCredential(signature));

IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(
provisioningServiceClient,
AttestationMechanismType.SymmetricKey,
null,
AllocationPolicy.Static,
null,
null,
null)
.ConfigureAwait(false);

// act
IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment);

// assert
individualEnrollmentResult.Should().NotBeNull();

// cleanup
await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId);
}

public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(AttestationMechanismType attestationType)
{
using var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString);
Expand Down
2 changes: 1 addition & 1 deletion provisioning/service/src/Contract/SDKUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Microsoft.Azure.Devices.Provisioning.Service
{
internal class SDKUtils
{
private const string ApiVersionProvisioning = "2019-03-31";
private const string ApiVersionProvisioning = "2021-10-01";
public const string ApiVersionQueryString = CustomHeaderConstants.ApiVersion + "=" + ApiVersionProvisioning;
}
}

0 comments on commit 00ab690

Please sign in to comment.