Go SDK
Read secrets from any Go application using Ed25519 machine authentication, on persistent hosts or serverless and ephemeral environments.
Installation
go get github.com/SikkerKeyOfficial/sikkerkey-go@latest
Zero external dependencies. Standard library only.
Quick Start
import sikkerkey "github.com/SikkerKeyOfficial/sikkerkey-go"
sk, err := sikkerkey.New("vault_abc123")
secret, err := sk.GetSecret("sk_a1b2c3d4e5")
The SDK loads the machine identity from ~/.sikkerkey/vaults/{vaultId}/identity.json, signs every request with the machine's Ed25519 private key, and returns the decrypted value.
Client Creation
// Explicit vault ID
sk, err := sikkerkey.New("vault_abc123")
// Direct path to identity file
sk, err := sikkerkey.New("/etc/sikkerkey/vaults/vault_abc123/identity.json")
// Auto-detect from SIKKERKEY_IDENTITY env or single vault on disk
sk, err := sikkerkey.NewAutoDetect()
Auto-detection fails with a clear error if multiple vaults are registered and no vault is specified.
Serverless (Memory-Only Bootstrap)
On a long-lived host the SDK loads a persistent identity from disk. Serverless and other ephemeral or read-only-filesystem environments (AWS Lambda, Google Cloud Run, Fly.io, and similar) have no identity to persist. BootstrapInMemory handles that case: it generates an Ed25519 keypair in memory, enrolls a short-lived ephemeral machine with an enrollment token, and returns a ready client, without writing anything to disk.
sk, err := sikkerkey.BootstrapInMemory(
os.Getenv("SIKKERKEY_VAULT_ID"),
os.Getenv("SIKKERKEY_ENROLLMENT_TOKEN"),
)
if err != nil {
log.Fatal(err)
}
dbURL, err := sk.GetSecret("sk_db_prod")
Create an enrollment token in the dashboard and supply its plaintext plus your vault ID. The token only registers an ephemeral machine scoped to the policy you set (projects, secrets, lifetime); it cannot read secrets on its own.
How It Works
BootstrapInMemoryenrolls once, when you call it: it generates a keypair in memory, registers an ephemeral machine, and returns a ready*Client.- The returned client signs every read with the in-memory key, exactly like one from
New. - Nothing is written to disk. The private key lives only in process memory and is gone when the process exits.
- The ephemeral machine lives for the lifetime set on the enrollment token. Reading after it expires returns an authentication error, so set the token's machine lifetime to suit your workload. The common path is to read secrets at startup and hold the values.
Options
Pass an optional BootstrapOptions to label the machine:
sk, err := sikkerkey.BootstrapInMemory(vaultID, token, sikkerkey.BootstrapOptions{
Hostname: "worker-1", // defaults to $HOSTNAME, then "serverless"
Name: "batch-runner", // overridden if the token defines a name pattern
})
Provisioning the Token for Serverless
When you create the enrollment token for a serverless deployment:
- Set a short machine lifetime (minutes). Each cold start mints a fresh ephemeral machine, and short-lived ones free their slot quickly as they expire.
- Set max-uses high enough for your cold-start and concurrency volume.
- Leave the source-CIDR restriction unset, since serverless egress IPs are dynamic.
- If the vault has an IP allowlist, make sure it permits the platform's egress or leave it off. Enrollment enforces the allowlist.
- Set a name pattern on the token (for example
serverless-{uuid8}) so each cold-start machine gets a unique name. A name pattern takes precedence overName.{uuidN}inserts N random characters (4 to 32, default 8);{uuid}inserts 8.
Each live ephemeral machine counts against your plan's machine limit until it expires and is cleaned up. Requires outbound HTTPS.
Reading Secrets
Single Value
apiKey, err := sk.GetSecret("sk_stripe_prod")
Structured (Multiple Fields)
fields, err := sk.GetFields("sk_db_prod")
host := fields["host"]
password := fields["password"]
GetFields parses the secret value as JSON into map[string]string. Returns an error if the value is not a JSON object.
Single Field
password, err := sk.GetField("sk_db_prod", "password")
Returns an error if the field does not exist in the structured secret.
Listing Secrets
All Secrets
secrets, err := sk.ListSecrets()
for _, s := range secrets {
fmt.Printf("%s: %s\n", s.ID, s.Name)
}
Returns []SecretListItem with fields ID, Name, FieldNames (*string), and ProjectID (*string).
By Project
secrets, err := sk.ListSecretsByProject("proj_abc123")
Export
Export all accessible secrets as a flat key-value map in a single round trip:
env, err := sk.Export("")
// env["API_KEY"] = "sk-live-..."
// env["DB_CREDS_HOST"] = "db.example.com"
// env["DB_CREDS_PASSWORD"] = "s3cret"
Structured secrets are flattened: SECRET_NAME_FIELD_NAME. For example, a secret named db-creds with fields host and password becomes DB_CREDS_HOST and DB_CREDS_PASSWORD.
Scope to a Project
env, err := sk.Export("proj_abc123")
Pass "" to export all projects.
Watching for Changes
Watch secrets for real-time updates. When a secret is rotated, updated, or deleted, the callback fires with the new value.
sk.Watch("sk_db_credentials", func(event sikkerkey.WatchEvent) {
switch event.Status {
case sikkerkey.WatchStatusChanged:
// event.Value has the new value
// event.Fields has parsed key-value pairs for structured secrets
db.Reconfigure(event.Fields["username"], event.Fields["password"])
case sikkerkey.WatchStatusDeleted:
log.Println("Secret deleted")
case sikkerkey.WatchStatusAccessDenied:
log.Println("Access revoked")
case sikkerkey.WatchStatusError:
log.Printf("Error: %s", event.Error)
}
})
Polling starts automatically on the first Watch() call and runs on a background goroutine. Default interval is 15 seconds (server enforces a 10-second minimum).
sk.SetPollInterval(30) // seconds
sk.Unwatch("sk_db_credentials") // stop watching one secret
sk.Close() // stop all watches
Multi-Vault
A machine registered with multiple vaults uses a separate keypair per vault:
prod, _ := sikkerkey.New("vault_a1b2c3d4e5f6g7h8")
staging, _ := sikkerkey.New("vault_x9y8z7w6v5u4t3s2")
prodDb, _ := prod.GetSecret("sk_db_prod")
stagingDb, _ := staging.GetSecret("sk_db_staging")
List Registered Vaults
vaults := sikkerkey.ListVaults()
// ["vault_a1b2c3d4e5f6g7h8", "vault_x9y8z7w6v5u4t3s2"]
ListVaults is a package-level function, not a method on Client.
Identity Resolution
The SDK resolves the identity file in this order:
- Explicit path — if the argument starts with
/or containsidentity.json, treat it as a file path - Vault ID — looks up
~/.sikkerkey/vaults/{vaultId}/identity.json - Environment variable — reads
SIKKERKEY_IDENTITYfor a path toidentity.json - Auto-detect — if exactly one vault directory exists, uses it automatically
The vault ID prefix vault_ is added automatically if not present. The base directory can be overridden with SIKKERKEY_HOME.
Use NewAutoDetect() to skip the vault ID argument and rely on steps 3–4.
Error Handling
All errors are prefixed with sikkerkey:.
secret, err := sk.GetSecret("sk_nonexistent")
if err != nil {
switch {
case strings.Contains(err.Error(), "not found"):
// 404
case strings.Contains(err.Error(), "authentication failed"):
// 401
case strings.Contains(err.Error(), "access denied"):
// 403
case strings.Contains(err.Error(), "rate limited"):
// 429 (auto-retried first)
case strings.Contains(err.Error(), "server sealed"):
// 503 (auto-retried first)
}
}
| Status | Error contains | Meaning |
|---|---|---|
| 401 | authentication failed | Invalid signature or unknown machine |
| 403 | access denied | Machine not approved, disabled, or no grant |
| 404 | not found | Secret does not exist |
| 409 | conflict | Invalid operation |
| 429 | rate limited | Too many requests (retried automatically) |
| 503 | server sealed | Server needs unseal (retried automatically) |
Properties
| Method | Returns | Description |
|---|---|---|
MachineID() | string | Machine UUID |
MachineName() | string | Hostname from bootstrap |
VaultID() | string | Vault this identity belongs to |
APIURL() | string | SikkerKey API URL |
Method Reference
| Method | Returns | Description |
|---|---|---|
New(vaultOrPath) | (*Client, error) | Create client by vault ID or path |
NewAutoDetect() | (*Client, error) | Create client via auto-detection |
BootstrapInMemory(vaultID, token, opts...) | (*Client, error) | Memory-only serverless bootstrap (package function) |
GetSecret(secretID) | (string, error) | Read a secret value |
GetFields(secretID) | (map[string]string, error) | Read structured secret as key-value pairs |
GetField(secretID, field) | (string, error) | Read single field from structured secret |
ListSecrets() | ([]SecretListItem, error) | List all accessible secrets |
ListSecretsByProject(projectID) | ([]SecretListItem, error) | List secrets in a project |
Export(projectID) | (map[string]string, error) | Export secrets as env-var map |
ListVaults() | []string | List registered vault IDs (package function) |
Watch(secretID, callback) | — | Watch a secret for changes |
Unwatch(secretID) | — | Stop watching a secret |
SetPollInterval(seconds) | — | Set poll interval (min 10s) |
Close() | — | Stop all watches, shut down polling |
Retry Behavior
The SDK retries 429 and 503 responses up to 3 times with exponential backoff (1s, 2s, 4s). Each retry uses a fresh timestamp and nonce for replay protection. Network errors are also retried.
Environment Variables
| Variable | Description |
|---|---|
SIKKERKEY_IDENTITY | Path to identity.json — overrides vault lookup |
SIKKERKEY_HOME | Base config directory (default: ~/.sikkerkey) |
Dependencies
None. Standard library only: crypto/ed25519, crypto/sha256, crypto/x509, encoding/json, net/http, crypto/rand.
Requires Go 1.22+. All HTTP requests have a 15-second timeout.