Common Secrets Patterns

This guide covers common patterns and best practices for working with secrets that apply across all Julep SDKs.

Secret Management Lifecycle

The typical lifecycle for secrets in Julep applications includes:

  1. Creation: Establishing new secrets
  2. Retrieval: Accessing secret metadata (not values)
  3. Usage: Referencing secrets in tasks and tools
  4. Update: Rotating or changing secret values
  5. Deletion: Removing secrets when no longer needed

Naming Conventions

Consistent naming helps with secret organization:

  • Use snake_case formatting (e.g., aws_access_key)
  • Be descriptive but concise
  • Include service name as prefix (stripe_secret_key vs just secret_key)
  • For multiple environments, include environment prefix (dev_stripe_key, prod_stripe_key)

Secret Reference Patterns

When using secrets in tasks, you have several reference patterns available:

Direct Reference

Reference a secret directly by name:

secret_name: openai_api_key

Multiple Secrets

For operations requiring multiple secrets:

secrets:
  service_api_key: "api_key_secret_name"
  service_auth_token: "auth_token_secret_name"

Expression Reference

Reference secrets within expressions:

arguments:
  headers:
    Authorization: "$ f'Bearer {secrets.api_token}'"

LLM Provider Keys

Store LLM API keys with standard names for automatic lookup:

# Python SDK
client.secrets.create(
    name="OPENAI_API_KEY",
    value="sk-..."
)

# Node.js SDK
await julep.secrets.create({
  name: 'ANTHROPIC_API_KEY',
  value: 'sk-ant-...'
});

Error Handling

Common error scenarios when working with secrets:

  1. Secret Not Found: The referenced secret doesn’t exist
  2. Permission Denied: No access to the requested secret
  3. Validation Error: Secret name doesn’t match required format
  4. Duplicate Name: Attempting to create a secret with a name that already exists

Handle these consistently across your application:

# Python SDK
from julep.exceptions import SecretNotFoundError, ValidationError

try:
    secret = client.secrets.get(name="non_existent_secret")
except SecretNotFoundError:
    # Handle missing secret
    print("Secret not found, using default value")
except ValidationError as e:
    # Handle validation error
    print(f"Invalid secret name: {e}")
// Node.js SDK
try {
  const secret = await julep.secrets.get({ name: 'non_existent_secret' });
} catch (error) {
  if (error.code === 'not_found') {
    // Handle missing secret
    console.log('Secret not found, using default value');
  } else if (error.code === 'validation_error') {
    // Handle validation error
    console.log(`Invalid secret name: ${error.message}`);
  }
}

Testing with Secrets

For testing applications that use secrets:

  1. Create a separate set of test secrets with appropriate prefixes
  2. Use mocking in unit tests to avoid requiring real secrets
  3. For integration tests, use dedicated test accounts and credentials
  4. Never use production secrets in test environments

Example of mocking secrets for testing:

# Python mock example
import pytest
from unittest.mock import patch

@pytest.fixture
def mock_secrets():
    return {
        "api_key": "mock-api-key-123",
        "auth_token": "mock-token-456"
    }

@patch("julep.client.Secrets.get")
def test_with_mock_secrets(mock_get, mock_secrets):
    mock_get.return_value = mock_secrets
    # Test code that uses secrets
// JavaScript mock example
jest.mock('@julep/sdk', () => {
  return {
    Julep: jest.fn().mockImplementation(() => {
      return {
        secrets: {
          get: jest.fn().mockResolvedValue({
            name: 'api_key',
            value: 'mock-api-key-123'
          })
        }
      };
    })
  };
});

Migrating from Environment Variables

When migrating from environment variables to Julep secrets:

  1. Create a list of all environment variables used in your application
  2. Create corresponding secrets in Julep with the same names
  3. Update your code to reference Julep secrets instead of environment variables
  4. Validate functionality before removing the original environment variables

Migration script example:

import os
from julep import Julep

client = Julep(api_key="your_api_key")

# List of environment variables to migrate
env_vars_to_migrate = [
    "OPENAI_API_KEY",
    "STRIPE_SECRET_KEY",
    "DATABASE_URL",
    "AUTH_TOKEN"
]

# Migrate each environment variable to a Julep secret
for env_var in env_vars_to_migrate:
    value = os.environ.get(env_var)
    if value:
        try:
            client.secrets.create(
                name=env_var.lower(),  # Convert to snake_case
                value=value,
                description=f"Migrated from environment variable {env_var}"
            )
            print(f"Successfully migrated {env_var} to Julep secret")
        except Exception as e:
            print(f"Failed to migrate {env_var}: {e}")
    else:
        print(f"Environment variable {env_var} not found")

Integration with External Secret Managers

For organizations using external secret managers, you can sync to Julep:

# Example syncing AWS Secrets Manager to Julep
import boto3
from julep import Julep

# Initialize clients
julep_client = Julep(api_key="your_api_key")
aws_client = boto3.client('secretsmanager')

# Get secrets from AWS
response = aws_client.list_secrets()
for secret in response['SecretList']:
    # Get the secret value
    secret_value = aws_client.get_secret_value(SecretId=secret['ARN'])
    
    # Create or update the secret in Julep
    try:
        julep_client.secrets.create(
            name=f"aws_{secret['Name']}",
            value=secret_value['SecretString'],
            description=f"Synced from AWS Secrets Manager: {secret['Name']}",
            metadata={
                "source": "aws",
                "arn": secret['ARN'],
                "sync_date": datetime.now().isoformat()
            }
        )
        print(f"Synced secret {secret['Name']}")
    except Exception:
        # Secret already exists, update it
        julep_client.secrets.update(
            name=f"aws_{secret['Name']}",
            value=secret_value['SecretString'],
            metadata={
                "source": "aws",
                "arn": secret['ARN'],
                "sync_date": datetime.now().isoformat()
            }
        )
        print(f"Updated secret {secret['Name']}")

Security Best Practices

  1. Limit who has access to create and manage secrets
  2. Never log secret values, even in debug environments
  3. Rotate secrets regularly, especially for high-value credentials
  4. Use the most specific scope possible for each secret
  5. Audit secret usage and access patterns
  6. Use metadata to track important information about secrets
  7. Implement an encrypted backup strategy for critical secrets

Next Steps