Files
git.stella-ops.org/docs/modules/attestor/rekor-verification-design.md

34 KiB

Rekor Verification Technical Design

Document ID: DOCS-ATTEST-REKOR-001 Version: 2.0 Last Updated: 2026-01-13 Status: Draft


1. OVERVIEW

This document provides the comprehensive technical design for Rekor transparency log verification in StellaOps. It covers four key capabilities:

  1. Merkle Proof Verification - Cryptographic verification of inclusion proofs
  2. Durable Retry Queue - Reliable submission with failure recovery
  3. Time Skew Validation - Replay protection via timestamp validation
  4. Tile-Based Verification (v2) - Support for Rekor v2 Sunlight format
Sprint Priority Description
SPRINT_3000_0001_0001 P0 Merkle Proof Verification
SPRINT_3000_0001_0002 P1 Rekor Retry Queue & Metrics
SPRINT_3000_0001_0003 P2 Time Skew Validation
SPRINT_3000_0001_0004 P1 Rekor v2 Tile-Based Verification

2. ARCHITECTURE CONTEXT

Current State

┌─────────────────────────────────────────────────────────────────────┐
│                        Attestor Module                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌─────────────────────┐    ┌─────────────────────┐                 │
│  │ AttestorSubmission  │───►│   IRekorClient      │                 │
│  │ Service             │    │   (HttpRekorClient) │                 │
│  └─────────────────────┘    └──────────┬──────────┘                 │
│           │                            │                             │
│           ▼                            ▼                             │
│  ┌─────────────────────┐    ┌─────────────────────┐                 │
│  │ IAttestorEntry      │    │   Rekor API         │                 │
│  │ Repository          │    │   (External)        │                 │
│  └─────────────────────┘    └─────────────────────┘                 │
│                                                                      │
│  Current Limitations:                                                │
│  ✗ Stores proofs but doesn't verify them cryptographically          │
│  ✗ Failed submissions are lost (no retry)                           │
│  ✗ No integrated_time validation                                    │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Target State

┌─────────────────────────────────────────────────────────────────────┐
│                        Attestor Module (Enhanced)                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌─────────────────────┐    ┌─────────────────────┐                 │
│  │ AttestorSubmission  │───►│   IRekorClient      │                 │
│  │ Service             │    │   + VerifyInclusion │◄──┐             │
│  └─────────┬───────────┘    └──────────┬──────────┘   │             │
│            │                           │               │             │
│            │ (on failure)              │               │             │
│            ▼                           ▼               │             │
│  ┌─────────────────────┐    ┌─────────────────────┐   │             │
│  │ IRekorSubmission    │    │ MerkleProofVerifier │   │             │
│  │ Queue (PostgreSQL)  │    │ CheckpointVerifier  │   │             │
│  └─────────┬───────────┘    └─────────────────────┘   │             │
│            │                                           │             │
│            ▼                                           │             │
│  ┌─────────────────────┐    ┌─────────────────────┐   │             │
│  │ RekorRetryWorker    │───►│   Rekor API         │───┘             │
│  │ (Background)        │    │   (External)        │                 │
│  └─────────────────────┘    └─────────────────────┘                 │
│                                                                      │
│  ┌─────────────────────┐    ┌─────────────────────┐                 │
│  │ AttestorVerification│───►│ ITimeSkewValidator  │                 │
│  │ Service             │    │ (integrated_time)   │                 │
│  └─────────────────────┘    └─────────────────────┘                 │
│                                                                      │
│  Enhancements:                                                       │
│  ✓ Cryptographic Merkle proof verification                          │
│  ✓ Durable retry queue with exponential backoff                     │
│  ✓ Time skew detection and alerting                                 │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

3. COMPONENT DESIGN

3.1 Merkle Proof Verification

3.1.1 Algorithm

Rekor uses RFC 6962 (Certificate Transparency) Merkle tree structure:

                    Root Hash
                   /         \
                  /           \
            Hash(0,1)       Hash(2,3)
            /     \         /     \
         H(0)   H(1)     H(2)   H(3)
          │       │        │       │
        Leaf0   Leaf1    Leaf2   Leaf3

Leaf Hash Computation:

leafHash = SHA256(0x00 || RFC6962_Entry)

Interior Node Computation:

interiorHash = SHA256(0x01 || leftChild || rightChild)

Inclusion Proof Verification:

