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

Edge module created from env vars #3140

Merged
merged 1 commit into from
Mar 1, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal static HttpClient GetHttpClient(Uri providerUri)
throw new InvalidOperationException("ProviderUri scheme is not supported");
}

internal static string GetBaseUrl(Uri providerUri)
internal static string GetBaseUri(Uri providerUri)
{
return providerUri.Scheme.Equals(UnixScheme, StringComparison.OrdinalIgnoreCase)
? $"{HttpScheme}://{providerUri.Segments.Last()}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task<string> SignAsync(string moduleId, string generationId, string
{
var hsmHttpClient = new HttpHsmClient(httpClient)
{
BaseUrl = HttpClientHelper.GetBaseUrl(_providerUri)
BaseUrl = HttpClientHelper.GetBaseUri(_providerUri)
};

SignResponse response = await SignWithRetryAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ private async Task<Socket> GetConnectedSocketAsync()
// around unix sockets in the BCL. For older versions of the framework we will continue to use the existing class since it works
// fine. For netcore 2.1 and greater as well as .NET 5.0 and greater the native framework version can be an alternatve.

var endpoint = new Microsoft.Azure.Devices.Client.HsmAuthentication.Transport.UnixDomainSocketEndPoint(_providerUri.LocalPath);
var endpoint = new UnixDomainSocketEndPoint(_providerUri.LocalPath);
await socket.ConnectAsync(endpoint).ConfigureAwait(false);
return socket;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;

namespace Microsoft.Azure.Devices.Client.HsmAuthentication.Transport
{
internal sealed class UnixDomainSocketEndPoint : EndPoint
{
private const int NativePathLength = 91; // sockaddr_un.sun_path at http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_un.h.html, -1 for terminator
private const AddressFamily EndPointAddressFamily = AddressFamily.Unix;
drwill-ms marked this conversation as resolved.
Show resolved Hide resolved

private static readonly Encoding s_pathEncoding = Encoding.UTF8;

private const int NativePathOffset = 2; // = offset of(struct sockaddr_un, sun_path). It's the same on Linux and OSX

private const int NativePathLength = 91; // sockaddr_un.sun_path at http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_un.h.html, -1 for terminator

private const int NativeAddressSize = NativePathOffset + NativePathLength;

private readonly string _path;
private readonly byte[] _encodedPath;

Expand All @@ -27,5 +36,70 @@ internal UnixDomainSocketEndPoint(string path)
throw new ArgumentOutOfRangeException(nameof(path), path);
}
}

internal UnixDomainSocketEndPoint(SocketAddress socketAddress)
{
if (socketAddress == null)
{
throw new ArgumentNullException(nameof(socketAddress));
}

if (socketAddress.Family != EndPointAddressFamily
|| socketAddress.Size > NativeAddressSize)
{
throw new ArgumentOutOfRangeException(nameof(socketAddress));
}

if (socketAddress.Size > NativePathOffset)
{
_encodedPath = new byte[socketAddress.Size - NativePathOffset];
for (int i = 0; i < _encodedPath.Length; i++)
{
_encodedPath[i] = socketAddress[NativePathOffset + i];
}

_path = s_pathEncoding.GetString(_encodedPath, 0, _encodedPath.Length);
}
else
{
_encodedPath = Array.Empty<byte>();
_path = string.Empty;
}
}

/// <summary>
/// Do not remove. Even though there are no references, it will be called by System.Net.HttpClient.
/// </summary>
public override SocketAddress Serialize()
{
var result = new SocketAddress(AddressFamily.Unix, NativeAddressSize);
Debug.Assert(_encodedPath.Length + NativePathOffset <= result.Size, "Expected path to fit in address");

for (int index = 0; index < _encodedPath.Length; index++)
{
result[NativePathOffset + index] = _encodedPath[index];
}
result[NativePathOffset + _encodedPath.Length] = 0; // path must be null-terminated

return result;
}


/// <summary>
/// Do not remove. Even though there are no references, it will be called by System.Net.HttpClient.
/// </summary>
public override EndPoint Create(SocketAddress socketAddress) => new UnixDomainSocketEndPoint(socketAddress);


/// <summary>
/// Do not remove. Even though there are no references, it will be called by System.Net.HttpClient.
/// </summary>
public override AddressFamily AddressFamily => EndPointAddressFamily;


/// <summary>
/// Do not remove. Even though there are no references, it will be called by System.Net.HttpClient.
/// </summary>
public override string ToString() => _path;
}
}
42 changes: 25 additions & 17 deletions iothub/device/src/Edge/EdgeModuleClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,36 @@ namespace Microsoft.Azure.Devices.Client
/// </summary>
internal static class EdgeModuleClientHelper
{
private const string IotEdgedUriVariableName = "IOTEDGE_WORKLOADURI";
private const string EdgeHsmApiVersion = "2018-06-28";

private const string IotEdgeWorkloadUriVariableName = "IOTEDGE_WORKLOADURI";
private const string IotHubHostNameVariableName = "IOTEDGE_IOTHUBHOSTNAME";
private const string GatewayHostnameVariableName = "IOTEDGE_GATEWAYHOSTNAME";
private const string GatewayHostNameVariableName = "IOTEDGE_GATEWAYHOSTNAME";
private const string DeviceIdVariableName = "IOTEDGE_DEVICEID";
private const string ModuleIdVariableName = "IOTEDGE_MODULEID";
private const string ModuleGenerationIdVariableName = "IOTEDGE_MODULEGENERATIONID";
private const string AuthSchemeVariableName = "IOTEDGE_AUTHSCHEME";
private const string SasTokenAuthScheme = "SasToken";
private const string EdgehubConnectionstringVariableName = "EdgeHubConnectionString";
private const string IothubConnectionstringVariableName = "IotHubConnectionString";
private const string EdgeHubConnectionStringVariableName = "EdgeHubConnectionString";
private const string IotHubConnectionStringVariableName = "IotHubConnectionString";
private const string EdgeCaCertificateFileVariableName = "EdgeModuleCACertificateFile";

internal static IotHubConnectionCredentials CreateIotHubConnectionCredentialsFromEnvironment()
{
IDictionary envVariables = Environment.GetEnvironmentVariables();

string connectionString = GetValueFromEnvironment(envVariables, EdgehubConnectionstringVariableName)
?? GetValueFromEnvironment(envVariables, IothubConnectionstringVariableName);
string connectionString = GetValueFromEnvironment(envVariables, EdgeHubConnectionStringVariableName)
?? GetValueFromEnvironment(envVariables, IotHubConnectionStringVariableName);

// First try to create from connection string and if env variable for connection string is not found try to create from edgedUri
// First try to create from connection string and if env variable for connection string is not found try
// to create from IOTEDGE_WORKLOADURI.
if (!string.IsNullOrWhiteSpace(connectionString))
{
return new IotHubConnectionCredentials(connectionString);
}

string edgedUri = GetValueFromEnvironment(envVariables, IotEdgedUriVariableName)
?? throw new InvalidOperationException($"Environment variable {IotEdgedUriVariableName} is required.");
string edgeWorkloadUri = GetValueFromEnvironment(envVariables, IotEdgeWorkloadUriVariableName)
?? throw new InvalidOperationException($"Environment variable {IotEdgeWorkloadUriVariableName} is required.");
string deviceId = GetValueFromEnvironment(envVariables, DeviceIdVariableName)
?? throw new InvalidOperationException($"Environment variable {DeviceIdVariableName} is required.");
string moduleId = GetValueFromEnvironment(envVariables, ModuleIdVariableName)
Expand All @@ -55,14 +58,14 @@ internal static IotHubConnectionCredentials CreateIotHubConnectionCredentialsFro
?? throw new InvalidOperationException($"Environment variable {AuthSchemeVariableName} is required.");
string generationId = GetValueFromEnvironment(envVariables, ModuleGenerationIdVariableName)
?? throw new InvalidOperationException($"Environment variable {ModuleGenerationIdVariableName} is required.");
string gateway = GetValueFromEnvironment(envVariables, GatewayHostnameVariableName);
string gateway = GetValueFromEnvironment(envVariables, GatewayHostNameVariableName);

if (!StringComparer.OrdinalIgnoreCase.Equals(authScheme, SasTokenAuthScheme))
{
throw new InvalidOperationException($"Unsupported authentication scheme. Supported scheme is {SasTokenAuthScheme}.");
}

ISignatureProvider signatureProvider = new HttpHsmSignatureProvider(edgedUri, ClientApiVersionHelper.ApiVersionLatest);
ISignatureProvider signatureProvider = new HttpHsmSignatureProvider(edgeWorkloadUri, EdgeHsmApiVersion);

// TODO: environment variables need to be added to accept SasTokenTimeToLive and SasTokenRenewalBuffer.
// These values can then be passed on to EdgeModuleAuthenticationWithHsm (internal class).
Expand All @@ -79,16 +82,18 @@ internal static IotHubConnectionCredentials CreateIotHubConnectionCredentialsFro
return new IotHubConnectionCredentials(authMethod, hostName, gateway);
}

internal static async Task<ICertificateValidator> CreateCertificateValidatorFromEnvironmentAsync(ITrustBundleProvider trustBundleProvider, IotHubClientOptions options)
internal static async Task<ICertificateValidator> CreateCertificateValidatorFromEnvironmentAsync(
ITrustBundleProvider trustBundleProvider,
IotHubClientOptions options)
{
Debug.Assert(options != null);

ICertificateValidator certificateValidator = NullCertificateValidator.Instance;

IDictionary envVariables = Environment.GetEnvironmentVariables();

string connectionString = GetValueFromEnvironment(envVariables, EdgehubConnectionstringVariableName)
?? GetValueFromEnvironment(envVariables, IothubConnectionstringVariableName);
string connectionString = GetValueFromEnvironment(envVariables, EdgeHubConnectionStringVariableName)
?? GetValueFromEnvironment(envVariables, IotHubConnectionStringVariableName);

// First try to create from connection string and if env variable for connection string is not found try to create from edgedUri
if (!string.IsNullOrWhiteSpace(connectionString))
Expand All @@ -107,15 +112,18 @@ internal static async Task<ICertificateValidator> CreateCertificateValidatorFrom
return certificateValidator;
}

string edgedUri = GetValueFromEnvironment(envVariables, IotEdgedUriVariableName) ?? throw new InvalidOperationException($"Environment variable {IotEdgedUriVariableName} is required.");
string gateway = GetValueFromEnvironment(envVariables, GatewayHostnameVariableName);
string edgeWorkloadUri = GetValueFromEnvironment(envVariables, IotEdgeWorkloadUriVariableName)
?? throw new InvalidOperationException($"Environment variable {IotEdgeWorkloadUriVariableName} is required.");
string gateway = GetValueFromEnvironment(envVariables, GatewayHostNameVariableName);

if (Logging.IsEnabled)
Logging.Info("EdgeModuleClientFactory setupTrustBundle from service");

if (!string.IsNullOrEmpty(gateway))
{
IList<X509Certificate2> certs = await trustBundleProvider.GetTrustBundleAsync(new Uri(edgedUri), ClientApiVersionHelper.ApiVersionLatest).ConfigureAwait(false);
IList<X509Certificate2> certs = await trustBundleProvider
.GetTrustBundleAsync(new Uri(edgeWorkloadUri), EdgeHsmApiVersion)
.ConfigureAwait(false);
certificateValidator = CreateCertificateValidator(certs, options);
}

Expand Down
2 changes: 1 addition & 1 deletion iothub/device/src/Edge/TrustBundleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task<IList<X509Certificate2>> GetTrustBundleAsync(Uri providerUri,
using HttpClient httpClient = HttpClientHelper.GetHttpClient(providerUri);
var hsmHttpClient = new HttpHsmClient(httpClient)
{
BaseUrl = HttpClientHelper.GetBaseUrl(providerUri)
BaseUrl = HttpClientHelper.GetBaseUri(providerUri)
};
TrustBundleResponse response = await GetTrustBundleWithRetryAsync(hsmHttpClient, apiVersion).ConfigureAwait(false);

Expand Down