Add authority bootstrap flows and Concelier ops runbooks
This commit is contained in:
		| @@ -1,8 +1,10 @@ | ||||
| using System; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Security.Cryptography; | ||||
| using System.Text; | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| @@ -16,6 +18,7 @@ using StellaOps.Cli.Services; | ||||
| using StellaOps.Cli.Services.Models; | ||||
| using StellaOps.Cli.Telemetry; | ||||
| using StellaOps.Cli.Tests.Testing; | ||||
| using StellaOps.Cryptography; | ||||
|  | ||||
| namespace StellaOps.Cli.Tests.Commands; | ||||
|  | ||||
| @@ -208,6 +211,34 @@ public sealed class CommandHandlersTests | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Theory] | ||||
|     [InlineData(null)] | ||||
|     [InlineData("default")] | ||||
|     [InlineData("libsodium")] | ||||
|     public async Task HandleAuthRevokeVerifyAsync_VerifiesBundlesUsingProviderRegistry(string? providerHint) | ||||
|     { | ||||
|         var original = Environment.ExitCode; | ||||
|         using var tempDir = new TempDirectory(); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             var artifacts = await WriteRevocationArtifactsAsync(tempDir, providerHint); | ||||
|  | ||||
|             await CommandHandlers.HandleAuthRevokeVerifyAsync( | ||||
|                 artifacts.BundlePath, | ||||
|                 artifacts.SignaturePath, | ||||
|                 artifacts.KeyPath, | ||||
|                 verbose: true, | ||||
|                 cancellationToken: CancellationToken.None); | ||||
|  | ||||
|             Assert.Equal(0, Environment.ExitCode); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             Environment.ExitCode = original; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task HandleAuthStatusAsync_ReportsCachedToken() | ||||
|     { | ||||
| @@ -360,6 +391,79 @@ public sealed class CommandHandlersTests | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static async Task<RevocationArtifactPaths> WriteRevocationArtifactsAsync(TempDirectory temp, string? providerHint) | ||||
|     { | ||||
|         var (bundleBytes, signature, keyPem) = await BuildRevocationArtifactsAsync(providerHint); | ||||
|  | ||||
|         var bundlePath = Path.Combine(temp.Path, "revocation-bundle.json"); | ||||
|         var signaturePath = Path.Combine(temp.Path, "revocation-bundle.json.jws"); | ||||
|         var keyPath = Path.Combine(temp.Path, "revocation-key.pem"); | ||||
|  | ||||
|         await File.WriteAllBytesAsync(bundlePath, bundleBytes); | ||||
|         await File.WriteAllTextAsync(signaturePath, signature); | ||||
|         await File.WriteAllTextAsync(keyPath, keyPem); | ||||
|  | ||||
|         return new RevocationArtifactPaths(bundlePath, signaturePath, keyPath); | ||||
|     } | ||||
|  | ||||
|     private static async Task<(byte[] Bundle, string Signature, string KeyPem)> BuildRevocationArtifactsAsync(string? providerHint) | ||||
|     { | ||||
|         var bundleBytes = Encoding.UTF8.GetBytes("{\"revocations\":[]}"); | ||||
|  | ||||
|         using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256); | ||||
|         var parameters = ecdsa.ExportParameters(includePrivateParameters: true); | ||||
|  | ||||
|         var signingKey = new CryptoSigningKey( | ||||
|             new CryptoKeyReference("revocation-test"), | ||||
|             SignatureAlgorithms.Es256, | ||||
|             privateParameters: in parameters, | ||||
|             createdAt: DateTimeOffset.UtcNow); | ||||
|  | ||||
|         var provider = new DefaultCryptoProvider(); | ||||
|         provider.UpsertSigningKey(signingKey); | ||||
|         var signer = provider.GetSigner(SignatureAlgorithms.Es256, signingKey.Reference); | ||||
|  | ||||
|         var header = new Dictionary<string, object> | ||||
|         { | ||||
|             ["alg"] = SignatureAlgorithms.Es256, | ||||
|             ["kid"] = signingKey.Reference.KeyId, | ||||
|             ["typ"] = "application/vnd.stellaops.revocation-bundle+jws", | ||||
|             ["b64"] = false, | ||||
|             ["crit"] = new[] { "b64" } | ||||
|         }; | ||||
|  | ||||
|         if (!string.IsNullOrWhiteSpace(providerHint)) | ||||
|         { | ||||
|             header["provider"] = providerHint; | ||||
|         } | ||||
|  | ||||
|         var serializerOptions = new JsonSerializerOptions | ||||
|         { | ||||
|             PropertyNamingPolicy = null, | ||||
|             DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull | ||||
|         }; | ||||
|  | ||||
|         var headerJson = JsonSerializer.Serialize(header, serializerOptions); | ||||
|         var encodedHeader = Base64UrlEncoder.Encode(Encoding.UTF8.GetBytes(headerJson)); | ||||
|  | ||||
|         var signingInput = new byte[encodedHeader.Length + 1 + bundleBytes.Length]; | ||||
|         var headerBytes = Encoding.ASCII.GetBytes(encodedHeader); | ||||
|         Buffer.BlockCopy(headerBytes, 0, signingInput, 0, headerBytes.Length); | ||||
|         signingInput[headerBytes.Length] = (byte)'.'; | ||||
|         Buffer.BlockCopy(bundleBytes, 0, signingInput, headerBytes.Length + 1, bundleBytes.Length); | ||||
|  | ||||
|         var signatureBytes = await signer.SignAsync(signingInput); | ||||
|         var encodedSignature = Base64UrlEncoder.Encode(signatureBytes); | ||||
|         var jws = string.Concat(encodedHeader, "..", encodedSignature); | ||||
|  | ||||
|         var publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo(); | ||||
|         var keyPem = new string(PemEncoding.Write("PUBLIC KEY", publicKeyBytes)); | ||||
|  | ||||
|         return (bundleBytes, jws, keyPem); | ||||
|     } | ||||
|  | ||||
|     private sealed record RevocationArtifactPaths(string BundlePath, string SignaturePath, string KeyPath); | ||||
|  | ||||
|     private static IServiceProvider BuildServiceProvider( | ||||
|         IBackendOperationsClient backend, | ||||
|         IScannerExecutor? executor = null, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user