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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user