kage

command module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 20 Imported by: 0

README

kage

kage stores Kubernetes Secret values in an age-encrypted file and synchronizes them to a Kubernetes namespace.

It is intentionally small and opinionated. For a more general and powerful secret-management system—with correspondingly more configuration and complexity—see SOPS.

Installation

Install the latest version with Go:

go install github.com/paulgmiller/kage@latest

Make sure Go's binary directory (normally $(go env GOPATH)/bin) is on your PATH.

Kage currently uses ~/.ssh/id_ed25519 to decrypt secret files. Applying secrets also requires a working Kubernetes configuration at ~/.kube/config.

Secret file format

The decrypted file is dotenv-like. Start each Kubernetes Secret with a #secret:<name> header:

#secret:api
API_TOKEN=replace-with-a-secret
DATABASE_URL="postgres://user:password@example/db"

#secret:worker
QUEUE_TOKEN=replace-with-another-secret

Secret names must be valid Kubernetes DNS subdomains. Keys may not be duplicated within a secret, and values must contain at least five characters.

The file passed to kage must be encrypted with age. To allow kage to update or re-encrypt it, place a recipients.txt file in the same directory:

# One age or SSH recipient per line
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...

For example, with the age command installed:

age -R secrets/recipients.txt -o secrets/envtest secrets/envtest.plaintext

Remove the plaintext copy securely after confirming that the encrypted file can be read.

Usage

Kage uses focused subcommands for each operation. The default encrypted file is secrets/envtest; select another file with the persistent --secret-file (or -f) option.

Inspect secret names and masked values:

kage check --secret-file secrets/envtest

Preview changes to existing secrets in a namespace:

kage apply --secret-file secrets/envtest --namespace my-namespace

Apply changes:

kage apply --secret-file secrets/envtest --namespace my-namespace --confirm

Add or update a value in the encrypted file:

kage set --secret-file secrets/envtest 'api/API_TOKEN=new-secret-value'

Create an encrypted file from every Secret currently in a namespace:

kage create --secret-file secrets/envtest --namespace my-namespace

This reads the encryption recipients from secrets/recipients.txt, just like set and reencrypt. Secret names and keys are sorted so that decrypting repeated exports produces stable plaintext. The command refuses to create an invalid kage file, including when the namespace contains no Secrets or a value shorter than five characters.

If the encrypted file does not exist, set creates it using the adjacent recipients.txt. This is the simplest way to start a new file:

mkdir -p secrets
cp ~/.ssh/id_ed25519.pub secrets/recipients.txt
kage set --secret-file secrets/envtest 'api/API_TOKEN=new-secret-value'

Re-encrypt the file using its adjacent recipients.txt, for example after changing the recipient list:

kage reencrypt --secret-file secrets/envtest

Show all command-line options:

kage --help

Kage creates opaque Kubernetes Secrets and marks them with the managed-by: github.com/paulgmiller/kage annotation.

Loading secrets locally

Applications written in Go can use kage.Load() similarly to godotenv.Load(). It first loads .env, then decrypts and loads secrets/envtest when ~/.ssh/id_ed25519 matches a recipient used to encrypt the file. When called from a subdirectory, Kage looks for the encrypted file in each parent directory through the Git root:

package main

import (
	"log"
	"os"

	"github.com/paulgmiller/kage/pkg/kage"
)

func main() {
	if err := kage.Load(); err != nil {
		log.Fatal(err)
	}

	apiToken := os.Getenv("API_TOKEN")
	_ = apiToken
}

Select a different encrypted file with the optional argument or the KAGE_SECRET_FILE environment variable. An argument takes precedence over the environment variable:

if err := kage.Load("secrets/development"); err != nil {
	log.Fatal(err)
}

Relative configured paths use the same upward search. Absolute paths are used exactly as supplied.

Like godotenv, kage.Load() does not overwrite environment variables that are already set. This makes it possible to use ordinary values from .env and encrypted local values from secrets/envtest, while allowing the shell or CI environment to take precedence.

End-to-end test

The end-to-end test requires Docker on a Linux host. It builds an isolated test image, creates a kind cluster, and verifies two Secrets:

./test/e2e.sh

Without --confirm, kage apply is a dry run: it reports both creations and updates without changing the target namespace.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL