encrypted-env-loader 0.3.0
Securely load and manage encrypted environment variables
License: MIT
Requires Python: >=3.9
Keywords: environment, encryption, security, dotenv, configuration

# Encrypted Environment Loader

A secure Python package for managing encrypted environment variables. Load sensitive configuration from encrypted `.env` files using Fernet encryption, with convenient keyring integration for local development.

## Features

- **Secure encryption** using Fernet (AES 128 in CBC mode with HMAC-SHA256)
- **Keyring integration** for secure local key storage (macOS Keychain, Windows Credential Manager, Linux Secret Service)
- **Profile support** for multiple environments (dev, prod, test, etc.)
- **CLI tools** for file management and subprocess execution
- **Python API** with context managers and decorators
- **Shell integration** for fish, bash, and zsh
- **Safe editing** with backup and validation
- **CI/CD friendly** with quiet modes and secret masking
- **Modern Python** with type hints and proper packaging

## Installation

```bash
pip install encrypted-env-loader
```

## Quick Start

### 1. Initialize a new encrypted environment file

```bash
# Generate new encrypted file with random key
encrypted-env init
# Output: Encryption key: gAAAAABh...
# Output: Set your encryption key: export ENCRYPTED_ENV_KEY='gAAAAABh...'

# Set the key in your shell
export ENCRYPTED_ENV_KEY='gAAAAABh...'
```

### 2. Edit your environment variables

```bash
# Opens decrypted content in $EDITOR, re-encrypts on save
encrypted-env edit
```

### 3. Run commands with encrypted environment

```bash
# Run any command with encrypted env loaded
encrypted-env run -- python app.py
encrypted-env run -- ./deploy.sh
```

### Or use shell integration

```bash
# Load into current shell (fish)
eval (encrypted-env load)

# Load into current shell (bash/zsh)  
eval $(encrypted-env load)
```

## Keyring Integration (New in v0.2.0)

For **local development**, store encryption keys securely in your system keyring instead of environment variables:

### 1. Store key in keyring

```bash
# Generate and store key for default profile
encrypted-env keyring set-key

# Store key for specific environment
encrypted-env keyring set-key --profile dev
encrypted-env keyring set-key --profile prod
```

### 2. Use encrypted environment with keyring

```bash
# Automatically uses keyring-stored key
encrypted-env run --profile dev -- python app.py
encrypted-env load --profile dev
encrypted-env edit --profile dev
```

### 3. Production/CI usage

```bash
# Force environment variable usage (skip keyring)
encrypted-env run --profile prod --no-keyring -- ./deploy.sh
export ENCRYPTED_ENV_KEY="your-key-here"
encrypted-env load --profile prod --no-keyring
```

### 4. Manage keyring keys

```bash
# List stored keys
encrypted-env keyring list-keys

# Retrieve a key  
encrypted-env keyring get-key --profile dev

# Delete a key
encrypted-env keyring delete-key --profile dev
```

**Key Benefits:**
- 🔐 **Secure**: Keys stored in OS-native keyring (never in plaintext)
- 🚀 **Convenient**: No need to manage environment variables locally
- 🔄 **Flexible**: Fallback to environment variables for production/CI
- 📋 **Profile-aware**: Separate keys per environment automatically

## Security Features

### CI/CD Safe Operations
All commands support `--quiet` mode for CI environments and have secure defaults:

```bash
# Generate keys silently (CI-safe)
KEY=$(encrypted-env generate-key --quiet)

# Show variable names only (never exposes values)
encrypted-env show --names-only

# Validate files without output (exit codes only)
encrypted-env validate --quiet
```

### Secret Masking
By default, commands mask sensitive values:

```bash
# Safe - shows masked values
encrypted-env show
# Output: DATABASE_URL=***
#         API_KEY=***

# Requires explicit flag to show values
encrypted-env show --show-values  # WARNING: exposes secrets
```

## Examples and Testing

Run the comprehensive demo to see all features:

```bash
# Interactive demo with full output
./examples/demo.sh

# CI-friendly mode (no secrets exposed)
./examples/demo.sh --ci

# View usage examples
./examples/basic_usage.sh
```

## CLI Reference

### Core Operations

#### `run` - Execute commands with encrypted environment
```bash
encrypted-env run [--file FILE] [--profile PROFILE] [--no-keyring] -- <command>

# Examples
encrypted-env run -- python app.py
encrypted-env run --profile prod -- ./deploy.sh
encrypted-env run --file .env.custom.encrypted -- npm start
encrypted-env run --profile dev --no-keyring -- python app.py  # force env var usage
```

