Integrating the Go SDK
Problem
You want your Go application to read (and maybe write) secrets from SPIKE
directly, without shelling out to the spike CLI. The SDK does this, and it
hides the hard parts: acquiring the SVID from the SPIRE Agent, setting up mTLS,
rotating certificates, and talking to Nexus. Your code just asks for a path.
The work is mostly making sure the environment around the code is right.
TL;DR
import spike "github.com/spiffe/spike-sdk-go/api"
api, err := spike.New() // uses the default Workload API socket
if err != nil { /* handle */ }
defer api.Close()
secret, err := api.GetSecret("tenants/myapp/db/creds")
if err != nil { /* handle */ }
fmt.Println(secret.Data["password"])
The workload needs a SPIRE entry, a SPIKE policy granting access to the path,
and SPIFFE_ENDPOINT_SOCKET / SPIKE_NEXUS_API_URL set. See
Granting a workload access for the
identity and policy half.
Workflow
-
Add the dependency.
go get github.com/spiffe/spike-sdk-go/api -
Create the client and read a secret.
spike.New()connects via the default Workload API socket;GetSecretreturns a struct whoseDatais the key-value map stored at the path:package main import ( "fmt" spike "github.com/spiffe/spike-sdk-go/api" ) func main() { api, err := spike.New() if err != nil { fmt.Println("connect:", err) return } defer api.Close() secret, err := api.GetSecret("tenants/myapp/db/creds") if err != nil { fmt.Println("read:", err) return } fmt.Println("user:", secret.Data["username"]) } -
Write a secret (if the workload’s policy grants
write):err = api.PutSecret("tenants/myapp/db/creds", map[string]string{ "username": "dbuser", "password": "s3cr3t", }) -
Read a specific version with options:
opts := &spike.GetSecretOptions{Version: 1} old, err := api.GetSecretWithOptions("tenants/myapp/db/creds", opts) -
Wire the runtime so the SDK can find SPIRE and Nexus:
export SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/sockets/agent.sock export SPIKE_NEXUS_API_URL=https://spike-nexus:8553
Tips
- Pick a fetch pattern that fits the workload.
- Startup fetch — read all secrets once at boot. Simple; the app restarts to pick up changes.
- On-demand fetch — read per request. Always fresh; more calls to Nexus.
- Cached with refresh — cache and refresh on a ticker. Balances freshness and load; guard the cache with a mutex.
- Reuse the client.
spike.New()sets up the SVID source and mTLS; create it once and reuse it, anddefer api.Close(). - Versioning is built in. Every
PutSecretto a path creates a new version; read old ones withGetSecretWithOptions. See Storing and reading secrets. - In Kubernetes, mount the SPIRE Agent socket into the pod and set the two environment variables; the SDK Integration Guide has a full Deployment manifest.
Pitfalls
- The error usually names the layer. Map it before debugging SPIKE:
no registration entry found-> SPIRE entry / selectors.403 Forbidden-> missing or mismatched SPIKE policy.connection refused(SVID) -> SPIRE Agent down or wrong socket.connection refused(Nexus) ->SPIKE_NEXUS_API_URLwrong or Nexus down.
Datais a map, read the field.GetSecretreturns the whole key-value map; pullsecret.Data["password"], not the struct itself.- Paths are namespaces.
tenants/myapp/db/creds, never with a leading slash, and identical to the path in the policy. - Don’t log secrets. It is easy to
fmt.Println(secret.Data)while debugging and leave it in. The value is sensitive; keep it out of logs.
Cross-links
- Granting a workload access to secrets
- Storing and reading secrets
- Writing access policies
- Reference: SDK Integration Guide
What’s next
Keep your deployment current and patched: Upgrading SPIKE.