Skip to content

Commit

Permalink
core: Add support for marking outputs as sensitive (hashicorp#6559)
Browse files Browse the repository at this point in the history
* 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    = <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.
  • Loading branch information
jen20 authored and cristicalin committed May 24, 2016
1 parent 4cc79c5 commit 0b36274
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 11 deletions.
27 changes: 20 additions & 7 deletions command/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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))
}
}
Expand Down Expand Up @@ -376,14 +377,19 @@ Options:
return strings.TrimSpace(helpText)
}

func outputsAsString(state *terraform.State) string {
func outputsAsString(state *terraform.State, schema []*config.Output) string {
if state == nil {
return ""
}

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
Expand All @@ -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 = <sensitive>\n",
k,
strings.Repeat(" ", keyLen-len(k))))
} else {
outputBuf.WriteString(fmt.Sprintf(
" %s%s = %s\n",
k,
strings.Repeat(" ", keyLen-len(k)),
v))
}
}
}

Expand Down
30 changes: 30 additions & 0 deletions command/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <sensitive>") {
t.Fatalf("bad: output should contain 'sensitive' output\n%s", output)
}
}

func TestApply_vars(t *testing.T) {
statePath := testTempFile(t)

Expand Down
1 change: 1 addition & 0 deletions command/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion command/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
12 changes: 12 additions & 0 deletions command/test-fixtures/apply-sensitive-output/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
variable "input" {
default = "Hello world"
}

output "notsensitive" {
value = "${var.input}"
}

output "sensitive" {
sensitive = true
value = "${var.input}"
}
22 changes: 19 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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(
Expand Down
24 changes: 24 additions & 0 deletions website/source/docs/configuration/outputs.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<sensitive>`
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.

0 comments on commit 0b36274

Please sign in to comment.