#### `load` - Generate shell commands to load environment
```bash
encrypted-env load [--file FILE] [--profile PROFILE] [--shell SHELL] [--quiet] [--no-keyring]

# Usage in fish shell
eval (encrypted-env load)
eval (encrypted-env load --profile dev)
eval (encrypted-env load --profile prod --no-keyring)  # force env var usage

# Usage in bash/zsh
eval $(encrypted-env load)
eval $(encrypted-env load --profile prod)
```

#### `clear` - Generate shell commands to clear environment
```bash
encrypted-env clear [--file FILE] [--profile PROFILE] [--shell SHELL] [--quiet] [--no-keyring]

# Usage
eval (encrypted-env clear)  # fish
eval $(encrypted-env clear)  # bash/zsh
```

### File Management

#### `init` - Create new encrypted environment file
```bash
encrypted-env init [--file FILE] [--profile PROFILE] [--key-file KEYFILE] [--quiet]

# Examples
encrypted-env init                           # creates .env.encrypted
encrypted-env init --profile dev             # creates .env.dev.encrypted  
encrypted-env init --key-file .env.key      # saves key to file
encrypted-env init --quiet                   # CI-friendly (key only)
```

#### `encrypt` - Encrypt existing .env file
```bash
encrypted-env encrypt <source> [--output OUTPUT] [--profile PROFILE] [--quiet]

# Examples
encrypted-env encrypt .env                   # creates .env.encrypted
encrypted-env encrypt .env.dev --profile dev  # creates .env.dev.encrypted
encrypted-env encrypt .env --output custom.encrypted
```

#### `decrypt` - Decrypt to filesystem
```bash
encrypted-env decrypt [--file FILE] [--output OUTPUT] [--profile PROFILE] [--quiet]

# Examples
encrypted-env decrypt                        # decrypts to .env
encrypted-env decrypt --profile dev         # decrypts .env.dev.encrypted to .env
encrypted-env decrypt --output .env.backup  # custom output file
```

#### `edit` - Safely edit encrypted files
```bash
encrypted-env edit [--file FILE] [--profile PROFILE] [--quiet]

# Opens in $EDITOR (vi by default)
# Creates backup before editing
# Validates .env format before re-encrypting
encrypted-env edit --profile prod
```

### Key Management

#### `generate-key` - Generate new encryption key
```bash
encrypted-env generate-key [--quiet]

# Interactive mode
encrypted-env generate-key
# Output: Generated key: gAAAAABh...

# CI mode
encrypted-env generate-key --quiet
# Output: gAAAAABh...
```

#### `rekey` - Change encryption key
```bash
encrypted-env rekey [--file FILE] [--profile PROFILE] [--old-key-env VAR] [--new-key-env VAR] [--quiet]

# Examples
ENCRYPTED_ENV_KEY="old_key" NEW_KEY="new_key" encrypted-env rekey --new-key-env NEW_KEY
encrypted-env rekey --quiet  # generates new random key silently
```

### Keyring Management (New in v0.2.0)

#### `keyring set-key` - Store encryption key in system keyring
```bash
encrypted-env keyring set-key [--profile PROFILE] [--key KEY] [--quiet]

# Examples
encrypted-env keyring set-key                    # generates key for default profile
encrypted-env keyring set-key --profile dev     # generates key for dev profile
encrypted-env keyring set-key --key "ABC123"    # store specific key
encrypted-env keyring set-key --quiet           # CI-friendly output
```

#### `keyring get-key` - Retrieve encryption key from keyring
```bash
encrypted-env keyring get-key [--profile PROFILE] [--quiet]

# Examples
encrypted-env keyring get-key                    # get default profile key
encrypted-env keyring get-key --profile dev     # get dev profile key  
encrypted-env keyring get-key --quiet           # key only, no messages
```

#### `keyring delete-key` - Remove encryption key from keyring
```bash
encrypted-env keyring delete-key [--profile PROFILE] [--quiet]

# Examples  
encrypted-env keyring delete-key                 # delete default profile key
encrypted-env keyring delete-key --profile dev  # delete dev profile key
encrypted-env keyring delete-key --quiet        # no confirmation prompt
```

#### `keyring list-keys` - List all profiles with stored keys
```bash
encrypted-env keyring list-keys [--quiet]

# Example output:
# Found keys for 3 profiles:
#   - default
#   - dev  
#   - prod

# Quiet mode (CI-friendly)
encrypted-env keyring list-keys --quiet
# Output: default
#         dev
#         prod
```

