Metadata-Version: 2.4
Name: wg-api-client
Version: 0.3.0
Summary: WireGuard Configuration API Client
Home-page: https://github.com/tiiuae/wg-api-client-lib
Author: TII UAE
Author-email: info@tii.ae
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
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: Topic :: System :: Networking
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests<3.0.0,>=2.25.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# wg-api-client

Python client library and CLI for the WireGuard Configuration
Distribution API. Version 0.3.0.

## Installation

Requires Python 3.9 or later.

```bash
pip install wg-api-client
```

From source:

```bash
git clone https://github.com/tiiuae/wg-api-client-lib.git
cd wg-api-client-lib
pip install -e .
```

Key generation requires `wireguard-tools`:

```bash
sudo apt install -y wireguard-tools
```

Runtime dependency: `requests >=2.25.0, <3.0.0`.

## Configuration

Settings are read from environment variables, CLI flags, or a
config file. No credentials or URLs are hardcoded.

### Environment variables

| Variable            | Required | Description                                    |
|---------------------|----------|------------------------------------------------|
| `WG_API_URL`        | yes      | API base URL (e.g. `https://host:8080/api/v1`) |
| `WG_API_CREDENTIAL` | yes      | SHA-256 hashed credential                      |
| `WG_API_CA_CERT`    | no       | Path to CA certificate bundle for TLS          |

### Config file

Stored at `~/.wg_api_config` (permissions `0600`). Created on
first successful `auth`. Uses INI format with `[General]` and
`[Auth]` sections.

Generate a credential hash:

```bash
echo -n "your-secret" | sha256sum | awk '{print $1}'
```

## CLI Usage

```text
wg-api-client [global flags] <command> [command flags]
```

### Global Flags

| Flag                          | Env variable        | Description                               |
|-------------------------------|---------------------|-------------------------------------------|
| `--api-url URL`               | `WG_API_URL`        | API base URL                              |
| `--hashed-credential HASH`    | `WG_API_CREDENTIAL` | Hashed credential                         |
| `--ca-cert PATH`              | `WG_API_CA_CERT`    | CA certificate bundle                     |
| `--config-file PATH`          |                     | Config file (default: `~/.wg_api_config`) |
| `--output-format {text,json}` |                     | Output format (default: `text`)           |

### Commands

#### auth

Authenticate and store the JWT token in the config file.

```bash
wg-api-client auth
```

#### get-config

Register a device and write a WireGuard configuration file.

```bash
wg-api-client get-config \
  --cluster-id CLUSTER_ID \
  --role node \
  --output wg.conf
```

| Flag            | Description                                                   |
|-----------------|---------------------------------------------------------------|
| `--cluster-id`  | Cluster to join (required)                                    |
| `--role`        | Device role: `node`, `controller`, or `uxu` (default: `node`) |
| `--device-id`   | Custom device ID (auto-generated from hardware if omitted)    |
| `--public-key`  | WireGuard public key (generated if omitted)                   |
| `--output`      | Output file path (default: `wg.conf`)                         |
| `--allowed-ips` | Additional allowed IP ranges (repeatable)                     |
| `--table`       | Routing table for the WireGuard interface                     |
| `--listen-port` | Listen port for the WireGuard interface                       |

#### register-preauth

Register a device using a pre-authorized key (no JWT needed).

```bash
wg-api-client register-preauth \
  --preauth-key KEY \
  --output wg.conf
```

The preauth key determines the cluster and role automatically.
Device ID and keys are generated if not provided.

#### rotate-key

Rotate a device's WireGuard public key.

```bash
wg-api-client rotate-key DEVICE_ID --new-public-key KEY
```

Generates a new key pair if `--new-public-key` is not provided.

#### list-devices

List all registered devices (admin). Supports `--status` filter.

```bash
wg-api-client list-devices
wg-api-client list-devices --status active
wg-api-client list-devices --status pending
```

#### get-device / delete-device

