up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-28 00:45:16 +02:00
parent 3b96b2e3ea
commit 1c6730a1d2
95 changed files with 14504 additions and 463 deletions

View File

@@ -2205,6 +2205,237 @@ internal static class CommandHandlers
}
}
public static async Task HandleTenantsListAsync(
IServiceProvider services,
StellaOpsCliOptions options,
string? tenant,
bool json,
bool verbose,
CancellationToken cancellationToken)
{
await using var scope = services.CreateAsyncScope();
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("tenants-list");
Environment.ExitCode = 0;
if (string.IsNullOrWhiteSpace(options.Authority?.Url))
{
logger.LogError("Authority URL is not configured. Set STELLAOPS_AUTHORITY_URL or update your configuration.");
Environment.ExitCode = 1;
return;
}
var client = scope.ServiceProvider.GetService<IAuthorityConsoleClient>();
if (client is null)
{
logger.LogError("Authority console client is not available. Ensure Authority is configured and services are registered.");
Environment.ExitCode = 1;
return;
}
var effectiveTenant = TenantProfileStore.GetEffectiveTenant(tenant);
if (string.IsNullOrWhiteSpace(effectiveTenant))
{
logger.LogError("Tenant context is required. Provide --tenant, set STELLAOPS_TENANT environment variable, or run 'stella tenants use <tenant-id>'.");
Environment.ExitCode = 1;
return;
}
try
{
var tenants = await client.ListTenantsAsync(effectiveTenant, cancellationToken).ConfigureAwait(false);
if (json)
{
var output = new { tenants = tenants };
var jsonText = JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonText);
}
else
{
if (tenants.Count == 0)
{
logger.LogInformation("No tenants available for the authenticated principal.");
return;
}
logger.LogInformation("Available tenants ({Count}):", tenants.Count);
foreach (var t in tenants)
{
var status = string.Equals(t.Status, "active", StringComparison.OrdinalIgnoreCase) ? "" : $" ({t.Status})";
logger.LogInformation(" {Id}: {DisplayName}{Status}", t.Id, t.DisplayName, status);
if (verbose)
{
logger.LogInformation(" Isolation: {IsolationMode}", t.IsolationMode);
if (t.DefaultRoles.Count > 0)
{
logger.LogInformation(" Default roles: {Roles}", string.Join(", ", t.DefaultRoles));
}
if (t.Projects.Count > 0)
{
logger.LogInformation(" Projects: {Projects}", string.Join(", ", t.Projects));
}
}
}
}
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
logger.LogError("Authentication required. Run 'stella auth login' first.");
Environment.ExitCode = 1;
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
logger.LogError("Access denied. The authenticated principal does not have permission to list tenants.");
Environment.ExitCode = 1;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to retrieve tenant list: {Message}", ex.Message);
Environment.ExitCode = 1;
}
}
public static async Task HandleTenantsUseAsync(
IServiceProvider services,
StellaOpsCliOptions options,
string tenantId,
bool verbose,
CancellationToken cancellationToken)
{
await using var scope = services.CreateAsyncScope();
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("tenants-use");
Environment.ExitCode = 0;
if (string.IsNullOrWhiteSpace(tenantId))
{
logger.LogError("Tenant identifier is required.");
Environment.ExitCode = 1;
return;
}
var normalizedTenant = tenantId.Trim().ToLowerInvariant();
string? displayName = null;
if (!string.IsNullOrWhiteSpace(options.Authority?.Url))
{
var client = scope.ServiceProvider.GetService<IAuthorityConsoleClient>();
if (client is not null)
{
try
{
var tenants = await client.ListTenantsAsync(normalizedTenant, cancellationToken).ConfigureAwait(false);
var match = tenants.FirstOrDefault(t =>
string.Equals(t.Id, normalizedTenant, StringComparison.OrdinalIgnoreCase));
if (match is not null)
{
displayName = match.DisplayName;
if (verbose)
{
logger.LogDebug("Validated tenant '{TenantId}' with display name '{DisplayName}'.", normalizedTenant, displayName);
}
}
else if (verbose)
{
logger.LogWarning("Tenant '{TenantId}' not found in available tenants. Setting anyway.", normalizedTenant);
}
}
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
{
if (verbose)
{
logger.LogWarning("Could not validate tenant against Authority: {Message}", ex.Message);
}
}
}
}
try
{
await TenantProfileStore.SetActiveTenantAsync(normalizedTenant, displayName, cancellationToken).ConfigureAwait(false);
logger.LogInformation("Active tenant set to '{TenantId}'.", normalizedTenant);
if (!string.IsNullOrWhiteSpace(displayName))
{
logger.LogInformation("Tenant display name: {DisplayName}", displayName);
}
logger.LogInformation("Profile saved to: {Path}", TenantProfileStore.GetProfilePath());
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to save tenant profile: {Message}", ex.Message);
Environment.ExitCode = 1;
}
}
public static async Task HandleTenantsCurrentAsync(
bool json,
bool verbose,
CancellationToken cancellationToken)
{
Environment.ExitCode = 0;
try
{
var profile = await TenantProfileStore.LoadAsync(cancellationToken).ConfigureAwait(false);
if (json)
{
var output = profile ?? new TenantProfile();
var jsonText = JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonText);
return;
}
if (profile is null || string.IsNullOrWhiteSpace(profile.ActiveTenant))
{
Console.WriteLine("No active tenant configured.");
Console.WriteLine("Use 'stella tenants use <tenant-id>' to set one.");
return;
}
Console.WriteLine($"Active tenant: {profile.ActiveTenant}");
if (!string.IsNullOrWhiteSpace(profile.ActiveTenantDisplayName))
{
Console.WriteLine($"Display name: {profile.ActiveTenantDisplayName}");
}
if (profile.LastUpdated.HasValue)
{
Console.WriteLine($"Last updated: {profile.LastUpdated.Value:u}");
}
if (verbose)
{
Console.WriteLine($"Profile path: {TenantProfileStore.GetProfilePath()}");
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"Failed to load tenant profile: {ex.Message}");
Environment.ExitCode = 1;
}
}
public static async Task HandleTenantsClearAsync(CancellationToken cancellationToken)
{
Environment.ExitCode = 0;
try
{
await TenantProfileStore.ClearActiveTenantAsync(cancellationToken).ConfigureAwait(false);
Console.WriteLine("Active tenant cleared.");
Console.WriteLine("Subsequent commands will require --tenant or STELLAOPS_TENANT environment variable.");
}
catch (Exception ex)
{
Console.Error.WriteLine($"Failed to clear tenant profile: {ex.Message}");
Environment.ExitCode = 1;
}
}
public static async Task HandleVulnObservationsAsync(
IServiceProvider services,
string tenant,
@@ -8315,6 +8546,21 @@ internal static class CommandHandlers
diag.Code ?? "-",
diag.Path ?? "-",
Markup.Escape(diag.Message));
}
AnsiConsole.Write(table);
}
}
return result.Success ? ExitSuccess : ExitValidationError;
}
catch (Exception ex)
{
AnsiConsole.MarkupLine("[red]Error:[/] {0}", Markup.Escape(ex.Message));
return ExitInputError;
}
}
#region Risk Profile Commands
public static async Task HandleRiskProfileValidateAsync(
@@ -8417,16 +8663,12 @@ internal static class CommandHandlers
}
}
return result.Success ? ExitSuccess : ExitValidationError;
Environment.ExitCode = result.IsValid ? 0 : 1;
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($"[red]Error:[/] {Markup.Escape(ex.Message)}");
if (verbose)
{
AnsiConsole.WriteException(ex);
}
return ExitInputError;
AnsiConsole.MarkupLine("[red]Error:[/] {0}", Markup.Escape(ex.Message));
Environment.ExitCode = 1;
}
}
@@ -8919,33 +9161,6 @@ internal static class CommandHandlers
public JsonElement? ExpectedFindings { get; set; }
}
if (!string.IsNullOrEmpty(outputPath))
{
var reportJson = JsonSerializer.Serialize(report, new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
});
await File.WriteAllTextAsync(outputPath, reportJson).ConfigureAwait(false);
AnsiConsole.MarkupLine("Validation report written to [cyan]{0}[/]", Markup.Escape(outputPath));
}
}
Environment.ExitCode = result.IsValid ? 0 : (strict ? 1 : 0);
if (!result.IsValid && !strict)
{
Environment.ExitCode = 1;
}
}
catch (Exception ex)
{
AnsiConsole.MarkupLine("[red]Error:[/] {0}", Markup.Escape(ex.Message));
Environment.ExitCode = 1;
}
await Task.CompletedTask.ConfigureAwait(false);
}
public static async Task HandleRiskProfileSchemaAsync(string? outputPath, bool verbose)
{
_ = verbose;