feat: Initialize Zastava Webhook service with TLS and Authority authentication
- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint. - Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately. - Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly. - Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
This commit is contained in:
		
							
								
								
									
										175
									
								
								src/StellaOps.Scanner.WebService/Endpoints/PolicyEndpoints.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								src/StellaOps.Scanner.WebService/Endpoints/PolicyEndpoints.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| using System.Collections.Immutable; | ||||
| 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; | ||||
| using StellaOps.Scanner.WebService.Constants; | ||||
| using StellaOps.Scanner.WebService.Contracts; | ||||
| using StellaOps.Scanner.WebService.Infrastructure; | ||||
| using StellaOps.Scanner.WebService.Security; | ||||
| using StellaOps.Scanner.WebService.Services; | ||||
|  | ||||
| namespace StellaOps.Scanner.WebService.Endpoints; | ||||
|  | ||||
| internal static class PolicyEndpoints | ||||
| { | ||||
|     private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web) | ||||
|     { | ||||
|         DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull | ||||
|     }; | ||||
|     public static void MapPolicyEndpoints(this RouteGroupBuilder apiGroup, string policySegment) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(apiGroup); | ||||
|  | ||||
|         var policyGroup = apiGroup | ||||
|             .MapGroup(NormalizeSegment(policySegment)) | ||||
|             .WithTags("Policy"); | ||||
|  | ||||
|         policyGroup.MapGet("/schema", HandleSchemaAsync) | ||||
|             .WithName("scanner.policy.schema") | ||||
|             .Produces(StatusCodes.Status200OK) | ||||
|             .RequireAuthorization(ScannerPolicies.Reports) | ||||
|             .WithOpenApi(operation => | ||||
|             { | ||||
|                 operation.Summary = "Retrieve the embedded policy JSON schema."; | ||||
|                 operation.Description = "Returns the policy schema (`policy-schema@1`) used to validate YAML or JSON rulesets."; | ||||
|                 return operation; | ||||
|             }); | ||||
|  | ||||
|         policyGroup.MapPost("/diagnostics", HandleDiagnosticsAsync) | ||||
|             .WithName("scanner.policy.diagnostics") | ||||
|             .Produces<PolicyDiagnosticsResponseDto>(StatusCodes.Status200OK) | ||||
|             .Produces(StatusCodes.Status400BadRequest) | ||||
|             .RequireAuthorization(ScannerPolicies.Reports) | ||||
|             .WithOpenApi(operation => | ||||
|             { | ||||
|                 operation.Summary = "Run policy diagnostics."; | ||||
|                 operation.Description = "Accepts YAML or JSON policy content and returns normalization issues plus recommendations (ignore rules, VEX include/exclude, vendor precedence)."; | ||||
|                 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; | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     private static IResult HandleSchemaAsync(HttpContext context) | ||||
|     { | ||||
|         var schema = PolicySchemaResource.ReadSchemaJson(); | ||||
|         return Results.Text(schema, "application/schema+json", Encoding.UTF8); | ||||
|     } | ||||
|  | ||||
|     private static IResult HandleDiagnosticsAsync( | ||||
|         PolicyDiagnosticsRequestDto request, | ||||
|         TimeProvider timeProvider, | ||||
|         HttpContext context) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(request); | ||||
|         ArgumentNullException.ThrowIfNull(timeProvider); | ||||
|  | ||||
|         if (request.Policy is null || string.IsNullOrWhiteSpace(request.Policy.Content)) | ||||
|         { | ||||
|             return ProblemResultFactory.Create( | ||||
|                 context, | ||||
|                 ProblemTypes.Validation, | ||||
|                 "Invalid policy diagnostics request", | ||||
|                 StatusCodes.Status400BadRequest, | ||||
|                 detail: "Policy content is required for diagnostics."); | ||||
|         } | ||||
|  | ||||
|         var format = PolicyDtoMapper.ParsePolicyFormat(request.Policy.Format); | ||||
|         var binding = PolicyBinder.Bind(request.Policy.Content, format); | ||||
|         var diagnostics = PolicyDiagnostics.Create(binding, timeProvider); | ||||
|  | ||||
|         var response = new PolicyDiagnosticsResponseDto | ||||
|         { | ||||
|             Success = diagnostics.ErrorCount == 0, | ||||
|             Version = diagnostics.Version, | ||||
|             RuleCount = diagnostics.RuleCount, | ||||
|             ErrorCount = diagnostics.ErrorCount, | ||||
|             WarningCount = diagnostics.WarningCount, | ||||
|             GeneratedAt = diagnostics.GeneratedAt, | ||||
|             Issues = diagnostics.Issues.Select(PolicyDtoMapper.ToIssueDto).ToImmutableArray(), | ||||
|             Recommendations = diagnostics.Recommendations | ||||
|         }; | ||||
|  | ||||
|         return Json(response); | ||||
|     } | ||||
|  | ||||
|     private static async Task<IResult> HandlePreviewAsync( | ||||
|         PolicyPreviewRequestDto request, | ||||
|         PolicyPreviewService previewService, | ||||
|         HttpContext context, | ||||
|         CancellationToken cancellationToken) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(request); | ||||
|         ArgumentNullException.ThrowIfNull(previewService); | ||||
|  | ||||
|         if (string.IsNullOrWhiteSpace(request.ImageDigest)) | ||||
|         { | ||||
|             return ProblemResultFactory.Create( | ||||
|                 context, | ||||
|                 ProblemTypes.Validation, | ||||
|                 "Invalid policy preview request", | ||||
|                 StatusCodes.Status400BadRequest, | ||||
|                 detail: "imageDigest is required."); | ||||
|         } | ||||
|  | ||||
|         if (!request.ImageDigest.Contains(':', StringComparison.Ordinal)) | ||||
|         { | ||||
|             return ProblemResultFactory.Create( | ||||
|                 context, | ||||
|                 ProblemTypes.Validation, | ||||
|                 "Invalid policy preview request", | ||||
|                 StatusCodes.Status400BadRequest, | ||||
|                 detail: "imageDigest must include algorithm prefix (e.g. sha256:...)."); | ||||
|         } | ||||
|  | ||||
|         if (request.Findings is not null) | ||||
|         { | ||||
|             var missingIds = request.Findings.Any(f => string.IsNullOrWhiteSpace(f.Id)); | ||||
|             if (missingIds) | ||||
|             { | ||||
|                 return ProblemResultFactory.Create( | ||||
|                     context, | ||||
|                     ProblemTypes.Validation, | ||||
|                     "Invalid policy preview request", | ||||
|                     StatusCodes.Status400BadRequest, | ||||
|                     detail: "All findings must include an id value."); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var domainRequest = PolicyDtoMapper.ToDomain(request); | ||||
|         var response = await previewService.PreviewAsync(domainRequest, cancellationToken).ConfigureAwait(false); | ||||
|         var payload = PolicyDtoMapper.ToDto(response); | ||||
|         return Json(payload); | ||||
|     } | ||||
|  | ||||
|     private static string NormalizeSegment(string segment) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(segment)) | ||||
|         { | ||||
|             return "/policy"; | ||||
|         } | ||||
|  | ||||
|         var trimmed = segment.Trim('/'); | ||||
|         return "/" + trimmed; | ||||
|     } | ||||
|  | ||||
|     private static IResult Json<T>(T value) | ||||
|     { | ||||
|         var payload = JsonSerializer.Serialize(value, SerializerOptions); | ||||
|         return Results.Content(payload, "application/json", Encoding.UTF8); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user