From 6f41cc7c4ac06fa349714d66dd01589228a47685 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 9 May 2016 15:24:41 -0400 Subject: [PATCH 1/3] core: Add support for marking outputs as sensitive This commit allows an output to be marked "sensitive", in which case the value is redacted in the post-refresh and post-apply list of outputs. For example, the configuration: ``` variable "input" { default = "Hello world" } output "notsensitive" { value = "${var.input}" } output "sensitive" { sensitive = true value = "${var.input}" } ``` Would result in the output: ``` terraform apply Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: notsensitive = Hello world sensitive = ``` The `terraform output` command continues to display the value as before. Limitations: Note that sensitivity is not tracked internally, so if the output is interpolated in another module into a resource, the value will be displayed. The value is still present in the state. --- command/apply.go | 27 ++++++++++++----- command/apply_test.go | 30 +++++++++++++++++++ command/output.go | 1 + command/refresh.go | 2 +- .../apply-sensitive-output/main.tf | 12 ++++++++ config/config.go | 22 ++++++++++++-- 6 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 command/test-fixtures/apply-sensitive-output/main.tf diff --git a/command/apply.go b/command/apply.go index f18865bdb193..9d0c3956a905 100644 --- a/command/apply.go +++ b/command/apply.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/go-getter" "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/terraform" ) @@ -250,7 +251,7 @@ func (c *ApplyCommand) Run(args []string) int { } if !c.Destroy { - if outputs := outputsAsString(state); outputs != "" { + if outputs := outputsAsString(state, ctx.Module().Config().Outputs); outputs != "" { c.Ui.Output(c.Colorize().Color(outputs)) } } @@ -376,7 +377,7 @@ Options: return strings.TrimSpace(helpText) } -func outputsAsString(state *terraform.State) string { +func outputsAsString(state *terraform.State, schema []*config.Output) string { if state == nil { return "" } @@ -384,6 +385,11 @@ func outputsAsString(state *terraform.State) string { outputs := state.RootModule().Outputs outputBuf := new(bytes.Buffer) if len(outputs) > 0 { + schemaMap := make(map[string]*config.Output) + for _, s := range schema { + schemaMap[s.Name] = s + } + outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") // Output the outputs in alphabetical order @@ -400,11 +406,18 @@ func outputsAsString(state *terraform.State) string { for _, k := range keys { v := outputs[k] - outputBuf.WriteString(fmt.Sprintf( - " %s%s = %s\n", - k, - strings.Repeat(" ", keyLen-len(k)), - v)) + if schemaMap[k].Sensitive { + outputBuf.WriteString(fmt.Sprintf( + " %s%s = \n", + k, + strings.Repeat(" ", keyLen-len(k)))) + } else { + outputBuf.WriteString(fmt.Sprintf( + " %s%s = %s\n", + k, + strings.Repeat(" ", keyLen-len(k)), + v)) + } } } diff --git a/command/apply_test.go b/command/apply_test.go index 21a063a39553..dfb4a37e2a1c 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -886,6 +886,36 @@ func TestApply_stateNoExist(t *testing.T) { } } +func TestApply_sensitiveOutput(t *testing.T) { + statePath := testTempFile(t) + + p := testProvider() + ui := new(cli.MockUi) + c := &ApplyCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + testFixturePath("apply-sensitive-output"), + } + + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.OutputWriter.String()) + } + + output := ui.OutputWriter.String() + if !strings.Contains(output, "notsensitive = Hello world") { + t.Fatalf("bad: output should contain 'notsensitive' output\n%s", output) + } + if !strings.Contains(output, "sensitive = ") { + t.Fatalf("bad: output should contain 'sensitive' output\n%s", output) + } +} + func TestApply_vars(t *testing.T) { statePath := testTempFile(t) diff --git a/command/output.go b/command/output.go index 33e6b6fcfadb..7c2324b41b1d 100644 --- a/command/output.go +++ b/command/output.go @@ -82,6 +82,7 @@ func (c *OutputCommand) Run(args []string) int { for _, k := range ks { v := mod.Outputs[k] + c.Ui.Output(fmt.Sprintf("%s = %s", k, v)) } return 0 diff --git a/command/refresh.go b/command/refresh.go index c64505b2ea75..7a6f7f9c3896 100644 --- a/command/refresh.go +++ b/command/refresh.go @@ -109,7 +109,7 @@ func (c *RefreshCommand) Run(args []string) int { return 1 } - if outputs := outputsAsString(newState); outputs != "" { + if outputs := outputsAsString(newState, ctx.Module().Config().Outputs); outputs != "" { c.Ui.Output(c.Colorize().Color(outputs)) } diff --git a/command/test-fixtures/apply-sensitive-output/main.tf b/command/test-fixtures/apply-sensitive-output/main.tf new file mode 100644 index 000000000000..87994ae9fcd6 --- /dev/null +++ b/command/test-fixtures/apply-sensitive-output/main.tf @@ -0,0 +1,12 @@ +variable "input" { + default = "Hello world" +} + +output "notsensitive" { + value = "${var.input}" +} + +output "sensitive" { + sensitive = true + value = "${var.input}" +} diff --git a/config/config.go b/config/config.go index 38578ae493f8..b3a48be1d13f 100644 --- a/config/config.go +++ b/config/config.go @@ -146,9 +146,12 @@ type Variable struct { } // Output is an output defined within the configuration. An output is -// resulting data that is highlighted by Terraform when finished. +// resulting data that is highlighted by Terraform when finished. An +// output marked Sensitive will be output in a masked form following +// application, but will still be available in state. type Output struct { Name string + Sensitive bool RawConfig *RawConfig } @@ -558,9 +561,22 @@ func (c *Config) Validate() error { for k := range o.RawConfig.Raw { if k == "value" { valueKeyFound = true - } else { - invalidKeys = append(invalidKeys, k) + continue + } + if k == "sensitive" { + if sensitive, ok := o.RawConfig.config[k].(bool); ok { + if sensitive { + o.Sensitive = true + } + continue + } + + errs = append(errs, fmt.Errorf( + "%s: value for 'sensitive' must be boolean", + o.Name)) + continue } + invalidKeys = append(invalidKeys, k) } if len(invalidKeys) > 0 { errs = append(errs, fmt.Errorf( From e385ad47090edca1237cc2a0b21df938b92c15a6 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 9 May 2016 15:38:00 -0400 Subject: [PATCH 2/3] docs: Add documentation about sensitive outputs --- .../source/docs/configuration/outputs.html.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/website/source/docs/configuration/outputs.html.md b/website/source/docs/configuration/outputs.html.md index 3e8ac5c7bae6..a4d37388947d 100644 --- a/website/source/docs/configuration/outputs.html.md +++ b/website/source/docs/configuration/outputs.html.md @@ -57,3 +57,27 @@ output NAME { value = VALUE } ``` + +## Sensitive Outputs + +Outputs can be marked as containing sensitive material by setting the +`sensitive` attribute to `true`, like this: + +``` +output "sensitive" { + sensitive = true + value = VALUE +} +``` + +When outputs are displayed on-screen following a `terraform apply` or +`terraform refresh`, sensitive outputs are redacted, with `` +displayed in place of their value. + +### Limitations of Sensitive Outputs + +* the values of sensitive outputs are still stored in the Terraform + state, and available using the `terraform output` command, so cannot be + relied on as a sole means of protecting values. +* sensitivity is not tracked internally, so if the output is interpolated in + another module into a resource, the value will be displayed. From bccf611ad81988b8fe3314c588bfb6747edce737 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 9 May 2016 15:45:25 -0400 Subject: [PATCH 3/3] docs: Fix capitalization --- website/source/docs/configuration/outputs.html.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/configuration/outputs.html.md b/website/source/docs/configuration/outputs.html.md index a4d37388947d..762e00fcbbf0 100644 --- a/website/source/docs/configuration/outputs.html.md +++ b/website/source/docs/configuration/outputs.html.md @@ -76,8 +76,8 @@ displayed in place of their value. ### Limitations of Sensitive Outputs -* the values of sensitive outputs are still stored in the Terraform +* The values of sensitive outputs are still stored in the Terraform state, and available using the `terraform output` command, so cannot be relied on as a sole means of protecting values. -* sensitivity is not tracked internally, so if the output is interpolated in +* Sensitivity is not tracked internally, so if the output is interpolated in another module into a resource, the value will be displayed.