mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
The metrics endpoint fans out a gather to every connected host, so anyone who could reach the controller could read cluster statistics and put load on all hosts at will. Scraping now needs a user token holding the new core.controller.metrics permission. The token is accepted via Authorization: Bearer as well as X-Access-Token because Prometheus can only send the former. Concurrent scrapes share one gather so hosts see at most one metrics request at a time. The three connection gauges in controller.ts now no-op before the controller exists, like the other gauges there, so the endpoint can be tested against the mock controller. Closes #1009 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
596 lines
16 KiB
TypeScript
596 lines
16 KiB
TypeScript
/**
|
|
* User and permissions library
|
|
*
|
|
* Defines data types for users, roles and permissions.
|
|
*
|
|
* @author Hornwitser
|
|
* @module lib/users
|
|
*/
|
|
import type { PluginNodeEnvInfo, PluginWebpackEnvInfo } from "./plugin.js";
|
|
import { Permission } from "./data/index.js";
|
|
|
|
export const permissions = new Map<string, Permission>();
|
|
|
|
/**
|
|
* Registry of known permission names.
|
|
*
|
|
* Plugins add their permissions with module augmentation:
|
|
*
|
|
* ```ts
|
|
* declare module "@clusterio/lib" {
|
|
* export interface Permissions {
|
|
* "foo_frobber.frobnicate": never;
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* Only the keys matter, the values are always never.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
export interface Permissions extends Record<(typeof corePermissions)[number]["name"], never> {}
|
|
|
|
/** Name of a permission known at compile time, see {@link Permissions}. */
|
|
export type PermissionName = keyof Permissions;
|
|
|
|
/** Definition of a permission, see {@link definePermission}. */
|
|
export type PermissionDefinition = {
|
|
/** The internal name for the permission. */
|
|
name: PermissionName,
|
|
/** User facing name for the permission. */
|
|
title: string,
|
|
/** User facing description. */
|
|
description: string,
|
|
/** If true this permission is granted by the generated Player role by default. */
|
|
grantByDefault?: boolean,
|
|
};
|
|
|
|
/**
|
|
* Define a new user permission for Clusterio
|
|
*
|
|
* Defines a permission for use in Clusterio. Defined permissions can be
|
|
* granted to roles, and checked on users. Name should be `lower_case`
|
|
* style and start with the plugin name followed by a dot.
|
|
*
|
|
* Plugins should prefer declaring permissions in the `permissions` array
|
|
* of their {@link PluginDeclaration}, which is registered by
|
|
* {@link registerPluginPermissions}.
|
|
*
|
|
* @param def - The definition for the permission.
|
|
*/
|
|
export function definePermission({
|
|
name,
|
|
title,
|
|
description,
|
|
grantByDefault = false,
|
|
}: PermissionDefinition) {
|
|
if (typeof name !== "string" || !name.length) {
|
|
throw new Error("Expected name to be a non-empty string");
|
|
}
|
|
if (typeof title !== "string" || !title.length) {
|
|
throw new Error("Expected title to be a non-empty string");
|
|
}
|
|
if (typeof description !== "string" || !description.length) {
|
|
throw new Error("Expected description to be a non-empty string");
|
|
}
|
|
if (typeof grantByDefault !== "boolean") {
|
|
throw new Error("Expected grantByDefault to be a boolean");
|
|
}
|
|
|
|
if (permissions.has(name)) {
|
|
throw new Error(`Permission '${name}' is already defined`);
|
|
}
|
|
|
|
permissions.set(name, new Permission(name, title, description, grantByDefault));
|
|
}
|
|
|
|
/**
|
|
* Define all permissions declared by the provided plugin infos
|
|
*
|
|
* @param pluginInfos - Array of plugin info objects.
|
|
*/
|
|
export function registerPluginPermissions(pluginInfos: PluginNodeEnvInfo[] | PluginWebpackEnvInfo[]) {
|
|
for (const pluginInfo of pluginInfos) {
|
|
for (const permission of pluginInfo.permissions ?? []) {
|
|
if (typeof permission.name !== "string" || !permission.name.startsWith(`${pluginInfo.name}.`)) {
|
|
throw new Error(
|
|
`Expected name of permission '${permission.name}' for ${pluginInfo.name} ` +
|
|
`to start with '${pluginInfo.name}.'`
|
|
);
|
|
}
|
|
definePermission(permission);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Definitions for the built in permissions used in Clusterio.
|
|
// description should answer "this permission allows you to ___"
|
|
export const corePermissions = [
|
|
{
|
|
name: "core.admin",
|
|
title: "Administrator",
|
|
description: "Bypass all permission checks.",
|
|
},
|
|
{
|
|
name: "core.control.connect",
|
|
title: "Connect to WebSocket",
|
|
description: "Connect to the controller's WebSocket control interface.",
|
|
grantByDefault: true,
|
|
},
|
|
|
|
{
|
|
name: "core.controller.stop",
|
|
title: "Stop controller",
|
|
description:
|
|
"Stop the Node.js controller process making the cluster inoperable until someone with access to the " +
|
|
"system it runs on manually starts it again.",
|
|
},
|
|
{
|
|
name: "core.controller.restart",
|
|
title: "Restart controller",
|
|
description: "Restart the Node.js controller process if the system is set up for restarting.",
|
|
},
|
|
{
|
|
name: "core.controller.update",
|
|
title: "Update controller",
|
|
description: "Remotely update the controller if the controller allows for remote updates.",
|
|
},
|
|
{
|
|
name: "core.controller.get_config",
|
|
title: "Get controller config",
|
|
description: "Get the config of controller.",
|
|
},
|
|
{
|
|
name: "core.controller.update_config",
|
|
title: "Modify controller config",
|
|
description: "Modify the controller config or entries of the controller config.",
|
|
},
|
|
{
|
|
name: "core.controller.metrics",
|
|
title: "Scrape metrics",
|
|
description: "Fetch the Prometheus metrics for the cluster from the controller's /metrics endpoint.",
|
|
},
|
|
{
|
|
name: "core.system.subscribe",
|
|
title: "Subscribe to system updates",
|
|
description:
|
|
"Subscribe to be notified when the system info detailing system specific information along with metrics " +
|
|
"such as cpu, memory and disk usage for the controller and hosts are updated.",
|
|
},
|
|
{
|
|
name: "core.host.stop",
|
|
title: "Stop Hosts",
|
|
description:
|
|
"Stop Node.js host processes making the host inaccessible until someone with access to the system " +
|
|
"it runs on manually starts it again.",
|
|
},
|
|
{
|
|
name: "core.host.restart",
|
|
title: "Restart Hosts",
|
|
description: "Restart Node.js host processes if the system they run on are set up for restarting.",
|
|
},
|
|
{
|
|
name: "core.host.update",
|
|
title: "Update Hosts",
|
|
description: "Remotely update a host if the host allows for remote updates.",
|
|
},
|
|
{
|
|
name: "core.host.get_config",
|
|
title: "Get host configs",
|
|
description: "Get the config of hosts.",
|
|
},
|
|
{
|
|
name: "core.host.update_config",
|
|
title: "Modify host configs",
|
|
description: "Modify the host config or entries of the host config.",
|
|
},
|
|
{
|
|
name: "core.host.list",
|
|
title: "List hosts",
|
|
description: "Get the full list of hosts in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.host.subscribe",
|
|
title: "Subscribe to host updates",
|
|
description: "Subscribe to be notified on updates on the details of hosts.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.host.generate_token",
|
|
title: "Generate host token",
|
|
description: "Generate tokens for hosts to connect to the cluster with.",
|
|
},
|
|
{
|
|
name: "core.host.revoke_token",
|
|
title: "Revoke host token",
|
|
description: "Revoke access token and terminate existing connection for hosts.",
|
|
},
|
|
{
|
|
name: "core.host.create_config",
|
|
title: "Create host config",
|
|
description: "Create host configs via the controller.",
|
|
},
|
|
|
|
{
|
|
name: "core.instance.get",
|
|
title: "Get instance",
|
|
description: "Get the details of an instance in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.instance.list",
|
|
title: "List instances",
|
|
description: "Get the full list of instances in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.instance.subscribe",
|
|
title: "Subscribe to instance updates",
|
|
description: "Subscribe to be notified on updates on the details and status of instances.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.instance.create",
|
|
title: "Create instance",
|
|
description: "Create new instances on the controller.",
|
|
},
|
|
{
|
|
name: "core.instance.get_config",
|
|
title: "Get instance config",
|
|
description: "Get the config of instances.",
|
|
},
|
|
{
|
|
name: "core.instance.update_config",
|
|
title: "Modify instance config",
|
|
description: "Modify the config or entries of the config for instances.",
|
|
},
|
|
{
|
|
name: "core.instance.assign",
|
|
title: "Assign instance",
|
|
description: "Assign or reassign instances to a host.",
|
|
},
|
|
{
|
|
name: "core.instance.save.list",
|
|
title: "List saves",
|
|
description: "List the saves currently on instances.",
|
|
},
|
|
{
|
|
name: "core.instance.save.subscribe",
|
|
title: "Subscribe to save updates",
|
|
description: "Subscribe to be notifed on updates to the list of saves of instances.",
|
|
},
|
|
{
|
|
name: "core.instance.save.create",
|
|
title: "Create new save",
|
|
description: "Create new savegames on instances.",
|
|
},
|
|
{
|
|
name: "core.instance.save.rename",
|
|
title: "Rename save",
|
|
description: "Rename savegames on instances.",
|
|
},
|
|
{
|
|
name: "core.instance.save.copy",
|
|
title: "Copy save",
|
|
description: "Create copies of savegames on instances.",
|
|
},
|
|
{
|
|
name: "core.instance.save.delete",
|
|
title: "Delete save",
|
|
description: "Delete savegames on instances.",
|
|
},
|
|
{
|
|
name: "core.instance.save.upload",
|
|
title: "Upload save",
|
|
description: "Upload savegames to instances.",
|
|
},
|
|
{
|
|
name: "core.instance.save.transfer",
|
|
title: "Transfer save",
|
|
description: "Transfer savegames between instances.",
|
|
},
|
|
{
|
|
name: "core.instance.save.download",
|
|
title: "Download save",
|
|
description: "Download savegames from instances.",
|
|
},
|
|
{
|
|
name: "core.instance.export_data",
|
|
title: "Export locale and icons",
|
|
description: "Export the the locale and icons from an instance and upload it to the controller.",
|
|
},
|
|
{
|
|
name: "core.instance.extract_players",
|
|
title: "Extract player stats from running save",
|
|
description:
|
|
"Run extraction to create a user for each player in the save and set the online time from the save.",
|
|
},
|
|
{
|
|
name: "core.instance.start",
|
|
title: "Start instance",
|
|
description: "Start instances.",
|
|
},
|
|
{
|
|
name: "core.instance.restart",
|
|
title: "Restart instance",
|
|
description: "Restart instances.",
|
|
},
|
|
{
|
|
name: "core.instance.load_scenario",
|
|
title: "Load scenario",
|
|
description: "Start instances by loading a scenario.",
|
|
},
|
|
{
|
|
name: "core.instance.stop",
|
|
title: "Stop instance",
|
|
description: "Stop instances.",
|
|
},
|
|
{
|
|
name: "core.instance.kill",
|
|
title: "Kill instance",
|
|
description: "Terminate running instances without saving or cleanup.",
|
|
},
|
|
{
|
|
name: "core.instance.delete",
|
|
title: "Delete instance",
|
|
description: "Delete instances (includes config and all files stored like saves and logs).",
|
|
},
|
|
{
|
|
name: "core.instance.send_rcon",
|
|
title: "Send RCON",
|
|
description: "Send arbitrary RCON commands to instances.",
|
|
},
|
|
|
|
{
|
|
name: "core.mod_pack.get",
|
|
title: "Retrieve mod pack",
|
|
description: "Get the details of a mod pack in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod_pack.list",
|
|
title: "List mod packs",
|
|
description: "Get the full list of a mod packs in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod_pack.subscribe",
|
|
title: "Subscribe to mod pack updates",
|
|
description: "Subscribe to be notified on updates on the details of mod packs.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod_pack.create",
|
|
title: "Create mod pack",
|
|
description: "Create new mod pack in the cluster.",
|
|
},
|
|
{
|
|
name: "core.mod_pack.update",
|
|
title: "Update mod pack",
|
|
description: "Edit existing mod pack definition in the cluster.",
|
|
},
|
|
{
|
|
name: "core.mod_pack.delete",
|
|
title: "Delete mod pack",
|
|
description: "Delete mod pack definition in the cluster.",
|
|
},
|
|
{
|
|
name: "core.mod.get",
|
|
title: "Get mods",
|
|
description: "Get the details of a mod stored in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod.list",
|
|
title: "List mods",
|
|
description: "Get the full list of mods stored on the controller.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod.search",
|
|
title: "Search mods",
|
|
description: "Search through the list of mods stored on the controller.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod.search_portal",
|
|
title: "Search mod portal",
|
|
description: "Search for mods on the Factorio mod portal.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod.subscribe",
|
|
title: "Subscribe to mod updates",
|
|
description: "Subscribe to be notified on updates on the details of mods.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod.upload",
|
|
title: "Upload mods",
|
|
description: "Upload mods to the controller.",
|
|
},
|
|
{
|
|
name: "core.mod.download",
|
|
title: "Download mod",
|
|
description: "Download mods stored on the controller.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.mod.delete",
|
|
title: "Delete mod",
|
|
description: "Delete mods stored on the controller.",
|
|
},
|
|
{
|
|
name: "core.mod.download_from_portal",
|
|
title: "Download mods from portal",
|
|
description: "Allow downloading mods directly from the Factorio mod portal to the controller.",
|
|
grantByDefault: true,
|
|
},
|
|
|
|
{
|
|
name: "core.permission.list",
|
|
title: "List permissions",
|
|
description: "Get the full list of permissions in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
|
|
{
|
|
name: "core.role.list",
|
|
title: "List roles",
|
|
description: "Get the full list of roles and their permissions in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.role.subscribe",
|
|
title: "Subscribe to role updates",
|
|
description: "Subscribe to be notified on updates on the details of roles.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.role.create",
|
|
title: "Create role",
|
|
description: "Create new roles with permissions.",
|
|
},
|
|
{
|
|
name: "core.role.update",
|
|
title: "Update role",
|
|
description: "Edit existing roles including permissions they grant.",
|
|
},
|
|
{
|
|
name: "core.role.delete",
|
|
title: "Delete role",
|
|
description: "Permanently delete roles.",
|
|
},
|
|
|
|
{
|
|
name: "core.user.get",
|
|
title: "Get user",
|
|
description: "Get the details of a user in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.user.list",
|
|
title: "List users",
|
|
description: "Get the full list of users in the cluster.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.user.subscribe",
|
|
title: "Subscribe to user updates",
|
|
description: "Subscribe to be notified on updates on the details and status of users.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.user.create",
|
|
title: "Create users",
|
|
description: "Create user acounts with a given name.",
|
|
},
|
|
{
|
|
name: "core.user.revoke_token",
|
|
title: "Revoke user token",
|
|
description: "Revoke access token and terminate all existing control connections for oneself.",
|
|
grantByDefault: true,
|
|
},
|
|
{
|
|
name: "core.user.revoke_other_token",
|
|
title: "Revoke other user's token",
|
|
description: "Allow revoking tokens for other users than oneself. Requires Revoke user token.",
|
|
},
|
|
{
|
|
name: "core.user.update_roles",
|
|
title: "Update user roles",
|
|
description: "Add or remove any role to any user.",
|
|
},
|
|
{
|
|
name: "core.user.set_admin",
|
|
title: "Set user admin status",
|
|
description: "Promote or demote any user to Factorio admin.",
|
|
},
|
|
{
|
|
name: "core.user.set_banned",
|
|
title: "Set user ban status",
|
|
description: "Ban or unban any user.",
|
|
},
|
|
{
|
|
name: "core.user.set_whitelisted",
|
|
title: "Set user whitelist status",
|
|
description: "Add or remove any user to/from the whitelist.",
|
|
},
|
|
{
|
|
name: "core.user.delete",
|
|
title: "Delete user",
|
|
description: "Delete users and all data stored for them.",
|
|
},
|
|
{
|
|
name: "core.user.bulk_import",
|
|
title: "Bulk user import",
|
|
description: "Bulk import users including admin, whitelist, and bans." +
|
|
" (Imports types are restricted by other permissions, e.g. core.user.set_admin)",
|
|
},
|
|
{
|
|
name: "core.user.bulk_export",
|
|
title: "Bulk user export",
|
|
description: "Bulk export users including admin, whitelist, and bans.",
|
|
},
|
|
{
|
|
name: "core.user.bulk_restore",
|
|
title: "Bulk user restore",
|
|
description: "Bulk restore users including admin, whitelist, and bans." +
|
|
" (Restore types are restricted by other permissions, e.g. core.user.set_admin)",
|
|
},
|
|
|
|
{
|
|
name: "core.log.follow",
|
|
title: "Follow cluster log",
|
|
description: "Receive new entries in the cluster log. Required to see instance console.",
|
|
},
|
|
{
|
|
name: "core.log.query",
|
|
title: "Query cluster log",
|
|
description: "Query past entries in the cluster log. Required to see past entries in instance console.",
|
|
},
|
|
|
|
{
|
|
name: "core.plugin.list",
|
|
title: "List plugins",
|
|
description: "List all installed plugins on a machine.",
|
|
},
|
|
{
|
|
name: "core.plugin.update",
|
|
title: "Update plugin",
|
|
description: "Remotely update a plugin if the target allows remote updates of plugins.",
|
|
},
|
|
{
|
|
name: "core.plugin.install",
|
|
title: "Install plugin",
|
|
description: "Remotely install a plugin if the target allows remote installs of plugins.",
|
|
},
|
|
|
|
{
|
|
name: "core.debug.dump_ws",
|
|
title: "Dump WebSocket",
|
|
description: "Dump all WebSocket communicatation from the controller.",
|
|
},
|
|
|
|
{
|
|
name: "core.external.get_factorio_versions",
|
|
title: "Get factorio versions",
|
|
description: "Get list of all factorio versions from Wube",
|
|
},
|
|
|
|
{
|
|
name: "core.external.get_latest_releases",
|
|
title: "Get factorio releases",
|
|
description: "Get the latest stable and experimental factorio releases from Wube",
|
|
},
|
|
] as const satisfies readonly {
|
|
name: string,
|
|
title: string,
|
|
description: string,
|
|
grantByDefault?: boolean,
|
|
}[];
|
|
|
|
for (const permission of corePermissions) {
|
|
definePermission(permission);
|
|
}
|