core: Add support for marking outputs as sensitive (#6559)
* 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.
This commit is contained in:
parent
5e4edf81f2
commit
b62f6af158
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/go-getter"
|
"github.com/hashicorp/go-getter"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -250,7 +251,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Destroy {
|
if !c.Destroy {
|
||||||
if outputs := outputsAsString(state); outputs != "" {
|
if outputs := outputsAsString(state, ctx.Module().Config().Outputs); outputs != "" {
|
||||||
c.Ui.Output(c.Colorize().Color(outputs))
|
c.Ui.Output(c.Colorize().Color(outputs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,7 +377,7 @@ Options:
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputsAsString(state *terraform.State) string {
|
func outputsAsString(state *terraform.State, schema []*config.Output) string {
|
||||||
if state == nil {
|
if state == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -384,6 +385,11 @@ func outputsAsString(state *terraform.State) string {
|
||||||
outputs := state.RootModule().Outputs
|
outputs := state.RootModule().Outputs
|
||||||
outputBuf := new(bytes.Buffer)
|
outputBuf := new(bytes.Buffer)
|
||||||
if len(outputs) > 0 {
|
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")
|
outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
|
||||||
|
|
||||||
// Output the outputs in alphabetical order
|
// Output the outputs in alphabetical order
|
||||||
|
@ -400,11 +406,18 @@ func outputsAsString(state *terraform.State) string {
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
v := outputs[k]
|
v := outputs[k]
|
||||||
|
|
||||||
outputBuf.WriteString(fmt.Sprintf(
|
if schemaMap[k].Sensitive {
|
||||||
" %s%s = %s\n",
|
outputBuf.WriteString(fmt.Sprintf(
|
||||||
k,
|
" %s%s = <sensitive>\n",
|
||||||
strings.Repeat(" ", keyLen-len(k)),
|
k,
|
||||||
v))
|
strings.Repeat(" ", keyLen-len(k))))
|
||||||
|
} else {
|
||||||
|
outputBuf.WriteString(fmt.Sprintf(
|
||||||
|
" %s%s = %s\n",
|
||||||
|
k,
|
||||||
|
strings.Repeat(" ", keyLen-len(k)),
|
||||||
|
v))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
func TestApply_vars(t *testing.T) {
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
|
|
||||||
for _, k := range ks {
|
for _, k := range ks {
|
||||||
v := mod.Outputs[k]
|
v := mod.Outputs[k]
|
||||||
|
|
||||||
c.Ui.Output(fmt.Sprintf("%s = %s", k, v))
|
c.Ui.Output(fmt.Sprintf("%s = %s", k, v))
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -109,7 +109,7 @@ func (c *RefreshCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if outputs := outputsAsString(newState); outputs != "" {
|
if outputs := outputsAsString(newState, ctx.Module().Config().Outputs); outputs != "" {
|
||||||
c.Ui.Output(c.Colorize().Color(outputs))
|
c.Ui.Output(c.Colorize().Color(outputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
variable "input" {
|
||||||
|
default = "Hello world"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "notsensitive" {
|
||||||
|
value = "${var.input}"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "sensitive" {
|
||||||
|
sensitive = true
|
||||||
|
value = "${var.input}"
|
||||||
|
}
|
|
@ -146,9 +146,12 @@ type Variable struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output is an output defined within the configuration. An output is
|
// 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 {
|
type Output struct {
|
||||||
Name string
|
Name string
|
||||||
|
Sensitive bool
|
||||||
RawConfig *RawConfig
|
RawConfig *RawConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,9 +561,22 @@ func (c *Config) Validate() error {
|
||||||
for k := range o.RawConfig.Raw {
|
for k := range o.RawConfig.Raw {
|
||||||
if k == "value" {
|
if k == "value" {
|
||||||
valueKeyFound = true
|
valueKeyFound = true
|
||||||
} else {
|
continue
|
||||||
invalidKeys = append(invalidKeys, k)
|
|
||||||
}
|
}
|
||||||
|
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 {
|
if len(invalidKeys) > 0 {
|
||||||
errs = append(errs, fmt.Errorf(
|
errs = append(errs, fmt.Errorf(
|
||||||
|
|
|
@ -57,3 +57,27 @@ output NAME {
|
||||||
value = VALUE
|
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.
|
||||||
|
|
Loading…
Reference in New Issue