Given:

  • leafIndex: Position of leaf in tree
  • treeSize: Total number of leaves
  • proofPath[]: Sibling hashes along path to root
  • expectedRoot: Root hash from checkpoint
def verify_inclusion(leaf_hash, leaf_index, tree_size, proof_path, expected_root):
    current_hash = leaf_hash
    current_index = leaf_index
    remaining_size = tree_size

    for sibling_hash in proof_path:
        if current_index % 2 == 1:
            # Current node is right child
            current_hash = sha256(0x01 || sibling_hash || current_hash)
        else:
            # Current node is left child
            current_hash = sha256(0x01 || current_hash || sibling_hash)

        current_index = current_index // 2
        remaining_size = (remaining_size + 1) // 2

    return current_hash == expected_root

3.1.2 Checkpoint Verification

Rekor checkpoints are signed using the log's private key:

Checkpoint Format:
─────────────────
rekor.sigstore.dev - 1234567
<tree_size>
<root_hash_base64>

— rekor.sigstore.dev <signature>

Verification steps:

  1. Parse checkpoint text format
  2. Extract signature and public key hint
  3. Verify Ed25519/ECDSA signature over checkpoint body
  4. Extract root hash and tree size

3.1.3 Implementation Classes

/// <summary>
/// RFC 6962 Merkle proof verification.
/// </summary>
public static class MerkleProofVerifier
{
    private static readonly byte LeafPrefix = 0x00;
    private static readonly byte NodePrefix = 0x01;

    public static bool VerifyInclusion(
        byte[] leafHash,
        long leafIndex,
        long treeSize,
        IReadOnlyList<byte[]> proofPath,
        byte[] expectedRoot)
    {
        ArgumentNullException.ThrowIfNull(leafHash);
        ArgumentNullException.ThrowIfNull(proofPath);
        ArgumentNullException.ThrowIfNull(expectedRoot);

        if (leafHash.Length != 32 || expectedRoot.Length != 32)
            throw new ArgumentException("Hash must be 32 bytes (SHA-256)");

        if (leafIndex < 0 || leafIndex >= treeSize)
            throw new ArgumentOutOfRangeException(nameof(leafIndex));

        var currentHash = leafHash;
        var currentIndex = leafIndex;
        var currentSize = treeSize;

        foreach (var siblingHash in proofPath)
        {
            if (siblingHash.Length != 32)
                throw new ArgumentException("Sibling hash must be 32 bytes");

            if (currentIndex % 2 == 1)
            {
                // Current is right child
                currentHash = HashInterior(siblingHash, currentHash);
            }
            else
            {
                // Current is left child
                currentHash = HashInterior(currentHash, siblingHash);
            }

            currentIndex /= 2;
            currentSize = (currentSize + 1) / 2;
        }

        return currentHash.AsSpan().SequenceEqual(expectedRoot);
    }

    private static byte[] HashInterior(byte[] left, byte[] right)
    {
        Span<byte> buffer = stackalloc byte[1 + 32 + 32];
        buffer[0] = NodePrefix;
        left.CopyTo(buffer.Slice(1, 32));
        right.CopyTo(buffer.Slice(33, 32));
        return SHA256.HashData(buffer);
    }

    public static byte[] ComputeLeafHash(byte[] entryData)
    {
        Span<byte> buffer = stackalloc byte[1 + entryData.Length];
        buffer[0] = LeafPrefix;
        entryData.CopyTo(buffer.Slice(1));
        return SHA256.HashData(buffer);
    }
}

3.2 Durable Retry Queue

3.2.1 State Machine

                         PENDING
                            │
                            │ Worker picks up
                            ▼
                       SUBMITTING
                      /          \
                     /            \
               (success)        (failure)
                  /                \
                 ▼                  ▼
            SUBMITTED            RETRYING
                                    │
                                    │ (after backoff delay)
                                    ▼
                               SUBMITTING
                                    │
                                    │ (max attempts exceeded)
                                    ▼
                              DEAD_LETTER

3.2.2 Exponential Backoff

public static TimeSpan CalculateBackoff(int attemptCount, RekorQueueOptions options)
{
    var delayMs = options.InitialDelayMs * Math.Pow(options.BackoffMultiplier, attemptCount - 1);
    var cappedDelayMs = Math.Min(delayMs, options.MaxDelayMs);

    // Add jitter (±10%)
    var jitter = Random.Shared.NextDouble() * 0.2 - 0.1;
    var finalDelayMs = cappedDelayMs * (1 + jitter);

    return TimeSpan.FromMilliseconds(finalDelayMs);
}

