using System.Net.Http.Json; using System.Text.Json; using Microsoft.Extensions.Logging; namespace StellaOps.Policy.Engine.Attestation; /// /// HTTP client for communicating with the Attestor service. /// public sealed class HttpAttestorClient : IAttestorClient { private readonly HttpClient _httpClient; private readonly VerdictAttestationOptions _options; private readonly ILogger _logger; public HttpAttestorClient( HttpClient httpClient, VerdictAttestationOptions options, ILogger logger) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _options = options ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); // Configure HTTP client _httpClient.BaseAddress = new Uri(_options.AttestorUrl); _httpClient.Timeout = _options.Timeout; } public async Task CreateAttestationAsync( VerdictAttestationRequest request, CancellationToken cancellationToken = default) { if (request is null) { throw new ArgumentNullException(nameof(request)); } _logger.LogDebug( "Sending verdict attestation request to Attestor: {PredicateType} for {SubjectName}", request.PredicateType, request.Subject.Name); try { // POST to internal attestation endpoint var response = await _httpClient.PostAsJsonAsync( "/internal/api/v1/attestations/verdict", new { predicateType = request.PredicateType, predicate = request.Predicate, subject = new[] { new { name = request.Subject.Name, digest = request.Subject.Digest } } }, cancellationToken); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync( cancellationToken: cancellationToken); if (result is null) { throw new InvalidOperationException("Attestor returned null response."); } _logger.LogDebug( "Verdict attestation created: {VerdictId}, URI: {Uri}", result.VerdictId, result.AttestationUri); return new VerdictAttestationResult( verdictId: result.VerdictId, attestationUri: result.AttestationUri, rekorLogIndex: result.RekorLogIndex ); } catch (HttpRequestException ex) { _logger.LogError( ex, "HTTP error creating verdict attestation: {StatusCode}", ex.StatusCode); throw; } catch (JsonException ex) { _logger.LogError( ex, "Failed to deserialize Attestor response"); throw; } } // API response model (internal, not part of public contract) private sealed record AttestationApiResponse( string VerdictId, string AttestationUri, long? RekorLogIndex); }