"Correct" way to read custom fields in ```onepassword_item``` data source

mike99
mike99
Community Member
edited April 25 in Secrets Automation

Hi,

We're looking at migrating from HashiCorp Vault to 1Password as a secret provider for our terraform projects.

At present we have something like this:

provider "vault" {
  address = "https://vault.mydomain.internal"
}

data "vault_generic_secret" "azure" {
  path = "secret/path/to/my/secret"
}

provider "azurerm" {
  client_id       = data.vault_generic_secret.azure.data["clientid"]
  tenant_id       = data.vault_generic_secret.azure.data["tenant_id"]
  client_secret   = data.vault_generic_secret.azure.data["client_secret"]
  subscription_id = data.vault_generic_secret.azure.data["subscription_id"]
  features {}
}

The best I've ben able to put together with the onepassword provider is this:

provider "onepassword" {
  # uses the OP_SERVICE_ACCOUNT_TOKEN environment variable for authentication.
  # also looks in the system path for the 1password cli (op.exe / op)
}

data "onepassword_item" "my_spn" {
  vault = "my-vault"
  title = "my-spn"
}

provider "azurerm" {
   tenant_id       = [for field in data.onepassword_item.my_spn.section[0].field : field.value if field.label == "tenant_id"][0]
  client_id       = [for field in data.onepassword_item.my_spn.section[0].field : field.value if field.label == "client_id"][0]
  client_secret   = [for field in data.onepassword_item.my_spn.section[0].field : field.value if field.label == "client_secret"][0]
  subscription_id = [for field in data.onepassword_item.my_spn.section[0].field : field.value if field.label == "subscription_id "][0]
  features          {}
}

Have I got completely the wrong end of the stick and there's an easier way to access custom fields?

I've used the 1Password Github actions and the 1Password CLI and they both support a much simpler syntax so I was hoping for something similar in the terraform provider - e.g. something like:

Here's the GitHub Actions and OP cli syntax for comparison...

provider "azurerm" {
  client_id       = data.onepassword_item.my_spn.field["client_id"]
  tenant_id       = data.onepassword_item.my_spn.field["tenant_id"]
  client_secret   = data.onepassword_item.my_spn.field["client_secret"]
  subscription_id = data.onepassword_item.my_spn.field["subscription_id"]
  features {}
}

github actions

- name: load secrets
        id:   onepassword
        uses: 1password/load-secrets-action@v2
        with:
          export-env: true
        env:
          OP_SERVICE_ACCOUNT_TOKEN:    "${{ secrets.ONEPASSWORD_SERVICE_ACCOUNT_TOKEN }}"
          MY_SPN:                 "op://my-vault/my-spn/client_id"

op cli

C:\> op read "op://my-vault/my-spn/client_id"

Thanks,

Mike


1Password Version: Not Provided
Extension Version: Not Provided
OS Version: Not Provided
Browser: Not Provided

Comments

  • jerdew
    jerdew
    Community Member
    edited May 3

    I found this issue comment helpful: https://github.com/1Password/terraform-provider-onepassword/issues/117#issuecomment-2059385999

    and ended up with

    data "onepassword_item" "terraform" {
      vault = "MyVault"
      title = "MyTerraformItem"
    }
    
    locals {
      op_terraform_fields = { for f in data.onepassword_item.terraform.section[0].field : f.label => {
        id      = f.id
        purpose = f.purpose
        type    = f.type
        value   = f.value
        }
      }
    
      # accessed like so: local.op_terraform_sections["Did You Get"].fields["that thing I sent you"].value
      op_terraform_sections = { for s in data.onepassword_item.terraform.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 != "" }
    }
    

    and then the 'fields' one is the sectionless section found at [0], and used by me as

    provider "aws" {
      region     = "us-east-1"
      access_key = local.op_terraform_fields["ACCESS_KEY"].value
      secret_key = local.op_terraform_fields["SECRET_ACCESS_KEY"].value
    }
    
  • mike99
    mike99
    Community Member
    edited May 8

    @jerdew - nice, although that looks like it would get quite busy if you have multiple onepassword items that you want to reference in the same project.

    For completeness, I went my original approach in the end:

    provider "azurerm" {
       tenant_id       = [for field in data.onepassword_item.my_spn.section[0].field : field.value if field.label == "tenant_id"][0]
      .. etc ...
    }
    

    I also created a local variable like you did for referencing secrets in resources:

    locals {
      my_spn = {
        tenant_id       = [for field in data.onepassword_item.my_spn.section[0].field : field.value if field.label == "tenant_id"][0]
        client_id       = [for field in data.onepassword_item.my_spn.section[0].field : field.value if field.label == "client_id"][0]
        client_secret   = [for field in data.onepassword_item.my_spn.section[0].field : field.value if field.label == "client_secret"][0]
      }
    }
    

    and then

       some_resource_property = local.my_spn.tenant_id
    

    I didn't seem to be able to reference the locals in a provider block so I had to duplicate the long-hand version in the azurerm provider. I might take another look as your example looks like it does that ok...

  • jerdew
    jerdew
    Community Member

    I thought the same at first, but the issue comment helped me understand how a 'module' was gonna help me out. The key part is here:

    module "test_item" {
      source = "./op_password"
    
      vault_uuid = data.onepassword_vault.vault.uuid
      title      = "Test"
    }
    

    this is how my updated code now looks

    data "onepassword_item" "password" {
      vault = data.onepassword_vault.vault.uuid
      title = var.title
    }
    
    locals {
      op_fields = { for f in data.onepassword_item.password.section[0].field : f.label => {
        id      = f.id
        purpose = f.purpose
        type    = f.type
        value   = f.value
        }
      }
    ...
    

    The 'title' is the name of the 1password item, so you can add module "my_spn" { ... and module "my_other_spn" {, then access them as module.my_spn.op_fields["etc"]

This discussion has been closed.