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
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ woff2, strict CSP, no runtime CDN) for air-gap use.
encrypted key backup (PBES2 PKCS#8), reads it back, and builds the PKCS#12 with the backup's
own passphrase; the credential wizards hold only the encrypted backup in memory. Tests assert
on the request bodies that neither the key nor the passphrase is ever sent.
- First run: `BootstrapGate` (`src/components/layout/bootstrap-gate.tsx`) wraps the auth gate and
asks `GetBootstrapState` before anything else. Open first run tries WhoAmI, then shows the
`FirstRunWizard` (`src/components/first-run/`). `src/lib/bootstrap.ts` keeps the session secret
in module memory only and sends it as the `Fleetos-Bootstrap-Session` header; never put it in
web storage, a cookie or a URL. Mock mode keeps first run closed; set
`VITE_BOOTSTRAP_MOCK_STATE=open` to try the wizard offline.
- Operator CA registration is a preview then a confirm with `confirm_sha256`.
`OperatorCARegisterForm` takes the submit call as a prop, so the admin page and first run share
it; `registerRequestFields()` builds the body both RegisterOperatorCA RPCs take.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ The same bundle adapts at runtime based on the role of the node being viewed:
- 🔌 **Intermediate / Issuing nodes** — issuance profiles, certificate inventory, CSR review, CRL / OCSP status, adapter health, audit log tail.
- 👁️ **All nodes** — live status, configuration view (read-only when the node is linked to FM, which is the normal mode).

## 🚀 First run

A new Fleet Manager with no operator CA configured starts in first run. Before sign-in the web UI asks the manager for its first-run state. While it is open, a wizard takes the single-use bootstrap token from the manager's log (after a check of the server certificate's fingerprint), registers your external operator CA with its CRL source and OCSP mode once you confirm its fingerprint, and gets the first admin certificate. Path A makes the key in the browser, with a mandatory encrypted key backup, then builds the PKCS#12 once your CA signs the CSR. Path B gives the OpenSSL recipe and an optional pre-flight. It ends with install steps for each OS. The session secret stays in memory only.

## 🪪 Operator credentials

Operator certificates come from your own external operator CA (an offline OpenSSL CA or an enterprise CA). The Fleet Manager never signs one; it records them and can deny them.
Expand Down
6 changes: 4 additions & 2 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ describe("App routing", () => {
expect(screen.queryByRole("button", { name: /log in/i })).not.toBeInTheDocument();
});

it("keeps the console behind the login gate", () => {
// The gate asks the Fleet Manager whether first run is open before it shows
// the sign-in, so the Log in button appears once that answer is in.
it("keeps the console behind the login gate", async () => {
renderAt("/agent-keys");

expect(screen.getByRole("button", { name: /log in/i })).toBeInTheDocument();
expect(await screen.findByRole("button", { name: /log in/i })).toBeInTheDocument();
});
});
9 changes: 6 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Route, Routes } from "react-router-dom";

import { AppShell } from "@/components/layout/app-shell";
import { AuthGate } from "@/components/layout/auth-gate";
import { BootstrapGate } from "@/components/layout/bootstrap-gate";
import { AdoptPage } from "@/pages/adopt";
import { AgentKeysPage } from "@/pages/agent-keys";
import { ApprovalsPage } from "@/pages/approvals";
Expand Down Expand Up @@ -54,9 +55,11 @@ export const App = () => {
<Route element={<CredentialRequestPage />} path="request-credential" />
<Route
element={
<AuthGate>
<AppShell />
</AuthGate>
<BootstrapGate>
<AuthGate>
<AppShell />
</AuthGate>
</BootstrapGate>
}
>
<Route element={<DashboardPage />} index />
Expand Down
Loading
Loading