1Password ESO migration tooling
This doc describes the migration tooling used to move SOPS-managed Kubernetes secrets into 1Password for External Secrets Operator (ESO).
Workflow:
- Crawl SOPS files into metadata inventory.
- Push decrypted values to 1Password items with
op. - Generate
ExternalSecretmanifests that map item fields to K8s secret keys.
Goals
- Translate SOPS-managed Kubernetes Secrets into 1Password item fields.
- Keep plaintext out of disk by decrypting only during the push step.
- Persist an inventory that can be reused to generate ExternalSecrets.
Requirements
sopsinstalled locally.- 1Password account and dedicated vault (for example,
homelab). - 1Password CLI (
op) installed and authenticated. OP_VAULTset in the environment (or pass--vault).
Inventory format
The crawler writes inventory.json to a chosen output directory. It is metadata-only and does not store plaintext values.
Example:
{
"version": 1,
"root": "kubernetes/apps",
"vault": "homelab",
"entries": [
{
"sops_path": "kubernetes/apps/default/homebox/app/secret.sops.yaml",
"namespace": "default",
"app": "homebox",
"purpose": "app",
"section": null,
"item_name": "default.homebox",
"fields": [
"HBOX_MAILER_USERNAME",
"HBOX_MAILER_PASSWORD"
],
"item_id": null,
"ks_path": "kubernetes/apps/default/homebox/ks.yaml",
"helmrelease_path": "kubernetes/apps/default/homebox/app/helmrelease.yaml"
}
]
}Crawl workflow
Crawl a directory and build the inventory:
./scripts/onepassword/crawl.py \
--dir kubernetes/apps \
--output-dir ./migration-out \
--vault homelabNotes:
- Namespace/app inference uses nearest
ks.yamlandhelmrelease.yaml. opreferences followop://homelab/<namespace>.<appname>/[section]/<field>.- Item titles should be
{namespace}.{appname}and section should represent purpose when needed. - The crawler stores this as
sectionmetadata (nullforpurpose=app). - Separator preference between namespace and app name:
., then_, then-. --output-dirdefaults to--dir;OP_VAULTcan provide the default vault.
Push workflow
Push inventory entries to 1Password using op:
./scripts/onepassword/push.py \
--inventory ./migration-out/inventory.jsonNotes:
- The script decrypts each SOPS file locally and sends item templates to
opvia stdin. - Entries sharing the same
item_nameare merged into one 1Password item. - Entries with a non-null
sectionare written into that section on the item. - If two source files map to the same
item_nameand contain different values for the same field label, that item is skipped and reported as a conflict. - ESOβs 1Password provider resolves by field label and ignores section names, so field labels must stay unique within an item.
- Existing item titles prompt for apply mode: per-item, apply-all, or skip-all.
- Item IDs are written back to the inventory unless
--no-write-inventoryis used.
ExternalSecret generation
Generate ExternalSecret manifests next to each SOPS file:
./scripts/onepassword/externalsecrets.py \
--inventory ./migration-out/inventory.jsonNotes:
- Output files are derived from SOPS filename, e.g.
secret-oidc.sops.yaml->secret-oidc.externalsecret.yaml. - Generated manifests target
ClusterSecretStorenamedonepassword. - For each key,
remoteRef.keyis the item title andremoteRef.propertyis the field label.