save progress
This commit is contained in:
@@ -23,6 +23,7 @@ using StellaOps.Attestor.Core.Storage;
|
||||
using StellaOps.Attestor.Core.Submission;
|
||||
using StellaOps.Attestor.Core.Verification;
|
||||
using StellaOps.Attestor.Infrastructure;
|
||||
using StellaOps.Attestor.Spdx3;
|
||||
using StellaOps.Attestor.WebService.Options;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.Cryptography.DependencyInjection;
|
||||
@@ -129,6 +130,9 @@ internal static class AttestorWebServiceComposition
|
||||
builder.Services.AddScoped<Services.IProofChainQueryService, Services.ProofChainQueryService>();
|
||||
builder.Services.AddScoped<Services.IProofVerificationService, Services.ProofVerificationService>();
|
||||
|
||||
// SPDX 3.0.1 Build profile support (BP-007)
|
||||
builder.Services.AddSingleton<IBuildAttestationMapper, BuildAttestationMapper>();
|
||||
|
||||
builder.Services.AddSingleton<StellaOps.Attestor.StandardPredicates.IStandardPredicateRegistry>(sp =>
|
||||
{
|
||||
var registry = new StellaOps.Attestor.StandardPredicates.StandardPredicateRegistry();
|
||||
|
||||
@@ -11,6 +11,7 @@ using StellaOps.Attestor.Core.Signing;
|
||||
using StellaOps.Attestor.Core.Storage;
|
||||
using StellaOps.Attestor.Core.Submission;
|
||||
using StellaOps.Attestor.Core.Verification;
|
||||
using StellaOps.Attestor.Spdx3;
|
||||
using StellaOps.Attestor.WebService.Contracts;
|
||||
|
||||
namespace StellaOps.Attestor.WebService;
|
||||
@@ -394,6 +395,125 @@ internal static class AttestorWebServiceEndpoints
|
||||
|
||||
return Results.Ok(BulkVerificationContracts.MapJob(job));
|
||||
}).RequireAuthorization("attestor:write");
|
||||
|
||||
// SPDX 3.0.1 Build Profile export endpoint (BP-007)
|
||||
app.MapPost("/api/v1/attestations:export-build", (
|
||||
Spdx3BuildExportRequestDto? requestDto,
|
||||
HttpContext httpContext,
|
||||
IBuildAttestationMapper mapper) =>
|
||||
{
|
||||
if (requestDto is null)
|
||||
{
|
||||
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "Request body is required.");
|
||||
}
|
||||
|
||||
if (!IsJsonContentType(httpContext.Request.ContentType))
|
||||
{
|
||||
return UnsupportedMediaTypeResult();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(requestDto.BuildType))
|
||||
{
|
||||
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "buildType is required.");
|
||||
}
|
||||
|
||||
// Build the attestation payload from the request
|
||||
var configSource = (!string.IsNullOrWhiteSpace(requestDto.ConfigSourceUri) ||
|
||||
requestDto.ConfigSourceDigest?.Count > 0 ||
|
||||
!string.IsNullOrWhiteSpace(requestDto.ConfigEntryPoint))
|
||||
? new ConfigSource
|
||||
{
|
||||
Uri = requestDto.ConfigSourceUri,
|
||||
Digest = requestDto.ConfigSourceDigest ?? new Dictionary<string, string>(),
|
||||
EntryPoint = requestDto.ConfigEntryPoint
|
||||
}
|
||||
: null;
|
||||
|
||||
var materials = requestDto.Materials?.Select(m => new BuildMaterial
|
||||
{
|
||||
Uri = m.Uri,
|
||||
Digest = m.Digest ?? new Dictionary<string, string>()
|
||||
}).ToList() ?? new List<BuildMaterial>();
|
||||
|
||||
var attestationPayload = new BuildAttestationPayload
|
||||
{
|
||||
BuildType = requestDto.BuildType,
|
||||
Builder = !string.IsNullOrWhiteSpace(requestDto.BuilderId)
|
||||
? new BuilderInfo
|
||||
{
|
||||
Id = requestDto.BuilderId,
|
||||
Version = requestDto.BuilderVersion
|
||||
}
|
||||
: null,
|
||||
Invocation = new BuildInvocation
|
||||
{
|
||||
ConfigSource = configSource,
|
||||
Environment = requestDto.Environment ?? new Dictionary<string, string>(),
|
||||
Parameters = requestDto.Parameters ?? new Dictionary<string, string>()
|
||||
},
|
||||
Metadata = new BuildMetadata
|
||||
{
|
||||
BuildInvocationId = requestDto.BuildId,
|
||||
BuildStartedOn = requestDto.BuildStartTime,
|
||||
BuildFinishedOn = requestDto.BuildEndTime
|
||||
},
|
||||
Materials = materials
|
||||
};
|
||||
|
||||
// Check if the payload can be mapped
|
||||
if (!mapper.CanMapToSpdx3(attestationPayload))
|
||||
{
|
||||
return Results.Problem(
|
||||
statusCode: StatusCodes.Status400BadRequest,
|
||||
title: "Cannot map attestation to SPDX 3.0.1",
|
||||
detail: "The provided attestation payload is missing required fields for SPDX 3.0.1 Build profile.");
|
||||
}
|
||||
|
||||
// Map to SPDX 3.0.1 Build element
|
||||
var spdx3Build = mapper.MapToSpdx3(attestationPayload, requestDto.SpdxIdPrefix);
|
||||
|
||||
// Build response based on requested format
|
||||
var response = new Spdx3BuildExportResponseDto
|
||||
{
|
||||
Format = requestDto.Format,
|
||||
BuildSpdxId = spdx3Build.SpdxId,
|
||||
Spdx3Document = requestDto.Format is BuildAttestationFormat.Spdx3 or BuildAttestationFormat.Both
|
||||
? new
|
||||
{
|
||||
spdxVersion = "SPDX-3.0.1",
|
||||
conformsTo = new[] { "https://spdx.org/rdf/v3/Build" },
|
||||
spdxId = $"{requestDto.SpdxIdPrefix}/document",
|
||||
elements = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
type = spdx3Build.Type,
|
||||
spdxId = spdx3Build.SpdxId,
|
||||
name = spdx3Build.Name,
|
||||
build_buildType = spdx3Build.BuildType,
|
||||
build_buildId = spdx3Build.BuildId,
|
||||
build_buildStartTime = spdx3Build.BuildStartTime?.ToString("O", CultureInfo.InvariantCulture),
|
||||
build_buildEndTime = spdx3Build.BuildEndTime?.ToString("O", CultureInfo.InvariantCulture),
|
||||
build_configSourceUri = spdx3Build.ConfigSourceUri.IsEmpty ? null : spdx3Build.ConfigSourceUri.ToArray(),
|
||||
build_configSourceDigest = spdx3Build.ConfigSourceDigest.IsEmpty ? null : spdx3Build.ConfigSourceDigest.Select(h => new { algorithm = h.Algorithm, hashValue = h.HashValue }).ToArray(),
|
||||
build_configSourceEntrypoint = spdx3Build.ConfigSourceEntrypoint.IsEmpty ? null : spdx3Build.ConfigSourceEntrypoint.ToArray(),
|
||||
build_environment = spdx3Build.Environment.Count > 0 ? spdx3Build.Environment : null,
|
||||
build_parameter = spdx3Build.Parameter.Count > 0 ? spdx3Build.Parameter : null
|
||||
}
|
||||
}
|
||||
}
|
||||
: null,
|
||||
// DSSE envelope generation would require signing service integration
|
||||
// For now, return null for DSSE when not specifically requested or when signing is disabled
|
||||
DsseEnvelope = null,
|
||||
Signing = null
|
||||
};
|
||||
|
||||
return Results.Ok(response);
|
||||
})
|
||||
.RequireAuthorization("attestor:write")
|
||||
.RequireRateLimiting("attestor-submissions")
|
||||
.Produces<Spdx3BuildExportResponseDto>(StatusCodes.Status200OK);
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetAttestationDetailResultAsync(
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Spdx3BuildProfileContracts.cs
|
||||
// Sprint: SPRINT_20260107_004_003_BE
|
||||
// Task: BP-007 - Attestor WebService Integration for SPDX 3.0.1 Build Profile
|
||||
// Description: DTOs for SPDX 3.0.1 Build profile export endpoint
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Attestor.WebService.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Supported export formats for build attestations.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum BuildAttestationFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// DSSE (Dead Simple Signing Envelope) format - default.
|
||||
/// </summary>
|
||||
Dsse = 0,
|
||||
|
||||
/// <summary>
|
||||
/// SPDX 3.0.1 Build profile format.
|
||||
/// </summary>
|
||||
Spdx3 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Both DSSE and SPDX 3.0.1 formats combined.
|
||||
/// </summary>
|
||||
Both = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to export a build attestation in SPDX 3.0.1 format.
|
||||
/// </summary>
|
||||
public sealed record Spdx3BuildExportRequestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the build type URI (e.g., "https://slsa.dev/provenance/v1").
|
||||
/// </summary>
|
||||
[Required]
|
||||
public required string BuildType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the builder ID URI.
|
||||
/// </summary>
|
||||
public string? BuilderId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the builder version.
|
||||
/// </summary>
|
||||
public string? BuilderVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the build invocation ID.
|
||||
/// </summary>
|
||||
public string? BuildId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when the build started.
|
||||
/// </summary>
|
||||
public DateTimeOffset? BuildStartTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when the build finished.
|
||||
/// </summary>
|
||||
public DateTimeOffset? BuildEndTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration source URI.
|
||||
/// </summary>
|
||||
public string? ConfigSourceUri { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration source digest (algorithm:value).
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? ConfigSourceDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration entry point.
|
||||
/// </summary>
|
||||
public string? ConfigEntryPoint { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the build environment variables.
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? Environment { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the build parameters.
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? Parameters { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the build materials (source inputs).
|
||||
/// </summary>
|
||||
public List<BuildMaterialDto>? Materials { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the output format.
|
||||
/// </summary>
|
||||
public BuildAttestationFormat Format { get; init; } = BuildAttestationFormat.Dsse;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to sign the SPDX 3.0.1 document with DSSE.
|
||||
/// </summary>
|
||||
public bool Sign { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SPDX ID prefix for generated elements.
|
||||
/// </summary>
|
||||
public string SpdxIdPrefix { get; init; } = "urn:stellaops";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build material (input) DTO.
|
||||
/// </summary>
|
||||
public sealed record BuildMaterialDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the material URI.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public required string Uri { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the material digest (algorithm:value).
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? Digest { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response containing SPDX 3.0.1 Build profile export result.
|
||||
/// </summary>
|
||||
public sealed record Spdx3BuildExportResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the format of the response.
|
||||
/// </summary>
|
||||
public required BuildAttestationFormat Format { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SPDX 3.0.1 document (JSON-LD) when format is Spdx3 or Both.
|
||||
/// </summary>
|
||||
public object? Spdx3Document { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DSSE envelope when format is Dsse or Both.
|
||||
/// </summary>
|
||||
public DsseEnvelopeDto? DsseEnvelope { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SPDX ID of the generated Build element.
|
||||
/// </summary>
|
||||
public string? BuildSpdxId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the signing information.
|
||||
/// </summary>
|
||||
public BuildSigningInfoDto? Signing { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE envelope DTO.
|
||||
/// </summary>
|
||||
public sealed record DsseEnvelopeDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the payload type.
|
||||
/// </summary>
|
||||
public required string PayloadType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base64-encoded payload.
|
||||
/// </summary>
|
||||
public required string PayloadBase64 { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the signatures.
|
||||
/// </summary>
|
||||
public required List<DsseSignatureDto> Signatures { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE signature DTO.
|
||||
/// </summary>
|
||||
public sealed record DsseSignatureDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the key ID.
|
||||
/// </summary>
|
||||
public required string KeyId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base64-encoded signature.
|
||||
/// </summary>
|
||||
public required string Sig { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build signing information DTO.
|
||||
/// </summary>
|
||||
public sealed record BuildSigningInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the key ID used for signing.
|
||||
/// </summary>
|
||||
public required string KeyId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the signing algorithm.
|
||||
/// </summary>
|
||||
public required string Algorithm { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when the document was signed.
|
||||
/// </summary>
|
||||
public required string SignedAt { get; init; }
|
||||
}
|
||||
@@ -29,5 +29,6 @@
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Attestor.StandardPredicates/StellaOps.Attestor.StandardPredicates.csproj" />
|
||||
<ProjectReference Include="../../../Router/__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.Bundling\StellaOps.Attestor.Bundling.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.Spdx3\StellaOps.Attestor.Spdx3.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user