**Keyring Key Storage:**
- Service name: Git repository name (e.g., `encrypted-env-loader`)
- Username format: `{profile}-key` (e.g., `dev-key`, `default-key`)
- Automatic detection: Git repo name used for consistent keyring service naming

### Utilities

#### `status` - Show file information and variables
```bash
encrypted-env status [--file FILE] [--profile PROFILE] [--no-keyring]

# Example output with keyring:
# File: .env.encrypted
# Exists: True
# Size: 1024 bytes
# Key source: keyring (profile: default)
# Status: Valid (contains 5 variables)
# Variables:
#   - DATABASE_URL
#   - SECRET_KEY

# Example output with environment variable:
# File: .env.encrypted  
# Exists: True
# Size: 1024 bytes
# Key source: $ENCRYPTED_ENV_KEY
# Status: Valid (contains 5 variables)
# Variables:
#   - DATABASE_URL
#   - SECRET_KEY

# With --no-keyring flag:
# Key source: $ENCRYPTED_ENV_KEY (not found)
# Status: Cannot validate (no key)
```

#### `validate` - Check if file can be decrypted
```bash
encrypted-env validate [--file FILE] [--profile PROFILE] [--quiet] [--no-keyring]
# Exit code 0 if valid, 1 if invalid
```

#### `show` - Display variables (with security options)
```bash
encrypted-env show [--file FILE] [--profile PROFILE] [--names-only] [--show-values] [--no-keyring]

# Safe default (masks values)
encrypted-env show
# Output: DATABASE_URL=***

# CI-safe (names only)
encrypted-env show --names-only
# Output: DATABASE_URL
#         API_KEY

# Explicit flag required to show values
encrypted-env show --show-values  # WARNING: exposes secrets
```

## Python API

### Basic Usage

```python
from encrypted_env_loader import load_encrypted_env

# Load with default settings (.env.encrypted, keyring or ENCRYPTED_ENV_KEY)
env_vars = load_encrypted_env()

# Load with specific parameters
env_vars = load_encrypted_env(
    key="base64-encoded-key",
    file_path=".env.prod.encrypted",
    profile="prod",
    change_os_env=True,  # Updates os.environ
    use_keyring=True     # Try keyring first (default: True)
)

# Force environment variable usage (skip keyring)
env_vars = load_encrypted_env(
    profile="prod",
    use_keyring=False
)
```

### Context Manager

```python
from encrypted_env_loader import encrypted_env_context
import os

with encrypted_env_context(profile="dev"):
    # Environment variables loaded here (uses keyring automatically)
    database_url = os.getenv("DATABASE_URL")
    secret_key = os.getenv("SECRET_KEY")
# Environment automatically restored when exiting context

# Force environment variable usage  
with encrypted_env_context(profile="prod", use_keyring=False):
    # Uses ENCRYPTED_ENV_KEY environment variable
    pass
```

### Decorator

```python
from encrypted_env_loader import with_encrypted_env
import os

@with_encrypted_env(profile="prod")
def deploy_application():
    # Function runs with encrypted env loaded (keyring first, then env var)
    api_key = os.getenv("API_KEY")
    database_url = os.getenv("DATABASE_URL")
    # Environment restored after function returns

@with_encrypted_env(profile="prod", use_keyring=False)
def ci_deploy():
    # Force environment variable usage for CI/production
    pass

deploy_application()
```

### Keyring Functions (New in v0.2.0)

```python
from encrypted_env_loader import (
    get_key_from_keyring,
    set_key_in_keyring,
    delete_key_from_keyring,
    list_keyring_profiles,
    get_git_repo_name
)

# Get git repository name (used for keyring service name)
repo_name = get_git_repo_name()  # e.g., "my-project"

# Store key in keyring
success = set_key_in_keyring("my-secret-key", profile="dev")

# Retrieve key from keyring  
key = get_key_from_keyring(profile="dev")

# List profiles with stored keys
profiles = list_keyring_profiles()  # ["default", "dev", "prod"]

# Delete key from keyring
success = delete_key_from_keyring(profile="dev")
```

### Utility Functions

```python
from encrypted_env_loader import (
    generate_key,
    encrypt_env_file,
    decrypt_env_file,
    validate_encrypted_file
)

# Generate encryption key
key = generate_key()

# Encrypt a file (supports keyring)
encrypt_env_file(".env", ".env.encrypted", key)
encrypt_env_file(".env", ".env.encrypted", profile="dev")  # uses keyring
encrypt_env_file(".env", ".env.encrypted", profile="dev", use_keyring=False)  # force env var

# Decrypt and get variables (supports keyring)
env_vars = decrypt_env_file(".env.encrypted", key=key)
env_vars = decrypt_env_file(".env.encrypted", profile="dev")  # uses keyring
env_vars = decrypt_env_file(".env.encrypted", profile="dev", use_keyring=False)  # force env var

# Validate file (supports keyring)
is_valid = validate_encrypted_file(".env.encrypted", key=key)
is_valid = validate_encrypted_file(".env.encrypted", profile="dev")  # uses keyring
is_valid = validate_encrypted_file(".env.encrypted", profile="dev", use_keyring=False)  # force env var
```

