# NethSecurity → new `my` migration — end‑to‑end verification checklist **Target repo:** `NethServer/nethsecurity` **Parent issue:** NethServer/my#84 **Cutover PR:** #1609 `feat/backup-my-cutover` (pins `nethsecurity-ui` NethServer/nethsecurity-ui#746) ## Goal Verify, on a single controlled NethSecurity **enterprise** unit, the full migration flow from the old `my` (PHP on my‑ent / `legacy.my.nethesis.it`) to the new `my` (`my.nethesis.it`): credential rotation, native telemetry/backup/alerts on the new `my`, enterprise feed authorization, and the subscription‑gated services (DPI, Threat‑shield, Automatic updates). > **Pre‑flip verification.** These PRs point the **functional** endpoints (register, `collect_url`, > `/proxy/credentials`, alerts) at **`my-proxy-prod.onrender.com`**, and this checklist is run **before** > the DNS flip. Commands use `$MY`: > ```sh > MY=https://my-proxy-prod.onrender.com > ``` > (The onrender → `my.nethesis.it` swap happens later, coupled with the DNS flip.) > > **Exception — enterprise feeds.** The 3 feeds (distfeed/sp/bl) forwardAuth to **`my.nethesis.it/auth`** > — this is fixed in the feed hosts (= the transitional broker on my‑ent), independent of `$MY` and of > the flip. So the feed hosts (`distfeed.nethesis.it`, `bl.nethesis.it`) and the `/auth` checks in Step 5 > stay on their real hostnames, **not** `$MY`. --- ## Scenario A — migration of an existing (already‑registered) system _(For a clean install that registers fresh on the new `my`, see **Scenario B** near the end — Steps 3–7 are shared.)_ ## Step 0 — Pre‑check: the system already exists on the new `my` Before touching the unit, confirm the import from the old `my` landed. 1. Note the unit's **current (old‑my) system id**: ```sh uci get ns-plug.config.system_id # legacy UUID, e.g. 7DD2C9A8-.... ``` 2. Log in to the new `my` UI (`$MY`). Open **Systems**, find this unit (by name / reseller / customer). - [ ] The system is present, with the correct **plan**, **company/customer**, and **subscription** state. - [ ] Its **System ID** is shown and (once migrated) will match the rotated `NETH-…` id. --- ## Step 1 — Install the cutover firmware (PR #1609) Flash / `sysupgrade` the unit to the image built from **PR #1609** (keeps config). This firmware bundles: the rotation (`migrate-to-my`), single‑send native collect, the alerts cutover (`vmalert.initd`), the subscription UX, and the pinned `ns-ui` NethServer/nethsecurity-ui#746. - [ ] Unit boots on the PR build (`cat /etc/nethsecurity-release` / `ubus call system board`). --- ## Step 2 — Verify credential rotation (new creds set, legacy preserved) The first `send-*` cron (or a manual run) triggers `migrate-to-my`, which `GET $MY/proxy/credentials` with the legacy Basic‑Auth, writes the new creds, preserves the legacy ones, and sets `migrated=1`. ```sh uci show ns-plug | grep -E 'system_id|secret|type|migrated|collect_url|legacy' ``` Expected: - [ ] `ns-plug.config.system_id` = **`NETH-…`** (new format) - [ ] `ns-plug.config.secret` = **`my_…`** (new format) - [ ] `ns-plug.config.migrated` = **`1`** - [ ] `ns-plug.config.collect_url` = **`https://my-proxy-prod.onrender.com/collect/api/systems`** - [ ] `ns-plug.config.legacy_system_id` / `legacy_secret` = the **old UUID/hex** (preserved for rollback) **Idempotency:** re‑run and confirm it exits at the marker, no re‑rotation: ```sh /usr/sbin/migrate-to-my ; echo "exit=$?" # exits early, no change ``` - [ ] Second run is a no‑op (idempotent). --- ## Step 3 — Verify heartbeat / inventory / backup write to the new `my` ```sh # force a cycle /usr/sbin/send-heartbeat ; /usr/sbin/send-inventory ; /usr/sbin/send-backup logread -e ns-plug | tail -30 # POSTs go to $MY/collect/api/systems/{heartbeat,inventory,backups} ``` On the new `my` (UI or API), for this system: - [ ] **Last seen / heartbeat** updates (recent timestamp). - [ ] **Inventory** is present and current. - [ ] **Backup** is stored on the new `my` (Systems → backups / `GET $MY/collect/api/systems/backups`). - [ ] **`facts.migration.from_legacy_system_id`** on the system equals the **old** system id (the authoritative "this unit migrated" signal). --- ## Step 4 — Verify native alerts (Mimir alertmanager, not the legacy proxy) Post‑rotation the running vmalert switches (at reload) from `/proxy/alerts` to the **native** collect Mimir path derived from `collect_url`. ```sh /etc/init.d/vmalert reload uci get ns-plug.config.system_id ; logread -e vmalert | tail -20 ``` - [ ] vmalert notifier points to `…/collect/api/services/mimir/alertmanager` (native), not `/proxy/alerts`. - [ ] Fire a **test alert** (e.g. stop a monitored service / a test rule) and confirm it appears in the new `my` alerting view for this system (native Mimir), authenticated with the **new** creds. - [ ] No alert gap across the switch (legacy `/proxy/alerts` kept translating until the reload). --- ## Step 5 — Verify enterprise repositories + `/auth` authorization The 3 enterprise feeds authenticate `system_id:secret` via **forwardAuth to `my.nethesis.it/auth`** (handled by the transitional broker on my‑ent: `NETH-…` active → 200 grant‑all; legacy → unchanged). > **⚠️ Transitional grant‑all — Advanced Threat Shield / `ng-blacklist` is NOT truly gated yet.** > In this migration phase `https://my.nethesis.it/auth/service/ng-blacklist` (and every `/auth/service/*`, > `/auth/product/*`) always resolves to **200** as long as the system's **license is valid** (active on the > new `my`). The new `my` does **not yet** implement the old `my`'s per‑add‑on entitlement check, so > `ng-blacklist` (a granular paid add‑on on the old `my`) is served to any active enterprise unit > **regardless of purchase**. Not‑yet‑migrated units keep the granular (403‑capable) check via the old > `my`. Accepted as non‑blocking; per‑add‑on enforcement returns once the new `my` has entitlement > management. ```sh SID=$(uci get ns-plug.config.system_id); SEC=$(uci get ns-plug.config.secret) # /auth checks (via the feed hosts' forwardAuth target) curl -s -o /dev/null -w "auth -> %{http_code}\n" -u "$SID:$SEC" https://my.nethesis.it/auth curl -s -o /dev/null -w "auth/ng-blacklist-> %{http_code}\n" -u "$SID:$SEC" https://my.nethesis.it/auth/service/ng-blacklist # apk enterprise repository update-packages --enable-custom-repo 2>&1 | tail -20 # or: apk update ``` Expected: - [ ] `/auth` → **200** and `/auth/service/ng-blacklist` → **200** with the new creds. - [ ] `apk update` fetches the **enterprise** repo without auth errors (401/403). *(A `404` on a specific dev/PR‑build version path is a firmware‑version artifact, not an auth failure.)* - [ ] Wrong/empty creds → **401** (negative check). --- ## Step 6 — Verify subscription‑gated services are active ### DPI (netifyd — license + signatures) ```sh LOGLEVEL=DEBUG dpi-license-update ; echo "exit=$?" head -c 200 /etc/netifyd/license.json # expect: "NethSecurity Enterprise Edition" dpi-data-update ; /etc/init.d/netifyd status ``` - [ ] Enterprise **license** downloaded (`issued_to: "NethSecurity Enterprise Edition"`), netifyd reloaded. - [ ] Application/protocol catalogs updated; netifyd running. ### Threat‑shield (blocklists from `bl.nethesis.it`) ```sh /etc/init.d/banip reload ; banip -s | head # or ts-ip / ts-dns ``` - [ ] Enterprise blocklists (`nethesis-blacklists/*`) fetched (200), banip active with the feeds. ### Automatic updates - [ ] Automatic‑updates feed reachable with the new creds (part of the enterprise apk repo above). --- ## Step 7 — Subscription UX (re‑register is one‑shot by design) - [ ] System detail on the new `my`: **System ID linkable**, plan + company shown. - [ ] **Re‑register** the same key → **409 `system_already_registered`** (the new‑my key is one‑shot, cannot be freed/re‑registered by design). - [ ] **Cancel / remove subscription** shows the **irreversibility warning**. --- ## Step 8 — Hotspot (dedalo): login via My Nethesis (if the unit runs a hotspot) The hotspot page authenticates to the hotspot manager through the **My Nethesis OIDC device pairing** (nethesis/icaro#200) instead of account credentials: the unit starts a pairing, the browser popup runs the SSO login on the manager, and the unit polls for the session token. Everything downstream (hotspot list, unit registration, wax) is unchanged. > **Prereq:** nethesis/icaro#200 deployed on the hotspot manager. Until `my.nethspot.com` (prod) ships > it, test against **staging**: open the **Legacy login** accordion and set the endpoint to > `my.staging.nethspot.com` before clicking the button (on prod the UI reports "not supported yet"). - [ ] **Network → Hotspot → Settings**: primary action is **Login with My Nethesis**; the classic endpoint/username/password form is collapsed under **Legacy login**. - [ ] Click **Login with My Nethesis** → popup on the manager: with an active `my.nethesis.it` session (SSO) it completes by itself; on a fresh browser Logto asks credentials (+2FA) and the OAuth consent screen. - [ ] The popup ends on the themed result page showing **Account**, **Logged in as** (my user email) and **Unit**, then can be closed. - [ ] The unit picks the token up automatically (success notification) and the parent hotspot list loads. - [ ] The summary above the configuration shows **Account / Authenticated by / Hotspot manager** and persists across page reloads (stored in uci). - [ ] Configure and **Save** → the unit appears under the chosen parent hotspot on the manager. - [ ] On the manager, the session token description reads `OIDC device pairing by <email> (<sub>), unit: <name>` (auditable, revocable selectively). - [ ] **Deny path:** a my user without the required roles (global Admin + org Reseller) → popup shows "Unit not linked (unauthorized_role)" and the unit reports the failure (no token issued). - [ ] **Unregister** removes the hotspot config and clears the account summary. --- ## Scenario B — clean install (fresh registration, never registered) A unit flashed clean from the PR image and registered **directly** on the new `my` — no legacy creds, no rotation, no `proxy_mappings`. `packages/ns-plug/files/register` (enterprise) POSTs the secret to `$MY/backend/api/systems/register`, stores the returned native `system_key`, and sets `migrated=1` from the start so `migrate-to-my` stays a no‑op and telemetry/alerts go native from the first cycle. This is the steady‑state path for every new enterprise unit after the migration. **Prereq:** a valid **registration secret** for a system created on the new `my` (reseller creates the system/subscription on the `my` UI → copies its secret). ### B1 — Flash + register - [ ] Flash the clean unit with the PR #1609 image (never registered before). - [ ] Register from the UI subscription page (paste the secret) or: ```sh register enterprise <REGISTRATION_SECRET> ``` → `POST $MY/backend/api/systems/register` returns the native `system_key` (`NETH-…`). - [ ] Re‑registering the same secret → **exit 2 / HTTP 409** (`system_already_registered`, one‑shot key). ### B2 — Verify native registration (no rotation, no legacy) ```sh uci show ns-plug | grep -E 'system_id|secret|type|migrated|collect_url|repository_url|legacy' ``` - [ ] `system_id` = **`NETH-…`**, `secret` = **`my_…`**, `type=enterprise` - [ ] `migrated` = **`1`** (set by `register`, not by `migrate-to-my`) - [ ] `collect_url` = **`https://my-proxy-prod.onrender.com/collect/api/systems`** - [ ] `repository_url` = `https://<id>:<secret>@distfeed.nethesis.it/repository/enterprise/nethsecurity` - [ ] **No `legacy_*`** keys (nothing to preserve — correct) - [ ] `migrate-to-my` is a **no‑op** (exits at the `migrated=1` guard) ### B3–B7 — same as Scenario A Heartbeat/inventory/backup (Step 3), native alerts (Step 4), `/auth` + enterprise repos (Step 5), DPI / Threat‑shield / Automatic updates (Step 6), subscription UX (Step 7), hotspot via My Nethesis (Step 8, if used) — verify **exactly as in Scenario A**. **Only difference:** on the new `my` the system's **`facts.migration.from_legacy_system_id` is NOT set** (there is no legacy system) — expected for a clean install. --- ## Rollback (single‑unit blast radius) Restore the preserved legacy creds and clear the migration flag: ```sh uci set ns-plug.config.system_id="$(uci get ns-plug.config.legacy_system_id)" uci set ns-plug.config.secret="$(uci get ns-plug.config.legacy_secret)" uci set ns-plug.config.migrated='0' uci delete ns-plug.config.collect_url uci commit ns-plug /etc/init.d/vmalert reload ``` The unit returns to legacy heartbeat/`/proxy/*` dual‑send (translated by the my‑ent proxy). No data loss. --- ## Summary checklist - [ ] System present on the new `my` (Step 0) - [ ] Cutover firmware installed (Step 1) - [ ] Creds rotated `NETH-…`/`my_…`, `migrated=1`, legacy preserved, idempotent (Step 2) - [ ] Heartbeat + inventory + backup native on new `my`; `from_legacy_system_id` set (Step 3) - [ ] Native alerts on new‑my Mimir, no gap (Step 4) - [ ] `/auth` 200 + enterprise apk repo OK (Step 5) - [ ] DPI enterprise license + signatures, Threat‑shield blocklists, Automatic updates active (Step 6) - [ ] Subscription UX incl. one‑shot re‑register (Step 7) - [ ] Hotspot login via My Nethesis + unit attached to the parent hotspot (Step 8, if used) - [ ] Rollback path validated (optional)