You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
22 KiB

# tera-api
Initial mock backend for TERA Core-X48 PRO management APIs.
## First user task
Portable Go is installed under the repository `.tools/` directory. Run:
```powershell
cd services\tera-api
powershell -NoProfile -ExecutionPolicy Bypass -File ..\..\scripts\use-go.ps1 test ./...
powershell -NoProfile -ExecutionPolicy Bypass -File ..\..\scripts\use-go.ps1 run ./cmd/tera-api
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-logd
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-hwd
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-seriald
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-serial-check
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-serial-line-check
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-serial-termios.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-target-rootfs-staging.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-target-rootfs-staging.ps1 -InitSystem buildroot
```
For a same-origin Direct Mode attachment load against a deployed target, keep
the web password in the process environment and run the dedicated client. The
client sends the browser-required `Origin` header and `tera.direct.v1`
subprotocol, consumes one-use stream tickets, keeps reading to service
WebSocket heartbeats, validates one welcome frame for every attachment, checks
the matching channel `session` logs, and closes every session during cleanup.
With `-attachments 48`, every CH01-CH48 channel is covered once without sending
serial payload; `192` covers four viewers per channel:
```powershell
$env:TERA_TARGET_PASSWORD = '<read from the protected lab credential source>'
powershell -NoProfile -ExecutionPolicy Bypass -File ..\..\scripts\use-go.ps1 run ./cmd/tera-target-session-load -base-url http://192.168.0.223:8083 -attachments 192 -hold-seconds 60
Remove-Item Env:\TERA_TARGET_PASSWORD
```
Then verify:
```powershell
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/system/status
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/system/services
Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '{"username":"admin","password":"mock-admin-secret"}' http://127.0.0.1:8080/api/v1/auth/login
Invoke-RestMethod -Method Post -Headers @{"Authorization"="Bearer <session-token>"} http://127.0.0.1:8080/api/v1/auth/logout
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/me
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/tokens
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"name":"automation"}' http://127.0.0.1:8080/api/v1/auth/tokens
Invoke-RestMethod -Method Delete -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/tokens/token_000001
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/users
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"username":"fieldops","password":"fieldops-secret-1","role":"port_operator"}' http://127.0.0.1:8080/api/v1/auth/users
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"enabled":false}' http://127.0.0.1:8080/api/v1/auth/users/fieldops
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"password":"fieldops-secret-2"}' http://127.0.0.1:8080/api/v1/auth/users/fieldops/password
Invoke-RestMethod -Method Delete -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/users/fieldops
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/groups
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/permissions
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/config/status
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/config/export
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/time/settings
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"timezone":"Asia/Seoul","ntp_servers":["time1.example.local","time2.example.local"]}' http://127.0.0.1:8080/api/v1/time/settings
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/security/management-access
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"https_enabled":true,"https_port":8443,"ssh_enabled":true,"ssh_port":2222,"http_enabled":false,"http_port":80,"telnet_enabled":false,"telnet_port":23,"idle_timeout_minutes":30}' http://127.0.0.1:8080/api/v1/security/management-access
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"interfaces":[{"name":"Interface-1","address":"192.168.10.233/24","gateway":"192.168.10.1"}]}' http://127.0.0.1:8080/api/v1/network/running
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/ports
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/alarms/rules
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"mode":"Reverse Telnet"}' http://127.0.0.1:8080/api/v1/ports/CH01
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"enabled":false}' http://127.0.0.1:8080/api/v1/alarms/rules/auth_failures
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/config/apply
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"browser_time":"2026-06-16T10:31:12+09:00","timezone":"Asia/Seoul"}' http://127.0.0.1:8080/api/v1/time/sync-browser
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/time/sync-ntp
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"target":"192.168.0.1"}' http://127.0.0.1:8080/api/v1/diagnostics/network-test
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"target":"8.8.8.8","count":4}' http://127.0.0.1:8080/api/v1/diagnostics/network-test
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/diagnostics/support-bundle
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} 'http://127.0.0.1:8080/api/v1/logs/audit?actor=admin&order=desc&limit=100'
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} 'http://127.0.0.1:8080/api/v1/logs/system?severity=warning&keyword=failover&from=2026-06-15T00:00:00Z&to=2026-06-17T00:00:00Z'
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} 'http://127.0.0.1:8080/api/v1/logs/port?channel=CH01&keyword=LOGIN&actor=admin&from=2026-06-15T00:00:00Z&to=2026-06-17T00:00:00Z'
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} 'http://127.0.0.1:8080/api/v1/logs/links?kind=serial&resource=CH01'
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/logs/policy
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/logs/retention
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"port_days":180,"protected_usage_warning_pct":85}' http://127.0.0.1:8080/api/v1/logs/retention
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/logs/remote-syslog
Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"enabled":true,"host":"syslog.local","port":1514,"protocol":"tcp","facility":"local4"}' http://127.0.0.1:8080/api/v1/logs/remote-syslog
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"log_type":"port","channel":"CH01"}' http://127.0.0.1:8080/api/v1/logs/export
Invoke-WebRequest -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"log_type":"port","channel":"CH01","actor":"admin","from":"2026-06-15T00:00:00Z","to":"2026-06-17T00:00:00Z"}' -OutFile tera-port-logs.jsonl http://127.0.0.1:8080/api/v1/logs/export/download
Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/ports/CH01/restart
Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/operations
```
## Current scope
- Mock domain data with optional target-backed local service adapters and
bounded file-backed state for host and rootfs verification.
- In-memory `pending`/`running`/`persistent` config transaction state.
- JSON request bodies are bounded by `TERA_API_MAX_REQUEST_BYTES` and must
contain a single JSON value. Oversized bodies return `payload_too_large`
with HTTP 413; malformed or trailing JSON returns `validation_failed`.
- Audit events for user-verifiable command validation. By default these are
in-memory. Set `TERA_API_AUDIT_LOG` to a JSON Lines file path to enable
bounded file-backed prototype audit storage; set
`TERA_API_AUDIT_MAX_EVENTS` to control the retained event count.
- Config transaction state is in-memory by default. Set
`TERA_API_CONFIG_STORE` to a JSON file path to retain prototype
port, alarm, network, time, and log-retention `pending`/`running`/
`persistent` state across `tera-api` restarts. Prototype config export
returns running, persistent, and pending port/alarm/network/time/log-retention/
management-access/remote-syslog state and requires `config.read`.
- Operation state is in-memory by default. Set `TERA_API_OPERATION_STORE` to a
JSON file path to retain prototype operation results across `tera-api`
restarts. Operation result lookup and operation history listing require
`operation.read`.
- Prototype local auth users are in-memory by default. Set
`TERA_API_AUTH_STORE` to a JSON file path to retain prototype users with
nested password KDF records containing `algorithm`, `version`, `params`,
`salt`, and `hash`. Local password hashing uses Argon2id by default; the
record schema is versioned so a future FIPS-compatible
`pbkdf2-hmac-sha256` mode can be added without changing the auth store
shape. Local user creation and password reset enforce an eight-character
minimum by default. `TERA_API_MIN_PASSWORD_LENGTH` may raise that policy up
to 128 characters but cannot reduce it below eight. The target profile sets
`TERA_API_AUTH_STORE=/var/lib/tera-api/auth.json` so provisioned users and
disabled bootstrap accounts survive service restarts.
- Prototype bearer sessions use crypto-random tokens, store token hashes
in memory, expire after eight hours, and can be revoked through
`POST /api/v1/auth/logout`.
- Prototype API bearer tokens are available behind `auth.read`/`auth.write`.
Raw token material is returned only on creation, stored by hash, and bounded
by `TERA_API_AUTH_MAX_TOKENS` when configured. Token records are listed by
opaque `token_id` without exposing raw token material and can be revoked by
`DELETE /api/v1/auth/tokens/{token_id}`.
- Prototype local login tracks repeated failures in memory and temporarily
locks a normalized username after five failures for 15 minutes. Client
responses remain generic; lock details are recorded only in the audit log.
- Prototype local user, group, and permission read APIs are available behind
`auth.read`. Prototype local user creation, enable/disable updates, and
role updates, password resets/deletes are available behind `auth.write`.
These responses intentionally exclude password KDF records, salts, hashes,
session tokens, and token hashes. Deleting or demoting the last enabled
administrator is rejected to avoid local lockout. Disabling, deleting,
changing role, or resetting the password for a local user immediately revokes
that user's existing bearer sessions and API bearer tokens.
- Prototype audit log reads require `audit.read`; only `mock-admin` has this
permission in the current mock policy.
- System, structured port command/result, and serial/Ethernet link log search
require `log.read`; audited log export and NDJSON download require
`log.export`.
`GET /api/v1/logs/policy` exposes the 365-day maximum, command/result and link
capture, no-at-rest-encryption decision, and enabled bounded UART/Direct Mode
payload capture with a 4-KiB per-record maximum. The production environment connects `tera-api` to the
independent `tera-logd` Unix socket for authoritative reads/writes,
retention, health, and filtered NDJSON export.
- Mock log retention policy reads require `log.read`; updates require
`config.write`, participate in the config transaction, and preserve protected
logs across factory reset by policy. All retention values are limited to 1
through 365 days.
- In explicit host-prototype mode, `TERA_API_SYSTEM_LOG_STORE`,
`TERA_API_PORT_LOG_STORE`, and
`TERA_API_LINK_LOG_STORE` enable bounded JSON Lines prototype persistence;
`TERA_API_LOG_MAX_EVENTS` bounds each of those stores. Command and result
fields are credential-redacted and size-bounded before persistence. A
configured/enabled remote syslog target receives best-effort forwarded
records through the existing alarm-delivery boundary.
- Target packaging instead sets `TERA_API_LOG_SOCKET` to
`/run/tera-logd/log-store.sock` and enables the production scheduler with
`TERA_API_LOG_SCHEDULER_ENABLED=true`. It must not configure the prototype
JSON Lines paths at the same time.
- Mock remote syslog reads require `log.read`; updates require `config.write`,
participate in the config transaction, and intentionally contain no shared
secrets or credentials.
- Mock alarm rule read/update endpoints are available for Web UI flow
validation. Reads require `alarm.read`; updates are config transaction
changes and require `alarm.write`.
- Alarm delivery tests call the group-restricted `tera-alarmd` Unix socket in
target mode. `tera-alarmd` persists a bounded retry queue atomically,
restores queued/retry-pending work after restart, delivers local/web events,
and sends configured remote syslog over UDP or TCP without a shell. SMTP and
SNMP trap transports fail explicitly until protected credential storage is
implemented; no placeholder secret material is embedded in configuration.
- Mock serial channel inventory/detail reads require `port.read`. The current
mock policy grants it to `mock-admin` and `mock-port-operator`.
- Mock network state reads require `network.read`; only `mock-admin` has this
permission in the current mock policy. Mock network interface address/gateway
staging requires `network.write`, participates in the config transaction, and
becomes visible in running state after config apply.
- Mock dashboard system status reads require `system.read`; only `mock-admin`
has this permission in the current mock policy. Mock service health reads use
the same permission and report prototype logical service readiness.
- Mock config transaction status lookup requires `config.read`; only
`mock-admin` has this permission in the current mock policy. Mock config
export uses the same permission and intentionally excludes auth, session,
token, and password KDF material.
- Browser/PC time sync requires `time.sync`, calls the group-restricted
`tera-timed` Unix socket, returns an operation result, and records the final
audit outcome. `tera-timed` validates the bounded RFC3339 time and IANA
timezone, applies `/etc/localtime` transactionally, and restores the prior
timezone if the Linux clock update fails. The in-process mock is used only
when mock actors are explicitly enabled.
- Mock time settings read/update is available for maintenance flow validation.
Reads require `config.read`; updates require `config.write` and participate
in the config transaction.
- Mock management access policy read/update is available for security flow
validation. Reads require `config.read`; updates require `config.write`,
participate in the config transaction, and reject changes that disable every
HTTPS, SSH, and serial-console recovery path.
- Network-test and traceroute diagnostics require `diagnostics.run` and use a
bounded asynchronous operation queue in target mode. The process adapter
invokes only fixed absolute `ping`/`traceroute` executables without a shell,
revalidates targets, enforces request timeouts, bounds combined output, and
records accepted plus final audit events. Queue/worker counts are bounded and
unfinished work is marked failed on restart or shutdown. When
`TERA_API_SUPPORT_BUNDLE_DIR` is configured, support-bundle requests use the
same bounded queue to create mode-`0600` ZIP artifacts atomically. Bundles
include only system status, service health, redacted config export, bounded
audit/system/port events, and operation metadata; auth users, password KDF
records, bearer sessions, API token hashes, and secret material are excluded.
Artifact count, total bytes, and per-section bytes are bounded. Authorized
downloads use `GET /api/v1/diagnostics/support-bundles/{bundle_id}` with
`no-store`, checksum, and attachment headers and create a download audit.
- Green LED control is a real target hardware boundary, not mock serial data:
`hardware.read` lists the CH01–CH48 PCF8574 mapping and `hardware.write`
force/releases or pulses one green LED. `tera-api` stays unprivileged and
calls the group-restricted `tera-hwd` Unix socket; `tera-hwd` alone writes
the kernel `gpio-leds` brightness entries after the target DTB is active.
- Serial line configuration is routed through the separate `tera-seriald`
Unix-socket boundary. `tera-api` never opens `/dev/ttyWCH*`. The controller
accepts only the validated `fet3568j-c-20260721-v1` mapping: CH01-CH24 use
`/dev/ttyWCH4`-`/dev/ttyWCH27`, and CH25-CH48 use
`/dev/ttyWCH32`-`/dev/ttyWCH55`. Raw nodes 0-3 and 28-31 are excluded as the
unconnected direct CH384 UARTs. Missing or mismatched mapping evidence still
fails closed. See
`docs/sw-detail-design/serial-hardware-api-contract.md`.
- Port updates now stage the bounded full UART profile (supported baud rate,
5-8 data bits, none/even/odd parity, one or two stop bits, and none/RTS-CTS
flow control). Apply operations pass the complete profile to `tera-seriald`;
readback mismatch or partial failure restores all opened channels.
- Mock operation result lookup and operation history listing require
`operation.read`; only `mock-admin` has this permission in the current mock
policy.
- Prototype `X-Mock-Actor` auth/RBAC skeleton for mutating commands and
`GET /api/v1/auth/me` profile checks. Set
`TERA_API_ALLOW_MOCK_ACTORS=false` to disable this header-based prototype
bypass and require bearer session or API token authentication for protected
routes; CORS preflight responses also stop advertising `X-Mock-Actor` when
this bypass is disabled.
- Firmware upload is streamed through the protected `tera-updated` Unix socket;
`tera-api` does not own `/userdata/update` and cannot mutate partitions or the
eMMC layout. The current updater accepts validation-only packages and rejects
apply. Read-only diagnostics and explicitly configured remote syslog are the
only new external network actions.
The only implemented clock mutation crosses the narrow `tera-timed` local
socket; HTTP handlers and the unprivileged `tera-api` process cannot set the
system clock or write `/etc/localtime` directly.
- No production login, session token, password hash, external auth secret, or
TLS key storage exists in this prototype. File-backed audit/auth/config/
operation storage is prototype-only and is not a replacement for the
production `tera-logd`, product persistent configuration database, protected
secret storage, and operation journal paths. Prototype snapshot rewrites do
use same-directory temporary files, file and directory sync on Linux, mode
`0600`, atomic rename, and symbolic-link refusal so a crash cannot expose a
partially written JSON destination.
- API contract is documented in
`docs/sw-detail-design/web-api-contract.md`.
- Backend-to-target implementation and acceptance gates are documented in
`docs/sw-detail-design/backend-target-test-gates.md`.
- Green LED API and deployment details are documented in
`docs/sw-detail-design/green-led-api-contract.md` and
`target-bsp/docs/pcf8574-green-led-control.md`.
- Architecture/FigJam guardrails are documented in
`docs/sw-detail-design/architecture-figma-implementation-guardrails.md`.
## Next implementation steps
1. Replace prototype auth/RBAC storage with production session identity and
protected token storage. Direct Mode already reuses the common permission
and audit path and must not introduce a second auth store.
2. Add protected SMTP/SNMP credential storage before enabling those
`tera-alarmd` transports.
3. Replace the remaining prototype config and operation stores with product
persistence; authoritative log persistence is now owned by `tera-logd` but
still requires target Stage 4 application and soak evidence.
4. Keep update/recovery and GPT/eMMC changes disabled until their separate
device-side safety and rollback gates are explicitly approved.