Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,5 @@ These are hard constraints enforced by the project design. PRs that violate them
2. **All business logic lives in `ShieldEngine`.** Middleware and decorators are transport layers only.
3. **Decorators stamp `__shield_meta__` and do nothing else** — no logic at request time.
4. **`engine.check()` is the single chokepoint** — never duplicate the check logic elsewhere.
5. **Backends must implement the full `ShieldBackend` ABC** — no partial implementations.
5. **Backends must implement the full `ShieldBackend` ABC** — no partial implementations. If a method is not supported (e.g. `subscribe()` on `FileBackend`), it raises `NotImplementedError`. `ShieldEngine.start()` catches this internally and skips the listener — the engine handles the fallback, not the caller.
6. **Fail-open** — if the backend is unreachable, the request passes through. Shield never takes down an API.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div align="center">
<img src="api-shield-logo.svg" alt="API Shield" width="600"/>

<p><strong>Route(API) lifecycle management for ASGI Python web frameworks — maintenance mode, environment gating, deprecation, rate limiting, feature flags, admin panels, and more. No restarts required.</strong></p>
<p><strong>Feature flags and runtime control for Python APIs — rollouts, rate limits, manage maintenance windows across single ASGI services or a multi-service fleet without redeploying.</strong></p>

<a href="https://pypi.org/project/api-shield"><img src="https://img.shields.io/pypi/v/api-shield?color=F59E0B&label=pypi&cacheSeconds=300" alt="PyPI"></a>
<a href="https://pypi.org/project/api-shield"><img src="https://img.shields.io/pypi/pyversions/api-shield?color=F59E0B" alt="Python versions"></a>
Expand Down
2 changes: 1 addition & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

---

## [Unreleased]
## [0.8.0]

### Fixed

Expand Down
7 changes: 5 additions & 2 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ These constraints are enforced at review time. PRs that violate them will be ask
Every request path must flow through `engine.check()`. Never duplicate the check logic in middleware, a dependency, or a decorator.

5. **Backends implement the full `ShieldBackend` ABC.**
No partial implementations. If a method is not supported (e.g. `subscribe()` on `FileBackend`), it raises `NotImplementedError` and callers handle the fallback.
No partial implementations. If a method is not supported (e.g. `subscribe()` on `FileBackend`), it raises `NotImplementedError`. `ShieldEngine.start()` catches this internally and skips the listener — the engine handles the fallback, not the caller.

6. **Fail-open on backend errors.**
If `backend.get_state()` raises, `engine.check()` logs the error and lets the request through. Shield must never take down an API because its own storage is temporarily unavailable.
Expand All @@ -191,11 +191,14 @@ shield/
├── core/ # Zero framework dependencies — engine, models, backends
│ ├── engine.py # ShieldEngine — all business logic lives here
│ ├── models.py # RouteState, AuditEntry, RateLimitPolicy, …
│ ├── backends/ # MemoryBackend, FileBackend, RedisBackend
│ ├── backends/ # MemoryBackend, FileBackend, RedisBackend, ShieldServerBackend
│ ├── rate_limit/ # Rate limiting subsystem
│ └── scheduler.py # asyncio-based maintenance window scheduler
├── fastapi/ # FastAPI adapter — middleware, decorators, router, OpenAPI
├── admin/ # Unified admin ASGI app (dashboard UI + REST API + auth)
├── server/ # ShieldServer — standalone control plane for multi-service deployments
├── sdk/ # ShieldSDK — service-side client that connects to a Shield Server via SSE
├── adapters/ # Framework adapter helpers (ASGI base, future adapter scaffolding)
├── dashboard/ # HTMX/Jinja2 templates and static assets
│ ├── templates/ # Edit these, then run `npm run build:css`
│ └── static/ # shield.min.css lives here — commit after rebuilding
Expand Down
110 changes: 110 additions & 0 deletions docs/guides/shield-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,81 @@ shield global disable --reason "emergency maintenance"
shield global enable
```

---

## Global maintenance in multi-service environments

`shield global enable` and `shield global disable` operate on the Shield Server and affect **every route across every connected service** simultaneously. Use this for fleet-wide outages or deployments where all services must be taken offline at once.

```bash
# Block all routes on all services
shield global enable --reason "Deploying v3" --exempt /health --exempt /ready