Default backoff sequence:

Attempt Base Delay With Jitter
1 1s 0.9s - 1.1s
2 2s 1.8s - 2.2s
3 4s 3.6s - 4.4s
4 8s 7.2s - 8.8s
5 16s 14.4s - 17.6s

3.2.3 Queue Table Design

CREATE TABLE attestor_rekor_queue (
    id                  UUID PRIMARY KEY,
    tenant_id           TEXT NOT NULL,
    bundle_sha256       TEXT NOT NULL UNIQUE,  -- Idempotency key
    dsse_payload        BYTEA NOT NULL,
    backend             TEXT NOT NULL,
    status              TEXT NOT NULL,
    attempt_count       INT NOT NULL DEFAULT 0,
    max_attempts        INT NOT NULL,
    last_attempt_at     TIMESTAMPTZ,
    last_error          TEXT,
    next_retry_at       TIMESTAMPTZ,
    rekor_uuid          TEXT,                  -- Set on success
    rekor_log_index     BIGINT,                -- Set on success
    created_at          TIMESTAMPTZ NOT NULL,
    updated_at          TIMESTAMPTZ NOT NULL
);

-- Efficient dequeue query
CREATE INDEX idx_rekor_queue_dequeue
    ON attestor_rekor_queue (next_retry_at, status)
    WHERE status IN ('pending', 'retrying');

3.2.4 Dequeue Query

-- Atomic dequeue with row locking
WITH eligible AS (
    SELECT id
    FROM attestor_rekor_queue
    WHERE status IN ('pending', 'retrying')
      AND (next_retry_at IS NULL OR next_retry_at <= NOW())
    ORDER BY next_retry_at NULLS FIRST, created_at
    LIMIT :batch_size
    FOR UPDATE SKIP LOCKED
)
UPDATE attestor_rekor_queue q
SET status = 'submitting',
    updated_at = NOW()
FROM eligible e
WHERE q.id = e.id
RETURNING q.*;

3.3 Time Skew Validation

3.3.1 Threat Model

Attack Description Detection
Backdated Entry Attacker inserts entry with old timestamp Large positive skew
Future Timestamp Attacker pre-dates entry Negative skew (future)
Log Manipulation Attacker modifies existing entries Timestamp inconsistency

3.3.2 Threshold Design

                    Time Skew Detection Zones
    ─────────────────────────────────────────────────────────────────
    │ REJECT │   WARN   │       OK       │   WARN   │ REJECT │
    │ FUTURE │  FUTURE  │                │   PAST   │  PAST  │
    ─────────────────────────────────────────────────────────────────
    ◄───────────────────────┼───────────────────────►
         -60s     -5m       NOW      +5m      +1h
                                    (local time)

    Default Thresholds:
    • Future tolerance: 60 seconds (beyond = reject)
    • Warn threshold: 5 minutes
    • Reject threshold: 1 hour

3.3.3 Validation Flow

public TimeSkewResult Validate(DateTimeOffset integratedTime, DateTimeOffset localTime)
{
    var skew = integratedTime - localTime;

    // Future timestamps are highly suspicious
    if (skew > TimeSpan.Zero)
    {
        if (skew > _options.FutureTolerance)
        {
            return Rejected($"Future timestamp by {skew}");
        }
        return Ok(skew);  // Within future tolerance
    }

    // Past timestamps (normal case - Rekor time is in the past)
    var absSkew = skew.Duration();

    if (absSkew >= _options.RejectThreshold)
    {
        return Rejected($"Time skew {absSkew} exceeds reject threshold");
    }

    if (absSkew >= _options.WarnThreshold)
    {
        return Warning($"Time skew {absSkew} exceeds warn threshold");
    }

    return Ok(skew);
}

3.4 Tile-Based Verification (Rekor v2)

Rekor v2 introduces a tile-based log structure following the Sunlight/C2SP tlog-tiles specification. This enables offline-capable verification and more efficient proof computation.

3.4.1 Architecture Overview

