Skip to content

Commit

Permalink
[Event Hubs Client] Track Two: First Preview (Azure.Identity Support)
Browse files Browse the repository at this point in the history
    General
        - Ongoing review and updates to member visibility to reduce public scope and limit protected
          members and other minor refactorings.

    Event Hub Client
        - Added support for generic Azure.Identity token credentials, including compatibility for translating
          and passing as the appropriate token provider in the track one infrastructure.

        - Added support for a public `EventHubSharedKey` credential, to allow for authorization by the shared
          access key exposed in the Azure portal without using a connection string.

        - Added constructor overloads and associated infrastructure to allow for a namespace-level connection
          string to be used, so long as the Event Hub path is passed sepaarate of it.  This is intended to allow
          for the easiest "Hello World" scenario without needing to navigate deep into the Azure portal to find
          the correct connection string.

        - Generalized building and normalization of Event Hub resource names to the client for use with multiple
          credential types; previously, this was locked to the shared access signature credential.

    Authorization
        - Created an `EventHubSharedKeyCredential` to allow callers to make use of the shared key exposed in the
          Azure portal without using a connection string.

        - Added supporting types for accepting an Azure.Identity token credential and utilizing it with the existing
          track one security infrastructure.

     Live Tests
         - Added more basic scenarios around event hub client connection to the server to to smoke test the new credentials
           and constrctor overloads.
  • Loading branch information
jsquire committed Jun 18, 2019
1 parent 4689790 commit 3a678c7
Show file tree
Hide file tree
Showing 18 changed files with 1,223 additions and 191 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Messaging.EventHubs.Core;

