Skip to content
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
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<PackageVersion Include="Azure.ResourceManager.Resources" Version="1.8.0-alpha.20240309.1" />
<PackageVersion Include="Azure.Provisioning" Version="1.0.0-alpha.20240315.2" />
<!-- ASP.NET Core dependencies -->
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Certificate" Version="$(MicrosoftAspNetCoreAuthenticationCertificatePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(MicrosoftAspNetCoreOpenApiPackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.OutputCaching.StackExchangeRedis" Version="$(MicrosoftAspNetCoreOutputCachingStackExchangeRedisPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="$(MicrosoftExtensionsCachingStackExchangeRedisPackageVersion)" />
Expand Down Expand Up @@ -142,4 +143,4 @@
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<!-- Introduced by Microsoft.Azure.Cosmos, https://github.com/Azure/azure-cosmos-dotnet-v3/issues/4302 -->
</ItemGroup>
</Project>
</Project>
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>8.0.1</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>8.0.2</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftExtensionsPrimitivesPackageVersion>8.0.0</MicrosoftExtensionsPrimitivesPackageVersion>
<MicrosoftAspNetCoreAuthenticationCertificatePackageVersion>8.0.3</MicrosoftAspNetCoreAuthenticationCertificatePackageVersion>
<MicrosoftAspNetCoreOpenApiPackageVersion>8.0.3</MicrosoftAspNetCoreOpenApiPackageVersion>
<MicrosoftAspNetCoreOutputCachingStackExchangeRedisPackageVersion>8.0.2</MicrosoftAspNetCoreOutputCachingStackExchangeRedisPackageVersion>
<MicrosoftExtensionsCachingStackExchangeRedisPackageVersion>8.0.2</MicrosoftExtensionsCachingStackExchangeRedisPackageVersion>
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Dashboard/Aspire.Dashboard.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" />
<PackageReference Include="Humanizer.Core" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Certificate" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;

namespace Aspire.Dashboard.Authentication.OtlpApiKey;

public class OtlpApiKeyAuthenticationHandler : AuthenticationHandler<OtlpApiKeyAuthenticationHandlerOptions>
{
public const string ApiKeyHeaderName = "x-otlp-api-key";

public OtlpApiKeyAuthenticationHandler(IOptionsMonitor<OtlpApiKeyAuthenticationHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder)
{
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (string.IsNullOrEmpty(Options.OtlpApiKey))
{
throw new InvalidOperationException("OTLP API key is not configured.");
}

if (Context.Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKey))
{
if (Options.OtlpApiKey != apiKey)
{
return Task.FromResult(AuthenticateResult.Fail("Incoming API key doesn't match required API key."));
}
}
else
{
return Task.FromResult(AuthenticateResult.Fail($"API key from '{ApiKeyHeaderName}' header is missing."));
}

return Task.FromResult(AuthenticateResult.NoResult());
}
}

public static class OtlpApiKeyAuthenticationDefaults
{
public const string AuthenticationScheme = "OtlpApiKey";
}

public sealed class OtlpApiKeyAuthenticationHandlerOptions : AuthenticationSchemeOptions
{
public string? OtlpApiKey { get; set; }
}
11 changes: 11 additions & 0 deletions src/Aspire.Dashboard/Authentication/OtlpAuthMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Dashboard.Authentication;

public enum OtlpAuthMode
{
None,
ApiKey,
ClientCertificate
}
10 changes: 10 additions & 0 deletions src/Aspire.Dashboard/Authentication/OtlpAuthorization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Dashboard.Authentication;

public static class OtlpAuthorization
{
public const string PolicyName = "OtlpPolicy";
public const string OtlpClaimName = "OtlpClaim";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Security.Claims;
using System.Text.Encodings.Web;
using Aspire.Dashboard.Authentication.OtlpApiKey;
using Aspire.Dashboard.Authentication.OtlpConnection;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.Extensions.Options;

namespace Aspire.Dashboard.Authentication;

public sealed class OtlpCompositeAuthenticationHandler : AuthenticationHandler<OtlpCompositeAuthenticationHandlerOptions>
{
public OtlpCompositeAuthenticationHandler(IOptionsMonitor<OtlpCompositeAuthenticationHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder)
{
}

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var connectionResult = await Context.AuthenticateAsync(OtlpConnectionAuthenticationDefaults.AuthenticationScheme).ConfigureAwait(false);
if (connectionResult.Failure != null)
{
return connectionResult;
}

var scheme = Options.OtlpAuthMode switch
{
OtlpAuthMode.ApiKey => OtlpApiKeyAuthenticationDefaults.AuthenticationScheme,
OtlpAuthMode.ClientCertificate => CertificateAuthenticationDefaults.AuthenticationScheme,
_ => null
};

if (scheme is not null)
{
var result = await Context.AuthenticateAsync(scheme).ConfigureAwait(false);
if (result.Failure is not null)
{
return result;
}
}

var id = new ClaimsIdentity([new Claim(OtlpAuthorization.OtlpClaimName, bool.TrueString)]);

return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(id), Scheme.Name));
}
}

public static class OtlpCompositeAuthenticationDefaults
{
public const string AuthenticationScheme = "OtlpComposite";
}

public sealed class OtlpCompositeAuthenticationHandlerOptions : AuthenticationSchemeOptions
{
public OtlpAuthMode OtlpAuthMode { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Dashboard.Otlp.Security;
namespace Aspire.Dashboard.Authentication.OtlpConnection;

/// <summary>
/// This feature's presence on a connection indicates that the connection is for OTLP.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Server.Kestrel.Core;

namespace Aspire.Dashboard.Otlp.Security;
namespace Aspire.Dashboard.Authentication.OtlpConnection;

internal static class ListenOptionsOtlpExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;

namespace Aspire.Dashboard.Authentication.OtlpConnection;

public class OtlpConnectionAuthenticationHandler : AuthenticationHandler<OtlpConnectionAuthenticationHandlerOptions>
{
public OtlpConnectionAuthenticationHandler(IOptionsMonitor<OtlpConnectionAuthenticationHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder)
{
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (Context.Features.Get<IOtlpConnectionFeature>() == null)
{
return Task.FromResult(AuthenticateResult.Fail("OTLP is not enabled on this connection."));
}

return Task.FromResult(AuthenticateResult.NoResult());
}
}

public static class OtlpConnectionAuthenticationDefaults
{
public const string AuthenticationScheme = "OtlpConnection";
}

public sealed class OtlpConnectionAuthenticationHandlerOptions : AuthenticationSchemeOptions
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using Microsoft.AspNetCore.Connections;

namespace Aspire.Dashboard.Otlp.Security;
namespace Aspire.Dashboard.Authentication.OtlpConnection;

/// <summary>
/// This connection middleware registers an OTLP feature on the connection.
Expand Down
14 changes: 14 additions & 0 deletions src/Aspire.Dashboard/DashboardStartupConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Dashboard.Authentication;

namespace Aspire.Dashboard;

public sealed class DashboardStartupConfiguration
{
public required Uri[] BrowserUris { get; init; }
public required Uri OtlpUri { get; init; }
public required OtlpAuthMode OtlpAuthMode { get; init; }
public required string? OtlpApiKey { get; init; }
}
Loading