The database password your payment service uses to talk to the ledger is the same password it used two years ago, it lives in three places you know about and probably two you don’t, and if it leaks tomorrow you cannot tell which of forty services actually leaked it.
That is the quiet reality of static service credentials. Every shared secret between two workloads — a database password, an API key, a bearer token baked into an environment variable — is a standing liability. It does not expire on its own. It is copied into config files, CI pipelines, container images and the occasional Slack message. It authenticates a string, not a workload, so when it is misused you cannot attribute the misuse to anything. The industry has spent a decade building vaults to store these secrets more carefully. SPIFFE takes the opposite position: the most defensible secret is the one that does not exist, because the workload proves who it is instead of remembering a password.
What SPIFFE and SPIRE actually are
SPIFFE — the Secure Production Identity Framework For Everyone — is a set of open specifications for giving software workloads cryptographic identity. SPIRE is the reference implementation that issues and manages those identities. Both are graduated projects under the Cloud Native Computing Foundation, which matters if you are answering a supplier-risk question about whether you are betting on a hobby project or on something with a governance model behind it.
Free · 4 minutes
Would you survive contact with a determined attacker — or an auditor?
Fourteen questions on access, patching, detection, and recovery — the basics that prevent most real incidents, and the ones most often assumed rather than verified. Banded finding on screen, full sheet by email.
The core idea is a single primitive: the SPIFFE ID. It is a URI of the form spiffe://trust-domain/path — for example spiffe://payments.example.org/ledger-writer. That identity is delivered to the workload as an SVID (a SPIFFE Verifiable Identity Document), which comes in two shapes: an X.509-SVID, where the SPIFFE ID sits in the URI SAN of a short-lived certificate, and a JWT-SVID for cases where you cannot use certificates directly. The X.509-SVID is the one that carries the weight here, because it slots straight into mutual TLS.
This is the same instinct as removing long-lived cloud keys in favour of federated, short-lived tokens — the ground I covered in killing static cloud keys with OIDC federation and removing cloud access keys from CI entirely. SPIFFE generalises it to every service-to-service call inside your estate, not just the calls that happen to cross a cloud provider’s boundary.
Attestation is the part that does the work
An identity is only worth as much as the process that hands it out. If SPIRE gave a certificate to anything that asked, you would have rebuilt shared secrets with extra steps. It does not. It attests, in two stages.
Node attestation establishes the identity of the machine running a SPIRE agent. The agent proves the node’s identity using something the platform can vouch for — an AWS instance identity document, a GCP or Azure instance token, a Kubernetes service-account token, or a join token for cases the cloud plugins do not cover. The node is now a known quantity.
Workload attestation answers a narrower question: which process on this node is calling me? When a workload connects to the agent’s local Workload API — exposed over a Unix domain socket, so possession of the socket is itself a boundary — the agent interrogates the kernel and the orchestrator for that caller’s properties. Its Kubernetes namespace and service account, its Unix UID, its container image. Those observed properties are the selectors, and they are matched against registration entries the SPIRE server holds. Only if the caller genuinely matches does it receive the SVID for that identity. The workload never presents a secret to get its identity; it presents its own attested attributes, which it cannot fake without already having compromised the node.
Registration, issuance and an authorisation policy
A registration entry is the rule that binds a SPIFFE ID to a set of selectors under a parent (the attesting agent). You create the entries once; SPIRE issues and, crucially, rotates the SVIDs automatically for the life of the workload. The example below registers a ledger-writer service, then shows a Go service fetching its own X.509-SVID and enforcing an authorisation policy keyed on the caller’s SPIFFE ID rather than on a network location or an API key.
# Register the workload with the SPIRE server.
# The selectors are what the agent must observe about the caller
# before it will hand over this identity.
spire-server entry create \
-parentID spiffe://payments.example.org/spire/agent/k8s_psat/prod \
-spiffeID spiffe://payments.example.org/ledger-writer \
-selector k8s:ns:payments \
-selector k8s:sa:ledger-writer
# ---- ledger service (Go, using go-spiffe/v2) ----
package main
import (
"context"
"log"
"net/http"
"github.com/spiffe/go-spiffe/v2/spiffeid"
"github.com/spiffe/go-spiffe/v2/workloadapi"
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
)
func main() {
ctx := context.Background()
// Fetch and keep an up-to-date X.509-SVID from the local
// Workload API socket. The source rotates the SVID for us;
// there is no static credential to store or renew by hand.
source, err := workloadapi.NewX509Source(ctx,
workloadapi.WithClientOptions(
workloadapi.WithAddr("unix:///run/spire/agent/api.sock"),
),
)
if err != nil {
log.Fatalf("unable to obtain SVID: %v", err)
}
defer source.Close()
// Authorisation policy: only the payment-api workload may call us.
// The decision is made on the peer's attested SPIFFE ID, not on
// an IP address, a shared token, or who holds a network path.
allowed := spiffeid.RequireFromString(
"spiffe://payments.example.org/payment-api")
authorizer := tlsconfig.AuthorizeID(allowed)
server := &http.Server{
Addr: ":8443",
TLSConfig: tlsconfig.MTLSServerConfig(source, source, authorizer),
Handler: http.HandlerFunc(handleWrite),
}
log.Fatal(server.ListenAndServeTLS("", ""))
}
func handleWrite(w http.ResponseWriter, r *http.Request) {
// By the time a request reaches here, mTLS has already proven the
// caller is the payment-api workload. Read its identity if needed.
id, ok := x509svid.IDFromConnectionState(r.TLS)
if ok {
log.Printf("authenticated caller: %s", id)
}
w.WriteHeader(http.StatusOK)
}
Read what that authorisation line is doing. The ledger service does not check a password. It checks that the peer on the far end of the TLS handshake presented a valid, in-date certificate whose SPIFFE ID is exactly the payment API’s. Nothing else can call it, because nothing else can obtain that identity, because nothing else attests to the selectors that identity requires.
What changes for the people who own the risk
The SVID is short-lived by design — TTLs are commonly set to an hour or less, and SPIRE rotates them well before expiry — so the window in which a stolen certificate is useful is measured in minutes, not the indefinite lifetime of a leaked API key. That single property changes the shape of several conversations a regulated firm keeps having.
Credential rotation stops being a project. There is no annual scramble to rotate the database password across forty services, because there is no static database password holding those forty services together; each authenticates as itself and is authorised on its own SPIFFE ID. Attribution becomes real: when a supervisor or an incident review asks which service made a call, the answer is a verifiable identity in the mTLS session, not an inference from network logs. And the blast radius of a compromised node is bounded by attestation — a foothold on one node yields only the identities that node’s workloads legitimately attest to, not a master secret that unlocks the estate.
This is the concrete mechanism underneath the slogans. When I write that identity should be the default architecture rather than the network perimeter, workload identity is the part that makes it true for machines. Zero trust between services is not a product you buy; it is the practice of authenticating every call on proven identity, which is exactly what I meant by stripping the marketing out of zero trust.
Where the difficulty actually sits
SPIFFE is not free of edges. Someone has to run the SPIRE server as production infrastructure, protect its signing key, and treat the trust domain’s root as the crown jewel it is. The registration entries become a governed dataset — who can create an entry, and against which selectors, is now an access-control question in its own right, because an over-broad selector is a backdoor identity. Legacy systems that cannot speak mTLS or call a Workload API need a proxy or a sidecar to bridge them in, and that bridge is where shortcuts reintroduce the static secret you were trying to remove.
None of that is a reason to keep the shared password. It is a reason to run the migration deliberately, highest-value trust boundary first. The static service credential is not a secret you are keeping safe. It is a breach you have not attributed yet.
Build and rescue work
Hands-on delivery of this kind is handled by Sixteen Pillars Studio.
Free interactive tool
Website compliance checklist
What your site has to do, based on what it actually does
Answer as much or as little as you like — the list builds as you go. Nothing is stored against your name and no email is required.
Everything that applies
Ordered by what to do first: legal requirements you can close quickly, then larger pieces of work, then what is expected rather than required. Not exhaustive, and not a legal audit.
Dated PDF, yours to keep or circulate.
Can you trust the architecture you have?
Architecture diagrams rarely show the reality of how systems actually operate. An independent review establishes what is really there.