From cc05c2366fd4a5a65bd88b8228436c2d3542f0e1 Mon Sep 17 00:00:00 2001 From: ycanty Date: Mon, 22 Feb 2021 13:52:07 -0500 Subject: [PATCH] [DT-3750] Add --config-dump option (#189) Added a new --config-dump option that dumps the TGF configuration and exits. It also filters out AWS credentials from the environment so the dumped configuration can be used in more contexts and avoids leaking secrets. Example usage: `tgf --ignore-user-config --config-dump` Co-authored-by: Jo Giroux --- README.md | 6 ------ cli.go | 11 ++--------- config.go | 6 +++++- config_run.go | 14 ++++++++++++++ config_run_test.go | 24 +++++++++++++++++++----- config_test.go | 36 ++++++++++++++++++++++++++++++++++++ docker_test.go | 1 + 7 files changed, 77 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 478aa87..db35eb4 100644 --- a/README.md +++ b/README.md @@ -369,9 +369,3 @@ Builds are automatically launched on tagging. Tags with the format image-0.0.0 automatically launch a Docker images build that are available through Docker Hub. Tags with the format v0.0.0 automatically launch a new release on Github for the TGF executable. - -Tests that involve docker builds are not compatible with docker buildkit. Run them with: - -```bash -DOCKER_BUILDKIT=0 go test ./... -``` diff --git a/cli.go b/cli.go index 1b31f25..ece6bf3 100644 --- a/cli.go +++ b/cli.go @@ -87,6 +87,7 @@ type TGFApplication struct { AwsProfile string ConfigFiles string ConfigLocation string + ConfigDump bool DisableUserConfig bool DockerBuild bool DockerInteractive bool @@ -164,6 +165,7 @@ func NewTGFApplication(args []string) *TGFApplication { app.Flag("ssm-path", "Parameter Store path used to find AWS common configuration shared by a team").PlaceHolder("").Default(defaultSSMParameterFolder).StringVar(&app.PsPath) app.Flag("config-files", "Set the files to look for (default: "+remoteDefaultConfigPath+")").PlaceHolder("").StringVar(&app.ConfigFiles) app.Flag("config-location", "Set the configuration location").PlaceHolder("").StringVar(&app.ConfigLocation) + app.Flag("config-dump", "Print the TGF configuration and exit").BoolVar(&app.ConfigDump) app.Flag("update", "Run auto update script").IsSetByUser(&app.AutoUpdateSet).BoolVar(&app.AutoUpdate) kingpin.CommandLine = app.Application @@ -266,14 +268,5 @@ func (app *TGFApplication) ShowHelp(c *kingpin.ParseContext) error { // Run execute the application func (app *TGFApplication) Run() int { - if app.GetCurrentVersion { - if version == locallyBuilt { - fmt.Println("tgf (built from source)") - } else { - fmt.Printf("tgf v%s\n", version) - } - return 0 - } - return RunWithUpdateCheck(InitConfig(app)) } diff --git a/config.go b/config.go index b0cc048..0a935ce 100644 --- a/config.go +++ b/config.go @@ -252,7 +252,10 @@ func (config *TGFConfig) InitAWS() error { "AWS_REGION": *session.Config.Region, } { os.Setenv(key, value) - config.Environment[key] = value + if !config.tgf.ConfigDump { + // If we are saving the current configuration, we do not want to save the current credentials + config.Environment[key] = value + } } return nil } @@ -279,6 +282,7 @@ func (config *TGFConfig) setDefaultValues() { if err := config.InitAWS(); err != nil { log.Fatal(err) } + if app.ConfigLocation == "" { values := config.readSSMParameterStore(app.PsPath) app.ConfigLocation = values[remoteConfigLocationParameter] diff --git a/config_run.go b/config_run.go index f3eb9c6..e08d8c4 100644 --- a/config_run.go +++ b/config_run.go @@ -31,6 +31,20 @@ func (config *TGFConfig) Run() int { return 1 } + if app.GetCurrentVersion { + if version == locallyBuilt { + fmt.Println("tgf (built from source)") + } else { + fmt.Printf("tgf v%s\n", version) + } + return 0 + } + + if app.ConfigDump { + fmt.Println(config.String()) + return 0 + } + if app.GetAllVersions { if filepath.Base(config.EntryPoint) != "terragrunt" { log.Error("--all-version works only with terragrunt as the entrypoint") diff --git a/config_run_test.go b/config_run_test.go index 27ca51f..8b86390 100644 --- a/config_run_test.go +++ b/config_run_test.go @@ -3,16 +3,18 @@ package main import ( "bytes" "fmt" + "github.com/coveooss/gotemplate/v3/yaml" "io/ioutil" "os" "path/filepath" "strings" "testing" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) -func setup(t *testing.T, testFunction func()) string { +func setup(t *testing.T, testFunction func()) (string, string) { // To ensure that the test is not altered by the environment env := os.Environ() os.Clearenv() @@ -45,12 +47,13 @@ func setup(t *testing.T, testFunction func()) string { testFunction() w.Close() out, _ := ioutil.ReadAll(r) - return string(out) + logBuffer.String() + return string(out), logBuffer.String() } func TestCurrentVersion(t *testing.T) { version = locallyBuilt - output := setup(t, func() { + output, _ := setup(t, func() { + log.SetDefaultConsoleHookLevel(logrus.WarnLevel) app := NewTGFApplication([]string{"--current-version"}) exitCode := app.Run() assert.Equal(t, 0, exitCode, "exitCode") @@ -59,10 +62,21 @@ func TestCurrentVersion(t *testing.T) { } func TestAllVersions(t *testing.T) { - output := setup(t, func() { + _, logOutput := setup(t, func() { app := NewTGFApplication([]string{"--all-versions", "--no-aws", "--ignore-user-config", "--entrypoint=OTHER_FILE"}) exitCode := InitConfig(app).Run() assert.Equal(t, 1, exitCode, "exitCode") }) - assert.Contains(t, output, "ERROR: --all-version works only with terragrunt as the entrypoint\n") + assert.Contains(t, logOutput, "ERROR: --all-version works only with terragrunt as the entrypoint\n") +} + +func TestConfigDump_isValidYAML(t *testing.T) { + output, _ := setup(t, func() { + app := NewTGFApplication([]string{"-L=5", "--config-dump", "--no-aws", "--ignore-user-config", "--entrypoint=OTHER_FILE"}) + exitCode := InitConfig(app).Run() + assert.Equal(t, 0, exitCode, "exitCode") + }) + + // --config-dump output can be redirected to a file, so it must be valid YAML. + assert.NoError(t, yaml.UnmarshalStrict([]byte(output), &TGFConfig{})) } diff --git a/config_test.go b/config_test.go index 4516777..98fb501 100644 --- a/config_test.go +++ b/config_test.go @@ -126,6 +126,42 @@ func TestSetConfigDefaultValues(t *testing.T) { assert.Nil(t, config.ImageVersion) } +/* +Test that --config-dump filters out AWS secrets. +This allows the dumped configuration to be used in more contexts, without exposing secrets. +See https://coveord.atlassian.net/browse/DT-3750 +*/ +func TestConfigDumpFiltersOutAWSEnvironment(t *testing.T) { + log.SetOut(os.Stdout) + + // We must reset the cached AWS config check since it could have been modified by another test + resetCache() + tempDir, _ := filepath.EvalSymlinks(must(ioutil.TempDir("", "TestGetConfig")).(string)) + currentDir, _ := os.Getwd() + assert.NoError(t, os.Chdir(tempDir)) + defer func() { + assert.NoError(t, os.Chdir(currentDir)) + assert.NoError(t, os.RemoveAll(tempDir)) + }() + + testTgfConfigFile := fmt.Sprintf("%s/.tgf.config", tempDir) + + tgfConfig := []byte(String(` + docker-image: coveo/stuff + docker-image-build: RUN ls test2 + docker-image-build-tag: hello + docker-image-build-folder: my-folder + `).UnIndent().TrimSpace()) + ioutil.WriteFile(testTgfConfigFile, tgfConfig, 0644) + + app := NewTestApplication([]string{"--config-dump"}, true) + config := InitConfig(app) + + for key := range config.Environment { + assert.False(t, strings.Contains(key, "AWS"), "Environment must not contain any AWS_* key, but found %s", key) + } +} + func TestTwoLevelsOfTgfConfig(t *testing.T) { tempDir, _ := filepath.EvalSymlinks(must(ioutil.TempDir("", "TestGetConfig")).(string)) currentDir, _ := os.Getwd() diff --git a/docker_test.go b/docker_test.go index bee76c1..64fa1fd 100644 --- a/docker_test.go +++ b/docker_test.go @@ -17,6 +17,7 @@ import ( func TestGetImage(t *testing.T) { + os.Setenv("DOCKER_BUILDKIT", "0") // For docker tests to pass on Mac, docker buildkit must be disabled testImageName := "test-image" + strconv.Itoa(randInt()) testTag := "test" + strconv.Itoa(randInt()) testImageNameTagged := testImageName + ":" + testTag