How to design a Terraform resource with multiple sensitive attributes

Viewed 148

Context: I'm developing a new resource for my TF Provider.

This foo resource has a name and associated config: a list of key value pairs (both sensitive and non-sensitive).

There're 3 options I've identified:

resource "foo" "option1" {
  name = "option1"
  config = {
    "name"            = "option1"
    "errors.length"   = 3
    "tasks.type"      = "FOO"
  }
  config_sensitive = {
    "jira.key"         = "..."
    "credentials.json" = "..."
  }
}

resource "foo" "option2" {
  name = "option2"
  config = {
    "name"            = "option1"
    "errors.length"   = 3
    "tasks.type"      = "FOO"
    "jira.key"         = "..."
    "credentials.json" = "..."
  }
}

resource "foo" "option3" {
  name = "option3"
  config = file("config.json")
}

The advantage of option #3 is it looks very readable but requires a user to store an extra json file (with secrets) in the same folder (I'm not sure how acceptable that setup is). Option #2 looks tempting but foo should accept updates and if we mark the whole block as sensitive (since it may contain secret key-value pairs), the update functionality will suffer (user won't see the expected change). So Option #1 is the winner in my eyes since it's the most explicit one and allows us to distinguish between sensitive and non-sensitive attributes (while allowing updates for non-sensitive ones). Reading from file the whole config is probably not ideal since it doesn't really allow an engineer to see how the config looks like without opening another file.

There's also this weird duplicated name attribute but let's ignore it for now.

What configuration is the most acceptable and used by other TF Providers?

2 Answers

Option #3 should be struck immediately for three reasons:

  • You cannot realiably use the sensitive flag in the schema struct like you can with 1 and 2.
  • It requires a JSON format value which is cumbersome to work with unless you are forced into it (e.g. security policies).
  • Someone could inline the JSON and not store it in a file, which would completely workaround your attempt to obscure the secrets.

Options 1 and 2 are honestly no different from a secrets management perspective. You could apply the sensitive flag to either in the nested schema struct on a per-attribute basis, and use e.g. Vault to pass in values on a KV basis for either.

I would opt for 1 over 2 simply because it appears to me from your question that the arguments and values in the two blocks have no relationship with each other. Therefore, it makes more sense to organize your schema into two separate blocks for code cleanliness purposes.

I will also mention that if it is possible to refactor the credentials.json into your provider, and leverage the JIRA provider for the jira.key, then that would be best practices by both code architecture and security. It is also how the major providers handle this situation.

Terraform providers should handle the credential/auth implementation and the resource handles the resource configuration.

e.g.

resource "jira_issue" "some_story" {
  title  = "My story"
  type   = "story"
  labels = ["someexampleonstackoverflow","jakewashere"]
}

Notice there's no config that doesn't relate to the thing I'm creating inside the Terraform resource.

It's very acceptable to have some documented convention in your provider that reads credentials from somewhere, whether that's an OS variable, file on disk etc.

For example: The Google Cloud provider, will read an environment variable if it's populated, if not it'll attempt to read either a configuration file that sits inside a hidden directory within $HOME or attempts to read a localhost http metadata server for the credentials.

Related