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 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

  1. The CloudTaser wrapper fetches secrets from an EU-hosted OpenBao/Vault 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:

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_PRELOAD interposer 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_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:

cloudtaser-wrapper --vault-addr=https://vault.eu.example.com \
                   --secrets=DB_PASSWORD,API_KEY \
                   -- ./my-go-app

The wrapper:

  1. Authenticates to the EU-hosted vault
  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.