```bash
wg-api-client get-device DEVICE_ID
wg-api-client delete-device DEVICE_ID --dry-run
wg-api-client delete-device DEVICE_ID
```

#### delete-all-devices

```bash
wg-api-client delete-all-devices --confirm
```

#### bulk-register

Bulk register devices from a JSON file (admin).

```bash
wg-api-client bulk-register devices.json
```

JSON file format:

```json
[
  {"device_id": "node-01", "role": "node", "public_key": "...", "cluster_id": "..."},
  {"device_id": "node-02", "role": "node", "public_key": "...", "cluster_id": "..."}
]
```

#### Cluster Management

```bash
wg-api-client create-cluster production
wg-api-client list-clusters
wg-api-client get-cluster CLUSTER_ID
wg-api-client delete-cluster CLUSTER_ID
wg-api-client get-controller CLUSTER_ID
wg-api-client list-cluster-devices CLUSTER_ID
wg-api-client get-uxu
```

#### Pre-Auth Key Management

```bash
wg-api-client create-preauth-key \
  --cluster-id CLUSTER_ID \
  --role node \
  --reusable

wg-api-client list-preauth-keys
wg-api-client delete-preauth-key KEY_ID
```

| Flag               | Description                                |
|--------------------|--------------------------------------------|
| `--cluster-id`     | Cluster for the key (required)             |
| `--role`           | Device role: `node`, `controller`, `uxu`   |
| `--reusable`       | Allow multiple uses                        |
| `--expires-in`     | Expiry in hours                            |

#### Credential Management

```bash
wg-api-client add-credential \
  --hashed-credential HASH \
  --role user \
  --allowed-device-roles controller node
```

`--allowed-device-roles` restricts which device roles this
credential can register. Omit for no restriction.

#### health / version

```bash
wg-api-client health
wg-api-client version
```

#### completion

```bash
eval "$(wg-api-client completion bash)"
eval "$(wg-api-client completion zsh)"
```

## Device Roles

| Role         | Description                             | Default |
|--------------|-----------------------------------------|---------|
| `node`       | Spoke device, can only reach controller | yes     |
| `controller` | Hub device, one per cluster             | no      |
| `uxu`        | Operator device, global access, unique  | no      |

## Python Library

### Basic usage

```python
from wg_api_client import WireGuardAPI, WireGuardHelper
from wg_api_client.unique_id import get_unique_device_id

api = WireGuardAPI(
    api_url="https://vpn.example.com/api/v1",
    hashed_credential="your-sha256-hash",
)

success, message = api.authenticate()

private_key, public_key = WireGuardHelper.generate_keypair()
device_id = get_unique_device_id()

success, config_data = api.request_wireguard_config(
    device_id=device_id,
    role="node",
    public_key=public_key,
    cluster_id="your-cluster-id",
)

if success:
    WireGuardHelper.create_client_config(
        config_data, "wg.conf", private_key=private_key
    )
```

### Register with pre-auth key

```python
api = WireGuardAPI(api_url="https://vpn.example.com/api/v1")

success, config_data = api.request_config_with_preauth(
    device_id="drone-01",
    public_key=public_key,
    preauth_key="your-preauth-key",
)
```

No credential or authentication needed. The preauth key determines
the cluster and role.

### Key rotation

```python
success, data = api.rotate_key("drone-01", new_public_key)
```

### Bulk registration

```python
devices = [
    {"device_id": "n1", "role": "node", "public_key": k1, "cluster_id": cid},
    {"device_id": "n2", "role": "node", "public_key": k2, "cluster_id": cid},
]
success, results = api.bulk_register(devices)
```

### Pre-auth key management

```python
success, key_data = api.create_preauth_key(
    cluster_id="...", role="node", reusable=True
)
preauth_key = key_data["key"]  # shown only once

success, keys = api.list_preauth_keys()
success, msg = api.delete_preauth_key(key_id)
```

### Scoped credentials

