1
votes

I'm writing a custom Terraform provider, and I have a resource that has an argument that is a map[string]string which may contain sensitive values. I want to make the values sensitive but not the keys. I tried setting the Sensitive attribute of the Elem in the map to true (see example below) but I still get the values printed out the console during the plan phase.

return &schema.Resource{
    // ...
    Schema: map[string]*schema.Schema{
        "sensitive_map": {
            Type:     schema.TypeMap,
            Optional: true,
            Elem: &schema.Schema{
                Type: schema.TypeString,
                // Sensitive: true,
            },
        },
    },
}

Example plan phase output:

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # deploy_project.this will be created
  + resource "my_resource" "this" {
      + sensitive_map                  = {
          + "key" = "value"
        }
      + id                        = (known after apply)
    }

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

How can I get the value to be marked as sensitive but not the key?

2
I double-checked the documentation and it seems to confirm that Sensitive is not a behavior field for the Terraform resource schema. Assuming sdk2 and TF >= 0.13 etc., this would probably be achieved (if possible) in the manner coded in your question. As it is, this behavior is (I believe) opt-in and not opt-out, and therefore is configured in the TF config and not the provider. I could be wrong though.Matt Schuchard
@MattSchuchard I'm using the sdk v2 and terraform 0.14.5, I also tried putting the Sensitive flag on the Map itself rather than the Elem and it does mark it as sensitive in the plan output, except it marks the entire Map as sensitive, including the keysWilliam Perron

2 Answers

1
votes

In the Terraform SDK's current model of sensitivity, there is no way to achieve what you are aiming to achieve. Sensitivity is set for an entire attribute at a time, not for parts of an attribute.

Although the SDK model re-uses *schema.Schema as a possible type for Elem as a convenience, in practice only a small subset of the schema.Schema fields can work in that position, because a declaration like this is roughly the same as declaring a variable like the following in a Terraform module:

variable "sensitive_map" {
  type      = map(string)
  sensitive = true
}

Notice that the "sensitive" concept applies to the variable as a whole. It isn't a part of the variable's type constraint, so there isn't any way to write down "map of sensitive strings" as a type constraint. Although provider arguments are not actually module variables, they do still participate in the same system of values and types that variables do, and so have a similar set of capabilities.

0
votes

I ended settling for a solution using nested blocks rather than a simple map. The schema definition is more complex than a simple map and it makes for a userland configuration that is more verbose but it does satisfy my initial requirements quite well.

"sensitive_map": {
    Type:     schema.TypeList,
    Optional: true,
    Elem: &schema.Resource{
        Schema: map[string]*schema.Schema{
            "key": {
                Type:     schema.TypeString,
                Required: true,
                Elem:     &schema.Schema{Type: schema.TypeString},
            },
            "value": {
                Type:      schema.TypeString,
                Required:  true,
                Sensitive: true,
                Elem:      &schema.Schema{Type: schema.TypeString},
            },
        },
    },
},

And it shows up in the plan phase as:

  + resource "my_resource" "this" {
      + sensitive_map {
          + key   = "foo"
          + value = (sensitive value)
        }
    }

It changes the representation in Go from a map[string]string to a map[string]interface{} where the empty interface is itself a map[string]string. In the Create hook of the resource, this is what the code looks like to parse the input config:

sensitiveMap := make(client.EnvVars)
tmp := d.Get("sensitive_map").([]interface{})
for _, v := range tmp {
    keyval := v.(map[string]interface{})
    vars[keyval["key"].(string)] = keyval["value"].(string)
}

I'm sure it could be optimized further but for now it works just fine!