Skip to content

Commit

Permalink
Support reading secrets from a separate config file
Browse files Browse the repository at this point in the history
Users can specify a new overlay config file. This file can contain
secrets. The file is specified in two ways:

- VOUCH_SECRETS_FILE env var: path of the overlay config file

- CREDENTIALS_DIRECTORY env var that contains a file called
  VOUCH_SECRETS_FILE. This can be used with systemd LoadCredential.
  • Loading branch information
squalus committed Aug 7, 2022
1 parent 2d3ea12 commit 2575e30
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Coming soon! Please document any work in progress here as part of your PR. It will be moved to the next tag when released.

- [Support reading secrets from a separate file](https://github.com/vouch/vouch-proxy/pull/487)

## v0.37.0

- [allow configurable Write, Read and Idle timeouts for the http server](https://github.com/vouch/vouch-proxy/pull/468)
Expand Down
2 changes: 2 additions & 0 deletions config/testing/secret_overlay.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
oauth:
client_secret: my client secret from overlay
30 changes: 27 additions & 3 deletions pkg/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ func configureFromEnv() bool {
if err != nil {
log.Fatal(err.Error())
}

// did anything change?
if !reflect.DeepEqual(preEnvConfig, *Cfg) ||
!reflect.DeepEqual(preEnvGenOAuth, *GenOAuth) {
Expand Down Expand Up @@ -270,8 +271,31 @@ func setRootDir() {

// parseConfig parse the config file
func parseConfigFile() error {
configEnv := os.Getenv(Branding.UCName + "_CONFIG")
secretsFileVar := Branding.UCName + "_SECRETS_FILE"
overlayConfigPath := os.Getenv(secretsFileVar)
if overlayConfigPath == "" {
// see if systemd LoadCredential was used to pass in the overlay config
if credDir := os.Getenv("CREDENTIALS_DIRECTORY"); credDir != "" {
overlayConfigPath = filepath.Join(credDir, secretsFileVar)
if _, err := os.Stat(overlayConfigPath); os.IsNotExist(err) {
log.Warnf("%s not found in CREDENTIALS_DIRECTORY %s", secretsFileVar, credDir)
overlayConfigPath = ""
} else {
log.Infof("reading secrets file from CREDENTIALS_DIRECTORY: %s", overlayConfigPath)
}
}
} else {
log.Infof("reading secrets file from %s env var: %s", secretsFileVar, overlayConfigPath)
}
if overlayConfigPath != "" {
viper.SetConfigFile(overlayConfigPath)
if err := viper.ReadInConfig(); err != nil {
return err
}
viper.SetConfigFile("")
}

configEnv := os.Getenv(Branding.UCName + "_CONFIG")
if configEnv != "" {
log.Warnf("config file loaded from environmental variable %s: %s", Branding.UCName+"_CONFIG", configEnv)
configFile, _ := filepath.Abs(configEnv)
Expand All @@ -287,8 +311,8 @@ func parseConfigFile() error {
viper.SetConfigType("yaml")
viper.AddConfigPath(filepath.Join(RootDir, "config"))
}
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
err := viper.MergeInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file

return fmt.Errorf("%w: %s", errConfigNotFound, err)
}
Expand Down
48 changes: 48 additions & 0 deletions pkg/cfg/oauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ OR CONDITIONS OF ANY KIND, either express or implied.
package cfg

import (
"io/ioutil"
"net/url"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -43,3 +46,48 @@ func Test_configureOAuthWithClaims(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, authCodeURL.Query().Get("claims"), `{"userinfo":{"email":{"essential":true},"email_verified":{"essential":true},"given_name":{"essential":true},"http://example.info/claims/groups":null,"nickname":null,"picture":null},"id_token":{"acr":{"values":["urn:mace:incommon:iap:silver"]},"auth_time":{"essential":true}}}`)
}

func Test_readOverlayConfig_fileVar(t *testing.T) {
defer cleanupEnv()
rootDir := os.Getenv(Branding.UCName + "_ROOT")
assert.NotEmpty(t, rootDir)
assert.NoError(t, os.Setenv(Branding.UCName+"_SECRETS_FILE", filepath.Join(rootDir, "config/testing/secret_overlay.yml")))
setUp("config/testing/handler_login_url.yml")
assert.Equal(t, "my client secret from overlay", OAuthClient.ClientSecret)
}

func Test_readOverlayConfig_credentialsDir(t *testing.T) {
defer cleanupEnv()
rootDir := os.Getenv(Branding.UCName + "_ROOT")
assert.NotEmpty(t, rootDir)
tempDir, err := ioutil.TempDir("", "")
assert.NoError(t, err)
defer func() {
_ = os.RemoveAll(tempDir)
}()
destFileName := Branding.UCName + "_SECRETS_FILE"
srcFileName := filepath.Join(rootDir, "config/testing/secret_overlay.yml")
assert.NoError(t, os.Symlink(srcFileName, filepath.Join(tempDir, destFileName)))
assert.NoError(t, os.Setenv("CREDENTIALS_DIRECTORY", tempDir))
setUp("config/testing/handler_login_url.yml")
assert.Equal(t, "my client secret from overlay", OAuthClient.ClientSecret)
}

func Test_readOverlayConfig_emptyCredentialsDir(t *testing.T) {
defer cleanupEnv()
tempDir, err := ioutil.TempDir("", "")
assert.NoError(t, err)
defer func() {
_ = os.RemoveAll(tempDir)
}()
assert.NoError(t, os.Setenv("CREDENTIALS_DIRECTORY", tempDir))
setUp("config/testing/handler_login_url.yml")
assert.Equal(t, "", OAuthClient.ClientSecret)
}

func Test_readOverlayConfig_missingCredentialsDir(t *testing.T) {
defer cleanupEnv()
assert.NoError(t, os.Setenv("CREDENTIALS_DIRECTORY", "/this/doesnt/exist"))
setUp("config/testing/handler_login_url.yml")
assert.Equal(t, "", OAuthClient.ClientSecret)
}

0 comments on commit 2575e30

Please sign in to comment.