Skip to content
Closed
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
18 changes: 3 additions & 15 deletions Auth/RemoteCommandGuard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
namespace SLDataAPI.Auth;

/// <summary>
/// SLDataAPI 自身管理 CLI(<c>sldataapi</c> / 别名 <c>slda</c>)的远程执行硬拒绝(层 1),
/// 以及"当前线程正在执行远程控制通道下发的命令"标记(供层 1 兜底与层 2 人工确认使用)。
/// SLDataAPI 自身管理 CLI(<c>sldataapi</c> / 别名 <c>slda</c>)的远程执行硬拒绝,
/// 以及"当前线程正在执行远程控制通道下发的命令"标记。
///
/// 动机:远程控制通道只能证明"请求持有某把 API Key",无法证明操作者身份。
/// 一旦某把 Key 拿到 /control/console/ 授权,就能凭控制台命令无限增发新 Key,
Expand All @@ -27,7 +27,7 @@ public static class RemoteCommandGuard
/// <summary>控制面拒绝文案(HTTP 403 / WS result 的 message)。</summary>
public const string RemoteDenyMessage =
"拒绝执行:SLDataAPI 管理命令(sldataapi / slda)不允许通过远程控制通道执行。" +
"API Key 的创建与吊销只能在服务器本地控制台(LocalAdmin / RemoteAdmin / 游戏内控制台)操作,并需人工确认。";
"API Key 的创建与吊销只能在服务器本地控制台(LocalAdmin / RemoteAdmin / 游戏内控制台)操作。";

/// <summary>
/// 命令是否会调起 SLDataAPI 管理 CLI。逐个空白分隔的 token 判定(不只看首 token,
Expand Down Expand Up @@ -87,16 +87,4 @@ public void Dispose()
if (_remoteDepth > 0) _remoteDepth--;
}
}

// ────────────── y/n 回答解析 ──────────────

/// <summary>解析人工确认的回答:仅明确的肯定回答算通过,空/无法识别一律按拒绝。</summary>
public static bool IsAffirmative(string? answer)
{
if (string.IsNullOrWhiteSpace(answer))
return false;

string a = answer!.Trim().ToLowerInvariant();
return a is "y" or "yes" or "是";
}
}
37 changes: 0 additions & 37 deletions Commands/ApikeyConfirmTestCommand.cs

This file was deleted.

21 changes: 2 additions & 19 deletions Commands/ApikeyCreateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Text;
using CommandSystem;
using SLDataAPI.Auth;
using SLDataAPI.Services;

namespace SLDataAPI.Commands;

Expand All @@ -27,23 +26,7 @@ public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out s
? string.Join(" ", arguments.Array!, arguments.Offset + 2, arguments.Count - 2)
: "";

// 层 2:真正的服务端操作者(LocalAdmin / RemoteAdmin / 游戏内控制台)必须在服务器桌面
// 新弹出的确认窗口里显式确认才会铸出新 Key。远程控制通道在层 1 已被硬拒绝,走不到这里;
// 即使走到,Confirm 也会因远程执行上下文在弹窗之前直接拒绝。
var confirm = new ConfirmPanelModel
{
Action = ConfirmAction.Create,
KeyId = id,
Template = template.Trim().ToLowerInvariant(),
Note = note,
};
if (!OperatorConfirmService.Confirm(confirm, out string denyReason))
{
response = $"已中止创建 API Key(未获服务端确认):{denyReason}\n确认面板会在服务器桌面新弹出一个 cmd 窗口(不占用 LocalAdmin 窗口),请在那个窗口按 Y 确认。";
Log.Warn($"[SLDataAPI] API Key 创建未获确认,已中止 id={id}:{denyReason}");
return false;
}

// 远程控制通道已被 RemoteCommandGuard 硬拒绝,走不到这里。
if (!ApiKeyService.TryCreate(id, template, note, out string plaintext, out string error))
{
response = "创建失败: " + error;
Expand All @@ -62,4 +45,4 @@ public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out s
Log.Info($"[SLDataAPI] 已创建 API Key id={id} template={template}(明文仅回显给命令发送者)");
return true;
}
}
}
7 changes: 3 additions & 4 deletions Commands/ApikeyParentCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,19 @@ public sealed class ApikeyParentCommand : ParentCommand

