Skip to content

Commit

Permalink
Refactor configuration, authentication and uploaders to react dynamic…
Browse files Browse the repository at this point in the history
…ally to credentials in env-vars changing (again) and allow external configuration via file-sources (as proposed by @rdeanmcdonald in Lucretius#12)
  • Loading branch information
Argelbargel committed Sep 18, 2023
1 parent 284513f commit 1dfa2ac
Show file tree
Hide file tree
Showing 54 changed files with 1,066 additions and 819 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@
local/*
main
out/
.build/
.build/
.idea/
*.iml
116 changes: 62 additions & 54 deletions README.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions cmd/vault-raft-snapshot-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import (
var Version = "development"
var Platform = "linux/amd64"

var snapshotterOptions internal.SnapshotterOptions = internal.SnapshotterOptions{
var snapshotterOptions = internal.SnapshotterOptions{
ConfigFileName: "snapshots",
ConfigFileSearchPaths: []string{"/etc/vault.d/", "."},
EnvPrefix: "VRSA",
Expand Down Expand Up @@ -105,15 +105,15 @@ Options:
}

func startSnapshotter(configFile cli.Path) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

snapshotterOptions.ConfigFilePath = configFile
snapshotter, err := internal.CreateSnapshotter(ctx, snapshotterOptions)
snapshotter, err := internal.CreateSnapshotter(snapshotterOptions)
if err != nil {
log.Fatalf("Cannot create snapshotter: %s\n", err)
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
Expand Down
6 changes: 1 addition & 5 deletions internal/app/vault_raft_snapshot_agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ func NewParser[T Configuration](envPrefix string, configFilename string, configS
// ReadConfig reads the configuration file
func (p Parser[T]) ReadConfig(config T, file string) error {
err := p.delegate.BindAllEnv(
map[string]string{
"vault.url": "VAULT_ADDR",
"uploaders.aws.credentials.key": "AWS_ACCESS_KEY_ID",
"uploaders.aws.credentials.secret": "AWS_SECRET_ACCESS_KEY",
},
map[string]string{"vault.url": "VAULT_ADDR"},
)
if err != nil {
return fmt.Errorf("could not bind environment-variables: %s", err)
Expand Down
5 changes: 0 additions & 5 deletions internal/app/vault_raft_snapshot_agent/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,13 @@ func TestReadConfigBindsEnvVariables(t *testing.T) {
parser := NewParser[*configDataStub]("TEST", "")

t.Setenv("VAULT_ADDR", "http://from.env:8200")
t.Setenv("AWS_ACCESS_KEY_ID", "env-key")
t.Setenv("AWS_SECRET_ACCESS_KEY", "env-secret")
t.Setenv("TEST_VAULT_TEST", "test")


data := configDataStub{hasUploaders: true}
err := parser.ReadConfig(&data, "")
assert.NoError(t, err, "ReadConfig failed unexpectedly")

assert.Equal(t, os.Getenv("VAULT_ADDR"), data.Vault.Url, "ReadConfig did not bind env-var VAULT_ADDR")
assert.Equal(t, os.Getenv("AWS_ACCESS_KEY_ID"), data.Uploaders.AWS.Credentials.Key, "ReadConfig did not bind env-var AWS_ACCESS_KEY_ID")
assert.Equal(t, os.Getenv("AWS_SECRET_ACCESS_KEY"), data.Uploaders.AWS.Credentials.Secret, "ReadConfig did not bind env-var SECRET_ACCESS_KEY")
assert.Equal(t, os.Getenv("TEST_VAULT_TEST"), data.Vault.Test, "ReadConfig did not bind env-var TEST_VAULT_TEST")
}

Expand Down
23 changes: 20 additions & 3 deletions internal/app/vault_raft_snapshot_agent/config/rattlesnake.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package config

import (
"errors"
"fmt"
"github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/secret"
"path/filepath"
"reflect"
"strings"

"github.com/creasty/defaults"
Expand Down Expand Up @@ -77,12 +80,12 @@ func (r rattlesnake) Unmarshal(config interface{}) error {
return fmt.Errorf("could not set configuration's default-values: %s", err)
}

pathResolver := newPathResolver(filepath.Dir(r.ConfigFileUsed()))
if err := pathResolver.Resolve(config); err != nil {
if err := secret.ResolveFilePaths(config, filepath.Dir(r.ConfigFileUsed())); err != nil {
return fmt.Errorf("could not resolve relative paths in configuration: %s", err)
}

validate := validator.New()
validate.RegisterCustomTypeFunc(validateSecret, secret.Zero)
if err := validate.Struct(config); err != nil {
return err
}
Expand All @@ -98,10 +101,24 @@ func (r rattlesnake) OnConfigChange(run func()) {
}

func (r rattlesnake) IsConfigurationNotFoundError(err error) bool {
_, notfound := err.(viper.ConfigFileNotFoundError)
var configFileNotFoundError viper.ConfigFileNotFoundError
notfound := errors.As(err, &configFileNotFoundError)
return notfound
}

func validateSecret(field reflect.Value) interface{} {
s, ok := field.Interface().(secret.Secret)
if !ok {
return nil
}

v, err := s.Resolve(false)
if err != nil {
v = ""
}
return v
}

// implements automatic unmarshalling from environment variables
// see https://github.com/spf13/viper/pull/1429
// can be removed if that pr is merged
Expand Down
70 changes: 46 additions & 24 deletions internal/app/vault_raft_snapshot_agent/config/rattlesnake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package config

import (
"fmt"
"os"
"github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/secret"
"path/filepath"
"testing"

Expand All @@ -11,68 +11,90 @@ import (
"github.com/Argelbargel/vault-raft-snapshot-agent/internal/app/vault_raft_snapshot_agent/test"
)

type rattlesnakeConfigStub struct {
Path string `default:"/test/file" resolve-path:""`
Url string `validate:"omitempty,http_url"`
}

func TestUnmarshalResolvesRelativePaths(t *testing.T) {
func TestUnmarshalResolvesRelativePathsInSecrets(t *testing.T) {
rattlesnake := newRattlesnake("test", "TEST")

wd, err := os.Getwd()
assert.NoError(t, err, "Getwd failed unexpectedly")
config := struct {
File secret.Secret
}{
File: "file://./file.ext",
}

err = rattlesnake.SetConfigFile(fmt.Sprintf("%s/config.yml", wd))
baseDir := t.TempDir()
err := rattlesnake.SetConfigFile(fmt.Sprintf("%s/config.yml", baseDir))
assert.NoError(t, err, "SetConfigFile failed unexpectedly")

t.Setenv("TEST_PATH", "./file.ext")
config := rattlesnakeConfigStub{}
err = rattlesnake.Unmarshal(&config)

assert.NoError(t, err, "Unmarshal failed unexpectedly")
assert.Equal(t, filepath.Clean(fmt.Sprintf("%s/file.ext", wd)), config.Path)
assert.Equal(t, secret.FromFile(filepath.Clean(fmt.Sprintf("%s/file.ext", baseDir))), config.File)
}

func TestUnmarshalSetsDefaultValues(t *testing.T) {
rattlesnake := newRattlesnake("test", "TEST")

config := rattlesnakeConfigStub{}
var config struct {
Default string `default:"default-value"`
}

err := rattlesnake.Unmarshal(&config)

assert.NoError(t, err, "Unmarshal failed unexpectedly")
assert.Equal(t, "/test/file", config.Path)
assert.Equal(t, "default-value", config.Default)
}

func TestUnmarshalValidatesValues(t *testing.T) {
rattlesnake := newRattlesnake("test", "TEST")

t.Setenv("TEST_URL", "not_an_url")
config := rattlesnakeConfigStub{}
config := struct {
Url string `validate:"http_url"`
}{
Url: "invalid-url",
}

err := rattlesnake.Unmarshal(&config)

assert.Error(t, err, "Unmarshal should fail on validation error")
assert.Equal(t, "invalid-url", config.Url)
}

func TestUnmarshalValidatesSecrets(t *testing.T) {
rattlesnake := newRattlesnake("test", "TEST")

config := struct {
Secret secret.Secret `validate:"required"`
}{
Secret: secret.FromFile("./missing/file"),
}

err := rattlesnake.Unmarshal(&config)

assert.Error(t, err, "Unmarshal should fail on validation error")
assert.Equal(t, "not_an_url", config.Url)
}

func TestOnConfigChangeRunsHandler(t *testing.T) {
rattlesnake := newRattlesnake("test", "TEST")

configFile := fmt.Sprintf("%s/config.yml", t.TempDir())

err := rattlesnake.SetConfigFile(configFile)
err := test.WriteFile(t, configFile, "{\"value\": \"\"}")
assert.NoError(t, err, "writing config file failed unexpectedly")

err = rattlesnake.SetConfigFile(configFile)
assert.NoError(t, err, "SetConfigFile failed unexpectedly")

err = test.WriteFile(t, configFile, "{\"url\": \"http://example.com\"}")
assert.NoError(t, err, "writing config file failed unexpectedly")
var config struct {
Value string
}

err = rattlesnake.Unmarshal(&rattlesnakeConfigStub{})
err = rattlesnake.Unmarshal(&config)
assert.NoError(t, err, "Unmarshal failed unexpectedly")

changed := make(chan bool, 1)
rattlesnake.OnConfigChange(func() {
changed <- true
})

err = test.WriteFile(t, configFile, "{\"url\": \"http://new.com\"}")
err = test.WriteFile(t, configFile, "{\"value\": \"new\"}")
assert.NoError(t, err, "writing config file failed unexpectedly")

assert.True(t, <-changed)
Expand Down
86 changes: 0 additions & 86 deletions internal/app/vault_raft_snapshot_agent/config/resolve-path-tag.go

This file was deleted.

This file was deleted.

Loading

0 comments on commit 1dfa2ac

Please sign in to comment.