All articles
December 10, 2024·3 min read

How HashiCorp Vault Handles Secret Leases

A deep dive into Vault's lease system — how secrets get a TTL, how they're renewed, and what happens when they expire in a distributed cluster.

HashiCorpVaultGoDistributed Systems

What is a secret lease?

When Vault issues a dynamic secret — a database credential, an AWS access key, a TLS certificate — it doesn't hand you a credential and walk away. Every dynamic secret is paired with a lease: a structured record that says "this secret is valid for N seconds, and can be renewed up to M times."

This is one of Vault's most powerful and underappreciated design decisions. It turns the problem of secret rotation from a scheduled maintenance task into a first-class runtime operation.

The anatomy of a lease

A lease record stored in Vault's backend looks roughly like this:

type leaseEntry struct {
    LeaseID         string
    ClientToken     string
    Path            string
    Data            map[string]interface{}
    IssueTime       time.Time
    ExpireTime      time.Time
    RenewTime       time.Time
    MaxTTL          time.Duration
    Renewable       bool
}

Every time a secret is issued, Vault creates one of these entries in its storage backend, indexes it by expiry time, and registers it with the expiration manager.

The expiration manager

The expiration manager is a background goroutine that runs a heap-ordered priority queue of leases, sorted by their ExpireTime. Every time a lease nears expiration, the manager wakes up and either:

  1. Notifies the caller that their lease has expired (and revokes the underlying secret)
  2. Automatically renews the lease if the secret backend supports it

Here's the core of the expiration loop in simplified Go:

func (m *ExpirationManager) runExpirationLoop() {
    for {
        select {
        case <-m.quitCh:
            return
        case <-time.After(m.nextExpiration()):
            m.expireLeases()
        }
    }
}

The beautiful part: the expiration manager is deterministic and restartable. If Vault crashes and restarts, it reads all lease entries from the backend, rebuilds the heap, and picks up right where it left off. No secrets silently outlive their intended lifetime.

Raft and lease consistency

On a Vault HA cluster using integrated Raft storage, lease operations flow through the Raft log before being applied to state. This means:

  • A lease creation is not confirmed until it has been committed to a quorum of nodes
  • A lease revocation is linearizable — once you call vault lease revoke, no follower will ever serve that lease again

This is a hard constraint: you can't have a database credential that Vault thinks is revoked but one replica still considers valid. The Raft log is the single source of truth.

What this means for you as a consumer

If you're calling Vault from an application:

  • Always cache the lease_id alongside the secret
  • Set up a renewer goroutine (the official Go SDK has vault.Renewer built-in)
  • Don't use MaxTTL as a crutch — treat every secret as temporary and test your rotation path regularly

The lease system is what makes Vault operationally honest about secret lifecycles. It's one of the design decisions I find most interesting to work on.


This is the first of a series on Vault internals. Next up: how Vault's token hierarchy and policies interact during auth.