How the Database-First Credential Provisioning Pattern Works in Open Notebook
The database-first credential provisioning pattern stores AI provider credentials in an encrypted database table and dynamically injects them into environment variables at runtime, falling back to existing environment variables when no database record is found.
Open Notebook implements a secure, flexible approach to managing AI service credentials through the key_provider.py module. Instead of relying solely on static environment variables, the system queries the database for provider credentials, decrypts them on demand, and provisions them into the process environment before instantiating AI models via the Esperanto library.
Architecture Overview
The pattern centers on a runtime provisioning layer that bridges the database and the AI factory. When a model needs initialization, the system first checks the credential table for the requested provider. If a record exists, the encrypted API key and configuration fields are decrypted and mapped to the specific environment variables expected by the underlying SDK (OpenAI, Azure, Vertex, etc.). This ensures that sensitive credentials remain encrypted at rest while remaining accessible to the application without manual configuration files.
Core Implementation in key_provider.py
The implementation resides in open_notebook/ai/key_provider.py and consists of a lookup layer, a configuration mapping, and specialized provisioning handlers for different provider types.
Database Lookup and Decryption
The _get_default_credential() function queries the Credential model via Credential.get_by_provider() (defined in open_notebook/domain/credential.py at lines 39–44). This method retrieves the first credential record matching the requested provider name and automatically decrypts the stored API key using the application's encryption key.
# From open_notebook/domain/credential.py
credential = await Credential.get_by_provider("openai")
# Returns decrypted credential with api_key accessible
Provider-to-Environment Mapping
PROVIDER_CONFIG serves as the single source of truth that links provider identifiers to their corresponding environment variable names. This dictionary handles the translation between Open Notebook's internal provider names (e.g., "openai") and the environment variables that the Esperanto library and underlying SDKs expect (e.g., OPENAI_API_KEY).
Simple Provider Provisioning
For providers requiring only an API key, _provision_simple_provider() (lines 13–42) handles the provisioning:
- Retrieves the credential from the database
- Sets the primary API key environment variable
- Optionally sets a base URL if specified in the credential record
# Simplified logic from key_provider.py
os.environ[env_var] = credential.api_key
if credential.api_base:
os.environ[f"{prefix}_API_BASE"] = credential.api_base
Complex Provider Handlers
Specialized functions handle multi-variable providers:
_provision_vertex(): Extractsproject,location, andcredentials_pathfrom the database record and setsVERTEX_PROJECT,VERTEX_LOCATION, andGOOGLE_APPLICATION_CREDENTIALS_provision_azure(): Maps the credential toAZURE_OPENAI_API_KEY,AZURE_OPENAI_ENDPOINT, andAZURE_OPENAI_API_VERSION_provision_openai_compatible(): Handles generic OpenAI-compatible endpoints with custom base URLs
Runtime Execution Flow
The public entry point provision_provider_keys(provider) orchestrates the provisioning sequence:
- Normalization: Standardizes the provider name (e.g., converting "azure_openai" to "azure")
- Dispatch: Routes to the appropriate helper based on provider complexity
- Environment Injection: Writes decrypted values to
os.environ - Fallback: If no database record exists, the function preserves existing environment variables (lines 89–106), ensuring backward compatibility
This function is typically invoked in open_notebook/ai/models.py (lines 135–140) before model creation, or via API endpoints in api/routers/models.py (lines 504–534).
# Runtime usage pattern
await provision_provider_keys("openai") # DB → env vars
model = await AIFactory.create_language(..., provider="openai")
Practical Code Examples
Provisioning a Single Provider at Runtime
Use provision_provider_keys() when initializing specific models to ensure credentials are loaded before instantiation:
from open_notebook.ai.key_provider import provision_provider_keys
async def init_model():
# Load DB-stored key for OpenAI (if present)
await provision_provider_keys("openai")
# Now the environment contains OPENAI_API_KEY → model can be created
model = await Model.create(
name="gpt-4",
provider="openai",
credential=await Credential.get_by_provider("openai")[0].id,
)
return model
Bulk Provisioning at Startup
The provision_all_keys() function iterates over every entry in PROVIDER_CONFIG and calls provision_provider_keys() for each provider plus complex variants:
from open_notebook.ai.key_provider import provision_all_keys
async def startup():
results = await provision_all_keys()
# results is a dict like {"openai": True, "vertex": False, ...}
print("Credential provisioning:", results)
Note: This approach is discouraged for per-request use because stale environment variables could persist after a credential is deleted from the database.
Azure OpenAI Configuration
Complex providers requiring multiple configuration parameters are handled automatically:
from open_notebook.ai.key_provider import provision_provider_keys
async def use_azure():
await provision_provider_keys("azure")
# Environment now contains:
# AZURE_OPENAI_API_KEY,
# AZURE_OPENAI_ENDPOINT,
# AZURE_OPENAI_API_VERSION, …
model = await Model.create(name="gpt-35-turbo", provider="azure")
return model
Summary
- Database-First Pattern: Credentials are stored encrypted in the
credentialtable and decrypted only at runtime viaCredential.get_by_provider() - Environment Injection: The
provision_provider_keys()function inopen_notebook/ai/key_provider.pymaps database records to environment variables expected by AI SDKs - Provider Flexibility: Simple providers use
_provision_simple_provider()while complex ones (Azure, Vertex) use specialized handlers that set multiple environment variables - Backward Compatibility: If no database record exists, the system falls back to existing environment variables without error
- Security: Credentials remain encrypted at rest and are only exposed in memory during the active process session
Frequently Asked Questions
What happens if no credential exists in the database for a provider?
If provision_provider_keys() cannot find a credential record for the requested provider, it silently falls back to the existing environment variables (lines 89–106 in key_provider.py). This preserves backward compatibility with traditional environment-based configuration while allowing seamless migration to database-stored credentials.
How are credentials secured in the database?
The Credential model in open_notebook/domain/credential.py stores API keys in an encrypted format. When retrieved via get_by_provider(), the model automatically decrypts the sensitive fields using the application's configured encryption key, ensuring that plaintext secrets never persist in the database.
Why inject credentials into environment variables instead of passing them directly to the AI factory?
The database-first credential provisioning pattern uses environment variables to maintain compatibility with the Esperanto library and underlying AI SDKs (OpenAI, Azure, etc.), which expect configuration through specific environment variable names. This abstraction allows Open Notebook to support multiple credential sources while leveraging existing SDK authentication mechanisms without modifying third-party library code.
When should I use provision_all_keys() versus provision_provider_keys()?
Use provision_provider_keys() for specific provider initialization to avoid polluting the environment with unused credentials. Use provision_all_keys() only during application startup to preload all configured providers, but avoid it in per-request contexts because environment variables persist across requests and could retain stale credentials after database deletion.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →