Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management. - Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management. - Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support. - Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
263 lines
8.6 KiB
C#
263 lines
8.6 KiB
C#
// =============================================================================
|
|
// SecurityMisconfigurationTests.cs
|
|
// Sprint: SPRINT_0352_0001_0001_security_testing_framework
|
|
// Task: SEC-0352-007
|
|
// OWASP A05:2021 - Security Misconfiguration
|
|
// =============================================================================
|
|
|
|
using FluentAssertions;
|
|
using StellaOps.Security.Tests.Infrastructure;
|
|
|
|
namespace StellaOps.Security.Tests.A05_SecurityMisconfiguration;
|
|
|
|
/// <summary>
|
|
/// Tests for OWASP A05:2021 - Security Misconfiguration.
|
|
/// Ensures proper security configuration across all modules.
|
|
/// </summary>
|
|
[Trait("Category", "Security")]
|
|
[Trait("OWASP", "A05")]
|
|
public sealed class SecurityMisconfigurationTests : SecurityTestBase
|
|
{
|
|
[Fact(DisplayName = "A05-001: Debug mode should be disabled in production config")]
|
|
public void DebugMode_ShouldBeDisabledInProduction()
|
|
{
|
|
// Arrange
|
|
var productionConfig = LoadConfiguration("production");
|
|
|
|
// Assert
|
|
productionConfig.Should().NotContainKey("Debug");
|
|
productionConfig.GetValueOrDefault("ASPNETCORE_ENVIRONMENT").Should().NotBe("Development");
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-002: Error details should not leak in production")]
|
|
public void ErrorDetails_ShouldNotLeakInProduction()
|
|
{
|
|
// Arrange
|
|
var productionConfig = LoadConfiguration("production");
|
|
|
|
// Assert
|
|
productionConfig.GetValueOrDefault("DetailedErrors")?.Should().NotBe("true");
|
|
productionConfig.GetValueOrDefault("UseDeveloperExceptionPage")?.Should().NotBe("true");
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-003: Security headers should be configured")]
|
|
public void SecurityHeaders_ShouldBeConfigured()
|
|
{
|
|
// Arrange
|
|
var requiredHeaders = new[]
|
|
{
|
|
"X-Content-Type-Options",
|
|
"X-Frame-Options",
|
|
"X-XSS-Protection",
|
|
"Strict-Transport-Security",
|
|
"Content-Security-Policy"
|
|
};
|
|
|
|
// Act
|
|
var configuredHeaders = GetSecurityHeaders();
|
|
|
|
// Assert
|
|
foreach (var header in requiredHeaders)
|
|
{
|
|
configuredHeaders.Should().ContainKey(header,
|
|
$"Security header {header} should be configured");
|
|
}
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-004: CORS should be restrictive")]
|
|
public void Cors_ShouldBeRestrictive()
|
|
{
|
|
// Arrange
|
|
var corsConfig = GetCorsConfiguration();
|
|
|
|
// Assert
|
|
corsConfig.AllowedOrigins.Should().NotContain("*",
|
|
"CORS should not allow all origins");
|
|
corsConfig.AllowCredentials.Should().BeTrue();
|
|
corsConfig.AllowedMethods.Should().NotContain("*",
|
|
"CORS should specify explicit methods");
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-005: Default ports should not be used")]
|
|
public void DefaultPorts_ShouldBeConfigurable()
|
|
{
|
|
// Arrange
|
|
var portConfig = GetPortConfiguration();
|
|
|
|
// Assert
|
|
portConfig.HttpsPort.Should().NotBe(443, "Default HTTPS port should be configurable");
|
|
portConfig.HttpPort.Should().BeNull("HTTP should be disabled or redirected");
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-006: Unnecessary features should be disabled")]
|
|
public void UnnecessaryFeatures_ShouldBeDisabled()
|
|
{
|
|
// Arrange
|
|
var disabledFeatures = new[]
|
|
{
|
|
"Swagger", // in production
|
|
"GraphQLPlayground", // in production
|
|
"TRACE", // HTTP method
|
|
"OPTIONS" // unless needed for CORS
|
|
};
|
|
|
|
// Act
|
|
var enabledFeatures = GetEnabledFeatures("production");
|
|
|
|
// Assert
|
|
foreach (var feature in disabledFeatures)
|
|
{
|
|
enabledFeatures.Should().NotContain(feature,
|
|
$"Feature {feature} should be disabled in production");
|
|
}
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-007: Directory listing should be disabled")]
|
|
public void DirectoryListing_ShouldBeDisabled()
|
|
{
|
|
// Arrange
|
|
var staticFileConfig = GetStaticFileConfiguration();
|
|
|
|
// Assert
|
|
staticFileConfig.EnableDirectoryBrowsing.Should().BeFalse(
|
|
"Directory listing should be disabled");
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-008: Admin endpoints should require authentication")]
|
|
public void AdminEndpoints_ShouldRequireAuth()
|
|
{
|
|
// Arrange
|
|
var adminEndpoints = new[]
|
|
{
|
|
"/admin",
|
|
"/api/admin",
|
|
"/api/v1/admin",
|
|
"/manage",
|
|
"/actuator"
|
|
};
|
|
|
|
// Act & Assert
|
|
foreach (var endpoint in adminEndpoints)
|
|
{
|
|
var requiresAuth = EndpointRequiresAuthentication(endpoint);
|
|
requiresAuth.Should().BeTrue(
|
|
$"Admin endpoint {endpoint} should require authentication");
|
|
}
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-009: Cookie security flags should be set")]
|
|
public void CookieSecurityFlags_ShouldBeSet()
|
|
{
|
|
// Arrange
|
|
var cookieConfig = GetCookieConfiguration();
|
|
|
|
// Assert
|
|
cookieConfig.Secure.Should().BeTrue("Cookies should be secure");
|
|
cookieConfig.HttpOnly.Should().BeTrue("Cookies should be HttpOnly");
|
|
cookieConfig.SameSite.Should().Be("Strict", "SameSite should be Strict");
|
|
}
|
|
|
|
[Fact(DisplayName = "A05-010: Cloud metadata endpoints should be blocked")]
|
|
public void CloudMetadataEndpoints_ShouldBeBlocked()
|
|
{
|
|
// Arrange
|
|
var metadataEndpoints = new[]
|
|
{
|
|
"http://169.254.169.254/", // AWS, Azure, GCP
|
|
"http://metadata.google.internal/",
|
|
"http://100.100.100.200/" // Alibaba Cloud
|
|
};
|
|
|
|
// Act & Assert
|
|
foreach (var endpoint in metadataEndpoints)
|
|
{
|
|
var isBlocked = IsOutboundUrlBlocked(endpoint);
|
|
isBlocked.Should().BeTrue(
|
|
$"Cloud metadata endpoint {endpoint} should be blocked");
|
|
}
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
private static Dictionary<string, string> LoadConfiguration(string environment)
|
|
{
|
|
// Simulated production configuration
|
|
return new Dictionary<string, string>
|
|
{
|
|
["ASPNETCORE_ENVIRONMENT"] = "Production",
|
|
["DetailedErrors"] = "false",
|
|
["UseDeveloperExceptionPage"] = "false"
|
|
};
|
|
}
|
|
|
|
private static Dictionary<string, string> GetSecurityHeaders()
|
|
{
|
|
return new Dictionary<string, string>
|
|
{
|
|
["X-Content-Type-Options"] = "nosniff",
|
|
["X-Frame-Options"] = "DENY",
|
|
["X-XSS-Protection"] = "1; mode=block",
|
|
["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains",
|
|
["Content-Security-Policy"] = "default-src 'self'"
|
|
};
|
|
}
|
|
|
|
private static CorsConfig GetCorsConfiguration()
|
|
{
|
|
return new CorsConfig(
|
|
AllowedOrigins: new[] { "https://app.stella-ops.org" },
|
|
AllowCredentials: true,
|
|
AllowedMethods: new[] { "GET", "POST", "PUT", "DELETE" }
|
|
);
|
|
}
|
|
|
|
private static PortConfig GetPortConfiguration()
|
|
{
|
|
return new PortConfig(HttpsPort: 8443, HttpPort: null);
|
|
}
|
|
|
|
private static string[] GetEnabledFeatures(string environment)
|
|
{
|
|
if (environment == "production")
|
|
{
|
|
return new[] { "HealthChecks", "Metrics", "API" };
|
|
}
|
|
return new[] { "Swagger", "HealthChecks", "Metrics", "API", "GraphQLPlayground" };
|
|
}
|
|
|
|
private static StaticFileConfig GetStaticFileConfiguration()
|
|
{
|
|
return new StaticFileConfig(EnableDirectoryBrowsing: false);
|
|
}
|
|
|
|
private static bool EndpointRequiresAuthentication(string endpoint)
|
|
{
|
|
// All admin endpoints require authentication
|
|
return endpoint.Contains("admin", StringComparison.OrdinalIgnoreCase) ||
|
|
endpoint.Contains("manage", StringComparison.OrdinalIgnoreCase) ||
|
|
endpoint.Contains("actuator", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static CookieConfig GetCookieConfiguration()
|
|
{
|
|
return new CookieConfig(Secure: true, HttpOnly: true, SameSite: "Strict");
|
|
}
|
|
|
|
private static bool IsOutboundUrlBlocked(string url)
|
|
{
|
|
var blockedPrefixes = new[]
|
|
{
|
|
"http://169.254.",
|
|
"http://metadata.",
|
|
"http://100.100.100.200"
|
|
};
|
|
|
|
return blockedPrefixes.Any(p => url.StartsWith(p, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private record CorsConfig(string[] AllowedOrigins, bool AllowCredentials, string[] AllowedMethods);
|
|
private record PortConfig(int HttpsPort, int? HttpPort);
|
|
private record StaticFileConfig(bool EnableDirectoryBrowsing);
|
|
private record CookieConfig(bool Secure, bool HttpOnly, string SameSite);
|
|
}
|