In tile-based logs, the Merkle tree is stored in fixed-size chunks (tiles) of 256 entries each:

                    Tile Structure (256 entries/tile)
    ───────────────────────────────────────────────────────────
                           Level 2 (root)
                              [Tile]
                             /      \
                    Level 1 (intermediate)
                   [Tile 0]  [Tile 1]  ...
                   /      \
              Level 0 (leaves)
    [Tile 0] [Tile 1] [Tile 2] [Tile 3] ...

    Each tile contains up to 256 hashes (32 bytes each = 8KB max)

3.4.2 Log Version Configuration

StellaOps supports automatic version detection and explicit version selection:

public enum RekorLogVersion
{
    Auto = 0,   // Auto-detect based on endpoint availability
    V1 = 1,     // Traditional Trillian-based Rekor (API proofs)
    V2 = 2      // Tile-based Sunlight format
}

Version Selection Logic:

Version PreferTileProofs Result
V2 (any) Always use tile proofs
V1 (any) Always use API proofs
Auto true Prefer tile proofs if available
Auto false Use API proofs (default)

3.4.3 Checkpoint Format

V2 checkpoints follow the c2sp.org/tlog-tiles format:

rekor.sigstore.dev - 2605736670972794746
<tree_size>
<root_hash_base64>

- rekor.sigstore.dev <signature_base64>

Checkpoint Components:

  • Line 1: Origin identifier (log name + instance)
  • Line 2: Tree size (number of leaves)
  • Line 3: Root hash (base64-encoded SHA-256)
  • Blank line: Separator
  • Signature lines: One or more - <origin> <signature> lines

3.4.4 Tile Path Calculation

Tiles are fetched via URL paths following the scheme:

GET {tile_base_url}/tile/{level}/{index:03d}[.p/{partial_width}]

Examples:
- /tile/0/000        # Level 0, tile 0 (entries 0-255)
- /tile/0/001        # Level 0, tile 1 (entries 256-511)
- /tile/1/000        # Level 1, tile 0 (intermediate hashes)
- /tile/0/042.p/128  # Partial tile with 128 entries

3.4.5 Implementation Classes

IRekorTileClient Interface:

public interface IRekorTileClient
{
    Task<RekorTileCheckpoint?> GetCheckpointAsync(
        RekorBackend backend,
        CancellationToken cancellationToken = default);

    Task<RekorTileData?> GetTileAsync(
        RekorBackend backend,
        int level,
        long index,
        CancellationToken cancellationToken = default);

    Task<RekorTileEntry?> GetEntryAsync(
        RekorBackend backend,
        long logIndex,
        CancellationToken cancellationToken = default);

    Task<RekorTileInclusionProof?> ComputeInclusionProofAsync(
        RekorBackend backend,
        long logIndex,
        long treeSize,
        CancellationToken cancellationToken = default);
}

RekorTileData Model:

public sealed class RekorTileData
{
    public required int Level { get; init; }
    public required long Index { get; init; }
    public required int Width { get; init; }  // Number of hashes (max 256)
    public required byte[] Hashes { get; init; }  // Width * 32 bytes

    public byte[] GetHash(int position)
    {
        if (position < 0 || position >= Width)
            throw new ArgumentOutOfRangeException(nameof(position));

        var result = new byte[32];
        Array.Copy(Hashes, position * 32, result, 0, 32);
        return result;
    }
}

3.4.6 Proof Computation Algorithm

Computing an inclusion proof from tiles:

def compute_inclusion_proof(log_index, tree_size, tile_client):
    """Compute inclusion proof by fetching necessary tiles."""
    proof_path = []
    level = 0
    index = log_index
    size = tree_size

    while size > 1:
        tile_index = index // 256
        position_in_tile = index % 256

        # Determine sibling position
        if index % 2 == 1:
            sibling_pos = position_in_tile - 1
        else:
            sibling_pos = position_in_tile + 1 if position_in_tile + 1 < size else None

        if sibling_pos is not None:
            tile = tile_client.get_tile(level, tile_index)
            proof_path.append(tile.get_hash(sibling_pos))

        index = index // 2
        size = (size + 1) // 2
        level += 1

    return proof_path

3.4.7 Configuration

attestor:
  rekor:
    primary:
      url: https://rekor.sigstore.dev
      # Version: Auto, V1, or V2
      version: Auto
      # Custom tile base URL (optional, defaults to {url}/tile/)
      tile_base_url: ""
      # Log ID for multi-log environments (hex-encoded SHA-256)
      log_id: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
      # Prefer tile proofs when version is Auto
      prefer_tile_proofs: false

Environment Variables:

