Go SDK¶
The CloudTaser Go SDK provides direct access to secrets stored in kernel-protected memory (memfd_secret) without environment variable exposure. It is designed for Go applications compiled with CGO_ENABLED=0, which are statically linked and cannot use the LD_PRELOAD interposer that CloudTaser provides for dynamically linked applications.
How It Works¶
- The CloudTaser wrapper fetches secrets from an EU-hosted OpenBao/Vault instance and writes them into a
memfd_secretfile descriptor inKEY=VALUE\0format. - The wrapper sets
CLOUDTASER_SECRET_FDto the fd number and exec's the target application. - The SDK calls
secrets.Open(), which reads the fd number from the environment,mmaps the memfd, and parses the secrets into a thread-safe in-memory store. - The application calls
store.Get("KEY")to retrieve secrets. The returned values point into the originalmemfd_secret-protected pages -- zero-copy, no heap allocation. - On
store.Close(), the mmap is released and the protected memory is freed.
Installation¶
Requires Go 1.25+ and golang.org/x/sys (pulled automatically).
API Reference¶
The SDK exposes a single package: github.com/cloudtaser/cloudtaser-sdk/secrets.
secrets.Open() (*Store, error)¶
Opens the secret store by reading the CLOUDTASER_SECRET_FD environment variable, mmap'ing the memfd file descriptor, and parsing KEY=VALUE\0 entries.
Return values:
| Condition | Store | Error |
|---|---|---|
| Running under CloudTaser wrapper | Non-nil store | nil |
CLOUDTASER_SECRET_FD not set (local dev, non-CloudTaser) |
nil |
nil |
| Invalid fd number or mmap failure | nil |
Non-nil error |
After Open() succeeds, the file descriptor is closed. The mmap keeps the memory mapping alive and kernel-protected.
store.Get(key string) string¶
Returns the secret value for the given key.
Lookup order:
- If the store is non-nil and open: returns the value from
memfd_secretmemory. If the key is not in memfd, falls back toos.Getenv(key). - If the store is nil: returns
os.Getenv(key)directly. - If the store is closed: returns empty string.
A nil store (from Open() returning nil, nil) is safe to call -- it returns os.Getenv(key) directly.
store.MustGet(key string) string¶
Same as Get, but panics if the returned value is empty. Use for secrets that must be present at startup.
// Panics with "cloudtaser-sdk: secret \"DB_PASSWORD\" not found"
// if DB_PASSWORD is not available from either memfd or env
password := store.MustGet("DB_PASSWORD")
store.Has(key string) bool¶
Returns true if the key exists in the memfd store. On a nil store, falls back to checking whether os.Getenv(key) is non-empty.
store.Keys() []string¶
Returns all secret key names from the memfd store. Returns nil on a nil store. Values are never exposed through this method.
store.Close() error¶
Unmaps the memfd_secret memory. After Close():
Get()returns empty strings (does not fall back to env vars for a closed non-nil store)Has()returnsfalseKeys()returnsnil- Calling
Close()again is safe (no-op)
Usage Examples¶
Basic¶
package main
import (
"log"
"github.com/cloudtaser/cloudtaser-sdk/secrets"
)
func main() {
store, err := secrets.Open()
if err != nil {
log.Fatal(err)
}
defer store.Close()
dbPassword := store.Get("DB_PASSWORD")
// use dbPassword to connect to database
_ = dbPassword
}
Graceful Degradation¶
When CLOUDTASER_SECRET_FD is not set (local development, CI, non-CloudTaser environments), Open() returns nil, nil. A nil store falls back to reading environment variables, so the same code works in both protected and unprotected environments:
store, err := secrets.Open()
if err != nil {
log.Fatal(err)
}
defer store.Close()
// Under CloudTaser: reads from memfd_secret (kernel-protected memory)
// Locally: reads from os.Getenv("DB_PASSWORD")
password := store.Get("DB_PASSWORD")
Checking Protection Status¶
store, err := secrets.Open()
if err != nil {
log.Fatal(err)
}
defer store.Close()
if store != nil {
log.Println("CloudTaser protection: ACTIVE (secrets from memfd_secret)")
} else {
log.Println("CloudTaser protection: INACTIVE (falling back to env vars)")
}
Listing Available Secrets¶
store, _ := secrets.Open()
defer store.Close()
// List all secret keys (values are not exposed)
keys := store.Keys()
for _, key := range keys {
log.Printf("Secret available: %s", key)
}
// Check if a specific secret exists
if store.Has("API_KEY") {
apiKey := store.Get("API_KEY")
_ = apiKey
}
HTTP Demo Server¶
The SDK includes a demo application at examples/demo/main.go that exposes three endpoints:
| Endpoint | Description |
|---|---|
GET / |
Lists available secret keys (not values) and count |
GET /health |
Returns ok status and delivery method (memfd_secret or env_vars) |
GET /protected |
Shows secret availability with masked values (first 2, last 2 chars shown) |
Run it under the wrapper:
cloudtaser-wrapper --vault-addr=https://vault.eu.example.com \
--secrets=DB_PASSWORD,API_KEY \
-- ./demo
SDK vs Wrapper¶
| SDK (this) | Wrapper alone | |
|---|---|---|
| Target | Go applications (static binaries, CGO_ENABLED=0) |
Any process (Python, Node.js, Java, PostgreSQL, etc.) |
| Code changes | Yes -- import SDK and call secrets.Open() |
No -- wrapper injects via LD_PRELOAD or env vars |
memfd_secret protection |
Yes -- secrets read directly from protected pages | Yes -- wrapper writes to memfd, passes fd |
Secrets in /proc/self/environ |
No | No (when using LD_PRELOAD or memfd) |
| Secrets on heap | No -- Get() returns strings backed by mmap region |
Varies by delivery method |
| Works with static binaries | Yes | LD_PRELOAD does not work; env var delivery works but exposes to /proc |
Recommendation:
- Go applications (static binaries): Use this SDK
- Dynamic applications (PostgreSQL, Node.js, Python, Java): Use the
LD_PRELOADinterposer via the wrapper - Both together cover approximately 100% of workloads
Non-Linux Platforms¶
On non-Linux platforms (macOS, Windows), Open() always returns nil, nil and all operations fall back to environment variables. This allows development and testing on any platform while secrets are protected in production on Linux.
Security Properties¶
memfd_secretpages: Secrets reside in kernel memory pages created withmemfd_secret(). These pages are removed from the kernel's direct mapping, making them invisible to the kernel itself, root users, kernel modules,/proc/kcore, and/dev/mem.- No heap copies:
store.Get()returns a string backed by the mmap'd region. No copies are made to the Go heap. - No
/procexposure: Unlike environment variables, secrets delivered via memfd are not visible in/proc/self/environor/proc/self/cmdline. - fd closed after mmap: The file descriptor is closed immediately after mmap. The mapping keeps the memory alive, but the fd cannot be re-read by other processes.
- Thread-safe: All store operations are protected by a read-write mutex.
- Graceful cleanup:
store.Close()unmaps the protected memory.
Integration with the Wrapper¶
The CloudTaser wrapper manages the secret lifecycle:
cloudtaser-wrapper --vault-addr=https://vault.eu.example.com \
--secrets=DB_PASSWORD,API_KEY \
-- ./my-go-app
The wrapper:
- Authenticates to the EU-hosted vault
- Fetches the requested secrets
- Creates a
memfd_secretfd and writes secrets asKEY=VALUE\0 - Sets
CLOUDTASER_SECRET_FD=<fd>in the environment - Exec's the target application (fd is inherited)
The SDK then reads from this fd. The fd is closed after mmap, but the memory mapping remains alive and kernel-protected.