evcc-io/docs/agents/api-security.md
Michael Geers 4c907c5afa
Merge commit from fork
* Config: require admin password for script plugins

* refactor
2026-06-24 15:45:15 +02:00

126 lines
5.6 KiB
Markdown

# API Security & Authentication
How evcc authenticates HTTP requests and how endpoints are classified by
sensitivity.
## Threat Model
evcc is designed for use within a trusted home network. The auth layer
protects credential management, configuration changes, and system operations
(logs, backup/restore/reset, shutdown). Read-only state and basic charging
controls are intentionally unauthenticated.
## Auth Modes
| Mode | Trigger | Behavior |
|------------|-----------------------|---------------------------------------------------|
| `Enabled` | default | password required; JWT or API key accepted |
| `Disabled` | `--disable-auth` flag | all auth checks skipped |
| `Locked` | demo mode | mutating endpoints return 403; reads still work |
Mode is fixed at startup. The frontend mirrors the mode so admin actions can
be greyed out and a banner shown.
## Endpoint Sensitivity Tiers
Three tiers, by what the caller has to prove:
**Public.** No auth. State, loadpoint controls, login. Anyone on the
network can read and operate.
**Secure.** Requires a valid session: either the auth cookie (browser, JWT)
or an API key in the `Authorization: Bearer …` header (automation). Used
for configuration and system administration.
**Critical.** Secure plus an additional admin-password check inside the
handler. Used for destructive or credential-scoped operations.
For some Critical endpoints (backup, restore, reset) the password check is
**skipped when the caller is authenticated via API key**, so unattended automation
doesn't need to embed the admin password. For credential-management
endpoints (rotate API key, change admin password) the password check is
**strict**: a leaked API key must not be able to rotate itself or change
the admin password.
Disabling auth short-circuits all checks.
## Sessions
Two transports, no overlap:
- Browsers use a session cookie (JWT, 90-day TTL, issued on login).
- Automation uses an API key in the `Authorization: Bearer …` header.
API keys are random alphanumeric strings prefixed `evcc_`. The prefix makes
leaked keys recognizable to secret-scanning tools.
A single API key per installation; regenerating replaces the previous one.
Plaintext is shown to the user **once** at generation time and cannot be
retrieved afterwards.
## Credential Storage
Admin password and API key are stored as bcrypt hashes. The JWT signing
secret is a per-installation random value. Plaintext credentials are never
persisted.
Removing the admin password (CLI recovery) also clears the JWT secret and the
API key, which invalidates all outstanding sessions and any previously-issued
API key. Regenerating the API key replaces the stored hash; the previous key
stops working immediately.
## API Key Lifecycle
Two operations:
- **Status.** Whether a key is configured. Secure tier; never returns
plaintext.
- **Regenerate.** Critical tier, strict password check. Returns the new
plaintext key exactly once.
There is no delete operation: regenerating and discarding the new key
achieves the same effect (the previous key stops working immediately).
## Endpoint Matrix
| Endpoint category | Tier | Additional Requirements |
|----------------------------------------------|-----------|------------------------------------|
| State / read-only / basic charging control | Public | |
| Set or update admin password | Public | admin password |
| Configuration | Secure | |
| Configuration embedding a script plugin | Critical | api key or admin password |
| System: logs, cache, shutdown | Secure | |
| API key status | Secure | |
| System: backup / restore / reset | Critical | api key or admin password |
| API key regenerate | Critical | admin password |
Device test, create, and update (`/api/config/test/{class}` and `/api/config/devices/{class}`)
instantiate a config immediately, so a `script` plugin in the payload runs a shell command on the
server. Because that command could read credentials a session is not otherwise allowed to see (for
example the contents of the database), these requests are treated as Critical when the config embeds
a script plugin, at any nesting depth. A session caller must supply the admin password in the
`X-Admin-Password` header; an API-key caller passes without it. The `go` (yaegi) and `js` (otto)
plugins are excluded: their interpreters are sandboxed to pure computation and cannot read files,
spawn processes, or open network connections.
**Public** endpoints accept any caller. **Secure** endpoints require a
valid session (cookie or API key). **Critical** endpoints require extra
authentication in the form of an admin password (or, for some, an API
key).
## Frontend
Auth management lives under **General Config → Security**, which links to
two sub-flows: change admin password, and manage the API key. The API key
flow has a reveal view that shows the plaintext exactly once with a
copy-to-clipboard link.
When auth is disabled, the security modals show a warning banner and
disable all action buttons. This is UI-only; the backend still accepts the
underlying calls so legitimate automation against a disabled-auth instance
keeps working.
## OpenAPI
The OpenAPI spec declares two security schemes (cookie and bearer);
protected operations accept either.