Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
namespace Omika.Tests.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de sécurité du reverse proxy Apache local : analyse la source
|
||||
/// d'Omika.Api (Program.cs + ForwardedHeadersConfiguration.cs) pour garantir
|
||||
/// l'ordre du middleware, l'absence de configuration « trust-all », l'absence
|
||||
/// de certificat Kestrel et l'absence de bind sur 0.0.0.0.
|
||||
/// </summary>
|
||||
public class ReverseProxySecurityTests
|
||||
{
|
||||
private const string ProgramFile = "src/Omika.Api/Program.cs";
|
||||
private const string ForwardedHeadersFile = "src/Omika.Api/ForwardedHeadersConfiguration.cs";
|
||||
|
||||
[Fact]
|
||||
public void Program_UsesForwardedHeadersBeforeHttpsRedirection()
|
||||
{
|
||||
string source = ReadRepoFile(ProgramFile);
|
||||
|
||||
int forwarded = source.IndexOf("app.UseForwardedHeaders(", StringComparison.Ordinal);
|
||||
int https = source.IndexOf("app.UseHttpsRedirection(", StringComparison.Ordinal);
|
||||
|
||||
Assert.True(forwarded >= 0, "UseForwardedHeaders doit être présent.");
|
||||
Assert.True(https >= 0, "UseHttpsRedirection doit être présent.");
|
||||
Assert.True(
|
||||
forwarded < https,
|
||||
"UseForwardedHeaders doit s'exécuter AVANT UseHttpsRedirection.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReverseProxy_DoesNotClearProxyTrustLists()
|
||||
{
|
||||
string combined = ReadRepoFile(ProgramFile) + "\n" + ReadRepoFile(ForwardedHeadersFile);
|
||||
|
||||
Assert.DoesNotContain("KnownNetworks.Clear", combined, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("KnownIPNetworks.Clear", combined, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("KnownProxies.Clear", combined, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("RequireHeaderSymmetry = false", combined, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReverseProxy_DoesNotAddWildcardProxyTrust()
|
||||
{
|
||||
string combined = ReadRepoFile(ProgramFile) + "\n" + ReadRepoFile(ForwardedHeadersFile);
|
||||
|
||||
// Aucune adresse « any » (0.0.0.0 / [::]) ni réseau wildcard dans la confiance.
|
||||
Assert.DoesNotContain("IPAddress.Any", combined, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("IPAddress.IPv6Any", combined, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("0.0.0.0", combined, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OmikaApi_DoesNotConfigureKestrelCertificate()
|
||||
{
|
||||
foreach (string file in EnumerateOmikaApiSourceFiles())
|
||||
{
|
||||
string content = File.ReadAllText(file);
|
||||
|
||||
// Pas de terminaison TLS dans Kestrel (certificat / HTTPS) :
|
||||
// c'est Apache qui termine TLS.
|
||||
Assert.DoesNotContain("UseHttps(", content, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("AddHttps", content, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("X509Certificate", content, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".pfx", content, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("HttpsConnectionAdapterOptions", content, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OmikaApi_DoesNotBindToAnyAddress()
|
||||
{
|
||||
foreach (string file in EnumerateOmikaApiSourceFiles())
|
||||
{
|
||||
string content = File.ReadAllText(file);
|
||||
|
||||
Assert.DoesNotContain("0.0.0.0", content, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("IPAddress.Any", content, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("IPAddress.IPv6Any", content, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("ListenAnyIP", content, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_DoesNotHardcodeKestrelPort()
|
||||
{
|
||||
string source = ReadRepoFile(ProgramFile);
|
||||
|
||||
// Le port (5080) et l'URL doivent rester dans appsettings.Production.json,
|
||||
// jamais en dur dans Program.cs.
|
||||
Assert.DoesNotContain("5080", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("UseUrls", source, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string ReadRepoFile(string relativePath)
|
||||
{
|
||||
return File.ReadAllText(Path.Combine(RepoRoot(), relativePath.Replace('/', Path.DirectorySeparatorChar)));
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateOmikaApiSourceFiles()
|
||||
{
|
||||
string apiRoot = Path.Combine(RepoRoot(), "src", "Omika.Api");
|
||||
return Directory.EnumerateFiles(apiRoot, "*.cs", SearchOption.AllDirectories)
|
||||
.Where(f =>
|
||||
!f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar) &&
|
||||
!f.Contains(Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
private static string RepoRoot()
|
||||
{
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "Omika.slnx")))
|
||||
{
|
||||
dir = dir.Parent;
|
||||
}
|
||||
|
||||
Assert.NotNull(dir);
|
||||
return dir!.FullName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user