Skip to content

Commit f64da5d

Browse files
committed
Add secure OpenAPI backend and public release hardening
1 parent 1a29f9b commit f64da5d

22 files changed

Lines changed: 2232 additions & 143 deletions

‎.dockerignore‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.git
2+
.github
3+
.DS_Store
4+
.env
5+
.env.*
6+
config
7+
results
8+
.tmp
9+
*.log
10+
*.out
11+
*.err
12+
*.gguf
13+
id_rsa*
14+
id_ed25519*
15+
*.key
16+
*.pem

‎.github/workflows/test.yml‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: test
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
test:
12+
strategy:
13+
matrix:
14+
os:
15+
- ubuntu-latest
16+
- macos-latest
17+
runs-on: ${{ matrix.os }}
18+
steps:
19+
- uses: actions/checkout@v4
20+
- run: make test

‎.gitignore‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
.DS_Store
22
.env
3+
.env.*
34
config
5+
*.key
6+
*.pem
7+
id_rsa*
8+
id_ed25519*
9+
*.gguf
10+
llama-server
11+
__pycache__/
12+
*.pyc
413
*.log
514
*.out
615
*.err

‎CONTRIBUTING.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Contributing
2+
3+
Run the local checks before opening a pull request:
4+
5+
```bash
6+
make test
7+
git diff --check
8+
```
9+
10+
Changes must remain compatible with macOS Bash 3.2 unless the affected component is
11+
explicitly Python-only. OpenAPI code must use the standard library unless a new dependency
12+
is justified and documented.
13+
14+
Never commit AK/SK values, tokens, SSH keys, personal HOME paths, job IDs, prompts,
15+
answers, model weights, or compiled `llama-server` binaries. Tests must use example
16+
accounts such as `alice` and non-routable hosts such as `example.test`.
17+
18+
Mutating OpenAPI and Slurm operations must not be retried automatically after an ambiguous
19+
timeout. Query state first to avoid duplicate jobs.

‎Dockerfile‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@ ENV TZ=Asia/Shanghai
1616
SHELL ["/bin/bash", "-c"]
1717