# Restore all routes
shield global disable
```

---

## Per-service maintenance

Per-service maintenance puts **all routes of one service** into maintenance mode without affecting other services. SDK clients with a matching `app_id` receive the sentinel over SSE and treat all their routes as in maintenance — no individual route changes needed.

### From the engine (programmatic)

```python
# Put payments-service into maintenance
await engine.enable_service_maintenance(
"payments-service",
reason="DB migration",
exempt_paths=["/health"],
)

# Restore
await engine.disable_service_maintenance("payments-service")

# Inspect
cfg = await engine.get_service_maintenance("payments-service")
print(cfg.enabled, cfg.reason)
```

### From the REST API

```http
POST /api/services/payments-service/maintenance/enable
Authorization: Bearer <token>
Content-Type: application/json

{"reason": "DB migration", "exempt_paths": ["/health"]}
```

```http
POST /api/services/payments-service/maintenance/disable
```

### From the CLI

`shield sm` and `shield service-maintenance` are aliases for the same command group:

```bash
# Enable — all routes of payments-service return 503
shield sm enable payments-service --reason "DB migration"
shield sm enable payments-service --reason "Upgrade" --exempt /health

# Check current state
shield sm status payments-service

# Restore
shield sm disable payments-service
```

### From the dashboard

Open the Routes page and select the service using the service filter. A **Service Maintenance** card appears with Enable and Disable controls.

!!! tip "Use `@force_active` on health checks"
Health and readiness endpoints should always be decorated with `@force_active` so they are never affected by global or per-service maintenance mode.

### Useful discovery commands

```bash
Expand Down Expand Up @@ -227,6 +302,41 @@ Use a **different Redis database** (or a different Redis instance) from the one

---

## Per-service rate limits

A per-service rate limit applies a single policy to **all routes of one service** without configuring each route individually. It is checked after the all-services global rate limit and before any per-route limit:

```
global maintenance -> service maintenance -> global rate limit -> service rate limit -> per-route rate limit
```

Configure it with the `shield srl` CLI (also available as `shield service-rate-limit`):

```bash
# Cap all payments-service routes at 1000 per minute per IP
shield srl set payments-service 1000/minute

# With options
shield srl set payments-service 500/minute --algorithm sliding_window --key ip
shield srl set payments-service 2000/hour --burst 50 --exempt /health --exempt GET:/metrics

# Inspect, pause, reset counters, remove
shield srl get payments-service
shield srl disable payments-service
shield srl enable payments-service
shield srl reset payments-service
shield srl delete payments-service
```

From the dashboard: open the **Rate Limits** tab and select a service using the service filter. A **Service Rate Limit** card appears above the policies table with controls to configure, pause, reset, and remove the policy.

The service rate limit uses the same `GlobalRateLimitPolicy` model as the all-services global rate limit. It is stored in the backend under a sentinel key and survives Shield Server restarts on `FileBackend` or `RedisBackend`.

!!! note "Independent layers"
The all-services global rate limit (`shield grl`) and the per-service rate limit (`shield srl`) are independent. A request must pass both before reaching per-route checking. You can configure one, both, or neither.

---

## SSE event types

