Primary assets:
- local secret values stored in the plugin mount;
- destination credentials or workload identity configuration;
- association policy that decides where secrets are copied;
- remote destination secret values;
- queue and status records that may reveal operational metadata;
- ownership metadata that controls remote mutation safety.
Threats in scope:
- destination outage causing lost or duplicated sync operations;
- compromised destination credential being reused outside the plugin;
- accidental overwrite of existing remote secrets;
- app writer causing sync to an unauthorized destination;
- SSRF through custom destination endpoints;
- secret value leakage through logs, status, metrics, errors, or tests;
- stale restore data overwriting newer remote destination state;
- replay of old queue operations after plugin or OpenBao restart;
- two mounts or restored clusters managing the same remote object.
Threats out of scope for the plugin:
- compromise of OpenBao root/operator credentials;
- compromise of OpenBao storage encryption or seal material;
- malicious OpenBao core binary;
- full compromise of destination provider control planes.
OpenBao policies remain the primary authorization layer. The plugin exposes separate path surfaces so operators can delegate safely:
info all roles that need API defaults or capabilities
config platform operators
config/restore-guard/* platform operators
data/* app/team writers and readers
metadata/* app/team readers, writers, or delegated owners
sources/* app/team writers or delegated owners
destinations/* platform operators
associations/* platform operators or delegated app owners
queue/* platform operators
status/* app/team readers or platform operators
reconcile/<path>/plan delegated owners or platform operators
reconcile/<path> platform operators when applying status refresh
Use Policy examples for concrete OpenBao policy snippets.
Association creation is the highest-risk authorization operation because it causes a secret to leave OpenBao. It requires destination authority and, in hardened posture, source eligibility.
Fresh mounts start with security_posture=standard. In that mode, a trusted
platform operator may own both destinations/* and associations/*, and
unconstrained destinations are valid for operator-managed sync. When
application owners receive association-management policy for their own source
prefixes, operators should set security_posture=hardened.
Source eligibility is local source metadata, not an implicit source-read
permission check. In hardened posture, the backend requires
source sync to be explicitly enabled before an enabled association can enqueue
or dispatch sync work. OpenBao policy must therefore control who can call
sources/<path>/enable and sources/<path>/disable.
Delegated association owners do not need source payload read access unless the
deployment also wants them to inspect the secret value. Combine delegated
association access with app reader or writer policy only when that is intended.
security_posture=hardened makes this self-service model enforceable:
association create, enable, manual sync, reconcile, and queued dispatch refuse
destinations whose allowed_source_path_prefixes or
allowed_resolved_name_prefixes are empty, returning the
destination_unconstrained blocker. Hardened posture also rejects writes that
would create or update an unconstrained destination.
Destination authority can be proven through:
- OpenBao policy controlling who can write
destinations/*; - destination-level source path prefix constraints;
- destination-level resolved remote-name prefix constraints;
- provider capability checks during association plan, activation, manual sync, enable, and dispatch.
Event-triggered dispatch and queue/drain execute remote mutations only through
the durable queue dispatcher. They honor the global disabled flag and restore
guard, refuse unsafe OpenBao replication states, recover incomplete enqueue
intents before dispatch, and do not expose source payload data in responses.
queue/drain is an operator action because it can execute remote mutations for
all due operations in the queue.
Implemented controls:
- explicit source sync enablement before enabled association activation in hardened posture;
- opt-in
security_posture=hardened, which requires constrained destinations for delegated association use; - destination-level allowed source path prefixes;
- destination-level allowed resolved remote-name prefixes;
- required ownership metadata;
- plan output that shows remote object names and destination scope.
Delegated app owners must not be able to use a broad platform destination to write arbitrary remote names. Destination records can constrain source paths and resolved remote-name prefixes. In hardened posture, both constraint lists must be present. The configured constraints are checked during association plan, association activation, manual sync, enable, manual reconcile, background drift read-state, and queued dispatch, so a destination policy tightened after enqueue still blocks remote mutation and provider read-state.
- Secret values are stored only in plugin storage under the OpenBao barrier.
- Reads return only requested secret data, following OpenBao policy.
- Secret payloads do not appear in logs, status records, metrics, or error strings.
- Queue, status, plan, and reconcile responses can include versions, destination names, verification markers, and remote object identifiers, but not source values.
- Non-payload metadata, including source paths, resolved remote names, and Kubernetes data-map key names, can appear in validation and diagnostic responses. Grant plan, reconcile, status, and association read access only to roles that may inspect that metadata.
- Durable status records can include payload hashes for comparison and drift tracking, but payload hashes are not telemetry labels.
- Tests include canary secret values and assert they do not appear in logs, errors, queue, status, plan output, reconcile output, or metrics.
Source secret versions and destination credentials are stored under seal-wrapped storage prefixes.
- Destination credential paths are listed in
SealWrapStorage. - Sensitive fields are split from non-sensitive destination metadata.
- Reads of destination config redact sensitive values.
- Credential rotation is possible without recreating associations.
- Prefer workload identity and federated identity over static keys where the destination supports it.
- AWS destinations support SDK default credentials, assume-role auth, and
web-identity auth. Assume-role
external_idis stored in seal-wrapped sensitive config. Web-identity token contents are not stored by the backend; the configured token file must be mounted for the plugin process. web_identity_token_fileand Kuberneteskubeconfig_pathare public destination config file paths. A caller withdestinations/*write access can point the plugin process at files that the process can read; contents are not stored or echoed by the backend. Treat destination write access as trusted platform-operator authority over these runtime file inputs.- Static access keys and session tokens are recognized as sensitive fields but intentionally remain unsupported auth material.
- GitLab API tokens and Kubernetes bearer tokens are stored in seal-wrapped sensitive config and redacted on destination reads.
- Derived short-lived tokens are cached only in memory and invalidated on destination config updates.
- Provider SDK default credential chains are explicitly allowed or disabled by destination config; no surprising ambient credentials.
Destination configs that include custom endpoints are explicit and validated:
endpoint_urlrequires anendpoint_policy;endpoint_policy=localis for development endpoints such as LocalStack and may usehttponly for local hosts;endpoint_policy=privaterequireshttpsand permits only RFC 1918 IPv4 or unique-local IPv6 addresses;- default AWS endpoints remain preferred for production;
- AWS private custom endpoints are rechecked at client creation time and every DNS answer must remain private;
- GitLab
base_urlrejects localhost, private, link-local, multicast, and unspecified hosts by default, and rechecks DNS answers at client creation time unlessallow_private_network=trueis set; - Kubernetes token-auth
api_serverrejects localhost, private, link-local, multicast, and unspecified hosts by default, and rechecks DNS answers at client creation time unlessallow_private_api_server=trueis set; - optional explicit allowlists are still required for hardened private endpoint deployments;
- AWS and GitLab provider-owned HTTP clients do not inherit ambient proxy configuration;
- GitLab provider redirects are disabled by the default provider HTTP client.
Provider clients use context timeouts and bounded network behavior where the provider owns HTTP transport. AWS and GitLab providers use bounded default HTTP clients; GitLab provider requests also use bounded response-body reads.
The implemented safety posture is ownership-only overwrite. There is no API setting that force-overwrites unowned remote objects.
Use Ownership and safety for the operator-facing model behind ownership metadata, drift, collisions, stale objects, and restore identity.
Remote ownership metadata includes the logical fields supported by the provider:
managed=<true>
mount_uuid=<mount-uuid>
restore_epoch=<restore-epoch>
association_id=<association-id>
source_path=<source-path>
source_version=<source-version>
object_id=<object-id>
payload_sha256=<hash>
AWS Secrets Manager tags, Kubernetes annotations, and GitLab variable descriptions include the OpenBao mount UUID and restore epoch. Provider ownership checks require both values to match the current runtime identity; missing or mismatched values are treated as ownership loss.
Remote ownership metadata is not encrypted as part of the source payload. It can expose source paths, remote names, versions, object identifiers, payload hashes, and runtime identity to principals that can inspect destination metadata. Do not place sensitive information in source or remote names, and scope destination metadata-read permissions to roles that need this operational context. Exact metadata carriers and keys are documented on the provider operations pages.
If ownership metadata is absent or conflicts, the plugin reports
REMOTE_OWNERSHIP_LOST and does not overwrite. If ownership matches but the
remote payload differs from the current OpenBao source version, reconcile
reports DRIFTED. Operators can remove or inspect the remote object and then
run manual sync when OpenBao should recreate or repair the object.
Delete behavior:
- association
delete_modedefaults toretain; - remote delete is enqueued only for
delete_mode=delete; - provider delete must prove ownership or return an
ownershiperror; - deleting, soft-deleting, or destroying the current local source version cancels queued upserts for that version before any remote delete is processed;
- undeleting the current local source version queues replacement upserts for enabled associations.
OpenBao audit devices capture requests to plugin paths according to normal
OpenBao audit behavior. The plugin does not add secret values to responses
beyond the explicit data/* read path. Requests that write data/<path> still
flow through OpenBao audit devices like other secret-engine writes; configure
and rotate audit HMAC keys according to the deployment's OpenBao audit policy.
For remote operations, the plugin persists and logs correlation metadata:
- OpenBao mount accessor or mount name;
- source path;
- source version;
- association ID;
- object ID;
- operation ID;
- destination reference;
- remote object identifier;
- result state and error class.
This gives operators enough evidence to correlate OpenBao audit logs with destination-side audit logs without recording secret payloads.
OpenBao backup/restore captures local secret versions, destination configs, associations, queue, and status. After restore, blind reconciliation could overwrite newer remote state.
The plugin uses a restore guard:
- fresh mounts start with
restore_guard=false; - operators can set
restore_guard=trueafter restore or clone review starts; - event-triggered, periodic, and manual-drain remote mutation is disabled while the guard is active;
- background drift detection may continue to update local status from provider read-state checks while the guard is active, but repair enqueue and dispatch remain blocked;
reconcile/<path>/planandreconcile/<path>remain available to inspect provider state before pushing restored data to destinations;- explicit operator acknowledgement to resume sync;
- persisted restore epoch rotated on acknowledgement and included in provider ownership metadata where possible.
Use the operator runbook for restore review.
Instrument metrics through the OpenTelemetry metric API where practical. The plugin must not store exporter credentials in plugin storage. Keep exporter and collector setup as a deployment concern.
OpenTelemetry instrument names are documented in Observability. Security-relevant instruments include:
openbao.secret_sync.queue.depth
openbao.secret_sync.queue.capacity
openbao.secret_sync.queue.utilization
openbao.secret_sync.operations
openbao.secret_sync.provider.requests
openbao.secret_sync.provider.request.duration
openbao.secret_sync.readiness.checks
openbao.secret_sync.remote_mutation.blocked
openbao.secret_sync.reconcile.runs
openbao.secret_sync.drift.repairs
openbao.secret_sync.restore_guard.active
Prometheus exporters may translate instrument names and units into Prometheus
series such as openbao_secret_sync_operations_total.
Metric labels do not include secret values, high-cardinality source paths, resolved remote names, destination names, association ids, operation ids, payload hashes, ARNs, or cloud account ids by default.
Logs include:
- operation ID;
- association ID;
- destination reference;
- source path;
- source version;
- error class;
- retry schedule.
Logs do not include:
- secret payloads;
- destination credential material;
- authorization tokens;
- full provider responses when they may contain secret data.
Health is layered:
- plugin process healthy;
- mount configured;
- queue writable and below critical capacity;
- destination config valid;
- destination reachable;
- destination authorized;
- restore guard state visible.
Destination outages degrade sync status and queue progress, not local source read/write availability. There is no synchronous provider-write mode.
Implemented rate-limit handling is based on provider error classification and
bounded queue retry. Provider rate_limit errors map to
DESTINATION_RATE_LIMITED status and retry-wait queue state. Queue capacity
limits accepted backlog before source writes commit.
Automatic retry policy:
- retry provider
rate_limitandunavailableerrors only; - use a bounded attempt budget before marking the operation terminal;
- keep validation, authentication, authorization, ownership, collision, and capacity failures terminal until an operator changes configuration or retries manually;
- manual queue retry resets the attempt counter and schedules the operation immediately.
The plugin is shipped like other OpenBao external plugins:
- reproducible Go builds where practical;
- checksums for every release artifact;
- SBOM, dependency vulnerability scan, and dependency license report;
- signed release artifacts;
- documented
bao plugin registerflow; - documented minimum OpenBao version;
- documented plugin file ownership and permissions;
- changelog with migration notes for storage schema changes.
Storage schema changes are explicit and backward compatible where possible. If a migration is required, the plugin fails closed with a clear error until an operator runs an explicit migration or mounts a compatible version.
The practical operator workflow lives in Operator runbook. This document records the security and operations requirements that the runbook satisfies.
Runbooks cover:
- install, register, and mount;
- configure destination;
- create association;
- validate destination;
- inspect queue and status;
- drain due queue work for deterministic verification or controlled catch-up;
- retry failed operation;
- pause and resume sync;
- restore-safe reconciliation;
- rotate destination credentials;
- handle remote ownership loss;
- handle destination rate limits;
- handle queue capacity pressure;
- uninstall and retain, orphan, or delete owned remote secrets.