权限模型
SPC(系统权限控制)概述
TOS 7 引入 SPC(System Permission Control,系统权限控制) 系统,遵循最小权限原则,对应用的系统访问行为进行管控:
- 应用无法直接修改系统文件或获取 root 权限,所有权限请求需通过平台 API 提交
- 开发者需在权限声明中明确应用的权限需求,应用只有经平台审核通过后才能获得对应的访问权限
- 禁止任何绕过 SPC 权限检查的行为,此类应用将无法通过审核或被下架
概述
TOS7 遵循 最小权限原则。应用只能请求运行所必需的最小权限。TOS7 应用与 SPC(系统权限控制) 系统交互。应用必须:
- 在权限声明(第 8 节)中声明权限需求
- 不得绕过 SPC 权限检查
- 使用平台 API 进行权限请求,而不是直接修改系统文件
平台为 Deb 和 Docker 应用提供结构化的权限模型。
用户与用户组模型
严禁应用用户使用 root 权限。所有应用必须以非 root 专属用户运行。Deb 应用:
| 场景 | 用户 | 说明 | 配置要求 |
|---|---|---|---|
| 专属用户 | <appid> | 必须使用。由 preinst 脚本创建。权限最小。 | 强制 |
强制要求: 所有 Deb 应用必须创建专属用户(
<appid>),并以该用户运行应用,严禁使用 root 权限。专属用户需在preinst脚本中创建,确保应用运行时权限最小化。应用数据目录(如/Volume*/@apps/<appid>/)的权限需设置为专属用户所有,避免权限不足或越权访问。
创建专属用户:
# 在 preinst 中
# 系统将自动为新用户分配唯一的 UID
useradd --system --no-create-home --shell /usr/sbin/nologin <appid>
Docker 应用:
| 场景 | 用户 | 说明 |
|---|---|---|
| 非 root | UID:GID(如 1000:1000) | 必须使用。在 compose 中通过 user 字段指定。 |
文件系统权限
Deb 应用的标准目录权限:
| 路径 | 归属 | 权限 | 说明 |
|---|---|---|---|
/Volume*/@apps/<appid>/ | <appid>:<appid> | 755 | 应用目录(服务只读) |
/Volume*/@apps/<appid>/bin/ | <appid>:<appid> | 755 | 可执行文件 |
/Volume*/@apps/<appid>/config/ | <appid>:<appid> | 750 | 配置文件(服务只读) |
/Volume*/@apps/<appid>/site/ | <appid>:<appid> | 755 | Web UI 文件 |
/Volume*/@apps/<appid>/data/ | <appid>:<appid> | 750 | 运行时数据(读写) |
/Volume*/@apps/<appid>/logs/ | <appid>:<appid> | 750 | 应用日志(读写) |
注意 1:
/Volume*/中的*表示用户在安装时所选的卷号(例如 Volume1、Volume2)。
注意 2: 应用二进制和配置文件对服务用户应为只读。只有数据和日志目录应为可写。
注意 3:数据类型
- 运行时数据(
/Volume*/@apps/<appid>/data/)—— 应用生成的缓存、临时文件和运行时状态。此数据由应用管理,可安全重新生成。- 用户数据(通过
ter_share_add创建的共享文件夹)—— 持久化的业务数据(文档、照片、数据库)。此数据必须存储在/Volume*/下的共享文件夹中,以便用户通过 SMB/NFS 访问。
网络权限
| 权限 | Deb 应用 | Docker 应用 | 说明 |
|---|---|---|---|
| 绑定端口 | 在服务配置中绑定指定端口 | 在 compose 中映射端口 | 不得与系统端口冲突 |
| 访问本地服务 | 默认允许 | 使用 network_mode: host 或显式链接 | 尽量减少网络暴露 |
| 出站连接 | 允许 | 允许 | 出站无限制 |
共享文件夹访问
TNAS 共享文件夹是主要的数据访问机制。需要访问用户数据的应用必须:
- 创建共享文件夹,通过
ter_share_add:
ter_share_add -name <appid>-data -owner <appid>
- 或请求访问现有共享文件夹,通过加入
allusers组:
usermod -aG allusers <appid>
- Docker 应用通过卷挂载共享文件夹:
Volumes:
- /Volume*/<共享文件夹>:/data:rw # 读写访问
- /Volume*/<共享文件夹>:/media:ro # 只读访问
应用不得直接修改共享文件夹权限。使用 TOS 共享文件夹管理 API 或让用户手动配置访问权限。
权限请求流程
当应用需要访问共享文件夹时:
-
专用应用文件夹(推荐):
- 在 postinst 中通过
ter_share_add创建 - 应用拥有完整的读写权限
- 无需用户授权
- 在 postinst 中通过
-
用户共享文件夹(需要授权):
- 应用请求
allusers组成员资格 - 用户通过 TOS 共享文件夹设置授权文件夹访问
- 应用在权限声明中注明只读或读写需求
- 应用请求
-
权限格式:
# Docker 卷
- /Volume*/<共享文件夹>:/data:rw # 读写访问
- /Volume*/<共享文件夹>:/media:ro # 只读访问
系统资源限制
应用安装路径
第三方应用完整安装在 存储卷(数据盘)上的 /Volume*/@apps/<appid>/,而不是系统盘(/)。
*表示用户在安装时所选的卷号(例如 Volume1、Volume2 等)。- 所有应用文件——包括二进制文件、配置文件、日志、脚本和 Web UI 文件——均存储在
/Volume*/@apps/<appid>/下。 - 系统盘上仅保留轻量级的 注册/入口记录(用于 TOS 识别已安装的应用)。此记录占用空间可忽略不计,不构成任何容量问题。
- 只有系统内置应用才驻留在系统盘(
/usr/local/system_app_data/)上。
对第三方开发者而言: 由于整个应用(包括程序文件、配置和日志)安装在数据盘上,系统盘容量对您的应用不是问题。所有业务数据也应存储在数据盘(
/Volume*/)上,数据盘没有容量限制。
按应用类型的默认资源配额:
| 应用类型 | CPU 限制 | 内存限制 | 示例 |
|---|---|---|---|
| 媒体服务器 | 200%(2 核) | 2048M | Jellyfin, Plex, Emby |
| 下载管理器 | 100%(1 核) | 512M | Aria2, qBittorrent |
| 实用工具 | 50% | 256M | 文件管理器, 文本编辑器 |
| Web 服务 | 100%(1 核) | 512M | CMS, 博客, Wiki |
| 数据库 | 200%(2 核) | 2048M | MySQL, PostgreSQL, Redis |
| 安全类 | 50% | 256M | 防火墙, 杀毒软件 |
以上为平台默认值。开发者可在权限声明中附合理理由申请更高的限制。
Deb 应用(通过 systemd):
[Service]
# 内存限制
MemoryMax=512M
# CPU 配额(200% = 2 核)
CPUQuota=200%
# 文件描述符限制
LimitNOFILE=65536
# 进程数限制
LimitNPROC=256
Docker 应用(通过 compose):
services:
myapp:
deploy:
resources:
limits:
cpus: '2.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 128M
权限声明
注意: 下表是一个 示例,展示如何记录应用的权限需求。请将表中的值(端口号、文件路径、用户名)替换为应用实际使用的值。
为保持透明,应用应在 README.md 中记录其权限需求:
| 权限 | 理由 |
|---|---|
网络:端口 <你的端口> | Web UI 访问 |
文件系统:<你的数据路径> | 运行时数据存储 |
用户:<你的 appid>(系统用户) | 隔离的服务执行 |
| 共享文件夹:无 | 不需要用户数据访问 |
权限红线(自动驳回)
以下权限请求将导致 自动驳回:
| 违规行为 | 说明 |
|---|---|
| Root 执行 | 请求 root 用户运行应用(包括在 systemd 服务文件中设置 User=root,以及在 Docker 中不指定 user 字段(默认以 root 运行)两种情况) |
| 特权模式 | 请求 --privileged Docker 模式 |
| 系统目录写入 | 请求写入 /etc/、/usr/、/boot/ 等系统目录 |
| 跨应用数据访问 | 请求访问其他应用的数据目录 |
| 无限制网络访问 | 未提供书面合理理由而申请 network_mode: host(仅系统级网络工具可申请) |
| 过多端口暴露 | 请求超过功能所需的端口数量 |