public override string Command => "apikey";
public override string[] Aliases => new[] { "key", "keys" };
public override string Description => "API Key 管理(create / revoke / list / confirmtest)";
public override string Description => "API Key 管理(create / revoke / list)";

public override void LoadGeneratedCommands()
{
RegisterCommand(new ApikeyCreateCommand());
RegisterCommand(new ApikeyRevokeCommand());
RegisterCommand(new ApikeyListCommand());
RegisterCommand(new ApikeyConfirmTestCommand());
}

protected override bool ExecuteParent(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
response = "用法:\n sldataapi apikey create <id> <duty|admin> [note]\n sldataapi apikey revoke <id>\n" +
" sldataapi apikey list\n sldataapi apikey confirmtest(自检确认窗口,不动任何 Key)";
" sldataapi apikey list";
return false;
}
}
}
17 changes: 2 additions & 15 deletions Commands/ApikeyRevokeCommand.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using CommandSystem;
using SLDataAPI.Auth;
using SLDataAPI.Services;

namespace SLDataAPI.Commands;

Expand All @@ -22,19 +21,7 @@ public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out s

string id = arguments.Array![arguments.Offset];

// 层 2:与 create 同理——吊销同样是密钥面变更(可被用来把值班 Key 踢掉后重铸),需要人工确认
var confirm = new ConfirmPanelModel
{
Action = ConfirmAction.Revoke,
KeyId = id,
};
if (!OperatorConfirmService.Confirm(confirm, out string denyReason))
{
response = $"已中止吊销 API Key(未获服务端确认):{denyReason}\n确认面板会在服务器桌面新弹出一个 cmd 窗口(不占用 LocalAdmin 窗口),请在那个窗口按 Y 确认。";
Log.Warn($"[SLDataAPI] API Key 吊销未获确认,已中止 id={id}:{denyReason}");
return false;
}

// 远程控制通道已被 RemoteCommandGuard 硬拒绝,走不到这里。
if (!ApiKeyService.TryRevoke(id, out string error))
{
response = "吊销失败: " + error;
Expand All @@ -45,4 +32,4 @@ public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out s
Log.Info($"[SLDataAPI] 已吊销 API Key id={id}");
return true;
}
}
}
11 changes: 0 additions & 11 deletions Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,4 @@ public class Config

/// <summary>控制日志最大条数,超出自动删除最旧条目(0/负数 = 不清理)。</summary>
public int ControlLogMaxRecords { get; set; } = 500;

// ================== API Key 管理的人工确认(v2.6.0-preview-DevOnly 安全加固) ==================

/// <summary>
/// `sldataapi apikey create|revoke` 的确认面板等待服务端操作者按键的秒数
/// (在服务器桌面新弹出的 cmd 窗口里 Y 确认 / N 取消,默认取消;LocalAdmin 窗口不受影响)。
/// 超时按拒绝处理(不创建 / 不吊销)。取值钳制在 5–120 秒;
/// 注意确认期间命令所在的主线程处于等待状态,不宜设置过长。
/// 该确认不可关闭:远程控制通道已被硬拒绝执行这些命令,本地确认是最后一道人工闸门。
/// </summary>
public int ApiKeyConfirmTimeoutSeconds { get; set; } = 20;
}
6 changes: 3 additions & 3 deletions Control/ControlController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ private static (int, string) RunCommand(string body)
if (req == null || string.IsNullOrWhiteSpace(req.command))
return (400, Json(false, "缺少 command 字段"));

