Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sensitivity and required-replace fields to plan file, and expose sensitivity to JSON plan #28201

Merged
merged 4 commits into from
Mar 29, 2021

Conversation

alisdair
Copy link
Member

@alisdair alisdair commented Mar 25, 2021

  • plans/planfile: Add required-replace and sensitive

    The stored planfile now serializes the required-replace path set and the collection of before/after sensitivity marks. This ensures that storing a plan and displaying it with terraform show renders the same output for plans with required-replace resources, and those with sensitive values in the diff.

  • command/jsonplan: Add sensitive value mapping data

    Similar to after_unknown, before_sensitive and after_sensitive are values with similar structure to before and after which encode the presence of sensitive values in a planned change. These should be used to obscure sensitive values from human-readable output.

    These values follow the same structure as the before and after values, replacing sensitive values with true, and non-sensitive values with false. Following the after_unknown precedent, we omit non-sensitive false values for object attributes/map values, to make serialization more compact.

    One difference from after_unknown is that a sensitive complex value (collection or structural type) is replaced with true. If the complex value itself is sensitive, all of its contents should be obscured.

  • command/jsonconfig: Add variable sensitive flag

Prompts for reviewers:

  • Despite now being in the plan file, I have not (yet) added the required-replace data to the JSON plan output. I'm not sure the same type of structure would make sense for encoding this. The required-replace pathset is not about either the before or the after value, but about the nested values at the given paths being unequal. Since there isn't an urgent use case for this, I decided to skip it for this PR, but I'd be interested in any thoughts on how to tackle this.
  • As this changes the JSON plan output, I'd especially like thoughts from the Sentinel team (hi @vancluever!) about this encoding of sensitive values. Does this make sense to you?

Screenshots and sample JSON

Before

before

After

after

JSON output

The JSON below is for the plan in the above screenshots. Here's the relevant resource_changes value:

[
  {
    "address": "random_pet.pet",
    "mode": "managed",
    "type": "random_pet",
    "name": "pet",
    "provider_name": "registry.terraform.io/hashicorp/random",
    "change": {
      "actions": [
        "delete",
        "create"
      ],
      "before": {
        "id": "foo-firm-eagle",
        "keepers": null,
        "length": 2,
        "prefix": "foo",
        "separator": "-"
      },
      "after": {
        "keepers": {
          "foo": "foo"
        },
        "length": 2,
        "prefix": "foo",
        "separator": "-"
      },
      "after_unknown": {
        "id": true,
        "keepers": {}
      },
      "before_sensitive": {},
      "after_sensitive": {
        "keepers": {
          "foo": true
        },
        "prefix": true
      }
    }
  }
]
Entire JSON plan, which is quite long, so hidden in this description by default
{
  "format_version": "0.1",
  "terraform_version": "0.15.0-dev",
  "variables": {
    "foo": {
      "value": "foo"
    }
  },
  "planned_values": {
    "outputs": {
      "pet": {
        "sensitive": true
      }
    },
    "root_module": {
      "resources": [
        {
          "address": "random_pet.pet",
          "mode": "managed",
          "type": "random_pet",
          "name": "pet",
          "provider_name": "registry.terraform.io/hashicorp/random",
          "schema_version": 0,
          "values": {
            "keepers": {
              "foo": "foo"
            },
            "length": 2,
            "prefix": "foo",
            "separator": "-"
          }
        }
      ]
    }
  },
  "resource_changes": [
    {
      "address": "random_pet.pet",
      "mode": "managed",
      "type": "random_pet",
      "name": "pet",
      "provider_name": "registry.terraform.io/hashicorp/random",
      "change": {
        "actions": [
          "delete",
          "create"
        ],
        "before": {
          "id": "foo-firm-eagle",
          "keepers": null,
          "length": 2,
          "prefix": "foo",
          "separator": "-"
        },
        "after": {
          "keepers": {
            "foo": "foo"
          },
          "length": 2,
          "prefix": "foo",
          "separator": "-"
        },
        "after_unknown": {
          "id": true,
          "keepers": {}
        },
        "before_sensitive": {},
        "after_sensitive": {
          "keepers": {
            "foo": true
          },
          "prefix": true
        }
      }
    }
  ],
  "output_changes": {
    "pet": {
      "actions": [
        "update"
      ],
      "before": "foo-firm-eagle",
      "after_unknown": true
    }
  },
  "prior_state": {
    "format_version": "0.1",
    "terraform_version": "0.15.0",
    "values": {
      "outputs": {
        "pet": {
          "sensitive": false,
          "value": "foo-firm-eagle"
        }
      },
      "root_module": {
        "resources": [
          {
            "address": "random_pet.pet",
            "mode": "managed",
            "type": "random_pet",
            "name": "pet",
            "provider_name": "registry.terraform.io/hashicorp/random",
            "schema_version": 0,
            "values": {
              "id": "foo-firm-eagle",
              "keepers": null,
              "length": 2,
              "prefix": "foo",
              "separator": "-"
            }
          }
        ]
      }
    }
  },
  "configuration": {
    "root_module": {
      "outputs": {
        "pet": {
          "sensitive": true,
          "expression": {
            "references": [
              "random_pet.pet"
            ]
          }
        }
      },
      "resources": [
        {
          "address": "random_pet.pet",
          "mode": "managed",
          "type": "random_pet",
          "name": "pet",
          "provider_config_key": "random",
          "expressions": {
            "keepers": {
              "references": [
                "var.foo"
              ]
            },
            "prefix": {
              "references": [
                "var.foo"
              ]
            }
          },
          "schema_version": 0
        }
      ],
      "variables": {
        "foo": {
          "default": "foo"
        }
      }
    }
  }
}

