Secrets Management

This guide covers advanced topics for managing secrets in Julep, including security architecture, best practices, rotation policies, and integration patterns.

Security Architecture

Secrets in Julep are stored with a layered security approach:

  1. Application-level Validation: Secrets are validated before being stored
  2. Database Encryption: Secrets are stored encrypted using PostgreSQL’s pgcrypto extension with AES-256
  3. Access Control: Secrets are scoped to developers and only accessible within their resources
  4. Master Key Security: A separate master encryption key secures all stored secrets

The encryption process works as follows:

  • When a secret is created, its value is encrypted using the master key
  • The encrypted value is stored in the database’s value_encrypted column
  • When a secret is accessed, the value is decrypted using the master key
  • The master key is stored as an environment variable, separate from the database

Creating Effective Secret Names

Secrets should have descriptive names that follow these conventions:

  • Use snake_case formatting
  • Begin with a letter and contain only alphanumeric characters and underscores
  • Use a prefix to indicate the service (e.g., aws_secret_key, stripe_api_key)
  • Be specific enough to understand the purpose (e.g., gmail_oauth_token vs email_token)

Secret Rotation Best Practices

Regular rotation of secrets is a security best practice:

  1. Create a new secret with a temporary name
  2. Update your services to use the new secret
  3. Once confirmed working, delete the old secret
  4. Update the new secret’s name to the standard name

For automated rotation:

from julep import Julep
import uuid

client = Julep(api_key="your_api_key")

# Generate temporary name
temp_name = f"stripe_key_rotation_{uuid.uuid4().hex[:8]}"

# Create new secret with temp name
client.secrets.create(
    name=temp_name,
    value="sk_new_value...",
    description="New Stripe API key (rotation)",
    metadata={"rotation_date": "2025-05-10"}
)

# Test the new key (implement your validation logic here)
# ...

# If valid, delete old secret and rename new one
client.secrets.delete(name="stripe_api_key")
client.secrets.update(
    name=temp_name,
    new_name="stripe_api_key",
    description="Stripe API key",
    metadata={"last_rotated": "2025-05-10"}
)

Using Secrets with Different Tool Types

API Tools

For HTTP-based tools, reference secrets in the headers or authentication:

steps:
  - kind: tool_call
    tool: api_service
    operation: fetch_data
    arguments:
      url: "https://api.example.com/data"
      headers:
        Authorization: "$ f'Bearer {secrets.api_token}'"

Database Connections

For database tools, use secrets for connection credentials:

steps:
  - kind: tool_call
    tool: database
    operation: query
    arguments:
      query: "SELECT * FROM users LIMIT 10"
    secrets:
      db_username: "my_db_user"
      db_password: "my_db_password"

AI Service Integration

For AI services that require API keys:

steps:
  - kind: prompt
    model: "$ secrets.preferred_model"
    provider: "$ secrets.provider_name"
    prompt: "Generate creative ideas for a marketing campaign"
    api_key: "$ secrets.openai_api_key"

Managing Secrets for Multi-Environment Deployments

For applications deployed across development, staging, and production environments:

  1. Use consistent naming conventions with environment prefixes:

    • dev_stripe_key, staging_stripe_key, prod_stripe_key
  2. Use metadata to tag secrets by environment:

    client.secrets.create(
        name="stripe_api_key",
        value="sk_test_...",
        metadata={"environment": "production"}
    )
    
  3. Filter secrets by environment when listing:

    prod_secrets = client.secrets.list(
        metadata={"environment": "production"}
    )
    

Secret Templating

For complex configurations that require multiple secrets:

steps:
  - kind: tool_call
    tool: database
    operation: query
    arguments:
      connection_string: "$ f'mongodb+srv://{secrets.db_username}:{secrets.db_password}@{secrets.db_host}/{secrets.db_name}'"

Securing LLM API Keys with Secrets

Julep automatically looks for LLM API keys in your secrets store based on the provider name. Use these naming conventions for automatic lookup:

ProviderSecret Name
OpenAIOPENAI_API_KEY
AnthropicANTHROPIC_API_KEY
GoogleGOOGLE_API_KEY
Azure OpenAIAZURE_OPENAI_API_KEY
CohereCOHERE_API_KEY

Example of setting up an LLM API key:

client.secrets.create(
    name="OPENAI_API_KEY",
    value="sk-...",
    description="OpenAI API key for GPT-4 access"
)

Audit and Monitoring

Best practices for security monitoring:

  1. Regularly audit secret access and usage
  2. Track changes to secrets via the updated_at timestamp
  3. Implement secret expiration for highly sensitive data
  4. Use metadata to track last review or rotation dates

Example audit script:

from julep import Julep
from datetime import datetime, timedelta

client = Julep(api_key="your_api_key")

# Find secrets not rotated in over 90 days
old_threshold = datetime.now() - timedelta(days=90)
secrets = client.secrets.list()

for secret in secrets.items:
    if secret.updated_at < old_threshold:
        print(f"WARNING: Secret {secret.name} has not been rotated in over 90 days")

Troubleshooting

Common issues when working with secrets:

  1. Secret Not Found: Check that the secret name matches exactly, including case
  2. Permission Errors: Verify the developer ID has access to the secret
  3. Encryption Errors: Ensure the master key is correctly set in the environment
  4. Reference Errors: Ensure the secret reference syntax is correct in expressions and templates

Next Steps