Skip to main content

netbox-openbao

Keep device, VM, and service secret material in OpenBao, while NetBox owns the credential inventory and its relationships.

netbox-secrets        stores secrets *in* NetBox.
netbox-vault-secrets  reads secrets in the *browser*.
netbox-openbao        keeps secrets in OpenBao and resolves them *server-side
                      over the API* — so your automation, not just your
                      operators, can use them.

That third line is the gap this plugin exists to close.

Status: early alpha. The data model, REST API, and security invariants are implemented and tested against NetBox 4.7 and OpenBao 2.6. See Roadmap for what is deliberately not here yet.


The idea

Split secret material from credential metadata. Material lives only in OpenBao. Everything non-secret lives in NetBox, fully searchable, filterable, and API-queryable.

For an SSH keypair:

Goes to OpenBao Stays in NetBox (plaintext, indexed)
private key public key
passphrase SHA256 fingerprint
password username
API token key type and bit length
certificate private key serial, issuer, subject, not_before, not_after

So you can answer "which certificates expire in the next 30 days?", "which devices trust fingerprint X?", or render a config template containing a public key — with zero OpenBao reads and no reveal permission. That is the headline feature, and it falls out of the split rather than being bolted on.

Why a Django plugin and not a sidecar service

This is a credentials plugin. A separate service resolving secrets would have to answer "is this NetBox user allowed to see this credential?" itself — and then you have two authorization implementations for your most sensitive data, which will drift. Every drift is a privilege-escalation bug.

As a Django plugin it reuses NetBox's tokens, object permissions, constraints, and changelog directly. Blocking I/O is handled with NetBox's own RQ job framework: a single reveal is synchronous and sub-100ms; bulk work is a background job.

Security properties

These are enforced structurally, not by convention, and each is covered by a test in netbox_openbao/tests/test_security.py:

  • No Credential model field can hold credential material. There is no column to leak, so the changelog, export templates, and the REST representation are safe by construction rather than by careful configuration. OpenBao service identities live separately as versioned ciphertext in EngineAuthMaterial and are excluded from those surfaces.
  • secret_data is write_only. DRF itself refuses to serialize it — into a GET, a brief=true response, the browsable API, or an OpenAPI example.
  • reveal is a permission of its own, separate from view and constrainable in the standard ObjectPermission UI. A role can inventory every credential and read none.
  • Reveal responses are JSON-only and no-store. The browsable renderer would template the secret into cacheable HTML; it is explicitly removed.
  • The UI reveal is POST-only, so a secret is never fetched by a bookmark, a prefetch, a link scanner, or a history replay.
  • Authentication material is encrypted at rest. RoleIDs, SecretIDs, tokens, Kubernetes login settings, and broker client keys are stored only as versioned ciphertext derived from Django's SECRET_KEY. REST and UI surfaces expose configured/missing status, never values or ciphertext.
  • Per-tier AppRoles, so a leaked SecretID reads only its own tier and a tier whose SecretID was never delivered to an instance is unreadable from it. This bounds blast radius; it does not re-authorize the NetBox user, and the documentation is careful about the difference.
  • Backend exceptions carry no server text. An OpenBao 403 body can enumerate policy rules; it never reaches a log, a traceback, or a response.
  • Every access is audited — who, when, from where, and whether it succeeded. Never the value.
  • OpenBao administration is typed and permissioned. Cluster, auth/MFA, secret-engine, policy, identity, OIDC, and namespace operations use static reviewed contracts intersected with runtime capability discovery. The Web UI and REST API never become a generic privileged proxy.

Requirements

NetBox 4.7 (4.7.0–4.7.99; exact 4.7 beta2 evidence below)
Python 3.12+
PostgreSQL 15+ with the ltree extension (a NetBox 4.7 requirement)
Redis 6+
OpenBao 2.6.x, KV v2 mount
HashiCorp Vault supported as an alternative backend — see below
Broker mode optional; needs netbox-openbao-broker

NetBox 4.7 is required: SSH credentials are tied to an Application Service (ipam.Service), whose ports 4.7 stores as a port_mappings array, and the seeded SSH service template relies on the same model. NetBox 4.6 and earlier are not supported. The full suite currently passes on exact NetBox v4.7.0-beta2. See docs/installation.md for the history of the former 4.6 boundary.

Install

pip install netbox-openbao
# configuration.py
PLUGINS = ['netbox_openbao']
python manage.py migrate
systemctl restart netbox netbox-rq

Configure all runtime settings and service identities in OpenBao → Configuration → Settings, through /api/plugins/openbao/, or with the management command:

python manage.py openbao_configure engine \
  --slug primary --name Primary --api-url https://bao.example.net:8200
python manage.py openbao_configure auth --engine primary --set role_id
python manage.py openbao_configure auth --engine primary --set secret_id

The prompts do not echo material or place it in shell history. SECRET_KEY is the single root of trust and cannot live in the database beside the ciphertext it protects. See the rotation procedure in docs/configuration.md.

assignable_models is the allowlist of object types a credential may be assigned to. An installed plugin can add its own models to it from AppConfig.ready() — see Assignable object types — so integrating with this plugin does not require every deployment to restate the same list in a settings file. assignable_models_deny subtracts from the result, which is what keeps the final word with the operator.

Full detail in docs/installation.md and docs/configuration.md, or on the documentation site: https://emersonfelipesp.github.io/netbox-openbao/.

Release operators must use the immutable Gitea package and NMS proof-v3 deployment path. Target IDs, release-candidate and final publication triggers, validation, and rollback are documented in docs/release-deployment.md.

Using it

# Inventory — never returns material
curl -H "Authorization: Bearer $TOKEN" \
  'https://netbox.example.net/api/plugins/openbao/credentials/?credential_type=x509-keypair'

# Everything expiring in the next 30 days — zero OpenBao reads
curl -H "Authorization: Bearer $TOKEN" \
  'https://netbox.example.net/api/plugins/openbao/credentials/?expires_within_days=30'

# What does device 88 hold?
curl -H "Authorization: Bearer $TOKEN" \
  'https://netbox.example.net/api/plugins/openbao/credentials/?assigned_object_type=dcim.device&assigned_object_id=88'

# Resolve material — requires netbox_openbao.reveal_credential
curl -H "Authorization: Bearer $TOKEN" \
  'https://netbox.example.net/api/plugins/openbao/credentials/142/reveal/?reason=CHG-1234'

OpenBao or Vault

The plugin is named for OpenBao and that is its reference backend, but a SecretEngine can be pointed at HashiCorp Vault instead by changing one field. The two share the KV v2 and AppRole surfaces, and the plugin's whole wire-protocol contract is run as one shared suite against both servers, so this is verified rather than claimed.

Broker mode, and what it is honestly worth

A third backend, broker, points an engine at netbox-openbao-broker instead of at OpenBao. NetBox then holds a client certificate that lets it ask, and the broker holds the AppRole that can actually read.

It is worth being precise about what that buys, because it is easy to oversell and an operator might relax controls elsewhere on the strength of it:

It does not make "NetBox compromise ≠ secret compromise" true. An attacker with code execution in NetBox can still ask the broker for material, and the broker will answer for anything NetBox is authorized to request.

What it does buy: stealing NetBox's database or configuration no longer yields credentials that read the vault directly, the broker's audit log sits outside NetBox's blast radius, and the AppRole's SecretID never exists on the NetBox host at all. That is a real improvement against offline compromise, backup theft, and configuration leakage — and it is not the stronger claim.

Optional throughout. The default deployment is unchanged, and nothing above the SecretBackend abstraction knows which mode is in use. See docs/installation.md.

OpenBao administration also has full broker parity. The plugin negotiates a versioned, digest-bound, instance-enabled contract before exposing cluster, authentication, secret-engine, policy/identity, lease/tool, or Raft snapshot operations. The broker accepts only reviewed operation identifiers and bounded typed inputs; it is not an arbitrary OpenBao proxy and never replaces NetBox's per-user authorization.

Data model

Model Role
OpenBaoCluster One administrative API endpoint and namespace. Holds no auth material.
SecretEngine One credential-storage mount, associated with a cluster. Holds no auth material.
CredentialPolicy An authorization tier mapped onto a real OpenBao policy, with its own AppRole.
Credential The inventory record: identity, public material, lifecycle. Never the secret.
CredentialAssignment Many-to-many binding to Devices, VMs, and Application Services, with a purpose. For SSH it also holds the pinned host key.
SSHPublicKey A user's public SSH key and computed fingerprint for an SSH Application Service.
CredentialAccessLog Append-only correlation between a NetBox user and an OpenBao read.
OpenBaoAdministrationLog Append-only metadata correlation for administrative probes and operations.

The OpenBao path is UUID-derived and immutable (<prefix>/credentials/<uuid>). A path derived from the object graph would break the first time a credential is renamed or reassigned, and a broken path is an orphaned secret nobody can find. Discovery instead comes from KV v2 custom_metadata, which carries the credential's NetBox identity, type, policy, and assignments, so tooling outside NetBox can list the mount and filter on them.

