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:
master
2025-10-19 18:36:22 +03:00
parent 2062da7a8b
commit d099a90f9b
966 changed files with 91038 additions and 1850 deletions

View File

@@ -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.

View File

@@ -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>

View File

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

View File

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

View File

@@ -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";
}

View File

@@ -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.
>