105 lines
3.5 KiB
C#
105 lines
3.5 KiB
C#
|
|
using w4c_workflows.Services.Security;
|
||
|
|
using Xunit;
|
||
|
|
|
||
|
|
namespace w4c_workflows.Tests;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Guard tests: verify where DNS is consulted and how resolution results are
|
||
|
|
/// combined with the policy.
|
||
|
|
/// </summary>
|
||
|
|
public class EgressGuardTests
|
||
|
|
{
|
||
|
|
[Fact]
|
||
|
|
public async Task Literal_private_address_is_blocked_without_dns()
|
||
|
|
{
|
||
|
|
var resolver = StubHostAddressResolver.Returning(EgressTestData.PublicAddress);
|
||
|
|
|
||
|
|
var decision = await EgressTestData.Guard(resolver: resolver)
|
||
|
|
.AuthorizeAsync(new Uri("http://127.0.0.1/x"), default);
|
||
|
|
|
||
|
|
Assert.False(decision.Allowed);
|
||
|
|
Assert.Equal("blocked_address", decision.Code);
|
||
|
|
Assert.Empty(resolver.Queries);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Literal_public_address_is_allowed_without_dns()
|
||
|
|
{
|
||
|
|
var resolver = StubHostAddressResolver.Returning("10.0.0.1"); // ignored: literal wins
|
||
|
|
|
||
|
|
var decision = await EgressTestData.Guard(resolver: resolver)
|
||
|
|
.AuthorizeAsync(new Uri("http://93.184.216.34/x"), default);
|
||
|
|
|
||
|
|
Assert.True(decision.Allowed);
|
||
|
|
Assert.Empty(resolver.Queries);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Host_resolving_to_a_private_address_is_blocked()
|
||
|
|
{
|
||
|
|
var resolver = StubHostAddressResolver.Returning("10.1.2.3");
|
||
|
|
|
||
|
|
var decision = await EgressTestData.Guard(resolver: resolver)
|
||
|
|
.AuthorizeAsync(new Uri("https://internal.example.com/x"), default);
|
||
|
|
|
||
|
|
Assert.False(decision.Allowed);
|
||
|
|
Assert.Equal("blocked_address", decision.Code);
|
||
|
|
Assert.Equal(new[] { "internal.example.com" }, resolver.Queries);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Host_resolving_to_a_public_address_is_allowed()
|
||
|
|
{
|
||
|
|
var resolver = StubHostAddressResolver.Returning(EgressTestData.PublicAddress);
|
||
|
|
|
||
|
|
var decision = await EgressTestData.Guard(resolver: resolver)
|
||
|
|
.AuthorizeAsync(new Uri("https://example.com/x"), default);
|
||
|
|
|
||
|
|
Assert.True(decision.Allowed);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Mixed_public_and_private_answers_are_blocked()
|
||
|
|
{
|
||
|
|
var resolver = StubHostAddressResolver.Returning(EgressTestData.PublicAddress, "192.168.0.1");
|
||
|
|
|
||
|
|
var decision = await EgressTestData.Guard(resolver: resolver)
|
||
|
|
.AuthorizeAsync(new Uri("https://rebind.example.com/x"), default);
|
||
|
|
|
||
|
|
Assert.False(decision.Allowed);
|
||
|
|
Assert.Equal("blocked_address", decision.Code);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Resolution_failure_is_denied()
|
||
|
|
{
|
||
|
|
var decision = await EgressTestData.Guard(resolver: StubHostAddressResolver.Failing())
|
||
|
|
.AuthorizeAsync(new Uri("https://does-not-resolve.example.com/x"), default);
|
||
|
|
|
||
|
|
Assert.False(decision.Allowed);
|
||
|
|
Assert.Equal("dns_failure", decision.Code);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Blocked_host_is_rejected_before_resolution()
|
||
|
|
{
|
||
|
|
var resolver = StubHostAddressResolver.Returning(EgressTestData.PublicAddress);
|
||
|
|
var policy = EgressTestData.Policy(o => o.BlockedHosts = ["nope.example.com"]);
|
||
|
|
|
||
|
|
var decision = await EgressTestData.Guard(policy, resolver)
|
||
|
|
.AuthorizeAsync(new Uri("https://nope.example.com/x"), default);
|
||
|
|
|
||
|
|
Assert.False(decision.Allowed);
|
||
|
|
Assert.Equal("host_blocked", decision.Code);
|
||
|
|
Assert.Empty(resolver.Queries);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Max_redirects_is_exposed_from_the_policy()
|
||
|
|
{
|
||
|
|
var guard = EgressTestData.Guard(EgressTestData.Policy(o => o.MaxRedirects = 2));
|
||
|
|
|
||
|
|
Assert.Equal(2, guard.MaxRedirects);
|
||
|
|
}
|
||
|
|
}
|