安全模型
Semaphore 持有访问你基础设施的凭据,并针对它运行代码。由此引出两个特性, 它们塑造了本页上的其他所有决定:密钥绝不能传回浏览器,以及 任何能启动任务的人,都能在该任务可触达的机器上运行代码。
本页解释这一模型。关于实现它的具体设置,请参见 安全。
信任边界
| 边界 | 跨越者 | 保护方式 |
|---|---|---|
| 浏览器 ↔ 服务器 | 会话、API 令牌 | TLS、安全 Cookie、反向代理 |
| 服务器 ↔ 数据库 | 所有持久化状态 | 网络限制;密钥在写入之前已 加密 |
| 服务器 ↔ 运行器 | 作业载荷,包括密钥 | HTTPS 以及每个运行器各自的 bearer token |
| 任务 ↔ 被管理主机 | 你的自动化内容 | 你交给模板的那些密钥 |
任务处在上述每一条边界的另一侧。它在自己的环境中接收所需的密钥, 从那一刻起,你仓库中的代码就决定了这些密钥的去向。
身份
用户通过三种方式之一进行认证,三者最终都产生同样的会话:
- 本地账号。 密码使用 Argon2id 哈希(2.20 之前为 bcrypt,首次登录时升级)。 可以强制要求 TOTP 双因素认证。
- LDAP 或 Active Directory。 由目录服务校验密码; Semaphore 只保留账号。
- OpenID Connect。 由提供方完成认证,Semaphore 把声明映射到用户上。
非交互式访问使用由用户创建的 API 令牌,它携带该用户的权限。 运行器完全不使用用户身份:它们使用注册时签发的自有令牌进行认证。
任务也可以携带身份。借助任务 JWT,一次运行会 收到一个短期有效的签名令牌,其中标明项目、模板和用户,外部密钥存储可以校验它, 从而免去你保存长期凭据。
授权
存在两个层级,并且彼此独立。
服务器层级。 管理员管理用户、全局运行器和服务器设置。 身为服务器管理员本身并不意味着成为某个项目的成员。
项目层级。 每个成员在每个项目中拥有一个角色:
| 角色 | 可以 |
|---|---|
| Owner | 项目中的一切,包括成员管理和删除项目。 |
| Manager | 运行任务,管理资源和模板。 |
| Task Runner | 运行任务。仅此而已。 |
| Guest | 只读。 |
当这四个角色过于粗放时,Enterprise 还提供自定义角色 Enterprisesince 2.17。
对安全而言真正重要的界线在 Task Runner 与 Manager 之间。 Manager 可以修改模板执行的内容,因而可以用该项目的凭据运行任意代码。 Task Runner 只能启动已经存在的东西——除非模板暴露了会进入命令行的提示或调查变量, 在这种情况下,是模板作者有意扩大了这条边界。
密钥
密文值——SSH 私钥、密码、令牌、密文变量——在存储之前会使用 access_key_encryption
中的密钥加密,因此仅凭一份数据库转储并不会泄露它们。API 从不返回密文值;
UI 只显示某个密钥已设置,而不显示它的内容。
密钥在任务启动的那一刻通过其环境传入。正因如此,任务输出值得当作敏感信息对待: 一个打印变量的 playbook 会把它打印进其他项目成员都能读到的日志里。
如果你根本不希望持有这些密钥, 外部密钥存储可以把值保存在 HashiCorp Vault、 OpenBao、AWS Secrets Manager 或 Devolutions Server 中,并在每次运行时获取。
执行不受信任的代码
在默认部署下,任务是 Semaphore 服务器上的一个进程,拥有服务器的文件系统和网络访问权限。 当所有能编辑模板的人本来就被信任可以访问该服务器时,这是合适的。
当情况并非如此时,请把执行挪离服务器:
- 运行器把任务放到另一台机器上,因此攻陷一个任务 不会连带攻陷 Web 服务或数据库。
- Docker 或 Kubernetes 执行器为每个作业提供一个全新的容器或 Pod, 因此一次运行无法读取另一次运行的文件,也读不到宿主机的文件。
- 使用不同密钥的独立项目意味着任务只能触达其所属项目的凭据所允许的范围。
项目成员能够修改的仓库,就是将以该项目凭据运行的代码。请保护模板所构建的分支, 或者把模板指向只有审阅者才能写入的分支。
留给你自己的部分
Semaphore 是自托管的,因此模型中有一部分需要由你提供:
- 服务前面的 TLS,可以是内置的,也可以来自反向代理。
- 对数据库以及服务器管理面的网络访问限制。
- 数据库和
access_key_encryption的备份——没有前者时后者毫无用处, 没有后者时前者无法读取。 - 保持版本最新。请把漏洞报告到
[email protected]。