docs: add stelloauth addon design
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
# Home Assistant Stelloauth Add-on Design
|
||||
|
||||
## Purpose
|
||||
|
||||
Build a maintainable Home Assistant OS custom add-on repository that runs
|
||||
Stelloauth locally for the Stellantis Vehicles integration. The add-on replaces
|
||||
the shared OAuth worker without changing Home Assistant OS, bypassing
|
||||
Supervisor, or installing Docker Compose.
|
||||
|
||||
The finished repository must install from Home Assistant's add-on store, start
|
||||
and stop through Supervisor, survive restarts, keep CloakBrowser's CDP endpoint
|
||||
private, and provide one exact Login service URL for the integration.
|
||||
|
||||
## Verified target environment
|
||||
|
||||
The target was inspected read-only in Home Assistant on 2026-09-24:
|
||||
|
||||
- Home Assistant OS 18.2
|
||||
- Home Assistant Core 2026.9.3
|
||||
- Home Assistant Supervisor 2026.09.2
|
||||
- Container architecture `amd64`
|
||||
- CPU architecture `x86_64`
|
||||
- Machine type `ova` on KVM
|
||||
- Home Assistant host address `192.168.1.20`
|
||||
- Approximately 4 GB free disk space at inspection time
|
||||
|
||||
The add-on will also declare and test `aarch64`, because both required upstream
|
||||
images publish Linux arm64 manifests.
|
||||
|
||||
## Sources and pinned versions
|
||||
|
||||
The initial add-on version is `0.1.0` and pins these upstream inputs:
|
||||
|
||||
| Component | Version | Source commit or OCI index digest |
|
||||
| --- | --- | --- |
|
||||
| Stelloauth | `v0.6.0` | `367d4f8c02a3b072c59142c49dffc129edc8548b` |
|
||||
| Stelloauth image contract | `v0.6.0` | `sha256:51b2194ec9b80cc484d11c016ec5436a12161277a493afdab7078959966d5aa9` |
|
||||
| CloakBrowser wrapper/image | `0.5.10` | `f04c23da285b3b3d3cf10c8f9d282e7adc1d52ce` / `sha256:2ed5b2d047cbdde22cde7ef1a796526c716aadaa5bccbe1db5ade49282b64a76` |
|
||||
| Go builder | `1.27.1-bookworm` | `sha256:69a7b9788769bec032d238959b61854e9ae87f57be9029ec04e9885fabf99195` |
|
||||
| Stellantis Vehicles integration | `2026.9.4` | `9e0ef96f8fe478af291da4c38c923ada78d0ebf6` |
|
||||
|
||||
The Stelloauth and CloakBrowser OCI indexes contain both `linux/amd64` and
|
||||
`linux/arm64` manifests. The Dockerfile will use index digests so Docker selects
|
||||
the manifest matching the Supervisor build architecture.
|
||||
|
||||
## Architecture decision
|
||||
|
||||
Use one add-on container with two supervised application processes:
|
||||
|
||||
```text
|
||||
Home Assistant Core
|
||||
|
|
||||
| POST http://0031621f-stelloauth:8080/worker
|
||||
v
|
||||
Stelloauth 0.0.0.0:8080
|
||||
|
|
||||
| CDP http://127.0.0.1:9222
|
||||
v
|
||||
CloakBrowser 127.0.0.1:9222
|
||||
```
|
||||
|
||||
A small Python process manager is the container command. Supervisor's Docker
|
||||
init remains enabled and acts as PID 1/subreaper. The manager starts
|
||||
CloakBrowser, proves CDP readiness, starts Stelloauth, monitors both processes,
|
||||
forwards termination, and performs bounded graceful shutdown. If either
|
||||
process exits unexpectedly, the manager stops the other process and exits
|
||||
nonzero so Supervisor can restart the add-on.
|
||||
|
||||
This design keeps both upstream applications as separate processes while
|
||||
giving them one lifecycle and a loopback-only CDP connection.
|
||||
|
||||
### Rejected alternatives
|
||||
|
||||
Two add-ons were rejected because CloakBrowser would have to listen on the
|
||||
shared Supervisor network, installation order and cross-add-on discovery would
|
||||
be more fragile, and shutdown/readiness would span two Supervisor lifecycles.
|
||||
|
||||
A derived prebuilt public image was rejected because the CloakBrowser Binary
|
||||
License prohibits redistributing or repackaging the binary. The repository
|
||||
therefore contains build instructions only. The user's Supervisor obtains the
|
||||
official CloakBrowser image and creates an image solely for internal use.
|
||||
|
||||
A host port enabled by default was rejected because `/worker` accepts MyOpel
|
||||
credentials and has no authentication. The supported internal Supervisor
|
||||
network does not expose the listener on the LAN.
|
||||
|
||||
## Repository structure
|
||||
|
||||
```text
|
||||
repository.yaml
|
||||
README.md
|
||||
LICENSE
|
||||
stelloauth/
|
||||
config.yaml
|
||||
Dockerfile
|
||||
README.md
|
||||
DOCS.md
|
||||
CHANGELOG.md
|
||||
translations/
|
||||
da.yaml
|
||||
en.yaml
|
||||
rootfs/
|
||||
usr/local/bin/addon-supervisor
|
||||
patches/
|
||||
stelloauth-security.patch
|
||||
cloakserve-loopback.patch
|
||||
tests/
|
||||
test_addon_metadata.py
|
||||
test_supervisor.py
|
||||
test_security_patches.py
|
||||
test_runtime.sh
|
||||
```
|
||||
|
||||
No `build.yaml` is used. Current Home Assistant documentation states that the
|
||||
legacy builder no longer reads it. The Dockerfile provides an explicit base
|
||||
image and the required `io.hass.*` labels.
|
||||
|
||||
## Build design
|
||||
|
||||
The Dockerfile has two stages:
|
||||
|
||||
1. A pinned Go builder clones the Stelloauth `v0.6.0` tag, verifies that HEAD is
|
||||
exactly `367d4f8c02a3b072c59142c49dffc129edc8548b`, applies the maintained
|
||||
security patch, runs the relevant Go tests, and builds a static binary.
|
||||
2. The runtime stage derives from the official pinned CloakBrowser `0.5.10`
|
||||
image, applies the MIT-licensed wrapper patch, copies the patched Stelloauth
|
||||
binary and process manager, clears the upstream entrypoint, and starts the
|
||||
manager as the container command.
|
||||
|
||||
The proprietary CloakBrowser Chromium binary is not modified. The official
|
||||
image already downloads it during CloakBrowser's own build and is consumed as
|
||||
an upstream dependency.
|
||||
|
||||
## Supervisor metadata
|
||||
|
||||
`stelloauth/config.yaml` will use:
|
||||
|
||||
- `slug: stelloauth`
|
||||
- `version: 0.1.0`
|
||||
- `arch: [amd64, aarch64]`
|
||||
- `startup: application`
|
||||
- `boot: auto`
|
||||
- `init: true`
|
||||
- no `privileged`, `full_access`, `host_network`, Docker API, devices, or maps
|
||||
- `8080/tcp: null` by default
|
||||
- an HTTP watchdog against `/` on the internal port
|
||||
- no ingress
|
||||
|
||||
Stelloauth's UI uses absolute paths such as `/oauth` and `/configs`, so it does
|
||||
not work correctly beneath Home Assistant's dynamic ingress prefix without a
|
||||
proxy/rewriting layer. Ingress adds no value to the integration's machine to
|
||||
machine worker call and is omitted from version 0.1.0.
|
||||
|
||||
## Networking and Login service URL
|
||||
|
||||
The custom repository URL is:
|
||||
|
||||
```text
|
||||
https://git.radixadm.dk/dennis/homeassistant-stelloauth-addon.git
|
||||
```
|
||||
|
||||
Supervisor derives repository ID `0031621f` from the first eight hexadecimal
|
||||
characters of SHA-1 over the lowercased exact repository URL. With add-on slug
|
||||
`stelloauth`, Home Assistant Core can resolve the add-on as
|
||||
`0031621f-stelloauth` on the internal Supervisor network.
|
||||
|
||||
The exact Stellantis Vehicles value is:
|
||||
|
||||
```text
|
||||
http://0031621f-stelloauth:8080/worker
|
||||
```
|
||||
|
||||
The integration uses this value verbatim and does not append `/worker`.
|
||||
|
||||
For troubleshooting only, the user may set the disabled host mapping to port
|
||||
8080 in the add-on Network panel. The corresponding fallback is:
|
||||
|
||||
```text
|
||||
http://192.168.1.20:8080/worker
|
||||
```
|
||||
|
||||
The documentation will tell the user to disable the host mapping again after
|
||||
testing.
|
||||
|
||||
## Configuration
|
||||
|
||||
The default configuration works without user input:
|
||||
|
||||
```yaml
|
||||
queue_timeout: 60s
|
||||
rate_limit_count: 5
|
||||
rate_limit_duration: 1h
|
||||
```
|
||||
|
||||
The process manager maps these values to:
|
||||
|
||||
- `CLOAK_CDP_URL=http://127.0.0.1:9222`
|
||||
- `CLOAK_MAX_SESSIONS=1`
|
||||
- `CLOAK_QUEUE_TIMEOUT=<queue_timeout>`
|
||||
- `RATE_LIMIT_COUNT=<rate_limit_count>`
|
||||
- `RATE_LIMIT_DURATION=<rate_limit_duration>`
|
||||
- `HTTP_ADDRESS=0.0.0.0`
|
||||
- `PORT=8080`
|
||||
- `METRICS_ADDRESS=127.0.0.1`
|
||||
- `METRICS_PORT=9090`
|
||||
|
||||
`CLOAK_MAX_SESSIONS` is fixed at one because the free CloakBrowser tier allows
|
||||
one concurrent login and the add-on is designed for one household. The queue
|
||||
absorbs overlapping setup/reauth attempts without launching extra browsers.
|
||||
|
||||
No option contains MyOpel credentials, tokens, cookies, or OAuth codes.
|
||||
|
||||
## Security hardening
|
||||
|
||||
Upstream Stelloauth `v0.6.0` cannot be used unmodified because it logs email
|
||||
addresses and full current/redirect URLs. A redirect URL contains the OAuth
|
||||
code. Its worker also accepts an arbitrary authorize URL, creating a
|
||||
browser-based SSRF surface.
|
||||
|
||||
The minimal maintained patch will:
|
||||
|
||||
1. Remove email addresses from `/oauth` and `/worker` log lines.
|
||||
2. Replace full redirect/current/error URLs with fixed event messages.
|
||||
3. Require worker URLs to use HTTPS, the default HTTPS port, and exact host/path
|
||||
combinations for these current Stellantis identity providers:
|
||||
- `idpcvs.citroen.com/am/oauth2/authorize`
|
||||
- `idpcvs.driveds.com/am/oauth2/authorize`
|
||||
- `idpcvs.opel.com/am/oauth2/authorize`
|
||||
- `idpcvs.peugeot.com/am/oauth2/authorize`
|
||||
- `idpcvs.vauxhall.co.uk/am/oauth2/authorize`
|
||||
4. Limit worker request bodies to 64 KiB.
|
||||
5. Use the direct remote address for rate limiting instead of trusting a
|
||||
caller-supplied `X-Forwarded-For` header.
|
||||
|
||||
The CloakBrowser wrapper patch changes the container listener from
|
||||
`0.0.0.0:9222` to `127.0.0.1:9222`. Port 9222 is not declared in Supervisor
|
||||
metadata and cannot be mapped through the add-on UI.
|
||||
|
||||
Only Stelloauth listens on the Supervisor network. Its port is not mapped to
|
||||
the HAOS host by default. No TLS is required for the worker call because the
|
||||
request stays on the private container network; the subsequent Stellantis
|
||||
traffic uses HTTPS.
|
||||
|
||||
## Credential and temporary-data lifecycle
|
||||
|
||||
Email and password exist only in the incoming request body and in process
|
||||
memory while the OAuth flow runs. They are passed to the page through CDP and
|
||||
are not written to `/data`.
|
||||
|
||||
Each OAuth attempt uses a unique CloakBrowser fingerprint and profile under
|
||||
`/tmp/cloakserve`. Stelloauth closes the browser target when the flow ends.
|
||||
CloakBrowser runs with `--idle-timeout=30`, and the process manager removes
|
||||
stale `/tmp/cloakserve` contents before startup. Container shutdown terminates
|
||||
CloakBrowser, which closes remaining Chromium processes.
|
||||
|
||||
No persistent credential, cookie, token, profile, screenshot, HTML dump, or
|
||||
debug dump is created. The only persistent add-on data is Supervisor's
|
||||
`/data/options.json` containing the non-secret options above.
|
||||
|
||||
## Startup, readiness, health, and shutdown
|
||||
|
||||
Startup order:
|
||||
|
||||
1. Validate `/data/options.json` and reject malformed values without echoing
|
||||
its content.
|
||||
2. Remove stale CloakBrowser profiles from `/tmp/cloakserve`.
|
||||
3. Start `cloakserve --idle-timeout=30`.
|
||||
4. Wait with bounded backoff for `GET http://127.0.0.1:9222/`.
|
||||
5. Request `/json/version?fingerprint=addon-readiness` and require a valid
|
||||
`webSocketDebuggerUrl`.
|
||||
6. Close the readiness profile with
|
||||
`POST /fingerprint/addon-readiness/close`.
|
||||
7. Start Stelloauth and wait for `GET http://127.0.0.1:8080/`.
|
||||
8. Enter the process-monitor loop.
|
||||
|
||||
Startup logs use fixed messages such as `Starting CloakBrowser`,
|
||||
`CloakBrowser ready`, `Starting Stelloauth`, and
|
||||
`Stelloauth listening on 0.0.0.0:8080`.
|
||||
|
||||
The Supervisor watchdog checks `GET /`. This proves the Stelloauth listener is
|
||||
alive. The process manager supplies the missing combined health property: if
|
||||
CloakBrowser exits, it immediately stops Stelloauth and exits, causing the
|
||||
watchdog/Supervisor restart path to recover both processes together.
|
||||
|
||||
On SIGTERM or SIGINT, the manager sends SIGTERM to Stelloauth and CloakBrowser,
|
||||
waits up to ten seconds, sends SIGKILL only to remaining children, reaps them,
|
||||
and exits with the original shutdown status.
|
||||
|
||||
## Testing and acceptance evidence
|
||||
|
||||
Automated validation will cover:
|
||||
|
||||
1. YAML syntax and required/current Home Assistant metadata fields.
|
||||
2. Default options matching their schemas.
|
||||
3. Repository ID and documented internal hostname derivation.
|
||||
4. Patch application to the exact pinned upstream commits.
|
||||
5. Unit tests for options, startup ordering, readiness, child failure, signal
|
||||
forwarding, bounded shutdown, and no secret values in manager logs.
|
||||
6. Stelloauth security tests for URL allowlisting, request-size limits,
|
||||
forwarded-header spoofing, and redacted logs.
|
||||
7. An amd64 Docker build and container run on the development host.
|
||||
8. CloakBrowser startup and a real CDP `/json/version` probe.
|
||||
9. Stelloauth startup and HTTP `GET /` response.
|
||||
10. `POST /worker` contract behavior with invalid/non-Stellantis URLs, without
|
||||
real MyOpel credentials.
|
||||
11. Verification from outside the container that port 9222 is unreachable.
|
||||
12. Graceful stop and restart with no remaining child processes.
|
||||
13. Log scanning with sentinel email, password, cookie, OAuth code, access
|
||||
token, and refresh token values.
|
||||
14. Base-image manifest checks for amd64 and arm64 plus an arm64 build when the
|
||||
local Docker builder supports emulation.
|
||||
15. Idle RAM and image/disk size measurement. Login-flow RAM will be documented
|
||||
as unmeasured unless real credentials are explicitly supplied for testing.
|
||||
|
||||
Home Assistant's devcontainer/Supervisor validation is preferred when it is
|
||||
available. Standalone Docker tests remain the reproducible minimum.
|
||||
|
||||
## Documentation and release maintenance
|
||||
|
||||
The root README explains repository installation. `stelloauth/DOCS.md`
|
||||
contains the user guide shown by Supervisor: install, start-on-boot, watchdog,
|
||||
log readiness, endpoint test, integration setup, Opel/DK selection,
|
||||
troubleshooting, security model, resource results, and removal.
|
||||
|
||||
`CHANGELOG.md` starts at `0.1.0` and names both pinned upstream versions.
|
||||
Version updates require changing the pins/digests, reapplying security patches,
|
||||
running the full validation, and reviewing upstream logging, worker validation,
|
||||
license, endpoints, and image manifests before release.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- The first local build is large because CloakBrowser contains Chromium. The
|
||||
target currently has only about 4 GB free, so installation may require disk
|
||||
cleanup after the measured build size is known.
|
||||
- A complete MyOpel login and peak login RAM measurement cannot be performed
|
||||
without real credentials. No credentials will be requested or stored merely
|
||||
to make the build pass.
|
||||
- The fixed allowlist must be updated if the Stellantis Vehicles integration
|
||||
adds or changes identity-provider hosts.
|
||||
- Changing the exact Gitea repository URL changes Supervisor's repository ID,
|
||||
add-on hostname, and Login service URL.
|
||||
- The add-on uses the free CloakBrowser tier and intentionally serializes login
|
||||
flows.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Installation or deployment to the production Home Assistant host
|
||||
- Docker Compose, Docker-in-Docker, Portainer, host networking, or HAOS system
|
||||
partition changes
|
||||
- Publishing a prebuilt image containing the CloakBrowser binary
|
||||
- Modifying the Stellantis Vehicles integration
|
||||
- Automated use of real MyOpel credentials
|
||||
- Pushing commits to Gitea
|
||||
|
||||
## Primary references
|
||||
|
||||
- <https://github.com/tamcore/stelloauth/tree/v0.6.0>
|
||||
- <https://github.com/CloakHQ/CloakBrowser/tree/v0.5.10>
|
||||
- <https://github.com/andreadegiovine/homeassistant-stellantis-vehicles/releases/tag/2026.9.4>
|
||||
- <https://developers.home-assistant.io/docs/apps/configuration/>
|
||||
- <https://developers.home-assistant.io/docs/apps/communication/>
|
||||
- <https://developers.home-assistant.io/docs/apps/repository/>
|
||||
- <https://developers.home-assistant.io/docs/apps/testing/>
|
||||
Reference in New Issue
Block a user