Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism

- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency.
- Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling.
- Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies.
- Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification.
- Create validation script for CI/CD templates ensuring all required files and structures are present.
This commit is contained in:
StellaOps Bot
2025-12-26 15:17:15 +02:00
parent 7792749bb4
commit c8f3120174
349 changed files with 78558 additions and 1342 deletions

View File

@@ -231,9 +231,264 @@ cosign verify-attestation \
See [Cosign Verification Examples](./cosign-verification-examples.md) for more details.
---
# Aggregated Attestation Bundle Format
This section describes the StellaOps Attestation Bundle format for aggregating multiple attestations for long-term verification.
## Overview
Aggregated attestation bundles collect multiple attestations from a time period into a single verifiable package. This enables:
- **Long-term verification** of keyless-signed artifacts after certificate expiry
- **Organizational endorsement** via optional org-key signature
- **Offline verification** with bundled Rekor inclusion proofs
- **Regulatory compliance** with audit-ready evidence packages
## Bundle Structure
```json
{
"metadata": {
"bundleId": "sha256:abc123...",
"version": "1.0",
"createdAt": "2025-12-26T02:00:00Z",
"periodStart": "2025-12-01T00:00:00Z",
"periodEnd": "2025-12-31T23:59:59Z",
"attestationCount": 1542,
"tenantId": "tenant-1",
"orgKeyFingerprint": "sha256:def456..."
},
"attestations": [
{
"entryId": "uuid-1",
"rekorUuid": "24296fb2...",
"rekorLogIndex": 12345678,
"artifactDigest": "sha256:...",
"predicateType": "verdict.stella/v1",
"signedAt": "2025-12-15T10:30:00Z",
"signingMode": "keyless",
"signingIdentity": {
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:org/repo:ref:refs/heads/main",
"san": "https://github.com/org/repo/.github/workflows/release.yml@refs/heads/main"
},
"inclusionProof": {
"checkpoint": {
"origin": "rekor.sigstore.dev - ...",
"size": 12000000,
"rootHash": "base64...",
"timestamp": "2025-12-15T10:30:05Z"
},
"path": ["base64hash1", "base64hash2", ...]
},
"envelope": {
"payloadType": "application/vnd.in-toto+json",
"payload": "base64...",
"signatures": [{"sig": "base64...", "keyid": ""}],
"certificateChain": ["base64cert1", ...]
}
}
],
"merkleTree": {
"algorithm": "SHA256",
"root": "sha256:abc123...",
"leafCount": 1542
},
"orgSignature": {
"keyId": "org-signing-key-2025",
"algorithm": "ECDSA_P256",
"signature": "base64...",
"signedAt": "2025-12-26T02:05:00Z",
"certificateChain": ["base64cert1", ...]
}
}
```
## Components
### Metadata
| Field | Type | Description |
|-------|------|-------------|
| `bundleId` | string | Content-addressed ID: `sha256:<merkle_root>` |
| `version` | string | Bundle schema version (currently "1.0") |
| `createdAt` | ISO 8601 | Bundle creation timestamp (UTC) |
| `periodStart` | ISO 8601 | Start of attestation collection period |
| `periodEnd` | ISO 8601 | End of attestation collection period |
| `attestationCount` | int | Number of attestations in bundle |
| `tenantId` | string | Optional tenant identifier |
| `orgKeyFingerprint` | string | Fingerprint of org signing key (if signed) |
### Attestations
Each attestation entry contains:
| Field | Type | Description |
|-------|------|-------------|
| `entryId` | string | Unique entry identifier |
| `rekorUuid` | string | Rekor transparency log UUID |
| `rekorLogIndex` | long | Rekor log index |
| `artifactDigest` | string | SHA256 digest of attested artifact |
| `predicateType` | string | In-toto predicate type |
| `signedAt` | ISO 8601 | When attestation was signed |
| `signingMode` | string | `keyless`, `kms`, `hsm`, or `fido2` |
| `signingIdentity` | object | Signer identity information |
| `inclusionProof` | object | Rekor Merkle inclusion proof |
| `envelope` | object | DSSE envelope with signatures and certificates |
### Merkle Tree
Deterministic Merkle tree over attestation hashes:
| Field | Type | Description |
|-------|------|-------------|
| `algorithm` | string | Hash algorithm (always "SHA256") |
| `root` | string | Merkle root: `sha256:<64-hex>` |
| `leafCount` | int | Number of leaves (= attestation count) |
### Org Signature
Optional organizational endorsement:
| Field | Type | Description |
|-------|------|-------------|
| `keyId` | string | Signing key identifier |
| `algorithm` | string | `ECDSA_P256`, `Ed25519`, or `RSA_PSS_SHA256` |
| `signature` | string | Base64-encoded signature |
| `signedAt` | ISO 8601 | Signature timestamp |
| `certificateChain` | array | PEM-encoded certificate chain |
## Determinism
Bundles are deterministic - same attestations produce same bundle:
1. **Attestation ordering**: Sorted by `entryId` lexicographically
2. **Merkle tree**: Leaves computed as `SHA256(canonicalized_attestation_json)`
3. **Bundle ID**: Derived from Merkle root: `sha256:<merkle_root>`
4. **JSON serialization**: Canonical ordering (sorted keys, no whitespace)
## Verification
### Full Bundle Verification
```csharp
using StellaOps.Attestor.Bundling.Verification;
var verifier = new AttestationBundleVerifier();
var result = await verifier.VerifyAsync(bundle);
if (result.Valid)
{
Console.WriteLine($"Merkle root verified: {result.MerkleRootVerified}");
Console.WriteLine($"Org signature verified: {result.OrgSignatureVerified}");
Console.WriteLine($"Attestations verified: {result.AttestationsVerified}");
}
```
### Individual Attestation Verification
```csharp
// Extract single attestation for verification
var attestation = bundle.Attestations.First(a => a.ArtifactDigest == targetDigest);
// Verify inclusion proof against Rekor
var proofValid = await RekorVerifier.VerifyInclusionAsync(
attestation.RekorLogIndex,
attestation.InclusionProof);
// Verify DSSE envelope signature
var sigValid = await DsseVerifier.VerifyAsync(
attestation.Envelope,
attestation.SigningIdentity);
```
## Storage
### S3/Object Storage
```yaml
attestor:
bundling:
storage:
backend: s3
s3:
bucket: stellaops-attestor
prefix: bundles/
objectLock: governance # WORM protection
storageClass: STANDARD
```
### Filesystem
```yaml
attestor:
bundling:
storage:
backend: filesystem
filesystem:
path: /var/lib/stellaops/attestor/bundles
directoryPermissions: "0750"
filePermissions: "0640"
```
## Retention
Bundles follow configurable retention policies:
| Setting | Default | Description |
|---------|---------|-------------|
| `defaultMonths` | 24 | Standard retention period |
| `minimumMonths` | 6 | Cannot be reduced below this |
| `maximumMonths` | 120 | Maximum allowed retention |
### Tenant Overrides
```yaml
attestor:
bundling:
retention:
defaultMonths: 24
tenantOverrides:
tenant-gov: 84 # 7 years
tenant-finance: 120 # 10 years
```
## Export Formats
### JSON (Default)
Human-readable, suitable for debugging and audit:
```bash
stella attestor bundle export --format json bundle-sha256-abc.json
```
### CBOR
Compact binary format (~40% smaller):
```bash
stella attestor bundle export --format cbor bundle-sha256-abc.cbor
```
### Compression
Both formats support compression:
```yaml
attestor:
bundling:
export:
compression: zstd # none | gzip | zstd
compressionLevel: 3
```
## References
- [Sigstore Bundle Specification](https://github.com/sigstore/cosign/blob/main/specs/BUNDLE_SPEC.md)
- [Sigstore Protobuf Specs](https://github.com/sigstore/protobuf-specs)
- [DSSE Specification](https://github.com/secure-systems-lab/dsse)
- [RFC 6962 - Certificate Transparency](https://www.rfc-editor.org/rfc/rfc6962)
- [Bundle Rotation Operations](./operations/bundle-rotation.md)

View File

@@ -0,0 +1,302 @@
# Bundle Rotation Operations Guide
This guide covers operational procedures for attestation bundle rotation in StellaOps.
## Overview
Bundle rotation is a scheduled process that aggregates attestations from a time period into a verifiable bundle. This enables long-term verification of keyless-signed artifacts beyond their certificate expiry.
## Rotation Schedule
### Default Schedule
```yaml
attestor:
bundling:
schedule:
cron: "0 2 1 * *" # Monthly on the 1st at 02:00 UTC
cadence: monthly
timezone: UTC
skipWeekends: false
```
### Cadence Options
| Cadence | Period | Use Case |
|---------|--------|----------|
| `weekly` | Previous 7 days | High-volume environments |
| `monthly` | Previous month | Standard deployment (default) |
| `quarterly` | Previous quarter | Low-volume, compliance-focused |
## Manual Rotation
### Trigger Immediate Rotation
```bash
# Rotate current period
stella attestor bundle rotate
# Rotate specific period
stella attestor bundle rotate --start 2025-12-01 --end 2025-12-31
# Rotate for specific tenant
stella attestor bundle rotate --tenant tenant-gov
```
### API Trigger
```http
POST /api/v1/bundles
Content-Type: application/json
{
"periodStart": "2025-12-01T00:00:00Z",
"periodEnd": "2025-12-31T23:59:59Z",
"tenantId": null,
"signWithOrgKey": true,
"orgKeyId": "org-signing-key-2025"
}
```
## Monitoring
### Key Metrics
| Metric | Description | Alert Threshold |
|--------|-------------|-----------------|
| `attestor_bundle_created_total` | Bundles created | N/A (informational) |
| `attestor_bundle_creation_duration_seconds` | Creation time | > 30 minutes |
| `attestor_bundle_attestations_count` | Attestations per bundle | > 10,000 |
| `attestor_bundle_size_bytes` | Bundle size | > 100 MB |
| `attestor_bundle_retention_deleted_total` | Expired bundles deleted | N/A |
### Grafana Dashboard
Import the attestor observability dashboard:
```bash
stella observability import --dashboard attestor-bundling
```
See: `docs/modules/attestor/operations/dashboards/attestor-observability.json`
### Health Check
```bash
# Check bundle rotation status
stella attestor bundle status
# Sample output:
# Last Rotation: 2025-12-01T02:00:00Z
# Next Scheduled: 2026-01-01T02:00:00Z
# Bundles This Month: 3
# Total Attestations Bundled: 4,521
# Status: Healthy
```
## Retention Policy
### Configuration
```yaml
attestor:
bundling:
retention:
enabled: true
defaultMonths: 24
minimumMonths: 6
maximumMonths: 120
expiryAction: delete # delete | archive | markOnly
archiveStorageTier: glacier
gracePeriodDays: 30
notifyBeforeExpiry: true
notifyDaysBeforeExpiry: 30
maxBundlesPerRun: 100
```
### Retention Actions
| Action | Behavior |
|--------|----------|
| `delete` | Permanently remove expired bundles |
| `archive` | Move to cold storage (S3 Glacier) |
| `markOnly` | Mark as expired but retain |
### Manual Retention Enforcement
```bash
# Preview expired bundles
stella attestor bundle retention --dry-run
# Apply retention policy
stella attestor bundle retention --apply
# Force delete specific bundle
stella attestor bundle delete sha256:abc123...
```
## Troubleshooting
### Bundle Creation Failed
**Symptoms:** Rotation job completes with errors
**Check:**
```bash
# View recent rotation logs
stella logs --service attestor --filter "bundle rotation"
# Check attestor health
stella attestor health
```
**Common causes:**
1. Database connection issues
2. Insufficient attestations in period
3. Org key unavailable for signing
### Large Bundle Size
**Symptoms:** Bundle exceeds size limits or takes too long
**Solutions:**
1. Reduce `maxAttestationsPerBundle` to create multiple smaller bundles
2. Increase `queryBatchSize` for faster database queries
3. Enable compression for storage
```yaml
attestor:
bundling:
aggregation:
maxAttestationsPerBundle: 5000
queryBatchSize: 1000
```
### Org Key Signing Failed
**Symptoms:** Bundle created without org signature
**Check:**
```bash
# Verify org key availability
stella signer keys list --type org
# Test key signing
stella signer keys test org-signing-key-2025
```
**Solutions:**
1. Ensure KMS/HSM connectivity
2. Verify key permissions
3. Check key rotation schedule
### Retention Not Running
**Symptoms:** Expired bundles not being deleted
**Check:**
```bash
# Verify retention is enabled
stella attestor bundle retention --status
# Check for blocked bundles
stella attestor bundle list --status expired
```
**Solutions:**
1. Ensure `retention.enabled: true`
2. Check grace period configuration
3. Verify storage backend permissions
## Disaster Recovery
### Bundle Export
Export bundles for backup:
```bash
# Export all bundles from a period
stella attestor bundle export \
--start 2025-01-01 \
--end 2025-12-31 \
--output /backup/bundles/
# Export specific bundle
stella attestor bundle export sha256:abc123 --output bundle.json
```
### Bundle Import
Restore bundles from backup:
```bash
# Import bundle file
stella attestor bundle import /backup/bundles/bundle-sha256-abc123.json
# Bulk import
stella attestor bundle import /backup/bundles/*.json
```
### Verification After Restore
```bash
# Verify imported bundle
stella attestor bundle verify sha256:abc123
# Verify all bundles
stella attestor bundle verify --all
```
## Runbooks
### Monthly Rotation Check
1. **Pre-rotation (1 day before):**
```bash
stella attestor bundle preview --period 2025-12
```
2. **Post-rotation (rotation day + 1):**
```bash
stella attestor bundle list --created-after 2025-12-01
stella attestor bundle verify --period 2025-12
```
3. **Verify notifications sent:**
Check Slack/Teams/Email for rotation summary
### Quarterly Audit
1. **List all bundles:**
```bash
stella attestor bundle list --format json > audit-report.json
```
2. **Verify sample bundles:**
```bash
# Random sample of 10%
stella attestor bundle verify --sample 0.1
```
3. **Check retention compliance:**
```bash
stella attestor bundle retention --audit
```
### Emergency Bundle Access
For urgent verification needs:
```bash
# Extract specific attestation from bundle
stella attestor bundle extract sha256:abc123 --entry-id uuid-1
# Verify attestation outside bundle
stella attestor verify --envelope attestation.dsse
```
## Related Documentation
- [Bundle Format Specification](../bundle-format.md)
- [Attestor Architecture](../architecture.md)
- [Observability Guide](./observability.md)
- [Air-Gap Operations](../airgap.md)