-
Notifications
You must be signed in to change notification settings - Fork 11
feat: Managed bypass tokens #309
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
base: develop
Are you sure you want to change the base?
Changes from all commits
6b95b49
636b767
1fdec31
bec2d4d
af602a0
818aea7
d8f6358
c61efd1
8380e0c
8d6b508
b5768a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| using System.Security.Cryptography; | ||
| using System.Text; | ||
| using OneOf.Types; | ||
| using OpenShock.Common.Extensions; | ||
| using OpenShock.Common.Models; | ||
| using OpenShock.Common.Services.Configuration; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace OpenShock.Common.Middleware; | ||
|
|
||
| /// <summary> | ||
| /// Resolves the <c>X-OpenShock-Bypass-Token</c> header by comparing it to admin-set configuration | ||
| /// properties (<c>TURNSTILE_BYPASS_TOKEN</c>, <c>RATE_LIMIT_BYPASS_TOKEN</c>). The matched bypass | ||
| /// flags are stored on <see cref="HttpContext.Items"/> so downstream guards (rate limiter selectors, | ||
| /// turnstile service) can read them synchronously. | ||
| /// | ||
| /// Runs before <c>UseRateLimiter</c>. | ||
| /// </summary> | ||
| public sealed class BypassTokenMiddleware | ||
| { | ||
| public const string TurnstileConfigKey = "TURNSTILE_BYPASS_TOKEN"; | ||
| public const string RateLimitConfigKey = "RATE_LIMIT_BYPASS_TOKEN"; | ||
|
|
||
| private readonly RequestDelegate _next; | ||
|
|
||
| public BypassTokenMiddleware(RequestDelegate next) | ||
| { | ||
| _next = next; | ||
| } | ||
|
|
||
| public async Task InvokeAsync(HttpContext context, IConfigurationService config, ILogger<BypassTokenMiddleware> logger) | ||
| { | ||
| if (!context.TryGetBypassTokenFromHeader(out var presented)) | ||
| { | ||
| await _next(context); | ||
| return; | ||
| } | ||
|
|
||
| var matched = BypassTokenType.None; | ||
|
|
||
| if (await MatchesAsync(config, TurnstileConfigKey, presented)) matched |= BypassTokenType.Turnstile; | ||
| if (await MatchesAsync(config, RateLimitConfigKey, presented)) matched |= BypassTokenType.RateLimit; | ||
|
|
||
| if (matched != BypassTokenType.None) | ||
| { | ||
| context.SetBypassedTypes(matched); | ||
|
|
||
| // A credential that switches off Turnstile and rate limiting should never be used without | ||
| // leaving a trace. Logged at warning so it stands out in a production log, and the token | ||
| // itself is never written - only which protections it disabled, and for what. | ||
| logger.LogWarning( | ||
| "Bypass token accepted for {Matched} on {Method} {Path} from {RemoteIp}", | ||
| matched, context.Request.Method, context.Request.Path, context.Connection.RemoteIpAddress); | ||
|
|
||
| } | ||
| else | ||
| { | ||
| // A presented-but-unmatched token is either a stale secret or someone probing for one. | ||
| logger.LogWarning( | ||
| "Bypass token presented but matched nothing on {Method} {Path} from {RemoteIp}", | ||
| context.Request.Method, context.Request.Path, context.Connection.RemoteIpAddress); | ||
|
|
||
| } | ||
|
|
||
| await _next(context); | ||
|
Comment on lines
+33
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift Bound work for invalid bypass headers. Any caller can send this header with an arbitrary value. Each attempt performs two configuration-service calls and emits a warning before Cache the active bypass secrets outside the request path. Sample or rate-limit unmatched-token events, while retaining a bounded audit signal. 🧰 Tools🪛 GitHub Check: CodeQL[warning] 53-53: Log entries created from user input [warning] 53-53: Log entries created from user input [warning] 60-60: Log entries created from user input [warning] 60-60: Log entries created from user input 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| private static async Task<bool> MatchesAsync(IConfigurationService config, string key, string presented) | ||
| { | ||
| var result = await config.TryGetStringAsync(key); | ||
| return result.TryPickT0(out var configured, out _) | ||
| && !string.IsNullOrEmpty(configured) | ||
| && CryptographicOperations.FixedTimeEquals( | ||
| Encoding.UTF8.GetBytes(configured), | ||
| Encoding.UTF8.GetBytes(presented)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| namespace OpenShock.Common.Models; | ||
|
|
||
| [Flags] | ||
| public enum BypassTokenType | ||
| { | ||
| None = 0, | ||
| Turnstile = 1 << 0, | ||
| RateLimit = 1 << 1 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Include System accounts in bypass restrictions.
The application treats
AdminandSystemas privileged, but every new bypass restriction checks onlyRoleType.Admin. A System account can therefore use Turnstile bypass during login, password-reset initiation, or token reporting.API/Services/Account/AccountService.cs#L160-L164: includeRoleType.SysteminIsPrivilegedEmailAsync.API/Controller/Account/LoginV2.cs#L55-L58: reject bypassed authentication forAdminandSystem.API/Controller/Account/PasswordResetInitiateV2.cs#L46-L50: use the corrected protected-role predicate.API/Controller/Tokens/ReportTokens.cs#L55-L57: reject bypassed token reporting forAdminandSystem.📍 Affects 4 files
API/Services/Account/AccountService.cs#L160-L164(this comment)API/Controller/Account/LoginV2.cs#L55-L58API/Controller/Account/PasswordResetInitiateV2.cs#L46-L50API/Controller/Tokens/ReportTokens.cs#L55-L57🤖 Prompt for AI Agents