1Password / terraform-provider-onepassword

Use the 1Password Terraform Provider to reference, create, or update items in your 1Password Vaults.
https://1password.com/secrets
MIT License
323 stars 44 forks source link

Allow Section Fields LookUp by Title #117

Open yordis opened 9 months ago

yordis commented 9 months ago

Summary

Allow Section Fields LookUp by Title when using data "onepassword_item" resource.

Use cases

As a programmer, I would like to have a way to lookup a Section Field by Title.

Given the following secret:

{
  "overview": {
    "title": "my-title",
  },
  "details": {
    "sections": [
      {
        "name": "add more",
        "title": "",
        "fields": [
          {
            "t": "DO_ACCESS_TOKEN",
            "k": "concealed"
          },
          {
            "t": "DO_SPACES_ACCESS_ID",
            "k": "concealed"
          },
          {
            "t": "DO_SPACES_ACCESS_KEY",
            "k": "concealed"
          },
        ]
      }
    ]
  }
}

I would like to be able to use the title (in this case DO_ACCESS_TOKEN) of the field.

data "onepassword_item" "main" {}

resource "terraform_data" "do_access_token" {
  input = data.onepassword_item.my-title.section.fields["DO_ACCESS_TOKEN"]
}

Proposed solution

Introduce fields (plural), or break change (🤷🏻) in order to look up the fields by title.

Otherwise, document how I suppose to do this because my skills are limited and I can not find a simple way to do it. I am not sure what I am doing wrong here.

Is there a workaround to accomplish this today?

I think that, you loop thru the fields, match in the field name, get the value back into a local and then use it! 😭

References & Prior Work

kitforbes commented 5 months ago

I started playing with the provider last night and wanted behaviour like this. I've created a simple wrapper in my project to re-shape the object that we get from the onepassword_item data source.

Some caveats:

  1. A "section" name (label) must be unique
  2. A "field" name (label) within a section (either top-level ("") or custom) must be unique
  3. I've only tested this with a "password" type object so far, so the example below will not fit every category. Extending it should be simple enough.

If there are duplicates, Terraform will fail due to a non-unique key.

module/variables.tf

variable "title" {
  type = string
}

variable "vault_uuid" {
  type = string
}

module/main.tf

terraform {
  required_version = ">= 1.0.0"

  required_providers {
    onepassword = {
      source = "1Password/onepassword"
    }
  }
}

data "onepassword_item" "main" {
  vault = var.vault_uuid
  title = var.title
}

locals {
  fields = { for f in data.onepassword_item.main.section[0].field : f.label => {
      id = f.id
      purpose = f.purpose
      type = f.type
      value = f.value
    }
  }

  sections = { for s in data.onepassword_item.main.section : s.label => {
    id = s.id
    fields = { for f in s.field : f.label => {
      id = f.id
      purpose = f.purpose
      type = f.type
      value = f.value
    } }
  } if s.label != "" }
}

module/outputs.tf

output "category" {
  value = data.onepassword_item.main.category
}

output "database" {
  value = data.onepassword_item.main.database
}

output "fields" {
  value = local.fields
}

output "hostname" {
  value = data.onepassword_item.main.hostname
}

output "id" {
  value = data.onepassword_item.main.id
}

output "note_value" {
  value     = data.onepassword_item.main.note_value
  sensitive = true
}

output "password" {
  value     = data.onepassword_item.main.password
  sensitive = true
}

output "port" {
  value = data.onepassword_item.main.port
}

output "sections" {
  value = local.sections
}

output "tags" {
  value = data.onepassword_item.main.tags
}

output "title" {
  value = data.onepassword_item.main.title
}

output "type" {
  value = data.onepassword_item.main.type
}

output "url" {
  value = data.onepassword_item.main.url
}

output "username" {
  value = data.onepassword_item.main.username
}

output "uuid" {
  value = data.onepassword_item.main.uuid
}

output "vault" {
  value = data.onepassword_item.main.vault
}

Consume the module like this:

data "onepassword_vault" "vault" {
  name = "My Vault"
}

module "test_item" {
  source = "./module"

  vault_uuid = data.onepassword_vault.vault.uuid
  title      = "Test"
}

output "value_from_text_field_in_section" {
  value = module.test_item.sections["Section One"].fields["text"].value
}

The item used in this example has this layout: image

dannysauer commented 4 months ago

The module is way prettier than what I did. Given a onepassword_item named determined_ci_github (hence the dcg prefix):

locals {
  dcg_section_label = "Tokens"
  dcg_field_id      = "jxu6aak6lkhdxij6whyqels42u"
  dcg_sections      = data.onepassword_item.determined_ci_github.section
  dcg_section_index = index(local.dcg_sections.*.label, local.dcg_section_label)
  dcg_fields        = local.dcg_sections[local.dcg_section_index].field
  dcg_field_index   = index(local.dcg_fields.*.id, local.dcg_field_id)
  dcg_field         = local.dcg_fields[local.dcg_field_index]
}

So I find the section with the desired label, then in that section I find the field with the given ID (since it has a name with weird chars). Then afterwards, I can reference local.dcg_field.value. Probably should've added dcg_value to locals to shorten that up a bit more. Meh.

This approach becomes a real mess when you want multiple fields from one item. The module is likely much better for that.

ekostjuk commented 4 months ago

A bit simpler workaround, but still, having to boilerplate this all around the code is not clean. (using separate vault id verification here)

data onepassword_vault ops {
  name = var.ops_vault
}
data onepassword_item service {
  vault = split("/", data.onepassword_vault.ops.id)[1]
  title = "service"
}
locals {
  service = {for k1, v1 in data.onepassword_item.service.section : v1.label =>  {for k2, v2 in v1.field : v2.label => v2.value}}
}