Production Secrets Management in Helix

The Helix project has established methods to handle sensitive information in production environments effectively. Below is the structured approach employed for managing and storing secrets.

Environment Variables

The primary mechanism for managing secrets is the use of environment variables. Sensitive data such as database credentials, API tokens, and other configuration parameters should never be hardcoded. Instead, leverage environment variables to inject these values at runtime.

Example: Setting Environment Variables in a Production Environment

When deploying the Helix application, you can set environment variables directly within the Docker container. For example:

docker run -e DB_USER=myuser -e DB_PASS=mypassword helix_image_name

In your Go application, access these secrets with the os package:

import (
    "os"
)

func GetDatabaseCredentials() (string, string) {
    user := os.Getenv("DB_USER")
    pass := os.Getenv("DB_PASS")
    return user, pass
}

Configuration Files

It is important to manage configuration files that do not include sensitive information and do not store secrets directly. Use templated configuration files instead, which refer to environment variables for secret values.

Example: Configuring a JSON file

Example configuration file template (config.json.template):

{
  "database": {
    "user": "${DB_USER}",
    "password": "${DB_PASS}"
  }
}

Use a build tool or script to replace these template variables before starting the application.

Secret Management Tools

For additional layers of security, integrate with secret management tools such as HashiCorp Vault or AWS Secrets Manager. These tools provide secure storage, access controls, and auditing capabilities for sensitive data.

Example: Accessing Secrets in Go using HashiCorp Vault

Assuming you have set up Vault, access secrets as follows:

import (
    "fmt"
    "log"
    "github.com/hashicorp/vault/api"
)

func GetSecret() {
    client, err := api.NewClient(api.DefaultConfig())
    if err != nil {
        log.Fatal(err)
    }

    client.SetToken("your-vault-token")

    secret, err := client.Logical().Read("secret/data/mysecret")
    if err != nil {
        log.Fatal(err)
    }
    data := secret.Data["data"].(map[string]interface{})
    fmt.Println(data["key"])
}

Best Practices

  1. Use Non-Privileged Users: Ensure your application does not run with higher privileges than necessary, which limits access to sensitive operations.

  2. Rotate Secrets Regularly: Implement a policy for rotating secrets frequently to minimize the impact of a leaked secret.

  3. Audit Access to Secrets: Use logging and monitoring on your secret management service to track any access to sensitive information.

  4. Use Docker Secrets: If deploying to Docker Swarm, utilize Docker Secrets to manage sensitive data:

echo "mypassword" | docker secret create db_pass -

In your service, access secrets with:

RUN --mount=type=secret,id=db_pass \
    sh -c 'echo "DB_PASS=$(cat /run/secrets/db_pass)" >> .env'

By adhering to these practices and utilizing the provided examples for integrating secrets management into your code, you can ensure a secure approach to handling sensitive information in production.

Source: Dockerfile.