Metadata-Version: 2.4
Name: python-auth-proxy
Version: 0.2.0
Summary: Modular authenticating reverse proxy
Project-URL: Homepage, https://git.private.coffee/kumi/auth-proxy
Project-URL: Bug Tracker, https://git.private.coffee/kumi/auth-proxy/issues
Author-email: Kumi Mitterer <auth-proxy@kumi.email>
License: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Security
Requires-Python: >=3.8
Requires-Dist: aiohttp
Requires-Dist: pyjwt
Requires-Dist: pyyaml
Requires-Dist: requests
Description-Content-Type: text/markdown

# Modular Authenticating Reverse Proxy

A flexible, modular reverse proxy with pluggable authentication mechanisms. Designed to sit behind an internet-facing web server like Nginx or Caddy, this proxy authenticates requests before forwarding them to your backend applications.

## Features

- **Modular Authentication**: Pluggable authentication system with support for external plugins
- **Multiple Authentication Methods**: Support for using multiple auth methods per path
- **Flexible Configuration**: Simple YAML-based configuration
- **Path-Based Rules**: Configure which paths require authentication and which don't
- **Regular Expression Matching**: Powerful path matching with regex support
- **Connection Flexibility**: Support for both TCP and Unix sockets
- **Header Forwarding**: Authentication information is forwarded to the backend via HTTP headers
- **Lightweight**: Focused on authentication, not trying to replace your main web server

## Installation

```bash
# Install from PyPI
pip install python-auth-proxy
```

This will set you up with the `auth-proxy` command, as well as the `auth-proxy-validate` tool for path rule validation. Out of the box, the proxy comes with a single built-in authentication plugin: HTTP Basic Authentication (`basic`).

## Quick Start

1. Create a configuration file:

```yaml
# config.yaml
listen:
  host: 127.0.0.1
  port: 8000

backend:
  scheme: http
  host: localhost
  port: 3000

auth_plugins:
  basic:
    users:
      admin: password123
      user1: secret456

auth:
  default_plugins: [basic]
  default_behavior: "authenticated"

paths:
  - path: "^/api/.*$"
    regex: true
    authenticate: true
    plugins: [basic]

  - path: "^/public/.*$"
    regex: true
    authenticate: false

  - path: "/health"
    authenticate: false
```

2. Run the proxy:

```bash
auth-proxy -c config.yaml
```

## Configuration Options

### Listener Configuration

```yaml
listen:
  # TCP socket
  host: 127.0.0.1 # Optional, defaults to 127.0.0.1
  port: 8000

  # Or Unix socket
  socket: /tmp/auth_proxy.sock
```

### Backend Configuration

```yaml
backend:
  # TCP socket
  scheme: http # Optional, defaults to http
  host: localhost
  port: 3000

  # Or Unix socket
  socket: /tmp/app.sock
```

### Authentication Plugins

You can configure multiple authentication plugins and use them for different paths:

```yaml
# Define all available plugins and their configurations
auth_plugins:
  # Standard plugin instance
  oidc:
    issuer: https://your-oidc-provider.com
    client_id: your-client-id
    client_secret: your-client-secret

  # Named instances of the basic auth plugin
  user-basic:
    type: basic
    users:
      user1: password123
      user2: secret456

  admin-basic:
    type: basic
    users:
      admin: admin-password
      superuser: super-secret
```

### Global Authentication Settings

```yaml
auth:
  # Default plugins to use if not specified in a path
  default_plugins: [oidc, jwt]

  # Default authentication mode: "any" or "all"
  default_mode: "any"

  # Default behavior for paths not matching any rule: "authenticated" or "unauthenticated"
  default_behavior: "authenticated"
```

### Path Rules and Precedence

Path rules are processed in the order they appear in the configuration. The first matching rule takes precedence:

```yaml
paths:
  # More specific rule first
  - path: "^/api/public/.*$"
    regex: true
    authenticate: false

  # More general rule second
  - path: "^/api/.*$"
    regex: true
    authenticate: true
    plugins: [oidc]
```

In this example, paths starting with `/api/public/` will not require authentication, while all other API paths will require OIDC authentication.

### Regular Expression Path Matching

For more flexible path matching, you can use regular expressions:

```yaml
paths:
  # Match all paths that start with /api/ followed by anything
  - path: "^/api/.*$"
    regex: true
    authenticate: true
    plugins: [jwt]

  # Match specific pattern
  - path: "^/users/[0-9]+/profile$"
    regex: true
    authenticate: true
    plugins: [oidc]
```

When using regex paths, set `regex: true` in the path rule.

### Multiple Authentication Methods Per Path

You can specify multiple authentication plugins for a path:

```yaml
paths:
  # Allow access if either OIDC or JWT authentication succeeds
  - path: "^/api/.*$"
    regex: true
    authenticate: true
    plugins: [oidc, jwt]
    mode: "any" # "any" (default) or "all"

  # Require both OIDC and Basic authentication to succeed
  - path: "^/secure/.*$"
    regex: true
    authenticate: true
    plugins: [oidc, basic]
    mode: "all"
```

The `mode` parameter determines how multiple plugins are evaluated:

- `any`: The request is authenticated if any plugin succeeds (OR logic)
- `all`: The request is authenticated only if all plugins succeed (AND logic)

### Named Plugin Instances

You can create multiple instances of the same plugin type with different configurations:

```yaml
auth_plugins:
  # Simple case - plugin name matches plugin type
  oidc:
    issuer: https://accounts.google.com
    client_id: your-client-id
    client_secret: your-client-secret

  # Named instances must specify their type
  user-basic:
    type: basic
    users:
      user1: password123
      user2: secret456

  admin-basic:
    type: basic
    users:
      admin: admin-password
      superuser: super-secret
```

This allows you to have multiple configurations of the same plugin type for different purposes.

### Default Behavior

You can specify the default behavior for paths that don't match any rule:

```yaml
auth:
  default_behavior: "authenticated" # or "unauthenticated"
```

This setting determines whether paths not matching any rule require authentication by default:

- `authenticated`: Paths not matching any rule will require authentication using the default plugins
- `unauthenticated`: Paths not matching any rule will not require authentication

If not specified, the default is `authenticated`.

## Available Authentication Plugins

The proxy currently comes with one built-in authentication plugin:

1. **basic**: HTTP Basic Authentication

To list all available authentication plugins (including installed third-party plugins):

```bash
auth-proxy --list-plugins
```

## Path Rule Validation

To help understand which rule would match a given URL path, use the validation tool:

```bash
auth-proxy-validate -c config.yaml /api/v1/users
```

Example output:

```
Path: /api/v1/users
Matching rule #3: ^/api/v1/.*$
Authentication required: True
Authentication plugins: oidc, user-basic
Authentication mode: any

Full matching rule:
  path: ^/api/v1/.*$
  regex: True
  authenticate: True
  plugins: ['oidc', 'user-basic']
  mode: any
```

This tool is useful for:

- Testing your path rules
- Debugging authentication issues
- Understanding rule precedence

## Creating a Custom Authentication Plugin

You can create your own authentication plugins for auth-proxy:

1. Create a new Python package for your plugin with a `pyproject.toml`:

```toml
# pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "auth-proxy-my-plugin"
version = "0.1.0"
description = "My custom authentication plugin for auth-proxy"
requires-python = ">=3.8"
dependencies = [
    "auth-proxy",
]

[project.entry-points."auth_proxy.plugins"]
my-plugin = "auth_proxy_my_plugin.plugin:MyAuthPlugin"
```

2. Implement your plugin class:

```python
# auth_proxy_my_plugin/plugin.py
from typing import Dict, Any
from auth_proxy.auth_plugins.base import AuthPlugin

class MyAuthPlugin(AuthPlugin):
    def __init__(self, config: Dict[str, Any]):
        super().__init__(config)
        # Initialize with your config parameters
        self.some_setting = config.get('some_setting')

    def authenticate(self, request_headers: Dict[str, str], path: str) -> bool:
        # Implement your authentication logic
        # Return True if authenticated, False otherwise
        return True

    def get_auth_headers(self, request_headers: Dict[str, str], path: str) -> Dict[str, str]:
        # Return headers to add to the proxied request
        return {'X-Auth-User': 'example-user'}
```

3. Install your plugin:

```bash
pip install -e .
```

4. Configure the proxy to use your plugin:

```yaml
auth_plugins:
  my-plugin:
    some_setting: some_value

paths:
  - path: "^/api/.*$"
    regex: true
    authenticate: true
    plugins: [my-plugin]
```

## Plugin API

Authentication plugins must implement the `AuthPlugin` base class with two methods:

### authenticate(request_headers, path)

Determines if the request is authenticated.

- **Parameters**:
  - `request_headers` (Dict[str, str]): The HTTP headers from the incoming request
  - `path` (str): The request path
- **Returns**:
  - `bool`: True if the request is authenticated, False otherwise

### get_auth_headers(request_headers, path)

