Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

credentials

The credentials module provides a secure way to manage both sensitive and non-sensitive configurations for Go applications. It supports encrypted configuration files, environment variables, and plain text configuration files, allowing users to easily manage settings securely and flexibly.

Features

  • Encrypted Credentials: Store sensitive configuration values (like API keys) in an encrypted file.
  • Environment Variables: Environment variables can override configurations, providing additional flexibility.
  • Custom Configuration Structs: Define your own configuration struct and pass it to the module, making it adaptable to any configuration needs.

Security

Credentials are sealed with AES-256-GCM. GCM is authenticated: if the file is modified by anyone without the master key, decryption fails loudly instead of returning altered contents.

Versions before v2 used AES-CFB, which is unauthenticated. Under CFB a plaintext bit can be flipped by flipping the matching ciphertext bit — no key required, and nothing detects it. Go's standard library now marks CFB deprecated for this reason. If your credentials.yml.enc predates this change, see Migrating.

The master key is written 0600. The encrypted file is written 0644 — it is meant to be committed; the key beside it never should be, which is what the bundled .gitignore enforces.

Installation

go install github.com/roonglit/credentials/cmd/credentials@latest

Environments

One shared file, or one file per environment with its own key:

config/credentials.yml.enc            + config/master.key                shared
config/credentials/staging.yml.enc    + config/credentials/staging.key
config/credentials/production.yml.enc + config/credentials/production.key

Use separate files for anything but development. The point is not tidiness, it is key separation: with one shared key, anyone who can boot the app locally can decrypt production. A new hire gets master.key on their first day and it opens the production database URL. Separate files mean a leaked development key leaks only development.

credentials edit                            # shared file
credentials edit -e production              # config/credentials/production.yml.enc
credentials show -e staging

A per-environment file is flat — the file already is that environment, so there is no section to nest under:

# config/credentials/production.yml.enc
DATABASE_URL: postgres://…
API_TOKEN: 

The shared file keeps the older shape, a section per environment, and is still what Read falls back to when no per-environment file exists. Projects that have only the shared file need no changes.

reader := credentials.NewConfigReader()      // defaults to ./config
err := reader.Read(os.Getenv("APP_ENV"), &cfg)

Set CREDENTIALS_KEY to a hex key to skip the key file entirely — the role RAILS_MASTER_KEY plays in Rails, and what you want in a container.

Commands

credentials edit      # decrypt, open in $EDITOR, re-encrypt
credentials show      # print the decrypted contents to stdout
credentials migrate   # re-encrypt an old file, no editor needed

Environment:

variable meaning
CREDENTIALS_DIR directory holding master.key and credentials.yml.enc (default config)
VISUAL, EDITOR editor used by edit (default vi)
CREDENTIALS_ALLOW_LEGACY set to 0 for strict mode: refuse pre-v2 files instead of warning
CREDENTIALS_KEY hex key, used instead of any key file

credentials edit creates both files on first run. It refuses to generate a new master key when an encrypted file already exists, since that would make the existing file permanently unreadable.

Migrating

Upgrading does not break anything. Old files still open, the exported API is unchanged from v1.0.0, and no code change is required to bump the dependency. Reading a pre-v2 file prints one warning per file:

credentials: config/credentials.yml.enc is in the legacy unauthenticated format.
It cannot detect tampering. Run `credentials migrate` to fix it permanently.

Migrate once, per project:

credentials migrate

It decrypts with the old format and rewrites with the new one, using the same master key. Commit the result; the warning stops. credentials edit migrates as a side effect of saving, so a file you were editing anyway needs nothing extra.

Writes are always in the current format — there is no way to write a legacy file, which is what stops the old format lingering.

Set CREDENTIALS_ALLOW_LEGACY=0 for strict mode, where an unmigrated file is an error rather than a warning. Worth turning on in CI once you have migrated, so a file cannot quietly regress.

Reading Configuration in Your Project

Install the Credentials Package

Run the following command to install the credentials package into your project:

go get github.com/roonglit/credentials/pkg/credentials

Define Your Configuration Struct

Define a custom struct with fields that match the configuration keys in credentials.yml.enc. Use mapstructure tags to map the struct fields to the configuration keys.

package main

import (
    "fmt"
    "log"
    "time"
    
    "github.com/roonglit/credentials/pkg/credentials"
)

// Define your custom configuration struct
type MyConfig struct {
    ServerAddress        string        `mapstructure:"SERVER_ADDRESS"`
    DBUri                string        `mapstructure:"DB_URI"`
    AccessTokenDuration  time.Duration `mapstructure:"ACCESS_TOKEN_DURATION"`
    RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"`
    TokenSymmetricKey    string        `mapstructure:"TOKEN_SYMMETRIC_KEY"`
}

Initialize and Use the ConfigReader

Use ConfigReader to load and decrypt configurations. The Read method will populate your custom struct with values from credentials.yml.enc and environment variables.

Here's how to initialize and use the ConfigReader with the default configuration folder:

func main() {
    // Initialize the ConfigReader with the default config folder
    reader := credentials.NewConfigReader()

    // User-defined configuration struct
    var config MyConfig

    // Read configuration with mode "debug" or "production"
    if err := reader.Read("debug", &config); err != nil {
        log.Fatalf("Failed to read configuration: %v", err)
    }

    fmt.Printf("Loaded Configuration: %+v\n", config)
}

If your configuration folder is different, you can provide the path as an argument:

reader := credentials.NewConfigReader("path/to/config")

License

This project is licensed under the MIT License.

Contributing

Contributions are welcome! If you have suggestions or improvements, feel free to open a pull request.

About

No description, website, or topics provided.

Resources

Stars

35 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages