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:
@@ -18,4 +18,5 @@ Team 8 owns the end-to-end security posture for StellaOps Authority and its cons
|
||||
- Rate-limit `/token` and bootstrap endpoints once CORE8 hooks are available.
|
||||
- Deliver offline revocation bundles signed with detached JWS and provide a verification script.
|
||||
- Maintain `docs/security/authority-threat-model.md` and ensure mitigations are tracked.
|
||||
- All crypto consumption flows through `StellaOps.Cryptography` abstractions to enable sovereign crypto providers.
|
||||
- All crypto consumption flows through `StellaOps.Cryptography` abstractions to enable sovereign crypto providers.
|
||||
- Every new cryptographic algorithm, dependency, or acceleration path ships as an `ICryptoProvider` plug-in under `StellaOps.Cryptography.*`; feature code must never bind directly to third-party crypto libraries.
|
||||
|
||||
@@ -6,6 +6,15 @@ using System.Security.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the underlying key material for a <see cref="CryptoSigningKey"/>.
|
||||
/// </summary>
|
||||
public enum CryptoSigningKeyKind
|
||||
{
|
||||
Ec,
|
||||
Raw
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents asymmetric signing key material managed by a crypto provider.
|
||||
/// </summary>
|
||||
@@ -13,6 +22,10 @@ public sealed class CryptoSigningKey
|
||||
{
|
||||
private static readonly ReadOnlyDictionary<string, string?> EmptyMetadata =
|
||||
new(new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase));
|
||||
private static readonly byte[] EmptyKey = Array.Empty<byte>();
|
||||
|
||||
private readonly byte[] privateKeyBytes;
|
||||
private readonly byte[] publicKeyBytes;
|
||||
|
||||
public CryptoSigningKey(
|
||||
CryptoKeyReference reference,
|
||||
@@ -37,6 +50,10 @@ public sealed class CryptoSigningKey
|
||||
AlgorithmId = algorithmId;
|
||||
CreatedAt = createdAt;
|
||||
ExpiresAt = expiresAt;
|
||||
Kind = CryptoSigningKeyKind.Ec;
|
||||
|
||||
privateKeyBytes = EmptyKey;
|
||||
publicKeyBytes = EmptyKey;
|
||||
|
||||
PrivateParameters = CloneParameters(privateParameters, includePrivate: true);
|
||||
PublicParameters = CloneParameters(privateParameters, includePrivate: false);
|
||||
@@ -48,6 +65,45 @@ public sealed class CryptoSigningKey
|
||||
StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public CryptoSigningKey(
|
||||
CryptoKeyReference reference,
|
||||
string algorithmId,
|
||||
ReadOnlyMemory<byte> privateKey,
|
||||
DateTimeOffset createdAt,
|
||||
DateTimeOffset? expiresAt = null,
|
||||
ReadOnlyMemory<byte> publicKey = default,
|
||||
IReadOnlyDictionary<string, string?>? metadata = null)
|
||||
{
|
||||
Reference = reference ?? throw new ArgumentNullException(nameof(reference));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
}
|
||||
|
||||
if (privateKey.IsEmpty)
|
||||
{
|
||||
throw new ArgumentException("Private key material must be provided.", nameof(privateKey));
|
||||
}
|
||||
|
||||
AlgorithmId = algorithmId;
|
||||
CreatedAt = createdAt;
|
||||
ExpiresAt = expiresAt;
|
||||
Kind = CryptoSigningKeyKind.Raw;
|
||||
|
||||
privateKeyBytes = privateKey.ToArray();
|
||||
publicKeyBytes = publicKey.IsEmpty ? EmptyKey : publicKey.ToArray();
|
||||
|
||||
PrivateParameters = default;
|
||||
PublicParameters = default;
|
||||
Metadata = metadata is null
|
||||
? EmptyMetadata
|
||||
: new ReadOnlyDictionary<string, string?>(metadata.ToDictionary(
|
||||
static pair => pair.Key,
|
||||
static pair => pair.Value,
|
||||
StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key reference (id + provider hint).
|
||||
/// </summary>
|
||||
@@ -68,6 +124,21 @@ public sealed class CryptoSigningKey
|
||||
/// </summary>
|
||||
public ECParameters PublicParameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the underlying key material representation.
|
||||
/// </summary>
|
||||
public CryptoSigningKeyKind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw private key bytes when available (empty for EC-backed keys).
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> PrivateKey => privateKeyBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw public key bytes when available (empty for EC-backed keys or when not supplied).
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> PublicKey => publicKeyBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp when the key was created/imported.
|
||||
/// </summary>
|
||||
|
||||
@@ -86,6 +86,10 @@ public sealed class DefaultCryptoProvider : ICryptoProvider
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(signingKey);
|
||||
EnsureSigningSupported(signingKey.AlgorithmId);
|
||||
if (signingKey.Kind != CryptoSigningKeyKind.Ec)
|
||||
{
|
||||
throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys.");
|
||||
}
|
||||
ValidateSigningKey(signingKey);
|
||||
|
||||
signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey);
|
||||
|
||||
@@ -64,6 +64,10 @@ public sealed class LibsodiumCryptoProvider : ICryptoProvider
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(signingKey);
|
||||
EnsureAlgorithmSupported(signingKey.AlgorithmId);
|
||||
if (signingKey.Kind != CryptoSigningKeyKind.Ec)
|
||||
{
|
||||
throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys.");
|
||||
}
|
||||
|
||||
signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey);
|
||||
}
|
||||
|
||||
@@ -8,4 +8,6 @@ public static class SignatureAlgorithms
|
||||
public const string Es256 = "ES256";
|
||||
public const string Es384 = "ES384";
|
||||
public const string Es512 = "ES512";
|
||||
public const string Ed25519 = "ED25519";
|
||||
public const string EdDsa = "EdDSA";
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
> Remark (2025-10-14): Offline kit docs include manifest verification workflow; attestation artifacts referenced.
|
||||
| SEC5.H | DONE (2025-10-13) | Security Guild + Authority Core | Ensure `/token` denials persist audit records with correlation IDs. | SEC2.A, SEC2.B | ✅ Audit store captures denials; ✅ Tests cover success/failure/lockout; ✅ Threat model review updated. |
|
||||
| D5.A | DONE (2025-10-12) | Security Guild | Flesh out `StellaOps.Cryptography` provider registry, policy, and DI helpers enabling sovereign crypto selection. | SEC1.A, SEC4.B | ✅ `ICryptoProviderRegistry` implementation with provider selection rules; ✅ `StellaOps.Cryptography.DependencyInjection` extensions; ✅ Tests covering fallback ordering. |
|
||||
| SEC6.A | DONE (2025-10-19) | Security Guild | Ship BouncyCastle-backed Ed25519 signing as a `StellaOps.Cryptography` plug-in and migrate Scanner WebService signing to consume the provider registry; codify the plug-in rule in AGENTS.<br>2025-10-19: Added `StellaOps.Cryptography.Plugin.BouncyCastle`, updated DI and ReportSigner, captured provider tests (`BouncyCastleEd25519CryptoProviderTests`). | D5.A | ✅ Plug-in registered via DI (`AddStellaOpsCrypto` + `AddBouncyCastleEd25519Provider`); ✅ Report signer resolves keys through registry; ✅ Unit tests cover Ed25519 sign/verify via provider. |
|
||||
|
||||
> Remark (2025-10-13, SEC2.B): Coordinated with Authority Core — audit sinks now receive `/token` success/failure events; awaiting host test suite once signing fixture lands.
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user