From a91156b13461fafaec0c1adc6c201e59a35bcf38 Mon Sep 17 00:00:00 2001 From: Christopher Boumenot Date: Fri, 29 Apr 2016 15:02:28 -0700 Subject: [PATCH] Dump Azure configuration values. Dump the top level variables in the config object. Any values with 'secret' or 'password' in the name are masked. --- builder/azure/arm/builder.go | 11 +- builder/azure/common/dump_config.go | 69 ++++++++++++ builder/azure/common/dump_config_test.go | 127 +++++++++++++++++++++++ 3 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 builder/azure/common/dump_config.go create mode 100644 builder/azure/common/dump_config_test.go diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 5f3e07ec7..c2e7af193 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -17,7 +17,7 @@ import ( "github.com/mitchellh/packer/builder/azure/common/lin" "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/common" + packerCommon "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/helper/communicator" "github.com/mitchellh/packer/packer" ) @@ -53,6 +53,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { ui.Say("Preparing builder ...") + log.Print(":: Configuration") + packerAzureCommon.DumpConfig(b.config, func(s string) { log.Print(s) }) + b.stateBag.Put("hook", hook) b.stateBag.Put(constants.Ui, ui) @@ -93,7 +96,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Host: lin.SSHHost, SSHConfig: lin.SSHConfig(b.config.UserName), }, - &common.StepProvision{}, + &packerCommon.StepProvision{}, NewStepGetOSDisk(azureClient, ui), NewStepPowerOffCompute(azureClient, ui), NewStepCaptureImage(azureClient, ui), @@ -122,7 +125,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, nil }, }, - &common.StepProvision{}, + &packerCommon.StepProvision{}, NewStepGetOSDisk(azureClient, ui), NewStepPowerOffCompute(azureClient, ui), NewStepCaptureImage(azureClient, ui), @@ -179,7 +182,7 @@ func (b *Builder) createRunner(steps *[]multistep.Step, ui packer.Ui) multistep. if b.config.PackerDebug { return &multistep.DebugRunner{ Steps: *steps, - PauseFn: common.MultistepDebugFn(ui), + PauseFn: packerCommon.MultistepDebugFn(ui), } } diff --git a/builder/azure/common/dump_config.go b/builder/azure/common/dump_config.go new file mode 100644 index 000000000..8be2c9aa3 --- /dev/null +++ b/builder/azure/common/dump_config.go @@ -0,0 +1,69 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/reflectwalk" + "reflect" + "strings" +) + +type walker struct { + depth int + say func(string) +} + +func newDumpConfig(say func(string)) *walker { + return &walker{ + depth: 0, + say: say, + } +} + +func (s *walker) Enter(l reflectwalk.Location) error { + s.depth += 1 + return nil +} + +func (s *walker) Exit(l reflectwalk.Location) error { + s.depth -= 1 + return nil +} + +func (s *walker) Struct(v reflect.Value) error { + return nil +} + +func (s *walker) StructField(f reflect.StructField, v reflect.Value) error { + if !s.shouldDump(v) { + return nil + } + + switch v.Kind() { + case reflect.String: + s.say(fmt.Sprintf("%s=%s", f.Name, s.formatValue(f.Name, v.String()))) + } + + return nil +} + +func (s *walker) shouldDump(v reflect.Value) bool { + return s.depth == 2 && v.IsValid() && v.CanInterface() +} + +func (s *walker) formatValue(name, value string) string { + if s.isMaskable(name) { + return strings.Repeat("*", len(value)) + } + + return value +} + +func (s *walker) isMaskable(name string) bool { + up := strings.ToUpper(name) + return strings.Contains(up, "SECRET") || strings.Contains(up, "PASSWORD") +} + +func DumpConfig(config interface{}, say func(string)) { + walker := newDumpConfig(say) + reflectwalk.Walk(config, walker) +} diff --git a/builder/azure/common/dump_config_test.go b/builder/azure/common/dump_config_test.go new file mode 100644 index 000000000..20f2e488b --- /dev/null +++ b/builder/azure/common/dump_config_test.go @@ -0,0 +1,127 @@ +package common + +import "testing" + +func TestShouldDumpPublicValues(t *testing.T) { + type S struct { + MyString string + myString string + } + + data := &S{ + MyString: "value1", + } + + dumps := make([]string, 0, 2) + DumpConfig(data, func(s string) { dumps = append(dumps, s) }) + + if len(dumps) != 1 { + t.Fatalf("Expected len(dumps) to be 1, but got %d", len(dumps)) + + } + if dumps[0] != "MyString=value1" { + t.Errorf("Expected dumps[0] to be 'MyString=value1', but got %s", dumps[0]) + } +} + +func TestShouldOnlyDumpStrings(t *testing.T) { + type S struct { + MyString string + MyInt int + MyFloat32 float32 + MyStringPointer *string + } + + s := "value1" + data := &S{ + MyString: s, + MyInt: 1, + MyFloat32: 2.0, + MyStringPointer: &s, + } + + dumps := make([]string, 0, 4) + DumpConfig(data, func(s string) { dumps = append(dumps, s) }) + + if len(dumps) != 1 { + t.Fatalf("Expected len(dumps) to be 1, but got %d", len(dumps)) + + } + if dumps[0] != "MyString=value1" { + t.Errorf("Expected dumps[0] to be 'MyString=value1', but got %s", dumps[0]) + } +} + +func TestDumpShouldMaskSensitiveFieldValues(t *testing.T) { + type S struct { + MyString string + MySecret string + MySecretValue string + MyPassword string + MyPasswordValue string + } + + data := &S{ + MyString: "my-string", + MySecret: "s3cr3t", + MySecretValue: "s3cr3t-value", + MyPassword: "p@ssw0rd", + MyPasswordValue: "p@ssw0rd-value", + } + + dumps := make([]string, 0, 5) + DumpConfig(data, func(s string) { dumps = append(dumps, s) }) + + if len(dumps) != 5 { + t.Fatalf("Expected len(dumps) to be 5, but got %d", len(dumps)) + + } + if dumps[0] != "MyString=my-string" { + t.Errorf("Expected dumps[0] to be 'MyString=my-string', but got %s", dumps[0]) + } + if dumps[1] != "MySecret=******" { + t.Errorf("Expected dumps[1] to be 'MySecret=******', but got %s", dumps[1]) + } + if dumps[2] != "MySecretValue=************" { + t.Errorf("Expected dumps[2] to be 'MySecret=************', but got %s", dumps[2]) + } + if dumps[3] != "MyPassword=********" { + t.Errorf("Expected dumps[3] to be 'MyPassword=******** but got %s", dumps[3]) + } + if dumps[4] != "MyPasswordValue=**************" { + t.Errorf("Expected dumps[4] to be 'MyPasswordValue=**************' but got %s", dumps[4]) + } +} + +func TestDumpConfigShouldDumpTopLevelValuesOnly(t *testing.T) { + type N struct { + NestedString string + } + + type S struct { + Nested1 N + MyString string + Nested2 N + } + + data := &S{ + Nested1: N{ + NestedString: "nested-string1", + }, + MyString: "my-string", + Nested2: N{ + NestedString: "nested-string2", + }, + } + + dumps := make([]string, 0, 1) + DumpConfig(data, func(s string) { dumps = append(dumps, s) }) + + if len(dumps) != 1 { + t.Fatalf("Expected len(dumps) to be 1, but got %d", len(dumps)) + + } + if dumps[0] != "MyString=my-string" { + t.Errorf("Expected dumps[0] to be 'MyString=my-string', but got %s", dumps[0]) + } +}