scottwinkler / terraform-provider-shell

Terraform provider for executing shell commands and saving output to state file
Mozilla Public License 2.0
279 stars 61 forks source link

Feature request: Sensitive environment block #22

Closed lukasmrtvy closed 4 years ago

lukasmrtvy commented 4 years ago

There is a problem if You want to pass sensitive value to shell provider, usually environment variable ( templatefile() ), its visible in plan/apply.

Would be nice to have something like: sensitive_environemnt {} block for hidding environment variables in terraform plan/apply overview.

scottwinkler commented 4 years ago

the entire environment of the local machine gets used, the environment block is just to override any specific environment variables. if you would like to keep some variables a secret, I would suggest setting these as proper environment variables on your local machine. that way they will not be picked up by the plan.

lukasmrtvy commented 4 years ago

Yes, but I can not set environment variables ouside terraform, (of course, its possible, but not in my use case -> I am using atlantis to run tf plans ). I would like to see some sensitive_environment {} block still.

scottwinkler commented 4 years ago

@lukasmrtvy My first thought is to say that if you can't set environment variables before running Terraform, maybe its just a problem with your choice of technology and you should consider using a purpose built tool that does let you set environment variables, such as Terraform Cloud. Not being able to set environment variables sounds like a huge limitation of Atlantis and I would look into moving away from it as soon as possible if that really is the case.

On the other hand, I could see how a lot of environment variables could be used to store secrets data. Most other providers would set variables like this sensitive by default. In that case, setting variables to sensitive would be the best course of action.

If I was going to make variables sensitive, I would rather not make a special block just for sensitive variables. The easiest and least invasive thing to do would be to simply mark environment as a sensitive attribute, for example:

            "environment": {
                Type:      schema.TypeMap,
                Optional:  true,
                Sensitive: true,
                Elem:      schema.TypeString,
            },

In the apply, environment variables would not be able to be seen in the plan:

  # shell_script.test6 will be created
  + resource "shell_script" "test6" {
      + dirty             = false
      + environment       = (sensitive value)
      + id                = (known after apply)
      + output            = (known after apply)
      + triggers          = {
          + "abc" = "123"
        }
      + working_directory = "."

      + lifecycle_commands {
          + create = "#!/bin/bash\necho \"creating...\"\necho \"writing some error\" >&2\n\nIN=$(cat)\necho \"stdin: ${IN}\" #the old state, not useful for create step since the old state was empty\n\n#business logic\n/bin/cat <<END >ex.json\n  {\"commit_id\": \"b8f2b8b\", \"environment\": \"$yolo\", \"tags_at_commit\": \"sometags\", \"project\": \"someproject\", \"current_date\": \"09/10/2014\", \"version\": \"someversion\"}\nEND\ncat ex.json"
          + delete = "#!/bin/bash\necho \"deleting...\"\necho \"writing some error\" >&2\n\nIN=$(cat)\necho \"stdin: ${IN}\" #the old state\n\n#business logic\nrm -rf ex.json"
          + read   = "#!/bin/bash\necho \"reading..\"\necho \"writing some error\" >&2\n\nIN=$(cat)\necho \"stdin: ${IN}\" #the old state\n\n#business logic\ncat ex.json >&3 #must write state to >&3"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

This works but now you can't see the environment variables in the plan, not even the name. Also, the environment attribute is still stored in the state file, so if you have read access to the raw state file then you could still circumvent the sensitive attribute (this is true for all sensitive variables though, so not really a problem):

osxqchenmbp15:examples swinkler$ cat terraform.tfstate
{
  "version": 4,
  "terraform_version": "0.12.9",
  "serial": 1,
  "lineage": "4c558864-7fbb-282b-2fa2-b210b967337f",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "shell_script",
      "name": "test6",
      "provider": "provider.shell",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "dirty": false,
            "environment": {
              "ball": "room",
              "yolo": "yolo"
            },
            "id": "bo0i474llhcjqa8qc4ag",
            "lifecycle_commands": [
              {
                "create": "#!/bin/bash\necho \"creating...\"\necho \"writing some error\" \u003e\u00262\n\nIN=$(cat)\necho \"stdin: ${IN}\" #the old state, not useful for create step since the old state was empty\n\n#business logic\n/bin/cat \u003c\u003cEND \u003eex.json\n  {\"commit_id\": \"b8f2b8b\", \"environment\": \"$yolo\", \"tags_at_commit\": \"sometags\", \"project\": \"someproject\", \"current_date\": \"09/10/2014\", \"version\": \"someversion\"}\nEND\ncat ex.json",
                "delete": "#!/bin/bash\necho \"deleting...\"\necho \"writing some error\" \u003e\u00262\n\nIN=$(cat)\necho \"stdin: ${IN}\" #the old state\n\n#business logic\nrm -rf ex.json",
                "read": "#!/bin/bash\necho \"reading..\"\necho \"writing some error\" \u003e\u00262\n\nIN=$(cat)\necho \"stdin: ${IN}\" #the old state\n\n#business logic\ncat ex.json \u003e\u00263 #must write state to \u003e\u00263",
                "update": ""
              }
            ],
            "output": {
              "commit_id": "b8f2b8b",
              "current_date": "09/10/2014",
              "environment": "yolo",
              "project": "someproject",
              "tags_at_commit": "sometags",
              "version": "someversion"
            },
            "triggers": {
              "abc": "123"
            },
            "working_directory": "."
          },
          "private": "bnVsbA=="
        }
      ]
    }
  ]
}

alternatively, I could alter the schema to make environment a list of objects. Each object defines a key, value, and whether on not it is sensitive. For example:

resource "shell_script" "test6" {
  lifecycle_commands {
    create = file("${path.module}/scripts/create.sh")
    read   = file("${path.module}/scripts/read.sh")
    delete = file("${path.module}/scripts/delete.sh")
  }

  environment = [
    {
      key   = "AWS_ACCESS_KEY_ID"
      value = var.aws_access_key_id
    },
    {
      key       = "AWS_SECRET_ACCESS_KEY"
      value     = var.aws_secret_access_key
      sensitive = true
    }
  ]

  triggers = {
    abc = 123
  }
}

I like this way the best, but it would also be a breaking change to the provider.

scottwinkler commented 4 years ago

released in 1.3.0