Add unit tests for Router configuration and transport layers
- Implemented tests for RouterConfig, RoutingOptions, StaticInstanceConfig, and RouterConfigOptions to ensure default values are set correctly. - Added tests for RouterConfigProvider to validate configurations and ensure defaults are returned when no file is specified. - Created tests for ConfigValidationResult to check success and error scenarios. - Developed tests for ServiceCollectionExtensions to verify service registration for RouterConfig. - Introduced UdpTransportTests to validate serialization, connection, request-response, and error handling in UDP transport. - Added scripts for signing authority gaps and hashing DevPortal SDK snippets.
This commit is contained in:
@@ -74,6 +74,7 @@ internal static class CommandFactory
|
||||
root.Add(BuildApiCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(BuildSdkCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(BuildMirrorCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(BuildAirgapCommand(services, verboseOption, cancellationToken));
|
||||
|
||||
var pluginLogger = loggerFactory.CreateLogger<CliCommandModuleLoader>();
|
||||
var pluginLoader = new CliCommandModuleLoader(services, options, pluginLogger);
|
||||
@@ -4207,12 +4208,22 @@ internal static class CommandFactory
|
||||
{
|
||||
Description = "Output path for verification report."
|
||||
};
|
||||
var verifyFormatOption = new Option<string?>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format: table (default), json."
|
||||
};
|
||||
var verifyExplainOption = new Option<bool>("--explain")
|
||||
{
|
||||
Description = "Include detailed explanations for each verification check."
|
||||
};
|
||||
|
||||
verify.Add(envelopeOption);
|
||||
verify.Add(policyOption);
|
||||
verify.Add(rootOption);
|
||||
verify.Add(checkpointOption);
|
||||
verify.Add(verifyOutputOption);
|
||||
verify.Add(verifyFormatOption);
|
||||
verify.Add(verifyExplainOption);
|
||||
|
||||
verify.SetAction((parseResult, _) =>
|
||||
{
|
||||
@@ -4221,44 +4232,70 @@ internal static class CommandFactory
|
||||
var root = parseResult.GetValue(rootOption);
|
||||
var checkpoint = parseResult.GetValue(checkpointOption);
|
||||
var output = parseResult.GetValue(verifyOutputOption);
|
||||
var format = parseResult.GetValue(verifyFormatOption) ?? "table";
|
||||
var explain = parseResult.GetValue(verifyExplainOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAttestVerifyAsync(services, envelope, policy, root, checkpoint, output, verbose, cancellationToken);
|
||||
return CommandHandlers.HandleAttestVerifyAsync(services, envelope, policy, root, checkpoint, output, format, explain, verbose, cancellationToken);
|
||||
});
|
||||
|
||||
// attest list
|
||||
var list = new Command("list", "List attestations from the backend.");
|
||||
var tenantOption = new Option<string?>("--tenant")
|
||||
// attest list (CLI-ATTEST-74-001)
|
||||
var list = new Command("list", "List attestations from local storage or backend.");
|
||||
var listTenantOption = new Option<string?>("--tenant")
|
||||
{
|
||||
Description = "Tenant identifier to filter by."
|
||||
Description = "Filter by tenant identifier."
|
||||
};
|
||||
var issuerOption = new Option<string?>("--issuer")
|
||||
var listIssuerOption = new Option<string?>("--issuer")
|
||||
{
|
||||
Description = "Issuer identifier to filter by."
|
||||
Description = "Filter by issuer identifier."
|
||||
};
|
||||
var formatOption = new Option<string?>("--format", new[] { "-f" })
|
||||
var listSubjectOption = new Option<string?>("--subject", new[] { "-s" })
|
||||
{
|
||||
Description = "Output format (table, json)."
|
||||
Description = "Filter by subject (e.g., image digest, package PURL)."
|
||||
};
|
||||
var limitOption = new Option<int?>("--limit", new[] { "-n" })
|
||||
var listTypeOption = new Option<string?>("--type", new[] { "-t" })
|
||||
{
|
||||
Description = "Maximum number of results to return."
|
||||
Description = "Filter by predicate type URI."
|
||||
};
|
||||
var listScopeOption = new Option<string?>("--scope")
|
||||
{
|
||||
Description = "Filter by scope (local, remote, all). Default: all."
|
||||
};
|
||||
var listFormatOption = new Option<string?>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format (table, json). Default: table."
|
||||
};
|
||||
var listLimitOption = new Option<int?>("--limit", new[] { "-n" })
|
||||
{
|
||||
Description = "Maximum number of results to return. Default: 50."
|
||||
};
|
||||
var listOffsetOption = new Option<int?>("--offset")
|
||||
{
|
||||
Description = "Number of results to skip (for pagination). Default: 0."
|
||||
};
|
||||
|
||||
list.Add(tenantOption);
|
||||
list.Add(issuerOption);
|
||||
list.Add(formatOption);
|
||||
list.Add(limitOption);
|
||||
list.Add(listTenantOption);
|
||||
list.Add(listIssuerOption);
|
||||
list.Add(listSubjectOption);
|
||||
list.Add(listTypeOption);
|
||||
list.Add(listScopeOption);
|
||||
list.Add(listFormatOption);
|
||||
list.Add(listLimitOption);
|
||||
list.Add(listOffsetOption);
|
||||
|
||||
list.SetAction((parseResult, _) =>
|
||||
{
|
||||
var tenant = parseResult.GetValue(tenantOption);
|
||||
var issuer = parseResult.GetValue(issuerOption);
|
||||
var format = parseResult.GetValue(formatOption) ?? "table";
|
||||
var limit = parseResult.GetValue(limitOption);
|
||||
var tenant = parseResult.GetValue(listTenantOption);
|
||||
var issuer = parseResult.GetValue(listIssuerOption);
|
||||
var subject = parseResult.GetValue(listSubjectOption);
|
||||
var type = parseResult.GetValue(listTypeOption);
|
||||
var scope = parseResult.GetValue(listScopeOption) ?? "all";
|
||||
var format = parseResult.GetValue(listFormatOption) ?? "table";
|
||||
var limit = parseResult.GetValue(listLimitOption);
|
||||
var offset = parseResult.GetValue(listOffsetOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAttestListAsync(services, tenant, issuer, format, limit, verbose, cancellationToken);
|
||||
return CommandHandlers.HandleAttestListAsync(services, tenant, issuer, subject, type, scope, format, limit, offset, verbose, cancellationToken);
|
||||
});
|
||||
|
||||
// attest show
|
||||
@@ -4291,9 +4328,398 @@ internal static class CommandFactory
|
||||
return CommandHandlers.HandleAttestShowAsync(services, id, output, includeProof, verbose, cancellationToken);
|
||||
});
|
||||
|
||||
// attest sign (CLI-ATTEST-73-001)
|
||||
var sign = new Command("sign", "Create and sign a DSSE attestation envelope.");
|
||||
var predicateFileOption = new Option<string>("--predicate", new[] { "-p" })
|
||||
{
|
||||
Description = "Path to the predicate JSON file.",
|
||||
Required = true
|
||||
};
|
||||
var predicateTypeOption = new Option<string>("--predicate-type")
|
||||
{
|
||||
Description = "Predicate type URI (e.g., https://slsa.dev/provenance/v1).",
|
||||
Required = true
|
||||
};
|
||||
var subjectNameOption = new Option<string>("--subject")
|
||||
{
|
||||
Description = "Subject name or URI to attest.",
|
||||
Required = true
|
||||
};
|
||||
var subjectDigestOption = new Option<string>("--digest")
|
||||
{
|
||||
Description = "Subject digest in format algorithm:hex (e.g., sha256:abc123...).",
|
||||
Required = true
|
||||
};
|
||||
var signKeyOption = new Option<string?>("--key", new[] { "-k" })
|
||||
{
|
||||
Description = "Key identifier or path for signing."
|
||||
};
|
||||
var keylessOption = new Option<bool>("--keyless")
|
||||
{
|
||||
Description = "Use keyless (OIDC) signing via Sigstore Fulcio."
|
||||
};
|
||||
var transparencyLogOption = new Option<bool>("--rekor")
|
||||
{
|
||||
Description = "Submit attestation to Rekor transparency log (default: false)."
|
||||
};
|
||||
var noRekorOption = new Option<bool>("--no-rekor")
|
||||
{
|
||||
Description = "Explicitly skip Rekor submission."
|
||||
};
|
||||
var signOutputOption = new Option<string?>("--output", new[] { "-o" })
|
||||
{
|
||||
Description = "Output path for the signed DSSE envelope JSON."
|
||||
};
|
||||
var signFormatOption = new Option<string?>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format: dsse (default), sigstore-bundle."
|
||||
};
|
||||
|
||||
sign.Add(predicateFileOption);
|
||||
sign.Add(predicateTypeOption);
|
||||
sign.Add(subjectNameOption);
|
||||
sign.Add(subjectDigestOption);
|
||||
sign.Add(signKeyOption);
|
||||
sign.Add(keylessOption);
|
||||
sign.Add(transparencyLogOption);
|
||||
sign.Add(noRekorOption);
|
||||
sign.Add(signOutputOption);
|
||||
sign.Add(signFormatOption);
|
||||
|
||||
sign.SetAction((parseResult, _) =>
|
||||
{
|
||||
var predicatePath = parseResult.GetValue(predicateFileOption)!;
|
||||
var predicateType = parseResult.GetValue(predicateTypeOption)!;
|
||||
var subjectName = parseResult.GetValue(subjectNameOption)!;
|
||||
var digest = parseResult.GetValue(subjectDigestOption)!;
|
||||
var keyId = parseResult.GetValue(signKeyOption);
|
||||
var keyless = parseResult.GetValue(keylessOption);
|
||||
var useRekor = parseResult.GetValue(transparencyLogOption);
|
||||
var noRekor = parseResult.GetValue(noRekorOption);
|
||||
var output = parseResult.GetValue(signOutputOption);
|
||||
var format = parseResult.GetValue(signFormatOption) ?? "dsse";
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAttestSignAsync(
|
||||
services,
|
||||
predicatePath,
|
||||
predicateType,
|
||||
subjectName,
|
||||
digest,
|
||||
keyId,
|
||||
keyless,
|
||||
useRekor && !noRekor,
|
||||
output,
|
||||
format,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
// attest fetch (CLI-ATTEST-74-002)
|
||||
var fetch = new Command("fetch", "Download attestation envelopes and payloads to disk.");
|
||||
var fetchIdOption = new Option<string?>("--id")
|
||||
{
|
||||
Description = "Attestation ID to fetch."
|
||||
};
|
||||
var fetchSubjectOption = new Option<string?>("--subject", new[] { "-s" })
|
||||
{
|
||||
Description = "Subject filter (e.g., image digest, package PURL)."
|
||||
};
|
||||
var fetchTypeOption = new Option<string?>("--type", new[] { "-t" })
|
||||
{
|
||||
Description = "Predicate type filter."
|
||||
};
|
||||
var fetchOutputDirOption = new Option<string>("--output-dir", new[] { "-o" })
|
||||
{
|
||||
Description = "Output directory for downloaded files.",
|
||||
Required = true
|
||||
};
|
||||
var fetchIncludeOption = new Option<string?>("--include")
|
||||
{
|
||||
Description = "What to download: envelope, payload, both (default: both)."
|
||||
};
|
||||
var fetchScopeOption = new Option<string?>("--scope")
|
||||
{
|
||||
Description = "Source scope: local, remote, all (default: all)."
|
||||
};
|
||||
var fetchFormatOption = new Option<string?>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format for payloads: json (default), raw."
|
||||
};
|
||||
var fetchOverwriteOption = new Option<bool>("--overwrite")
|
||||
{
|
||||
Description = "Overwrite existing files."
|
||||
};
|
||||
|
||||
fetch.Add(fetchIdOption);
|
||||
fetch.Add(fetchSubjectOption);
|
||||
fetch.Add(fetchTypeOption);
|
||||
fetch.Add(fetchOutputDirOption);
|
||||
fetch.Add(fetchIncludeOption);
|
||||
fetch.Add(fetchScopeOption);
|
||||
fetch.Add(fetchFormatOption);
|
||||
fetch.Add(fetchOverwriteOption);
|
||||
|
||||
fetch.SetAction((parseResult, _) =>
|
||||
{
|
||||
var id = parseResult.GetValue(fetchIdOption);
|
||||
var subject = parseResult.GetValue(fetchSubjectOption);
|
||||
var type = parseResult.GetValue(fetchTypeOption);
|
||||
var outputDir = parseResult.GetValue(fetchOutputDirOption)!;
|
||||
var include = parseResult.GetValue(fetchIncludeOption) ?? "both";
|
||||
var scope = parseResult.GetValue(fetchScopeOption) ?? "all";
|
||||
var format = parseResult.GetValue(fetchFormatOption) ?? "json";
|
||||
var overwrite = parseResult.GetValue(fetchOverwriteOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAttestFetchAsync(
|
||||
services,
|
||||
id,
|
||||
subject,
|
||||
type,
|
||||
outputDir,
|
||||
include,
|
||||
scope,
|
||||
format,
|
||||
overwrite,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
// attest key (CLI-ATTEST-75-001)
|
||||
var key = new Command("key", "Manage attestation signing keys.");
|
||||
|
||||
// attest key create
|
||||
var keyCreate = new Command("create", "Create a new signing key for attestations.");
|
||||
var keyNameOption = new Option<string>("--name", new[] { "-n" })
|
||||
{
|
||||
Description = "Key identifier/name.",
|
||||
Required = true
|
||||
};
|
||||
var keyAlgorithmOption = new Option<string?>("--algorithm", new[] { "-a" })
|
||||
{
|
||||
Description = "Key algorithm: ECDSA-P256 (default), ECDSA-P384."
|
||||
};
|
||||
var keyPasswordOption = new Option<string?>("--password", new[] { "-p" })
|
||||
{
|
||||
Description = "Password to protect the key (required for file-based keys)."
|
||||
};
|
||||
var keyOutputOption = new Option<string?>("--output", new[] { "-o" })
|
||||
{
|
||||
Description = "Output path for the key directory (default: ~/.stellaops/keys)."
|
||||
};
|
||||
var keyFormatOption = new Option<string?>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format: table (default), json."
|
||||
};
|
||||
var keyExportPublicOption = new Option<bool>("--export-public")
|
||||
{
|
||||
Description = "Export public key to file alongside key creation."
|
||||
};
|
||||
|
||||
keyCreate.Add(keyNameOption);
|
||||
keyCreate.Add(keyAlgorithmOption);
|
||||
keyCreate.Add(keyPasswordOption);
|
||||
keyCreate.Add(keyOutputOption);
|
||||
keyCreate.Add(keyFormatOption);
|
||||
keyCreate.Add(keyExportPublicOption);
|
||||
|
||||
keyCreate.SetAction((parseResult, _) =>
|
||||
{
|
||||
var name = parseResult.GetValue(keyNameOption)!;
|
||||
var algorithm = parseResult.GetValue(keyAlgorithmOption) ?? "ECDSA-P256";
|
||||
var password = parseResult.GetValue(keyPasswordOption);
|
||||
var output = parseResult.GetValue(keyOutputOption);
|
||||
var format = parseResult.GetValue(keyFormatOption) ?? "table";
|
||||
var exportPublic = parseResult.GetValue(keyExportPublicOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAttestKeyCreateAsync(
|
||||
services,
|
||||
name,
|
||||
algorithm,
|
||||
password,
|
||||
output,
|
||||
format,
|
||||
exportPublic,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
key.Add(keyCreate);
|
||||
|
||||
// attest bundle (CLI-ATTEST-75-002)
|
||||
var bundle = new Command("bundle", "Build and verify attestation bundles.");
|
||||
|
||||
// attest bundle build
|
||||
var bundleBuild = new Command("build", "Build an audit bundle from artifacts (attestations, SBOMs, VEX, scans).");
|
||||
var bundleSubjectNameOption = new Option<string>("--subject-name", new[] { "-s" })
|
||||
{
|
||||
Description = "Primary subject name (e.g., image reference).",
|
||||
Required = true
|
||||
};
|
||||
var bundleSubjectDigestOption = new Option<string>("--subject-digest", new[] { "-d" })
|
||||
{
|
||||
Description = "Subject digest in algorithm:hex format (e.g., sha256:abc123...).",
|
||||
Required = true
|
||||
};
|
||||
var bundleSubjectTypeOption = new Option<string?>("--subject-type")
|
||||
{
|
||||
Description = "Subject type: IMAGE (default), REPO, SBOM, OTHER."
|
||||
};
|
||||
var bundleInputDirOption = new Option<string>("--input", new[] { "-i" })
|
||||
{
|
||||
Description = "Input directory containing artifacts to bundle.",
|
||||
Required = true
|
||||
};
|
||||
var bundleOutputOption = new Option<string>("--output", new[] { "-o" })
|
||||
{
|
||||
Description = "Output path for the bundle (directory or .tar.gz file).",
|
||||
Required = true
|
||||
};
|
||||
var bundleFromOption = new Option<string?>("--from")
|
||||
{
|
||||
Description = "Start of time window for artifacts (ISO-8601)."
|
||||
};
|
||||
var bundleToOption = new Option<string?>("--to")
|
||||
{
|
||||
Description = "End of time window for artifacts (ISO-8601)."
|
||||
};
|
||||
var bundleIncludeOption = new Option<string?>("--include")
|
||||
{
|
||||
Description = "Artifact types to include: attestations,sboms,vex,scans,policy,all (default: all)."
|
||||
};
|
||||
var bundleCompressOption = new Option<bool>("--compress")
|
||||
{
|
||||
Description = "Compress output as tar.gz."
|
||||
};
|
||||
var bundleCreatorIdOption = new Option<string?>("--creator-id")
|
||||
{
|
||||
Description = "Creator user ID (default: current user)."
|
||||
};
|
||||
var bundleCreatorNameOption = new Option<string?>("--creator-name")
|
||||
{
|
||||
Description = "Creator display name (default: current user)."
|
||||
};
|
||||
var bundleFormatOption = new Option<string?>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format: table (default), json."
|
||||
};
|
||||
|
||||
bundleBuild.Add(bundleSubjectNameOption);
|
||||
bundleBuild.Add(bundleSubjectDigestOption);
|
||||
bundleBuild.Add(bundleSubjectTypeOption);
|
||||
bundleBuild.Add(bundleInputDirOption);
|
||||
bundleBuild.Add(bundleOutputOption);
|
||||
bundleBuild.Add(bundleFromOption);
|
||||
bundleBuild.Add(bundleToOption);
|
||||
bundleBuild.Add(bundleIncludeOption);
|
||||
bundleBuild.Add(bundleCompressOption);
|
||||
bundleBuild.Add(bundleCreatorIdOption);
|
||||
bundleBuild.Add(bundleCreatorNameOption);
|
||||
bundleBuild.Add(bundleFormatOption);
|
||||
|
||||
bundleBuild.SetAction((parseResult, _) =>
|
||||
{
|
||||
var subjectName = parseResult.GetValue(bundleSubjectNameOption)!;
|
||||
var subjectDigest = parseResult.GetValue(bundleSubjectDigestOption)!;
|
||||
var subjectType = parseResult.GetValue(bundleSubjectTypeOption) ?? "IMAGE";
|
||||
var inputDir = parseResult.GetValue(bundleInputDirOption)!;
|
||||
var output = parseResult.GetValue(bundleOutputOption)!;
|
||||
var from = parseResult.GetValue(bundleFromOption);
|
||||
var to = parseResult.GetValue(bundleToOption);
|
||||
var include = parseResult.GetValue(bundleIncludeOption) ?? "all";
|
||||
var compress = parseResult.GetValue(bundleCompressOption);
|
||||
var creatorId = parseResult.GetValue(bundleCreatorIdOption);
|
||||
var creatorName = parseResult.GetValue(bundleCreatorNameOption);
|
||||
var format = parseResult.GetValue(bundleFormatOption) ?? "table";
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAttestBundleBuildAsync(
|
||||
services,
|
||||
subjectName,
|
||||
subjectDigest,
|
||||
subjectType,
|
||||
inputDir,
|
||||
output,
|
||||
from,
|
||||
to,
|
||||
include,
|
||||
compress,
|
||||
creatorId,
|
||||
creatorName,
|
||||
format,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
// attest bundle verify
|
||||
var bundleVerify = new Command("verify", "Verify an attestation bundle's integrity and signatures.");
|
||||
var bundleVerifyInputOption = new Option<string>("--input", new[] { "-i" })
|
||||
{
|
||||
Description = "Input bundle path (directory or .tar.gz file).",
|
||||
Required = true
|
||||
};
|
||||
var bundleVerifyPolicyOption = new Option<string?>("--policy")
|
||||
{
|
||||
Description = "Policy file for attestation verification (JSON with requiredPredicateTypes, minimumSignatures, etc.)."
|
||||
};
|
||||
var bundleVerifyRootOption = new Option<string?>("--root")
|
||||
{
|
||||
Description = "Trust root file (PEM certificate or public key) for signature verification."
|
||||
};
|
||||
var bundleVerifyOutputOption = new Option<string?>("--output", new[] { "-o" })
|
||||
{
|
||||
Description = "Write verification report to file (JSON format)."
|
||||
};
|
||||
var bundleVerifyFormatOption = new Option<string?>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format: table (default), json."
|
||||
};
|
||||
var bundleVerifyStrictOption = new Option<bool>("--strict")
|
||||
{
|
||||
Description = "Treat warnings as errors (exit code 1 on any issue)."
|
||||
};
|
||||
|
||||
bundleVerify.Add(bundleVerifyInputOption);
|
||||
bundleVerify.Add(bundleVerifyPolicyOption);
|
||||
bundleVerify.Add(bundleVerifyRootOption);
|
||||
bundleVerify.Add(bundleVerifyOutputOption);
|
||||
bundleVerify.Add(bundleVerifyFormatOption);
|
||||
bundleVerify.Add(bundleVerifyStrictOption);
|
||||
|
||||
bundleVerify.SetAction((parseResult, _) =>
|
||||
{
|
||||
var input = parseResult.GetValue(bundleVerifyInputOption)!;
|
||||
var policy = parseResult.GetValue(bundleVerifyPolicyOption);
|
||||
var root = parseResult.GetValue(bundleVerifyRootOption);
|
||||
var output = parseResult.GetValue(bundleVerifyOutputOption);
|
||||
var format = parseResult.GetValue(bundleVerifyFormatOption) ?? "table";
|
||||
var strict = parseResult.GetValue(bundleVerifyStrictOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAttestBundleVerifyAsync(
|
||||
services,
|
||||
input,
|
||||
policy,
|
||||
root,
|
||||
output,
|
||||
format,
|
||||
strict,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
bundle.Add(bundleBuild);
|
||||
bundle.Add(bundleVerify);
|
||||
|
||||
attest.Add(sign);
|
||||
attest.Add(verify);
|
||||
attest.Add(list);
|
||||
attest.Add(show);
|
||||
attest.Add(fetch);
|
||||
attest.Add(key);
|
||||
attest.Add(bundle);
|
||||
|
||||
return attest;
|
||||
}
|
||||
@@ -9835,4 +10261,238 @@ internal static class CommandFactory
|
||||
|
||||
return mirror;
|
||||
}
|
||||
|
||||
private static Command BuildAirgapCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken)
|
||||
{
|
||||
var airgap = new Command("airgap", "Manage air-gapped environment operations.");
|
||||
|
||||
// airgap import (CLI-AIRGAP-57-001)
|
||||
var import = new Command("import", "Import an air-gap mirror bundle into the local data store.");
|
||||
|
||||
var bundlePathOption = new Option<string>("--bundle", new[] { "-b" })
|
||||
{
|
||||
Description = "Path to the bundle directory (contains manifest.json and artifacts).",
|
||||
Required = true
|
||||
};
|
||||
|
||||
var importTenantOption = new Option<string?>("--tenant")
|
||||
{
|
||||
Description = "Import data under a specific tenant scope."
|
||||
};
|
||||
|
||||
var globalOption = new Option<bool>("--global")
|
||||
{
|
||||
Description = "Import data to the global scope (requires elevated permissions)."
|
||||
};
|
||||
|
||||
var dryRunOption = new Option<bool>("--dry-run")
|
||||
{
|
||||
Description = "Preview the import without making changes."
|
||||
};
|
||||
|
||||
var forceOption = new Option<bool>("--force")
|
||||
{
|
||||
Description = "Force import even if checksums have been verified before."
|
||||
};
|
||||
|
||||
var verifyOnlyOption = new Option<bool>("--verify-only")
|
||||
{
|
||||
Description = "Verify bundle integrity without importing."
|
||||
};
|
||||
|
||||
var importJsonOption = new Option<bool>("--json")
|
||||
{
|
||||
Description = "Output results in JSON format."
|
||||
};
|
||||
|
||||
import.Add(bundlePathOption);
|
||||
import.Add(importTenantOption);
|
||||
import.Add(globalOption);
|
||||
import.Add(dryRunOption);
|
||||
import.Add(forceOption);
|
||||
import.Add(verifyOnlyOption);
|
||||
import.Add(importJsonOption);
|
||||
|
||||
import.SetAction((parseResult, _) =>
|
||||
{
|
||||
var bundlePath = parseResult.GetValue(bundlePathOption)!;
|
||||
var tenant = parseResult.GetValue(importTenantOption);
|
||||
var global = parseResult.GetValue(globalOption);
|
||||
var dryRun = parseResult.GetValue(dryRunOption);
|
||||
var force = parseResult.GetValue(forceOption);
|
||||
var verifyOnly = parseResult.GetValue(verifyOnlyOption);
|
||||
var json = parseResult.GetValue(importJsonOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAirgapImportAsync(
|
||||
services,
|
||||
bundlePath,
|
||||
tenant,
|
||||
global,
|
||||
dryRun,
|
||||
force,
|
||||
verifyOnly,
|
||||
json,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
airgap.Add(import);
|
||||
|
||||
// airgap seal (CLI-AIRGAP-57-002)
|
||||
var seal = new Command("seal", "Seal the environment for air-gapped operation.");
|
||||
|
||||
var sealConfigDirOption = new Option<string?>("--config-dir", new[] { "-c" })
|
||||
{
|
||||
Description = "Path to the configuration directory (defaults to ~/.stellaops)."
|
||||
};
|
||||
|
||||
var sealVerifyOption = new Option<bool>("--verify")
|
||||
{
|
||||
Description = "Verify imported bundles before sealing."
|
||||
};
|
||||
|
||||
var sealForceOption = new Option<bool>("--force")
|
||||
{
|
||||
Description = "Force seal even if verification warnings exist."
|
||||
};
|
||||
|
||||
var sealDryRunOption = new Option<bool>("--dry-run")
|
||||
{
|
||||
Description = "Preview the seal operation without making changes."
|
||||
};
|
||||
|
||||
var sealJsonOption = new Option<bool>("--json")
|
||||
{
|
||||
Description = "Output results in JSON format."
|
||||
};
|
||||
|
||||
var sealReasonOption = new Option<string?>("--reason")
|
||||
{
|
||||
Description = "Reason for sealing (recorded in audit log)."
|
||||
};
|
||||
|
||||
seal.Add(sealConfigDirOption);
|
||||
seal.Add(sealVerifyOption);
|
||||
seal.Add(sealForceOption);
|
||||
seal.Add(sealDryRunOption);
|
||||
seal.Add(sealJsonOption);
|
||||
seal.Add(sealReasonOption);
|
||||
|
||||
seal.SetAction((parseResult, _) =>
|
||||
{
|
||||
var configDir = parseResult.GetValue(sealConfigDirOption);
|
||||
var verify = parseResult.GetValue(sealVerifyOption);
|
||||
var force = parseResult.GetValue(sealForceOption);
|
||||
var dryRun = parseResult.GetValue(sealDryRunOption);
|
||||
var json = parseResult.GetValue(sealJsonOption);
|
||||
var reason = parseResult.GetValue(sealReasonOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAirgapSealAsync(
|
||||
services,
|
||||
configDir,
|
||||
verify,
|
||||
force,
|
||||
dryRun,
|
||||
json,
|
||||
reason,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
airgap.Add(seal);
|
||||
|
||||
// airgap export-evidence (CLI-AIRGAP-58-001)
|
||||
var exportEvidence = new Command("export-evidence", "Export portable evidence packages for audit and compliance.");
|
||||
|
||||
var evidenceOutputOption = new Option<string>("--output", new[] { "-o" })
|
||||
{
|
||||
Description = "Output directory for the evidence package.",
|
||||
Required = true
|
||||
};
|
||||
|
||||
var evidenceIncludeOption = new Option<string[]>("--include", new[] { "-i" })
|
||||
{
|
||||
Description = "Evidence types to include: attestations, sboms, scans, vex, all (default: all).",
|
||||
AllowMultipleArgumentsPerToken = true
|
||||
};
|
||||
|
||||
var evidenceFromOption = new Option<DateTimeOffset?>("--from")
|
||||
{
|
||||
Description = "Include evidence from this date (UTC, ISO-8601)."
|
||||
};
|
||||
|
||||
var evidenceToOption = new Option<DateTimeOffset?>("--to")
|
||||
{
|
||||
Description = "Include evidence up to this date (UTC, ISO-8601)."
|
||||
};
|
||||
|
||||
var evidenceTenantOption = new Option<string?>("--tenant")
|
||||
{
|
||||
Description = "Export evidence for a specific tenant."
|
||||
};
|
||||
|
||||
var evidenceSubjectOption = new Option<string?>("--subject")
|
||||
{
|
||||
Description = "Filter evidence by subject (e.g., image digest, package PURL)."
|
||||
};
|
||||
|
||||
var evidenceCompressOption = new Option<bool>("--compress")
|
||||
{
|
||||
Description = "Compress the output package as a .tar.gz archive."
|
||||
};
|
||||
|
||||
var evidenceJsonOption = new Option<bool>("--json")
|
||||
{
|
||||
Description = "Output results in JSON format."
|
||||
};
|
||||
|
||||
var evidenceVerifyOption = new Option<bool>("--verify")
|
||||
{
|
||||
Description = "Verify evidence signatures before export."
|
||||
};
|
||||
|
||||
exportEvidence.Add(evidenceOutputOption);
|
||||
exportEvidence.Add(evidenceIncludeOption);
|
||||
exportEvidence.Add(evidenceFromOption);
|
||||
exportEvidence.Add(evidenceToOption);
|
||||
exportEvidence.Add(evidenceTenantOption);
|
||||
exportEvidence.Add(evidenceSubjectOption);
|
||||
exportEvidence.Add(evidenceCompressOption);
|
||||
exportEvidence.Add(evidenceJsonOption);
|
||||
exportEvidence.Add(evidenceVerifyOption);
|
||||
|
||||
exportEvidence.SetAction((parseResult, _) =>
|
||||
{
|
||||
var output = parseResult.GetValue(evidenceOutputOption)!;
|
||||
var include = parseResult.GetValue(evidenceIncludeOption) ?? Array.Empty<string>();
|
||||
var from = parseResult.GetValue(evidenceFromOption);
|
||||
var to = parseResult.GetValue(evidenceToOption);
|
||||
var tenant = parseResult.GetValue(evidenceTenantOption);
|
||||
var subject = parseResult.GetValue(evidenceSubjectOption);
|
||||
var compress = parseResult.GetValue(evidenceCompressOption);
|
||||
var json = parseResult.GetValue(evidenceJsonOption);
|
||||
var verify = parseResult.GetValue(evidenceVerifyOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleAirgapExportEvidenceAsync(
|
||||
services,
|
||||
output,
|
||||
include,
|
||||
from,
|
||||
to,
|
||||
tenant,
|
||||
subject,
|
||||
compress,
|
||||
json,
|
||||
verify,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
airgap.Add(exportEvidence);
|
||||
|
||||
return airgap;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
207
src/Cli/StellaOps.Cli/Services/Models/AttestorTransportModels.cs
Normal file
207
src/Cli/StellaOps.Cli/Services/Models/AttestorTransportModels.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-ATTEST-73-001: Attestor SDK transport contract models
|
||||
// Based on docs/schemas/attestor-transport.schema.json
|
||||
|
||||
/// <summary>
|
||||
/// Request to create an attestation.
|
||||
/// </summary>
|
||||
internal sealed class AttestationRequest
|
||||
{
|
||||
[JsonPropertyName("requestType")]
|
||||
public string RequestType { get; init; } = "CREATE_ATTESTATION";
|
||||
|
||||
[JsonPropertyName("requestId")]
|
||||
public string RequestId { get; init; } = Guid.NewGuid().ToString();
|
||||
|
||||
[JsonPropertyName("correlationId")]
|
||||
public string? CorrelationId { get; init; }
|
||||
|
||||
[JsonPropertyName("predicateType")]
|
||||
public string PredicateType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("subject")]
|
||||
public IReadOnlyList<AttestationSubjectDto> Subject { get; init; } = Array.Empty<AttestationSubjectDto>();
|
||||
|
||||
[JsonPropertyName("predicate")]
|
||||
public object Predicate { get; init; } = new { };
|
||||
|
||||
[JsonPropertyName("signingOptions")]
|
||||
public SigningOptionsDto? SigningOptions { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for attestation creation.
|
||||
/// </summary>
|
||||
internal sealed class AttestationResponseDto
|
||||
{
|
||||
[JsonPropertyName("responseType")]
|
||||
public string ResponseType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("requestId")]
|
||||
public string RequestId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("attestation")]
|
||||
public AttestationEnvelopeDto? Attestation { get; init; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public AttestationErrorDto? Error { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subject for attestation.
|
||||
/// </summary>
|
||||
internal sealed class AttestationSubjectDto
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public Dictionary<string, string> Digest { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signing options for attestation.
|
||||
/// </summary>
|
||||
internal sealed class SigningOptionsDto
|
||||
{
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("provider")]
|
||||
public string? Provider { get; init; }
|
||||
|
||||
[JsonPropertyName("algorithm")]
|
||||
public string? Algorithm { get; init; }
|
||||
|
||||
[JsonPropertyName("transparencyLog")]
|
||||
public bool TransparencyLog { get; init; }
|
||||
|
||||
[JsonPropertyName("timestampAuthority")]
|
||||
public string? TimestampAuthority { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE attestation envelope from response.
|
||||
/// </summary>
|
||||
internal sealed class AttestationEnvelopeDto
|
||||
{
|
||||
[JsonPropertyName("payloadType")]
|
||||
public string PayloadType { get; init; } = "application/vnd.in-toto+json";
|
||||
|
||||
[JsonPropertyName("payload")]
|
||||
public string Payload { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public IReadOnlyList<DsseSignatureDto> Signatures { get; init; } = Array.Empty<DsseSignatureDto>();
|
||||
|
||||
[JsonPropertyName("envelopeDigest")]
|
||||
public string? EnvelopeDigest { get; init; }
|
||||
|
||||
[JsonPropertyName("transparencyLogEntry")]
|
||||
public TransparencyLogEntryDto? TransparencyLogEntry { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE signature.
|
||||
/// </summary>
|
||||
internal sealed class DsseSignatureDto
|
||||
{
|
||||
[JsonPropertyName("keyid")]
|
||||
public string KeyId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sig")]
|
||||
public string Sig { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transparency log entry from Rekor.
|
||||
/// </summary>
|
||||
internal sealed class TransparencyLogEntryDto
|
||||
{
|
||||
[JsonPropertyName("logIndex")]
|
||||
public long LogIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("logId")]
|
||||
public string? LogId { get; init; }
|
||||
|
||||
[JsonPropertyName("integratedTime")]
|
||||
public DateTimeOffset? IntegratedTime { get; init; }
|
||||
|
||||
[JsonPropertyName("inclusionProof")]
|
||||
public string? InclusionProof { get; init; }
|
||||
|
||||
[JsonPropertyName("entryUri")]
|
||||
public string? EntryUri { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error from attestation operation.
|
||||
/// </summary>
|
||||
internal sealed class AttestationErrorDto
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("details")]
|
||||
public Dictionary<string, object>? Details { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for the attest sign command.
|
||||
/// </summary>
|
||||
internal sealed class AttestSignOptions
|
||||
{
|
||||
public string PredicatePath { get; init; } = string.Empty;
|
||||
public string PredicateType { get; init; } = string.Empty;
|
||||
public string SubjectName { get; init; } = string.Empty;
|
||||
public string SubjectDigest { get; init; } = string.Empty;
|
||||
public string? KeyId { get; init; }
|
||||
public bool Keyless { get; init; }
|
||||
public bool UseRekor { get; init; }
|
||||
public string? OutputPath { get; init; }
|
||||
public string Format { get; init; } = "dsse";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result from attest sign command.
|
||||
/// </summary>
|
||||
internal sealed class AttestSignResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("envelopePath")]
|
||||
public string? EnvelopePath { get; init; }
|
||||
|
||||
[JsonPropertyName("envelopeDigest")]
|
||||
public string? EnvelopeDigest { get; init; }
|
||||
|
||||
[JsonPropertyName("predicateType")]
|
||||
public string PredicateType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("subjectName")]
|
||||
public string SubjectName { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("subjectDigest")]
|
||||
public string SubjectDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("transparencyLogEntry")]
|
||||
public TransparencyLogEntryDto? TransparencyLogEntry { get; init; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
@@ -58,6 +58,8 @@
|
||||
<ProjectReference Include="../../Policy/StellaOps.PolicyDsl/StellaOps.PolicyDsl.csproj" />
|
||||
<ProjectReference Include="../../Policy/__Libraries/StellaOps.Policy/StellaOps.Policy.csproj" />
|
||||
<ProjectReference Include="../../Policy/StellaOps.Policy.RiskProfile/StellaOps.Policy.RiskProfile.csproj" />
|
||||
<ProjectReference Include="../../Attestor/StellaOps.Attestation/StellaOps.Attestation.csproj" />
|
||||
<ProjectReference Include="../../Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">
|
||||
|
||||
@@ -19,6 +19,12 @@ internal static class CliMetrics
|
||||
/// </summary>
|
||||
public static string SealedModePhaseLabel { get; set; } = "AirGapped-Phase-1";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a metric tag (KeyValuePair) for use with counters/histograms.
|
||||
/// </summary>
|
||||
private static KeyValuePair<string, object?> Tag(string key, object? value)
|
||||
=> new(key, value);
|
||||
|
||||
/// <summary>
|
||||
/// Appends sealed mode tags to the given tags array if in sealed mode.
|
||||
/// </summary>
|
||||
@@ -56,102 +62,116 @@ internal static class CliMetrics
|
||||
private static readonly Counter<long> RubyResolveCounter = Meter.CreateCounter<long>("stellaops.cli.ruby.resolve.count");
|
||||
private static readonly Counter<long> PhpInspectCounter = Meter.CreateCounter<long>("stellaops.cli.php.inspect.count");
|
||||
private static readonly Counter<long> PythonInspectCounter = Meter.CreateCounter<long>("stellaops.cli.python.inspect.count");
|
||||
private static readonly Counter<long> AttestSignCounter = Meter.CreateCounter<long>("stellaops.cli.attest.sign.count");
|
||||
private static readonly Counter<long> AttestVerifyCounter = Meter.CreateCounter<long>("stellaops.cli.attest.verify.count");
|
||||
private static readonly Histogram<double> CommandDurationHistogram = Meter.CreateHistogram<double>("stellaops.cli.command.duration.ms");
|
||||
|
||||
public static void RecordScannerDownload(string channel, bool fromCache)
|
||||
=> ScannerDownloadCounter.Add(1, WithSealedModeTag(
|
||||
new("channel", channel),
|
||||
new("cache", fromCache ? "hit" : "miss")));
|
||||
Tag("channel", channel),
|
||||
Tag("cache", fromCache ? "hit" : "miss")));
|
||||
|
||||
public static void RecordScannerInstall(string channel)
|
||||
=> ScannerInstallCounter.Add(1, WithSealedModeTag(new("channel", channel)));
|
||||
=> ScannerInstallCounter.Add(1, WithSealedModeTag(Tag("channel", channel)));
|
||||
|
||||
public static void RecordScanRun(string runner, int exitCode)
|
||||
=> ScanRunCounter.Add(1, WithSealedModeTag(
|
||||
new("runner", runner),
|
||||
new("exit_code", exitCode)));
|
||||
Tag("runner", runner),
|
||||
Tag("exit_code", exitCode)));
|
||||
|
||||
public static void RecordOfflineKitDownload(string kind, bool fromCache)
|
||||
=> OfflineKitDownloadCounter.Add(1, WithSealedModeTag(
|
||||
new("kind", string.IsNullOrWhiteSpace(kind) ? "unknown" : kind),
|
||||
new("cache", fromCache ? "hit" : "miss")));
|
||||
Tag("kind", string.IsNullOrWhiteSpace(kind) ? "unknown" : kind),
|
||||
Tag("cache", fromCache ? "hit" : "miss")));
|
||||
|
||||
public static void RecordOfflineKitImport(string? status)
|
||||
=> OfflineKitImportCounter.Add(1, WithSealedModeTag(
|
||||
new("status", string.IsNullOrWhiteSpace(status) ? "queued" : status)));
|
||||
Tag("status", string.IsNullOrWhiteSpace(status) ? "queued" : status)));
|
||||
|
||||
public static void RecordPolicySimulation(string outcome)
|
||||
=> PolicySimulationCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordTaskRunnerSimulation(string outcome)
|
||||
=> TaskRunnerSimulationCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordPolicyActivation(string outcome)
|
||||
=> PolicyActivationCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordAdvisoryRun(string taskType, string outcome)
|
||||
=> AdvisoryRunCounter.Add(1, WithSealedModeTag(
|
||||
new("task", string.IsNullOrWhiteSpace(taskType) ? "unknown" : taskType.ToLowerInvariant()),
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("task", string.IsNullOrWhiteSpace(taskType) ? "unknown" : taskType.ToLowerInvariant()),
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordSourcesDryRun(string status)
|
||||
=> SourcesDryRunCounter.Add(1, WithSealedModeTag(
|
||||
new("status", string.IsNullOrWhiteSpace(status) ? "unknown" : status)));
|
||||
Tag("status", string.IsNullOrWhiteSpace(status) ? "unknown" : status)));
|
||||
|
||||
public static void RecordAocVerify(string outcome)
|
||||
=> AocVerifyCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordPolicyFindingsList(string outcome)
|
||||
=> PolicyFindingsListCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordPolicyFindingsGet(string outcome)
|
||||
=> PolicyFindingsGetCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordPolicyFindingsExplain(string outcome)
|
||||
=> PolicyFindingsExplainCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordNodeLockValidate(string outcome)
|
||||
=> NodeLockValidateCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordPythonLockValidate(string outcome)
|
||||
=> PythonLockValidateCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordJavaLockValidate(string outcome)
|
||||
=> JavaLockValidateCounter.Add(1, WithSealedModeTag(
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordRubyInspect(string outcome)
|
||||
=> RubyInspectCounter.Add(1, new KeyValuePair<string, object?>[]
|
||||
{
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
|
||||
});
|
||||
=> RubyInspectCounter.Add(1, WithSealedModeTag(
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordRubyResolve(string outcome)
|
||||
=> RubyResolveCounter.Add(1, new KeyValuePair<string, object?>[]
|
||||
{
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
|
||||
});
|
||||
=> RubyResolveCounter.Add(1, WithSealedModeTag(
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordPhpInspect(string outcome)
|
||||
=> PhpInspectCounter.Add(1, new KeyValuePair<string, object?>[]
|
||||
{
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
|
||||
});
|
||||
=> PhpInspectCounter.Add(1, WithSealedModeTag(
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static void RecordPythonInspect(string outcome)
|
||||
=> PythonInspectCounter.Add(1, new KeyValuePair<string, object?>[]
|
||||
{
|
||||
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
|
||||
});
|
||||
=> PythonInspectCounter.Add(1, WithSealedModeTag(
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
/// <summary>
|
||||
/// Records a successful attestation signing operation (CLI-ATTEST-73-001).
|
||||
/// </summary>
|
||||
/// <param name="predicateType">The predicate type URI.</param>
|
||||
/// <param name="signingMode">The signing mode (keyed, keyless).</param>
|
||||
/// <param name="rekorSubmitted">Whether the attestation was submitted to Rekor.</param>
|
||||
public static void AttestSignCompleted(string predicateType, string signingMode, bool rekorSubmitted)
|
||||
=> AttestSignCounter.Add(1, WithSealedModeTag(
|
||||
Tag("predicate_type", string.IsNullOrWhiteSpace(predicateType) ? "unknown" : predicateType),
|
||||
Tag("signing_mode", string.IsNullOrWhiteSpace(signingMode) ? "unknown" : signingMode),
|
||||
Tag("rekor_submitted", rekorSubmitted.ToString().ToLowerInvariant())));
|
||||
|
||||
/// <summary>
|
||||
/// Records an attestation verification operation.
|
||||
/// </summary>
|
||||
/// <param name="outcome">The verification outcome.</param>
|
||||
public static void RecordAttestVerify(string outcome)
|
||||
=> AttestVerifyCounter.Add(1, WithSealedModeTag(
|
||||
Tag("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
|
||||
|
||||
public static IDisposable MeasureCommandDuration(string command)
|
||||
{
|
||||
@@ -180,7 +200,7 @@ internal static class CliMetrics
|
||||
|
||||
_disposed = true;
|
||||
var elapsed = (DateTime.UtcNow - _start).TotalMilliseconds;
|
||||
CommandDurationHistogram.Record(elapsed, new KeyValuePair<string, object?>[] { new("command", _command) });
|
||||
CommandDurationHistogram.Record(elapsed, WithSealedModeTag(Tag("command", _command)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user