Files
git.stella-ops.org/docs/contracts/sealed-mode.md
master cc69d332e3
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Add unit tests for RabbitMq and Udp transport servers and clients
- Implemented comprehensive unit tests for RabbitMqTransportServer, covering constructor, disposal, connection management, event handlers, and exception handling.
- Added configuration tests for RabbitMqTransportServer to validate SSL, durable queues, auto-recovery, and custom virtual host options.
- Created unit tests for UdpFrameProtocol, including frame parsing and serialization, header size validation, and round-trip data preservation.
- Developed tests for UdpTransportClient, focusing on connection handling, event subscriptions, and exception scenarios.
- Established tests for UdpTransportServer, ensuring proper start/stop behavior, connection state management, and event handling.
- Included tests for UdpTransportOptions to verify default values and modification capabilities.
- Enhanced service registration tests for Udp transport services in the dependency injection container.
2025-12-05 19:01:12 +02:00

301 lines
6.2 KiB
Markdown

# Sealed Mode Contract (AIRGAP-57)
**Contract ID:** `CONTRACT-SEALED-MODE-004`
**Version:** 1.0
**Status:** Published
**Last Updated:** 2025-12-05
## Overview
This contract defines the sealed-mode operation contract for air-gapped environments. It covers sealing/unsealing state transitions, staleness detection, time anchoring, and egress policy enforcement.
## Implementation References
- **Controller:** `src/AirGap/StellaOps.AirGap.Controller/`
- **Time:** `src/AirGap/StellaOps.AirGap.Time/`
- **Policy:** `src/AirGap/StellaOps.AirGap.Policy/`
- **Documentation:** `docs/airgap/sealing-and-egress.md`, `docs/airgap/staleness-and-time.md`
## Data Models
### AirGapState
The core sealed-mode state model.
```csharp
public sealed record AirGapState
{
public string Id { get; init; } = "singleton";
public string TenantId { get; init; } = "default";
public bool Sealed { get; init; } = false;
public string? PolicyHash { get; init; } = null;
public TimeAnchor TimeAnchor { get; init; } = TimeAnchor.Unknown;
public DateTimeOffset LastTransitionAt { get; init; }
public StalenessBudget StalenessBudget { get; init; } = StalenessBudget.Default;
}
```
### JSON Representation
```json
{
"id": "singleton",
"tenant_id": "default",
"sealed": true,
"policy_hash": "sha256:...",
"time_anchor": {
"anchor_time": "2025-12-05T10:00:00Z",
"source": "roughtime",
"format": "roughtime",
"signature_fingerprint": "...",
"token_digest": "sha256:..."
},
"last_transition_at": "2025-12-05T10:00:00Z",
"staleness_budget": {
"warning_seconds": 3600,
"breach_seconds": 7200
}
}
```
### TimeAnchor
Cryptographically verified time reference.
```json
{
"anchor_time": "2025-12-05T10:00:00Z",
"source": "roughtime|rfc3161",
"format": "roughtime|rfc3161",
"signature_fingerprint": "sha256:...",
"token_digest": "sha256:..."
}
```
### StalenessBudget
Defines staleness thresholds.
```json
{
"warning_seconds": 3600,
"breach_seconds": 7200
}
```
| Field | Default | Description |
|-------|---------|-------------|
| `warning_seconds` | 3600 | Warning threshold (1 hour) |
| `breach_seconds` | 7200 | Breach threshold (2 hours) |
### StalenessEvaluation
Result of staleness calculation.
```json
{
"age_seconds": 1800,
"warning_seconds": 3600,
"breach_seconds": 7200,
"is_breached": false,
"remaining_seconds": 1800
}
```
## API Endpoints
### Seal Environment
```
POST /system/airgap/seal
Content-Type: application/json
Authorization: Bearer <token with airgap:seal scope>
{
"policy_hash": "sha256:...",
"time_anchor": { ... },
"staleness_budget": {
"warning_seconds": 3600,
"breach_seconds": 7200
}
}
Response: 200 OK
{
"sealed": true,
"last_transition_at": "2025-12-05T10:00:00Z"
}
```
### Unseal Environment
```
POST /system/airgap/unseal
Authorization: Bearer <token with airgap:seal scope>
Response: 200 OK
{
"sealed": false,
"last_transition_at": "2025-12-05T10:00:00Z"
}
```
### Get Status
```
GET /system/airgap/status
Authorization: Bearer <token with airgap:status:read scope>
Response: 200 OK
{
"sealed": true,
"tenant_id": "default",
"staleness": {
"age_seconds": 1800,
"is_breached": false,
"remaining_seconds": 1800
},
"time_anchor": { ... },
"policy_hash": "sha256:..."
}
```
### Verify Bundle
```
POST /system/airgap/verify
Content-Type: application/json
Authorization: Bearer <token with airgap:verify scope>
{
"bundle_path": "/path/to/bundle.json",
"trust_roots_path": "/path/to/trust-roots.json"
}
Response: 200 OK
{
"valid": true,
"verification_result": {
"dsse_valid": true,
"tuf_valid": true,
"merkle_valid": true
}
}
```
## Egress Policy
### EgressPolicy Model
```csharp
public sealed class EgressPolicy
{
public EgressPolicyMode Mode { get; } // Sealed | Unsealed
public IReadOnlyList<string> AllowedHosts { get; }
public bool PermitLoopback { get; }
public bool PermitPrivateNetworks { get; }
}
```
### EgressRequest / EgressDecision
```json
// Request
{
"component": "excititor",
"destination": "https://api.github.com",
"intent": "fetch_advisories",
"operation": "GET"
}
// Decision
{
"allowed": false,
"reason": "AIRGAP_EGRESS_BLOCKED",
"remediation": "Add api.github.com to allowlist or unseal environment"
}
```
### Enforcement
When sealed:
- All outbound connections blocked by default
- Only allowlisted destinations permitted
- Loopback and private networks optionally permitted
## Time Verification
### Roughtime Verification
1. Parse Roughtime response
2. Verify Ed25519 signature against trusted public key
3. Extract anchor time from signed response
### RFC 3161 Verification
1. Parse SignedCms structure
2. Validate TSA certificate chain
3. Extract signing time from timestamp token
## Startup Diagnostics
Pre-flight checks when starting in sealed mode:
1. Verify time anchor is present
2. Check staleness budget not breached
3. Validate trust roots are loaded
4. Confirm egress policy is enforced
```
GET /healthz/ready
Response: 200 OK (if healthy)
Response: 503 Service Unavailable (if sealed mode requirements unmet)
```
## Telemetry
### Metrics
| Metric | Type | Description |
|--------|------|-------------|
| `airgap_sealed` | gauge | 1 if sealed, 0 if unsealed |
| `airgap_anchor_drift_seconds` | gauge | Seconds since time anchor |
| `airgap_anchor_expiry_seconds` | gauge | Seconds until staleness breach |
| `airgap_seal_total` | counter | Total seal operations |
| `airgap_unseal_total` | counter | Total unseal operations |
| `airgap_startup_blocked_total` | counter | Blocked startup attempts |
### Structured Logging
```json
{
"event": "airgap.sealed",
"tenant_id": "default",
"policy_hash": "sha256:...",
"timestamp": "2025-12-05T10:00:00Z"
}
```
## Authority Scopes
| Scope | Description |
|-------|-------------|
| `airgap:seal` | Seal/unseal environment |
| `airgap:status:read` | Read sealed status |
| `airgap:verify` | Verify bundles |
| `airgap:import` | Import bundles |
## Unblocks
This contract unblocks the following tasks:
- POLICY-AIRGAP-57-001
- POLICY-AIRGAP-57-002
- POLICY-AIRGAP-58-001
## Related Contracts
- [Mirror Bundle Contract](./mirror-bundle.md) - Bundle format for sealed import
- [Verification Policy Contract](./verification-policy.md) - Attestation verification