← Week 2: SPIRE Internals and Attestation

Day 14: Challenge — Local SPIRE Deployment with Two Workload Identities

Phase 4 · August 18, 2026

← Week 2: SPIRE Internals and Attestation

Agenda (3 hours)

  • Build (120 min): Two-service local deployment with SPIRE issuing SVIDs to both
  • Verify (60 min): Each service fetches its SVID; each validates the other's identity
← Week 2: SPIRE Internals and Attestation

Week 2 Concepts Inventory

Rate each: ✓ solid / ~ partial / ✗ need review

  • [ ] SPIRE three-tier architecture (server / agent / workload)
  • [ ] Node attestation — what the agent sends and what the server verifies
  • [ ] AWS IID attestation flow — replay prevention, agent SPIFFE ID format
  • [ ] Workload attestation — unix / docker / k8s selector syntax
  • [ ] Selector AND/OR semantics in registration entries
  • [ ] SPIRE CA options — self-signed vs. UpstreamAuthority
  • [ ] SPIRE server HA — shared datastore, multiple server instances
  • [ ] Join token attestation (local dev shortcut)
← Week 2: SPIRE Internals and Attestation

The Challenge: Two-Service SVID Exchange

Build a local scenario where:

  1. service-a (UID 1000): a small Rust binary that fetches its SVID,
    prints its SPIFFE ID, and writes its cert to /tmp/svid-a.pem
  2. service-b (UID 1001): a small Rust binary that fetches its SVID,
    prints its SPIFFE ID, and writes its cert to /tmp/svid-b.pem
  3. Validation: each service reads the other's cert and validates it
    against the trust bundle

The validation in step 3 simulates what happens in mTLS —
each peer checks that the other's cert is signed by the trusted CA.

← Week 2: SPIRE Internals and Attestation

Service Binary Sketch

// Both services use the same binary, differentiated by UID
// Run as: sudo -u service-a ./spiffe-demo svid-exchange --name a

pub async fn run_svid_exchange(name: &str) -> Result<()> {
    let mut client = WorkloadApiClient::default().await?;
    let svids = client.fetch_x509_svid().await?;
    let svid = svids.first().ok_or("no SVID issued for this process")?;
    
    println!("[{}] Got SPIFFE ID: {}", name, svid.spiffe_id());
    
    // Write cert to shared location
    let pem = pem::encode(&pem::Pem::new("CERTIFICATE", svid.cert_chain()[0].clone()));
    std::fs::write(format!("/tmp/svid-{name}.pem"), &pem)?;
    
    println!("[{}] Wrote cert to /tmp/svid-{name}.pem", name, name);
    
    // Read peer's cert and validate
    let peer = if name == "a" { "b" } else { "a" };
    // wait briefly for peer to write its cert
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    
    validate_peer_svid(&format!("/tmp/svid-{peer}.pem"), svid.trust_bundle())?;
    println!("[{}] Peer service-{} identity valid ✓", name, peer);
    
    Ok(())
}
← Week 2: SPIRE Internals and Attestation

Trust Bundle Validation

use x509_parser::prelude::*;

fn validate_peer_svid(cert_path: &str, trust_bundle: &[Vec<u8>]) -> Result<()> {
    let cert_pem = std::fs::read_to_string(cert_path)?;
    let cert_der = pem::parse(&cert_pem)?.contents().to_vec();
    let (_, cert) = X509Certificate::from_der(&cert_der)?;
    
    // 1. Check validity period
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)?.as_secs() as i64;
    cert.validity().is_valid_at(x509_parser::time::ASN1Time::from_timestamp(now)?)?;
    
    // 2. Verify chain against trust bundle
    // (reuse validate_chain() from toy-pki or call x509-parser's verify)
    
    // 3. Extract and print the SPIFFE ID
    let spiffe_id = extract_spiffe_id(&cert_der)
        .ok_or("not a valid SVID: no SPIFFE URI SAN")?;
    println!("  Peer SPIFFE ID: {}", spiffe_id);
    
    Ok(())
}
← Week 2: SPIRE Internals and Attestation

Deployment Steps

# 1. SPIRE is running from Days 12-13

# 2. Register entries
docker compose exec spire-server spire-server entry create \
  -parentID spiffe://example.org/spire/agent/join_token/abc123 \
  -spiffeID spiffe://example.org/service-a \
  -selector unix:uid:1000 -ttl 3600

docker compose exec spire-server spire-server entry create \
  -parentID spiffe://example.org/spire/agent/join_token/abc123 \
  -spiffeID spiffe://example.org/service-b \
  -selector unix:uid:1001 -ttl 3600

# 3. Run service-a (in one terminal, as UID 1000)
sudo -u \#1000 SPIFFE_ENDPOINT_SOCKET=unix:///tmp/spire-agent/public/api.sock \
  ./target/debug/spiffe-demo svid-exchange --name a &

# 4. Run service-b (in another terminal, as UID 1001)
sudo -u \#1001 SPIFFE_ENDPOINT_SOCKET=unix:///tmp/spire-agent/public/api.sock \
  ./target/debug/spiffe-demo svid-exchange --name b
← Week 2: SPIRE Internals and Attestation

Expected Output

[a] Got SPIFFE ID: spiffe://example.org/service-a
[a] Wrote cert to /tmp/svid-a.pem
[b] Got SPIFFE ID: spiffe://example.org/service-b
[b] Wrote cert to /tmp/svid-b.pem
[a] Peer service-b identity valid ✓
[b] Peer service-a identity valid ✓
  Peer SPIFFE ID: spiffe://example.org/service-a
  Peer SPIFFE ID: spiffe://example.org/service-b

This is the workload identity exchange pattern in miniature.
In production mTLS, this happens inside the TLS handshake — but the
cryptographic operation (chain validation + SPIFFE ID extraction) is the same.

← Week 2: SPIRE Internals and Attestation

Looking Ahead: Week 3

Week 3 applies everything to AWS and your provisioning service:

  • SPIRE on Lambda (the gap and the bridge)
  • Full Rust SVID validator
  • mTLS with SPIFFE SVIDs
  • PQC intersection
  • Written fit analysis

The capstone deliverable: spiffe-demo — all features working —
plus a complete spiffe-analysis.md ready to share with a tech lead.

← Week 2: SPIRE Internals and Attestation

Resources

  • spiffe crate: docs.rs/spiffe — WorkloadApiClient, X509Svid types
  • pem crate: docs.rs/pem — for writing/reading PEM files
  • x509-parser: docs.rs/x509-parser — chain validation
  • SPIRE server entry management: spiffe.io/docs/latest/deploying/registering