feat(security): DisableLoginGuard refuses DisableLogin outside Development without explicit ack
This commit is contained in:
@@ -24,4 +24,7 @@ public sealed class AuthDisableLoginOptions
|
|||||||
|
|
||||||
/// <summary>The username the auto-login principal is minted with. Default "multi-role".</summary>
|
/// <summary>The username the auto-login principal is minted with. Default "multi-role".</summary>
|
||||||
public string User { get; set; } = "multi-role";
|
public string User { get; set; } = "multi-role";
|
||||||
|
|
||||||
|
/// <summary>Explicit second acknowledgement key: permits DisableLogin outside Development. Default false.</summary>
|
||||||
|
public bool AllowOutsideDevelopment { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace ZB.MOM.WW.ScadaBridge.Security.Auth;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Startup guard for the dev/test <see cref="AuthDisableLoginOptions.DisableLogin"/> flag.
|
||||||
|
/// DisableLogin authenticates EVERY request with ALL roles (including Operator+Verifier,
|
||||||
|
/// nullifying the two-person secured-write control) on a SCADA control surface, so it is
|
||||||
|
/// refused outside the Development environment unless the operator sets the explicit
|
||||||
|
/// second acknowledgement key <see cref="AuthDisableLoginOptions.AllowOutsideDevelopment"/>.
|
||||||
|
/// Called by the Host composition root before AddSecurity(); fail-fast at boot.
|
||||||
|
/// </summary>
|
||||||
|
public static class DisableLoginGuard
|
||||||
|
{
|
||||||
|
public static void Validate(bool disableLogin, string environmentName, bool allowOutsideDevelopment)
|
||||||
|
{
|
||||||
|
if (!disableLogin) return;
|
||||||
|
if (string.Equals(environmentName, "Development", StringComparison.OrdinalIgnoreCase)) return;
|
||||||
|
if (allowOutsideDevelopment) return;
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"ScadaBridge:Security:Auth:DisableLogin=true disables ALL authentication on a SCADA control " +
|
||||||
|
$"surface and is refused outside the Development environment (current: '{environmentName}'). " +
|
||||||
|
"Remove the flag, run with ASPNETCORE_ENVIRONMENT=Development, or — only if you fully accept an " +
|
||||||
|
"unauthenticated deployment — additionally set " +
|
||||||
|
"ScadaBridge:Security:Auth:AllowOutsideDevelopment=true.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using ZB.MOM.WW.ScadaBridge.Security.Auth;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.ScadaBridge.Security.Tests;
|
||||||
|
|
||||||
|
public class DisableLoginGuardTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Production")]
|
||||||
|
[InlineData("Staging")]
|
||||||
|
[InlineData("")]
|
||||||
|
public void Validate_DisableLoginOutsideDevelopment_WithoutAck_Throws(string env)
|
||||||
|
{
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => DisableLoginGuard.Validate(disableLogin: true, environmentName: env, allowOutsideDevelopment: false));
|
||||||
|
Assert.Contains("DisableLogin", ex.Message);
|
||||||
|
Assert.Contains("Development", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Development", false)] // dev env: allowed without ack
|
||||||
|
[InlineData("development", false)] // case-insensitive env name
|
||||||
|
[InlineData("Production", true)] // explicit second ack key
|
||||||
|
public void Validate_Allowed_DoesNotThrow(string env, bool ack)
|
||||||
|
=> DisableLoginGuard.Validate(true, env, ack);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_FlagOff_NeverThrows()
|
||||||
|
=> DisableLoginGuard.Validate(false, "Production", false);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user