namespace Azure.Messaging.EventHubs.Authorization
{
/// <summary>
/// Provides a credential based on a shared access signature for a given
/// Event Hub instance.
/// </summary>
///
/// <seealso cref="Azure.Core.TokenCredential" />
///
public sealed class EventHubSharedKeyCredential : TokenCredential
{
/// <summary>
/// The name of the shared access key to be used for authorization, as
/// reported by the Azure portal.
/// </summary>
///
public string SharedAccessKeyName { get; }

/// <summary>
/// The value of the shared access key to be used for authorization, as
/// reported by the Azure portal.
/// </summary>
///
private string SharedAccessKey { get; }

/// <summary>
/// Initializes a new instance of the <see cref="EventHubSharedKeyCredential"/> class.
/// </summary>
///
/// <param name="sharedAccessKeyName">The name of the shared access key to be used for authorization, as reported by the Azure portal.</param>
/// <param name="sharedAccessKey">The value of the shared access key to be used for authorization, as reported by the Azure portal.</param>
///
public EventHubSharedKeyCredential(string sharedAccessKeyName,
string sharedAccessKey)
{
Guard.ArgumentNotNullOrEmpty(nameof(sharedAccessKeyName), sharedAccessKeyName);
Guard.ArgumentNotNullOrEmpty(nameof(sharedAccessKey), sharedAccessKey);

SharedAccessKeyName = sharedAccessKeyName;
SharedAccessKey = sharedAccessKey;
}

/// <summary>
/// Retrieves the token that represents the shared access signature credential, for
/// use in authorization against an Event Hub.
/// </summary>
///
/// <param name="scopes">The access scopes to request a token for.</param>
/// <param name="cancellationToken">The token used to request cancellation of the operation.</param>
///
/// <returns>The token representating the shared access signature for this credential.</returns>
///
public override AccessToken GetToken(string[] scopes, CancellationToken cancellationToken) => throw new InvalidOperationException(Resources.SharedKeyCredentialCannotGenerateTokens);

/// <summary>
/// Retrieves the token that represents the shared access signature credential, for
/// use in authorization against an Event Hub.
/// </summary>
///
/// <param name="scopes">The access scopes to request a token for.</param>
/// <param name="cancellationToken">The token used to request cancellation of the operation.</param>
///
/// <returns>The token representating the shared access signature for this credential.</returns>
///
public override Task<AccessToken> GetTokenAsync(string[] scopes, CancellationToken cancellationToken) => throw new InvalidOperationException(Resources.SharedKeyCredentialCannotGenerateTokens);

/// <summary>
/// Coverts to shared access signature credential.
/// </summary>
///
/// <param name="eventHubResource">The Event Hubs resource to which the token is intended to serve as authorization.</param>
/// <param name="signatureValidityDuration">The duration that the signature should be considered valid; if not specified, a default will be assumed.</param>
///
/// <returns>A <see cref="SharedAccessSignatureCredential" /> based on the requested shared access key.</returns>
///
internal SharedAccessSignatureCredential ConvertToSharedAccessSignatureCredential(string eventHubResource,
TimeSpan? signatureValidityDuration = default) =>
new SharedAccessSignatureCredential(new SharedAccessSignature(eventHubResource, SharedAccessKeyName, SharedAccessKey, signatureValidityDuration));

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Messaging.EventHubs.Core;

namespace Azure.Messaging.EventHubs.Authorization
{
/// <summary>
/// Provides a generic token-based credential for a given Event Hub instance.
/// </summary>
///
/// <seealso cref="Azure.Core.TokenCredential" />
///
internal class EventHubTokenCredential : TokenCredential
{
/// <summary>
/// The Event Hubs resource to which the token is intended to serve as authorization.
/// </summary>
///
public string Resource { get; }

/// <summary>
/// The <see cref="TokenCredential" /> that forms the basis of this security token.
/// </summary>
///
private TokenCredential Credential { get; }

/// <summary>
/// Initializes a new instance of the <see cref="SharedAccessSignatureCredential"/> class.
/// </summary>
///
/// <param name="tokenCredential">The <see cref="TokenCredential" /> on which to base the token.</param>
/// <param name="eventHubResource">The Event Hubs resource to which the token is intended to serve as authorization.</param>
///
public EventHubTokenCredential(TokenCredential tokenCredential,
string eventHubResource)
{
Guard.ArgumentNotNull(nameof(tokenCredential), tokenCredential);
Guard.ArgumentNotNullOrEmpty(nameof(eventHubResource), eventHubResource);

Credential = tokenCredential;
Resource = eventHubResource;
}

/// <summary>
/// Retrieves the token that represents the shared access signature credential, for
/// use in authorization against an Event Hub.
/// </summary>
///
/// <param name="scopes">The access scopes to request a token for.</param>
/// <param name="cancellationToken">The token used to request cancellation of the operation.</param>
///
/// <returns>The token representating the shared access signature for this credential.</returns>
///
public override AccessToken GetToken(string[] scopes, CancellationToken cancellationToken) => Credential.GetToken(scopes, cancellationToken);

/// <summary>
/// Retrieves the token that represents the shared access signature credential, for
/// use in authorization against an Event Hub.
/// </summary>
///
/// <param name="scopes">The access scopes to request a token for.</param>
/// <param name="cancellationToken">The token used to request cancellation of the operation.</param>
///
/// <returns>The token representating the shared access signature for this credential.</returns>
///
public override Task<AccessToken> GetTokenAsync(string[] scopes, CancellationToken cancellationToken) => Credential.GetTokenAsync(scopes, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ internal class SharedAccessSignature
private const char TokenValuePairDelimiter = '&';

/// <summary>The default length of time to consider a signature valid, if not otherwise specified.</summary>
private static readonly TimeSpan DefaultSignatureValidityDuration = TimeSpan.FromMinutes(20);
private static readonly TimeSpan DefaultSignatureValidityDuration = TimeSpan.FromMinutes(30);

/// <summary>Represents the Unix epoch time value, January 1, 1970 12:00:00, UTC.</summary>
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
Expand Down Expand Up @@ -92,24 +92,19 @@ internal class SharedAccessSignature
/// Initializes a new instance of the <see cref="SharedAccessSignature"/> class.
/// </summary>
///
/// <param name="transportType">The type of protocol and transport that will be used for communicating with the Event Hubs service.</param>
/// <param name="host">The fully qualified host name for the Event Hubs namespace. This is likely to be similar to <c>{yournamespace}.servicebus.windows.net</c>.</param>
/// <param name="eventHubPath">The path of the specific Event Hub to connect the client to.</param>
/// <param name="eventHubResource">The Event Hubs resource to which the token is intended to serve as authorization.</param>
/// <param name="sharedAccessKeyName">The name of the shared access key that the signature should be based on.</param>
/// <param name="sharedAccessKey">The value of the shared access key for the signagure.</param>
/// <param name="signatureValidityDuration">The duration that the signature should be considered valid; if not specified, a default will be assumed.</param>
///
public SharedAccessSignature(TransportType transportType,
string host,
string eventHubPath,
public SharedAccessSignature(string eventHubResource,
string sharedAccessKeyName,
string sharedAccessKey,
TimeSpan? signatureValidityDuration = default)
{
signatureValidityDuration = signatureValidityDuration ?? DefaultSignatureValidityDuration;

Guard.ArgumentNotNullOrEmpty(nameof(host), host);
Guard.ArgumentNotNullOrEmpty(nameof(eventHubPath), eventHubPath);
Guard.ArgumentNotNullOrEmpty(nameof(eventHubResource), eventHubResource);
Guard.ArgumentNotNullOrEmpty(nameof(sharedAccessKeyName), sharedAccessKeyName);
Guard.ArgumentNotNullOrEmpty(nameof(sharedAccessKey), sharedAccessKey);

Expand All @@ -120,7 +115,7 @@ public SharedAccessSignature(TransportType transportType,
SharedAccessKeyName = sharedAccessKeyName;
SharedAccessKey = sharedAccessKey;
ExpirationUtc = DateTime.UtcNow.Add(signatureValidityDuration.Value);
Resource = BuildAudience(transportType, host, eventHubPath);
Resource = eventHubResource;
Value = BuildSignature(Resource, sharedAccessKeyName, sharedAccessKey, ExpirationUtc);
}

Expand Down Expand Up @@ -157,7 +152,7 @@ public SharedAccessSignature(string sharedAccessSignature) : this(sharedAccessSi
/// nitializes a new instance of the <see cref="SharedAccessSignature" /> class.
/// </summary>
///
/// <param name="resource">The resource to which this signature applies.</param>
/// <param name="eventHubResource">The Event Hubs resource to which the token is intended to serve as authorization.</param>
/// <param name="sharedAccessKeyName">The name of the shared access key that the signature should be based on.</param>
/// <param name="sharedAccessKey">The value of the shared access key for the signagure.</param>
/// <param name="value">The shared access signature to be used for authorization.</param>
Expand All @@ -168,13 +163,13 @@ public SharedAccessSignature(string sharedAccessSignature) : this(sharedAccessSi
/// allowing for direct setting of the property values without validation or adjustment.
/// </remarks>
///
internal SharedAccessSignature(string resource,
internal SharedAccessSignature(string eventHubResource,
string sharedAccessKeyName,
string sharedAccessKey,
string value,
DateTime expirationUtc)
{
Resource = resource;
Resource = eventHubResource;
SharedAccessKeyName = sharedAccessKeyName;
SharedAccessKey = sharedAccessKey;
Value = value;
Expand Down Expand Up @@ -388,38 +383,6 @@ private static string BuildSignature(string audience,
}
}

/// <summary>
/// Builds the audience for use in the signature.
/// </summary>
///
/// <param name="transportType">The type of protocol and transport that will be used for communicating with the Event Hubs service.</param>
/// <param name="host">The fully qualified host name for the Event Hubs namespace. This is likely to be similar to <c>{yournamespace}.servicebus.windows.net</c>.</param>
/// <param name="eventHubPath">The path of the specific Event Hub to connect the client to.</param>
///
/// <returns>The value to use as the audience of the signature.</returns>
///
private static string BuildAudience(TransportType transportType,
string host,
string eventHubPath)
{
var builder = new UriBuilder(host)
{
Scheme = transportType.GetUriScheme(),
Path = eventHubPath,
Port = -1,
Fragment = String.Empty,
Password = String.Empty,
UserName = String.Empty,
};

if (builder.Path.EndsWith("/"))
{
builder.Path = builder.Path.TrimEnd('/');
}

return builder.Uri.AbsoluteUri.ToLowerInvariant();
}

/// <summary>
/// Converts a Unix-style timestamp into the corresponding <see cref="DateTime" />
/// value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,12 @@ internal static TrackOne.EventHubClient CreateClient(string host,
tokenProvider = new TrackOneSharedAccessTokenProvider(sasCredential.SharedAccessSignature);
break;

case EventHubTokenCredential eventHubCredential:
tokenProvider = new TrackOneGenericTokenProvider(eventHubCredential);
break;

default:
throw new NotImplementedException("Only shared key credentials are currently supported.");
//TODO: Revisit this once Azure.Identity is ready for managed identities.
throw new ArgumentException(Resources.UnsupportedCredential, nameof(credential));
}

// Create the endpoint for the client.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Azure.Core;
using Azure.Messaging.EventHubs.Core;
using TrackOne;

namespace Azure.Messaging.EventHubs.Compatibility
{
/// <summary>
/// A compatibility shim allowing a token credential to be used as a
/// generic JWT security token with the Track One types.
/// </summary>
///
/// <seealso cref="Azure.Core.TokenCredential"/>
/// <seealso cref="TrackOne.SecurityToken" />
///
internal class TrackOneGenericToken : SecurityToken
{
/// <summary>
/// The <see cref="TokenCredential" /> that forms the basis of this security token.
/// </summary>
///
public TokenCredential Credential { get; }

/// <summary>
/// Initializes a new instance of the <see cref="TrackOneGenericToken"/> class.
/// </summary>
///
/// <param name="tokenCredential">The <see cref="TokenCredential" /> on which to base the token.</param>
/// <param name="jwtTokenString">The raw JWT token value from the <paramref name="tokenCredential" /></param>
/// <param name="eventHubResource">The Event Hubs resource to which the token is intended to serve as authorization.</param>
/// <param name="tokenExpirationUtc">The date and time that the token expires, in UTC.</param>
///
public TrackOneGenericToken(TokenCredential tokenCredential,
string jwtTokenString,
string eventHubResource,
DateTime tokenExpirationUtc) :
base(jwtTokenString, tokenExpirationUtc, eventHubResource, ClientConstants.JsonWebTokenType)
{
Guard.ArgumentNotNull(nameof(tokenCredential), tokenCredential);
Credential = tokenCredential;
}
}
}
Loading

0 comments on commit 3a678c7

Please sign in to comment.