# Rekor v2 Configuration
REKOR_SERVER_URL=https://rekor.sigstore.dev
REKOR_VERSION=Auto           # Auto, V1, or V2
REKOR_TILE_BASE_URL=         # Optional custom tile endpoint
REKOR_LOG_ID=c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d
REKOR_PREFER_TILE_PROOFS=false

3.4.8 Offline Verification Benefits

Tile-based verification enables true offline capability:

  1. Pre-fetch tiles: Download all necessary tiles during online phase
  2. Bundle checkpoint: Include signed checkpoint with offline kit
  3. Local proof computation: Compute proofs entirely from local tile data
  4. No API dependency: Verification works without Rekor connectivity
┌─────────────────────────────────────────────────────────────┐
│                    Offline Verification                      │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────┐     ┌──────────────┐     ┌──────────────┐  │
│  │ Checkpoint  │────►│ Tile Cache   │────►│ Proof        │  │
│  │ (signed)    │     │ (local)      │     │ Verifier     │  │
│  └─────────────┘     └──────────────┘     └──────────────┘  │
│                                                              │
│  Advantages:                                                 │
│  - No network round-trips for proof fetching                │
│  - Deterministic verification (same tiles = same proof)     │
│  - Caching efficiency (tiles are immutable)                 │
│  - Air-gap compatible                                        │
│                                                              │
└─────────────────────────────────────────────────────────────┘

4. DATA FLOW

4.1 Submission Flow (with Queue)

┌─────────┐      ┌──────────────────┐      ┌───────────┐
│ Client  │─────►│ SubmissionService │─────►│ RekorAPI  │
└─────────┘      └────────┬─────────┘      └─────┬─────┘
                          │                       │
                          │ (success)             │
                          ▼                       │
                 ┌─────────────────┐              │
                 │ Store Entry     │◄─────────────┘
                 │ (status=ok)     │
                 └─────────────────┘

                          │ (failure)
                          ▼
                 ┌─────────────────┐
                 │ Enqueue         │
                 │ (status=pending)│
                 └────────┬────────┘
                          │
                          ▼
                 ┌─────────────────┐      ┌───────────┐
                 │ RetryWorker     │─────►│ RekorAPI  │
                 │ (background)    │      └─────┬─────┘
                 └─────────────────┘            │
                          ▲                     │
                          │                     │
                          └─────────────────────┘
                              (retry loop)

4.2 Verification Flow (with Proof Verification)

┌─────────┐      ┌───────────────────┐      ┌─────────────────┐
│ Client  │─────►│ VerificationSvc   │─────►│ EntryRepository │
└─────────┘      └────────┬──────────┘      └────────┬────────┘
                          │                          │
                          │◄─────────────────────────┘
                          │      (AttestorEntry)
                          ▼
                 ┌─────────────────────┐
                 │ 1. TimeSkewValidator│
                 │    (integrated_time)│
                 └────────┬────────────┘
                          │
                          ▼
                 ┌─────────────────────┐
                 │ 2. MerkleProof      │
                 │    Verifier         │
                 └────────┬────────────┘
                          │
                          ▼
                 ┌─────────────────────┐
                 │ 3. Checkpoint       │
                 │    Verifier         │
                 └────────┬────────────┘
                          │
                          ▼
                 ┌─────────────────────┐
                 │ 4. Aggregate Result │
                 │    (VerificationRpt)│
                 └─────────────────────┘

5. CONFIGURATION REFERENCE

# attestor.yaml

attestor:
  rekor:
    primary:
      url: https://rekor.sigstore.dev
      proof_timeout_ms: 15000
      poll_interval_ms: 250
      max_attempts: 60

    mirror:
      enabled: false
      url: https://rekor-mirror.internal

    verification:
      public_key_path: /etc/stellaops/rekor-pub.pem
      # Or inline:
      # public_key_base64: LS0tLS1CRUdJTi...
      allow_offline_without_signature: false
      max_checkpoint_age_minutes: 60

    queue:
      enabled: true
      max_attempts: 5
      initial_delay_ms: 1000
      max_delay_ms: 60000
      backoff_multiplier: 2.0
      batch_size: 10
      poll_interval_ms: 5000
      dead_letter_retention_days: 30

    time_skew:
      enabled: true
      warn_threshold_seconds: 300      # 5 minutes
      reject_threshold_seconds: 3600   # 1 hour
      future_tolerance_seconds: 60     # 1 minute
      reject_future_timestamps: true
      skip_in_offline_mode: true

