Secure first-loop control-plane auth and mount routing.

Protect the control-plane API with explicit bearer auth, add node-scoped
registration/heartbeat credentials, and make export mount paths an explicit
contract field so mount profiles stay correct across runtimes.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Harivansh Rathi 2026-04-01 14:13:14 +00:00
parent a7f85f4871
commit ed40da7326
23 changed files with 3676 additions and 124 deletions

View file

@ -21,6 +21,9 @@ paths:
/api/v1/nodes/register:
post:
operationId: registerNode
security:
- NodeBootstrapToken: []
- NodeToken: []
requestBody:
required: true
content:
@ -30,13 +33,22 @@ paths:
responses:
"200":
description: Registered node
headers:
X-BetterNAS-Node-Token:
description: Returned when a node is first registered or migrated to node-scoped auth.
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/NasNode"
"401":
description: Unauthorized
/api/v1/nodes/{nodeId}/heartbeat:
post:
operationId: recordNodeHeartbeat
security:
- NodeToken: []
parameters:
- in: path
name: nodeId
@ -52,9 +64,13 @@ paths:
responses:
"204":
description: Heartbeat accepted
"401":
description: Unauthorized
/api/v1/exports:
get:
operationId: listExports
security:
- ClientToken: []
responses:
"200":
description: Export list
@ -64,9 +80,13 @@ paths:
type: array
items:
$ref: "#/components/schemas/StorageExport"
"401":
description: Unauthorized
/api/v1/mount-profiles/issue:
post:
operationId: issueMountProfile
security:
- ClientToken: []
requestBody:
required: true
content:
@ -80,9 +100,13 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/MountProfile"
"401":
description: Unauthorized
/api/v1/cloud-profiles/issue:
post:
operationId: issueCloudProfile
security:
- ClientToken: []
requestBody:
required: true
content:
@ -96,7 +120,22 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/CloudProfile"
"401":
description: Unauthorized
components:
securitySchemes:
ClientToken:
type: http
scheme: bearer
description: Bearer token required for export listing and profile issuance.
NodeBootstrapToken:
type: http
scheme: bearer
description: Bearer token required to register a new node before it receives a node-scoped token.
NodeToken:
type: http
scheme: bearer
description: Bearer token scoped to a previously registered node.
schemas:
NasNode:
type: object
@ -150,6 +189,8 @@ components:
type: string
path:
type: string
mountPath:
type: string
protocols:
type: array
items:
@ -223,6 +264,8 @@ components:
type: string
path:
type: string
mountPath:
type: string
protocols:
type: array
items:

View file

@ -25,6 +25,9 @@
"path": {
"type": "string"
},
"mountPath": {
"type": "string"
},
"protocols": {
"type": "array",
"items": {

View file

@ -6,6 +6,11 @@ export const FOUNDATION_API_ROUTES = {
issueCloudProfile: "/api/v1/cloud-profiles/issue",
} as const;
export const FOUNDATION_API_HEADERS = {
authorization: "Authorization",
nodeToken: "X-BetterNAS-Node-Token",
} as const;
export type NasNodeStatus = "online" | "offline" | "degraded";
export type StorageAccessProtocol = "webdav";
export type AccessMode = "mount" | "cloud";
@ -29,6 +34,7 @@ export interface StorageExport {
nasNodeId: string;
label: string;
path: string;
mountPath?: string;
protocols: StorageAccessProtocol[];
capacityBytes: number | null;
tags: string[];
@ -64,6 +70,7 @@ export interface CloudProfile {
export interface StorageExportInput {
label: string;
path: string;
mountPath?: string;
protocols: StorageAccessProtocol[];
capacityBytes: number | null;
tags: string[];