Skip to content

Commit a51a211

Browse files
committed
Telemetry: add endpoint/client tags, rename metrics
1 parent f006df4 commit a51a211

7 files changed

Lines changed: 193 additions & 105 deletions

File tree

README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ builder.Services.AddSecurityKey(options =>
216216

217217
AspNetCore.SecurityKey provides built-in IP address whitelisting capabilities to restrict API access based on client IP addresses. This feature supports both IPv4 and IPv6 addresses, individual IPs, and network ranges using CIDR notation.
218218

219-
### Configuration
219+
### IP Whitelisting Configuration
220220

221221
IP whitelisting is configured using the enhanced configuration format in `appsettings.json`:
222222

@@ -305,7 +305,7 @@ app.UseForwardedHeaders();
305305
app.UseSecurityKey();
306306
```
307307

308-
### Security Considerations
308+
### IP Whitelisting Considerations
309309

310310
1. **Combine with HTTPS**: IP whitelisting should always be combined with HTTPS to prevent man-in-the-middle attacks
311311
2. **Network Ranges**: Be careful with broad network ranges like `0.0.0.0/0` or `::/0` as they allow all addresses
@@ -724,21 +724,22 @@ Use these stable names when configuring OpenTelemetry:
724724

725725
### Metrics
726726

727-
| Metric | Unit | Description |
728-
| ------------------------------------- | ---------- | ------------------------------------------------------ |
729-
| `securitykey.authentication.requests` | `requests` | Number of SecurityKey authentication requests handled. |
730-
| `securitykey.authentication.failures` | `failures` | Number of failed SecurityKey authentication requests. |
731-
| `securitykey.authentication.duration` | `ms` | Duration of SecurityKey authentication attempts. |
727+
| Metric | Unit | Description |
728+
| --------------------------- | ----------- | ----------------------------------------------------- |
729+
| `securitykey.auth.requests` | `{request}` | Number of SecurityKey authentication attempts. |
730+
| `securitykey.auth.failures` | `{failure}` | Number of failed SecurityKey authentication attempts. |
731+
| `securitykey.auth.duration` | `ms` | Duration of SecurityKey authentication attempts. |
732732

733733
### Tags
734734

735-
| Tag | Description |
736-
| --------------------------------- | ------------------------------------------------------------------------------ |
737-
| `securitykey.auth.scheme` | Authentication scheme name when using the authentication handler. |
738-
| `securitykey.auth.result` | Authentication result, such as `success` or `failure`. |
739-
| `securitykey.auth.failure_reason` | Failure reason, such as `invalid_client` or `authentication_error`. |
740-
| `securitykey.auth.pattern` | Integration pattern, such as `middleware`, `endpoint_filter`, or `mvc_filter`. |
741-
| `securitykey.api_key.hash` | SHA-256 hash of the API key for tracking usage without exposing the raw key. |
735+
| Tag | Description |
736+
| --------------------------------- | -------------------------------------------------------------------------------- |
737+
| `securitykey.auth.scheme` | Authentication scheme name when using the authentication handler. |
738+
| `securitykey.auth.result` | Authentication result, such as `success` or `failure`. |
739+
| `securitykey.auth.failure_reason` | Failure reason, such as `invalid_client` or `authentication_error`. |
740+
| `securitykey.auth.pattern` | Integration pattern, such as `middleware`, `endpoint_filter`, or `mvc_filter`. |
741+
| `securitykey.client` | SHA-256 hash of the API key for tracking client usage without exposing raw keys. |
742+
| `securitykey.endpoint` | Resolved endpoint display name or request path. |
742743

743744
### OpenTelemetry Configuration Example
744745

src/AspNetCore.SecurityKey/SecurityKeyAuthenticationHandler.cs

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Text.Encodings.Web;
44

55
using Microsoft.AspNetCore.Authentication;
6+
using Microsoft.AspNetCore.Http;
67
using Microsoft.Extensions.DependencyInjection;
78
using Microsoft.Extensions.Logging;
89
using Microsoft.Extensions.Options;
@@ -16,6 +17,7 @@ namespace AspNetCore.SecurityKey;
1617
public class SecurityKeyAuthenticationHandler : AuthenticationHandler<SecurityKeyAuthenticationSchemeOptions>
1718
{
1819
private static readonly AuthenticateResult InvalidSecurityKey = AuthenticateResult.Fail("Invalid Security Key");
20+
private static readonly AuthenticateResult AuthenticationError = AuthenticateResult.Fail("Authentication error");
1921

2022
/// <summary>
2123
/// Initializes a new instance of the <see cref="SecurityKeyAuthenticationHandler"/> class.
@@ -40,6 +42,7 @@ public SecurityKeyAuthenticationHandler(
4042
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
4143
{
4244
var startTimestamp = 0L;
45+
string? endpoint = null;
4346
Activity? activity = null;
4447

4548
try
@@ -59,7 +62,10 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
5962
startTimestamp = Stopwatch.GetTimestamp();
6063
activity = SecurityKeyDiagnostics.ActivitySource.StartActivity(SecurityKeyDiagnostics.AuthenticationActivityName, ActivityKind.Server);
6164

65+
endpoint = GetEndpoint();
66+
6267
activity?.SetTag(SecurityKeyDiagnostics.AuthenticationSchemeTagName, Scheme.Name);
68+
activity?.SetTag(SecurityKeyDiagnostics.EndpointTagName, endpoint);
6369

6470
var ipAddress = keyExtractor.GetRemoteAddress(Context);
6571

@@ -70,7 +76,6 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
7076

7177
// Authenticate the security key and get the claims identity
7278
var identity = await keyValidator.Authenticate(securityKey, ipAddress, Scheme.Name, Context.RequestAborted);
73-
var securityKeyHash = SecurityKeyDiagnostics.ComputeSecurityKeyHash(securityKey);
7479

7580
if (!identity.IsAuthenticated)
7681
{
@@ -81,7 +86,8 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
8186
activity: activity,
8287
startTimestamp: startTimestamp,
8388
authenticationResult: SecurityKeyDiagnostics.AuthenticationResultFailure,
84-
securityKeyHash: securityKeyHash,
89+
securityKey: securityKey,
90+
endpoint: endpoint,
8591
failureReason: SecurityKeyDiagnostics.InvalidSecurityKeyFailureReason);
8692
}
8793

@@ -94,53 +100,57 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
94100
activity: activity,
95101
startTimestamp: startTimestamp,
96102
authenticationResult: SecurityKeyDiagnostics.AuthenticationResultSuccess,
97-
securityKeyHash: securityKeyHash);
103+
securityKey: securityKey,
104+
endpoint: endpoint);
98105

99106
}
107+
catch (OperationCanceledException) when (Context.RequestAborted.IsCancellationRequested)
108+
{
109+
throw;
110+
}
100111
catch (Exception ex) when (ex is not OperationCanceledException)
101112
{
102113
activity?.AddException(ex);
103114

104-
CompleteAuthentication(
105-
result: AuthenticateResult.Fail(ex),
115+
return CompleteAuthentication(
116+
result: AuthenticationError,
106117
activity: activity,
107118
startTimestamp: startTimestamp,
108119
authenticationResult: SecurityKeyDiagnostics.AuthenticationResultFailure,
120+
endpoint: endpoint,
109121
failureReason: SecurityKeyDiagnostics.AuthenticationErrorFailureReason);
110-
111-
throw;
112122
}
113123
finally
114124
{
115125
activity?.Dispose();
116126
}
117127
}
118128

119-
private static AuthenticateResult CompleteAuthentication(
129+
private AuthenticateResult CompleteAuthentication(
120130
AuthenticateResult result,
121131
Activity? activity,
122132
long startTimestamp,
123133
string authenticationResult,
124-
string? securityKeyHash = null,
134+
string? securityKey = null,
135+
string? endpoint = null,
125136
string? failureReason = null)
126137
{
127-
activity?.SetTag(SecurityKeyDiagnostics.AuthenticationResultTagName, authenticationResult);
128-
129-
if (securityKeyHash is not null)
130-
activity?.SetTag(SecurityKeyDiagnostics.SecurityKeyHashTagName, securityKeyHash);
131-
132-
if (failureReason is not null)
133-
activity?.SetTag(SecurityKeyDiagnostics.AuthenticationFailureReasonTagName, failureReason);
134-
135-
if (authenticationResult == SecurityKeyDiagnostics.AuthenticationResultFailure)
136-
activity?.SetStatus(ActivityStatusCode.Error, failureReason);
137-
138-
SecurityKeyDiagnostics.RecordAuthenticationMetrics(
138+
SecurityKeyDiagnostics.CompleteAuthentication(
139+
activity: activity,
139140
startTimestamp: startTimestamp,
140141
authenticationResult: authenticationResult,
141-
failureReason: failureReason,
142-
securityKeyHash: securityKeyHash);
142+
scheme: Scheme.Name,
143+
securityKey: securityKey,
144+
endpoint: endpoint,
145+
failureReason: failureReason);
143146

144147
return result;
145148
}
149+
150+
private string GetEndpoint()
151+
{
152+
return Context.GetEndpoint()?.DisplayName
153+
?? Request.Path.Value
154+
?? "unknown";
155+
}
146156
}

src/AspNetCore.SecurityKey/SecurityKeyAuthorizationFilter.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Diagnostics;
22

3+
using Microsoft.AspNetCore.Http;
34
using Microsoft.AspNetCore.Mvc;
45
using Microsoft.AspNetCore.Mvc.Filters;
56
using Microsoft.Extensions.Logging;
@@ -52,6 +53,9 @@ public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
5253

5354
using var activity = SecurityKeyDiagnostics.StartAuthenticationActivity(SecurityKeyDiagnostics.MvcFilterAuthenticationPattern);
5455
var startTimestamp = Stopwatch.GetTimestamp();
56+
var endpoint = GetEndpoint(context.HttpContext);
57+
58+
activity?.SetTag(SecurityKeyDiagnostics.EndpointTagName, endpoint);
5559

5660
try
5761
{
@@ -64,7 +68,8 @@ public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
6468
activity: activity,
6569
startTimestamp: startTimestamp,
6670
authenticationResult: SecurityKeyDiagnostics.AuthenticationResultSuccess,
67-
securityKey: securityKey);
71+
securityKey: securityKey,
72+
endpoint: endpoint);
6873

6974
return;
7075
}
@@ -76,6 +81,7 @@ public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
7681
startTimestamp: startTimestamp,
7782
authenticationResult: SecurityKeyDiagnostics.AuthenticationResultFailure,
7883
securityKey: securityKey,
84+
endpoint: endpoint,
7985
failureReason: SecurityKeyDiagnostics.InvalidSecurityKeyFailureReason);
8086

8187
context.Result = new UnauthorizedResult();
@@ -85,9 +91,17 @@ public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
8591
SecurityKeyDiagnostics.RecordAuthenticationException(
8692
activity: activity,
8793
startTimestamp: startTimestamp,
88-
exception: ex);
94+
exception: ex,
95+
endpoint: endpoint);
8996

9097
throw;
9198
}
9299
}
100+
101+
private static string GetEndpoint(HttpContext context)
102+
{
103+
return context.GetEndpoint()?.DisplayName
104+
?? context.Request.Path.Value
105+
?? "unknown";
106+
}
93107
}

0 commit comments

Comments
 (0)