audit notes work completed, test fixes work (95% done), new sprints, new data sources setup and configuration

This commit is contained in:
master
2026-01-14 10:48:00 +02:00
parent d7be6ba34b
commit 95d5898650
379 changed files with 40695 additions and 19041 deletions

View File

@@ -367,5 +367,5 @@ app.Run();
// Make Program class internal to prevent type conflicts when referencing this assembly
namespace StellaOps.Policy.Engine
{
internal partial class Program { }
public partial class Program { }
}

View File

@@ -0,0 +1,277 @@
using StellaOps.Policy.AuthSignals;
using Xunit;
namespace StellaOps.Policy.AuthSignals.Tests;
/// <summary>
/// Tests for PolicyAuthSignal and related models.
/// </summary>
public sealed class PolicyAuthSignalTests
{
[Fact]
public void PolicyAuthSignal_RequiredProperties_MustBeSet()
{
var signal = new PolicyAuthSignal
{
Id = "sig-001",
Tenant = "tenant-abc",
Subject = "artifact:sha256:abc123",
SignalType = "reachability",
Source = "scanner-v1",
Created = DateTime.UtcNow
};
Assert.Equal("sig-001", signal.Id);
Assert.Equal("tenant-abc", signal.Tenant);
Assert.Equal("artifact:sha256:abc123", signal.Subject);
Assert.Equal("reachability", signal.SignalType);
Assert.Equal("scanner-v1", signal.Source);
}
[Theory]
[InlineData("reachability")]
[InlineData("attestation")]
[InlineData("risk")]
[InlineData("vex")]
public void PolicyAuthSignal_SignalType_SupportedValues(string signalType)
{
var signal = new PolicyAuthSignal
{
Id = "sig-type-test",
Tenant = "t1",
Subject = "s1",
SignalType = signalType,
Source = "test",
Created = DateTime.UtcNow
};
Assert.Equal(signalType, signal.SignalType);
}
[Fact]
public void PolicyAuthSignal_WithConfidence_ContainsValue()
{
var signal = new PolicyAuthSignal
{
Id = "sig-conf",
Tenant = "t1",
Subject = "s1",
SignalType = "risk",
Source = "risk-engine",
Confidence = 0.95,
Created = DateTime.UtcNow
};
Assert.Equal(0.95, signal.Confidence);
}
[Fact]
public void PolicyAuthSignal_WithEvidence_ContainsRefs()
{
var evidence = new[]
{
new EvidenceRef
{
Kind = "attestation",
Uri = "oci://registry.io/attestation@sha256:xyz",
Digest = "sha256:xyz123"
},
new EvidenceRef
{
Kind = "linkset",
Uri = "https://transparency.example.com/entry/123",
Digest = "sha256:link456",
Scope = "org.example.project"
}
};
var signal = new PolicyAuthSignal
{
Id = "sig-ev",
Tenant = "t1",
Subject = "s1",
SignalType = "attestation",
Source = "attestor",
Evidence = evidence,
Created = DateTime.UtcNow
};
Assert.Equal(2, signal.Evidence.Count);
Assert.Equal("attestation", signal.Evidence[0].Kind);
Assert.Equal("linkset", signal.Evidence[1].Kind);
Assert.Equal("org.example.project", signal.Evidence[1].Scope);
}
[Fact]
public void PolicyAuthSignal_WithProvenance_ContainsDetails()
{
var provenance = new Provenance
{
Pipeline = "ci/build-and-scan",
Inputs = new[] { "dockerfile:sha256:abc", "sources:sha256:def" },
Signer = "build-bot@ci.example.com",
Transparency = new Transparency
{
RekorUuid = "rekor-uuid-123456"
}
};
var signal = new PolicyAuthSignal
{
Id = "sig-prov",
Tenant = "t1",
Subject = "s1",
SignalType = "attestation",
Source = "signer",
Provenance = provenance,
Created = DateTime.UtcNow
};
Assert.NotNull(signal.Provenance);
Assert.Equal("ci/build-and-scan", signal.Provenance.Pipeline);
Assert.Equal(2, signal.Provenance.Inputs!.Count);
Assert.Equal("build-bot@ci.example.com", signal.Provenance.Signer);
Assert.Equal("rekor-uuid-123456", signal.Provenance.Transparency!.RekorUuid);
}
[Fact]
public void PolicyAuthSignal_DefaultEvidence_IsEmpty()
{
var signal = new PolicyAuthSignal
{
Id = "sig-default",
Tenant = "t1",
Subject = "s1",
SignalType = "vex",
Source = "scanner",
Created = DateTime.UtcNow
};
Assert.Empty(signal.Evidence);
}
[Fact]
public void Transparency_WithSkipReason_NoRekorUuid()
{
var transparency = new Transparency
{
SkipReason = "airgapped_environment"
};
Assert.Null(transparency.RekorUuid);
Assert.Equal("airgapped_environment", transparency.SkipReason);
}
[Fact]
public void PolicyAuthSignal_RecordEquality_WorksCorrectly()
{
var created = DateTime.UtcNow;
var signal1 = new PolicyAuthSignal
{
Id = "sig-eq",
Tenant = "t1",
Subject = "s1",
SignalType = "risk",
Source = "engine",
Confidence = 0.8,
Created = created
};
var signal2 = new PolicyAuthSignal
{
Id = "sig-eq",
Tenant = "t1",
Subject = "s1",
SignalType = "risk",
Source = "engine",
Confidence = 0.8,
Created = created
};
Assert.Equal(signal1, signal2);
Assert.Equal(signal1.GetHashCode(), signal2.GetHashCode());
}
}
/// <summary>
/// Tests for EvidenceRef record.
/// </summary>
public sealed class EvidenceRefTests
{
[Theory]
[InlineData("linkset")]
[InlineData("runtime")]
[InlineData("attestation")]
[InlineData("bundle")]
public void EvidenceRef_Kind_SupportedValues(string kind)
{
var evidenceRef = new EvidenceRef
{
Kind = kind,
Uri = "https://example.com/evidence",
Digest = "sha256:abc123"
};
Assert.Equal(kind, evidenceRef.Kind);
}
[Fact]
public void EvidenceRef_DefaultValues_AreEmpty()
{
var evidenceRef = new EvidenceRef();
Assert.Equal(string.Empty, evidenceRef.Kind);
Assert.Equal(string.Empty, evidenceRef.Uri);
Assert.Equal(string.Empty, evidenceRef.Digest);
Assert.Null(evidenceRef.Scope);
}
[Fact]
public void EvidenceRef_WithScope_ContainsValue()
{
var evidenceRef = new EvidenceRef
{
Kind = "runtime",
Uri = "https://example.com/runtime-check",
Digest = "sha256:runtime123",
Scope = "org.example.service.api"
};
Assert.Equal("org.example.service.api", evidenceRef.Scope);
}
}
/// <summary>
/// Tests for Provenance record.
/// </summary>
public sealed class ProvenanceTests
{
[Fact]
public void Provenance_AllPropertiesOptional()
{
var provenance = new Provenance();
Assert.Null(provenance.Pipeline);
Assert.Null(provenance.Inputs);
Assert.Null(provenance.Signer);
Assert.Null(provenance.Transparency);
}
[Fact]
public void Provenance_WithAllProperties_ContainsValues()
{
var provenance = new Provenance
{
Pipeline = "github-actions/build",
Inputs = new[] { "src:sha256:123", "config:sha256:456" },
Signer = "sigstore-bot",
Transparency = new Transparency { RekorUuid = "uuid-789" }
};
Assert.Equal("github-actions/build", provenance.Pipeline);
Assert.Equal(2, provenance.Inputs!.Count);
Assert.Equal("sigstore-bot", provenance.Signer);
Assert.NotNull(provenance.Transparency);
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<UseXunitV3>true</UseXunitV3>
</PropertyGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Policy.AuthSignals\StellaOps.Policy.AuthSignals.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"diagnosticMessages": true,
"parallelizeAssembly": true,
"parallelizeTestCollections": true,
"maxParallelThreads": -1
}

View File

@@ -127,7 +127,7 @@ internal sealed class TestAuthHandler : AuthenticationHandler<AuthenticationSche
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
TimeProvider clock)
: base(options, logger, encoder, clock)
{
}
@@ -154,3 +154,4 @@ internal sealed class TestAuthHandler : AuthenticationHandler<AuthenticationSche
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}

