Skip to content

Commit

Permalink
feat: allow reading secrets from files
Browse files Browse the repository at this point in the history
  • Loading branch information
Gregofi authored and davidmasek committed Jan 18, 2025
1 parent 9e7715c commit 3ad21d0
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 14 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ You can use **environment variables** instead. For example:
export BEACON_EMAIL_SMTP_PASSWORD="your-password"
```

For password, you can instead provide a file containing the password.
```sh
export BEACON_EMAIL_SMTP_PASSWORD_FILE="/path/to/password-file"
```

### Configuration sources

Beacon supports multiple configuration sources, with the following priority (highest to lowest):
Expand Down
29 changes: 17 additions & 12 deletions conf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"log"
"os"
"path/filepath"
"reflect"
"strings"
"time"

"github.com/caarlos0/env/v11"
Expand All @@ -19,7 +19,8 @@ import (
const ENV_VAR_PREFIX = "BEACON_"

type Secret struct {
value string
Value string `env:""`
FromFile string `env:"_FILE,file"`
}

func (s Secret) String() string {
Expand All @@ -31,18 +32,23 @@ func (s Secret) GoString() string {
}

func (s *Secret) Get() string {
return s.value
if s.FromFile != "" {
// File contents tend to end with newline
return strings.TrimSpace(s.FromFile)
} else {
return s.Value
}
}

func (s *Secret) IsSet() bool {
return s.value != ""
return s.Value != "" || s.FromFile != ""
}

type EmailConfig struct {
SmtpServer string `yaml:"smtp_server" env:"SMTP_SERVER"`
SmtpPort int `yaml:"smtp_port" env:"SMTP_PORT"`
SmtpUsername string `yaml:"smtp_username" env:"SMTP_USERNAME"`
SmtpPassword Secret `yaml:"smtp_password" env:"SMTP_PASSWORD"`
SmtpPassword Secret `yaml:"smtp_password" envPrefix:"SMTP_PASSWORD"`
SendTo string `yaml:"send_to" env:"SEND_TO"`
Sender string `yaml:"sender" env:"SENDER"`
Prefix string `yaml:"prefix" env:"PREFIX"`
Expand All @@ -66,10 +72,14 @@ func (s *Secret) UnmarshalYAML(node *yaml.Node) error {
if node.Kind != yaml.ScalarNode {
return fmt.Errorf("expected scalar node got %s", node.Value)
}
s.value = node.Value
s.Value = node.Value
return nil
}

func (s Secret) MarshalYAML() (interface{}, error) {
return "*****", nil
}

// TzLocation wraps a *time.Location so we can provide custom YAML unmarshalling.
type TzLocation struct {
Location *time.Location
Expand All @@ -86,7 +96,7 @@ type Config struct {
Port int `yaml:"port" env:"PORT"`
SchedulerPeriod time.Duration `yaml:"scheduler_period" env:"SCHEDULER_PERIOD"`

EmailConf EmailConfig `yaml:"email" envPrefix:"EMAIL_"`
EmailConf EmailConfig `yaml:"email" envPrefix:"EMAIL_"`

Services ServicesList

Expand Down Expand Up @@ -216,11 +226,6 @@ func ConfigFromBytes(data []byte) (*Config, error) {
}
err = env.ParseWithOptions(config, env.Options{
Prefix: ENV_VAR_PREFIX,
FuncMap: map[reflect.Type]env.ParserFunc{
reflect.TypeOf(Secret{}): func(v string) (interface{}, error) {
return Secret{v}, nil
},
},
})
if err != nil {
return nil, err
Expand Down
28 changes: 26 additions & 2 deletions conf/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ services:
}

func TestSecretPrint(t *testing.T) {
secret := Secret{"Greg"}
secret := Secret{"Greg", ""}
assert.Equal(t, secret.Get(), "Greg")
assert.NotContains(t, fmt.Sprint(secret), "Greg")
assert.NotContains(t, fmt.Sprint(&secret), "Greg")
Expand Down Expand Up @@ -143,7 +143,7 @@ prefix: "[test]"
"mail.smtp2go.com",
587,
"beacon",
Secret{"h4xor"},
Secret{"h4xor", ""},
"you@example.fake",
"noreply@example.fake",
"[test]",
Expand All @@ -160,3 +160,27 @@ func TestSecretFromEnv(t *testing.T) {
require.Equal(t, "secr4t", conf.EmailConf.SmtpPassword.Get())

}

func TestSecretFromFile(t *testing.T) {
// Create the file
f, err := os.Create("secret-test.txt")
require.NoError(t, err)
_, err = f.WriteString("foo\n")
require.NoError(t, err)

// Set both, file should have prio
err = os.Setenv("BEACON_EMAIL_SMTP_PASSWORD", "bar")
require.NoError(t, err)
err = os.Setenv("BEACON_EMAIL_SMTP_PASSWORD_FILE", "secret-test.txt")
require.NoError(t, err)

conf, err := ConfigFromBytes([]byte(""))
require.NoError(t, err)
require.Equal(t, "foo", conf.EmailConf.SmtpPassword.Get())

// Cleanup
err = os.Unsetenv("BEACON_EMAIL_SMTP_PASSWORD_FILE")
require.NoError(t, err)
err = os.Remove("secret-test.txt")
require.NoError(t, err)
}

0 comments on commit 3ad21d0

Please sign in to comment.