Implements the M5 endpoints of the contract (ADR 0023): clusters with member validation, transfer keys and HA credentials, TSIG keys with audited secret access and rotation, explicit zones and typed records, the DHCP cluster of networks, the cluster of agents and the default clusters and HA password file of the generator settings. Revisions are stored per target with sealed secret files and sealed source secrets (restored for rollbacks); bundles carry an agent's own target or the default group. Active leases are adopted as dynamic addresses. The derivation is called through a temporary adapter until derive implements the M5 contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
8.5 KiB
Go
140 lines
8.5 KiB
Go
// SPDX-License-Identifier: LicenseRef-DDI-Proprietary
|
|
|
|
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"git.beugsiud.de/beug.k/DDI/server/internal/auth"
|
|
)
|
|
|
|
// Routes returns the route table. It is the single place that assigns
|
|
// permissions; TestEveryRouteIsProtected and the OpenAPI contract test check it.
|
|
func (a *API) Routes() []Route {
|
|
const (
|
|
get, post, put, patch, del = http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete
|
|
)
|
|
perm := func(method, path string, p auth.Permission, write bool, h handlerFunc) Route {
|
|
return Route{Method: method, Path: path, Access: permission, Perm: p, Write: write, Handler: h}
|
|
}
|
|
return []Route{
|
|
// System and authentication.
|
|
{Method: get, Path: "/api/v1/system/info", Access: public, Handler: a.systemInfo},
|
|
{Method: post, Path: "/api/v1/auth/login", Access: public, Handler: a.login},
|
|
{Method: get, Path: "/api/v1/auth/oidc/start", Access: public, Handler: a.oidcStart},
|
|
{Method: get, Path: "/api/v1/auth/oidc/callback", Access: public, Handler: a.oidcCallback},
|
|
{Method: get, Path: "/api/v1/auth/session", Access: session, MFAPending: true, PasswordChange: true, Handler: a.getSession},
|
|
{Method: post, Path: "/api/v1/auth/logout", Access: session, MFAPending: true, PasswordChange: true, Handler: a.logout},
|
|
{Method: post, Path: "/api/v1/auth/totp/verify", Access: session, MFAPending: true, Handler: a.totpVerify},
|
|
{Method: post, Path: "/api/v1/auth/password", Access: session, PasswordChange: true, Handler: a.changePassword},
|
|
{Method: post, Path: "/api/v1/auth/totp/setup", Access: session, Handler: a.totpSetup},
|
|
{Method: post, Path: "/api/v1/auth/totp/enable", Access: session, Handler: a.totpEnable},
|
|
{Method: post, Path: "/api/v1/auth/totp/disable", Access: session, Handler: a.totpDisable},
|
|
|
|
// Users.
|
|
perm(get, "/api/v1/users", auth.PermUsersAdmin, false, a.listUsers),
|
|
perm(post, "/api/v1/users", auth.PermUsersAdmin, true, a.createUser),
|
|
perm(get, "/api/v1/users/{id}", auth.PermUsersAdmin, false, a.getUser),
|
|
perm(put, "/api/v1/users/{id}", auth.PermUsersAdmin, true, a.updateUser),
|
|
perm(del, "/api/v1/users/{id}", auth.PermUsersAdmin, true, a.deleteUser),
|
|
perm(post, "/api/v1/users/{id}/reset-password", auth.PermUsersAdmin, true, a.resetPassword),
|
|
|
|
// Audit log.
|
|
perm(get, "/api/v1/audit", auth.PermAuditRead, false, a.listAudit),
|
|
perm(get, "/api/v1/audit/export", auth.PermAuditRead, false, a.exportAudit),
|
|
|
|
// License (never write-locked: it is the way out of read-only mode).
|
|
perm(get, "/api/v1/license", auth.PermLicenseRead, false, a.getLicense),
|
|
perm(put, "/api/v1/license", auth.PermLicenseAdmin, false, a.setLicense),
|
|
perm(del, "/api/v1/license", auth.PermLicenseAdmin, false, a.removeLicense),
|
|
perm(post, "/api/v1/license/file", auth.PermLicenseAdmin, false, a.importLicenseFile),
|
|
perm(post, "/api/v1/license/refresh", auth.PermLicenseAdmin, false, a.refreshLicense),
|
|
|
|
// Settings.
|
|
perm(get, "/api/v1/settings/oidc", auth.PermSettingsAdm, false, a.getOIDCSettings),
|
|
perm(put, "/api/v1/settings/oidc", auth.PermSettingsAdm, true, a.setOIDCSettings),
|
|
perm(get, "/api/v1/settings/generator", auth.PermSettingsAdm, false, a.getGeneratorSettings),
|
|
perm(put, "/api/v1/settings/generator", auth.PermSettingsAdm, true, a.setGeneratorSettings),
|
|
|
|
// Generated configuration (read-only preview until agents arrive).
|
|
perm(get, "/api/v1/config/preview", auth.PermIPAMRead, false, a.previewConfig),
|
|
perm(get, "/api/v1/config/pending", auth.PermIPAMRead, false, a.pendingChanges),
|
|
|
|
// Live updates (Server-Sent Events, ADR 0022).
|
|
perm(get, "/api/v1/events", auth.PermIPAMRead, false, a.streamEvents),
|
|
|
|
// Agents and enrollment (ADR 0021).
|
|
perm(get, "/api/v1/agents", auth.PermIPAMRead, false, a.listAgents),
|
|
perm(get, "/api/v1/agents/{id}", auth.PermIPAMRead, false, a.getAgent),
|
|
perm(patch, "/api/v1/agents/{id}", auth.PermSettingsAdm, true, a.updateAgent),
|
|
// Locking agents out is a security function and stays available in
|
|
// license read-only mode, like deleting enrollment tokens.
|
|
perm(del, "/api/v1/agents/{id}", auth.PermSettingsAdm, false, a.deleteAgent),
|
|
perm(post, "/api/v1/agents/{id}/revoke", auth.PermSettingsAdm, false, a.revokeAgent),
|
|
perm(get, "/api/v1/enrollment-tokens", auth.PermSettingsAdm, false, a.listEnrollmentTokens),
|
|
perm(post, "/api/v1/enrollment-tokens", auth.PermSettingsAdm, true, a.createEnrollmentToken),
|
|
perm(del, "/api/v1/enrollment-tokens/{id}", auth.PermSettingsAdm, false, a.deleteEnrollmentToken),
|
|
|
|
// Revisions, deployments and leases.
|
|
perm(get, "/api/v1/revisions", auth.PermIPAMRead, false, a.listRevisions),
|
|
perm(post, "/api/v1/revisions", auth.PermDeploy, true, a.createRevision),
|
|
perm(get, "/api/v1/revisions/{number}", auth.PermIPAMRead, false, a.getRevision),
|
|
perm(post, "/api/v1/revisions/{number}/deploy", auth.PermDeploy, true, a.deployRevision),
|
|
perm(post, "/api/v1/revisions/{number}/rollback", auth.PermDeploy, true, a.rollbackRevision),
|
|
perm(get, "/api/v1/revisions/{number}/diff", auth.PermIPAMRead, false, a.diffRevision),
|
|
perm(get, "/api/v1/deployments", auth.PermIPAMRead, false, a.listDeployments),
|
|
perm(get, "/api/v1/leases", auth.PermIPAMRead, false, a.listLeases),
|
|
|
|
// Networks.
|
|
perm(get, "/api/v1/networks", auth.PermIPAMRead, false, a.listNetworks),
|
|
perm(post, "/api/v1/networks", auth.PermIPAMWrite, true, a.createNetwork),
|
|
perm(get, "/api/v1/networks/{id}", auth.PermIPAMRead, false, a.getNetwork),
|
|
perm(put, "/api/v1/networks/{id}", auth.PermIPAMWrite, true, a.updateNetwork),
|
|
perm(del, "/api/v1/networks/{id}", auth.PermIPAMWrite, true, a.deleteNetwork),
|
|
perm(get, "/api/v1/networks/{id}/map", auth.PermIPAMRead, false, a.networkMap),
|
|
|
|
// Addresses.
|
|
perm(get, "/api/v1/addresses", auth.PermIPAMRead, false, a.listAddresses),
|
|
perm(post, "/api/v1/addresses", auth.PermIPAMWrite, true, a.createAddress),
|
|
perm(get, "/api/v1/addresses/export", auth.PermIPAMRead, false, a.exportAddresses),
|
|
perm(post, "/api/v1/addresses/import", auth.PermIPAMWrite, true, a.importAddresses),
|
|
perm(post, "/api/v1/addresses/preview", auth.PermIPAMRead, false, a.previewAddress),
|
|
perm(post, "/api/v1/addresses/bulk", auth.PermIPAMWrite, true, a.bulkAddresses),
|
|
perm(get, "/api/v1/addresses/{id}", auth.PermIPAMRead, false, a.getAddress),
|
|
perm(put, "/api/v1/addresses/{id}", auth.PermIPAMWrite, true, a.updateAddress),
|
|
perm(del, "/api/v1/addresses/{id}", auth.PermIPAMWrite, true, a.deleteAddress),
|
|
|
|
// Clusters and TSIG keys (ADR 0023): read with ipam.read (never
|
|
// secrets), changed by administrators. Reading a key's secret
|
|
// changes nothing and stays available in read-only mode.
|
|
perm(get, "/api/v1/clusters", auth.PermIPAMRead, false, a.listClusters),
|
|
perm(post, "/api/v1/clusters", auth.PermSettingsAdm, true, a.createCluster),
|
|
perm(get, "/api/v1/clusters/{id}", auth.PermIPAMRead, false, a.getCluster),
|
|
perm(put, "/api/v1/clusters/{id}", auth.PermSettingsAdm, true, a.updateCluster),
|
|
perm(del, "/api/v1/clusters/{id}", auth.PermSettingsAdm, true, a.deleteCluster),
|
|
perm(get, "/api/v1/tsig-keys", auth.PermIPAMRead, false, a.listTSIGKeys),
|
|
perm(post, "/api/v1/tsig-keys", auth.PermSettingsAdm, true, a.createTSIGKey),
|
|
perm(put, "/api/v1/tsig-keys/{id}", auth.PermSettingsAdm, true, a.updateTSIGKey),
|
|
perm(del, "/api/v1/tsig-keys/{id}", auth.PermSettingsAdm, true, a.deleteTSIGKey),
|
|
perm(get, "/api/v1/tsig-keys/{id}/secret", auth.PermSettingsAdm, false, a.getTSIGKeySecret),
|
|
perm(post, "/api/v1/tsig-keys/{id}/rotate", auth.PermSettingsAdm, true, a.rotateTSIGKey),
|
|
|
|
// Zones and records (ADR 0023).
|
|
perm(get, "/api/v1/zones", auth.PermIPAMRead, false, a.listZones),
|
|
perm(post, "/api/v1/zones", auth.PermIPAMWrite, true, a.createZone),
|
|
perm(get, "/api/v1/zones/derived", auth.PermIPAMRead, false, a.listDerivedZones),
|
|
perm(get, "/api/v1/zones/{id}", auth.PermIPAMRead, false, a.getZone),
|
|
perm(put, "/api/v1/zones/{id}", auth.PermIPAMWrite, true, a.updateZone),
|
|
perm(del, "/api/v1/zones/{id}", auth.PermIPAMWrite, true, a.deleteZone),
|
|
perm(get, "/api/v1/zones/{id}/records", auth.PermIPAMRead, false, a.listRecords),
|
|
perm(post, "/api/v1/zones/{id}/records", auth.PermIPAMWrite, true, a.createRecord),
|
|
perm(put, "/api/v1/zones/{id}/records/{recordId}", auth.PermIPAMWrite, true, a.updateRecord),
|
|
perm(del, "/api/v1/zones/{id}/records/{recordId}", auth.PermIPAMWrite, true, a.deleteRecord),
|
|
|
|
// Saved filters are personal and allowed in read-only mode.
|
|
perm(get, "/api/v1/saved-filters", auth.PermIPAMRead, false, a.listSavedFilters),
|
|
perm(post, "/api/v1/saved-filters", auth.PermIPAMRead, false, a.createSavedFilter),
|
|
perm(del, "/api/v1/saved-filters/{id}", auth.PermIPAMRead, false, a.deleteSavedFilter),
|
|
}
|
|
}
|