OTS 1.23.1 配置参数详解

OTS v1.23.1 全配置面详解:10 个启动参数、Redis 存储环境变量、customize.yaml 17 个前端定制字段,含 expire 封顶回退、metrics 网段 ACL 实测与生产配置模板。

OTS自托管Golang安全2026/10/30

OTS 1.23.1 配置参数详解

目标:系统讲清一次性秘密分享平台 OTS v1.23.1 的全部可配置项——服务进程启动参数、存储后端环境变量、Web 前端定制文件(customize.yaml)三大配置面,含每个参数的真实默认值、取值范围、生效验证方法与生产配置模板。全部行为均在 Ubuntu 22.04 + OTS v1.23.1(监听 8146)实测验证,回显为真实输出。

一、环境信息

  • 操作系统:Ubuntu 22.04(systemd 托管)
  • OTS 版本:v1.23.1(官方二进制,/usr/local/bin/ots)
  • 监听端口:0.0.0.0:8146(本文实验端口)
  • 定制文件:/etc/ots/customize.yaml
  • 参数来源取证:ots --help 实测回显、官方 Wiki Customization 页(查询日期 2026-10-30)、仓库源码(main.go / api.go / pkg/customization/customize.go / pkg/storage)
  • 安装过程见 《Ubuntu 22.04 安装 OTS 指南》,本文默认服务已可运行

