Add unit tests for RabbitMq and Udp transport servers and clients
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- 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.
This commit is contained in:
300
docs/contracts/sealed-mode.md
Normal file
300
docs/contracts/sealed-mode.md
Normal file
@@ -0,0 +1,300 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user