View File

@@ -0,0 +1,308 @@
using StellaOps.Policy.Predicates.FixChain;
using System.Collections.Immutable;
using Xunit;
namespace StellaOps.Policy.Predicates.Tests;
/// <summary>
/// Tests for FixChainGateAction enum.
/// </summary>
public sealed class FixChainGateActionTests
{
[Theory]
[InlineData(FixChainGateAction.Block)]
[InlineData(FixChainGateAction.Warn)]
public void FixChainGateAction_AllValues_AreDefined(FixChainGateAction action)
{
Assert.True(Enum.IsDefined(action));
}
[Fact]
public void FixChainGateAction_AllValues_AreCounted()
{
var values = Enum.GetValues<FixChainGateAction>();
Assert.Equal(2, values.Length);
}
}
/// <summary>
/// Tests for FixChainGateOutcome enum.
/// </summary>
public sealed class FixChainGateOutcomeTests
{
[Theory]
[InlineData(FixChainGateOutcome.FixVerified)]
[InlineData(FixChainGateOutcome.SeverityExempt)]
[InlineData(FixChainGateOutcome.GracePeriod)]
[InlineData(FixChainGateOutcome.AttestationRequired)]
[InlineData(FixChainGateOutcome.InsufficientConfidence)]
[InlineData(FixChainGateOutcome.InconclusiveNotAllowed)]
[InlineData(FixChainGateOutcome.StillVulnerable)]
[InlineData(FixChainGateOutcome.GoldenSetNotApproved)]
[InlineData(FixChainGateOutcome.PartialFix)]
public void FixChainGateOutcome_AllValues_AreDefined(FixChainGateOutcome outcome)
{
Assert.True(Enum.IsDefined(outcome));
}
[Fact]
public void FixChainGateOutcome_AllValues_AreCounted()
{
var values = Enum.GetValues<FixChainGateOutcome>();
Assert.Equal(9, values.Length);
}
}
/// <summary>
/// Tests for FixChainGateContext record.
/// </summary>
public sealed class FixChainGateContextTests
{
[Fact]
public void FixChainGateContext_RequiredProperties_MustBeSet()
{
var context = new FixChainGateContext
{
CveId = "CVE-2024-0001",
ComponentPurl = "pkg:deb/debian/nginx@1.18.0-6.1",
Severity = "critical",
CvssScore = 9.8m
};
Assert.Equal("CVE-2024-0001", context.CveId);
Assert.Equal("pkg:deb/debian/nginx@1.18.0-6.1", context.ComponentPurl);
Assert.Equal("critical", context.Severity);
Assert.Equal(9.8m, context.CvssScore);
}
[Fact]
public void FixChainGateContext_Environment_DefaultsToProduction()
{
var context = new FixChainGateContext
{
CveId = "CVE-2024-0001",
ComponentPurl = "pkg:npm/%40example/lib@1.0.0",
Severity = "high",
CvssScore = 7.5m
};
Assert.Equal("production", context.Environment);
}
[Fact]
public void FixChainGateContext_OptionalProperties_CanBeNull()
{
var context = new FixChainGateContext
{
CveId = "CVE-2024-0001",
ComponentPurl = "pkg:pypi/requests@2.28.0",
Severity = "medium",
CvssScore = 5.3m
};
Assert.Null(context.BinarySha256);
Assert.Null(context.CvePublishedAt);
Assert.Null(context.Metadata);
}
[Fact]
public void FixChainGateContext_WithAllOptionalProperties_ContainsValues()
{
var publishedAt = DateTimeOffset.UtcNow.AddDays(-10);
var metadata = new Dictionary<string, string>
{
["ticket"] = "SEC-123",
["reviewer"] = "security-team"
};
var context = new FixChainGateContext
{
CveId = "CVE-2024-0001",
ComponentPurl = "pkg:golang/github.com/example/lib@v1.2.3",
Severity = "high",
CvssScore = 8.1m,
BinarySha256 = "sha256:abc123def456",
CvePublishedAt = publishedAt,
Environment = "staging",
Metadata = metadata
};
Assert.Equal("sha256:abc123def456", context.BinarySha256);
Assert.Equal(publishedAt, context.CvePublishedAt);
Assert.Equal("staging", context.Environment);
Assert.NotNull(context.Metadata);
Assert.Equal("SEC-123", context.Metadata["ticket"]);
}
}
/// <summary>
/// Tests for FixChainGateParameters record.
/// </summary>
public sealed class FixChainGateParametersTests
{
[Fact]
public void FixChainGateParameters_HasSensibleDefaults()
{
var parameters = new FixChainGateParameters();
Assert.Equal(2, parameters.Severities.Length);
Assert.Contains("critical", parameters.Severities);
Assert.Contains("high", parameters.Severities);
Assert.Equal(0.85m, parameters.MinConfidence);
Assert.Equal(7, parameters.GracePeriodDays);
Assert.True(parameters.RequireApprovedGoldenSet);
Assert.Equal(FixChainGateAction.Block, parameters.FailureAction);
}
[Fact]
public void FixChainGateParameters_AllowInconclusive_DefaultsFalse()
{
var parameters = new FixChainGateParameters();
Assert.False(parameters.AllowInconclusive);
}
[Fact]
public void FixChainGateParameters_CustomConfiguration_OverridesDefaults()
{
var parameters = new FixChainGateParameters
{
Severities = ["critical"],
MinConfidence = 0.95m,
GracePeriodDays = 14,
AllowInconclusive = true,
RequireApprovedGoldenSet = false,
FailureAction = FixChainGateAction.Warn
};
Assert.Single(parameters.Severities);
Assert.Equal(0.95m, parameters.MinConfidence);
Assert.Equal(14, parameters.GracePeriodDays);
Assert.True(parameters.AllowInconclusive);
Assert.False(parameters.RequireApprovedGoldenSet);
Assert.Equal(FixChainGateAction.Warn, parameters.FailureAction);
}
}
/// <summary>
/// Tests for FixChainGateResult record.
/// </summary>
public sealed class FixChainGateResultTests
{
[Fact]
public void FixChainGateResult_PassingResult_HasCorrectProperties()
{
var result = new FixChainGateResult
{
Passed = true,
Outcome = FixChainGateOutcome.FixVerified,
Reason = "Fix verified with 0.92 confidence",
Action = FixChainGateAction.Block,
EvaluatedAt = DateTimeOffset.UtcNow
};
Assert.True(result.Passed);
Assert.Equal(FixChainGateOutcome.FixVerified, result.Outcome);
}
[Fact]
public void FixChainGateResult_FailingResult_HasRecommendations()
{
var result = new FixChainGateResult
{
Passed = false,
Outcome = FixChainGateOutcome.AttestationRequired,
Reason = "No fix attestation found for critical CVE",
Action = FixChainGateAction.Block,
EvaluatedAt = DateTimeOffset.UtcNow,
Recommendations = ["Run stella-cli verify-fix CVE-2024-0001", "Update component to patched version"]
};
Assert.False(result.Passed);
Assert.Equal(2, result.Recommendations.Length);
}
[Fact]
public void FixChainGateResult_WithAttestation_ContainsAttestationInfo()
{
var attestation = new FixChainAttestationInfo
{
ContentDigest = "sha256:abc123",
VerdictStatus = "fixed",
Confidence = 0.95m,
GoldenSetId = "golden-001",
VerifiedAt = DateTimeOffset.UtcNow.AddHours(-1)
};
var result = new FixChainGateResult
{
Passed = true,
Outcome = FixChainGateOutcome.FixVerified,
Reason = "Fix verified",
Action = FixChainGateAction.Block,
EvaluatedAt = DateTimeOffset.UtcNow,
Attestation = attestation
};
Assert.NotNull(result.Attestation);
Assert.Equal("sha256:abc123", result.Attestation.ContentDigest);
Assert.Equal(0.95m, result.Attestation.Confidence);
}
}
/// <summary>
/// Tests for FixChainAttestationInfo record.
/// </summary>
public sealed class FixChainAttestationInfoTests
{
[Fact]
public void FixChainAttestationInfo_RequiredProperties_MustBeSet()
{
var verifiedAt = DateTimeOffset.UtcNow;
var info = new FixChainAttestationInfo
{
ContentDigest = "sha256:xyz789",
VerdictStatus = "fixed",
Confidence = 0.88m,
VerifiedAt = verifiedAt
};
Assert.Equal("sha256:xyz789", info.ContentDigest);
Assert.Equal("fixed", info.VerdictStatus);
Assert.Equal(0.88m, info.Confidence);
Assert.Equal(verifiedAt, info.VerifiedAt);
}
[Fact]
public void FixChainAttestationInfo_WithRationale_ContainsItems()
{
var info = new FixChainAttestationInfo
{
ContentDigest = "sha256:abc",
VerdictStatus = "fixed",
Confidence = 0.90m,
VerifiedAt = DateTimeOffset.UtcNow,
Rationale = ["Patch applied in version 1.2.3", "Binary hash matches golden set"]
};
Assert.Equal(2, info.Rationale.Length);
}
}
/// <summary>
/// Tests for FixChainGateOptions record.
/// </summary>
public sealed class FixChainGateOptionsTests
{
[Fact]
public void FixChainGateOptions_HasSensibleDefaults()
{
var options = new FixChainGateOptions();
Assert.True(options.Enabled);
Assert.Equal(0.85m, options.DefaultMinConfidence);
Assert.Equal(7, options.DefaultGracePeriodDays);
Assert.True(options.NotifyOnBlock);
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<UseXunitV3>true</UseXunitV3>
</PropertyGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Policy.Predicates\StellaOps.Policy.Predicates.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"diagnosticMessages": true,
"parallelizeAssembly": true,
"parallelizeTestCollections": true,
"maxParallelThreads": -1
}