Sources of Secrets by HashiCorp Vault - Revisited
Same Goal, Different Pattern In the previous article we synchronized 1Password items into Vault by pulling data with Terraform and writing it into a KV engine.

In the previous article we synchronized 1Password items into Vault by pulling data with Terraform and writing it into a KV engine. That pattern is great when you want Vault to own the canonical copy of a secret. But sometimes you just want Vault to expose secrets that are already governed elsewhere so that existing Vault clients can keep working with a consistent workflow.
Instead of copying the data, we can let Vault act as a smart proxy. The 1Password vault plugin exposes a dedicated secrets engine (op-connect) that delegates CRUD operations to the 1Password Connect API. Vault users read, list, create, and rotate items without ever seeing the underlying 1Password Connect credentials.
op/.op/*, so you inherit the same RBAC and audit logging you already use for other secret engines.vault CLI.You can evaluate everything locally by running Vault in dev mode:
1mkdir -p ./vault/plugins
2vault server -dev -dev-root-token-id=root -dev-plugin-dir=./vault/plugins -log-level=debug
⚠️ Dev mode keeps Vault unsealed and in-memory. Never run it outside of experiments.
Download the release that matches your Vault host architecture:
1# Example for linux/amd64, adjust version/filename as needed
2unzip ./vault-plugin-secrets-onepassword_1.1.0_linux_amd64.zip
3mv ./vault-plugin-secrets-onepassword_v1.1.0 ./vault/plugins/op-connect
Prefer to build it yourself?
1# Place the binary directly in Vault's plugin directory
2GOBIN=$(pwd)/vault/plugins go build -o ./vault/plugins/op-connect \
3 github.com/1Password/vault-plugin-secrets-onepassword
Next, start (or restart) Vault with the plugin directory configured. A minimal server configuration looks like this:
1plugin_directory = "${PWD}/vault/plugins"
2api_addr = "http://127.0.0.1:8200"
3
4storage "inmem" {}
5
6listener "tcp" {
7 address = "127.0.0.1:8200"
8 tls_disable = "true"
9}
If you do not already operate 1Password Connect, you can spin it up locally with Docker Compose. The API component handles Vault traffic, while the Sync component keeps data current with 1Password. Both containers share the same credential file and data volume so they stay in lockstep.
1services:
2 op-connect-api:
3 image: 1password/connect-api:latest
4 ports:
5 - "127.0.0.1:8080:8080"
6 volumes:
7 - "/opt/opconnect/1password-credentials.json:/home/opuser/.op/1password-credentials.json"
8 - "data:/home/opuser/.op/data"
9 op-connect-sync:
10 image: 1password/connect-sync:latest
11 ports:
12 - "127.0.0.1:8081:8080"
13 volumes:
14 - "/opt/opconnect/1password-credentials.json:/home/opuser/.op/1password-credentials.json"
15 - "data:/home/opuser/.op/data"
16
17
18volumes:
19 data:
Store the 1password-credentials.json file (downloaded from your Connect setup flow) at /opt/opconnect/ on the host, and ensure directory permissions prevent unintended reads. After running docker compose up -d, you can set OP_CONNECT_HOST=http://127.0.0.1:8080 and OP_CONNECT_TOKEN to the access token generated in 1Password; Vault can then talk to the local Connect API immediately.
Once Vault is running and unsealed, calculate the binary checksum and register the plugin. On macOS:
1export VAULT_ADDR=http://127.0.0.1:8200
2SHA256_CHECKSUM=$(shasum -a 256 ./vault/plugins/op-connect | cut -d ' ' -f1)
3vault plugin register -sha256="$SHA256_CHECKSUM" -version=v1.1.0 secret op-connect
Mount the engine and hand over your 1Password Connect details:
1vault secrets enable --path=op op-connect
2vault write op/config \
3 op_connect_host=$OP_CONNECT_HOST \
4 op_connect_token=$OP_CONNECT_TOKEN
You can also load the configuration from JSON if you prefer keeping the values out of your terminal history:
1{
2 "op_connect_host": "https://op-connect.example.com:8443/",
3 "op_connect_token": "your_access_token"
4}
1vault write op/config @op-connect-config.json
For Vault Enterprise namespaces repeat the enable + config commands within each namespace (vault secrets enable -namespace=finance op).
Now every Vault client can treat op/ like any other secrets engine:
1# Discover vaults exposed by the Connect token
2vault list op/vaults
3
4# List items inside a specific 1Password vault
5vault list op/vaults/engineering/items
6
7# Read a single item (title or UUID)
8vault read op/vaults/engineering/items/docker-hub-service-account
Creating or updating items works with JSON payloads that follow the Connect API schema:
1{
2 "category": "login",
3 "title": "Example Login",
4 "fields": [
5 { "id": "username", "type": "STRING", "purpose": "USERNAME", "value": "my_user" },
6 { "id": "password", "type": "CONCEALED", "purpose": "PASSWORD", "generate": true }
7 ]
8}
1vault write op/vaults/engineering/items @login.json
2vault delete op/vaults/engineering/items/docker-hub-service-account
Behind the scenes the plugin forwards the request to 1Password Connect, so rotations, audits, and access revocations stay centralized in 1Password.
Treat the op/* path like any other engine when crafting ACL policies. A minimal, read-only policy could look like this:
path "op/data" {
capabilities = ["list"]
}
path "op/vaults/engineering/*" {
capabilities = ["read", "list"]
}
Because Vault merely proxies the data, principle of least privilege matters in both systems:
op/* paths.If you instead require Vault to become the new source of truth, stick to the synchronization flow from the original article. Otherwise, the 1Password secrets engine is the fastest way to bridge both ecosystems while keeping governance inside 1Password.
Let us know if you want help hardening the deployment or integrating it with Terraform, Nomad, Kubernetes, or HCP.
You are interested in our courses or you simply have a question that needs answering? You can contact us at anytime! We will do our best to answer all your questions.
Contact us