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);
}