二、前置条件

  1. OTS v1.23.1 已安装并可访问(curl http://127.0.0.1:8146/api/healthz 返回 200);
  2. 能编辑 systemd 单元与定制文件(root 或 sudo 权限);
  3. 修改任意配置后的生效公式固定为:
systemctl daemon-reload && systemctl restart ots && systemctl is-active ots

实测回显:

active
  1. 若使用 Redis 后端,需备一个可达的 Redis 实例(连接串见 5.2 节)。

三、参数查看与修改方式

OTS 没有统一的 config.yaml——配置分三个入口,按作用对象区分:

入口作用对象修改后生效方式
进程启动参数(--flag)服务进程自身:监听、存储、过期上限、TLS、日志systemctl daemon-reload && systemctl restart ots
环境变量(大写同名 / REDIS_URL 等)与启动参数等价 + Redis 连接配置同上(systemd Environment= 注入)
定制文件(--customize 指向的 YAML)Web 前端与接口行为:标题、图标、有效期选项、附件、指标 ACL同上(随进程重启重读)

查看当前全部启动参数与真实默认值:

ots --help

实测回显(全文照抄):

Usage of /usr/local/bin/ots:
      --cert-file string      Path to the TLS certificate file
      --customize string      Customize-File to load
      --enable-tls            Enable HTTPS/TLS
      --key-file string       Path to the TLS private key file
      --listen string         IP/Port to listen on (default ":3000")
      --log-level string      Set log level (debug, info, warning, error) (default "info")
      --log-requests          Enable request logging (default true)
      --secret-expiry int     Maximum expiry of the stored secrets in seconds
      --storage-type string   Storage to use for putting secrets to (default "mem")
      --version               Print version information and exit

⚠️ 注意:--secret-expiry 的 help 未显示默认值,源码默认为 0(永不过期);--storage-type 默认 mem。此外每个启动参数都有同名大写环境变量形态(源码启用 rconfig.AutoEnv(true)):--listen ↔ LISTEN、--storage-type ↔ STORAGE_TYPE、--secret-expiry ↔ SECRET_EXPIRY,两者同时存在时启动参数优先。

运行中查看前端实际生效的定制(定制文件的 JSON 投影,排障利器):

curl -s http://127.0.0.1:8146/api/settings

实测回显(本文实验配置):

{"appTitle":"Corp Secrets - One Time Secrets","expiryChoices":[300,3600,86400,604800],"acceptedFileTypes":"","disableFileAttachment":false,"maxAttachmentSizeTotal":10485760,"footerLinks":[{"name":"Internal Security Policy","url":"https://intranet.example.com/security"}]}

💡 说明:/api/settings 只返回非零值字段(JSON 序列化带 omitempty),因此「关闭类」开关未开启时不出现——上例中 disableQRSupport 等缺失即代表默认(不关闭),这不是配置丢失。

四、参数总览

4.1 服务进程启动参数(10 个)

参数默认值等价环境变量一句话作用
--listen:3000LISTENHTTP(S) 监听地址与端口
--storage-typememSTORAGE_TYPE存储后端:mem / redis
--secret-expiry0SECRET_EXPIRY秘密最长存活秒数(0=永不过期)
--customize空CUSTOMIZE前端定制文件路径
--log-levelinfoLOG_LEVEL日志级别:debug/info/warning/error
--log-requeststrueLOG_REQUESTS记录每条 HTTP 请求访问日志
--enable-tlsfalseENABLE_TLS直接以 HTTPS 对外服务
--cert-file空CERT_FILETLS 证书文件路径
--key-file空KEY_FILETLS 私钥文件路径
--version—VERSION打印版本并退出

4.2 存储后端环境变量(仅 Redis 后端)

变量默认值作用
REDIS_URL无(必填)Redis 连接串 redis://USR:PWD@HOST:PORT/DB
REDIS_KEYio.luzifer.ots键前缀(源码 pkg/storage/redis/redis.go:os.Getenv("REDIS_KEY") 非空时覆盖)
SECRET_EXPIRY0mem/redis 共用的过期上限(Redis 以 TTL 实现到期自动删除)

4.3 定制文件字段(customize.yaml,17 个)

字段类型默认值作用
appIcon字符串空前端主图标(建议高 30px)
appIconDark字符串空暗色主题图标(缺省回落 appIcon)
appTitle字符串OTS - One Time Secrets应用标题
disableAppTitle布尔false隐藏标题文字(图标已含品牌时用)
disableExpiryOverride布尔false关闭前端与 API 的有效期覆盖能力
disablePoweredBy布尔false隐藏页脚项目回链
disableQRSupport布尔false关闭分享链接二维码按钮
disableThemeSwitcher布尔false关闭亮/暗主题切换
expiryChoices整数数组内置默认集前端有效期下拉选项(秒,顺序即展示顺序)
overlayFSPath字符串空前端静态资源覆盖目录
useFormalLanguage布尔false有正式称谓译本的语言用正式版文案
acceptedFileTypes字符串空附件选择框的 accept 类型过滤(纯前端约束)
disableFileAttachment布尔false整体关闭附件功能(纯前端约束)
maxAttachmentSizeTotal整数0(内部 64 MiB)附件总大小上限(字节,前端实施)
maxSecretSize整数121168782服务端秘密总大小上限(字节)
metricsAllowedSubnets字符串数组[](全拒绝)允许抓取 /metrics 的来源网段
footerLinks数组空页脚自定义链接(名称+URL)

五、核心参数分类详解

5.1 监听与 TLS:--listen / --enable-tls / --cert-file / --key-file

--listen 接受 Go 标准地址形态::8146(全接口)、0.0.0.0:8146(显式全接口,本文实测采用)、127.0.0.1:8146(仅本机,配合反代时最安全)。默认 :3000。

直连 HTTPS 形态(不依赖 nginx 等反代):

# /etc/systemd/system/ots.service 的 ExecStart 片段
ExecStart=/usr/local/bin/ots --listen=0.0.0.0:8146 --enable-tls --cert-file=/etc/ots/tls.crt --key-file=/etc/ots/tls.key --storage-type=mem --secret-expiry=604800 --customize=/etc/ots/customize.yaml

💡 技术拓展:把 TLS 做在应用层(--enable-tls)还是反代层(nginx/Caddy 终结 TLS)是两条常见路线——

方案优点取舍
应用层 TLS(OTS 自带)拓扑简单,一个进程闭环证书续期要自己挂钩;无访问日志聚合、限速能力
反代层 TLS(本文推荐)统一证书管理、可叠加访问控制/限流多一个组件,需配 X-Forwarded-Proto 等头

本文实测采用反代层方案(nginx 配置与实测回显见 《Ubuntu 22.04 安装 OTS 指南》第八章);单机内网小场景用 --enable-tls 更省事。

5.2 存储后端:--storage-type / REDIS_URL / REDIS_KEY / SECRET_EXPIRY

💡 技术拓展:OTS 支持 mem / redis 两种存储,本质是「进程内存 vs 外置存储」的取舍——

后端原理重启后未读秘密多实例适用
mem带周期清理的进程内 map(源码 pkg/storage/memory)全部丢失(官方明言 wiped on restart,实测复现)不支持个人/单机/演示
redisSET <prefix>:<id> <cipher> EX <expiry> 原子写入(源码 pkg/storage/redis)保留,到期 TTL 自动删除支持(共享 Redis)生产/负载均衡部署

本文实验用 mem 验证功能;生产建议 redis,理由是重启不丢与可水平扩展。

Redis 后端的 systemd 配置形态:

# /etc/systemd/system/ots.service 关键行(仅示例骨架,完整单元见安装指南)
[Service]
Environment=REDIS_URL=redis://:q1w2e3r4@127.0.0.1:6379/0
Environment=REDIS_KEY=io.luzifer.ots
Environment=SECRET_EXPIRY=604800
ExecStart=/usr/local/bin/ots --listen=0.0.0.0:8146 --storage-type=redis --customize=/etc/ots/customize.yaml

要点(依据官方 README 与源码,均在 2026-10-30 核对):

  • REDIS_URL 缺失时服务启动直接失败,报 REDIS_URL environment variable not set(源码 redis.go 显式检查);
  • 连接串支持 tcp:// 与 redis:// 两种 scheme(源码内做替换兼容);认证用户名的写法:Redis 6 之前用 auth 作用户名,Redis 6+ 用 ACL 用户;
  • REDIS_KEY 是键前缀而非单个键,默认 io.luzifer.ots,多套 OTS 共用一个 Redis 时可各配前缀隔离;
  • Redis 后端下 SECRET_EXPIRY/--secret-expiry 换算成 SET ... EX <秒> 的 TTL,到期由 Redis 自动删除——这是 mem 后端「周期清理」之外的被动兜底。

实测:mem 后端重启即焚(详见安装指南 FAQ Q2):创建秘密 → systemctl restart ots → 再读返回 404。切换存储后端属于一次性动作,切前无需迁移(mem 里本就无可迁数据;反之从 Redis 切回 mem 也会「看起来全丢」)。

5.3 有效期体系:--secret-expiry / expiryChoices / disableExpiryOverride

三件套构成 OTS 的过期控制面:

  1. 服务端硬上限 --secret-expiry(默认 0=永不过期):任何秘密存活时间不得超过它,本文设 604800(7 天),启动日志可见 secret_expiry=168h0m0s(实测);
  2. 前端选项 expiryChoices:创建页下拉框的可选秒值列表,本文配 [300, 3600, 86400, 604800](5 分钟/1 小时/1 天/7 天),/api/settings 实测回显同值;
  3. 总开关 disableExpiryOverride: true:关闭后前端下拉与 API 的 expire 参数一起失效,所有秘密一律用服务端上限(合规场景「必须到期即焚」时开启)。

API 侧 expire 参数的封顶与回退规则(源码 api.go parseExpiryOverride + 实测复现,?expire=<秒> 追加在 /api/create):

请求实测结果规则
?expire=3600expires_at = 现在+1 小时未超上限,按请求值
?expire=999999999expires_at = 现在+604800 秒(7 天)超过服务端上限被静默封顶为 604800
?expire=0expires_at = 现在+604800 秒0 值回退服务端默认上限
?expire=abcHTTP 400,{"success":false,"error":"<uuid>"}非数字 → invalid_expiry
?expire=-5HTTP 400负数 → invalid_expiry
disableExpiryOverride: true 时带 ?expire=60HTTP 400覆盖能力已关闭

⚠️ 注意:封顶是静默的——HTTP 仍返回 201,只是 expires_at 被缩短到服务端上限。依赖「精确 30 天过期」的调用方必须确保 --secret-expiry 大于等于业务期望值。

品牌与界面开关,全部随 --customize 文件下发到前端(模板渲染进 index.html + /api/settings 双通道)。实测验证 appTitle 生效:

curl -s http://127.0.0.1:8146/ | grep -oE '<title>[^<]*</title>'

实测回显:

<title>Corp Secrets - One Time Secrets</title>
  • appIcon/appIconDark:填图片文件名(相对根路径,如 app.png),图片本身通过 overlayFSPath 目录下发——覆盖目录里的文件会替代内嵌同名资源,并出现在站点根路径(官方 Wiki:app.png 将在 https://ots.example.com/app.png 提供);
  • disablePoweredBy:隐藏页脚「Powered by」回链(官方建议保留以支持项目,商业使用可考虑捐赠);
  • footerLinks:合规页脚(隐私政策/使用条款),数组元素为 {name, url},/api/settings 回显已含本文实验配置的示例条目;
  • disableThemeSwitcher:自定义单套主题(只覆盖了亮或暗一种资源)时关掉切换按钮;
  • useFormalLanguage:德语等提供正式称谓译本的语言改用正式文案,中文无差异。

5.5 大小限制组:maxSecretSize / maxAttachmentSizeTotal / acceptedFileTypes / disableFileAttachment

四项共同决定「能分享多大、什么类型的东西」:

  • maxSecretSize(服务端实施):秘密总大小上限,超出返回 HTTP 400(源码 api.go:secret size exceeds maximum 计入 secret_size 错误计数)。未配置时的内部默认值 121168782 字节 ≈ 115.6 MiB,官方 Wiki 给出推导:前端附件上限 64 MiB,经两轮 base64 膨胀(×4/3 ×4/3 = ×16/9)再加 1 MiB 余量;
  • maxAttachmentSizeTotal(前端实施):附件总大小,默认 0=内部 64 MiB;本文配 10485760(10 MiB),/api/settings 回显确认;
  • acceptedFileTypes(纯前端):文件选择框的 accept 过滤(如 .pem,.key,.txt);
  • disableFileAttachment(纯前端):整体关闭附件上传。

⚠️ 注意:官方 Wiki 用加粗强调后三项只是前端约束,不是安全边界——服务端无法感知密文内部「11 KB 秘密 + 10 KB 附件」的构成,也挡不住直接调 API 的客户端(如 ots-cli 不检查文件类型)。真正的入口限流要在反代上做(nginx client_max_body_size,见安装指南 8.2 节)。

5.6 指标与可观测:metricsAllowedSubnets / --log-level / --log-requests

/metrics 网段 ACL:默认空列表=拒绝一切来源(请求直接 404)。配置必须是带掩码的子网形态,单个 IP 也要写 127.0.0.1/32。本文实验配置放行 127.0.0.1/32 与实验网段 10.89.71.0/24,实测双向验证:

# 放行网段内的来源
curl -s -o /dev/null -w 'metrics_allow_code=%{http_code}\n' http://127.0.0.1:8146/metrics

实测回显:

metrics_allow_code=200

把放行列表清空重启后重测,返回 metrics_deny_code=404(实测);恢复配置后回到 200。指标样例(实测节选):

ots_secrets_create_errors{reason="invalid_expiry"} 1
ots_secrets_create_errors{reason="invalid_json"} 1
ots_secrets_create_errors{reason="secret_missing"} 1
ots_secrets_created 3
ots_secrets_read 1
ots_secrets_read_errors{reason="secret_not_found"} 3
ots_secrets_stored 2

⚠️ 注意:OTS 跑在反代后面时,/metrics 的网段校验看到的是代理的地址(官方 Wiki 原文提示),不要把反代网段无脑放行,除非该端点已在代理层另行鉴权。

日志两参数:--log-level 四档(debug/info/warning/error),排障期间可临时调 debug;--log-requests 默认开启,访问日志行格式实测:

time="2026-10-29T17:41:00Z" level=info msg="10.89.71.1 - \"GET /api/get/8866920b-d07b-4f42-9b2d-6acb8394feea\" 200 93 \"\" \"curl/8.18.0\" 32.426µs"

字段依次为:时间、来源 IP、方法与路径、状态码、响应字节数、referer、UA、耗时。错误类请求部分会落一行带 err_id 的结构化错误(与 API 响应里的 error UUID 对应,用于排查「接口只回 UUID 的问题」;实测 404 与非法 JSON 有日志、空秘密与非法 expire 刻意静默,完整对照见 《OTS 1.23.1 常用 CLI 命令与 API 详解》4.6 节):

time="2026-10-29T17:41:00Z" level=error msg="reading & destroying secret" err_id=ddf5013f-5b41-46c0-b218-67fc2e25223e error="secret not found"

六、生产配置模板

6.1 模板 A:内网单机(1C/1GB 内存/普通磁盘,团队内部口令交接)

# /etc/systemd/system/ots.service
[Unit]
Description=OTS - One-Time-Secret sharing platform
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=ots
Group=ots
ExecStart=/usr/local/bin/ots --listen=0.0.0.0:8146 --storage-type=mem --secret-expiry=86400 --log-level=info --customize=/etc/ots/customize.yaml
Environment=SECRET_EXPIRY=86400
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
# /etc/ots/customize.yaml
appTitle: "Internal Secrets"
disableExpiryOverride: false
expiryChoices: [300, 1800, 3600, 86400]
maxAttachmentSizeTotal: 10485760
metricsAllowedSubnets:
  - 127.0.0.1/32
footerLinks:
  - name: "Security Policy"
    url: "https://intranet.example.com/security"

场景说明:秘密最长活 1 天(--secret-expiry=86400),前端只能选 5 分钟~1 天;mem 后端零依赖(重启丢失未读秘密在内网交接场景可接受);内存占用实测 2–4 MB,磁盘无持久化需求。防火墙/反代限制来源网段(OTS 无账号体系)。

6.2 模板 B:多实例 + Redis(2C/4GB,高可用要求)

[Service]
Environment=REDIS_URL=redis://:q1w2e3r4@redis.internal:6379/0
Environment=REDIS_KEY=io.luzifer.ots
Environment=SECRET_EXPIRY=604800
ExecStart=/usr/local/bin/ots --listen=0.0.0.0:8146 --storage-type=redis --log-level=info --customize=/etc/ots/customize.yaml
# /etc/ots/customize.yaml
appTitle: "Secrets Relay"
expiryChoices: [300, 3600, 86400, 604800]
maxAttachmentSizeTotal: 33554432
metricsAllowedSubnets:
  - 127.0.0.1/32
  - 10.0.0.0/8

场景说明:两个以上 OTS 实例共享 Redis(Redis 侧持久化 AOF/RDB 按机房策略定),秘密最长 7 天、TTL 到期自动清;REDIS_KEY 保持默认前缀,多套环境用不同 Redis DB 或前缀隔离;指标放行监控网段 10.0.0.0/8。演示密码 q1w2e3r4 应替换为 Redis ACL 专用账号口令。

七、常见问题(FAQ)

Q1:改了 customize.yaml 没生效? 定制文件随进程启动读取(源码 customization.Load 在 init 阶段执行),必须 systemctl restart ots。验证通道:curl -s http://127.0.0.1:8146/api/settings 比对字段值;页面级字段(标题/图标)同时受前端缓存影响,强刷(Ctrl+Shift+R)再验证。

Q2:--secret-expiry=0 和 expiryChoices 能共存吗? 能,但语义组合要想清楚:服务端 0 = 不设硬上限、秘密按用户所选 expiryChoices 过期;若同时想让「不选就走默认」成立,应把服务端上限设为非 0 值(API ?expire=0 会回退该值,实测见 5.3 节)。

Q3:/api/settings 里没有我配的 disableQRSupport 等字段? /api/settings 的 JSON 带 omitempty,零值字段(false/0/空串)不出现——缺失即默认值。确认定制加载是否成功看启动日志(customize 文件路径错误时日志出现 customize file given but not found 警告,且按无定制继续运行,源码 customization.Load 行为)。

Q4:maxSecretSize 我配小了,前端还显示能传大文件? 正常。大小与类型四参数里只有 maxSecretSize 是服务端实施的,其余是前端约束;服务端拒绝发生在提交时(HTTP 400 + ots_secrets_create_errors{reason="secret_size"} 计数上涨)。想同时约束前端体验就成对配置。

Q5:日志里全是访问行太吵,能关吗? --log-requests=false(或 LOG_REQUESTS=false)关闭访问日志,仅保留错误/警告与启动行。访问日志不含秘密内容与解密密码(零知识架构决定服务端无从记录),只有密文操作元数据,按需保留即可。

Q6:想给不同部门不同有效期选项? 部署多个实例(同一二进制),各配独立 customize.yaml(不同 expiryChoices/标题),mem 后端各自独立,Redis 后端用不同 REDIS_KEY 前缀隔离,反代按路径/域名分流。

八、参考链接