Roadmap

Implemented: the credential models, the backend abstraction with the OpenBao implementation, credential type schemas and extractors, the full REST API with the security invariants above, list/detail/edit UI, Device/VM/Service panels, the background jobs, and staged rotation (write, verify, promote — never break running access). The OpenBao administration plane now has cluster inventory, bounded runtime capability discovery, dedicated permissions, metadata-only audit, guarded bootstrap and seal operations, HA and Raft state, peer-removal safety, and bounded authenticated snapshot recovery. Its pinned OpenBao 2.6.2 parity manifest records cluster bootstrap, Raft storage, secrets-engine lifecycle, the classified API explorer, KV v1/v2, transit, database, SSH, TOTP, PKI, Kubernetes, policies, identity, OIDC, namespaces, leases, tools, and UI response-header administration as complete. Mounted operation execution covers runtime-advertised GET, LIST, POST, PUT, PATCH, and DELETE templates that satisfy the reviewed grammar, schema, authorization, and material-handling contract, including external plugin-style schemas. The checked-in final-operation fixture and runtime conformance endpoint fail on missing, duplicate, unclassified, or stale OpenBao 2.6.2 contracts. Other discovered operations remain non-executable until their capability family is implemented and reviewed.

Also shipped since: the Ansible lookup plugin and broker mode, whose earlier description here — "so a NetBox compromise is not a secret compromise" — claimed more than the design delivers and has been corrected above.

Deliberately not yet here:

  • Cloud credential engines and the remaining typed dynamic-secret families
  • OpenBao capabilities introduced after the pinned 2.6.2 UI and runtime contracts. A later minor release requires a new review and evidence fixture; runtime discovery alone never expands execution.

Host operations via netbox-rpc are implemented on SecretEngine: bind an OpenBao host dcim.Device, then dispatch the seeded service.openbao.1.* read/write procedures through POST …/engines/{id}/run-procedure/ (UI mirror: the engine detail page). Each dispatch creates an audited OpenBaoProcedureRun row linked to netbox_rpc.RPCExecution.

Giving a device SSH access

There is an Add SSH access button on every Device and VM page. One form creates the ipam.Service, generates or accepts the keypair, writes the private half to OpenBao, and assigns the credential — in a single transaction. If it generates the key, the public half is shown once so it can go straight into authorized_keys.

Coming from netbox-secrets?

python manage.py openbao_import_secrets --engine primary --policy imported --dry-run

The importer copies — it never deletes — infers each secret's type and proves the inference by extraction, and is resumable. See docs/migration-from-netbox-secrets.md.

Connections use NetBox's built-in Application Service (ipam.Service); the plugin has no endpoint model of its own. The host is the single IP address filled in on the service and the port is its single TCP port. A service with no IP, several IPs, no TCP port or several TCP ports cannot be used to connect and is reported with a clear error. The SSH host-key pin lives on the credential assignment. The earlier NMS credential importer and its preflight command were removed in 0.2.1.

Development

docker compose -f docker-compose.dev.yml up -d

See docs/development.md for running the suite against real NetBox 4.7 and a live OpenBao dev server, and CONTRIBUTING.md for what a change is expected to bring with it.

The documentation site builds from the checkout with no package install and no NetBox — mkdocstrings reads the source statically:

pip install '.[docs]'
mkdocs serve

License

Apache-2.0.

Metadata

Release files for netbox-openbao 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for netbox-openbao 0.2.2
File Size Uploaded
netbox_openbao-0.2.2.tar.gz 480.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for netbox-openbao 0.2.2
File Interpreter ABI Platform
netbox_openbao-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 1.1 MB

Release files / netbox_openbao-0.2.2.tar.gz

Download URL netbox_openbao-0.2.2.tar.gz
Size 480.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5493206490a7313b453bf8470bf5b3fdc62e0557efa468f4e9506fa233f1fe73
BLAKE2b-256 checksum
How to use checksums
c4463990c8ff7a89b75abf443e8960aeda9d15edacf18cf8e248f0de8b8fe04f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / netbox_openbao-0.2.2-py3-none-any.whl

Download URL netbox_openbao-0.2.2-py3-none-any.whl
Size 581.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b682ae7ced48dce2b588e1ba8c4f2ff4ba525857f1d2759f1c7ee54a9c7ad24b
BLAKE2b-256 checksum
How to use checksums
e0ae4a02482e0ceea44fcdd0a68c7a5d4969ca82b398ca8d9fb0a12e22a78d41
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page