Skip to main content

OpenBao/Vault secrets source for pydantic-settings with AppRole authentication

Project description

openbao-pydantic-settings-adapter

PyPI version Python 3.11+ License: PolyForm Noncommercial

OpenBao/Vault secrets source for pydantic-settings with AppRole authentication, automatic token renewal, and graceful fallback to environment variables.

Features

  • AppRole Authentication - Secure machine-to-machine authentication
  • Automatic Token Renewal - TokenManager handles TTL-based token lifecycle
  • Namespace Support - Multi-tenant isolation (team/project level)
  • Two-Path Architecture - Separate secrets and supersecrets paths with deep merge
  • SecretStr Validation - Enforces SecretStr for supersecrets to prevent log exposure
  • Graceful Degradation - Falls back to .env when OpenBao is unavailable
  • Type Safety - Full type annotations with py.typed marker (PEP 561)

Installation

pip install openbao-pydantic-settings-adapter

Note: The import name is openbao_settings:

from openbao_settings import OpenBaoSettingsSource

Quick Start

from pydantic import SecretStr
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource

from openbao_settings import OpenBaoSettingsSource


class Settings(BaseSettings):
    database_url: str
    api_key: SecretStr  # Fields from supersecrets MUST use SecretStr

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return (
            init_settings,
            OpenBaoSettingsSource(settings_cls),  # OpenBao has priority
            env_settings,
            dotenv_settings,
        )


settings = Settings()

Configuration

Configure via environment variables:

