gosecrets

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Mar 25, 2026 License: MIT Imports: 9 Imported by: 0

README

Version Go Documentation Go Report Card codecov Run go tests Run golangci-lint

gosecrets

Encrypted credentials management for Go projects - inspired by Rails credentials.

Stop juggling .env files and environment variables. Keep your secrets encrypted in your repo, share them safely with your team, and access them with a clean API.


Example Flow

First, create stores for development and production environments:

gosecrets init                    # secrets/development.key + secrets/development.enc
gosecrets init --env production   # secrets/production.key + secrets/production.enc

Each init prints a master key — save it somewhere safe. You will need this key to decrypt your credentials in deployment (e.g. Portainer, CI). Locally, the key file on disk is used automatically.

You should see something like:

master key: e5d0529e.................................................
save this key somewhere safe, you need it to decrypt your credentials.

Edit your development config: gosecrets edit (pops up your default $EDITOR), edit yaml:

stripe:
  key: sk_test_xxx
  webhook_secret: whsec_test_abc

Edit your production config: gosecrets edit --env production:

stripe:
  key: sk_live_yyy
  webhook_secret: whsec_live_def

Fix your .gitignore file

secrets/*.key

Keep this in mind! NEVER COMMIT *.key files!

Now, in your Go project, your code is the same everywhere:

secrets, err := gosecrets.Load()
if err != nil {
    // handle your error
}

fmt.Println(secrets.String("stripe.key"))
// development: sk_test_xxx
// production:  sk_live_yyy

gosecrets.Load() picks the environment automatically from GOSECRETS_ENV. Default is development. In production, just set:

export GOSECRETS_ENV=production
export GOSECRETS_PRODUCTION_KEY="your-master-key-here"

# or whatever your env is:
# export GOSECRETS_STAGING_KEY="your-master-key-here"
# export GOSECRETS_FOO_KEY="your-master-key-here"

You can choose any environment name, such as foo:

export GOSECRETS_ENV=foo
gosecrets init                     # creates secrets/foo.key + secrets/foo.enc
gosecrets edit                     # add your secrets
secrets, err := gosecrets.Load()   // picks up GOSECRETS_ENV=foo automatically
Environment GOSECRETS_ENV .enc file Key from Key ENV VAR
development (empty or unset) secrets/development.enc secrets/development.key (disk) GOSECRETS_DEVELOPMENT_KEY
production production secrets/production.enc env var GOSECRETS_PRODUCTION_KEY
<any-name> <any-name> secrets/<any-name>.enc env var GOSECRETS_<ANY-NAME>_KEY

Custom Root Directory (--root)

By default, gosecrets creates and looks for the secrets/ directory in the current working directory. If your project layout requires a different location, use the --root flag or the GOSECRETS_ROOT environment variable.

Important: The --root flag only accepts relative paths within the project directory. Absolute paths and .. traversal are rejected. For paths outside the project (CI/CD, Docker), use the GOSECRETS_ROOT environment variable.

CLI usage (relative paths only)
# create secrets under ./deploy instead of current directory
gosecrets init --root ./deploy --env production
# → ./deploy/secrets/production.key
# → ./deploy/secrets/production.enc

# edit, show, get all support --root
gosecrets edit --root ./deploy --env production
gosecrets show --root ./deploy --env production
gosecrets get database.password --root ./deploy --env production

# --root=<path> syntax also works
gosecrets init --root=config

# these are rejected by the CLI:
gosecrets init --root /app         # ✘ absolute path
gosecrets init --root ../outside   # ✘ escapes project directory
gosecrets init --root=             # ✘ empty value
Environment variable (for CI/CD and Docker)

In CI/CD pipelines and Docker containers, the project root is often an absolute path like /app. Use GOSECRETS_ROOT for these cases:

# Docker / CI — set absolute path via env var
export GOSECRETS_ROOT=/app
gosecrets init --env production   # uses /app/secrets/
gosecrets edit --env production   # uses /app/secrets/
# Dockerfile example
ENV GOSECRETS_ROOT=/app
ENV GOSECRETS_ENV=production
ENV GOSECRETS_PRODUCTION_KEY=your-master-key-here

The --root flag always takes precedence over GOSECRETS_ROOT.

Library usage

When using gosecrets as a Go library, WithRoot() accepts both relative and absolute paths — no restriction:

// load from /app/secrets/production.enc (absolute — common in Docker)
secrets, err := gosecrets.Load(
    gosecrets.WithRoot("/app"),
    gosecrets.WithEnv("production"),
)

// load from ./deploy/secrets/development.enc (relative)
secrets, err := gosecrets.Load(
    gosecrets.WithRoot("./deploy"),
)

Without WithRoot(), the library reads from ./secrets/ relative to the working directory — same as the CLI.

Resolution order
Priority Source Accepts Example
1 (highest) --root flag relative only --root ./deploy
2 GOSECRETS_ROOT env var relative or absolute export GOSECRETS_ROOT=/app
3 (default) Current working directory ./secrets/
Directory layout with custom root
your-project/
├── deploy/                       ← --root ./deploy
│   └── secrets/
│       ├── production.enc        ← encrypted YAML (committed)
│       └── production.key        ← decryption key (.gitignore!)
├── secrets/                      ← default (no --root)
│   ├── development.enc
│   └── development.key
├── main.go
└── ...

How It Works ?

gosecrets stores your secrets encrypted inside your repository. Only the master key stays outside version control.

Each environment gets its own .enc / .key pair inside secrets/:

your-project/
├── secrets/
│   ├── development.enc   # encrypted YAML — committed to git
│   ├── development.key   # decryption key — add to .gitignore!
│   ├── production.enc    # encrypted YAML — committed to git
│   └── production.key    # decryption key — add to .gitignore!

In development, the key file on disk is enough. In production/CI, set GOSECRETS_<ENV>_KEY (e.g. GOSECRETS_PRODUCTION_KEY) as an environment variable and never deploy the key file.

.key files are for local work, env vars are for deployment. When both exist, the env var takes precedence.


Encryption

Credentials are encrypted with AES-256-GCM (authenticated encryption). Each write generates a fresh random nonce - the same plaintext produces different ciphertext every time.

┌──────────┐     ┌──────────────┐     ┌─────────────────┐
│ You edit │────▶│ gosecrets    │────▶│ <env>.enc       │
│ YAML     │     │ encrypts     │     │ (committed)     │
└──────────┘     └──────────────┘     └─────────────────┘

┌──────────┐     ┌──────────────┐     ┌─────────────────┐
│ Your app │◀────│ gosecrets    │◀────│ <env>.enc       │
│ reads    │     │ decrypts     │     │ + master key    │
└──────────┘     └──────────────┘     └─────────────────┘

Installation

Library:

go get -u github.com/bilustek/gosecrets

CLI Tool via Homebrew:

brew tap bilustek/tap
brew install gosecrets

or via go install:

go install github.com/bilustek/gosecrets/cmd/gosecrets@latest

Run gosecrets:

$ gosecrets

gosecrets - encrypted credentials for Go projects

Usage:
  gosecrets init [--env ENV] [--root DIR]       Initialize a new credential store
  gosecrets edit [--env ENV] [--root DIR]       Edit credentials in $EDITOR
  gosecrets show [--env ENV] [--root DIR]       Print decrypted credentials to stdout
  gosecrets get KEY [--env ENV] [--root DIR]    Get a specific value (dot notation)
  gosecrets version, --version, -v              Show version
  gosecrets help, --help, -h                    Show this help
  gosecrets completion bash                     Output bash completion script

Environment:
  GOSECRETS_ROOT                   Root directory for secrets/ (default: current directory)
  GOSECRETS_ENV                    Environment name (default: development)
  GOSECRETS_MASTER_KEY             Master key (overrides all key files)
  GOSECRETS_<ENV>_KEY              Environment-specific key (e.g. GOSECRETS_PRODUCTION_KEY)
  EDITOR / VISUAL                  Preferred text editor

Examples:
  gosecrets init                                  Creates ./secrets/development.{key,enc}
  gosecrets init --env production                 Creates ./secrets/production.{key,enc}
  gosecrets init --root ./deploy --env production Creates ./deploy/secrets/production.{key,enc}
  gosecrets edit                                  Opens credentials in your editor
  gosecrets get database.password                 Prints a specific value

Bash Completion

Enable tab completion for subcommands, --env values, --root directories, and get keys:

# add to your ~/.bashrc or ~/.bash_profile
eval "$(gosecrets completion bash)"

What gets completed:

Context Completes
gosecrets [TAB] init, edit, show, get, version, help, completion
gosecrets get [TAB] credential keys (e.g. database.host, api_key)
gosecrets --env [TAB] environment names from secrets/*.enc files
gosecrets --root [TAB] directory names (filesystem completion)
gosecrets init --[TAB] --env, --root
gosecrets completion [TAB] bash

API

All accessors support dot notation for nested keys (e.g. "database.password").

secrets, err := gosecrets.Load()
Load Options
Option Description Example
gosecrets.WithRoot(dir) Set root directory for secrets/ gosecrets.WithRoot("/app")
gosecrets.WithEnv(env) Set environment name gosecrets.WithEnv("production")
// defaults: ./secrets/development.enc
secrets, err := gosecrets.Load()

// custom root + env: ./deploy/secrets/production.enc
secrets, err := gosecrets.Load(
    gosecrets.WithRoot("./deploy"),
    gosecrets.WithEnv("production"),
)

// Docker/CI absolute path: /app/secrets/production.enc
secrets, err := gosecrets.Load(
    gosecrets.WithRoot("/app"),
    gosecrets.WithEnv("production"),
)
Accessors
Method Return Zero value Description
Get(key) any nil Raw value
String(key, fallback...) string "" String representation
Int(key, fallback...) int 0 Integer value
Int64(key, fallback...) int64 0 64-bit integer value
Float64(key, fallback...) float64 0 Floating point value
Bool(key, fallback...) bool false Boolean value
Duration(key, fallback...) time.Duration 0 Parses "5s", "1h30m", etc.
Map(key, fallback...) map[string]any nil Nested map
TCPAddr(key, fallback...) *net.TCPAddr nil Parses "host:port" via net.ResolveTCPAddr
Has(key) bool false Check if key exists
Keys() []string [] All dot-notation key paths
All() map[string]any Entire credentials map
MustGet(key) any panic Like Get, panics if missing
MustString(key) string panic Like String, panics if missing
MustTCPAddr(key) *net.TCPAddr panic Like TCPAddr, panics if missing or invalid

All accessors (except Get, Has, All, and Must* variants) accept an optional fallback value. If the key doesn't exist, the fallback is returned instead of the zero value:

// without fallback — returns zero value when key is missing
host := secrets.String("database.host")           // ""

// with fallback — returns fallback when key is missing
host := secrets.String("database.host", "0.0.0.0") // "0.0.0.0"
// examples
host := secrets.String("database.host")               // "localhost"
port := secrets.Int("database.port")                   // 5432
pi := secrets.Float64("pi")                            // 3.14
debug := secrets.Bool("debug")                         // true
timeout := secrets.Duration("timeout")                 // 5s
db := secrets.Map("database")                          // map[string]any{...}
redis := secrets.TCPAddr("redis_addr")                 // *net.TCPAddr{IP: ..., Port: 6379}

// with fallback values
host = secrets.String("cache.host", "localhost")       // "localhost" if missing
port = secrets.Int("cache.port", 6379)                 // 6379 if missing
timeout = secrets.Duration("cache.ttl", 5*time.Minute) // 5m if missing
redis = secrets.TCPAddr("cache.addr", "localhost:6379") // parsed fallback if missing

// must variants — panic if missing (use at startup)
apiKey := secrets.MustString("api_key")                // panics if not found
addr := secrets.MustTCPAddr("redis_addr")              // panics if not found or invalid

Change Log

2026-03-25

  • Add --root flag to all CLI commands (init, edit, show, get) — relative paths only
  • Add GOSECRETS_ROOT environment variable for absolute paths (CI/CD, Docker)
  • CLI rejects absolute paths in --root flag with a clear error message
  • Bash completion now supports --root with directory completion
  • Update documentation with custom root directory examples

2026-03-17

  • Add bash completion support (gosecrets completion bash)
  • Add Keys() method for listing all dot-notation key paths
  • Fix init next steps hint to include --env flag when custom environment is used

2026-03-01

  • Add optional fallback values to all accessors: String, Int, Int64, Float64, Bool, Duration, Map
  • Add TCPAddr(key, fallback...) method for resolving "host:port" strings to *net.TCPAddr
  • Add MustTCPAddr(key) method that panics if key is missing or address is invalid
  • Add --version, -v, --help, -h flags to CLI usage output

2026-02-28

  • Fix save (edit) bug v0.4.0
  • Initial release v0.1.0

Contributor(s)


Contribute

All PR's are welcome!

  1. fork (https://github.com/bilustek/gosecrets/fork)
  2. Create your branch (git checkout -b my-feature)
  3. commit yours (git commit -am 'add some functionality')
  4. push your branch (git push origin my-feature)
  5. Than create a new Pull Request!

License

This project is licensed under MIT


This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

Documentation

Overview

Package gosecrets provides encrypted credential management for Go projects, inspired by Rails credentials.

Quick start:

// Initialize (run once)
gosecrets init

// Edit credentials
gosecrets edit

// In your Go code:
secrets, err := gosecrets.Load()
dbPass := secrets.String("database.password")
apiKey := secrets.MustString("api_key")

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option func(*config) error

Option configures credential loading.

func WithEnv

func WithEnv(env string) Option

WithEnv sets environment-specific credential files. For example, WithEnv("production") reads production.enc with production.key. This overrides the GOSECRETS_ENV environment variable.

func WithRoot

func WithRoot(root string) Option

WithRoot sets the root directory for the credential store. The store directory will be root/secrets.

type Secrets

type Secrets struct {
	// contains filtered or unexported fields
}

Secrets holds the decrypted credentials as a nested map.

func Load

func Load(opts ...Option) (*Secrets, error)

Load reads and decrypts the credentials. Environment resolution order: WithEnv() > GOSECRETS_ENV > "development".

secrets, err := gosecrets.Load()
secrets, err := gosecrets.Load(gosecrets.WithRoot("/app"), gosecrets.WithEnv("production"))

func (*Secrets) All

func (s *Secrets) All() map[string]any

All returns the entire credentials map.

func (*Secrets) Bool

func (s *Secrets) Bool(key string, fallback ...bool) bool

Bool retrieves a boolean value using dot notation. Returns fallback (or false) if the key doesn't exist or isn't a bool.

func (*Secrets) Duration

func (s *Secrets) Duration(key string, fallback ...time.Duration) time.Duration

Duration retrieves a time.Duration value using dot notation. The value must be a string parseable by time.ParseDuration (e.g., "5s", "1h30m"). Returns fallback (or 0) if the key doesn't exist, isn't a string, or can't be parsed.

func (*Secrets) Float64

func (s *Secrets) Float64(key string, fallback ...float64) float64

Float64 retrieves a float64 value using dot notation. Returns fallback (or 0) if the key doesn't exist or isn't numeric.

func (*Secrets) Get

func (s *Secrets) Get(key string) any

Get retrieves a value using dot notation (e.g., "database.password"). Returns nil if the key doesn't exist.

func (*Secrets) Has

func (s *Secrets) Has(key string) bool

Has checks if a key exists in the credentials.

func (*Secrets) Int

func (s *Secrets) Int(key string, fallback ...int) int

Int retrieves an integer value using dot notation. Returns fallback (or 0) if the key doesn't exist or isn't numeric.

func (*Secrets) Int64

func (s *Secrets) Int64(key string, fallback ...int64) int64

Int64 retrieves an int64 value using dot notation. Returns fallback (or 0) if the key doesn't exist or isn't numeric.

func (*Secrets) Keys added in v0.3.0

func (s *Secrets) Keys() []string

Keys returns all dot-notation key paths in the credentials.

func (*Secrets) Map

func (s *Secrets) Map(key string, fallback ...map[string]any) map[string]any

Map retrieves a nested map using dot notation. Returns fallback (or nil) if the key doesn't exist or isn't a map.

func (*Secrets) MustGet

func (s *Secrets) MustGet(key string) any

MustGet is like Get but panics if the key doesn't exist. Use this for required credentials during application startup.

func (*Secrets) MustString

func (s *Secrets) MustString(key string) string

MustString is like String but panics if the key doesn't exist. Use this for required credentials during application startup.

func (*Secrets) MustTCPAddr added in v0.2.0

func (s *Secrets) MustTCPAddr(key string) *net.TCPAddr

MustTCPAddr is like TCPAddr but panics if the key doesn't exist or isn't a valid TCP address. Use this for required network addresses during application startup.

func (*Secrets) String

func (s *Secrets) String(key string, fallback ...string) string

String retrieves a string value using dot notation. Returns fallback (or empty string) if the key doesn't exist.

func (*Secrets) TCPAddr added in v0.2.0

func (s *Secrets) TCPAddr(key string, fallback ...string) *net.TCPAddr

TCPAddr retrieves a TCP address value using dot notation. The value must be a string parseable by net.ResolveTCPAddr (e.g., "localhost:5432"). Returns fallback parsed address (or nil) if the key doesn't exist or can't be resolved.

Directories

Path Synopsis
cmd
gosecrets command
internal
editor
Package editor handles opening decrypted credentials in the user's preferred text editor.
Package editor handles opening decrypted credentials in the user's preferred text editor.
krypto
Package krypto provides AES-256-GCM encryption and decryption for gosecrets credential files.
Package krypto provides AES-256-GCM encryption and decryption for gosecrets credential files.
store
Package store manages encrypted credential files and master keys.
Package store manages encrypted credential files and master keys.

Jump to

Keyboard shortcuts

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