mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-14 02:22:54 +00:00
[Feat] Add CyberArk Secrets Manager Integration (#16278)
* KeyManagementSystem add cyberark * add CyberArkSecretManager * add CyberArkSecretManager * add CyberArkSecretManager * docs add CyberArkSecretManager * docs * refactor to use get_secret_from_manager * Potential fix for code scanning alert no. 3645: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 3650: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 3649: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 3646: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
parent
f19356db60
commit
9a372bfad6
+11
-355
@@ -1,8 +1,4 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Image from '@theme/IdealImage';
|
||||
|
||||
# Secret Manager
|
||||
# Secret Managers Overview
|
||||
|
||||
:::info
|
||||
|
||||
@@ -14,359 +10,19 @@ import Image from '@theme/IdealImage';
|
||||
|
||||
:::
|
||||
|
||||
LiteLLM supports **reading secrets (eg. `OPENAI_API_KEY`)** and **writing secrets (eg. Virtual Keys)** from Azure Key Vault, Google Secret Manager, Hashicorp Vault, and AWS Secret Manager.
|
||||
LiteLLM supports **reading secrets (eg. `OPENAI_API_KEY`)** and **writing secrets (eg. Virtual Keys)** from Azure Key Vault, Google Secret Manager, Hashicorp Vault, CyberArk Conjur, and AWS Secret Manager.
|
||||
|
||||
## Supported Secret Managers
|
||||
|
||||
- AWS Key Management Service
|
||||
- AWS Secret Manager
|
||||
- [Azure Key Vault](#azure-key-vault)
|
||||
- [Google Secret Manager](#google-secret-manager)
|
||||
- Google Key Management Service
|
||||
- [Hashicorp Vault](#hashicorp-vault)
|
||||
|
||||
## AWS Secret Manager
|
||||
|
||||
Store your proxy keys in AWS Secret Manager.
|
||||
|
||||
|
||||
| Feature | Support | Description |
|
||||
|---------|----------|-------------|
|
||||
| Reading Secrets | ✅ | Read secrets e.g `OPENAI_API_KEY` |
|
||||
| Writing Secrets | ✅ | Store secrets e.g `Virtual Keys` |
|
||||
|
||||
#### Proxy Usage
|
||||
|
||||
1. Save AWS Credentials in your environment
|
||||
```bash
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = "" # Access key
|
||||
os.environ["AWS_SECRET_ACCESS_KEY"] = "" # Secret access key
|
||||
os.environ["AWS_REGION_NAME"] = "" # us-east-1, us-east-2, us-west-1, us-west-2
|
||||
```
|
||||
|
||||
2. Enable AWS Secret Manager in config.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="read_only" label="Read Keys from AWS Secret Manager">
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
master_key: os.environ/litellm_master_key
|
||||
key_management_system: "aws_secret_manager" # 👈 KEY CHANGE
|
||||
key_management_settings:
|
||||
hosted_keys: ["litellm_master_key"] # 👈 Specify which env keys you stored on AWS
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="write_only" label="Write Virtual Keys to AWS Secret Manager">
|
||||
|
||||
This will only store virtual keys in AWS Secret Manager. No keys will be read from AWS Secret Manager.
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "aws_secret_manager" # 👈 KEY CHANGE
|
||||
key_management_settings:
|
||||
store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
|
||||
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
|
||||
access_mode: "write_only" # Literal["read_only", "write_only", "read_and_write"]
|
||||
description: "litellm virtual key" # OPTIONAL, if set will set this as the description for all virtual keys
|
||||
tags: # OPTIONAL, if set will set this as the tags for all virtual keys
|
||||
Environment: "Prod"
|
||||
Owner: "AI Platform team"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="read_and_write" label="Read + Write Keys with AWS Secret Manager">
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
master_key: os.environ/litellm_master_key
|
||||
key_management_system: "aws_secret_manager" # 👈 KEY CHANGE
|
||||
key_management_settings:
|
||||
store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
|
||||
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
|
||||
access_mode: "read_and_write" # Literal["read_only", "write_only", "read_and_write"]
|
||||
hosted_keys: ["litellm_master_key"] # OPTIONAL. Specify which env keys you stored on AWS
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
3. Run proxy
|
||||
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
|
||||
#### Using K/V pairs in 1 AWS Secret
|
||||
|
||||
You can read multiple keys from a single AWS Secret using the `primary_secret_name` parameter:
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "aws_secret_manager"
|
||||
key_management_settings:
|
||||
hosted_keys: [
|
||||
"OPENAI_API_KEY_MODEL_1",
|
||||
"OPENAI_API_KEY_MODEL_2",
|
||||
]
|
||||
primary_secret_name: "litellm_secrets" # 👈 Read multiple keys from one JSON secret
|
||||
```
|
||||
|
||||
The `primary_secret_name` allows you to read multiple keys from a single AWS Secret as a JSON object. For example, the "litellm_secrets" would contain:
|
||||
|
||||
```json
|
||||
{
|
||||
"OPENAI_API_KEY_MODEL_1": "sk-key1...",
|
||||
"OPENAI_API_KEY_MODEL_2": "sk-key2..."
|
||||
}
|
||||
```
|
||||
|
||||
This reduces the number of AWS Secrets you need to manage.
|
||||
|
||||
|
||||
## Hashicorp Vault
|
||||
|
||||
|
||||
| Feature | Support | Description |
|
||||
|---------|----------|-------------|
|
||||
| Reading Secrets | ✅ | Read secrets e.g `OPENAI_API_KEY` |
|
||||
| Writing Secrets | ✅ | Store secrets e.g `Virtual Keys` |
|
||||
|
||||
Read secrets from [Hashicorp Vault](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2)
|
||||
|
||||
**Step 1.** Add Hashicorp Vault details in your environment
|
||||
|
||||
LiteLLM supports two methods of authentication:
|
||||
|
||||
1. TLS cert authentication - `HCP_VAULT_CLIENT_CERT` and `HCP_VAULT_CLIENT_KEY`
|
||||
2. Token authentication - `HCP_VAULT_TOKEN`
|
||||
|
||||
```bash
|
||||
HCP_VAULT_ADDR="https://test-cluster-public-vault-0f98180c.e98296b2.z1.hashicorp.cloud:8200"
|
||||
HCP_VAULT_NAMESPACE="admin"
|
||||
|
||||
# Authentication via TLS cert
|
||||
HCP_VAULT_CLIENT_CERT="path/to/client.pem"
|
||||
HCP_VAULT_CLIENT_KEY="path/to/client.key"
|
||||
|
||||
# OR - Authentication via token
|
||||
HCP_VAULT_TOKEN="hvs.CAESIG52gL6ljBSdmq*****"
|
||||
|
||||
|
||||
# OPTIONAL
|
||||
HCP_VAULT_REFRESH_INTERVAL="86400" # defaults to 86400, frequency of cache refresh for Hashicorp Vault
|
||||
```
|
||||
|
||||
**Step 2.** Add to proxy config.yaml
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "hashicorp_vault"
|
||||
|
||||
# [OPTIONAL SETTINGS]
|
||||
key_management_settings:
|
||||
store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
|
||||
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
|
||||
access_mode: "read_and_write" # Literal["read_only", "write_only", "read_and_write"]
|
||||
```
|
||||
|
||||
**Step 3.** Start + test proxy
|
||||
|
||||
```
|
||||
$ litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
[Quick Test Proxy](./proxy/user_keys)
|
||||
|
||||
|
||||
#### How it works
|
||||
|
||||
**Reading Secrets**
|
||||
LiteLLM reads secrets from Hashicorp Vault's KV v2 engine using the following URL format:
|
||||
```
|
||||
{VAULT_ADDR}/v1/{NAMESPACE}/secret/data/{SECRET_NAME}
|
||||
```
|
||||
|
||||
For example, if you have:
|
||||
- `HCP_VAULT_ADDR="https://vault.example.com:8200"`
|
||||
- `HCP_VAULT_NAMESPACE="admin"`
|
||||
- Secret name: `AZURE_API_KEY`
|
||||
|
||||
|
||||
LiteLLM will look up:
|
||||
```
|
||||
https://vault.example.com:8200/v1/admin/secret/data/AZURE_API_KEY
|
||||
```
|
||||
|
||||
#### Expected Secret Format
|
||||
LiteLLM expects all secrets to be stored as a JSON object with a `key` field containing the secret value.
|
||||
|
||||
For example, for `AZURE_API_KEY`, the secret should be stored as:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "sk-1234"
|
||||
}
|
||||
```
|
||||
|
||||
<Image img={require('../img/hcorp.png')} />
|
||||
|
||||
**Writing Secrets**
|
||||
|
||||
When a Virtual Key is Created / Deleted on LiteLLM, LiteLLM will automatically create / delete the secret in Hashicorp Vault.
|
||||
|
||||
- Create Virtual Key on LiteLLM either through the LiteLLM Admin UI or API
|
||||
|
||||
<Image img={require('../img/hcorp_create_virtual_key.png')} />
|
||||
|
||||
|
||||
- Check Hashicorp Vault for secret
|
||||
|
||||
LiteLLM stores secret under the `prefix_for_stored_virtual_keys` path (default: `litellm/`)
|
||||
|
||||
<Image img={require('../img/hcorp_virtual_key.png')} />
|
||||
|
||||
|
||||
## Azure Key Vault
|
||||
|
||||
#### Usage with LiteLLM Proxy Server
|
||||
|
||||
1. Install Proxy dependencies
|
||||
```bash
|
||||
pip install 'litellm[proxy]' 'litellm[extra_proxy]'
|
||||
```
|
||||
|
||||
2. Save Azure details in your environment
|
||||
```bash
|
||||
export["AZURE_CLIENT_ID"]="your-azure-app-client-id"
|
||||
export["AZURE_CLIENT_SECRET"]="your-azure-app-client-secret"
|
||||
export["AZURE_TENANT_ID"]="your-azure-tenant-id"
|
||||
export["AZURE_KEY_VAULT_URI"]="your-azure-key-vault-uri"
|
||||
```
|
||||
|
||||
3. Add to proxy config.yaml
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: "my-azure-models" # model alias
|
||||
litellm_params:
|
||||
model: "azure/<your-deployment-name>"
|
||||
api_key: "os.environ/AZURE-API-KEY" # reads from key vault - get_secret("AZURE_API_KEY")
|
||||
api_base: "os.environ/AZURE-API-BASE" # reads from key vault - get_secret("AZURE_API_BASE")
|
||||
|
||||
general_settings:
|
||||
key_management_system: "azure_key_vault"
|
||||
```
|
||||
|
||||
You can now test this by starting your proxy:
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
[Quick Test Proxy](./proxy/quick_start#using-litellm-proxy---curl-request-openai-package-langchain-langchain-js)
|
||||
|
||||
## Google Secret Manager
|
||||
|
||||
Support for [Google Secret Manager](https://cloud.google.com/security/products/secret-manager)
|
||||
|
||||
|
||||
1. Save Google Secret Manager details in your environment
|
||||
|
||||
```shell
|
||||
GOOGLE_SECRET_MANAGER_PROJECT_ID="your-project-id-on-gcp" # example: adroit-crow-413218
|
||||
```
|
||||
|
||||
Optional Params
|
||||
|
||||
```shell
|
||||
export GOOGLE_SECRET_MANAGER_REFRESH_INTERVAL = "" # (int) defaults to 86400
|
||||
export GOOGLE_SECRET_MANAGER_ALWAYS_READ_SECRET_MANAGER = "" # (str) set to "true" if you want to always read from google secret manager without using in memory caching. NOT RECOMMENDED in PROD
|
||||
```
|
||||
|
||||
2. Add to proxy config.yaml
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: fake-openai-endpoint
|
||||
litellm_params:
|
||||
model: openai/fake
|
||||
api_base: https://exampleopenaiendpoint-production.up.railway.app/
|
||||
api_key: os.environ/OPENAI_API_KEY # this will be read from Google Secret Manager
|
||||
|
||||
general_settings:
|
||||
key_management_system: "google_secret_manager"
|
||||
```
|
||||
|
||||
You can now test this by starting your proxy:
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
[Quick Test Proxy](./proxy/quick_start#using-litellm-proxy---curl-request-openai-package-langchain-langchain-js)
|
||||
|
||||
|
||||
## Google Key Management Service
|
||||
|
||||
Use encrypted keys from Google KMS on the proxy
|
||||
|
||||
Step 1. Add keys to env
|
||||
```
|
||||
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/credentials.json"
|
||||
export GOOGLE_KMS_RESOURCE_NAME="projects/*/locations/*/keyRings/*/cryptoKeys/*"
|
||||
export PROXY_DATABASE_URL_ENCRYPTED=b'\n$\x00D\xac\xb4/\x8e\xc...'
|
||||
```
|
||||
|
||||
Step 2: Update Config
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "google_kms"
|
||||
database_url: "os.environ/PROXY_DATABASE_URL_ENCRYPTED"
|
||||
master_key: sk-1234
|
||||
```
|
||||
|
||||
Step 3: Start + test proxy
|
||||
|
||||
```
|
||||
$ litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
And in another terminal
|
||||
```
|
||||
$ litellm --test
|
||||
```
|
||||
|
||||
[Quick Test Proxy](./proxy/user_keys)
|
||||
<!--
|
||||
## .env Files
|
||||
If no secret manager client is specified, Litellm automatically uses the `.env` file to manage sensitive data. -->
|
||||
|
||||
## AWS Key Management V1
|
||||
|
||||
:::tip
|
||||
|
||||
[BETA] AWS Key Management v2 is on the enterprise tier. Go [here for docs](./proxy/enterprise.md#beta-aws-key-manager---key-decryption)
|
||||
|
||||
:::
|
||||
|
||||
Use AWS KMS to storing a hashed copy of your Proxy Master Key in the environment.
|
||||
|
||||
```bash
|
||||
export LITELLM_MASTER_KEY="djZ9xjVaZ..." # 👈 ENCRYPTED KEY
|
||||
export AWS_REGION_NAME="us-west-2"
|
||||
```
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "aws_kms"
|
||||
key_management_settings:
|
||||
hosted_keys: ["LITELLM_MASTER_KEY"] # 👈 WHICH KEYS ARE STORED ON KMS
|
||||
```
|
||||
|
||||
[**See Decryption Code**](https://github.com/BerriAI/litellm/blob/a2da2a8f168d45648b61279d4795d647d94f90c9/litellm/utils.py#L10182)
|
||||
|
||||
## **All Secret Manager Settings**
|
||||
- [AWS Key Management Service](./secret_managers/aws_kms)
|
||||
- [AWS Secret Manager](./secret_managers/aws_secret_manager)
|
||||
- [Azure Key Vault](./secret_managers/azure_key_vault)
|
||||
- [CyberArk Conjur](./secret_managers/cyberark)
|
||||
- [Google Secret Manager](./secret_managers/google_secret_manager)
|
||||
- [Google Key Management Service](./secret_managers/google_kms)
|
||||
- [Hashicorp Vault](./secret_managers/hashicorp_vault)
|
||||
|
||||
## All Secret Manager Settings
|
||||
|
||||
All settings related to secret management
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# AWS Key Management V1
|
||||
|
||||
:::info
|
||||
|
||||
✨ **This is an Enterprise Feature**
|
||||
|
||||
[Enterprise Pricing](https://www.litellm.ai/#pricing)
|
||||
|
||||
[Contact us here to get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
|
||||
|
||||
:::
|
||||
|
||||
:::tip
|
||||
|
||||
[BETA] AWS Key Management v2 is on the enterprise tier. Go [here for docs](../proxy/enterprise.md#beta-aws-key-manager---key-decryption)
|
||||
|
||||
:::
|
||||
|
||||
Use AWS KMS to storing a hashed copy of your Proxy Master Key in the environment.
|
||||
|
||||
```bash
|
||||
export LITELLM_MASTER_KEY="djZ9xjVaZ..." # 👈 ENCRYPTED KEY
|
||||
export AWS_REGION_NAME="us-west-2"
|
||||
```
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "aws_kms"
|
||||
key_management_settings:
|
||||
hosted_keys: ["LITELLM_MASTER_KEY"] # 👈 WHICH KEYS ARE STORED ON KMS
|
||||
```
|
||||
|
||||
[**See Decryption Code**](https://github.com/BerriAI/litellm/blob/a2da2a8f168d45648b61279d4795d647d94f90c9/litellm/utils.py#L10182)
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# AWS Secret Manager
|
||||
|
||||
:::info
|
||||
|
||||
✨ **This is an Enterprise Feature**
|
||||
|
||||
[Enterprise Pricing](https://www.litellm.ai/#pricing)
|
||||
|
||||
[Contact us here to get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
|
||||
|
||||
:::
|
||||
|
||||
Store your proxy keys in AWS Secret Manager.
|
||||
|
||||
| Feature | Support | Description |
|
||||
|---------|----------|-------------|
|
||||
| Reading Secrets | ✅ | Read secrets e.g `OPENAI_API_KEY` |
|
||||
| Writing Secrets | ✅ | Store secrets e.g `Virtual Keys` |
|
||||
|
||||
## Proxy Usage
|
||||
|
||||
1. Save AWS Credentials in your environment
|
||||
```bash
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = "" # Access key
|
||||
os.environ["AWS_SECRET_ACCESS_KEY"] = "" # Secret access key
|
||||
os.environ["AWS_REGION_NAME"] = "" # us-east-1, us-east-2, us-west-1, us-west-2
|
||||
```
|
||||
|
||||
2. Enable AWS Secret Manager in config.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="read_only" label="Read Keys from AWS Secret Manager">
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
master_key: os.environ/litellm_master_key
|
||||
key_management_system: "aws_secret_manager" # 👈 KEY CHANGE
|
||||
key_management_settings:
|
||||
hosted_keys: ["litellm_master_key"] # 👈 Specify which env keys you stored on AWS
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="write_only" label="Write Virtual Keys to AWS Secret Manager">
|
||||
|
||||
This will only store virtual keys in AWS Secret Manager. No keys will be read from AWS Secret Manager.
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "aws_secret_manager" # 👈 KEY CHANGE
|
||||
key_management_settings:
|
||||
store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
|
||||
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
|
||||
access_mode: "write_only" # Literal["read_only", "write_only", "read_and_write"]
|
||||
description: "litellm virtual key" # OPTIONAL, if set will set this as the description for all virtual keys
|
||||
tags: # OPTIONAL, if set will set this as the tags for all virtual keys
|
||||
Environment: "Prod"
|
||||
Owner: "AI Platform team"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="read_and_write" label="Read + Write Keys with AWS Secret Manager">
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
master_key: os.environ/litellm_master_key
|
||||
key_management_system: "aws_secret_manager" # 👈 KEY CHANGE
|
||||
key_management_settings:
|
||||
store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
|
||||
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
|
||||
access_mode: "read_and_write" # Literal["read_only", "write_only", "read_and_write"]
|
||||
hosted_keys: ["litellm_master_key"] # OPTIONAL. Specify which env keys you stored on AWS
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
3. Run proxy
|
||||
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
## Using K/V pairs in 1 AWS Secret
|
||||
|
||||
You can read multiple keys from a single AWS Secret using the `primary_secret_name` parameter:
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "aws_secret_manager"
|
||||
key_management_settings:
|
||||
hosted_keys: [
|
||||
"OPENAI_API_KEY_MODEL_1",
|
||||
"OPENAI_API_KEY_MODEL_2",
|
||||
]
|
||||
primary_secret_name: "litellm_secrets" # 👈 Read multiple keys from one JSON secret
|
||||
```
|
||||
|
||||
The `primary_secret_name` allows you to read multiple keys from a single AWS Secret as a JSON object. For example, the "litellm_secrets" would contain:
|
||||
|
||||
```json
|
||||
{
|
||||
"OPENAI_API_KEY_MODEL_1": "sk-key1...",
|
||||
"OPENAI_API_KEY_MODEL_2": "sk-key2..."
|
||||
}
|
||||
```
|
||||
|
||||
This reduces the number of AWS Secrets you need to manage.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Azure Key Vault
|
||||
|
||||
:::info
|
||||
|
||||
✨ **This is an Enterprise Feature**
|
||||
|
||||
[Enterprise Pricing](https://www.litellm.ai/#pricing)
|
||||
|
||||
[Contact us here to get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
|
||||
|
||||
:::
|
||||
|
||||
## Usage with LiteLLM Proxy Server
|
||||
|
||||
1. Install Proxy dependencies
|
||||
```bash
|
||||
pip install 'litellm[proxy]' 'litellm[extra_proxy]'
|
||||
```
|
||||
|
||||
2. Save Azure details in your environment
|
||||
```bash
|
||||
export["AZURE_CLIENT_ID"]="your-azure-app-client-id"
|
||||
export["AZURE_CLIENT_SECRET"]="your-azure-app-client-secret"
|
||||
export["AZURE_TENANT_ID"]="your-azure-tenant-id"
|
||||
export["AZURE_KEY_VAULT_URI"]="your-azure-key-vault-uri"
|
||||
```
|
||||
|
||||
3. Add to proxy config.yaml
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: "my-azure-models" # model alias
|
||||
litellm_params:
|
||||
model: "azure/<your-deployment-name>"
|
||||
api_key: "os.environ/AZURE-API-KEY" # reads from key vault - get_secret("AZURE_API_KEY")
|
||||
api_base: "os.environ/AZURE-API-BASE" # reads from key vault - get_secret("AZURE_API_BASE")
|
||||
|
||||
general_settings:
|
||||
key_management_system: "azure_key_vault"
|
||||
```
|
||||
|
||||
You can now test this by starting your proxy:
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
[Quick Test Proxy](../proxy/quick_start#using-litellm-proxy---curl-request-openai-package-langchain-langchain-js)
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
# CyberArk Conjur
|
||||
|
||||
import Image from '@theme/IdealImage';
|
||||
|
||||
:::info
|
||||
|
||||
✨ **This is an Enterprise Feature**
|
||||
|
||||
[Enterprise Pricing](https://www.litellm.ai/#pricing)
|
||||
|
||||
[Contact us here to get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
|
||||
|
||||
:::
|
||||
|
||||
| Feature | Support | Description |
|
||||
|---------|----------|-------------|
|
||||
| Reading Secrets | ✅ | Read secrets e.g `OPENAI_API_KEY` |
|
||||
| Writing Secrets | ✅ | Store secrets e.g `Virtual Keys` |
|
||||
| Deleting Secrets | ❌ | Secrets must be removed via policy updates |
|
||||
|
||||
Read and write secrets from [CyberArk Conjur](https://www.cyberark.com/products/secrets-management/) (self-hosted secrets manager)
|
||||
|
||||
**Step 1.** Add CyberArk Conjur details in your environment
|
||||
|
||||
LiteLLM supports two methods of authentication:
|
||||
|
||||
1. API key authentication - `CYBERARK_API_KEY` (recommended)
|
||||
2. Certificate authentication - `CYBERARK_CLIENT_CERT` and `CYBERARK_CLIENT_KEY`
|
||||
|
||||
```bash
|
||||
CYBERARK_API_BASE="http://your-conjur-instance:8080"
|
||||
CYBERARK_ACCOUNT="default"
|
||||
CYBERARK_USERNAME="admin"
|
||||
|
||||
# Authentication via API key (recommended)
|
||||
CYBERARK_API_KEY="your-api-key-here"
|
||||
|
||||
# OR - Authentication via certificate
|
||||
CYBERARK_CLIENT_CERT="path/to/client.pem"
|
||||
CYBERARK_CLIENT_KEY="path/to/client.key"
|
||||
|
||||
# OPTIONAL
|
||||
CYBERARK_REFRESH_INTERVAL="300" # defaults to 300 seconds (5 minutes), frequency of token refresh
|
||||
```
|
||||
|
||||
**Step 2.** Add to proxy config.yaml
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "cyberark"
|
||||
|
||||
# [OPTIONAL SETTINGS]
|
||||
key_management_settings:
|
||||
store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
|
||||
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
|
||||
access_mode: "read_and_write" # Literal["read_only", "write_only", "read_and_write"]
|
||||
```
|
||||
|
||||
**Step 3.** Start + test proxy
|
||||
|
||||
```bash
|
||||
$ litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
[Quick Test Proxy](../proxy/user_keys)
|
||||
|
||||
## Writing Virtual Keys to CyberArk
|
||||
|
||||
When you create a virtual key in the LiteLLM UI, it automatically gets stored in CyberArk Conjur.
|
||||
|
||||
**Step 1:** Create a virtual key in the LiteLLM Admin UI
|
||||
|
||||
In this example, we create a key named `litellm-cyber-ark-secret-key`:
|
||||
|
||||
<Image img={require('../../static/img/cyberark1.png')} alt="Creating virtual key in LiteLLM UI" />
|
||||
|
||||
**Step 2:** Verify the secret exists in CyberArk
|
||||
|
||||
You can verify the virtual key was stored in CyberArk by querying the secrets API:
|
||||
|
||||
```bash
|
||||
TOKEN=$(curl -s -X POST http://0.0.0.0:8080/authn/default/admin/authenticate \
|
||||
-d "your-api-key" | base64 | tr -d '\n')
|
||||
|
||||
curl -H "Authorization: Token token=\"$TOKEN\"" \
|
||||
"http://0.0.0.0:8080/resources/default/variable" | jq .
|
||||
```
|
||||
|
||||
The response shows `litellm-cyber-ark-secret-key` exists in CyberArk:
|
||||
|
||||
<Image img={require('../../static/img/cyberark2.png')} alt="Virtual key stored in CyberArk API" />
|
||||
|
||||
The virtual key is stored with the full path: `default:variable:litellm/litellm-cyber-ark-secret-key`
|
||||
|
||||
## How it works
|
||||
|
||||
**Authentication**
|
||||
|
||||
CyberArk Conjur uses a two-step authentication process:
|
||||
|
||||
1. LiteLLM authenticates with your API key to get a session token
|
||||
2. The session token (base64-encoded) is used for subsequent API requests
|
||||
3. Tokens expire after ~8 minutes, so LiteLLM caches and refreshes them automatically
|
||||
|
||||
**Reading Secrets**
|
||||
|
||||
LiteLLM reads secrets from CyberArk Conjur using the following URL format:
|
||||
|
||||
```
|
||||
{CYBERARK_API_BASE}/secrets/{ACCOUNT}/variable/{SECRET_NAME}
|
||||
```
|
||||
|
||||
For example, if you have:
|
||||
- `CYBERARK_API_BASE="http://conjur.example.com:8080"`
|
||||
- `CYBERARK_ACCOUNT="default"`
|
||||
- Secret name: `AZURE_API_KEY`
|
||||
|
||||
LiteLLM will look up:
|
||||
```
|
||||
http://conjur.example.com:8080/secrets/default/variable/AZURE_API_KEY
|
||||
```
|
||||
|
||||
**Writing Secrets**
|
||||
|
||||
When a Virtual Key is created on LiteLLM, the following happens automatically:
|
||||
|
||||
1. LiteLLM creates a policy entry to define the variable in Conjur (if it doesn't exist)
|
||||
2. LiteLLM sets the secret value via the Conjur API
|
||||
|
||||
LiteLLM stores secrets under the `prefix_for_stored_virtual_keys` path (default: `litellm/`)
|
||||
|
||||
For example, a virtual key would be stored as: `litellm/virtual-key-name`
|
||||
|
||||
**Working curl examples**
|
||||
|
||||
Authenticate and get a token:
|
||||
```bash
|
||||
TOKEN=$(curl -s -X POST http://conjur.example.com:8080/authn/default/admin/authenticate \
|
||||
-d "your-api-key" | base64 | tr -d '\n')
|
||||
```
|
||||
|
||||
Read a secret:
|
||||
```bash
|
||||
curl -H "Authorization: Token token=\"$TOKEN\"" \
|
||||
"http://conjur.example.com:8080/secrets/default/variable/test-secret"
|
||||
```
|
||||
|
||||
Write a secret:
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Token token=\"$TOKEN\"" \
|
||||
--data "my-secret-value" \
|
||||
"http://conjur.example.com:8080/secrets/default/variable/test-secret"
|
||||
```
|
||||
|
||||
**Important Notes**
|
||||
|
||||
- Variables must be defined in a Conjur policy before setting their values
|
||||
- LiteLLM automatically creates policy entries when writing new secrets
|
||||
- Secret names with slashes (e.g., `litellm/key`) are automatically URL-encoded
|
||||
- CyberArk Conjur does not support direct secret deletion via API (must use policy updates)
|
||||
- Session tokens are cached for 5 minutes by default to minimize API calls
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Google Key Management Service
|
||||
|
||||
:::info
|
||||
|
||||
✨ **This is an Enterprise Feature**
|
||||
|
||||
[Enterprise Pricing](https://www.litellm.ai/#pricing)
|
||||
|
||||
[Contact us here to get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
|
||||
|
||||
:::
|
||||
|
||||
Use encrypted keys from Google KMS on the proxy
|
||||
|
||||
Step 1. Add keys to env
|
||||
```
|
||||
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/credentials.json"
|
||||
export GOOGLE_KMS_RESOURCE_NAME="projects/*/locations/*/keyRings/*/cryptoKeys/*"
|
||||
export PROXY_DATABASE_URL_ENCRYPTED=b'\n$\x00D\xac\xb4/\x8e\xc...'
|
||||
```
|
||||
|
||||
Step 2: Update Config
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "google_kms"
|
||||
database_url: "os.environ/PROXY_DATABASE_URL_ENCRYPTED"
|
||||
master_key: sk-1234
|
||||
```
|
||||
|
||||
Step 3: Start + test proxy
|
||||
|
||||
```
|
||||
$ litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
And in another terminal
|
||||
```
|
||||
$ litellm --test
|
||||
```
|
||||
|
||||
[Quick Test Proxy](../proxy/user_keys)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Google Secret Manager
|
||||
|
||||
:::info
|
||||
|
||||
✨ **This is an Enterprise Feature**
|
||||
|
||||
[Enterprise Pricing](https://www.litellm.ai/#pricing)
|
||||
|
||||
[Contact us here to get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
|
||||
|
||||
:::
|
||||
|
||||
Support for [Google Secret Manager](https://cloud.google.com/security/products/secret-manager)
|
||||
|
||||
1. Save Google Secret Manager details in your environment
|
||||
|
||||
```shell
|
||||
GOOGLE_SECRET_MANAGER_PROJECT_ID="your-project-id-on-gcp" # example: adroit-crow-413218
|
||||
```
|
||||
|
||||
Optional Params
|
||||
|
||||
```shell
|
||||
export GOOGLE_SECRET_MANAGER_REFRESH_INTERVAL = "" # (int) defaults to 86400
|
||||
export GOOGLE_SECRET_MANAGER_ALWAYS_READ_SECRET_MANAGER = "" # (str) set to "true" if you want to always read from google secret manager without using in memory caching. NOT RECOMMENDED in PROD
|
||||
```
|
||||
|
||||
2. Add to proxy config.yaml
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: fake-openai-endpoint
|
||||
litellm_params:
|
||||
model: openai/fake
|
||||
api_base: https://exampleopenaiendpoint-production.up.railway.app/
|
||||
api_key: os.environ/OPENAI_API_KEY # this will be read from Google Secret Manager
|
||||
|
||||
general_settings:
|
||||
key_management_system: "google_secret_manager"
|
||||
```
|
||||
|
||||
You can now test this by starting your proxy:
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
[Quick Test Proxy](../proxy/quick_start#using-litellm-proxy---curl-request-openai-package-langchain-langchain-js)
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import Image from '@theme/IdealImage';
|
||||
|
||||
# Hashicorp Vault
|
||||
|
||||
:::info
|
||||
|
||||
✨ **This is an Enterprise Feature**
|
||||
|
||||
[Enterprise Pricing](https://www.litellm.ai/#pricing)
|
||||
|
||||
[Contact us here to get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
|
||||
|
||||
:::
|
||||
|
||||
| Feature | Support | Description |
|
||||
|---------|----------|-------------|
|
||||
| Reading Secrets | ✅ | Read secrets e.g `OPENAI_API_KEY` |
|
||||
| Writing Secrets | ✅ | Store secrets e.g `Virtual Keys` |
|
||||
|
||||
Read secrets from [Hashicorp Vault](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2)
|
||||
|
||||
**Step 1.** Add Hashicorp Vault details in your environment
|
||||
|
||||
LiteLLM supports two methods of authentication:
|
||||
|
||||
1. TLS cert authentication - `HCP_VAULT_CLIENT_CERT` and `HCP_VAULT_CLIENT_KEY`
|
||||
2. Token authentication - `HCP_VAULT_TOKEN`
|
||||
|
||||
```bash
|
||||
HCP_VAULT_ADDR="https://test-cluster-public-vault-0f98180c.e98296b2.z1.hashicorp.cloud:8200"
|
||||
HCP_VAULT_NAMESPACE="admin"
|
||||
|
||||
# Authentication via TLS cert
|
||||
HCP_VAULT_CLIENT_CERT="path/to/client.pem"
|
||||
HCP_VAULT_CLIENT_KEY="path/to/client.key"
|
||||
|
||||
# OR - Authentication via token
|
||||
HCP_VAULT_TOKEN="hvs.CAESIG52gL6ljBSdmq*****"
|
||||
|
||||
|
||||
# OPTIONAL
|
||||
HCP_VAULT_REFRESH_INTERVAL="86400" # defaults to 86400, frequency of cache refresh for Hashicorp Vault
|
||||
```
|
||||
|
||||
**Step 2.** Add to proxy config.yaml
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "hashicorp_vault"
|
||||
|
||||
# [OPTIONAL SETTINGS]
|
||||
key_management_settings:
|
||||
store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
|
||||
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
|
||||
access_mode: "read_and_write" # Literal["read_only", "write_only", "read_and_write"]
|
||||
```
|
||||
|
||||
**Step 3.** Start + test proxy
|
||||
|
||||
```
|
||||
$ litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
[Quick Test Proxy](../proxy/user_keys)
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
**Reading Secrets**
|
||||
|
||||
LiteLLM reads secrets from Hashicorp Vault's KV v2 engine using the following URL format:
|
||||
```
|
||||
{VAULT_ADDR}/v1/{NAMESPACE}/secret/data/{SECRET_NAME}
|
||||
```
|
||||
|
||||
For example, if you have:
|
||||
- `HCP_VAULT_ADDR="https://vault.example.com:8200"`
|
||||
- `HCP_VAULT_NAMESPACE="admin"`
|
||||
- Secret name: `AZURE_API_KEY`
|
||||
|
||||
|
||||
LiteLLM will look up:
|
||||
```
|
||||
https://vault.example.com:8200/v1/admin/secret/data/AZURE_API_KEY
|
||||
```
|
||||
|
||||
### Expected Secret Format
|
||||
|
||||
LiteLLM expects all secrets to be stored as a JSON object with a `key` field containing the secret value.
|
||||
|
||||
For example, for `AZURE_API_KEY`, the secret should be stored as:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "sk-1234"
|
||||
}
|
||||
```
|
||||
|
||||
<Image img={require('../../img/hcorp.png')} />
|
||||
|
||||
**Writing Secrets**
|
||||
|
||||
When a Virtual Key is Created / Deleted on LiteLLM, LiteLLM will automatically create / delete the secret in Hashicorp Vault.
|
||||
|
||||
- Create Virtual Key on LiteLLM either through the LiteLLM Admin UI or API
|
||||
|
||||
<Image img={require('../../img/hcorp_create_virtual_key.png')} />
|
||||
|
||||
|
||||
- Check Hashicorp Vault for secret
|
||||
|
||||
LiteLLM stores secret under the `prefix_for_stored_virtual_keys` path (default: `litellm/`)
|
||||
|
||||
<Image img={require('../../img/hcorp_virtual_key.png')} />
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Secret Managers Overview
|
||||
|
||||
:::info
|
||||
|
||||
✨ **This is an Enterprise Feature**
|
||||
|
||||
[Enterprise Pricing](https://www.litellm.ai/#pricing)
|
||||
|
||||
[Contact us here to get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
|
||||
|
||||
:::
|
||||
|
||||
LiteLLM supports **reading secrets (eg. `OPENAI_API_KEY`)** and **writing secrets (eg. Virtual Keys)** from Azure Key Vault, Google Secret Manager, Hashicorp Vault, CyberArk Conjur, and AWS Secret Manager.
|
||||
|
||||
## Supported Secret Managers
|
||||
|
||||
- [AWS Key Management Service](./aws_kms)
|
||||
- [AWS Secret Manager](./aws_secret_manager)
|
||||
- [Azure Key Vault](./azure_key_vault)
|
||||
- [CyberArk Conjur](./cyberark)
|
||||
- [Google Secret Manager](./google_secret_manager)
|
||||
- [Google Key Management Service](./google_kms)
|
||||
- [Hashicorp Vault](./hashicorp_vault)
|
||||
|
||||
## All Secret Manager Settings
|
||||
|
||||
All settings related to secret management
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
key_management_system: "aws_secret_manager" # REQUIRED
|
||||
key_management_settings:
|
||||
|
||||
# Storing Virtual Keys Settings
|
||||
store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
|
||||
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL.I f set, this prefix will be used for stored virtual keys in the secret manager
|
||||
|
||||
# Access Mode Settings
|
||||
access_mode: "write_only" # OPTIONAL. Literal["read_only", "write_only", "read_and_write"]. Defaults to "read_only"
|
||||
|
||||
# Hosted Keys Settings
|
||||
hosted_keys: ["litellm_master_key"] # OPTIONAL. Specify which env keys you stored on AWS
|
||||
|
||||
# K/V pairs in 1 AWS Secret Settings
|
||||
primary_secret_name: "litellm_secrets" # OPTIONAL. Read multiple keys from one JSON secret on AWS Secret Manager
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 605 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 980 KiB |
@@ -260,7 +260,14 @@ const sidebars = {
|
||||
type: "category",
|
||||
label: "Secret Managers",
|
||||
items: [
|
||||
"secret",
|
||||
"secret_managers/overview",
|
||||
"secret_managers/aws_secret_manager",
|
||||
"secret_managers/aws_kms",
|
||||
"secret_managers/azure_key_vault",
|
||||
"secret_managers/cyberark",
|
||||
"secret_managers/google_secret_manager",
|
||||
"secret_managers/google_kms",
|
||||
"secret_managers/hashicorp_vault",
|
||||
"oidc"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
general_settings:
|
||||
key_management_system: "cyberark"
|
||||
key_management_settings:
|
||||
store_virtual_keys: true
|
||||
prefix_for_stored_virtual_keys: "litellm/"
|
||||
access_mode: "read_and_write"
|
||||
|
||||
model_list:
|
||||
- model_name: aws/anthropic/bedrock-claude-3-5-sonnet-v1
|
||||
litellm_params:
|
||||
|
||||
@@ -2658,6 +2658,12 @@ class ProxyConfig:
|
||||
)
|
||||
|
||||
HashicorpSecretManager()
|
||||
elif key_management_system == KeyManagementSystem.CYBERARK.value:
|
||||
from litellm.secret_managers.cyberark_secret_manager import (
|
||||
CyberArkSecretManager,
|
||||
)
|
||||
|
||||
CyberArkSecretManager()
|
||||
else:
|
||||
raise ValueError("Invalid Key Management System selected")
|
||||
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
import base64
|
||||
import os
|
||||
from typing import Any, Dict, Optional, Union
|
||||
from urllib.parse import quote
|
||||
|
||||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.caching import InMemoryCache
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
_get_httpx_client,
|
||||
get_async_httpx_client,
|
||||
httpxSpecialProvider,
|
||||
)
|
||||
from litellm.proxy._types import KeyManagementSystem
|
||||
|
||||
from .base_secret_manager import BaseSecretManager
|
||||
|
||||
|
||||
class CyberArkSecretManager(BaseSecretManager):
|
||||
def __init__(self):
|
||||
from litellm.proxy.proxy_server import CommonProxyErrors, premium_user
|
||||
|
||||
# CyberArk Conjur-specific config
|
||||
self.conjur_addr = os.getenv("CYBERARK_API_BASE", "http://127.0.0.1:8080")
|
||||
self.conjur_account = os.getenv("CYBERARK_ACCOUNT", "default")
|
||||
self.conjur_username = os.getenv("CYBERARK_USERNAME", "admin")
|
||||
self.conjur_api_key = os.getenv("CYBERARK_API_KEY", "")
|
||||
|
||||
# Optional config for certificate-based auth
|
||||
self.tls_cert_path = os.getenv("CYBERARK_CLIENT_CERT", "")
|
||||
self.tls_key_path = os.getenv("CYBERARK_CLIENT_KEY", "")
|
||||
|
||||
# Validate environment
|
||||
if not self.conjur_api_key and not (
|
||||
self.tls_cert_path and self.tls_key_path
|
||||
):
|
||||
raise ValueError(
|
||||
"Missing CyberArk credentials. Please set CYBERARK_API_KEY or both CYBERARK_CLIENT_CERT and CYBERARK_CLIENT_KEY in your environment."
|
||||
)
|
||||
|
||||
litellm.secret_manager_client = self
|
||||
litellm._key_management_system = KeyManagementSystem.CYBERARK
|
||||
|
||||
# Tokens expire after ~8 minutes, so we cache for 5 minutes to be safe
|
||||
_refresh_interval = int(os.environ.get("CYBERARK_REFRESH_INTERVAL", "300"))
|
||||
self.cache = InMemoryCache(default_ttl=_refresh_interval)
|
||||
|
||||
if premium_user is not True:
|
||||
raise ValueError(
|
||||
f"CyberArk secret manager is only available for premium users. {CommonProxyErrors.not_premium_user.value}"
|
||||
)
|
||||
|
||||
def _authenticate(self) -> str:
|
||||
"""
|
||||
Authenticate with CyberArk Conjur and get a session token.
|
||||
|
||||
The token is a JSON object that must be base64-encoded for use in subsequent requests.
|
||||
|
||||
Returns:
|
||||
str: Base64-encoded session token
|
||||
"""
|
||||
# Check if we have a cached token
|
||||
cached_token = self.cache.get_cache("cyberark_auth_token")
|
||||
if cached_token is not None:
|
||||
return cached_token
|
||||
|
||||
verbose_logger.debug("Authenticating with CyberArk Conjur...")
|
||||
auth_url = f"{self.conjur_addr}/authn/{self.conjur_account}/{self.conjur_username}/authenticate"
|
||||
|
||||
try:
|
||||
if self.tls_cert_path and self.tls_key_path:
|
||||
# Certificate-based authentication
|
||||
client = httpx.Client(cert=(self.tls_cert_path, self.tls_key_path))
|
||||
resp = client.post(auth_url, content=self.conjur_api_key)
|
||||
else:
|
||||
# API key authentication
|
||||
client = _get_httpx_client()
|
||||
resp = client.post(auth_url, content=self.conjur_api_key)
|
||||
|
||||
resp.raise_for_status()
|
||||
|
||||
# The response is a JSON token that needs to be base64-encoded
|
||||
token_json = resp.text
|
||||
token_b64 = base64.b64encode(token_json.encode()).decode()
|
||||
|
||||
verbose_logger.debug("Successfully authenticated with CyberArk Conjur.")
|
||||
|
||||
# Cache the token for the refresh interval
|
||||
self.cache.set_cache(key="cyberark_auth_token", value=token_b64)
|
||||
|
||||
return token_b64
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Could not authenticate to CyberArk Conjur: {e}")
|
||||
|
||||
def _get_request_headers(self) -> dict:
|
||||
"""
|
||||
Get headers for CyberArk API requests including authentication.
|
||||
|
||||
Returns:
|
||||
dict: Headers with authentication token
|
||||
"""
|
||||
token = self._authenticate()
|
||||
return {"Authorization": f'Token token="{token}"'}
|
||||
|
||||
def _ensure_variable_exists(self, secret_name: str) -> None:
|
||||
"""
|
||||
Ensure a variable exists in CyberArk Conjur by creating a policy entry if needed.
|
||||
|
||||
Args:
|
||||
secret_name: Name of the variable to ensure exists
|
||||
"""
|
||||
# In production, we'd check if the variable exists first
|
||||
# For now, we'll attempt to create it and ignore if it already exists
|
||||
policy_url = f"{self.conjur_addr}/policies/{self.conjur_account}/policy/root"
|
||||
policy_yaml = f"- !variable {secret_name}\n"
|
||||
|
||||
try:
|
||||
client = _get_httpx_client()
|
||||
resp = client.post(
|
||||
policy_url,
|
||||
headers={
|
||||
**self._get_request_headers(),
|
||||
"Content-Type": "application/x-yaml",
|
||||
},
|
||||
content=policy_yaml,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
verbose_logger.debug("Created policy entry for a variable.")
|
||||
except httpx.HTTPStatusError as e:
|
||||
# Variable might already exist, which is fine
|
||||
if e.response.status_code in [409, 422]:
|
||||
verbose_logger.debug(
|
||||
"A variable already exists or policy conflict (expected)"
|
||||
)
|
||||
else:
|
||||
verbose_logger.warning(
|
||||
f"Could not ensure variable exists: {e.response.status_code} - {e.response.text}"
|
||||
)
|
||||
except Exception as e:
|
||||
verbose_logger.warning(f"Error ensuring variable exists: {e}")
|
||||
|
||||
def get_url(self, secret_name: str) -> str:
|
||||
"""
|
||||
Build the URL for accessing a secret in CyberArk Conjur.
|
||||
|
||||
Args:
|
||||
secret_name: Name of the secret (will be URL-encoded)
|
||||
|
||||
Returns:
|
||||
str: Full URL for the secret
|
||||
"""
|
||||
# URL-encode the secret name to handle slashes and special characters
|
||||
encoded_name = quote(secret_name, safe="")
|
||||
return (
|
||||
f"{self.conjur_addr}/secrets/{self.conjur_account}/variable/{encoded_name}"
|
||||
)
|
||||
|
||||
async def async_read_secret(
|
||||
self,
|
||||
secret_name: str,
|
||||
optional_params: Optional[dict] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Reads a secret from CyberArk Conjur using an async HTTPX client.
|
||||
|
||||
Args:
|
||||
secret_name: Name/path of the secret to read
|
||||
optional_params: Additional parameters (not used for Conjur)
|
||||
timeout: Request timeout
|
||||
|
||||
Returns:
|
||||
Optional[str]: The secret value if found, None otherwise
|
||||
"""
|
||||
# Check cache first
|
||||
if self.cache.get_cache(secret_name) is not None:
|
||||
return self.cache.get_cache(secret_name)
|
||||
|
||||
async_client = get_async_httpx_client(
|
||||
llm_provider=httpxSpecialProvider.SecretManager,
|
||||
)
|
||||
|
||||
try:
|
||||
url = self.get_url(secret_name)
|
||||
response = await async_client.get(url, headers=self._get_request_headers())
|
||||
response.raise_for_status()
|
||||
|
||||
# CyberArk Conjur returns the raw secret value as text
|
||||
secret_value = response.text
|
||||
self.cache.set_cache(secret_name, secret_value)
|
||||
return secret_value
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
verbose_logger.debug(
|
||||
f"Secret {secret_name} not found in CyberArk Conjur"
|
||||
)
|
||||
else:
|
||||
verbose_logger.exception(
|
||||
f"Error reading secret from CyberArk Conjur: {e}"
|
||||
)
|
||||
return None
|
||||
except Exception as e:
|
||||
verbose_logger.exception(f"Error reading secret from CyberArk Conjur: {e}")
|
||||
return None
|
||||
|
||||
def sync_read_secret(
|
||||
self,
|
||||
secret_name: str,
|
||||
optional_params: Optional[dict] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Reads a secret from CyberArk Conjur using a sync HTTPX client.
|
||||
|
||||
Args:
|
||||
secret_name: Name/path of the secret to read
|
||||
optional_params: Additional parameters (not used for Conjur)
|
||||
timeout: Request timeout
|
||||
|
||||
Returns:
|
||||
Optional[str]: The secret value if found, None otherwise
|
||||
"""
|
||||
# Check cache first
|
||||
if self.cache.get_cache(secret_name) is not None:
|
||||
return self.cache.get_cache(secret_name)
|
||||
|
||||
sync_client = _get_httpx_client()
|
||||
|
||||
try:
|
||||
url = self.get_url(secret_name)
|
||||
response = sync_client.get(url, headers=self._get_request_headers())
|
||||
response.raise_for_status()
|
||||
|
||||
# CyberArk Conjur returns the raw secret value as text
|
||||
secret_value = response.text
|
||||
self.cache.set_cache(secret_name, secret_value)
|
||||
return secret_value
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
verbose_logger.debug(
|
||||
f"Secret {secret_name} not found in CyberArk Conjur"
|
||||
)
|
||||
else:
|
||||
verbose_logger.exception(
|
||||
f"Error reading secret from CyberArk Conjur: {e}"
|
||||
)
|
||||
return None
|
||||
except Exception as e:
|
||||
verbose_logger.exception(f"Error reading secret from CyberArk Conjur: {e}")
|
||||
return None
|
||||
|
||||
async def async_write_secret(
|
||||
self,
|
||||
secret_name: str,
|
||||
secret_value: str,
|
||||
description: Optional[str] = None,
|
||||
optional_params: Optional[dict] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
tags: Optional[Union[dict, list]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Writes a secret to CyberArk Conjur using an async HTTPX client.
|
||||
|
||||
Args:
|
||||
secret_name: Name/path of the secret to write
|
||||
secret_value: Value to store
|
||||
description: Optional description (not used by Conjur)
|
||||
optional_params: Additional parameters
|
||||
timeout: Request timeout
|
||||
tags: Optional tags (not used by Conjur)
|
||||
|
||||
Returns:
|
||||
dict: Response containing status and details of the operation
|
||||
"""
|
||||
async_client = get_async_httpx_client(
|
||||
llm_provider=httpxSpecialProvider.SecretManager,
|
||||
params={"timeout": timeout},
|
||||
)
|
||||
|
||||
try:
|
||||
# Ensure the variable exists in the policy first
|
||||
self._ensure_variable_exists(secret_name)
|
||||
|
||||
# Now set the secret value
|
||||
url = self.get_url(secret_name)
|
||||
response = await async_client.post(
|
||||
url=url, headers=self._get_request_headers(), content=secret_value
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# Update cache
|
||||
self.cache.set_cache(secret_name, secret_value)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Secret {secret_name} written successfully",
|
||||
}
|
||||
except Exception as e:
|
||||
verbose_logger.exception(f"Error writing secret to CyberArk Conjur: {e}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
async def async_rotate_secret(
|
||||
self,
|
||||
current_secret_name: str,
|
||||
new_secret_name: str,
|
||||
new_secret_value: str,
|
||||
optional_params: Optional[Dict] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
CyberArk Conjur does not have built-in secret rotation.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: Always raised
|
||||
"""
|
||||
raise NotImplementedError("CyberArk Conjur does not support secret rotation")
|
||||
|
||||
async def async_delete_secret(
|
||||
self,
|
||||
secret_name: str,
|
||||
recovery_window_in_days: Optional[int] = 7,
|
||||
optional_params: Optional[dict] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
) -> dict:
|
||||
"""
|
||||
CyberArk Conjur does not support direct secret deletion via API.
|
||||
Secrets can only be removed through policy updates.
|
||||
|
||||
Args:
|
||||
secret_name: Name of the secret
|
||||
recovery_window_in_days: Not used
|
||||
optional_params: Additional parameters
|
||||
timeout: Request timeout
|
||||
|
||||
Returns:
|
||||
dict: Response indicating operation not supported
|
||||
"""
|
||||
verbose_logger.warning(
|
||||
"CyberArk Conjur does not support direct secret deletion. "
|
||||
"Secrets must be removed through policy updates."
|
||||
)
|
||||
|
||||
# Clear from cache
|
||||
self.cache.delete_cache(secret_name)
|
||||
|
||||
return {
|
||||
"status": "not_supported",
|
||||
"message": "CyberArk Conjur does not support direct secret deletion. Use policy updates to remove variables.",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import ast
|
||||
import base64
|
||||
import binascii
|
||||
import os
|
||||
import traceback
|
||||
from typing import Any, Optional, Union
|
||||
@@ -14,6 +12,7 @@ from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
from litellm.secret_managers.get_azure_ad_token_provider import (
|
||||
get_azure_ad_token_provider,
|
||||
)
|
||||
from litellm.secret_managers.secret_manager_handler import get_secret_from_manager
|
||||
from litellm.types.secret_managers.main import KeyManagementSystem
|
||||
|
||||
oidc_cache = DualCache()
|
||||
@@ -22,13 +21,6 @@ oidc_cache = DualCache()
|
||||
######### Secret Manager ############################
|
||||
# checks if user has passed in a secret manager client
|
||||
# if passed in then checks the secret there
|
||||
def _is_base64(s):
|
||||
try:
|
||||
return base64.b64encode(base64.b64decode(s)).decode() == s
|
||||
except binascii.Error:
|
||||
return False
|
||||
|
||||
|
||||
def str_to_bool(value: Optional[str]) -> Optional[bool]:
|
||||
"""
|
||||
Converts a string to a boolean if it's a recognized boolean string.
|
||||
@@ -222,86 +214,13 @@ def get_secret( # noqa: PLR0915
|
||||
): # allow user to specify which keys to check in hosted key manager
|
||||
key_manager = "local"
|
||||
|
||||
if (
|
||||
key_manager == KeyManagementSystem.AZURE_KEY_VAULT.value
|
||||
or type(client).__module__ + "." + type(client).__name__
|
||||
== "azure.keyvault.secrets._client.SecretClient"
|
||||
): # support Azure Secret Client - from azure.keyvault.secrets import SecretClient
|
||||
secret = client.get_secret(secret_name).value
|
||||
elif (
|
||||
key_manager == KeyManagementSystem.GOOGLE_KMS.value
|
||||
or client.__class__.__name__ == "KeyManagementServiceClient"
|
||||
):
|
||||
encrypted_secret: Any = os.getenv(secret_name)
|
||||
if encrypted_secret is None:
|
||||
raise ValueError("Google KMS requires the encrypted secret to be in the environment!")
|
||||
b64_flag = _is_base64(encrypted_secret)
|
||||
if b64_flag is True: # if passed in as encoded b64 string
|
||||
encrypted_secret = base64.b64decode(encrypted_secret)
|
||||
ciphertext = encrypted_secret
|
||||
else:
|
||||
raise ValueError(
|
||||
"Google KMS requires the encrypted secret to be encoded in base64"
|
||||
) # fix for this vulnerability https://huntr.com/bounties/ae623c2f-b64b-4245-9ed4-f13a0a5824ce
|
||||
response = client.decrypt(
|
||||
request={
|
||||
"name": litellm._google_kms_resource_name,
|
||||
"ciphertext": ciphertext,
|
||||
}
|
||||
)
|
||||
secret = response.plaintext.decode("utf-8") # assumes the original value was encoded with utf-8
|
||||
elif key_manager == KeyManagementSystem.AWS_KMS.value:
|
||||
"""
|
||||
Only check the tokens which start with 'aws_kms/'. This prevents latency impact caused by checking all keys.
|
||||
"""
|
||||
encrypted_value = os.getenv(secret_name, None)
|
||||
if encrypted_value is None:
|
||||
raise Exception("AWS KMS - Encrypted Value of Key={} is None".format(secret_name))
|
||||
# Decode the base64 encoded ciphertext
|
||||
ciphertext_blob = base64.b64decode(encrypted_value)
|
||||
|
||||
# Set up the parameters for the decrypt call
|
||||
params = {"CiphertextBlob": ciphertext_blob}
|
||||
# Perform the decryption
|
||||
response = client.decrypt(**params)
|
||||
|
||||
# Extract and decode the plaintext
|
||||
plaintext = response["Plaintext"]
|
||||
secret = plaintext.decode("utf-8")
|
||||
if isinstance(secret, str):
|
||||
secret = secret.strip()
|
||||
elif key_manager == KeyManagementSystem.AWS_SECRET_MANAGER.value:
|
||||
from litellm.secret_managers.aws_secret_manager_v2 import (
|
||||
AWSSecretsManagerV2,
|
||||
)
|
||||
|
||||
if isinstance(client, AWSSecretsManagerV2):
|
||||
secret = client.sync_read_secret(
|
||||
secret_name=secret_name,
|
||||
primary_secret_name=key_management_settings.primary_secret_name,
|
||||
)
|
||||
print_verbose(f"get_secret_value_response: {secret}")
|
||||
elif key_manager == KeyManagementSystem.GOOGLE_SECRET_MANAGER.value:
|
||||
try:
|
||||
secret = client.get_secret_from_google_secret_manager(secret_name)
|
||||
print_verbose(f"secret from google secret manager: {secret}")
|
||||
if secret is None:
|
||||
raise ValueError(f"No secret found in Google Secret Manager for {secret_name}")
|
||||
except Exception as e:
|
||||
print_verbose(f"An error occurred - {str(e)}")
|
||||
raise e
|
||||
elif key_manager == KeyManagementSystem.HASHICORP_VAULT.value:
|
||||
try:
|
||||
secret = client.sync_read_secret(secret_name=secret_name)
|
||||
if secret is None:
|
||||
raise ValueError(f"No secret found in Hashicorp Secret Manager for {secret_name}")
|
||||
except Exception as e:
|
||||
print_verbose(f"An error occurred - {str(e)}")
|
||||
raise e
|
||||
elif key_manager == "local":
|
||||
secret = os.getenv(secret_name)
|
||||
else: # assume the default is infisicial client
|
||||
secret = client.get_secret(secret_name).secret_value
|
||||
# Delegate to the secret manager handler
|
||||
secret = get_secret_from_manager(
|
||||
client=client,
|
||||
key_manager=key_manager,
|
||||
secret_name=secret_name,
|
||||
key_management_settings=key_management_settings,
|
||||
)
|
||||
except Exception as e: # check if it's in os.environ
|
||||
verbose_logger.error(
|
||||
f"Defaulting to os.environ value for key={secret_name}. An exception occurred - {str(e)}.\n\n{traceback.format_exc()}"
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Secret Manager Handler
|
||||
|
||||
Handles retrieving secrets from different secret management systems.
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
from typing import Any, Optional
|
||||
|
||||
import litellm
|
||||
from litellm._logging import print_verbose
|
||||
from litellm.types.secret_managers.main import KeyManagementSystem
|
||||
|
||||
|
||||
def _is_base64(s):
|
||||
"""Check if a string is valid base64."""
|
||||
import binascii
|
||||
try:
|
||||
return base64.b64encode(base64.b64decode(s)).decode() == s
|
||||
except binascii.Error:
|
||||
return False
|
||||
|
||||
|
||||
def get_secret_from_manager(
|
||||
client: Any,
|
||||
key_manager: str,
|
||||
secret_name: str,
|
||||
key_management_settings: Optional[Any] = None,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Get a secret from the configured secret manager.
|
||||
|
||||
Args:
|
||||
client: The secret manager client instance
|
||||
key_manager: The type of key manager (e.g., "azure_key_vault", "google_kms", etc.)
|
||||
secret_name: The name/path of the secret to retrieve
|
||||
key_management_settings: Optional settings for the key management system
|
||||
|
||||
Returns:
|
||||
The secret value as a string, or None if not found
|
||||
|
||||
Raises:
|
||||
ValueError: If the secret cannot be retrieved or required parameters are missing
|
||||
Exception: For other errors during secret retrieval
|
||||
"""
|
||||
secret = None
|
||||
|
||||
if (
|
||||
key_manager == KeyManagementSystem.AZURE_KEY_VAULT.value
|
||||
or type(client).__module__ + "." + type(client).__name__
|
||||
== "azure.keyvault.secrets._client.SecretClient"
|
||||
): # support Azure Secret Client - from azure.keyvault.secrets import SecretClient
|
||||
secret = client.get_secret(secret_name).value
|
||||
|
||||
elif (
|
||||
key_manager == KeyManagementSystem.GOOGLE_KMS.value
|
||||
or client.__class__.__name__ == "KeyManagementServiceClient"
|
||||
):
|
||||
encrypted_secret: Any = os.getenv(secret_name)
|
||||
if encrypted_secret is None:
|
||||
raise ValueError("Google KMS requires the encrypted secret to be in the environment!")
|
||||
b64_flag = _is_base64(encrypted_secret)
|
||||
if b64_flag is True: # if passed in as encoded b64 string
|
||||
encrypted_secret = base64.b64decode(encrypted_secret)
|
||||
ciphertext = encrypted_secret
|
||||
else:
|
||||
raise ValueError(
|
||||
"Google KMS requires the encrypted secret to be encoded in base64"
|
||||
) # fix for this vulnerability https://huntr.com/bounties/ae623c2f-b64b-4245-9ed4-f13a0a5824ce
|
||||
response = client.decrypt(
|
||||
request={
|
||||
"name": litellm._google_kms_resource_name,
|
||||
"ciphertext": ciphertext,
|
||||
}
|
||||
)
|
||||
secret = response.plaintext.decode("utf-8") # assumes the original value was encoded with utf-8
|
||||
|
||||
elif key_manager == KeyManagementSystem.AWS_KMS.value:
|
||||
"""
|
||||
Only check the tokens which start with 'aws_kms/'. This prevents latency impact caused by checking all keys.
|
||||
"""
|
||||
encrypted_value = os.getenv(secret_name, None)
|
||||
if encrypted_value is None:
|
||||
raise Exception("AWS KMS - Encrypted Value of Key={} is None".format(secret_name))
|
||||
# Decode the base64 encoded ciphertext
|
||||
ciphertext_blob = base64.b64decode(encrypted_value)
|
||||
|
||||
# Set up the parameters for the decrypt call
|
||||
params = {"CiphertextBlob": ciphertext_blob}
|
||||
# Perform the decryption
|
||||
response = client.decrypt(**params)
|
||||
|
||||
# Extract and decode the plaintext
|
||||
plaintext = response["Plaintext"]
|
||||
secret = plaintext.decode("utf-8")
|
||||
if isinstance(secret, str):
|
||||
secret = secret.strip()
|
||||
|
||||
elif key_manager == KeyManagementSystem.AWS_SECRET_MANAGER.value:
|
||||
from litellm.secret_managers.aws_secret_manager_v2 import (
|
||||
AWSSecretsManagerV2,
|
||||
)
|
||||
|
||||
if isinstance(client, AWSSecretsManagerV2):
|
||||
primary_secret_name = None
|
||||
if key_management_settings is not None:
|
||||
primary_secret_name = key_management_settings.primary_secret_name
|
||||
|
||||
secret = client.sync_read_secret(
|
||||
secret_name=secret_name,
|
||||
primary_secret_name=primary_secret_name,
|
||||
)
|
||||
print_verbose(f"get_secret_value_response: {secret}")
|
||||
|
||||
elif key_manager == KeyManagementSystem.GOOGLE_SECRET_MANAGER.value:
|
||||
try:
|
||||
secret = client.get_secret_from_google_secret_manager(secret_name)
|
||||
print_verbose(f"secret from google secret manager: {secret}")
|
||||
if secret is None:
|
||||
raise ValueError(f"No secret found in Google Secret Manager for {secret_name}")
|
||||
except Exception as e:
|
||||
print_verbose(f"An error occurred - {str(e)}")
|
||||
raise e
|
||||
|
||||
elif key_manager == KeyManagementSystem.HASHICORP_VAULT.value:
|
||||
try:
|
||||
secret = client.sync_read_secret(secret_name=secret_name)
|
||||
if secret is None:
|
||||
raise ValueError(f"No secret found in Hashicorp Secret Manager for {secret_name}")
|
||||
except Exception as e:
|
||||
print_verbose(f"An error occurred - {str(e)}")
|
||||
raise e
|
||||
|
||||
elif key_manager == KeyManagementSystem.CYBERARK.value:
|
||||
try:
|
||||
secret = client.sync_read_secret(secret_name=secret_name)
|
||||
if secret is None:
|
||||
raise ValueError(f"No secret found in CyberArk Secret Manager for {secret_name}")
|
||||
except Exception as e:
|
||||
print_verbose(f"An error occurred - {str(e)}")
|
||||
raise e
|
||||
|
||||
elif key_manager == "local":
|
||||
secret = os.getenv(secret_name)
|
||||
|
||||
else: # assume the default is infisicial client
|
||||
secret = client.get_secret(secret_name).secret_value
|
||||
|
||||
return secret
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import enum
|
||||
from typing import List, Literal, Optional, Dict
|
||||
from typing import Dict, List, Literal, Optional
|
||||
|
||||
from litellm.types.llms.base import LiteLLMPydanticObjectBase
|
||||
|
||||
@@ -10,6 +10,7 @@ class KeyManagementSystem(enum.Enum):
|
||||
AWS_SECRET_MANAGER = "aws_secret_manager"
|
||||
GOOGLE_SECRET_MANAGER = "google_secret_manager"
|
||||
HASHICORP_VAULT = "hashicorp_vault"
|
||||
CYBERARK = "cyberark"
|
||||
LOCAL = "local"
|
||||
AWS_KMS = "aws_kms"
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Integration test for CyberArk Conjur Secret Manager.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
from unittest.mock import patch
|
||||
from litellm._uuid import uuid
|
||||
|
||||
# Set up environment variables for testing
|
||||
os.environ["CYBERARK_API_KEY"] = "2syke5r262b6je2f4et1x3jptmry3frfx83t65e6417zad632e5qq8a"
|
||||
os.environ["CYBERARK_API_BASE"] = "http://0.0.0.0:8080"
|
||||
os.environ["CYBERARK_ACCOUNT"] = "default"
|
||||
os.environ["CYBERARK_USERNAME"] = "admin"
|
||||
|
||||
from litellm.secret_managers.cyberark_secret_manager import CyberArkSecretManager
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cyberark_write_and_read_secret():
|
||||
"""
|
||||
Integration test: Write a secret to CyberArk Conjur and read it back to validate.
|
||||
"""
|
||||
with patch("litellm.proxy.proxy_server.premium_user", True):
|
||||
# Create CyberArk secret manager instance
|
||||
cyberark_manager = CyberArkSecretManager()
|
||||
|
||||
# Generate unique secret name and value
|
||||
secret_name = f"test-secret-{uuid.uuid4()}"
|
||||
secret_value = f"test-value-{uuid.uuid4()}"
|
||||
|
||||
# Write the secret
|
||||
write_response = await cyberark_manager.async_write_secret(
|
||||
secret_name=secret_name,
|
||||
secret_value=secret_value,
|
||||
)
|
||||
# Avoid logging write_response to prevent leaking secret names
|
||||
|
||||
# Validate write was successful
|
||||
assert write_response["status"] == "success"
|
||||
|
||||
# Read the secret back
|
||||
read_value = cyberark_manager.sync_read_secret(secret_name=secret_name)
|
||||
# Don't log secret value in clear text
|
||||
|
||||
# Validate the secret exists and has the correct value
|
||||
assert read_value is not None
|
||||
assert read_value == secret_value
|
||||
|
||||
Reference in New Issue
Block a user