网络规则
沙箱可以为它的出站流量设置自己的规则:能连接互联网上的哪些地方、能向某个主机发送哪些 HTTP 请求,以及哪些请求在出站时加上请求头。egress 在创建、修改或一次性运行沙箱 (POST /v1/run) 时设置。不设置就没有规则。它是一个像下面这样的对象,让沙箱能安装 Python 软件包,并用一个它永远看不到的令牌通过 GitHub 的 API 读取仓库,别的都不行(机密 GITHUB_TOKEN 见下文):
{"default": "deny",
"allow": ["pypi.org", "files.pythonhosted.org"],
"http": [{"host": "api.github.com", "allow": [{"methods": ["GET", "HEAD"], "paths": ["/repos/*"]}]}],
"inject": [{"host": "api.github.com", "headers": {"Authorization": "Bearer ${secrets.GITHUB_TOKEN}"}}]}default:allow(默认)或deny,用于两个列表都没有列出的地方。allow、deny:各最多 1,000 条:主机名 (pypi.org)、通配符(*.github.com,匹配它下面的所有域名,但不包括github.com本身)、地址或地址段 (140.82.112.0/20)。先看deny,所以它可以在allow中挖出例外。http:最多 100 个主机,每个都列出可以发给它的方法和路径;节点会拒绝其他请求 (403egress_denied)。inject:最多 100 个主机,每个有 1 到 20 个请求头,加到沙箱发往那里的 HTTPS 请求上。值中的${secrets.NAME}会在出站时替换为机密的值。deny_ports:最多 64 个端口(1 到 65535),沙箱的任何连接都到不了这些端口,无论通过 TCP 还是 UDP、去往哪里,也无论其他规则怎么写。它们最先被宿主机的包过滤器拒绝,永远不会交给代理。浏览器会话就是这样永久关闭邮件和矿池端口的。
glac sandboxes create --name agent --egress '{"default": "deny", "allow": ["pypi.org", "files.pythonhosted.org"]}'
glac sandboxes update agent --egress '{"allow": null}' # empties one list, keeps the rest--egress 接受 JSON 本身、包含它的文件,或 - 表示标准输入。修改会替换它给出的列表,清空设为 null 的列表,其他保持不变;"egress": null 会去掉所有规则。
在哪里生效。规则在宿主机上、机器之外执行:沙箱中的 root 也改不了它们。名称按每个连接自己声明的内容匹配(TLS 握手中的名称、HTTP 的 Host),DNS 只解析规则允许的名称,所以设置 default: deny 时,沙箱只能访问规则允许的地方。有些地方任何规则都打不开:私有地址段、云的元数据地址、宿主机和其他沙箱。
通过 IPv6 时。有自己公网 IPv6 地址的沙箱(网络)在 IPv6 上受同样的规则约束,名称规则也一样:它通过 IPv6 的 TCP 连接和 DNS 查询,和 IPv4 上的一样经过代理,它的机器会像响应 A 查询那样响应 AAAA 查询,代理为它发起的连接从沙箱自己的地址发出,计入同样的限制。沙箱通过 IPv6 连接时,代理也通过 IPv6 连接,并在 250 毫秒后同时尝试通过 IPv4 连接同一个主机,IPv6 被拒绝时则立即尝试:先连上的那个被采用,所以本来会自己退回 IPv4 的程序仍然能连通。两种方式都连不通、而目标地址在机器那里又没有对应名称的连接,有一秒时间在开头说明它的主机(TLS 握手或 HTTP 请求一开始就会这样做),然后被带到那个主机;那一秒内什么都没说、或没说明主机的连接会被拒绝。
经过代理的连接。指定了主机的规则,会让沙箱的每个 TCP 连接(IPv4 和 IPv6 都一样)都经过它所在机器的出站代理,代理按套餐把账户在这台机器上的沙箱限制在一定的同时连接数份额内,并限制新连接的速度(每秒 2,000 个,超出的排队等待):参见限制。超出份额时,新连接会被拒绝,可以重试(普通 HTTP 请求会得到 503 egress_connections_full)。
它们挡不住什么。用一张证书托管许多网站的主机,可以在用允许的名称握手之后被请求访问另一个网站;会转发流量的允许主机(代理、任何人都能写入的存储)也是一条出路。allow 中的每一条都意味着相应的信任。default: allow 加 deny 列表能拦住无意作恶的软件;它不是一堵墙。
机密
机密是按名称保存、之后不再显示的值 (/v1/secrets)。它只会发往它允许的主机 (allowed_hosts):会把它发往别处的规则或镜像会被拒绝 (403 secret_host_not_allowed)。
glac secrets put GITHUB_TOKEN --allowed-host api.github.com # asks for the value, not echoed
printf '%s' "$TOKEN" | glac secrets put GITHUB_TOKEN - --allowed-host api.github.com
glac secrets hosts GITHUB_TOKEN api.github.com '*.githubusercontent.com'- 在网络规则中:写有
${secrets.GITHUB_TOKEN}的inject请求头会在出站时得到它的值,所以沙箱中的程序永远拿不到它。 - 作为镜像仓库的登录凭据:值为
username:password的机密用来拉取私有镜像 (image_pull_secret)。 - 在方块中:
--secret VAR=NAME把值作为环境变量交给应用的实例,这时实例确实持有它。
项目能保存的机密数量取决于套餐,每个机密有大小上限,交给一个沙箱的机密合计也有上限:
| 免费版 | 付费版 | |
|---|---|---|
| 单个项目的机密 | 100 | 1,000 |
| 单个机密上限 | 64 KiB | 64 KiB |
| 提供给单个沙箱的机密合计 | 64 KiB | 64 KiB |
保存、修改和删除机密需要有 admin 权限范围的密钥。没有这个权限范围的密钥只能看到机密的名称,看不到值。