6. METRICS REFERENCE

Metric Type Labels Description
attestor.inclusion_verify_total Counter result Inclusion proof verifications
attestor.inclusion_verify_latency_seconds Histogram Verification latency
attestor.checkpoint_verify_total Counter result Checkpoint signature verifications
attestor.rekor_queue_depth Gauge Current pending + retrying items
attestor.rekor_retry_attempts_total Counter backend, attempt Retry attempts
attestor.rekor_submission_status_total Counter status, backend Submission outcomes
attestor.rekor_queue_wait_seconds Histogram Time in queue before submission
attestor.time_skew_detected_total Counter severity, action Time skew detections
attestor.time_skew_seconds Histogram Observed skew distribution

7. ERROR HANDLING

7.1 Error Taxonomy

Error Code Description Retry? Action
rekor_unavailable Rekor API not reachable Yes Queue for retry
rekor_conflict Duplicate entry (409) No Retrieve existing entry
rekor_rate_limited Rate limit exceeded (429) Yes Backoff and retry
rekor_internal_error Server error (5xx) Yes Queue for retry
proof_invalid Merkle proof verification failed No Reject, log alert
checkpoint_signature_invalid Checkpoint signature failed No Reject, log alert
time_skew_rejected Time skew exceeds threshold No Reject, log warning

7.2 Structured Logging

{
  "timestamp": "2025-12-14T10:30:00Z",
  "level": "Warning",
  "message": "Rekor submission failed, queuing for retry",
  "error_code": "rekor_unavailable",
  "bundle_sha256": "abc123...",
  "backend": "primary",
  "attempt_count": 1,
  "next_retry_at": "2025-12-14T10:30:02Z",
  "error_message": "Connection refused"
}

8. SECURITY CONSIDERATIONS

8.1 Key Management

  • Rekor public key must be distributed out-of-band
  • Support key rotation via versioned key configuration
  • Store keys in secure location (not in code/config)

8.2 Trust Model

                    Trust Hierarchy
                    ───────────────

                    ┌─────────────┐
                    │ Rekor Root  │
                    │ Public Key  │
                    └──────┬──────┘
                           │ signs
                           ▼
                    ┌─────────────┐
                    │ Checkpoint  │
                    │ (root hash) │
                    └──────┬──────┘
                           │ commits to
                           ▼
                    ┌─────────────┐
                    │ Merkle Tree │
                    │ (entries)   │
                    └──────┬──────┘
                           │ includes
                           ▼
                    ┌─────────────┐
                    │ Attestation │
                    │ (DSSE)      │
                    └─────────────┘

8.3 Offline Security

In air-gapped environments:

  • Checkpoint must be pre-distributed with offline bundle
  • Proof verification still works (no network needed)
  • Time skew validation should be skipped or use bundled reference time

9. TESTING STRATEGY

9.1 Test Categories

Category Coverage Target Tools
Unit >90% xUnit, Moq
Integration >80% Testcontainers (PostgreSQL)
Contract All public APIs Snapshot testing
Performance Latency P99 BenchmarkDotNet

9.2 Golden Fixtures

Obtain from public Sigstore Rekor instance:

# Get a real Rekor entry for testing
rekor-cli get --uuid 24296fb24b8ad77a... --format json > fixtures/rekor-entry-valid.json

# Get checkpoint
curl https://rekor.sigstore.dev/api/v1/log > fixtures/rekor-checkpoint.json

# Get public key
curl https://rekor.sigstore.dev/api/v1/log/publicKey > fixtures/rekor-pubkey.pem

10. MIGRATION GUIDE

10.1 Database Migrations

Run in order:

  1. 00X_rekor_submission_queue.sql - Queue table
  2. Update AttestorEntry schema if stored in PostgreSQL

10.2 Configuration Migration

# Before (existing)
attestor:
  rekor:
    primary:
      url: https://rekor.sigstore.dev

# After (add new sections)
attestor:
  rekor:
    primary:
      url: https://rekor.sigstore.dev
    verification:
      public_key_path: /etc/stellaops/rekor-pub.pem
    queue:
      enabled: true
    time_skew:
      enabled: true

10.3 Rollback Plan

  • Queue table can be dropped if not needed
  • All new features are configurable (can disable)
  • No breaking changes to existing API contracts

11. REFERENCES