Returns headers to add to the proxied request.

- **Parameters**:
  - `request_headers` (Dict[str, str]): The HTTP headers from the incoming request
  - `path` (str): The request path
- **Returns**:
  - `Dict[str, str]`: Headers to add to the proxied request

## Full Configuration Example

```yaml
# config.yaml
listen:
  host: 127.0.0.1
  port: 8000

backend:
  scheme: http
  host: localhost
  port: 3000

# Define all available plugins and their configurations
auth_plugins:
  oidc:
    issuer: https://accounts.google.com
    client_id: your-client-id
    client_secret: your-client-secret

  user-basic:
    type: basic
    users:
      user1: password123
      user2: secret456

  admin-basic:
    type: basic
    users:
      admin: admin-password
      superuser: super-secret

  web-jwt:
    type: jwt
    secret: your-web-secret-key
    algorithm: HS256
    audience: web-app

  mobile-jwt:
    type: jwt
    secret: your-mobile-secret-key
    algorithm: HS256
    audience: mobile-app

# Global authentication settings
auth:
  default_plugins: [oidc]
  default_mode: "any"
  default_behavior: "authenticated"

# Path rules with plugin selection (processed in order)
paths:
  # Specific public API endpoints (must come before general API rule)
  - path: "^/api/v1/public/.*$"
    regex: true
    authenticate: false

  # Admin API endpoints (specific before general)
  - path: "^/api/v1/admin/.*$"
    regex: true
    authenticate: true
    plugins: [admin-basic]

  # General API endpoints (after more specific rules)
  - path: "^/api/v1/.*$"
    regex: true
    authenticate: true
    plugins: [oidc, user-basic]
    mode: "any"

  # Mobile API endpoints
  - path: "^/api/mobile/.*$"
    regex: true
    authenticate: true
    plugins: [mobile-jwt]

  # Admin dashboard
  - path: "^/admin/.*$"
    regex: true
    authenticate: true
    plugins: [admin-basic]

  # Super secure endpoints - require both OIDC and admin basic auth
  - path: "^/secure/.*$"
    regex: true
    authenticate: true
    plugins: [oidc, admin-basic]
    mode: "all"

  # Public resources
  - path: "^/public/.*$"
    regex: true
    authenticate: false

  # Static assets
  - path: "^/assets/.*$"
    regex: true
    authenticate: false

  # Health check
  - path: "/health"
    authenticate: false

  # Metrics endpoint
  - path: "/metrics"
    authenticate: true
    plugins: [admin-basic]
```

## Deployment

### Docker

The proxy can be easily deployed using Docker:

```bash
# Pull the image
docker pull git.private.coffee/kumi/auth-proxy

# Run with a config file
docker run -v $(pwd)/config.yaml:/config/config.yaml -p 8000:8000 git.private.coffee/kumi/auth-proxy
```

Or using Docker Compose:

```yaml
# docker-compose.yml
version: "3"

services:
  auth-proxy:
    image: kumitterer/auth-proxy
    ports:
      - "8000:8000"
    volumes:
      - ./config.yaml:/config/config.yaml
```

### Behind Nginx or Caddy

The auth-proxy is designed to run behind a reverse proxy like Nginx or Caddy.

Example Nginx configuration:

```nginx
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

Example Caddy configuration:

```
example.com {
    reverse_proxy localhost:8000
}
```

## Command Line Options

```
usage: auth-proxy [-h] [-c CONFIG] [-v] [--list-plugins]

Modular Authenticating Reverse Proxy

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG, --config CONFIG
                        Path to config file (default: config.yaml)
  -v, --verbose         Enable verbose logging
  --list-plugins        List available authentication plugins
```

## Security Considerations

- The proxy does not handle TLS termination - use a front-facing web server for this
- Store sensitive configuration (like secrets) securely
- Review the authentication plugins you use for security best practices
- Consider using environment variables for secrets in production
- Always put more specific path rules before more general ones to avoid security bypasses

## Development

### Prerequisites

- Python 3.8 or higher
- pip

### Setting up the development environment

```bash
# Clone the repository
git clone https://git.private.coffee/kumi/auth-proxy.git
cd auth-proxy

# Create a virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install in development mode
pip install -e .

# Install development dependencies
pip install pytest black isort mypy
```

### Running tests

```bash
pytest
```

### Code formatting and linting

```bash
# Format code
black auth_proxy tests

# Sort imports
isort auth_proxy tests

# Type checking
mypy auth_proxy
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the [LICENSE](License) file for details.
