Write the small amount of shared code that every program needs to fetch a short-lived identity, verify a peer by SPIFFE ID rather than hostname, and keep rotating without a restart.
Learning objectives
Fetch an identity over a unix socket without sending any credential.
Replace hostname verification with this course's SPIFFE ID profile — one URI SAN, a trusted chain, the peer's role, and an allow list.
Explain why the identity is read through a callback instead of stored as a value.
Share one identity implementation across three Go programs and one Node service.
What this lesson is for
Lesson 11 built the agent that hands out sixty-second certificates. Nothing uses it yet.
This lesson writes the client side: one Go package and one TypeScript module that know how to ask for an identity, keep it fresh, and check who is on the other end of a connection. No existing program changes. Lesson 13 does the rewiring, and it goes quickly precisely because all the thinking happens here.
What stays exactly the same
Before looking at what changes, it is worth seeing how much does not. This is a smaller lesson than its length suggests.
Still true after this lesson
Why it does not change
Mutual TLS, both sides prove themselves
Lesson 7's idea is untouched. Only where the certificate comes from changes
The handshake proves possession by signing
Same protocol, same messages, same guarantees
Trust means "signed by a CA I chose"
Still true. The CA now lives in memory instead of a file
The six token checks
token.go is not edited at all in this lesson
Tokens carry the user, certificates carry the service
Unchanged. Only the format of the service name changes
Ports 8443, 8444, 8445
Unchanged
You are replacing one thing: where a service gets its certificate from. Files on disk become a socket it asks. Everything built on top of certificates keeps working because the certificates are still certificates.
The three new ideas
Read these before any code. Each one is a single sentence, and each one appears in both languages.
Idea 1: ask for an identity instead of loading one
Until now, a service read certs/go.crt and certs/go.key at startup. Now it opens the socket from Lesson 11 and reads a certificate out of it. Three lines replace two file reads.
Idea 2: check the name inside, not the address
Lesson 6's client asked "does this certificate say 127.0.0.1?". Our SVIDs carry no address at all, so that question has no answer. You ask a different one: "does this certificate say spiffe://demo.local/service/go?"
Same shape of check, different field. Both are "is this certificate about who I expected".
Idea 3: read the identity through a callback, not a value
This is the only genuinely unfamiliar one, and an analogy helps.
In simple words
Writing your phone number on a form fixes it at the moment you write it. Writing "call the front desk and ask for me" always reaches you, even after you move desks. A certificate value is the phone number. A callback is the front desk.
Because a certificate is replaced every twenty seconds, handing the TLS library a certificate value would hand it something that goes stale. Instead you hand it a small function that returns whatever the current certificate is. The library calls that function on every new connection, so rotation takes effect immediately, with no restart and no dropped requests.
In Go the function is GetCertificate. In Node it is a function returning the TLS options. Same idea, two spellings.
That is the whole lesson. Everything below applies these three ideas to five files.
Step 1: the shared Go package
Three Go programs now need identical logic: fetch an SVID, keep it fresh, verify peers. Copying it three times would be a bug factory, so make a package.
zsh — 80×24
$mkdir spiffe
$
Create spiffe/spiffe.go. Note the first line: package spiffe, notpackage main. Other programs import it by module path:
how the other programs import itGo
import "secure-services/spiffe"
secure-services is the module name from your go.mod, set back in Lesson 1. That is how Go finds a package inside your own project.
Identity and Source
spiffe/spiffe.goGo
// Identity is one short-lived certificate plus the CAs we currently trust.type Identity struct { Cert tls.Certificate Bundle *x509.CertPool ID string Expiry time.Time}// Source holds the identity in use right now and swaps it on rotation.type Source struct { current atomic.Pointer[Identity]}// New fetches the first identity. It fails if the workload API is not running.func New() (*Source, error) { id, err := fetch() if err != nil { return nil, err } s := &Source{} s.current.Store(id) return s, nil}// Current is the identity to use for the next connection.func (s *Source) Current() *Identity { return s.current.Load() }
atomic.Pointer is the point of this type. Many goroutines read the current identity while one goroutine replaces it, and an atomic pointer makes that safe without a mutex: readers either see the old identity or the new one, never a half-written mixture.
Fetching
spiffe/spiffe.goGo
// fetch asks the workload API for an identity.//// We send nothing: no key, no password, no token. The agent works out who we// are by inspecting our process. That is how the secret zero problem// disappears -- there is no first secret to hand out.func fetch() (*Identity, error) { conn, err := net.Dial("unix", SocketPath) if err != nil { return nil, err } defer conn.Close() var s svidJSON if err := json.NewDecoder(conn).Decode(&s); err != nil { return nil, fmt.Errorf("workload api gave no identity: %w", err) } cert, err := tls.X509KeyPair([]byte(s.Cert), []byte(s.Key)) if err != nil { return nil, err } leaf, err := x509.ParseCertificate(cert.Certificate[0]) if err != nil { return nil, err } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM([]byte(s.Bundle)) { return nil, errors.New("trust bundle is empty") } return &Identity{Cert: cert, Bundle: pool, ID: s.SpiffeID, Expiry: leaf.NotAfter}, nil}
Look at how little there is. Dial, read JSON, parse. We send nothing. No handshake, no credential, no key. That is the entire point of Lesson 11 arriving as three lines of client code.
Verifying a peer
Here is the surprise. The SVIDs in this course carry no hostname — no DNS name, no IP. So the check from Lesson 6 — does this certificate say 127.0.0.1? — has nothing to compare against.
(The specification does permit an SVID to carry DNS or IP SANs in addition to its one URI SAN, and some deployments issue them so that hostname-checking clients keep working. What is never optional is that authorization decisions are made on the SPIFFE ID, not on a hostname.)
You replace it with two checks:
is it signed by a CA in my trust bundle? (unchanged in spirit)
is the SPIFFE ID inside it one I expect? (replaces the hostname check)
spiffe/spiffe.goGo
func (s *Source) verify(allowed []string, peerRole x509.ExtKeyUsage) func([][]byte, [][]*x509.Certificate) error { return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { if len(rawCerts) == 0 { return errors.New("peer sent no certificate") } peer, err := x509.ParseCertificate(rawCerts[0]) if err != nil { return err } // Everything after the leaf is an intermediate. Our demo CA signs // leaves directly, so this is empty here -- but real deployments have // intermediates, and a verifier that ignores them rejects valid chains. intermediates := x509.NewCertPool() for _, raw := range rawCerts[1:] { ca, parseErr := x509.ParseCertificate(raw) if parseErr != nil { return fmt.Errorf("unparsable intermediate: %w", parseErr) } intermediates.AddCert(ca) } // Step 1: is it signed by a CA in our current trust bundle? if _, err := peer.Verify(x509.VerifyOptions{ Roots: s.Current().Bundle, Intermediates: intermediates, // Check the role the PEER is playing, not "any purpose". When we // are the server the peer is a client; when we call out it is a // server. ExtKeyUsageAny would accept a certificate issued for the // opposite direction. KeyUsages: []x509.ExtKeyUsage{peerRole}, }); err != nil { return fmt.Errorf("untrusted certificate: %w", err) } // Step 2: is this shaped like a leaf SVID at all? Verify above proves the signature // chain. It does NOT prove the peer sent a leaf: Go applies its CA // rules to issuers, not to the certificate being checked, so a CA // certificate presented as a client certificate passes unchallenged. // The X.509-SVID profile closes that door, so we enforce it here. // IsCA is also false when the basicConstraints extension is missing, // so test that the extension was present before reading its value. if !peer.BasicConstraintsValid { return errors.New("leaf SVID has no basicConstraints extension") } if peer.IsCA { return errors.New("leaf SVID has CA:TRUE") } if peer.KeyUsage&x509.KeyUsageDigitalSignature == 0 { return errors.New("leaf SVID does not set digitalSignature") } if peer.KeyUsage&(x509.KeyUsageCertSign|x509.KeyUsageCRLSign) != 0 { return errors.New("leaf SVID sets a signing key usage") } // Step 3: is the SPIFFE ID inside it one we allow? // // The SPIFFE specification is explicit: an X.509-SVID MUST contain // exactly one URI SAN, and a validator MUST reject anything with more. // Matching "any URI in the list" would let one certificate pass as one // identity and then be read as another. if len(peer.URIs) != 1 { return fmt.Errorf("an X.509-SVID must have exactly one URI SAN, got %d", len(peer.URIs)) } id := peer.URIs[0].String() if !slices.Contains(allowed, id) { return fmt.Errorf("peer is %s, expected one of %v", id, allowed) } return nil }}
allowed is a list rather than a single value because the auth server must accept calls from two different services. slices.Contains needs Go 1.21, which is exactly the minimum you declared in Lesson 1.
Why the leaf checks are not optional
The leaf checks in the middle of that function look like paperwork. They are not, and the reason is a genuine trap in how certificate verification works.
peer.Verify walks the signature chain. While doing that, it enforces CA rules on every certificate it treats as an issuer — is this allowed to sign, does the path length permit it. It applies none of those rules to the certificate you handed it, because that one is the thing being verified, not a link in the chain.
So a CA certificate presented as a client certificate passes chain verification. If that CA also carries an allowed SPIFFE ID, the identity check passes too, and a certificate that is allowed to mint identities is accepted as if it merely had one.
The X.509-SVID profile forbids exactly that shape. Section 4.1 requires the basic-constraints extension to say cA=false; an absent extension is not the same statement. Section 4.3 requires digitalSignature and forbids keyCertSign and cRLSign. Section 5.2 makes the leaf-validation checks mandatory for validators.
What this course does not check
The Go verifier enforces the section 5.2 leaf checks and the required key-usage bits. It does not attempt to validate every encoding detail in the issuance profile, such as whether the key-usage extension was marked critical.
The Node one can reject CA:TRUE, but Node's certificate object cannot distinguish an absent basic-constraints extension from one that explicitly says cA=false, and it does not expose the key-usage bit string. Reading those details means parsing DER by hand. Those gaps are why production code should use a maintained SPIFFE library rather than the two hundred lines you are writing here. You are building this to understand it, not to ship it.
The two configs
spiffe/spiffe.goGo
// ServerTLS accepts connections from the listed identities and nobody else.func (s *Source) ServerTLS(allowed ...string) *tls.Config { return &tls.Config{ GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return &s.Current().Cert, nil }, ClientAuth: tls.RequireAnyClientCert, VerifyPeerCertificate: s.verify(allowed, x509.ExtKeyUsageClientAuth), MinVersion: tls.VersionTLS13, }}// ClientTLS connects only to the identity given.func (s *Source) ClientTLS(expected string) *tls.Config { return &tls.Config{ GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return &s.Current().Cert, nil }, InsecureSkipVerify: true, VerifyPeerCertificate: s.verify([]string{expected}, x509.ExtKeyUsageServerAuth), MinVersion: tls.VersionTLS13, }}
Read those two signatures aloud. "Accept connections from these identities. Connect only to this identity." That is your entire network policy, written in code that a reviewer can check.
Two settings need explaining, and one of them looks alarming.
ClientAuth: tls.RequireAnyClientCert instead of Lesson 7's RequireAndVerifyClientCert. Both demand a certificate. The difference is who checks it.
Be clear about what we are and are not giving up here. A server verifying a client certificate does no hostname check at all — hostname verification is something a client does to a server, and there is no client hostname to check. RequireAndVerifyClientCert would happily validate the chain against ClientCAs. We take the work into VerifyPeerCertificate because we need checks Go does not offer: the SPIFFE ID allow-list, the one-URI-SAN rule, and the leaf profile. Since our callback already walks the chain against the current bundle, having Go walk it first would only duplicate the work — and would pin the trust roots at config time, which breaks rotation.
Read this before copying InsecureSkipVerify
On the client side, InsecureSkipVerify: true is a different and much sharper tool. Go's documentation is blunt: it makes the client accept any certificate and any hostname — the chain check and the name check, both gone. It is one of the most common serious security bugs in Go code.
It is defensible in ClientTLS for one reason only: VerifyPeerCertificate immediately reimplements both halves — chain verification against the current trust bundle, then the SPIFFE ID and leaf-profile checks — and still drops the connection on failure. We disable the built-in verification because it would check a hostname our SVIDs deliberately do not carry, and because it reads its roots once instead of every handshake. Never write the first line without the second.
Rotation and peer identity
spiffe/spiffe.goGo
// KeepFresh replaces the identity forever, on a timer.func (s *Source) KeepFresh(every time.Duration, report func(*Identity, error)) { for { time.Sleep(every) id, err := fetch() if err != nil { // The old identity is still valid for a while, so this is survivable. report(nil, err) continue } s.current.Store(id) report(id, nil) }}// PeerID pulls the SPIFFE ID off a certificate the handshake already verified.func PeerID(cert *x509.Certificate) (string, error) { if len(cert.URIs) != 1 { return "", fmt.Errorf("expected exactly one URI SAN, got %d", len(cert.URIs)) } return cert.URIs[0].String(), nil}
Both TLS configs read the identity through a callback — GetCertificate and GetClientCertificate — rather than holding a certificate value. That is the trick that makes rotation painless: swapping the pointer takes effect on the very next full handshake, with no restart and no dropped requests. Lesson 13 shows why "full handshake" is doing real work in that sentence, and how a connection can quietly go on using an identity you already replaced.
Why refresh every twenty seconds for a sixty-second certificate?
So you get two spare attempts before anything expires. Refreshing at fifty-five seconds would mean one network blip causes an outage. The general rule is to refresh at about one third of the lifetime.
The complete spiffe package
spiffe/spiffe.goGo
// Package spiffe holds the parts every program in this project needs:// fetching a short-lived identity, keeping it fresh, and checking who the// peer is. The two services and the auth server all import it.package spiffeimport ( "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "net" "slices" "sync/atomic" "time")// SocketPath is where the workload API listens.const SocketPath = "/tmp/spire-lite.sock"// Every identity in this project -- certificates AND tokens -- is one of these.const ( GoID = "spiffe://demo.local/service/go" TSID = "spiffe://demo.local/service/typescript" AuthID = "spiffe://demo.local/service/auth")// what the workload API sends backtype svidJSON struct { SpiffeID string `json:"spiffe_id"` Cert string `json:"cert"` Key string `json:"key"` Bundle string `json:"bundle"`}// Identity is one short-lived certificate plus the CAs we currently trust.type Identity struct { Cert tls.Certificate Bundle *x509.CertPool ID string Expiry time.Time}// Source holds the identity in use right now and swaps it on rotation.type Source struct { current atomic.Pointer[Identity]}// New fetches the first identity. It fails if the workload API is not running.func New() (*Source, error) { id, err := fetch() if err != nil { return nil, err } s := &Source{} s.current.Store(id) return s, nil}// Current is the identity to use for the next connection.func (s *Source) Current() *Identity { return s.current.Load() }// fetch asks the workload API for an identity.//// We send nothing: no key, no password, no token. The agent works out who we// are by inspecting our process, so there is no first secret to hand out.func fetch() (*Identity, error) { conn, err := net.Dial("unix", SocketPath) if err != nil { return nil, err } defer conn.Close() var s svidJSON if err := json.NewDecoder(conn).Decode(&s); err != nil { return nil, fmt.Errorf("workload api gave no identity: %w", err) } cert, err := tls.X509KeyPair([]byte(s.Cert), []byte(s.Key)) if err != nil { return nil, err } leaf, err := x509.ParseCertificate(cert.Certificate[0]) if err != nil { return nil, err } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM([]byte(s.Bundle)) { return nil, errors.New("trust bundle is empty") } return &Identity{Cert: cert, Bundle: pool, ID: s.SpiffeID, Expiry: leaf.NotAfter}, nil}// KeepFresh replaces the identity forever, on a timer. Certificates live 60// seconds; call this with something well under that.func (s *Source) KeepFresh(every time.Duration, report func(*Identity, error)) { for { time.Sleep(every) id, err := fetch() if err != nil { // The old identity is still valid for a while, so this is survivable. report(nil, err) continue } s.current.Store(id) report(id, nil) }}// verify replaces hostname checking with SPIFFE ID checking.func (s *Source) verify(allowed []string, peerRole x509.ExtKeyUsage) func([][]byte, [][]*x509.Certificate) error { return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { if len(rawCerts) == 0 { return errors.New("peer sent no certificate") } peer, err := x509.ParseCertificate(rawCerts[0]) if err != nil { return err } // Everything after the leaf is an intermediate. Our demo CA signs // leaves directly, so this is empty here -- but real deployments have // intermediates, and a verifier that ignores them rejects valid chains. intermediates := x509.NewCertPool() for _, raw := range rawCerts[1:] { ca, parseErr := x509.ParseCertificate(raw) if parseErr != nil { return fmt.Errorf("unparsable intermediate: %w", parseErr) } intermediates.AddCert(ca) } if _, err := peer.Verify(x509.VerifyOptions{ Roots: s.Current().Bundle, Intermediates: intermediates, // Check the role the PEER is playing, not "any purpose". When we // are the server the peer is a client; when we call out it is a // server. ExtKeyUsageAny would accept a certificate issued for the // opposite direction. KeyUsages: []x509.ExtKeyUsage{peerRole}, }); err != nil { return fmt.Errorf("untrusted certificate: %w", err) } // Is this shaped like a leaf SVID at all? Verify above proves the signature // chain. It does NOT prove the peer sent a leaf: Go applies its CA // rules to issuers, not to the certificate being checked, so a CA // certificate presented as a client certificate passes unchallenged. // The X.509-SVID profile closes that door, so we enforce it here. // IsCA is also false when the basicConstraints extension is missing, // so test that the extension was present before reading its value. if !peer.BasicConstraintsValid { return errors.New("leaf SVID has no basicConstraints extension") } if peer.IsCA { return errors.New("leaf SVID has CA:TRUE") } if peer.KeyUsage&x509.KeyUsageDigitalSignature == 0 { return errors.New("leaf SVID does not set digitalSignature") } if peer.KeyUsage&(x509.KeyUsageCertSign|x509.KeyUsageCRLSign) != 0 { return errors.New("leaf SVID sets a signing key usage") } // The SPIFFE specification is explicit: an X.509-SVID MUST contain // exactly one URI SAN, and a validator MUST reject anything with more. // Matching "any URI in the list" would let one certificate pass as one // identity and then be read as another. if len(peer.URIs) != 1 { return fmt.Errorf("an X.509-SVID must have exactly one URI SAN, got %d", len(peer.URIs)) } id := peer.URIs[0].String() if !slices.Contains(allowed, id) { return fmt.Errorf("peer is %s, expected one of %v", id, allowed) } return nil }}// ServerTLS accepts connections from the listed identities and nobody else.func (s *Source) ServerTLS(allowed ...string) *tls.Config { return &tls.Config{ GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return &s.Current().Cert, nil }, // Demand a certificate, but do the checking ourselves: we need the // SPIFFE ID allow-list and the leaf profile, and we need the trust // bundle read fresh on every handshake rather than pinned at config // time. (A server never hostname-checks a client certificate.) ClientAuth: tls.RequireAnyClientCert, VerifyPeerCertificate: s.verify(allowed, x509.ExtKeyUsageClientAuth), MinVersion: tls.VersionTLS13, }}// ClientTLS connects only to the identity given.func (s *Source) ClientTLS(expected string) *tls.Config { return &tls.Config{ GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return &s.Current().Cert, nil }, // InsecureSkipVerify switches off BOTH built-in checks: the signature // chain and the hostname. On its own that accepts any certificate at // all, and it is a real bug in most code that contains it. It is // survivable here only because VerifyPeerCertificate below redoes both // -- chain against the current bundle, then SPIFFE ID and leaf profile // -- and still drops the connection on failure. InsecureSkipVerify: true, VerifyPeerCertificate: s.verify([]string{expected}, x509.ExtKeyUsageServerAuth), MinVersion: tls.VersionTLS13, }}// PeerID pulls the SPIFFE ID off a certificate the handshake already verified.func PeerID(cert *x509.Certificate) (string, error) { if len(cert.URIs) != 1 { return "", fmt.Errorf("expected exactly one URI SAN, got %d", len(cert.URIs)) } return cert.URIs[0].String(), nil}
Step 2: the shared TypeScript module
Create spiffe.ts next to server.ts.
spiffe.tsTypeScript
// The SPIFFE half of the TypeScript service: fetch a short-lived identity,// read its expiry, and check who the peer is.import { createConnection } from "node:net";import { X509Certificate } from "node:crypto";import type { PeerCertificate } from "node:tls";export const SOCKET_PATH = "/tmp/spire-lite.sock";// Every identity in this project -- certificates AND tokens -- is one of these.export const GO_ID = "spiffe://demo.local/service/go";export const TS_ID = "spiffe://demo.local/service/typescript";export const AUTH_ID = "spiffe://demo.local/service/auth";export type Svid = { spiffe_id: string; cert: string; key: string; bundle: string;};/** * Ask the workload API for an identity. * * We send nothing: no key, no password, no token. The agent works out who we * are by inspecting our process, so there is no first secret to hand out. */export function fetchSvid(): Promise<Svid> { return new Promise((resolve, reject) => { const socket = createConnection({ path: SOCKET_PATH }); let data = ""; socket.on("data", (chunk) => (data += chunk)); socket.on("end", () => { try { resolve(JSON.parse(data)); } catch { reject(new Error("workload api gave no identity")); } }); socket.on("error", reject); });}/** When does this certificate expire? Read straight out of the PEM. */export function expiryOf(svid: Svid): string { return new X509Certificate(svid.cert).validTo;}/** * Pull the URI SANs out of Node's certificate object. * * Node flattens the whole SAN extension into one display string, so this is * string handling, not X.509 parsing. It is adequate for enforcing "exactly * one URI SAN" in a course; a production service should read the DER with a * SPIFFE library rather than splitting a string meant for humans. */function uriSans(cert: PeerCertificate): string[] { return (cert.subjectaltname ?? "") .split(", ") .filter((entry) => entry.startsWith("URI:")) .map((entry) => entry.slice("URI:".length));}/** * Check the peer's SPIFFE ID instead of its hostname. * * Node has already verified the signature chain against our trust bundle by * the time this runs; this is the identity half. Returning undefined means * "accepted", which is what Node's checkServerIdentity expects. */export function checkSpiffeId( cert: PeerCertificate, allowed: string[],): Error | undefined { // A leaf SVID must not be a CA certificate. Chain verification does not // check this for us: it applies CA rules to issuers, not to the certificate // being presented. Node exposes whether the certificate is a CA, so we can // reject CA:TRUE. It cannot tell cA:false from an omitted extension; the // callout below explains why this is still only a teaching validator. if (new X509Certificate(cert.raw).ca) { return new Error("leaf SVID has CA:TRUE"); } const ids = uriSans(cert); // The SPIFFE specification requires exactly one URI SAN and requires // validators to reject anything with more. Two URIs would let a certificate // pass as one identity and be read as another. if (ids.length !== 1) { return new Error(`an X.509-SVID must have exactly one URI SAN, got ${ids.length}`); } if (!allowed.includes(ids[0])) { return new Error(`peer is '${ids[0]}', expected one of ${allowed}`); } return undefined;}/** The SPIFFE ID on a certificate that checkSpiffeId has already accepted. */export function peerId(cert: PeerCertificate): string { const ids = uriSans(cert); if (ids.length !== 1) { throw new Error(`expected exactly one URI SAN, got ${ids.length}`); } return ids[0];}
createConnection({ path }) — a path instead of a host and port — is how Node dials a unix socket.
Node is gentler than Go here. rejectUnauthorized: true keeps chain validation working, and you override only the hostname part through checkServerIdentity. There is no InsecureSkipVerify equivalent to reach for.
The one-URI rule
An X.509-SVID must contain exactly one URI SAN, and a validator must reject a certificate carrying more than one. Both implementations above enforce that. A verifier that accepts "any URI in my allow list" lets a two-identity certificate pass one check and be read as the other one afterwards.
One honest limitation in the Node version: cert.subjectaltname is the SAN extension flattened into a display string, so splitting it on ", " is string handling rather than certificate parsing. It is good enough to enforce the single-SAN rule here, and it is not what you should ship. Production code should read the DER through a SPIFFE library, which also validates trust-domain syntax and handles federation.
Lesson 12 checkpoint
You are ready for Lesson 13 when you can explain all of these without rereading:
what a workload sends to the agent in order to receive a credential;
why hostname verification cannot work on an SVID, and what replaces it;
why the specification requires exactly one URI SAN, and what a validator must do with two;
why the peer's role decides which extended key usage you check;
which leaf-profile checks the Go version performs, and which ones the Node teaching version deliberately leaves to a real SPIFFE library;
exactly why InsecureSkipVerify is not a bug in ClientTLS, and when it would be;
why the identity is read through a callback rather than stored as a value.
Next you will point all four programs at this code, delete the certs/ folder, and find out what a rotated certificate does and does not prove.