Variable Description Required
BAO_ADDR OpenBao server URL (e.g., http://localhost:8200) Yes
BAO_SECRET_PATH Base path to secrets (e.g., myapp) Yes
BAO_APPROLE_ROLE_ID AppRole Role ID Yes
BAO_APPROLE_SECRET_ID AppRole Secret ID Yes
BAO_NAMESPACE OpenBao namespace for multi-tenant isolation No
BAO_MOUNT_POINT KV engine mount point (default: kv) No
BAO_TIMEOUT Connection timeout in seconds (default: 30) No
BAO_TOKEN_RENEWAL_THRESHOLD When to renew token (default: 0.75 = at 75% of TTL) No

For Docker/Kubernetes, you can use file-based credentials:

  • BAO_APPROLE_ROLE_ID_FILE
  • BAO_APPROLE_SECRET_ID_FILE
  • BAO_NAMESPACE_FILE

Secrets Architecture

The library reads from two paths and merges them:

kv/{BAO_SECRET_PATH}/secrets      - Regular secrets (developers can view/edit)
kv/{BAO_SECRET_PATH}/supersecrets - Sensitive secrets (admin only)

Important: Fields loaded from supersecrets MUST use SecretStr type annotation. The library validates this at runtime and raises SecurityMisconfigurationError if violated.

# Wrong - will raise SecurityMisconfigurationError
class Settings(BaseSettings):
    api_key: str  # Loaded from supersecrets but not SecretStr!

# Correct
class Settings(BaseSettings):
    api_key: SecretStr  # Properly protected

Token Lifecycle

TokenManager automatically handles token renewal:

  1. Caches tokens per (namespace, role_id) combination
  2. Renews at 75% of TTL (configurable via BAO_TOKEN_RENEWAL_THRESHOLD)
  3. Thread-safe for multi-threaded applications
  4. Graceful degradation if OpenBao becomes unavailable
from openbao_settings import TokenManager

# Manual token invalidation (e.g., for credential rotation)
TokenManager().invalidate()

# Check token health
TokenManager().is_healthy(namespace="myapp", role_id="...")

Diagnostics

Track where settings were loaded from:

from openbao_settings import get_last_source_info, SourceInfo

# Basic info (only OpenBao fields in field_sources)
info: SourceInfo = get_last_source_info()
print(info.status)              # True if loaded from OpenBao
print(info.source)              # "openbao" or "env"
print(info.details)             # Human-readable description
print(info.openbao_keys_loaded) # Number of keys from OpenBao
print(info.timestamp)           # When the check was performed

# Full field tracking (pass settings instance)
settings = Settings()
info = get_last_source_info(settings)
print(info.field_sources)
# {
#   "db.host": "openbao/secrets",
#   "db.port": "openbao/secrets",
#   "db.password": "openbao/supersecrets",
#   "services.otlp.domain": "openbao/secrets",
#   "services.otlp.password": "openbao/supersecrets",
#   "log_level": "env",
#   "debug": "default"
# }

Field source values (FieldSourceType) with dot notation for nested fields:

  • "openbao/secrets" — loaded from secrets/ path (e.g., "db.host")
  • "openbao/supersecrets" — loaded from supersecrets/ path (e.g., "db.password")
  • "openbao/merged" — same leaf field in BOTH paths (supersecrets wins)
  • "env" — loaded from environment variables or .env file
  • "default" — uses field's default value

API Reference

Classes

  • OpenBaoSettingsSource - Main settings source for pydantic-settings
  • OpenBaoClient - Low-level HTTP client for OpenBao API
  • TokenManager - Singleton for token lifecycle management
  • TokenEntry - Dataclass for cached token metadata (token, expires_at, ttl)

Exceptions

  • OpenBaoError - Base exception for all OpenBao errors
  • InvalidPathError - Path not found (HTTP 404)
  • InvalidRequestError - Invalid request/credentials (HTTP 400)
  • ForbiddenError - Access denied (HTTP 403)
  • InvalidResponseError - Unexpected API response structure
  • SecurityMisconfigurationError - SecretStr validation failed

Response Models

  • AppRoleLoginResponse - AppRole authentication response
  • AuthInfo - Auth block with token, TTL, policies (part of AppRoleLoginResponse.auth)
  • KvV2ReadResponse - KV v2 read response
  • SecretDataWrapper - Inner data wrapper for KV v2 secrets (part of KvV2ReadResponse.data)
  • SourceInfo - Settings source diagnostic info
  • FieldSourceType - Type alias for field source values (Literal["openbao/secrets", "openbao/supersecrets", "openbao/merged", "env", "default"])

Utilities

  • get_last_source_info(settings=None) - Get info about last settings load source
  • deep_merge(base, override) - Recursively merge dictionaries (override wins)

Development

Syncing with Remote

# Fetch commits and tags
git pull origin main --tags

# If local branch is behind remote
git reset --hard origin/main

Verify sync status

git log --oneline origin/main -5
git diff origin/main

CI/CD

The pipeline consists of two stages:

  1. auto_tag — on push to main, reads __version__ from __init__.py and automatically creates a tag (if it doesn't exist)

  2. mirror_to_github — on tag creation, mirrors the repository to GitHub (removing .gitlab-ci.yml)

Release flow

  1. Update __version__ in src/openbao_settings/__init__.py
  2. Push to main
  3. CI creates tag → mirrors to GitHub → publishes to PyPI

Versioning

This project follows Semantic Versioning (MAJOR.MINOR.PATCH):

  • PATCH — bug fixes, documentation, metadata
  • MINOR — new features (backwards compatible)
  • MAJOR — breaking changes

License

This project is licensed under the PolyForm Noncommercial License 1.0.0.

You may use this software for noncommercial purposes only.

See LICENSE for the full license text, or visit polyformproject.org.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openbao_pydantic_settings_adapter-1.0.6.tar.gz (23.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file openbao_pydantic_settings_adapter-1.0.6.tar.gz.

File metadata

File hashes

Hashes for openbao_pydantic_settings_adapter-1.0.6.tar.gz
Algorithm Hash digest
SHA256 5336806e320b13453f75156a54980e9046e404a9cbe53863905490c5e947ac18
MD5 d4fcc5f4afe2880f847f9b5183099783
BLAKE2b-256 69c0425ee92376ffb1a3d2d14cf577e1e56ef748ccded05f87e59342e6e41c46

See more details on using hashes here.

Provenance

The following attestation bundles were made for openbao_pydantic_settings_adapter-1.0.6.tar.gz:

Publisher: publish.yml on itstandart/openbao-pydantic-settings-adapter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file openbao_pydantic_settings_adapter-1.0.6-py3-none-any.whl.

File metadata

File hashes

Hashes for openbao_pydantic_settings_adapter-1.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 2340c9e8a7fb99b9706e2c2b646ba759634f244b77a209211df2dcbf8d1881fb
MD5 7fa3c6e4cd8de9fb7450fe74738ace99
BLAKE2b-256 6efd499bcfa1da156ef16787785b824e72f398cd0f7b3ea56560384d624086cd

See more details on using hashes here.

Provenance

The following attestation bundles were made for openbao_pydantic_settings_adapter-1.0.6-py3-none-any.whl:

Publisher: publish.yml on itstandart/openbao-pydantic-settings-adapter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page