380 lines
11 KiB
C#
380 lines
11 KiB
C#
// -----------------------------------------------------------------------------
|
|
// VexGateOptions.cs
|
|
// Sprint: SPRINT_20260106_003_002_SCANNER_vex_gate_service
|
|
// Task: T028 - Add gate policy to tenant configuration
|
|
// Description: Configuration options for VEX gate, bindable from YAML/JSON config.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using System.Collections.Immutable;
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
namespace StellaOps.Scanner.Gate;
|
|
|
|
/// <summary>
|
|
/// Configuration options for VEX gate service.
|
|
/// Binds to "VexGate" section in configuration files.
|
|
/// </summary>
|
|
public sealed class VexGateOptions : IValidatableObject
|
|
{
|
|
/// <summary>
|
|
/// Configuration section name.
|
|
/// </summary>
|
|
public const string SectionName = "VexGate";
|
|
|
|
/// <summary>
|
|
/// Enable VEX-first gating. Default: false.
|
|
/// When disabled, all findings pass through to triage unchanged.
|
|
/// </summary>
|
|
public bool Enabled { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Default decision when no rules match. Default: Warn.
|
|
/// </summary>
|
|
public string DefaultDecision { get; set; } = "Warn";
|
|
|
|
/// <summary>
|
|
/// Policy version for audit/replay purposes.
|
|
/// Should be incremented when rules change.
|
|
/// </summary>
|
|
public string PolicyVersion { get; set; } = "1.0.0";
|
|
|
|
/// <summary>
|
|
/// Evaluation rules (ordered by priority, highest first).
|
|
/// </summary>
|
|
public List<VexGateRuleOptions> Rules { get; set; } = [];
|
|
|
|
/// <summary>
|
|
/// Caching settings for VEX observation lookups.
|
|
/// </summary>
|
|
public VexGateCacheOptions Cache { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// Audit logging settings.
|
|
/// </summary>
|
|
public VexGateAuditOptions Audit { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// Metrics settings.
|
|
/// </summary>
|
|
public VexGateMetricsOptions Metrics { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// Bypass settings for emergency scans.
|
|
/// </summary>
|
|
public VexGateBypassOptions Bypass { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// Converts this options instance to a VexGatePolicy.
|
|
/// </summary>
|
|
public VexGatePolicy ToPolicy()
|
|
{
|
|
var defaultDecision = ParseDecision(DefaultDecision);
|
|
var rules = Rules
|
|
.Select(r => r.ToRule())
|
|
.OrderByDescending(r => r.Priority)
|
|
.ToImmutableArray();
|
|
|
|
return new VexGatePolicy
|
|
{
|
|
DefaultDecision = defaultDecision,
|
|
Rules = rules,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates options from a VexGatePolicy.
|
|
/// </summary>
|
|
public static VexGateOptions FromPolicy(VexGatePolicy policy)
|
|
{
|
|
return new VexGateOptions
|
|
{
|
|
Enabled = true,
|
|
DefaultDecision = policy.DefaultDecision.ToString(),
|
|
Rules = policy.Rules.Select(r => VexGateRuleOptions.FromRule(r)).ToList(),
|
|
};
|
|
}
|
|
|
|
private static VexGateDecision ParseDecision(string value)
|
|
{
|
|
return value.ToUpperInvariant() switch
|
|
{
|
|
"PASS" => VexGateDecision.Pass,
|
|
"WARN" => VexGateDecision.Warn,
|
|
"BLOCK" => VexGateDecision.Block,
|
|
_ => VexGateDecision.Warn,
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
|
{
|
|
if (Enabled && Rules.Count == 0)
|
|
{
|
|
yield return new ValidationResult(
|
|
"At least one rule is required when VexGate is enabled",
|
|
[nameof(Rules)]);
|
|
}
|
|
|
|
var ruleIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var rule in Rules)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(rule.RuleId))
|
|
{
|
|
yield return new ValidationResult(
|
|
"Rule ID is required for all rules",
|
|
[nameof(Rules)]);
|
|
}
|
|
else if (!ruleIds.Add(rule.RuleId))
|
|
{
|
|
yield return new ValidationResult(
|
|
$"Duplicate rule ID: {rule.RuleId}",
|
|
[nameof(Rules)]);
|
|
}
|
|
}
|
|
|
|
if (Cache.TtlSeconds <= 0)
|
|
{
|
|
yield return new ValidationResult(
|
|
"Cache TTL must be positive",
|
|
[nameof(Cache)]);
|
|
}
|
|
|
|
if (Cache.MaxEntries <= 0)
|
|
{
|
|
yield return new ValidationResult(
|
|
"Cache max entries must be positive",
|
|
[nameof(Cache)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configuration options for a single VEX gate rule.
|
|
/// </summary>
|
|
public sealed class VexGateRuleOptions
|
|
{
|
|
/// <summary>
|
|
/// Unique identifier for this rule.
|
|
/// </summary>
|
|
[Required]
|
|
public string RuleId { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Priority order (higher values evaluated first).
|
|
/// </summary>
|
|
public int Priority { get; set; } = 0;
|
|
|
|
/// <summary>
|
|
/// Decision to apply when this rule matches.
|
|
/// </summary>
|
|
[Required]
|
|
public string Decision { get; set; } = "Warn";
|
|
|
|
/// <summary>
|
|
/// Condition that must match for this rule to apply.
|
|
/// </summary>
|
|
public VexGateConditionOptions Condition { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// Converts to a VexGatePolicyRule.
|
|
/// </summary>
|
|
public VexGatePolicyRule ToRule()
|
|
{
|
|
return new VexGatePolicyRule
|
|
{
|
|
RuleId = RuleId,
|
|
Priority = Priority,
|
|
Decision = ParseDecision(Decision),
|
|
Condition = Condition.ToCondition(),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates options from a VexGatePolicyRule.
|
|
/// </summary>
|
|
public static VexGateRuleOptions FromRule(VexGatePolicyRule rule)
|
|
{
|
|
return new VexGateRuleOptions
|
|
{
|
|
RuleId = rule.RuleId,
|
|
Priority = rule.Priority,
|
|
Decision = rule.Decision.ToString(),
|
|
Condition = VexGateConditionOptions.FromCondition(rule.Condition),
|
|
};
|
|
}
|
|
|
|
private static VexGateDecision ParseDecision(string value)
|
|
{
|
|
return value.ToUpperInvariant() switch
|
|
{
|
|
"PASS" => VexGateDecision.Pass,
|
|
"WARN" => VexGateDecision.Warn,
|
|
"BLOCK" => VexGateDecision.Block,
|
|
_ => VexGateDecision.Warn,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configuration options for a rule condition.
|
|
/// </summary>
|
|
public sealed class VexGateConditionOptions
|
|
{
|
|
/// <summary>
|
|
/// Required VEX vendor status.
|
|
/// Options: not_affected, fixed, affected, under_investigation.
|
|
/// </summary>
|
|
public string? VendorStatus { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether the vulnerability must be exploitable.
|
|
/// </summary>
|
|
public bool? IsExploitable { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether the vulnerable code must be reachable.
|
|
/// </summary>
|
|
public bool? IsReachable { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether compensating controls must be present.
|
|
/// </summary>
|
|
public bool? HasCompensatingControl { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether the CVE is in KEV (Known Exploited Vulnerabilities).
|
|
/// </summary>
|
|
public bool? IsKnownExploited { get; set; }
|
|
|
|
/// <summary>
|
|
/// Required severity levels (any match).
|
|
/// </summary>
|
|
public List<string>? SeverityLevels { get; set; }
|
|
|
|
/// <summary>
|
|
/// Minimum confidence score required.
|
|
/// </summary>
|
|
public double? ConfidenceThreshold { get; set; }
|
|
|
|
/// <summary>
|
|
/// Converts to a VexGatePolicyCondition.
|
|
/// </summary>
|
|
public VexGatePolicyCondition ToCondition()
|
|
{
|
|
return new VexGatePolicyCondition
|
|
{
|
|
VendorStatus = ParseVexStatus(VendorStatus),
|
|
IsExploitable = IsExploitable,
|
|
IsReachable = IsReachable,
|
|
HasCompensatingControl = HasCompensatingControl,
|
|
SeverityLevels = SeverityLevels?.ToArray(),
|
|
MinConfidence = ConfidenceThreshold,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates options from a VexGatePolicyCondition.
|
|
/// </summary>
|
|
public static VexGateConditionOptions FromCondition(VexGatePolicyCondition condition)
|
|
{
|
|
return new VexGateConditionOptions
|
|
{
|
|
VendorStatus = condition.VendorStatus?.ToString().ToLowerInvariant(),
|
|
IsExploitable = condition.IsExploitable,
|
|
IsReachable = condition.IsReachable,
|
|
HasCompensatingControl = condition.HasCompensatingControl,
|
|
SeverityLevels = condition.SeverityLevels?.ToList(),
|
|
ConfidenceThreshold = condition.MinConfidence,
|
|
};
|
|
}
|
|
|
|
private static VexStatus? ParseVexStatus(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
return null;
|
|
|
|
return value.ToLowerInvariant() switch
|
|
{
|
|
"not_affected" or "notaffected" => VexStatus.NotAffected,
|
|
"fixed" => VexStatus.Fixed,
|
|
"affected" => VexStatus.Affected,
|
|
"under_investigation" or "underinvestigation" => VexStatus.UnderInvestigation,
|
|
_ => null,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache configuration options.
|
|
/// </summary>
|
|
public sealed class VexGateCacheOptions
|
|
{
|
|
/// <summary>
|
|
/// TTL for cached VEX observations (seconds). Default: 300.
|
|
/// </summary>
|
|
public int TtlSeconds { get; set; } = 300;
|
|
|
|
/// <summary>
|
|
/// Maximum cache entries. Default: 10000.
|
|
/// </summary>
|
|
public int MaxEntries { get; set; } = 10000;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Audit logging configuration options.
|
|
/// </summary>
|
|
public sealed class VexGateAuditOptions
|
|
{
|
|
/// <summary>
|
|
/// Enable structured audit logging for compliance. Default: true.
|
|
/// </summary>
|
|
public bool Enabled { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Include full evidence in audit logs. Default: true.
|
|
/// </summary>
|
|
public bool IncludeEvidence { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Log level for gate decisions. Default: Information.
|
|
/// </summary>
|
|
public string LogLevel { get; set; } = "Information";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metrics configuration options.
|
|
/// </summary>
|
|
public sealed class VexGateMetricsOptions
|
|
{
|
|
/// <summary>
|
|
/// Enable OpenTelemetry metrics. Default: true.
|
|
/// </summary>
|
|
public bool Enabled { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Histogram buckets for evaluation latency (milliseconds).
|
|
/// </summary>
|
|
public List<double> LatencyBuckets { get; set; } = [1, 5, 10, 25, 50, 100, 250];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bypass configuration options.
|
|
/// </summary>
|
|
public sealed class VexGateBypassOptions
|
|
{
|
|
/// <summary>
|
|
/// Allow gate bypass via CLI flag (--bypass-gate). Default: true.
|
|
/// </summary>
|
|
public bool AllowCliBypass { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Require specific reason when bypassing. Default: false.
|
|
/// </summary>
|
|
public bool RequireReason { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Emit warning when bypass is used. Default: true.
|
|
/// </summary>
|
|
public bool WarnOnBypass { get; set; } = true;
|
|
}
|