Fixes #27577.

The stored planfile now serializes the required-replace path set and the
collection of before/after sensitivity marks. This ensures that storing
a plan and displaying it with `terraform show` renders the same output
for plans with required-replace resources, and those with sensitive
values in the diff.
@alisdair alisdair added core json-output 0.15-backport If you add this label to a PR before merging, backport-assistant will open a new PR once merged labels Mar 25, 2021
@alisdair alisdair requested review from vancluever and a team March 25, 2021 18:58
@alisdair alisdair self-assigned this Mar 25, 2021
@codecov
Copy link

codecov bot commented Mar 25, 2021

Codecov Report

Merging #28201 (5e30d58) into main (550de86) will increase coverage by 0.02%.
The diff coverage is 59.18%.

Impacted Files Coverage Δ
command/jsonconfig/config.go 0.00% <0.00%> (ø)
plans/changes_src.go 0.00% <ø> (ø)
command/jsonplan/plan.go 33.09% <52.45%> (+6.18%) ⬆️
plans/planfile/tfplan.go 59.36% <64.70%> (+1.73%) ⬆️
dag/marshal.go 63.49% <0.00%> (+1.58%) ⬆️

Copy link
Contributor

@pselle pselle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate how this approach is following prior-art in both the AfterUnknown and also that we store sensitive paths (not a more generalized "marked paths") in the statefile format.

Copy link
Contributor

@vancluever vancluever left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @alisdair, this looks like a great start.

One thing we specifically need in Sentinel is this detail propagated to variables and outputs in the plan as well. This does not seem to be happening when it comes to the fixtures - can you check? I'm specifically looking at https://github.com/hashicorp/terraform/pull/28201/files#diff-0ce6a3851c6b35d990b314c3995e5e560ba42e9f923c1c808d4957e7144763f0 and the values do not seem to be propagated there, either in the top-level variables object nor as [before|after])_sensitive in output_changes. For the former I'd recommend just a simple sensitive attribute as that field is not a change, since it does not have state.

Additionally, I'm curious what a "required-replace" field is - is that an updated name for ForceNew? If so, we don't necessarily need it in the JSON right now, as that field is satisfied with the action order, but it'd be nice as a quality-of-life thing, so long as it's consistent with the aforementioned.

@vancluever
Copy link
Contributor

@alisdair additionally, what do you think the possibility is to be able to indicate in at least variables that a value came from a sensitive source, regardless of whether or not it was marked as sensitive in the config? Example: the value was provided in TFC from a sensitive variable even though it was not marked as so in configuration.

@alisdair
Copy link
Member Author

Thanks for the review, @vancluever!

One thing we specifically need in Sentinel is this detail propagated to variables and outputs in the plan as well. This does not seem to be happening when it comes to the fixtures - can you check?

That's correct for variables: adding the sensitive attribute to jsonconfig wasn't something I had thought of addressing as part of this PR. But it makes sense, so thanks for calling it out; added in 6068f60.

Outputs are different from resource changes, and more similar to variables. Unlike other language values, Terraform does not consider an output to be partially sensitive: it's either sensitive or not, depending on the sensitive argument in the configuration. Terraform will not allow binding a sensitive language value (e.g. one derived from a sensitive variable or sensitive resource attribute) to an output which does not have sensitive = true.

Additionally, I'm curious what a "required-replace" field is - is that an updated name for ForceNew? If so, we don't necessarily need it in the JSON right now, as that field is satisfied with the action order, but it'd be nice as a quality-of-life thing, so long as it's consistent with the aforementioned.

Yes, RequiredReplace is the plan representation of any paths which were marked ForceNew by the provider, which therefore caused the action to be replace. Out of curiosity, could you explain what you mean by "satisfied with the action order"?

Since I don't currently have any idea how to usefully represent a path set in JSON plan output, and it doesn't sound like it's urgent, I'm inclined not to block this change on figuring that out. I'd be happy to revisit it later though.

what do you think the possibility is to be able to indicate in at least variables that a value came from a sensitive source, regardless of whether or not it was marked as sensitive in the config? Example: the value was provided in TFC from a sensitive variable even though it was not marked as so in configuration.

Terraform doesn't know anything about sensitivity of variables except via the configuration. For TFC sensitive variables which are not marked as sensitive in the ingressed configuration, TFC uses an override file to add the sensitive = true argument to the configuration block. Correspondingly this should show up in the jsonconfig.