```python
success, cred = api.add_credential(
    "sha256-hash", "user",
    allowed_device_roles=["controller"],
)
```

### Factory from environment

```python
api = WireGuardAPI.from_env()
```

Reads `WG_API_URL` (required), `WG_API_CREDENTIAL`, and
`WG_API_CA_CERT`.

### Context manager

```python
with WireGuardAPI.from_env() as api:
    api.authenticate()
    success, devices = api.list_devices(status="active")
```

## API Reference

### WireGuardAPI

Constructor parameters:

| Parameter           | Type          | Default    | Description                       |
|---------------------|---------------|------------|-----------------------------------|
| `api_url`           | `str`         | (required) | Base URL for the API              |
| `hashed_credential` | `str`         | `None`     | Pre-hashed credential             |
| `token`             | `str`         | `None`     | Existing JWT token                |
| `verify`            | `bool \| str` | `True`     | TLS verify flag or CA bundle path |
| `timeout`           | `int`         | `10`       | Request timeout in seconds        |
| `max_retries`       | `int`         | `3`        | Retries on 502/503/504            |
| `backoff_factor`    | `float`       | `0.5`      | Exponential backoff factor        |

Methods:

| Method | Auth | Description |
| ------ | ---- | ----------- |
| `health_check()` | none | Check server health |
| `get_version()` | none | Get server version |
| `authenticate()` | cred | Login, obtain JWT |
| `refresh_auth_token()` | token | Refresh JWT |
| `ensure_authenticated()` | auto | Auto-refresh if needed |
| `request_wireguard_config(device_id, role, public_key, cluster_id)` | JWT | Register device |
| `request_config_with_preauth(device_id, public_key, preauth_key)` | preauth | Register with preauth key |
| `rotate_key(device_id, new_public_key)` | JWT | Rotate device key |
| `bulk_register(devices)` | admin | Bulk register devices |
| `list_devices(limit, offset, status)` | admin | List devices |
| `get_device(device_id)` | admin | Get device details |
| `delete_device(device_id)` | admin | Delete a device |
| `delete_all_devices()` | admin | Delete all devices |
| `create_cluster(name)` | admin | Create a cluster |
| `list_clusters(limit, offset)` | admin | List clusters |
| `get_cluster(cluster_id)` | admin | Get cluster details |
| `delete_cluster(cluster_id)` | admin | Delete cluster + devices |
| `get_cluster_controller(cluster_id)` | admin | Get cluster controller |
| `list_cluster_devices(cluster_id, limit, offset)` | admin | List devices in cluster |
| `get_uxu_device()` | admin | Get UXU device |
| `add_credential(hash, role, allowed_device_roles)` | admin | Add credential |
| `create_preauth_key(cluster_id, role, reusable, expires_in_hours)` | admin | Create preauth key |
| `list_preauth_keys()` | admin | List preauth keys |
| `delete_preauth_key(key_id)` | admin | Delete preauth key |
| `from_env()` | -- | Create from env vars |
| `close()` | -- | Close HTTP session |

## Exception Hierarchy

```text
WireGuardAPIError
  +-- AuthenticationError
  +-- APIConnectionError
  +-- TLSError
  +-- APITimeoutError
```

All exceptions accept `message` and `original_error` parameters.

## Security

- No hardcoded credentials or API URLs.
- TLS verification enabled by default; custom CA bundle supported.
- Config and key files written with `0600` permissions.
- Private keys passed explicitly, not stored as class state.
- API response data validated before writing config files.
- URL path parameters are percent-encoded.
- SSL errors classified into actionable messages.
- Retry with exponential backoff on transient HTTP errors.
- Output path validated against directory traversal.

## Development

```bash
pip install -r requirements-dev.txt
pip install -e .
```

### Test

```bash
pytest -v
```

### Lint

```bash
black --check wg_api_client/ tests/
isort --check wg_api_client/ tests/
ruff check .
```

## License

Apache 2.0