// 层 1:SLDataAPI 自身的管理 CLI 一律不经远程控制通道执行(所有子命令,不只 apikey)。
// SLDataAPI 自身的管理 CLI 一律不经远程控制通道执行(所有子命令,不只 apikey)。
// 远程通道只能证明"持有某把 Key",无法证明操作者身份;放行等于任何拿到 console 授权的 Key
// 都能无限增发新 Key 并从同一条通道取回明文。这里连命令都不派发,直接回控制面错误。
if (RemoteCommandGuard.IsManagementCommand(req.command))
Expand Down Expand Up @@ -308,8 +308,8 @@ private static (int, string) RunCommand(string body)
/// </summary>
private static string ExecuteConsoleCommand(string command)
{
// 层 1 兜底:任何走到这里的命令都来自远程控制通道(HTTP /control/* 与 WS call 同源),
// 管理 CLI 在此二次硬拦;同时标记执行上下文,让层 2 的人工确认永远无法被远程"确认"通过。
// 兜底:任何走到这里的命令都来自远程控制通道(HTTP /control/* 与 WS call 同源),
// 管理 CLI 在此二次硬拦,并标记远程执行上下文。
if (RemoteCommandGuard.IsManagementCommand(command))
{
Log.Warn($"[SLDataAPI][Control] 已拒绝远程执行 SLDataAPI 管理命令(兜底): {command}");
Expand Down
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ voice_port: 8082
voice_record_enabled: false
report_enabled: false
control_log_enabled: true
api_key_confirm_timeout_seconds: 20 # create/revoke Key 的确认窗口等待秒数
```

完整字段、YAML 坑、token 写法见 Wiki [[Configuration]](https://github.com/DNTOF/SLDataAPI/wiki/Configuration)。
Expand Down Expand Up @@ -103,12 +102,9 @@ X-SLDataAPI-Key: <api_key>
sldataapi apikey create <id> <duty|admin> [note]
sldataapi apikey list
sldataapi apikey revoke <id>
sldataapi apikey confirmtest # 自检确认窗口,不创建也不吊销任何 Key
```

`create` / `revoke` 需在服务器本机确认:执行后会在服务器桌面**新弹出一个 cmd 窗口**显示确认面板(Y 确认 / N 取消,默认取消,超时拒绝),LocalAdmin 窗口不会被接管;弹不出窗口时退化为 LocalAdmin 的 `[y/N]` 行提示。`duty` 偏只读;`admin` 按端点 catalog 授权,**不会**自动开放 catalog 为 `false` 的路径(控制台、插件、文件等),可用 `endpoints_override` 单独放开。

装好后先跑一次 `sldataapi apikey confirmtest` 验证确认窗口:应在服务器桌面弹出一个**新的** cmd 窗口(标题 `SLDataAPI Security Confirm …`,蓝底面板、逐秒倒计时),按 `Y` 回显"自检通过"、按 `N` 或等到倒计时结束回显"未通过",全程 LocalAdmin 窗口的输出不被清屏也不被接管。
`sldataapi` / `slda` 管理 CLI 已被远程控制通道(HTTP + WS 的 `/control/console/command`)硬拒绝;本地控制台(LocalAdmin / RemoteAdmin / 游戏内控制台)执行 `create` / `revoke` 会立即生效,不再弹出确认窗口或 `[y/N]` 提示。`duty` 偏只读;`admin` 按端点 catalog 授权,**不会**自动开放 catalog 为 `false` 的路径(控制台、插件、文件等),可用 `endpoints_override` 单独放开。

路径与 curl 示例:Wiki [[Preview-HTTP-API]](https://github.com/DNTOF/SLDataAPI/wiki/Preview-HTTP-API)。稳定 2.5 仍用 [[HTTP-API]](https://github.com/DNTOF/SLDataAPI/wiki/HTTP-API)。

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ SLDataAPI 提供服务器数据查询和远程控制能力(含执行控制台

**不在范围内:**

- 已知需要控制面凭据(2.6 起为 API Key,早期为 `control_token`)才能触发的行为——持有凭据本身就等同于拥有服务器控制台权限,这是设计如此,不是漏洞(例如 `/control/console/command` 能执行任意命令)。**例外**:借远程控制通道增发/吊销 API Key 属越权提升,在范围内——`sldataapi` / `slda` 管理 CLI 已被远程执行路径硬拒绝,`create` / `revoke` 另需服务器本地控制台人工确认;任何绕过这两层的路径都请报告。
- 已知需要控制面凭据(2.6 起为 API Key,早期为 `control_token`)才能触发的行为——持有凭据本身就等同于拥有服务器控制台权限,这是设计如此,不是漏洞(例如 `/control/console/command` 能执行任意命令)。**例外**:借远程控制通道增发/吊销 API Key 属越权提升,在范围内——`sldataapi` / `slda` 管理 CLI 已被远程执行路径硬拒绝;任何绕过该硬拒绝的路径都请报告。
- 纯粹的资源消耗类拒绝服务(比如无限制发包把带宽打满),除非能绕过已有的连接数/请求体大小限制
- 依赖社会工程学(骗管理员泄露 token)的攻击路径
- 对已经过期不再维护的旧版本的报告
Expand Down
Loading