ci/woodpecker/push/main-amd64-build Pipeline was successful
Agent, Probe and Backend now start from one static compose.yaml plus a local
.env: the panel (or the release assets, for the very first Backend) provides
the file, the panel's command writes the public values into .env, checks the
file's SHA-256 and runs compose up. Updating is compose pull && compose up -d.
The image is chosen by the CI channel (:latest, or MOSHIRO_VERSION in .env),
not by the panel; the previous one-line script stays as a fallback.
- packaging/compose/{agent,probe,backend}/compose.yaml are the single source,
embedded in the Backend and served at /install/compose/{file}; the root
compose.yaml moved there. Releases also carry them and list them in the
signed SHA256SUMS.
- The container entrypoint defaults the node name to the host name.
- Join token creation returns a compose block (.env, url, hash, per-platform
commands); the admin dialog leads with it.
- Remove code with no references (HotLayer members, LiveHub.SubscriberCount,
QueryService.ParseParams, ReleaseService.Forget, a few constants) and reduce
the install-code lookup to the role it is for.
137 lines
6.0 KiB
C#
137 lines
6.0 KiB
C#
namespace MoShiro.Backend.Application;
|
|
|
|
/// <summary>
|
|
/// 能力(capability):一项具体的权限。端点声明自己需要哪一项,<see cref="Principal.Has"/> 是唯一的判断入口,
|
|
/// 会话、API key 都走它。设计见 docs/security/access.md 第 3 节。
|
|
/// </summary>
|
|
public static class Capability
|
|
{
|
|
/// <summary>任何已认证的主体(会话或 API key);只有登出、查看自己这类端点用它。</summary>
|
|
public const string Authenticated = "authenticated";
|
|
|
|
public const string PublicRead = "public.read";
|
|
|
|
public const string FleetRead = "fleet.read";
|
|
public const string FleetWrite = "fleet.write";
|
|
public const string FleetEnroll = "fleet.enroll";
|
|
public const string FleetRevoke = "fleet.revoke";
|
|
|
|
public const string TasksRead = "tasks.read";
|
|
public const string TasksWrite = "tasks.write";
|
|
|
|
public const string DataRead = "data.read";
|
|
public const string DataWrite = "data.write";
|
|
|
|
public const string SettingsRead = "settings.read";
|
|
public const string SettingsWrite = "settings.write";
|
|
|
|
public const string UpdateRead = "update.read";
|
|
public const string UpdateConfirm = "update.confirm";
|
|
|
|
public const string KeysRead = "keys.read";
|
|
public const string KeysManage = "keys.manage";
|
|
|
|
public const string AccountsRead = "accounts.read";
|
|
public const string AccountsManage = "accounts.manage";
|
|
|
|
public const string AuditRead = "audit.read";
|
|
public const string EventsRead = "events.read";
|
|
public const string OpsMetrics = "ops.metrics";
|
|
|
|
// 预留:外部 webhook 回调和数据拉取(docs/security/access.md 6.3),目录里先存在,实现放后面
|
|
public const string DataExport = "data.export";
|
|
public const string WebhooksRead = "webhooks.read";
|
|
public const string WebhooksManage = "webhooks.manage";
|
|
}
|
|
|
|
/// <summary>一项能力的元数据。<c>Trust</c>:改变信任或有破坏性的能力(吊销、账户),界面上特别标出,授权与其他能力一样靠 API key 或登录会话;真正由 passkey 签名保护的是操作员集合,那是节点自己验证的;<c>Reserved</c>:还没有对应的功能。</summary>
|
|
public sealed record CapabilityInfo(string Id, string Group, bool Trust = false, bool Reserved = false, params string[] Implies);
|
|
|
|
public sealed record CapabilityPreset(string Id, IReadOnlyList<string> Capabilities);
|
|
|
|
/// <summary>能力目录:分组、依赖(写蕴含读)、预设,以及创建 API key 时的校验。</summary>
|
|
public static class CapabilityCatalog
|
|
{
|
|
public static IReadOnlyList<CapabilityInfo> All { get; } =
|
|
[
|
|
new(Capability.PublicRead, "public"),
|
|
|
|
new(Capability.FleetRead, "fleet"),
|
|
new(Capability.FleetWrite, "fleet", false, false, Capability.FleetRead),
|
|
new(Capability.FleetEnroll, "fleet", false, false, Capability.FleetRead),
|
|
new(Capability.FleetRevoke, "fleet", true, false, Capability.FleetRead),
|
|
|
|
new(Capability.TasksRead, "tasks"),
|
|
new(Capability.TasksWrite, "tasks", false, false, Capability.TasksRead),
|
|
|
|
new(Capability.DataRead, "data"),
|
|
new(Capability.DataWrite, "data", false, false, Capability.DataRead),
|
|
new(Capability.DataExport, "data", false, true, Capability.DataRead),
|
|
|
|
new(Capability.SettingsRead, "settings"),
|
|
new(Capability.SettingsWrite, "settings", false, false, Capability.SettingsRead),
|
|
|
|
new(Capability.UpdateRead, "update"),
|
|
new(Capability.UpdateConfirm, "update", false, false, Capability.UpdateRead),
|
|
|
|
new(Capability.KeysRead, "keys"),
|
|
new(Capability.KeysManage, "keys", false, false, Capability.KeysRead),
|
|
|
|
new(Capability.AccountsRead, "accounts"),
|
|
new(Capability.AccountsManage, "accounts", true, false, Capability.AccountsRead),
|
|
|
|
new(Capability.EventsRead, "ops"),
|
|
new(Capability.AuditRead, "ops"),
|
|
new(Capability.OpsMetrics, "ops"),
|
|
|
|
new(Capability.WebhooksRead, "integrations", false, true),
|
|
new(Capability.WebhooksManage, "integrations", false, true, Capability.WebhooksRead),
|
|
];
|
|
|
|
private static readonly Dictionary<string, CapabilityInfo> s_byId = All.ToDictionary(c => c.Id, StringComparer.Ordinal);
|
|
|
|
public static IReadOnlyList<string> Groups { get; } = [.. All.Select(c => c.Group).Distinct()];
|
|
|
|
public static IReadOnlyList<CapabilityPreset> Presets { get; } =
|
|
[
|
|
new("read-only", [.. All.Where(c => !c.Reserved && c.Id.EndsWith(".read", StringComparison.Ordinal)).Select(c => c.Id)]),
|
|
new("operations", Expand([Capability.PublicRead, Capability.FleetWrite, Capability.TasksWrite, Capability.DataWrite, Capability.SettingsWrite, Capability.UpdateConfirm, Capability.EventsRead, Capability.AuditRead])),
|
|
new("enroll", Expand([Capability.FleetEnroll])),
|
|
new("all", [.. All.Select(c => c.Id)]),
|
|
];
|
|
|
|
public static bool IsKnown(string id) => s_byId.ContainsKey(id);
|
|
|
|
/// <summary>补上蕴含的能力(写蕴含读),去重并排序,结果稳定。</summary>
|
|
public static IReadOnlyList<string> Expand(IEnumerable<string> ids)
|
|
{
|
|
var result = new SortedSet<string>(StringComparer.Ordinal);
|
|
foreach (string id in ids)
|
|
{
|
|
Add(result, id);
|
|
}
|
|
|
|
return [.. result];
|
|
}
|
|
|
|
private static void Add(SortedSet<string> set, string id)
|
|
{
|
|
if (id is Capability.Authenticated || !set.Add(id) || !s_byId.TryGetValue(id, out CapabilityInfo? info))
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (string implied in info.Implies)
|
|
{
|
|
Add(set, implied);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 登录会话的能力:全部,包括信任类,密码登录和 passkey 登录一样。区别在于敏感操作:信任类的操作还要有效的提权窗口(<see cref="ElevationService"/>),
|
|
/// 窗口要当场触碰 passkey 才能开,所以密码泄露的人做不了这些,日常管理却不需要每次触碰。
|
|
/// </summary>
|
|
public static IReadOnlyList<string> ForSession() => [.. All.Select(c => c.Id)];
|
|
|
|
}
|