Add tests and implement StubBearer authentication for Signer endpoints
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			- Created SignerEndpointsTests to validate the SignDsse and VerifyReferrers endpoints. - Implemented StubBearerAuthenticationDefaults and StubBearerAuthenticationHandler for token-based authentication. - Developed ConcelierExporterClient for managing Trivy DB settings and export operations. - Added TrivyDbSettingsPageComponent for UI interactions with Trivy DB settings, including form handling and export triggering. - Implemented styles and HTML structure for Trivy DB settings page. - Created NotifySmokeCheck tool for validating Redis event streams and Notify deliveries.
This commit is contained in:
		| @@ -1,8 +1,10 @@ | ||||
| using System.Collections.Immutable; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | ||||
| using System.Collections.ObjectModel; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Routing; | ||||
| using StellaOps.Policy; | ||||
| @@ -51,18 +53,30 @@ internal static class PolicyEndpoints | ||||
|                 return operation; | ||||
|             }); | ||||
|  | ||||
|         policyGroup.MapPost("/preview", HandlePreviewAsync) | ||||
|             .WithName("scanner.policy.preview") | ||||
|             .Produces<PolicyPreviewResponseDto>(StatusCodes.Status200OK) | ||||
|             .Produces(StatusCodes.Status400BadRequest) | ||||
|             .RequireAuthorization(ScannerPolicies.Reports) | ||||
|             .WithOpenApi(operation => | ||||
|             { | ||||
|                 operation.Summary = "Preview policy impact against findings."; | ||||
|                 operation.Description = "Evaluates the supplied findings against the active or proposed policy, returning diffs, quieted verdicts, and actionable validation messages."; | ||||
|                 return operation; | ||||
|             }); | ||||
|     } | ||||
|         policyGroup.MapPost("/preview", HandlePreviewAsync) | ||||
|             .WithName("scanner.policy.preview") | ||||
|             .Produces<PolicyPreviewResponseDto>(StatusCodes.Status200OK) | ||||
|             .Produces(StatusCodes.Status400BadRequest) | ||||
|             .RequireAuthorization(ScannerPolicies.Reports) | ||||
|             .WithOpenApi(operation => | ||||
|             { | ||||
|                 operation.Summary = "Preview policy impact against findings."; | ||||
|                 operation.Description = "Evaluates the supplied findings against the active or proposed policy, returning diffs, quieted verdicts, and actionable validation messages."; | ||||
|                 return operation; | ||||
|             }); | ||||
|  | ||||
|         policyGroup.MapPost("/runtime", HandleRuntimePolicyAsync) | ||||
|             .WithName("scanner.policy.runtime") | ||||
|             .Produces<RuntimePolicyResponseDto>(StatusCodes.Status200OK) | ||||
|             .Produces(StatusCodes.Status400BadRequest) | ||||
|             .RequireAuthorization(ScannerPolicies.Reports) | ||||
|             .WithOpenApi(operation => | ||||
|             { | ||||
|                 operation.Summary = "Evaluate runtime policy for digests."; | ||||
|                 operation.Description = "Returns per-image policy verdicts, signature and SBOM metadata, and cache hints for admission controllers."; | ||||
|                 return operation; | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     private static IResult HandleSchemaAsync(HttpContext context) | ||||
|     { | ||||
| @@ -152,9 +166,97 @@ internal static class PolicyEndpoints | ||||
|  | ||||
|         var domainRequest = PolicyDtoMapper.ToDomain(request); | ||||
|         var response = await previewService.PreviewAsync(domainRequest, cancellationToken).ConfigureAwait(false); | ||||
|         var payload = PolicyDtoMapper.ToDto(response); | ||||
|         return Json(payload); | ||||
|     } | ||||
|         var payload = PolicyDtoMapper.ToDto(response); | ||||
|         return Json(payload); | ||||
|     } | ||||
|  | ||||
|     private static async Task<IResult> HandleRuntimePolicyAsync( | ||||
|         RuntimePolicyRequestDto request, | ||||
|         IRuntimePolicyService runtimePolicyService, | ||||
|         HttpContext context, | ||||
|         CancellationToken cancellationToken) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(request); | ||||
|         ArgumentNullException.ThrowIfNull(runtimePolicyService); | ||||
|  | ||||
|         if (request.Images is null || request.Images.Count == 0) | ||||
|         { | ||||
|             return ProblemResultFactory.Create( | ||||
|                 context, | ||||
|                 ProblemTypes.Validation, | ||||
|                 "Invalid runtime policy request", | ||||
|                 StatusCodes.Status400BadRequest, | ||||
|                 detail: "images collection must include at least one digest."); | ||||
|         } | ||||
|  | ||||
|         var normalizedImages = new List<string>(); | ||||
|         var seen = new HashSet<string>(StringComparer.Ordinal); | ||||
|         foreach (var image in request.Images) | ||||
|         { | ||||
|             if (string.IsNullOrWhiteSpace(image)) | ||||
|             { | ||||
|                 return ProblemResultFactory.Create( | ||||
|                     context, | ||||
|                     ProblemTypes.Validation, | ||||
|                     "Invalid runtime policy request", | ||||
|                     StatusCodes.Status400BadRequest, | ||||
|                     detail: "Image digests must be non-empty."); | ||||
|             } | ||||
|  | ||||
|             var trimmed = image.Trim(); | ||||
|             if (!trimmed.Contains(':', StringComparison.Ordinal)) | ||||
|             { | ||||
|                 return ProblemResultFactory.Create( | ||||
|                     context, | ||||
|                     ProblemTypes.Validation, | ||||
|                     "Invalid runtime policy request", | ||||
|                     StatusCodes.Status400BadRequest, | ||||
|                     detail: "Image digests must include an algorithm prefix (e.g. sha256:...)."); | ||||
|             } | ||||
|  | ||||
|             if (seen.Add(trimmed)) | ||||
|             { | ||||
|                 normalizedImages.Add(trimmed); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (normalizedImages.Count == 0) | ||||
|         { | ||||
|             return ProblemResultFactory.Create( | ||||
|                 context, | ||||
|                 ProblemTypes.Validation, | ||||
|                 "Invalid runtime policy request", | ||||
|                 StatusCodes.Status400BadRequest, | ||||
|                 detail: "images collection must include at least one unique digest."); | ||||
|         } | ||||
|  | ||||
|         var namespaceValue = string.IsNullOrWhiteSpace(request.Namespace) ? null : request.Namespace.Trim(); | ||||
|         var normalizedLabels = new Dictionary<string, string>(StringComparer.Ordinal); | ||||
|         if (request.Labels is not null) | ||||
|         { | ||||
|             foreach (var pair in request.Labels) | ||||
|             { | ||||
|                 if (string.IsNullOrWhiteSpace(pair.Key)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var key = pair.Key.Trim(); | ||||
|                 var value = pair.Value?.Trim() ?? string.Empty; | ||||
|                 normalizedLabels[key] = value; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var evaluationRequest = new RuntimePolicyEvaluationRequest( | ||||
|             namespaceValue, | ||||
|             new ReadOnlyDictionary<string, string>(normalizedLabels), | ||||
|             normalizedImages); | ||||
|  | ||||
|         var evaluation = await runtimePolicyService.EvaluateAsync(evaluationRequest, cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|         var resultPayload = MapRuntimePolicyResponse(evaluation); | ||||
|         return Json(resultPayload); | ||||
|     } | ||||
|  | ||||
|     private static string NormalizeSegment(string segment) | ||||
|     { | ||||
| @@ -167,9 +269,53 @@ internal static class PolicyEndpoints | ||||
|         return "/" + trimmed; | ||||
|     } | ||||
|  | ||||
|     private static IResult Json<T>(T value) | ||||
|     { | ||||
|         var payload = JsonSerializer.Serialize(value, SerializerOptions); | ||||
|         return Results.Content(payload, "application/json", Encoding.UTF8); | ||||
|     } | ||||
| } | ||||
|     private static IResult Json<T>(T value) | ||||
|     { | ||||
|         var payload = JsonSerializer.Serialize(value, SerializerOptions); | ||||
|         return Results.Content(payload, "application/json", Encoding.UTF8); | ||||
|     } | ||||
|  | ||||
|     private static RuntimePolicyResponseDto MapRuntimePolicyResponse(RuntimePolicyEvaluationResult evaluation) | ||||
|     { | ||||
|         var results = new Dictionary<string, RuntimePolicyImageResponseDto>(evaluation.Results.Count, StringComparer.Ordinal); | ||||
|         foreach (var pair in evaluation.Results) | ||||
|         { | ||||
|             var decision = pair.Value; | ||||
|             RuntimePolicyRekorDto? rekor = null; | ||||
|             if (decision.Rekor is not null) | ||||
|             { | ||||
|                 rekor = new RuntimePolicyRekorDto | ||||
|                 { | ||||
|                     Uuid = decision.Rekor.Uuid, | ||||
|                     Url = decision.Rekor.Url, | ||||
|                     Verified = decision.Rekor.Verified | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             IDictionary<string, object?>? metadata = null; | ||||
|             if (decision.Metadata is not null && decision.Metadata.Count > 0) | ||||
|             { | ||||
|                 metadata = new Dictionary<string, object?>(decision.Metadata, StringComparer.OrdinalIgnoreCase); | ||||
|             } | ||||
|  | ||||
|             results[pair.Key] = new RuntimePolicyImageResponseDto | ||||
|             { | ||||
|                 PolicyVerdict = decision.PolicyVerdict, | ||||
|                 Signed = decision.Signed, | ||||
|                 HasSbomReferrers = decision.HasSbomReferrers, | ||||
|                 HasSbomLegacy = decision.HasSbomReferrers, | ||||
|                 Reasons = decision.Reasons.ToArray(), | ||||
|                 Rekor = rekor, | ||||
|                 Metadata = metadata | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         return new RuntimePolicyResponseDto | ||||
|         { | ||||
|             TtlSeconds = evaluation.TtlSeconds, | ||||
|             ExpiresAtUtc = evaluation.ExpiresAtUtc, | ||||
|             PolicyRevision = evaluation.PolicyRevision, | ||||
|             Results = results | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user