I hope all that makes sense! Let me know if I missed something or if you have any follow-up questions.

@vancluever
Copy link
Contributor

Outputs are different from resource changes, and more similar to variables. Unlike other language values, Terraform does not consider an output to be partially sensitive: it's either sensitive or not, depending on the sensitive argument in the configuration. Terraform will not allow binding a sensitive language value (e.g. one derived from a sensitive variable or sensitive resource attribute) to an output which does not have sensitive = true.

Thinking on this a bit more, and if this is something that is easy to surface into output_changes - maybe we could at least get an after_sensitive field? This would allow convenient differentiation between sensitive and non-sensitive outputs without having to do things like deep configuration or schema introspection.

FWIW: output_changes is using the general change attribute, as seen here, so if we wanted to add an attribute outside of this schema it would require changes to that part altogether.

Yes, RequiredReplace is the plan representation of any paths which were marked ForceNew by the provider, which therefore caused the action to be replace. Out of curiosity, could you explain what you mean by "satisfied with the action order"?

Since I don't currently have any idea how to usefully represent a path set in JSON plan output, and it doesn't sound like it's urgent, I'm inclined not to block this change on figuring that out. I'd be happy to revisit it later though.

That's basically the set of actions to be carried about by any particular change - ie: replace would be ["delete", "create"], while create-before-destroy would be ["create", "delete"]. Described by the Actions field.

It's not a big deal if this is not included for now as it would mainly be redundant anyway. AFAIK this is not causing a DX issue in Sentinel, and we can always add convenience helpers if it does become one.

Terraform doesn't know anything about sensitivity of variables except via the configuration. For TFC sensitive variables which are not marked as sensitive in the ingressed configuration, TFC uses an override file to add the sensitive = true argument to the configuration block. Correspondingly this should show up in the jsonconfig.

Alright, that sounds good. We can probably cross the bridge if need be if that ever gets changed with any upcoming architectural changes to de-couple CLI and core (any news on that btw? 🙂 )

Similar to `after_unknown`, `before_sensitive` and `after_sensitive` are
values with similar structure to `before` and `after` which encode the
presence of sensitive values in a planned change. These should be used
to obscure sensitive values from human-readable output.

These values follow the same structure as the `before` and `after`
values, replacing sensitive values with `true`, and non-sensitive values
with `false`. Following the `after_unknown` precedent, we omit
non-sensitive `false` values for object attributes/map values, to make
serialization more compact.

One difference from `after_unknown` is that a sensitive complex value
(collection or structural type) is replaced with `true`. If the complex
value itself is sensitive, all of its contents should be obscured.
When an output value changes, we have a small amount of information we
can convey about its sensitivity. If either the output was previously
marked sensitive, or is currently marked sensitive in the config, this
is tracked in the output change data.

This commit encodes this boolean in the change struct's
`before_sensitive` and `after_sensitive` fields, in the a way which
matches resource value sensitivity. Since we have so little information
to work with, these two values will always be booleans, and always equal
each.

This is logically consistent with how else we want to obscure sensitive
data: a changing output which was or is marked sensitive should not have
the value shown in human-readable output.
@alisdair alisdair force-pushed the alisdair/planfile-sensitive-required-replace branch from 6068f60 to 5e30d58 Compare March 26, 2021 23:26
@alisdair
Copy link
Member Author

Thinking on this a bit more, and if this is something that is easy to surface into output_changes - maybe we could at least get an after_sensitive field? This would allow convenient differentiation between sensitive and non-sensitive outputs without having to do things like deep configuration or schema introspection.

FWIW: output_changes is using the general change attribute, as seen here, so if we wanted to add an attribute outside of this schema it would require changes to that part altogether.

Ah, I was missing this context, thank you! This suggestion makes sense to me, and I've updated jsonplan to emit before_sensitive and after_sensitive values for outputs as well as resources. There's more detail in the commit message, but the gist is that these will either be true/true or false/false, because we don't have much sensitivity information to go on:

// Sensitive, if true, indicates that either the old or new value in the
// change is sensitive and so a rendered version of the plan in the UI
// should elide the actual values while still indicating the action of the
// change.
Sensitive bool

@vancluever
Copy link
Contributor

vancluever commented Mar 29, 2021

Ah, I was missing this context, thank you! This suggestion makes sense to me, and I've updated jsonplan to emit before_sensitive and after_sensitive values for outputs as well as resources. There's more detail in the commit message, but the gist is that these will either be true/true or false/false, because we don't have much sensitivity information to go on:

Yeah, that's fine and how the unknown values behave right now from the looks of it, so it's consistent. Specifically referring to the opaque values here, but before and after being the same is fine here too I think as I'm not too sure if there will be concern with relying on one over the other. If there is, we can cross that bridge when we get to it!

@ghost
Copy link

ghost commented Apr 29, 2021

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@ghost ghost locked as resolved and limited conversation to collaborators Apr 29, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
0.15-backport If you add this label to a PR before merging, backport-assistant will open a new PR once merged core json-output
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Hide sensitive values when showing stored plan
3 participants