1818
RUN apt-get update && \
19-
apt-get install -y --no-install-recommends ca-certificates openssh-server sudo curl && \
19+
apt-get install -y --no-install-recommends ca-certificates curl && \
2020
rm -rf /var/lib/apt/lists/*
2121

22-
RUN mkdir -p /opt/scnet-aichat/bin /var/run/sshd /root/.ssh
22+
RUN useradd --system --uid 10001 --create-home --home-dir /home/scnet-aichat scnet-aichat && \
23+
mkdir -p /opt/scnet-aichat/bin && \
24+
chown -R scnet-aichat:scnet-aichat /opt/scnet-aichat
2325
COPY llama-server /opt/scnet-aichat/bin/llama-server
24-
COPY start-server.sh /opt/scnet-aichat/start-server.sh
26+
COPY server/start-server.sh /opt/scnet-aichat/start-server.sh
2527
RUN chmod 755 /opt/scnet-aichat/bin/llama-server /opt/scnet-aichat/start-server.sh && \
26-
ssh-keygen -A
28+
chown -R scnet-aichat:scnet-aichat /opt/scnet-aichat
2729

2830
ENV SCNET_LLAMA_SERVER=/opt/scnet-aichat/bin/llama-server
2931
ENV SCNET_MODEL_PATH=/models/EVA-Qwen2.5-14B-v0.2-Q4_0.gguf
3032
ENV SCNET_PORT=8080
3133

34+
USER scnet-aichat
3235
EXPOSE 8080
3336
CMD ["/opt/scnet-aichat/start-server.sh"]

‎Makefile‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ test:
88

99
install:
1010
install -d "$(DESTDIR)$(PREFIX)/bin" "$(DESTDIR)$(SHAREDIR)" \
11-
"$(DESTDIR)$(SHAREDIR)/worker" "$(DESTDIR)$(SHAREDIR)/server"
11+
"$(DESTDIR)$(SHAREDIR)/worker" "$(DESTDIR)$(SHAREDIR)/server" \
12+
"$(DESTDIR)$(SHAREDIR)/scripts"
1213
install -m 755 scnet-aichat "$(DESTDIR)$(PREFIX)/bin/scnet-aichat"
1314
install -m 755 install.sh uninstall.sh "$(DESTDIR)$(SHAREDIR)/"
1415
install -m 755 worker/scnet-aichat-worker.slurm "$(DESTDIR)$(SHAREDIR)/worker/scnet-aichat-worker.slurm"
1516
install -m 755 server/llama-server.slurm server/build-server.sh server/start-server.sh "$(DESTDIR)$(SHAREDIR)/server/"
17+
install -m 755 scripts/scnet-openapi.py "$(DESTDIR)$(SHAREDIR)/scripts/scnet-openapi.py"
1618
install -m 644 config.example "$(DESTDIR)$(SHAREDIR)/config.example"
1719

1820
uninstall:

‎README.md‎

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# scnet-aichat
22

3-
一个纯 Bash 的 SCNet/Slurm AI 问答面板。默认每个问题提交一个 Slurm 推理作业;
4-
另外提供持久 `llama-server` 模式,用于连续问答。
3+
一个 SCNet/Slurm AI 问答面板。支持 SSH 和 SCNet OpenAPI 两种 backend;
4+
默认每个问题提交一个 Slurm 推理作业,SSH backend 另外提供持久
5+
`llama-server` 模式。
56

67
项目不在登录节点运行模型,也不包含模型、SSH 私钥、访问令牌、作业日志或个人路径。
78

@@ -12,6 +13,9 @@
1213
- Debian
1314
- 其他提供 Bash、OpenSSH、`awk`、`mktemp` 的 Unix-like 系统
1415

16+
SSH backend 不需要 Python;OpenAPI backend 和持久模式的 JSON 编码需要 Python 3
17+
(持久模式也可使用 Perl)。
18+
1519
远端目标环境是 Slurm + Hygon DCU/DTK + llama.cpp HIP,内置 14B 单卡和 32B 四卡资源模板。
1620

1721
## 一、安装和首次运行
@@ -22,23 +26,51 @@
2226

2327
### Agent 一句话安装
2428

25-
仓库是私有的,因此执行前需要本机已经配置 GitHub SSH key,或已经运行
26-
`gh auth login`。Agent 可直接执行下面的一句话:
29+
```bash
30+
bash -lc 'set -eu; d="${SCNET_AICHAT_DIR:-$HOME/.local/src/scnet-aichat}"; if [ -d "$d/.git" ]; then git -C "$d" pull --ff-only; else mkdir -p "$(dirname "$d")"; git clone https://github.com/lql341/scnet-aichat.git "$d"; fi; "$d/install.sh" --check'
31+
```
32+
33+
该命令安装本地客户端、初始化非密钥配置并运行本地测试;不会连接 SCNet、下载模型、
34+
覆盖已有配置或删除远端数据。
35+
36+
### 1. 推荐:配置 OpenAPI
2737

2838
```bash
29-
bash -lc 'set -eu; d="${SCNET_AICHAT_DIR:-$HOME/.local/src/scnet-aichat}"; if [ -d "$d/.git" ]; then git -C "$d" pull --ff-only; else mkdir -p "$(dirname "$d")"; git clone [email protected]:lql341/scnet-aichat.git "$d"; fi; "$d/install.sh" --check --remote-install'
39+
scnet-aichat setup new
40+
scnet-aichat --backend openapi doctor
41+
scnet-aichat --backend openapi ask "请解释张量并行。"
3042
```
3143

32-
该命令会安装本地客户端、初始化配置(只在配置不存在时创建)、上传远端 worker,
33-
并运行本地和远端检查;不会下载模型、覆盖已有配置或删除远端数据。
44+
配置面板要求输入 SCNet 平台用户名、AccessKey 和 SecretKey。SecretKey 不回显。
45+
凭据存储规则与 `scnet-hpc` 兼容:
46+
47+
- macOS:保存在 Keychain,service 为 `scnet-hpc-openapi`;
48+
- Linux:有 `secret-tool` 时保存在 Secret Service;
49+
- 没有安全凭据库:不写明文文件,只接受
50+
`SCNET_OPENAPI_USER`、`SCNET_OPENAPI_ACCESS_KEY`、
51+
`SCNET_OPENAPI_SECRET_KEY` 环境变量。
3452

35-
如果使用 GitHub CLI:
53+
每次调用都会用 AK/SK 获取临时区域 token;token 不落盘。区域、scheduler、区域用户名
54+
和 HOME 自动发现,非密钥 metadata 以 `0600` 保存到
55+
`~/.config/scnet-aichat/openapi.json`。
56+
57+
首次 setup 会列出账号已授权的区域,并默认选中昆山;用户只选择区域名称,不输入或
58+
管理 Region ID。选择结果保存在本地私有 metadata 中。未来启用华中一区等其他区域时,
59+
运行 `scnet-aichat setup modify` 重新选择即可。
60+
61+
配置生命周期:
3662

3763
```bash
38-
bash -lc 'set -eu; d="${SCNET_AICHAT_DIR:-$HOME/.local/src/scnet-aichat}"; if [ -d "$d/.git" ]; then git -C "$d" pull --ff-only; else mkdir -p "$(dirname "$d")"; gh repo clone lql341/scnet-aichat "$d"; fi; "$d/install.sh" --check --remote-install'
64+
scnet-aichat setup status
65+
scnet-aichat setup modify
66+
scnet-aichat setup reset # 只删除本项目 metadata
67+
scnet-aichat setup reset-credentials # 显式删除共享 AK/SK
3968
```
4069

41-
### 1. 准备 SSH profile
70+
OpenAPI 当前支持单次作业模式。持久 `llama-server` 依赖 allocation 内的 `srun`,
71+
暂时只支持 SSH backend。
72+
73+
### 2. 可选:配置 SSH profile
4274

4375
客户端只调用 SSH profile,不保存私钥。`~/.ssh/config` 的最小示例:
4476

@@ -58,10 +90,10 @@ Host kseshell
5890
ssh kseshell 'hostname; echo "$HOME"'
5991
```
6092

61-
### 2. 获取项目并创建配置
93+
### 3. 手工获取项目和配置
6294

6395
```bash
64-
git clone git@github.com:lql341/scnet-aichat.git
96+
git clone https://github.com/lql341/scnet-aichat.git
6597
cd scnet-aichat
6698

6799
mkdir -p ~/.config/scnet-aichat
@@ -81,25 +113,24 @@ ${EDITOR:-vi} ~/.config/scnet-aichat/config
81113
./tests/test.sh
82114
mkdir -p ~/.local/bin ~/.local/share/scnet-aichat
83115
cp scnet-aichat ~/.local/bin/scnet-aichat
84-
cp -R worker server config.example ~/.local/share/scnet-aichat/
116+
cp -R worker server scripts config.example ~/.local/share/scnet-aichat/
85117
chmod 755 ~/.local/bin/scnet-aichat
86118
```
87119

88120
然后把 `~/.local/bin` 加入 `PATH`,创建并编辑
89-
`~/.config/scnet-aichat/config`,最后执行 `scnet-aichat install` 和
90-
`scnet-aichat doctor`。
121+
`~/.config/scnet-aichat/config`。选择 OpenAPI 时先运行
122+
`scnet-aichat setup new`;选择 SSH 时确保 SSH profile 已配置。
91123

92-
### 3. 安装远端 worker 并检查
124+
### 4. 安装远端 worker 并检查
93125

94126
```bash
95127
./scnet-aichat install
96128
./scnet-aichat doctor
97129
```
98130

99-
`install` 只上传一个 Slurm worker 到远端 `~/.scnet-aichat/worker.slurm`;
100-
不会上传 GGUF,也不会覆盖模型。
131+
`install` 通过当前 backend 上传 Slurm worker;不会上传 GGUF,也不会覆盖模型。
101132

102-
### 4. 打开面板
133+
### 5. 打开面板
103134

104135
```bash
105136
./scnet-aichat
@@ -108,6 +139,8 @@ chmod 755 ~/.local/bin/scnet-aichat
108139
直接输入问题即可提交作业。面板命令:
109140

110141
```text
142+
/backend ssh
143+
/backend openapi
111144
/model 14b
112145
/model 32b
113146
/mode job
@@ -134,6 +167,7 @@ chmod 755 ~/.local/bin/scnet-aichat
134167

135168
```bash
136169
./scnet-aichat ask "请解释张量并行。"
170+
./scnet-aichat --backend openapi ask "请解释张量并行。"
137171
./scnet-aichat --model 32b --max-tokens 256 ask "写一个简短示例。"
138172
./scnet-aichat --mode server --model 14b ask "连续问答的第一问。"
139173
./scnet-aichat --mode server --model 14b --preset eva-rp ask "开始角色扮演。"
@@ -234,7 +268,11 @@ chmod 755 ~/.local/bin/scnet-aichat
234268
也可以通过 `SCNET_AICHAT_CONFIG` 指定其他文件。所有选项见
235269
[`config.example`](config.example)。
236270

237-
如果未设置 `SCNET_REMOTE_HOME`,客户端会通过 SSH 自动读取远端 `$HOME`,再推导:
271+
配置文件使用受限的 `SCNET_*=value` 解析器,不会作为 Shell 脚本执行;包含
272+
`ACCESS_KEY`、`SECRET_KEY`、`API_KEY`、`TOKEN` 或 `PASSWORD` 的键会被拒绝。
273+
274+
如果未设置 `SCNET_REMOTE_HOME`,SSH backend 会通过 SSH 读取远端 `$HOME`;
275+
OpenAPI backend 会从区域中心信息发现 HOME。随后推导:
238276

239277
- 远端应用目录:`$HOME/.scnet-aichat`
240278
- llama.cpp:`$HOME/eva-k100/llama.cpp-b5046/build-gfx906/bin/llama-cli`
@@ -266,6 +304,9 @@ chmod 755 ~/.local/bin/scnet-aichat
266304
频繁问答推荐保持一个 Slurm 作业运行,让 `llama-server` 只加载一次模型。脚本位于
267305
[`server/llama-server.slurm`](server/llama-server.slurm)。
268306

307+
持久模式当前要求 `--backend ssh`。服务默认只监听计算节点的
308+
`127.0.0.1`,客户端通过 allocation 内的 `srun` 调用。
309+
269310
### 1. 编译 server
270311

271312
如果共享存储中已有本项目验证过的 llama.cpp 源码:
@@ -299,7 +340,7 @@ ssh kseshell '
299340
sbatch \
300341
--output=$HOME/.scnet-aichat/server/slurm-%j.out \
301342
--error=$HOME/.scnet-aichat/server/slurm-%j.err \
302-
--export=ALL,SCNET_SERVER_APP_DIR=$HOME/.scnet-aichat \
343+
--export=SCNET_SERVER_APP_DIR=$HOME/.scnet-aichat \
303344
$HOME/.scnet-aichat/server/llama-server.slurm
304345
'
305346
```
@@ -345,7 +386,8 @@ SCNet Dockerfile 构建要求 `COPY`/`ADD` 的文件放到用户家目录 `docke
345386
```bash
346387
mkdir -p ~/dockerFileTemp
347388
scp /path/to/llama-server kseshell:~/dockerFileTemp/llama-server
348-
scp server/start-server.sh kseshell:~/dockerFileTemp/start-server.sh
389+
ssh kseshell 'mkdir -p ~/dockerFileTemp/server'
390+
scp server/start-server.sh kseshell:~/dockerFileTemp/server/start-server.sh
349391
scp Dockerfile kseshell:~/dockerFileTemp/Dockerfile
350392
```
351393

@@ -356,7 +398,7 @@ scp Dockerfile kseshell:~/dockerFileTemp/Dockerfile
356398
- 服务端口 `8080`;
357399
- GGUF 通过持久存储挂载到 `/models`;
358400
- `SCNET_MODEL_PATH` 指向挂载后的 GGUF;
359-
- 需要鉴权时给 server 启动参数增加 `--api-key`。
401+
- 必须通过安全环境注入设置 `SCNET_SERVER_API_KEY`。
360402

361403
不要把 GGUF 权重提交到 Git 或 Docker build context。计算节点不能联网时,应在 SCNet
362404
镜像构建服务或可联网的构建环境完成构建;容器运行时只使用已打包依赖、挂载模型和
@@ -382,12 +424,20 @@ make test
382424
SCNET_AICHAT_REMOTE_TESTS=1 ./tests/test.sh
383425
```
384426

385-
测试覆盖 Bash 语法、Bash 3.2 兼容性、14B/32B 资源映射、非法参数拒绝,以及可选的
386-
远端 worker、模型和分区检查。
427+
测试覆盖 Bash/Python 语法、OpenAPI 签名和发现、凭据与路径脱敏、安装布局、
428+
Bash 3.2 兼容性、14B/32B 资源映射、非法参数拒绝,以及可选的远端检查。
429+
430+
2026-09-28 已在昆山区域完成 OpenAPI 端到端 smoke:请求文件上传、14B Slurm
431+
提交、状态轮询、模型推理、回答及性能日志下载均成功。
387432

388433
## 安全
389434

390-
- 不要把 SSH 私钥、GitHub token 或集群凭据写入配置文件。
435+
- AK/SK 只进入 Keychain、Secret Service 或当前进程环境,不写普通配置文件。
436+
- OpenAPI 区域 token 每次运行重新获取,不持久化。
437+
- 个人 HOME 和模型绝对路径不会出现在 `--dry-run` 或历史列表中。
391438
- `config`、`.env`、日志和结果目录默认不会被 Git 跟踪。
439+
- worker 默认在作业结束时删除 prompt、system prompt 和运行路径输入文件;回答及
440+
Slurm 日志仍保留在远端请求目录,用户应按所在站点的数据保留策略清理。
441+
- Slurm 持久服务只监听回环地址;容器服务强制要求 API key。
392442
- 客户端会校验模型、资源数字、远端路径和作业号。
393443
- `cancel` 只在用户显式执行时调用 `scancel`。

‎SECURITY.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Security policy
2+
3+
## Reporting a vulnerability
4+
5+
Do not open a public issue containing credentials, tokens, private keys, personal paths,
6+
job output, or account-specific SCNet data.
7+
8+
Use GitHub's private security-advisory reporting for this repository. Include the affected
9+
version or commit, reproduction steps, and the expected impact. Replace all usernames,
10+
HOME paths, job IDs, tokens, AK/SK values, and service URLs with placeholders.
11+
12+
## Credential boundary
13+
14+
- OpenAPI AK/SK belong in macOS Keychain, Linux Secret Service, or process environment.
15+
- Region tokens are ephemeral and must not be persisted.
16+
- SSH private keys belong under the user's `~/.ssh` directory with mode `0600`.
17+
- Public issues and logs must use `$REMOTE_HOME` in place of personal absolute paths.
18+
- Container inference must set `SCNET_SERVER_API_KEY`.
19+
20+
If a credential was exposed, revoke or rotate it before reporting the incident.

‎THIRD_PARTY.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Third-party components
2+
3+
This repository contains orchestration code and does not distribute model weights or a
4+
compiled inference runtime.
5+
6+
| Component | Use | License handling |
7+
|---|---|---|
8+
| llama.cpp | Remote `llama-cli` and `llama-server` runtime | Upstream MIT license; include its license when redistributing a binary or container image |
9+
| EVA-Qwen2.5 14B/32B | User-supplied GGUF model | Verify the exact model and quantization source before redistribution |
10+
| SCNet OpenAPI | Authentication, files, and Slurm job control | Platform API; users supply their own account credentials |
11+
12+
The top-level MIT license applies only to code and documentation in this repository.

‎config.example‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Copy this file to ~/.config/scnet-aichat/config and adjust as needed.
22

3+
# Leave unset to use the backend selected by `scnet-aichat setup`, otherwise ssh.
4+
# SCNET_BACKEND=ssh
5+
36
SCNET_PROFILE=kseshell
47
SCNET_PARTITION=kshdnormal
58

@@ -28,6 +31,13 @@ SCNET_POLL_SECONDS=5
2831
# Persistent server settings. /mode server or --mode server starts it on demand.
2932
# SCNET_LLAMA_SERVER=/public/home/your-user/eva-k100/llama.cpp-b5046/build-gfx906/bin/llama-server
3033
SCNET_SERVER_PORT=18080
34+
SCNET_SERVER_START_TIMEOUT=900
35+
36+
# OpenAPI metadata is discovered by `scnet-aichat setup` and stored locally
37+
# with mode 0600. AK/SK are stored in Keychain/Secret Service, never here.
38+
# Region and scheduler are selected interactively by `scnet-aichat setup`.
39+
# Their internal IDs are stored in private local metadata, not configured here.
40+
SCNET_OPENAPI_TIMEOUT=30
3141

3242
# Resource mapping. Keep the CPU/DCU ratio valid for the target partition.
3343
SCNET_14B_GPUS=1

0 commit comments

Comments
 (0)