The Shield Server's `GET /api/sdk/events` stream carries two event types:
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
!!! warning "Early Access: your feedback shapes the roadmap"
`api-shield` is fully functional and ready to use. We are actively building on a solid foundation and would love to hear from you. If you have feedback, feature ideas, or suggestions, **[open an issue on GitHub](https://github.com/Attakay78/api-shield/issues)**. Every voice helps make the library better for everyone.

**Route(API) lifecycle management for ASGI Python web frameworks: maintenance mode, environment gating, deprecation, rate limiting, admin panels, and more. No restarts required.**
**Feature flags and runtime control for Python APIs — rollouts, rate limits, manage maintenance windows across single ASGI services or a multi-service fleet without redeploying.**

Most "route lifecycle management" tools are blunt instruments: shut everything down or nothing at all. `api-shield` treats each route as a first-class entity with its own lifecycle. State changes take effect immediately through middleware, with no redeployment and no server restart.
Most "runtime control management" tools are blunt instruments: shut everything down or nothing at all. `api-shield` treats each route as a first-class entity with its own lifecycle. State changes take effect immediately, with no redeployment and no server restart.

---

Expand Down
165 changes: 164 additions & 1 deletion docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,75 @@ shield global exempt-remove /monitoring/ping

---

## `shield sm` / `shield service-maintenance`

`shield sm` and `shield service-maintenance` are aliases for the same command group. Puts all routes of one service into maintenance mode without affecting other services. The affected SDK client's `app_id` must match the service name.

```bash
shield sm enable payments-service --reason "DB migration"
shield service-maintenance enable payments-service # identical
```

### `shield sm status`

Show the current maintenance configuration for a service.

```bash
shield sm status <service>
```

```bash
shield sm status payments-service
```

**Example output:**

```
Service maintenance (payments-service): ON
Reason : DB migration
Include @force_active: no
Exempt paths :
• /health
```

---

### `shield sm enable`

Block all routes of a service immediately. Routes return `503` until `shield sm disable` is called.

```bash
shield sm enable <service>
```

```bash
shield sm enable payments-service --reason "DB migration"
shield sm enable payments-service --reason "Upgrade" --exempt /health --exempt GET:/ready
shield sm enable orders-service --include-force-active
```

| Option | Description |
|---|---|
| `--reason TEXT` | Shown in every 503 response while maintenance is active |
| `--exempt PATH` | Exempt a path from the block (repeatable). Use bare `/health` or `GET:/health`. |
| `--include-force-active` | Also block `@force_active` routes. Use with care. |

---

### `shield sm disable`

Restore all routes of a service to their individual states.

```bash
shield sm disable <service>
```

```bash
shield sm disable payments-service
```

---

## Rate limit commands

`shield rl` and `shield rate-limits` are aliases for the same command group — use whichever you prefer. Requires `api-shield[rate-limit]` on the server.
Expand Down Expand Up @@ -450,11 +519,105 @@ shield grl disable

---

## `shield srl` / `shield service-rate-limit`

`shield srl` and `shield service-rate-limit` are aliases for the same command group. Manages the rate limit policy for a single service — applies to all routes of that service. Requires `api-shield[rate-limit]` on the server.

```bash
shield srl get payments-service
shield service-rate-limit get payments-service # identical
```

### `shield srl get`

Show the current rate limit policy for a service, including limit, algorithm, key strategy, burst, exempt routes, and enabled state.

```bash
shield srl get <service>
```

```bash
shield srl get payments-service
```

---

### `shield srl set`

Configure the rate limit for a service. Creates a new policy or replaces the existing one.

```bash
shield srl set <service> <limit>
```

```bash
shield srl set payments-service 1000/minute
shield srl set payments-service 500/minute --algorithm sliding_window --key ip
shield srl set payments-service 2000/hour --burst 50 --exempt /health --exempt GET:/metrics
```

| Option | Description |
|---|---|
| `--algorithm TEXT` | Counting algorithm: `fixed_window`, `sliding_window`, `moving_window`, `token_bucket` |
| `--key TEXT` | Key strategy: `ip`, `user`, `api_key`, `global` |
| `--burst INT` | Extra requests above the base limit |
| `--exempt TEXT` | Exempt route (repeatable). Bare path (`/health`) or method-prefixed (`GET:/metrics`) |

---

### `shield srl delete`

Remove the service rate limit policy entirely.

```bash
shield srl delete <service>
```

```bash
shield srl delete payments-service
```

---

### `shield srl reset`

Clear all counters for the service. The policy is kept; clients get their full quota back on the next request.

```bash
shield srl reset <service>
```

```bash
shield srl reset payments-service
```

---

### `shield srl enable`

Resume a paused service rate limit policy.

```bash
shield srl enable <service>
```

---

### `shield srl disable`

Pause the service rate limit without removing it. Per-route policies continue to enforce normally.

```bash
shield srl disable <service>
```

---

## Audit log

### `shield log`

Display the audit log, newest entries first. The `Status` column shows `old > new` for route state changes and a coloured action label for rate limit policy changes (including global RL actions such as `global set`, `global reset`, `global enabled`, `global disabled`).
Display the audit log, newest entries first. The `Status` column shows `old > new` for route state changes and a coloured action label for rate limit policy changes (including global RL actions such as `global set`, `global reset`, `global enabled`, `global disabled`, and service RL actions such as `svc set`, `svc reset`, `svc enabled`, `svc disabled`). The `Path` column shows human-readable labels for sentinel-keyed entries: `[Global Maintenance]`, `[Global Rate Limit]`, `[{service} Maintenance]`, and `[{service} Rate Limit]`.

```bash
shield log # page 1, 20 rows
Expand Down
Loading
Loading