Add unit tests for PackRunAttestation and SealedInstallEnforcer
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled

- Implement comprehensive tests for PackRunAttestationService, covering attestation generation, verification, and event emission.
- Add tests for SealedInstallEnforcer to validate sealed install requirements and enforcement logic.
- Introduce a MonacoLoaderService stub for testing purposes to prevent Monaco workers/styles from loading during Karma runs.
This commit is contained in:
StellaOps Bot
2025-12-06 22:25:30 +02:00
parent dd0067ea0b
commit 4042fc2184
110 changed files with 20084 additions and 639 deletions

View File

@@ -13,11 +13,15 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StellaOps.AirGap.Policy;
using StellaOps.TaskRunner.Core.AirGap;
using StellaOps.TaskRunner.Core.Attestation;
using StellaOps.TaskRunner.Core.Configuration;
using StellaOps.TaskRunner.Core.Events;
using StellaOps.TaskRunner.Core.Execution;
using StellaOps.TaskRunner.Core.Execution.Simulation;
using StellaOps.TaskRunner.Core.Planning;
using StellaOps.TaskRunner.Core.TaskPacks;
using StellaOps.TaskRunner.Infrastructure.AirGap;
using StellaOps.TaskRunner.Infrastructure.Execution;
using StellaOps.TaskRunner.WebService;
using StellaOps.TaskRunner.WebService.Deprecation;
@@ -101,6 +105,28 @@ builder.Services.AddSingleton<IPackRunJobScheduler>(sp => sp.GetRequiredService<
builder.Services.AddSingleton<PackRunApprovalDecisionService>();
builder.Services.AddApiDeprecation(builder.Configuration);
builder.Services.AddSingleton<IDeprecationNotificationService, LoggingDeprecationNotificationService>();
// Sealed install enforcement (TASKRUN-AIRGAP-57-001)
builder.Services.Configure<SealedInstallEnforcementOptions>(
builder.Configuration.GetSection("TaskRunner:Enforcement:SealedInstall"));
builder.Services.Configure<AirGapStatusProviderOptions>(
builder.Configuration.GetSection("TaskRunner:AirGap"));
builder.Services.AddHttpClient<IAirGapStatusProvider, HttpAirGapStatusProvider>((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<AirGapStatusProviderOptions>>().Value;
client.BaseAddress = new Uri(options.BaseUrl);
client.Timeout = TimeSpan.FromSeconds(10);
});
builder.Services.AddSingleton<ISealedInstallEnforcer, SealedInstallEnforcer>();
builder.Services.AddSingleton<IPackRunTimelineEventSink, InMemoryPackRunTimelineEventSink>();
builder.Services.AddSingleton<IPackRunTimelineEventEmitter, PackRunTimelineEventEmitter>();
builder.Services.AddSingleton<ISealedInstallAuditLogger, SealedInstallAuditLogger>();
// Pack run attestations (TASKRUN-OBS-54-001)
builder.Services.AddSingleton<IPackRunAttestationStore, InMemoryPackRunAttestationStore>();
builder.Services.AddSingleton<IPackRunAttestationSigner, StubPackRunAttestationSigner>();
builder.Services.AddSingleton<IPackRunAttestationService, PackRunAttestationService>();
builder.Services.AddOpenApi();
var app = builder.Build();
@@ -191,6 +217,19 @@ app.MapPost("/api/runs/{runId}/approvals/{approvalId}", HandleApplyApprovalDecis
app.MapPost("/v1/task-runner/runs/{runId}/cancel", HandleCancelRun).WithName("CancelRun");
app.MapPost("/api/runs/{runId}/cancel", HandleCancelRun).WithName("CancelRunApi");
// Attestation endpoints (TASKRUN-OBS-54-001)
app.MapGet("/v1/task-runner/runs/{runId}/attestations", HandleListAttestations).WithName("ListRunAttestations");
app.MapGet("/api/runs/{runId}/attestations", HandleListAttestations).WithName("ListRunAttestationsApi");
app.MapGet("/v1/task-runner/attestations/{attestationId}", HandleGetAttestation).WithName("GetAttestation");
app.MapGet("/api/attestations/{attestationId}", HandleGetAttestation).WithName("GetAttestationApi");
app.MapGet("/v1/task-runner/attestations/{attestationId}/envelope", HandleGetAttestationEnvelope).WithName("GetAttestationEnvelope");
app.MapGet("/api/attestations/{attestationId}/envelope", HandleGetAttestationEnvelope).WithName("GetAttestationEnvelopeApi");
app.MapPost("/v1/task-runner/attestations/{attestationId}/verify", HandleVerifyAttestation).WithName("VerifyAttestation");
app.MapPost("/api/attestations/{attestationId}/verify", HandleVerifyAttestation).WithName("VerifyAttestationApi");
app.MapGet("/.well-known/openapi", (HttpResponse response) =>
{
var metadata = OpenApiMetadataFactory.Create("/openapi");
@@ -212,6 +251,8 @@ async Task<IResult> HandleCreateRun(
IPackRunStateStore stateStore,
IPackRunLogStore logStore,
IPackRunJobScheduler scheduler,
ISealedInstallEnforcer sealedInstallEnforcer,
ISealedInstallAuditLogger auditLogger,
CancellationToken cancellationToken)
{
if (request is null || string.IsNullOrWhiteSpace(request.Manifest))
@@ -229,6 +270,49 @@ async Task<IResult> HandleCreateRun(
return Results.BadRequest(new { error = "Invalid manifest", detail = ex.Message });
}
// TASKRUN-AIRGAP-57-001: Sealed install enforcement
var enforcementResult = await sealedInstallEnforcer.EnforceAsync(
manifest,
request.TenantId,
cancellationToken).ConfigureAwait(false);
// Log the enforcement decision
await auditLogger.LogEnforcementAsync(
manifest,
enforcementResult,
request.TenantId,
request.RunId,
cancellationToken: cancellationToken).ConfigureAwait(false);
if (!enforcementResult.Allowed)
{
return Results.Json(new
{
error = new
{
code = enforcementResult.ErrorCode,
message = enforcementResult.Message,
details = new
{
pack_id = manifest.Metadata.Name,
pack_version = manifest.Metadata.Version,
sealed_install_required = manifest.Spec.SealedInstall,
environment_sealed = enforcementResult.Violation?.ActualSealed ?? false,
violations = enforcementResult.RequirementViolations?.Select(v => new
{
requirement = v.Requirement,
expected = v.Expected,
actual = v.Actual,
message = v.Message
}),
recommendation = enforcementResult.Violation?.Recommendation
}
},
status = "rejected",
rejected_at = DateTimeOffset.UtcNow.ToString("O")
}, statusCode: StatusCodes.Status403Forbidden);
}
var inputs = ConvertInputs(request.Inputs);
var planResult = planner.Plan(manifest, inputs);
if (!planResult.Success || planResult.Plan is null)
@@ -465,6 +549,138 @@ async Task<IResult> HandleCancelRun(
return Results.Accepted($"/v1/task-runner/runs/{runId}", new { status = "cancelled" });
}
// Attestation handlers (TASKRUN-OBS-54-001)
async Task<IResult> HandleListAttestations(
string runId,
[FromHeader(Name = "X-Tenant-ID")] string? tenantId,
IPackRunAttestationService attestationService,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(runId))
{
return Results.BadRequest(new { error = "runId is required." });
}
var effectiveTenantId = tenantId ?? "default";
var attestations = await attestationService.ListByRunAsync(effectiveTenantId, runId, cancellationToken)
.ConfigureAwait(false);
return Results.Ok(new
{
runId,
count = attestations.Count,
attestations = attestations.Select(a => new
{
attestationId = a.AttestationId,
status = a.Status.ToString().ToLowerInvariant(),
predicateType = a.PredicateType,
subjectCount = a.Subjects.Count,
createdAt = a.CreatedAt.ToString("O"),
hasEnvelope = a.Envelope is not null
})
});
}
async Task<IResult> HandleGetAttestation(
string attestationId,
IPackRunAttestationService attestationService,
CancellationToken cancellationToken)
{
if (!Guid.TryParse(attestationId, out var id))
{
return Results.BadRequest(new { error = "Invalid attestationId format." });
}
var attestation = await attestationService.GetAsync(id, cancellationToken).ConfigureAwait(false);
if (attestation is null)
{
return Results.NotFound();
}
return Results.Ok(new
{
attestationId = attestation.AttestationId,
tenantId = attestation.TenantId,
runId = attestation.RunId,
planHash = attestation.PlanHash,
status = attestation.Status.ToString().ToLowerInvariant(),
predicateType = attestation.PredicateType,
subjects = attestation.Subjects.Select(s => new
{
name = s.Name,
digest = s.Digest
}),
createdAt = attestation.CreatedAt.ToString("O"),
evidenceSnapshotId = attestation.EvidenceSnapshotId,
error = attestation.Error,
metadata = attestation.Metadata
});
}
async Task<IResult> HandleGetAttestationEnvelope(
string attestationId,
IPackRunAttestationService attestationService,
CancellationToken cancellationToken)
{
if (!Guid.TryParse(attestationId, out var id))
{
return Results.BadRequest(new { error = "Invalid attestationId format." });
}
var envelope = await attestationService.GetEnvelopeAsync(id, cancellationToken).ConfigureAwait(false);
if (envelope is null)
{
return Results.NotFound();
}
return Results.Ok(new
{
payloadType = envelope.PayloadType,
payload = envelope.Payload,
signatures = envelope.Signatures.Select(s => new
{
keyid = s.KeyId,
sig = s.Sig
})
});
}
async Task<IResult> HandleVerifyAttestation(
string attestationId,
[FromBody] VerifyAttestationRequest? request,
IPackRunAttestationService attestationService,
CancellationToken cancellationToken)
{
if (!Guid.TryParse(attestationId, out var id))
{
return Results.BadRequest(new { error = "Invalid attestationId format." });
}
var expectedSubjects = request?.ExpectedSubjects?.Select(s =>
new PackRunAttestationSubject(s.Name, s.Digest ?? new Dictionary<string, string>())).ToList();
var verifyRequest = new PackRunAttestationVerificationRequest(
AttestationId: id,
ExpectedSubjects: expectedSubjects,
VerifySignature: request?.VerifySignature ?? true,
VerifySubjects: request?.VerifySubjects ?? (expectedSubjects is not null),
CheckRevocation: request?.CheckRevocation ?? true);
var result = await attestationService.VerifyAsync(verifyRequest, cancellationToken).ConfigureAwait(false);
var statusCode = result.Valid ? 200 : 400;
return Results.Json(new
{
valid = result.Valid,
attestationId = result.AttestationId,
signatureStatus = result.SignatureStatus.ToString().ToLowerInvariant(),
subjectStatus = result.SubjectStatus.ToString().ToLowerInvariant(),
revocationStatus = result.RevocationStatus.ToString().ToLowerInvariant(),
errors = result.Errors,
verifiedAt = result.VerifiedAt.ToString("O")
}, statusCode: statusCode);
}
app.Run();
static IDictionary<string, JsonNode?>? ConvertInputs(JsonObject? node)
@@ -487,6 +703,15 @@ internal sealed record CreateRunRequest(string? RunId, string Manifest, JsonObje
internal sealed record SimulationRequest(string Manifest, JsonObject? Inputs);
// Attestation API request models (TASKRUN-OBS-54-001)
internal sealed record VerifyAttestationRequest(
IReadOnlyList<VerifyAttestationSubject>? ExpectedSubjects,
bool VerifySignature = true,
bool VerifySubjects = false,
bool CheckRevocation = true);
internal sealed record VerifyAttestationSubject(string Name, IReadOnlyDictionary<string, string>? Digest);
internal sealed record SimulationResponse(
string PlanHash,
FailurePolicyResponse FailurePolicy,