Skip to content

dvormagic/gsy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GSY (Go Secret YAML)

Go Report Card Go Reference License: MIT

GSY is a lightweight Go library for secure secret management in YAML configuration files. It provides a SecretString type that implements YAML unmarshaling, allowing you to use the same config files across local development (plain strings) and production (Google Cloud Secret Manager references) without code changes.

The library checks a global environment mode (local or prod) to decide how to resolve secrets during unmarshaling. This makes it ideal for Go applications using YAML configs (e.g., with gopkg.in/yaml.v3).

Why Use GSY?

  • Seamless Environment Switching: Use identical YAML files in dev and prod. Set SetEnv("local") for plain strings, SetEnv("prod") for GCP fetches – no config branching or rebuilds.
  • Enhanced Security: Secrets are never hardcoded in code or exposed in env vars. Production secrets stay securely in GCP Secret Manager, accessed on-demand.
  • Simplicity & Integration: Embed SecretString directly in your config structs. Works out-of-the-box with standard YAML unmarshaling; minimal boilerplate.
  • Lightweight & Fast: Minimal dependencies (only yaml.v3 and GCP client). No runtime overhead in local mode.
  • Flexible: Easy to extend for other secret providers (e.g., AWS SSM) by forking the unmarshaler logic.

GSY solves the common pain of managing secrets in microservices or CLI tools where configs are YAML-based, ensuring compliance with security best practices like least privilege access.

Installation

Add GSY to your go.mod:

go get github.com/dvormagic/gsy/secretstring

For production use with GCP, ensure your environment has Google Cloud authentication set up (e.g., via Application Default Credentials or service account keys).

Quick Start

The core of GSY is its support for multi-environment YAML files. Create separate configs like config.local.yml (plain strings for local dev) and config.yml (GCP secret references for prod). Load the appropriate file based on ENV, set SetEnv(env), and unmarshal – secrets resolve automatically.

Config Files

config.local.yml (plain strings, no maps needed):

api_key: "sk-123-local-api-key" # Resolved directly
database_url: "postgresql://user:local-pass@localhost/db" # Plain full URL
other_key: "regular-value"

config.yml (GCP references for prod):

api_key:
  secret: "projects/my-project/secrets/api-key/versions/latest" # Fetched from GCP
database_url:
  secret: "projects/my-project/secrets/db-url/versions/latest" # Fetched from GCP
other_key: "regular-value"

Example Code

package main

import (
	"fmt"
	"log"
	"os"

	"gopkg.in/yaml.v3"

	"github.com/dvormagic/gsy/secretstring"
)

type Config struct {
	APIKey    secretstring.SecretString `yaml:"api_key"`
	Database  secretstring.SecretString `yaml:"database_url"`
	OtherKey  string                    `yaml:"other_key"`
}

func main() {
	env := os.Getenv("ENV") // e.g., "local" or "prod"
	secretstring.SetEnv(env)

	var filename string
	if env == "local" {
		filename = "config.local.yml"
	} else {
		filename = "config.yml"
	}

	data, err := os.ReadFile(filename)
	if err != nil {
		log.Fatal("Failed to read config:", err)
	}

	var cfg Config
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("API Key: %s\n", string(cfg.APIKey))
	fmt.Printf("DB URL: %s\n", string(cfg.Database))
	fmt.Printf("Other: %s\n", cfg.OtherKey)
}

Outputs

Local Mode (ENV=local, loads config.local.yml):

API Key: sk-123-local-api-key
DB URL: postgresql://user:local-pass@localhost/db
Other: regular-value

Prod Mode (ENV=prod, loads config.yml, simulates GCP fetches):

API Key: x78xdfhawo487arhf2  # Mock GCP secret for api-key
DB URL: postgresql://user:x9f3k8m2p7q4r5t1@prod-db-host/db  # Mock GCP secret for db-url (full resolved URL)
Other: regular-value

This demonstrates the key benefit: same struct, different files/modes, automatic resolution – plain in local, fetched in prod.

Multi-Environment YAML Setup

[Keep existing Multi-Environment section as is, since it's complementary]

YAML Formats Supported

  • Plain String (Local/Prod fallback): api_key: "my-secret-value"
  • GCP Secret Reference (Prod only):
    api_key:
      secret: "projects/my-project/secrets/my-api-key/versions/latest"
    • secretName must be the full GCP resource name.
    • In local mode, it falls back to using the reference as a plain string (useful for mocking).

Usage Flow

graph LR
    A[YAML Config Load] --> B{SetEnv called?}
    B -->|No| C[Default: local mode]
    B -->|Yes| D[Mode: local or prod]
    D --> E[Unmarshal SecretString field]
    E --> F{Plain string?}
    F -->|Yes| G[Assign directly]
    F -->|No| H{Map with 'secret' key?}
    H -->|Yes & prod| I[Fetch from GCP Secret Manager]
    H -->|No| J[Error: Invalid format]
    I --> K[Assign fetched value]
    G --> L[Config ready]
    K --> L
    J --> M[Unmarshal fails]
Loading

Advanced Usage

GCP Setup

  • Ensure your Go app runs in an environment with GCP credentials (e.g., GCE, GKE, or GOOGLE_APPLICATION_CREDENTIALS env var).
  • Secret names follow GCP format: projects/{project}/secrets/{secret}/versions/latest.
  • Handle errors from UnmarshalYAML or FetchSecretFromGCP for IAM/permission issues.

Error Handling

if err := yaml.Unmarshal(data, &cfg); err != nil {
    // Could be GCP access error in prod
    log.Printf("Config error: %v", err)
    // Fallback: Use env vars or defaults
}

Custom Modes

The library uses a simple string check. Extend by modifying secretStringEnv logic or subclassing SecretString.

Direct Secret Fetching

Use FetchSecretFromGCP independently:

val, err := secretstring.FetchSecretFromGCP("projects/my-project/secrets/my-secret/versions/latest")
if err != nil {
    // Handle error
}

Examples

See the example/main.go for a runnable demo with both modes and error handling.

Testing

Run unit tests (add them via go test):

go test ./secretstring

Integration tests with GCP require a test project and secrets.

Contributing

  1. Fork the repo.
  2. Create a feature branch (git checkout -b feature/amazing-feature).
  3. Commit changes (git commit -m 'Add amazing feature').
  4. Push (git push origin feature/amazing-feature).
  5. Open a Pull Request.

Please add tests for new features and ensure code passes go fmt and go vet.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Inspired by secure config patterns in Go microservices. Thanks to Google Cloud for Secret Manager.


Built with ❤️ for secure Go configs.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages