search and ai stabilization work, localization stablized.

This commit is contained in:
master
2026-02-24 23:29:36 +02:00
parent 4f947a8b61
commit b07d27772e
766 changed files with 55299 additions and 3221 deletions

View File

@@ -28,5 +28,6 @@
<ProjectReference Include="..\__Libraries\StellaOps.Attestor.TrustRepo\StellaOps.Attestor.TrustRepo.csproj" />
<ProjectReference Include="..\..\__Libraries\StellaOps.Configuration\StellaOps.Configuration.csproj" />
<ProjectReference Include="..\..\__Libraries\StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj" />
<ProjectReference Include="..\..\Authority\StellaOps.Authority\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.csproj" />
</ItemGroup>
</Project>

View File

@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Http;
using StellaOps.Attestor.Core.Bulk;
using StellaOps.Attestor.Core.InToto;
using static StellaOps.Localization.T;
using StellaOps.Attestor.Core.Offline;
using StellaOps.Attestor.Core.Options;
using StellaOps.Attestor.Core.Signing;
@@ -88,7 +89,7 @@ internal static class AttestorWebServiceEndpoints
{
if (requestDto is null)
{
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "Request body is required.");
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: _t("attestor.validation.body_required"));
}
if (!IsJsonContentType(httpContext.Request.ContentType))
@@ -99,13 +100,13 @@ internal static class AttestorWebServiceEndpoints
var certificate = httpContext.Connection.ClientCertificate;
if (certificate is null)
{
return Results.Problem(statusCode: StatusCodes.Status403Forbidden, title: "Client certificate required");
return Results.Problem(statusCode: StatusCodes.Status403Forbidden, title: _t("attestor.validation.client_cert_required"));
}
var user = httpContext.User;
if (user?.Identity is not { IsAuthenticated: true })
{
return Results.Problem(statusCode: StatusCodes.Status401Unauthorized, title: "Authentication required");
return Results.Problem(statusCode: StatusCodes.Status401Unauthorized, title: _t("attestor.validation.authentication_required"));
}
var signingRequest = new AttestationSignRequest
@@ -167,7 +168,7 @@ internal static class AttestorWebServiceEndpoints
{
if (requestDto is null)
{
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "Request body is required.");
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: _t("attestor.validation.body_required"));
}
if (!IsJsonContentType(httpContext.Request.ContentType))
@@ -177,7 +178,7 @@ internal static class AttestorWebServiceEndpoints
if (string.IsNullOrWhiteSpace(requestDto.StepName))
{
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "stepName is required.");
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: _t("attestor.validation.step_name_required"));
}
var certificate = httpContext.Connection.ClientCertificate;
@@ -217,7 +218,7 @@ internal static class AttestorWebServiceEndpoints
{
if (string.IsNullOrWhiteSpace(material.Uri))
{
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "Material URI is required.");
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: _t("attestor.validation.material_uri_required"));
}
var digests = new ArtifactDigests { Sha256 = material.Sha256, Sha512 = material.Sha512 };
@@ -232,7 +233,7 @@ internal static class AttestorWebServiceEndpoints
{
if (string.IsNullOrWhiteSpace(product.Uri))
{
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "Product URI is required.");
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: _t("attestor.validation.product_uri_required"));
}
var digests = new ArtifactDigests { Sha256 = product.Sha256, Sha512 = product.Sha512 };
@@ -304,13 +305,13 @@ internal static class AttestorWebServiceEndpoints
var certificate = httpContext.Connection.ClientCertificate;
if (certificate is null)
{
return Results.Problem(statusCode: StatusCodes.Status403Forbidden, title: "Client certificate required");
return Results.Problem(statusCode: StatusCodes.Status403Forbidden, title: _t("attestor.validation.client_cert_required"));
}
var user = httpContext.User;
if (user?.Identity is not { IsAuthenticated: true })
{
return Results.Problem(statusCode: StatusCodes.Status401Unauthorized, title: "Authentication required");
return Results.Problem(statusCode: StatusCodes.Status401Unauthorized, title: _t("attestor.validation.authentication_required"));
}
var submissionContext = BuildSubmissionContext(user, certificate);
@@ -388,7 +389,7 @@ internal static class AttestorWebServiceEndpoints
var queued = await jobStore.CountQueuedAsync(cancellationToken).ConfigureAwait(false);
if (queued >= Math.Max(1, attestorOptions.Quotas.Bulk.MaxQueuedJobs))
{
return Results.Problem(statusCode: StatusCodes.Status429TooManyRequests, title: "Too many bulk verification jobs queued. Try again later.");
return Results.Problem(statusCode: StatusCodes.Status429TooManyRequests, title: _t("attestor.error.bulk_verify_queue_full"));
}
job = await jobStore.CreateAsync(job!, cancellationToken).ConfigureAwait(false);
@@ -431,7 +432,7 @@ internal static class AttestorWebServiceEndpoints
{
if (requestDto is null)
{
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "Request body is required.");
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: _t("attestor.validation.body_required"));
}
if (!IsJsonContentType(httpContext.Request.ContentType))
@@ -441,7 +442,7 @@ internal static class AttestorWebServiceEndpoints
if (string.IsNullOrWhiteSpace(requestDto.BuildType))
{
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: "buildType is required.");
return Results.Problem(statusCode: StatusCodes.Status400BadRequest, title: _t("attestor.validation.build_type_required"));
}
// Build the attestation payload from the request
@@ -492,8 +493,8 @@ internal static class AttestorWebServiceEndpoints
{
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.");
title: _t("attestor.error.spdx_mapping_failed"),
detail: _t("attestor.error.spdx_mapping_missing_fields"));
}
// Map to SPDX 3.0.1 Build element
@@ -730,7 +731,7 @@ internal static class AttestorWebServiceEndpoints
{
return Results.Problem(
statusCode: StatusCodes.Status415UnsupportedMediaType,
title: "Unsupported content type. Submit application/json payloads.",
title: _t("attestor.error.unsupported_content_type"),
extensions: new Dictionary<string, object?>
{
["code"] = "unsupported_media_type"

View File

@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using StellaOps.Attestor.Persistence.Repositories;
using StellaOps.Auth.ServerIntegration.Tenancy;
using static StellaOps.Localization.T;
namespace StellaOps.Attestor.WebService.Endpoints;
@@ -71,7 +72,7 @@ public static class PredicateRegistryEndpoints
var entry = await repository.GetByUriAsync(decoded, ct);
if (entry is null)
{
return Results.NotFound(new { error = "Predicate type not found", uri = decoded });
return Results.NotFound(new { error = _t("attestor.error.predicate_not_found"), uri = decoded });
}
return Results.Ok(entry);

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using StellaOps.Attestor.Core.Options;
using StellaOps.Localization;
using StellaOps.Attestor.WebService;
using StellaOps.Configuration;
using StellaOps.Auth.ServerIntegration;
@@ -30,6 +31,9 @@ builder.WebHost.ConfigureAttestorKestrel(attestorOptions, clientCertificateAutho
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
builder.Services.AddStellaOpsLocalization(builder.Configuration);
builder.Services.AddTranslationBundle(System.Reflection.Assembly.GetExecutingAssembly());
// Stella Router integration
var routerEnabled = builder.Services.AddRouterMicroservice(
builder.Configuration,
@@ -42,9 +46,11 @@ var app = builder.Build();
app.LogStellaOpsLocalHostname("attestor");
app.UseStellaOpsCors();
app.UseStellaOpsLocalization();
app.UseAttestorWebService(attestorOptions, routerEnabled);
app.Run();
await app.LoadTranslationsAsync();
await app.RunAsync().ConfigureAwait(false);
internal sealed class NoAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{

View File

@@ -33,6 +33,10 @@
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.Spdx3\StellaOps.Attestor.Spdx3.csproj" />
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.Watchlist\StellaOps.Attestor.Watchlist.csproj" />
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.Persistence\StellaOps.Attestor.Persistence.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Localization\StellaOps.Localization.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Translations\*.json" />
</ItemGroup>
<PropertyGroup Label="StellaOpsReleaseVersion">
<Version>1.0.0-alpha1</Version>

View File

@@ -0,0 +1,23 @@
{
"_meta": { "locale": "en-US", "namespace": "attestor", "version": "1.0" },
"attestor.error.predicate_not_found": "Predicate type not found.",
"attestor.error.watchlist_entry_not_found": "Watchlist entry {0} not found.",
"attestor.error.bulk_verify_queue_full": "Too many bulk verification jobs queued. Try again later.",
"attestor.error.spdx_mapping_failed": "Cannot map attestation to SPDX 3.0.1.",
"attestor.error.spdx_mapping_missing_fields": "The provided attestation payload is missing required fields for SPDX 3.0.1 Build profile.",
"attestor.error.unsupported_content_type": "Unsupported content type. Submit application/json payloads.",
"attestor.validation.body_required": "Request body is required.",
"attestor.validation.client_cert_required": "Client certificate required.",
"attestor.validation.authentication_required": "Authentication required.",
"attestor.validation.step_name_required": "stepName is required.",
"attestor.validation.material_uri_required": "Material URI is required.",
"attestor.validation.product_uri_required": "Product URI is required.",
"attestor.validation.build_type_required": "buildType is required.",
"attestor.watchlist.admin_only_global_system": "Only administrators can create global or system scope entries.",
"attestor.watchlist.admin_only_change_scope": "Only administrators can change entry scope.",
"attestor.watchlist.system_cannot_delete": "System scope entries cannot be deleted.",
"attestor.watchlist.admin_only_delete_global": "Only administrators can delete global scope entries."
}

View File

@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc;
using StellaOps.Attestor.Watchlist.Matching;
using StellaOps.Attestor.Watchlist.Models;
using StellaOps.Attestor.Watchlist.Storage;
using static StellaOps.Localization.T;
namespace StellaOps.Attestor.WebService;
@@ -107,13 +108,13 @@ internal static class WatchlistEndpoints
var entry = await repository.GetAsync(id, cancellationToken);
if (entry is null)
{
return Results.NotFound(new { Message = $"Watchlist entry {id} not found" });
return Results.NotFound(new { Message = _t("attestor.error.watchlist_entry_not_found", id) });
}
var tenantId = GetTenantId(context);
if (!CanAccessEntry(entry, tenantId))
{
return Results.NotFound(new { Message = $"Watchlist entry {id} not found" });
return Results.NotFound(new { Message = _t("attestor.error.watchlist_entry_not_found", id) });
}
return Results.Ok(WatchlistEntryResponse.FromDomain(entry));
@@ -135,7 +136,7 @@ internal static class WatchlistEndpoints
{
return Results.Problem(
statusCode: StatusCodes.Status403Forbidden,
title: "Only administrators can create global or system scope entries.");
title: _t("attestor.watchlist.admin_only_global_system"));
}
}
@@ -166,7 +167,7 @@ internal static class WatchlistEndpoints
var existing = await repository.GetAsync(id, cancellationToken);
if (existing is null || !CanAccessEntry(existing, tenantId))
{
return Results.NotFound(new { Message = $"Watchlist entry {id} not found" });
return Results.NotFound(new { Message = _t("attestor.error.watchlist_entry_not_found", id) });
}
// Can't change scope unless admin
@@ -174,7 +175,7 @@ internal static class WatchlistEndpoints
{
return Results.Problem(
statusCode: StatusCodes.Status403Forbidden,
title: "Only administrators can change entry scope.");
title: _t("attestor.watchlist.admin_only_change_scope"));
}
var updated = request.ToDomain(tenantId, userId) with
@@ -208,7 +209,7 @@ internal static class WatchlistEndpoints
var existing = await repository.GetAsync(id, cancellationToken);
if (existing is null || !CanAccessEntry(existing, tenantId))
{
return Results.NotFound(new { Message = $"Watchlist entry {id} not found" });
return Results.NotFound(new { Message = _t("attestor.error.watchlist_entry_not_found", id) });
}
// System entries cannot be deleted
@@ -216,7 +217,7 @@ internal static class WatchlistEndpoints
{
return Results.Problem(
statusCode: StatusCodes.Status403Forbidden,
title: "System scope entries cannot be deleted.");
title: _t("attestor.watchlist.system_cannot_delete"));
}
// Global entries require admin
@@ -224,7 +225,7 @@ internal static class WatchlistEndpoints
{
return Results.Problem(
statusCode: StatusCodes.Status403Forbidden,
title: "Only administrators can delete global scope entries.");
title: _t("attestor.watchlist.admin_only_delete_global"));
}
await repository.DeleteAsync(id, tenantId, cancellationToken);
@@ -244,7 +245,7 @@ internal static class WatchlistEndpoints
var entry = await repository.GetAsync(id, cancellationToken);
if (entry is null || !CanAccessEntry(entry, tenantId))
{
return Results.NotFound(new { Message = $"Watchlist entry {id} not found" });
return Results.NotFound(new { Message = _t("attestor.error.watchlist_entry_not_found", id) });
}
var identity = new SignerIdentityInput