feat(api): Add Policy Registry API specification
Some checks failed
AOC Guard CI / aoc-verify (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
mock-dev-release / package-mock-release (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-verify (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
mock-dev-release / package-mock-release (push) Has been cancelled
- Introduced OpenAPI specification for the StellaOps Policy Registry API, covering endpoints for verification policies, policy packs, snapshots, violations, overrides, sealed mode operations, and advisory staleness tracking. - Defined schemas, parameters, and responses for comprehensive API documentation. chore(scanner): Add global usings for scanner analyzers - Created GlobalUsings.cs to simplify namespace usage across analyzer libraries. feat(scanner): Implement Surface Service Collection Extensions - Added SurfaceServiceCollectionExtensions for dependency injection registration of surface analysis services. - Included methods for adding surface analysis, surface collectors, and entry point collectors to the service collection.
This commit is contained in:
369
docs/contracts/authority-crypto-provider.md
Normal file
369
docs/contracts/authority-crypto-provider.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# Authority Crypto Provider Contract
|
||||
|
||||
> **Status:** APPROVED
|
||||
> **Version:** 1.0.0
|
||||
> **Last Updated:** 2025-12-06
|
||||
> **Owner:** Authority Core Guild
|
||||
> **Unblocks:** AUTH-CRYPTO-90-001, SEC-CRYPTO-90-014, SCANNER-CRYPTO-90-001, ATTESTOR-CRYPTO-90-001
|
||||
|
||||
## Overview
|
||||
|
||||
This contract defines the Authority signing provider interface for StellaOps, enabling pluggable cryptographic backends including:
|
||||
- **Software keys** (default) — ECDSA P-256/P-384, RSA, EdDSA
|
||||
- **HSM integration** — PKCS#11, Cloud KMS (AWS, GCP, Azure)
|
||||
- **Regional compliance** — CryptoPro GOST (R1), SM2/SM3 (CN), eIDAS (EU), FIPS 140-2
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Authority Crypto Provider │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────┐│
|
||||
│ │ ISigningProvider Interface ││
|
||||
│ │ ││
|
||||
│ │ + Sign(data: byte[], keyId: string) → SignatureResult ││
|
||||
│ │ + Verify(data: byte[], signature: byte[], keyId: string) → bool ││
|
||||
│ │ + GetPublicKey(keyId: string) → PublicKeyInfo ││
|
||||
│ │ + ListKeys(filter: KeyFilter) → KeyInfo[] ││
|
||||
│ │ + CreateKey(spec: KeySpec) → KeyInfo ││
|
||||
│ │ + RotateKey(keyId: string) → KeyInfo ││
|
||||
│ │ + ExportJWKS(keyIds: string[]) → JWKS ││
|
||||
│ └─────────────────────────────────────────────────────────────────────────┘│
|
||||
│ │ │
|
||||
│ ┌────────────────────┼────────────────────┐ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Software │ │ PKCS#11 │ │ Cloud KMS │ │
|
||||
│ │ Provider │ │ Provider │ │ Provider │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ • File keys │ │ • HSM │ │ • AWS KMS │ │
|
||||
│ │ • Memory │ │ • SmartCard │ │ • GCP KMS │ │
|
||||
│ │ • Vault │ │ • CryptoPro │ │ • Azure KV │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 1. ISigningProvider Interface
|
||||
|
||||
### 1.1 Core Methods
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Pluggable cryptographic signing provider for Authority service.
|
||||
/// </summary>
|
||||
public interface ISigningProvider
|
||||
{
|
||||
/// <summary>Provider identifier (e.g., "software", "pkcs11", "aws-kms")</summary>
|
||||
string ProviderId { get; }
|
||||
|
||||
/// <summary>Supported algorithms by this provider</summary>
|
||||
IReadOnlyList<string> SupportedAlgorithms { get; }
|
||||
|
||||
/// <summary>Sign data with the specified key</summary>
|
||||
Task<SignatureResult> SignAsync(
|
||||
byte[] data,
|
||||
string keyId,
|
||||
SigningOptions? options = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>Verify a signature</summary>
|
||||
Task<bool> VerifyAsync(
|
||||
byte[] data,
|
||||
byte[] signature,
|
||||
string keyId,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>Get public key information</summary>
|
||||
Task<PublicKeyInfo> GetPublicKeyAsync(
|
||||
string keyId,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>List available keys</summary>
|
||||
Task<IReadOnlyList<KeyInfo>> ListKeysAsync(
|
||||
KeyFilter? filter = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>Create a new key pair</summary>
|
||||
Task<KeyInfo> CreateKeyAsync(
|
||||
KeySpec spec,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>Rotate a key (create new version)</summary>
|
||||
Task<KeyInfo> RotateKeyAsync(
|
||||
string keyId,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>Export keys as JWKS for distributed verification</summary>
|
||||
Task<JsonWebKeySet> ExportJwksAsync(
|
||||
IEnumerable<string>? keyIds = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>Import a public key for verification</summary>
|
||||
Task<KeyInfo> ImportPublicKeyAsync(
|
||||
byte[] keyData,
|
||||
string format,
|
||||
KeyMetadata? metadata = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Supporting Types
|
||||
|
||||
```csharp
|
||||
public record SignatureResult(
|
||||
byte[] Signature,
|
||||
string Algorithm,
|
||||
string KeyId,
|
||||
string? KeyVersion,
|
||||
DateTimeOffset Timestamp);
|
||||
|
||||
public record SigningOptions(
|
||||
string? Algorithm = null,
|
||||
bool IncludeTimestamp = true,
|
||||
string? Nonce = null);
|
||||
|
||||
public record PublicKeyInfo(
|
||||
string KeyId,
|
||||
string Algorithm,
|
||||
byte[] PublicKey,
|
||||
string Format, // "PEM", "DER", "JWK"
|
||||
string? Fingerprint,
|
||||
DateTimeOffset? ExpiresAt);
|
||||
|
||||
public record KeyInfo(
|
||||
string KeyId,
|
||||
string Algorithm,
|
||||
KeyState State,
|
||||
DateTimeOffset CreatedAt,
|
||||
DateTimeOffset? ExpiresAt,
|
||||
string? CurrentVersion,
|
||||
IReadOnlyDictionary<string, string>? Metadata);
|
||||
|
||||
public enum KeyState
|
||||
{
|
||||
Active,
|
||||
Disabled,
|
||||
PendingDeletion,
|
||||
Deleted
|
||||
}
|
||||
|
||||
public record KeySpec(
|
||||
string Algorithm,
|
||||
int? KeySize = null,
|
||||
string? Purpose = null, // "signing", "attestation", "authority"
|
||||
IReadOnlyDictionary<string, string>? Metadata = null,
|
||||
DateTimeOffset? ExpiresAt = null);
|
||||
|
||||
public record KeyFilter(
|
||||
string? Purpose = null,
|
||||
KeyState? State = null,
|
||||
string? Algorithm = null);
|
||||
```
|
||||
|
||||
## 2. Supported Algorithms
|
||||
|
||||
### 2.1 Algorithm Registry
|
||||
|
||||
| Algorithm | OID | Key Size | Compliance | Provider Support |
|
||||
|-----------|-----|----------|------------|------------------|
|
||||
| **ES256** | 1.2.840.10045.4.3.2 | P-256 | FIPS, eIDAS | All |
|
||||
| **ES384** | 1.2.840.10045.4.3.3 | P-384 | FIPS, eIDAS | All |
|
||||
| **RS256** | 1.2.840.113549.1.1.11 | 2048+ | FIPS, eIDAS | All |
|
||||
| **RS384** | 1.2.840.113549.1.1.12 | 2048+ | FIPS, eIDAS | All |
|
||||
| **EdDSA** | 1.3.101.112 | Ed25519 | — | Software, some HSM |
|
||||
| **PS256** | 1.2.840.113549.1.1.10 | 2048+ | FIPS | All |
|
||||
| **GOST R 34.10-2012** | 1.2.643.7.1.1.1.1 | 256/512 | R1 | PKCS#11 (CryptoPro) |
|
||||
| **SM2** | 1.2.156.10197.1.301 | 256 | CN | PKCS#11 |
|
||||
|
||||
### 2.2 Default Configuration
|
||||
|
||||
```yaml
|
||||
# etc/authority.yaml
|
||||
crypto:
|
||||
provider: software # or: pkcs11, aws-kms, gcp-kms, azure-keyvault
|
||||
|
||||
software:
|
||||
keys_path: /var/lib/stellaops/keys
|
||||
default_algorithm: ES256
|
||||
|
||||
pkcs11:
|
||||
library_path: /usr/lib/libpkcs11.so
|
||||
slot_id: 0
|
||||
pin_env: AUTHORITY_HSM_PIN
|
||||
# For CryptoPro:
|
||||
# library_path: /opt/cprocsp/lib/amd64/libcapi20.so
|
||||
|
||||
aws_kms:
|
||||
region: us-east-1
|
||||
key_alias_prefix: stellaops/
|
||||
|
||||
azure_keyvault:
|
||||
vault_url: https://stellaops.vault.azure.net/
|
||||
|
||||
gcp_kms:
|
||||
project: stellaops-prod
|
||||
location: global
|
||||
key_ring: attestation-keys
|
||||
|
||||
# Regional compliance overrides
|
||||
compliance:
|
||||
ru:
|
||||
provider: pkcs11
|
||||
algorithms: [GOST-R-34.10-2012-256, GOST-R-34.10-2012-512]
|
||||
library_path: /opt/cprocsp/lib/amd64/libcapi20.so
|
||||
cn:
|
||||
provider: pkcs11
|
||||
algorithms: [SM2]
|
||||
```
|
||||
|
||||
## 3. JWKS Export Requirements
|
||||
|
||||
### 3.1 JWKS Endpoint
|
||||
|
||||
The Authority service MUST expose a JWKS endpoint for distributed verification:
|
||||
|
||||
```
|
||||
GET /.well-known/jwks.json
|
||||
```
|
||||
|
||||
Response format:
|
||||
|
||||
```json
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"x": "base64url-encoded-x",
|
||||
"y": "base64url-encoded-y",
|
||||
"kid": "attestation-key-001",
|
||||
"alg": "ES256",
|
||||
"use": "sig",
|
||||
"key_ops": ["verify"],
|
||||
"x5t#S256": "sha256-fingerprint"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Key Rotation
|
||||
|
||||
When keys are rotated:
|
||||
1. New key becomes `Active`, old key becomes `Disabled` (verification-only)
|
||||
2. JWKS includes both keys during transition period
|
||||
3. Old key removed after `rotation_grace_period` (default: 7 days)
|
||||
4. All consuming services refresh JWKS on schedule or via webhook
|
||||
|
||||
### 3.3 Key Discovery Flow
|
||||
|
||||
```
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Scanner │ │ Authority │ │ Attestor │
|
||||
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
||||
│ │ │
|
||||
│ GET /jwks.json│ │
|
||||
│───────────────>│ │
|
||||
│<───────────────│ │
|
||||
│ JWKS │ │
|
||||
│ │ │
|
||||
│ Sign(SBOM) │ │
|
||||
│───────────────>│ │
|
||||
│<───────────────│ │
|
||||
│ Signature │ │
|
||||
│ │ │
|
||||
│ │ GET /jwks.json │
|
||||
│ │<────────────────│
|
||||
│ │────────────────>│
|
||||
│ │ JWKS │
|
||||
│ │ │
|
||||
│ │ Verify(SBOM) │
|
||||
│ │<────────────────│
|
||||
│ │ ✓ Valid │
|
||||
```
|
||||
|
||||
## 4. Provider Registration
|
||||
|
||||
### 4.1 Service Registration
|
||||
|
||||
```csharp
|
||||
// Program.cs
|
||||
services.AddAuthoritySigningProvider(options =>
|
||||
{
|
||||
options.Provider = configuration["Crypto:Provider"];
|
||||
options.Configuration = configuration.GetSection("Crypto");
|
||||
});
|
||||
|
||||
// Extension method
|
||||
public static IServiceCollection AddAuthoritySigningProvider(
|
||||
this IServiceCollection services,
|
||||
Action<CryptoProviderOptions> configure)
|
||||
{
|
||||
var options = new CryptoProviderOptions();
|
||||
configure(options);
|
||||
|
||||
return options.Provider switch
|
||||
{
|
||||
"software" => services.AddSingleton<ISigningProvider, SoftwareSigningProvider>(),
|
||||
"pkcs11" => services.AddSingleton<ISigningProvider, Pkcs11SigningProvider>(),
|
||||
"aws-kms" => services.AddSingleton<ISigningProvider, AwsKmsSigningProvider>(),
|
||||
"gcp-kms" => services.AddSingleton<ISigningProvider, GcpKmsSigningProvider>(),
|
||||
"azure-keyvault" => services.AddSingleton<ISigningProvider, AzureKeyVaultSigningProvider>(),
|
||||
_ => throw new ArgumentException($"Unknown provider: {options.Provider}")
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Regional Provider Registry
|
||||
|
||||
For multi-region deployments with compliance requirements:
|
||||
|
||||
```yaml
|
||||
# Regional key registry
|
||||
key_registry:
|
||||
attestation-sbom:
|
||||
default:
|
||||
key_id: "stellaops/attestation-sbom-001"
|
||||
algorithm: ES256
|
||||
provider: aws-kms
|
||||
ru:
|
||||
key_id: "ru/attestation-sbom-gost"
|
||||
algorithm: GOST-R-34.10-2012-256
|
||||
provider: pkcs11
|
||||
cn:
|
||||
key_id: "cn/attestation-sbom-sm2"
|
||||
algorithm: SM2
|
||||
provider: pkcs11
|
||||
```
|
||||
|
||||
## 5. Error Codes
|
||||
|
||||
| Code | Name | Description |
|
||||
|------|------|-------------|
|
||||
| `CRYPTO_001` | `KEY_NOT_FOUND` | Requested key does not exist |
|
||||
| `CRYPTO_002` | `KEY_DISABLED` | Key is disabled and cannot sign |
|
||||
| `CRYPTO_003` | `ALGORITHM_UNSUPPORTED` | Algorithm not supported by provider |
|
||||
| `CRYPTO_004` | `HSM_UNAVAILABLE` | HSM/PKCS#11 device not available |
|
||||
| `CRYPTO_005` | `SIGNATURE_FAILED` | Signing operation failed |
|
||||
| `CRYPTO_006` | `VERIFICATION_FAILED` | Signature verification failed |
|
||||
| `CRYPTO_007` | `KEY_EXPIRED` | Key has expired |
|
||||
| `CRYPTO_008` | `COMPLIANCE_VIOLATION` | Algorithm not allowed by compliance profile |
|
||||
|
||||
## 6. Tasks Unblocked
|
||||
|
||||
This contract unblocks:
|
||||
|
||||
| Task ID | Description | Status |
|
||||
|---------|-------------|--------|
|
||||
| AUTH-CRYPTO-90-001 | Authority signing provider contract | ✅ UNBLOCKED |
|
||||
| SEC-CRYPTO-90-014 | Security Guild crypto integration | ✅ UNBLOCKED |
|
||||
| SCANNER-CRYPTO-90-001 | Scanner SBOM signing | ✅ UNBLOCKED |
|
||||
| ATTESTOR-CRYPTO-90-001 | Attestor DSSE signing | ✅ UNBLOCKED |
|
||||
|
||||
## 7. Changelog
|
||||
|
||||
| Date | Version | Change |
|
||||
|------|---------|--------|
|
||||
| 2025-12-06 | 1.0.0 | Initial contract with interface, algorithms, JWKS, regional support |
|
||||
425
docs/contracts/sealed-install-enforcement.md
Normal file
425
docs/contracts/sealed-install-enforcement.md
Normal file
@@ -0,0 +1,425 @@
|
||||
# Sealed Install Enforcement Contract
|
||||
|
||||
> **Status:** APPROVED
|
||||
> **Version:** 1.0.0
|
||||
> **Last Updated:** 2025-12-06
|
||||
> **Owner:** AirGap Controller Guild
|
||||
> **Unblocks:** TASKRUN-AIRGAP-57-001, TASKRUN-AIRGAP-58-001
|
||||
|
||||
## Overview
|
||||
|
||||
This contract defines the sealed install enforcement semantics for StellaOps air-gapped deployments. When a pack or task declares `sealed_install: true`, the Task Runner MUST refuse to execute if the environment is not properly sealed.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Sealed Install Enforcement Flow │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Task Pack │ │ Task Runner │ │ AirGap │ │
|
||||
│ │ │────>│ │────>│ Controller │ │
|
||||
│ │ sealed_ │ │ Enforcement │ │ │ │
|
||||
│ │ install:true │ │ Check │ │ /status │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ Decision Matrix │ │
|
||||
│ │ │ │
|
||||
│ │ Pack: sealed Env: sealed │ │
|
||||
│ │ ────────────── ──────────── │ │
|
||||
│ │ true true → RUN │ │
|
||||
│ │ true false → DENY │ │
|
||||
│ │ false true → RUN │ │
|
||||
│ │ false false → RUN │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 1. Pack Declaration
|
||||
|
||||
### 1.1 Sealed Install Flag
|
||||
|
||||
Packs declare their sealed requirement in the pack manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"pack_id": "compliance-scan-airgap",
|
||||
"version": "1.0.0",
|
||||
"name": "Air-Gap Compliance Scanner",
|
||||
"sealed_install": true,
|
||||
"sealed_requirements": {
|
||||
"min_bundle_version": "2025.10.0",
|
||||
"max_advisory_staleness_hours": 168,
|
||||
"require_time_anchor": true,
|
||||
"allowed_offline_duration_hours": 720
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Sealed Requirements Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sealed_install": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, pack MUST run in sealed environment"
|
||||
},
|
||||
"sealed_requirements": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"min_bundle_version": {
|
||||
"type": "string",
|
||||
"description": "Minimum air-gap bundle version"
|
||||
},
|
||||
"max_advisory_staleness_hours": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"default": 168,
|
||||
"description": "Maximum age of advisory data in hours"
|
||||
},
|
||||
"require_time_anchor": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Require valid time anchor"
|
||||
},
|
||||
"allowed_offline_duration_hours": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"default": 720,
|
||||
"description": "Maximum allowed offline duration"
|
||||
},
|
||||
"require_signature_verification": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Require bundle signature verification"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Environment Detection
|
||||
|
||||
### 2.1 Sealed Mode Status API
|
||||
|
||||
The Task Runner queries the AirGap Controller to determine sealed status:
|
||||
|
||||
```
|
||||
GET /api/v1/airgap/status
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"sealed": true,
|
||||
"mode": "sealed",
|
||||
"sealed_at": "2025-12-01T00:00:00Z",
|
||||
"sealed_by": "ops-admin@company.com",
|
||||
"bundle_version": "2025.10.0",
|
||||
"bundle_digest": "sha256:abc123...",
|
||||
"last_advisory_update": "2025-12-01T00:00:00Z",
|
||||
"advisory_staleness_hours": 120,
|
||||
"time_anchor": {
|
||||
"timestamp": "2025-12-01T00:00:00Z",
|
||||
"signature": "base64...",
|
||||
"valid": true,
|
||||
"expires_at": "2025-12-31T00:00:00Z"
|
||||
},
|
||||
"egress_blocked": true,
|
||||
"network_policy": "deny-all"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Detection Heuristics
|
||||
|
||||
If the AirGap Controller is unavailable, the Task Runner uses fallback heuristics:
|
||||
|
||||
| Heuristic | Weight | Indicates |
|
||||
|-----------|--------|-----------|
|
||||
| No external DNS resolution | High | Sealed |
|
||||
| Blocked ports 80, 443 | High | Sealed |
|
||||
| AIRGAP_MODE=sealed env var | High | Sealed |
|
||||
| /etc/stellaops/sealed file exists | Medium | Sealed |
|
||||
| No internet connectivity | Medium | Sealed |
|
||||
| Local-only registry configured | Low | Sealed |
|
||||
|
||||
Combined heuristic score threshold: **0.7** to consider environment sealed.
|
||||
|
||||
## 3. Enforcement Logic
|
||||
|
||||
### 3.1 Pre-Execution Check
|
||||
|
||||
```csharp
|
||||
public sealed class SealedInstallEnforcer
|
||||
{
|
||||
public async Task<EnforcementResult> EnforceAsync(
|
||||
TaskPack pack,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// If pack doesn't require sealed install, allow
|
||||
if (!pack.SealedInstall)
|
||||
{
|
||||
return EnforcementResult.Allowed("Pack does not require sealed install");
|
||||
}
|
||||
|
||||
// Get environment sealed status
|
||||
var status = await _airgapController.GetStatusAsync(ct);
|
||||
|
||||
// Core check: environment must be sealed
|
||||
if (!status.Sealed)
|
||||
{
|
||||
return EnforcementResult.Denied(
|
||||
"SEALED_INSTALL_VIOLATION",
|
||||
"Pack requires sealed environment but environment is not sealed",
|
||||
new SealedInstallViolation
|
||||
{
|
||||
PackId = pack.PackId,
|
||||
RequiredSealed = true,
|
||||
ActualSealed = false,
|
||||
Recommendation = "Activate sealed mode with: stella airgap seal"
|
||||
});
|
||||
}
|
||||
|
||||
// Check sealed requirements
|
||||
if (pack.SealedRequirements != null)
|
||||
{
|
||||
var violations = ValidateRequirements(pack.SealedRequirements, status);
|
||||
if (violations.Any())
|
||||
{
|
||||
return EnforcementResult.Denied(
|
||||
"SEALED_REQUIREMENTS_VIOLATION",
|
||||
"Sealed requirements not met",
|
||||
violations);
|
||||
}
|
||||
}
|
||||
|
||||
return EnforcementResult.Allowed("Sealed install requirements satisfied");
|
||||
}
|
||||
|
||||
private List<RequirementViolation> ValidateRequirements(
|
||||
SealedRequirements requirements,
|
||||
SealedModeStatus status)
|
||||
{
|
||||
var violations = new List<RequirementViolation>();
|
||||
|
||||
// Bundle version check
|
||||
if (requirements.MinBundleVersion != null)
|
||||
{
|
||||
if (Version.Parse(status.BundleVersion) < Version.Parse(requirements.MinBundleVersion))
|
||||
{
|
||||
violations.Add(new RequirementViolation
|
||||
{
|
||||
Requirement = "min_bundle_version",
|
||||
Expected = requirements.MinBundleVersion,
|
||||
Actual = status.BundleVersion,
|
||||
Message = $"Bundle version {status.BundleVersion} < required {requirements.MinBundleVersion}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Advisory staleness check
|
||||
if (status.AdvisoryStalenessHours > requirements.MaxAdvisoryStalenessHours)
|
||||
{
|
||||
violations.Add(new RequirementViolation
|
||||
{
|
||||
Requirement = "max_advisory_staleness_hours",
|
||||
Expected = requirements.MaxAdvisoryStalenessHours.ToString(),
|
||||
Actual = status.AdvisoryStalenessHours.ToString(),
|
||||
Message = $"Advisory data is {status.AdvisoryStalenessHours}h old, max allowed is {requirements.MaxAdvisoryStalenessHours}h"
|
||||
});
|
||||
}
|
||||
|
||||
// Time anchor check
|
||||
if (requirements.RequireTimeAnchor && (status.TimeAnchor == null || !status.TimeAnchor.Valid))
|
||||
{
|
||||
violations.Add(new RequirementViolation
|
||||
{
|
||||
Requirement = "require_time_anchor",
|
||||
Expected = "valid time anchor",
|
||||
Actual = status.TimeAnchor?.Valid.ToString() ?? "missing",
|
||||
Message = "Valid time anchor required but not present"
|
||||
});
|
||||
}
|
||||
|
||||
return violations;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Decision Matrix
|
||||
|
||||
| Pack `sealed_install` | Environment Sealed | Bundle Valid | Advisories Fresh | Result |
|
||||
|-----------------------|-------------------|--------------|------------------|--------|
|
||||
| `true` | `true` | `true` | `true` | ✅ RUN |
|
||||
| `true` | `true` | `true` | `false` | ⚠️ WARN + RUN (if within grace) |
|
||||
| `true` | `true` | `false` | * | ❌ DENY |
|
||||
| `true` | `false` | * | * | ❌ DENY |
|
||||
| `false` | `true` | * | * | ✅ RUN |
|
||||
| `false` | `false` | * | * | ✅ RUN |
|
||||
|
||||
### 3.3 Grace Period Handling
|
||||
|
||||
For advisory staleness, a grace period can be configured:
|
||||
|
||||
```yaml
|
||||
# etc/taskrunner.yaml
|
||||
enforcement:
|
||||
sealed_install:
|
||||
staleness_grace_period_hours: 24
|
||||
staleness_warning_threshold_hours: 120
|
||||
deny_on_staleness: true # or false for warn-only
|
||||
```
|
||||
|
||||
## 4. Refusal Semantics
|
||||
|
||||
### 4.1 Error Response
|
||||
|
||||
When enforcement denies execution:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "SEALED_INSTALL_VIOLATION",
|
||||
"message": "Pack requires sealed environment but environment is not sealed",
|
||||
"details": {
|
||||
"pack_id": "compliance-scan-airgap",
|
||||
"pack_version": "1.0.0",
|
||||
"sealed_install_required": true,
|
||||
"environment_sealed": false,
|
||||
"violations": [],
|
||||
"recommendation": "Activate sealed mode with: stella airgap seal"
|
||||
}
|
||||
},
|
||||
"status": "rejected",
|
||||
"rejected_at": "2025-12-06T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 CLI Exit Codes
|
||||
|
||||
| Code | Name | Description |
|
||||
|------|------|-------------|
|
||||
| 40 | `SEALED_INSTALL_VIOLATION` | Pack requires sealed but environment is not |
|
||||
| 41 | `BUNDLE_VERSION_VIOLATION` | Bundle version below minimum |
|
||||
| 42 | `ADVISORY_STALENESS_VIOLATION` | Advisory data too stale |
|
||||
| 43 | `TIME_ANCHOR_VIOLATION` | Time anchor missing or invalid |
|
||||
| 44 | `SIGNATURE_VERIFICATION_VIOLATION` | Bundle signature verification failed |
|
||||
|
||||
### 4.3 Audit Logging
|
||||
|
||||
All enforcement decisions are logged:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "sealed_install_enforcement",
|
||||
"timestamp": "2025-12-06T10:00:00Z",
|
||||
"pack_id": "compliance-scan-airgap",
|
||||
"pack_version": "1.0.0",
|
||||
"decision": "denied",
|
||||
"reason": "SEALED_INSTALL_VIOLATION",
|
||||
"environment": {
|
||||
"sealed": false,
|
||||
"bundle_version": null,
|
||||
"advisory_staleness_hours": null
|
||||
},
|
||||
"user": "task-runner-service",
|
||||
"tenant_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Integration Points
|
||||
|
||||
### 5.1 Task Runner Integration
|
||||
|
||||
```csharp
|
||||
// In TaskRunner execution pipeline
|
||||
public async Task<TaskResult> ExecuteAsync(TaskPack pack, TaskContext context)
|
||||
{
|
||||
// Pre-execution enforcement
|
||||
var enforcement = await _sealedInstallEnforcer.EnforceAsync(pack);
|
||||
if (!enforcement.Allowed)
|
||||
{
|
||||
await _auditLogger.LogEnforcementDenialAsync(pack, enforcement);
|
||||
return TaskResult.Rejected(enforcement);
|
||||
}
|
||||
|
||||
// Continue with execution
|
||||
return await _executor.ExecuteAsync(pack, context);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 CLI Integration
|
||||
|
||||
```bash
|
||||
# Check sealed status before running pack
|
||||
$ stella pack run compliance-scan-airgap
|
||||
|
||||
Error: Sealed install violation
|
||||
Pack 'compliance-scan-airgap' requires a sealed environment.
|
||||
|
||||
Current environment:
|
||||
Sealed: false
|
||||
|
||||
To resolve:
|
||||
1. Import an air-gap bundle: stella airgap import <bundle.tar.gz>
|
||||
2. Activate sealed mode: stella airgap seal
|
||||
3. Verify status: stella airgap status
|
||||
|
||||
Exit code: 40
|
||||
```
|
||||
|
||||
## 6. Configuration
|
||||
|
||||
### 6.1 Task Runner Configuration
|
||||
|
||||
```yaml
|
||||
# etc/taskrunner.yaml
|
||||
enforcement:
|
||||
sealed_install:
|
||||
enabled: true
|
||||
|
||||
# Staleness handling
|
||||
staleness_grace_period_hours: 24
|
||||
staleness_warning_threshold_hours: 120
|
||||
deny_on_staleness: true
|
||||
|
||||
# Fallback detection
|
||||
use_heuristic_detection: true
|
||||
heuristic_threshold: 0.7
|
||||
|
||||
# Logging
|
||||
log_all_decisions: true
|
||||
audit_retention_days: 365
|
||||
```
|
||||
|
||||
### 6.2 Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `AIRGAP_MODE` | Force sealed mode detection | — |
|
||||
| `AIRGAP_CONTROLLER_URL` | AirGap controller endpoint | `http://localhost:8080` |
|
||||
| `SEALED_INSTALL_BYPASS` | Bypass enforcement (dev only) | `false` |
|
||||
|
||||
## 7. Tasks Unblocked
|
||||
|
||||
This contract unblocks:
|
||||
|
||||
| Task ID | Description | Status |
|
||||
|---------|-------------|--------|
|
||||
| TASKRUN-AIRGAP-57-001 | Sealed install enforcement contract | ✅ UNBLOCKED |
|
||||
| TASKRUN-AIRGAP-58-001 | Sealed install CLI integration | ✅ UNBLOCKED |
|
||||
|
||||
## 8. Changelog
|
||||
|
||||
| Date | Version | Change |
|
||||
|------|---------|--------|
|
||||
| 2025-12-06 | 1.0.0 | Initial contract with enforcement logic, decision matrix, CLI integration |
|
||||
Reference in New Issue
Block a user