Skip to content

Go SDK

The cloudtaser Go SDK provides direct access to secrets stored in kernel-protected memory (memfd_secret) without environment variable exposure. It is the recommended delivery path for high-sensitivity Go workloads that cannot tolerate the env-var intermediate that the default wrapper uses.

When should I reach for the SDK?

Use the SDK when: you ship a statically-linked Go binary (CGO_ENABLED=0, typical for cloud-native Go workloads) AND your threat model rejects secrets living in /proc/PID/environ even transiently (e.g. high-sensitivity workloads, certain regulated deployments).

Skip the SDK when: env-var delivery via execve() is acceptable. The wrapper works fine with static Go binaries -- they read inherited env through /proc/self/environ directly. eBPF blocks cross-process /proc/PID/environ reads, so the effective sovereignty guarantee holds without the SDK. See Wrapper Design -- static binary caveat for the trade-off.

The SDK is about bypassing the env-var intermediate, not about "making cloudtaser work on static binaries" -- the wrapper itself works on every binary format via fork+exec.


How It Works

  1. The cloudtaser wrapper fetches secrets from an EU-hosted OpenBao instance and writes them into a memfd_secret file descriptor in KEY=VALUE\0 format.
  2. The wrapper sets CLOUDTASER_SECRET_FD to the fd number and exec's the target application.
  3. 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.
  4. The application calls store.Get("KEY") to retrieve secrets. The returned values point into the original memfd_secret-protected pages -- zero-copy, no heap allocation.
  5. On store.Close(), the mmap is released and the protected memory is freed.

Installation

go get github.com/cloudtaser/cloudtaser-sdk

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:

  1. If the store is non-nil and open: returns the value from memfd_secret memory. If the key is not in memfd, falls back to os.Getenv(key).
  2. If the store is nil: returns os.Getenv(key) directly.
  3. 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() returns false
  • Keys() returns nil
  • 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:

VAULT_ADDR=https://vault.eu.example.com \
CLOUDTASER_SECRET_PATHS=secret/data/myapp/db \
cloudtaser-wrapper --secrets=DB_PASSWORD,API_KEY \
                   -- ./demo

SDK vs Wrapper

SDK (this) Wrapper alone (default)
Target High-sensitivity Go applications Any process (Python, Node.js, Java, Go, PostgreSQL, etc.)
Code changes Yes -- import SDK and call secrets.Open() No -- env vars inherited via execve()
memfd_secret protection Yes -- secrets read directly from protected pages Partial -- wrapper writes secrets to memfd, but also to its own env before fork+exec
Secrets in child's /proc/PID/environ No -- secret lives only in mmap'd memfd region Yes -- env-var intermediate exists, eBPF blocks cross-process reads
Secrets on heap No -- Get() returns strings backed by mmap region Depends on application
Works with statically-linked binaries Yes Yes -- execve() does not depend on the dynamic linker

Recommendation:

  • High-sensitivity Go workloads where the /proc/PID/environ intermediate is unacceptable even behind eBPF: use the SDK.
  • Everything else (PostgreSQL, Node.js, Python, Java, most Go services): the wrapper alone is sufficient; eBPF blocks cross-process /proc/PID/environ reads.
  • The SDK and wrapper are complementary, not alternatives: the SDK always runs under the wrapper.

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_secret pages: Secrets reside in kernel memory pages created with memfd_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 /proc exposure: Unlike environment variables, secrets delivered via memfd are not visible in /proc/self/environ or /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:

VAULT_ADDR=https://vault.eu.example.com \
CLOUDTASER_SECRET_PATHS=secret/data/myapp/db \
cloudtaser-wrapper --secrets=DB_PASSWORD,API_KEY \
                   -- ./my-go-app

The wrapper:

  1. Authenticates to the EU-hosted OpenBao
  2. Fetches the requested secrets
  3. Creates a memfd_secret fd and writes secrets as KEY=VALUE\0
  4. Sets CLOUDTASER_SECRET_FD=<fd> in the environment
  5. 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.