## Profiles

Profiles allow managing multiple environment configurations:

```bash
# Profile-based file naming
.env.encrypted          # default profile
.env.dev.encrypted      # dev profile  
.env.prod.encrypted     # prod profile
.env.test.encrypted     # test profile

# Usage
encrypted-env init --profile dev
encrypted-env run --profile prod -- python app.py
encrypted-env edit --profile test
```

## Shell Integration

### Fish Shell
```fish
# Load environment
eval (encrypted-env load --profile dev)

# Clear environment  
eval (encrypted-env clear --profile dev)

# One-liner with auto-clear
encrypted-env run --profile dev -- python app.py
```

### Bash/Zsh
```bash
# Load environment
eval $(encrypted-env load --profile dev)

# Clear environment
eval $(encrypted-env clear --profile dev)

# Use in loops (eval once, use many times)
eval $(encrypted-env load)
for i in {1..100}; do
    curl -H "Authorization: $SECRET_TOKEN" api.example.com/data/$i
done
eval $(encrypted-env clear)
```

## CI/CD Integration

### GitHub Actions Example
```yaml
- name: Setup encrypted environment
  run: |
    # Generate or retrieve key securely
    echo "${{ secrets.ENCRYPTED_ENV_KEY }}" > .env.key
    export ENCRYPTED_ENV_KEY=$(cat .env.key)
    
    # Validate encrypted file
    encrypted-env validate --quiet
    
    # Run tests with encrypted environment
    encrypted-env run -- pytest
```

### Security Best Practices
```bash
# Never expose secrets in CI logs
encrypted-env show --names-only          # ✅ Safe
encrypted-env validate --quiet           # ✅ Safe  
encrypted-env generate-key --quiet       # ✅ Safe

# Avoid these in CI
encrypted-env show --show-values         # ❌ Exposes secrets
encrypted-env status                     # ❌ May expose info
```

## Security Considerations

- **Key Storage**: Never commit encryption keys to version control
- **Key Rotation**: Regularly rotate encryption keys using `rekey` command
- **File Permissions**: Ensure encrypted files have appropriate permissions
- **Backup Strategy**: Keep secure backups of both encrypted files and keys
- **Environment Isolation**: Use profiles to separate dev/staging/prod secrets
- **CI/CD Safety**: Use `--quiet` and `--names-only` flags in automated environments

## Error Handling

The package provides specific exception types:

```python
from encrypted_env_loader import EncryptedEnvError, DecryptionError, KeyError

try:
    load_encrypted_env()
except KeyError:
    print("Encryption key missing or invalid")
except DecryptionError:
    print("File cannot be decrypted - wrong key or corrupted data")
except EncryptedEnvError:
    print("General error with encrypted environment operations")
```

## Development

### Setup
```bash
git clone https://github.com/igutekunst/encrypted-env-loader
cd encrypted-env-loader
pip install -e ".[dev]"
```

### Testing
```bash
# Run unit tests
pytest

# Run full demo/integration tests
./examples/demo.sh

# Run CI-safe tests
./examples/demo.sh --ci

# Test coverage
pytest --cov=encrypted_env_loader
```

### Code Quality
```bash
black src tests
isort src tests  
flake8 src tests
mypy src
```

## License

MIT License - see [LICENSE](LICENSE) file.

## Contributing

1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request

## Changelog

### 0.2.0
- **Keyring integration** for secure local key storage
- **Automatic git repo detection** for keyring service naming
- **Key resolution priority**: explicit key → keyring → environment variable
- **New CLI command group**: `keyring` with `set-key`, `get-key`, `delete-key`, `list-keys`
- **Enhanced commands**: All file operations support `--no-keyring` flag
- **Improved status reporting**: Shows key source (keyring vs environment variable)
- **Python API extensions**: All functions support `use_keyring` parameter
- **Production/CI compatibility**: Graceful fallback to environment variables

### 0.1.0
- Initial release
- Basic encryption/decryption functionality
- CLI with all core commands
- Python API with context managers and decorators
- Profile support
- Shell integration
- CI/CD safety features with quiet modes and secret masking