Add complimentary OOBE polish and configurable admin mTLS
Allow one server-audited onboarding polish request without credits and make the certificate gate temporarily reversible while preserving application authentication.
This commit is contained in:
+24
-6
@@ -2,8 +2,21 @@
|
||||
|
||||
`account.osglab.com` 在同一个 TLS `server` 中同时承载移动端 API 和管理端。
|
||||
由于 TLS 握手发生在 HTTP 路径匹配之前,配置必须使用 server 级
|
||||
`ssl_verify_client optional`:普通客户端不提供证书时仍可正常访问,只有
|
||||
`/admin`、`/admin/`、`/v1/admin` 和其子路径要求验证成功。
|
||||
`ssl_verify_client optional`:普通客户端不提供证书时仍可正常访问。OpenResty 会把实际
|
||||
证书验证结果传给 Ktor;`ADMIN_MTLS_REQUIRED=true`(默认值)时,`/admin`、`/admin/`、
|
||||
`/v1/admin` 和其子路径要求验证成功。
|
||||
|
||||
## 临时关闭
|
||||
|
||||
在 1Panel/Compose 环境中显式设置并重启应用:
|
||||
|
||||
```text
|
||||
ADMIN_MTLS_REQUIRED=false
|
||||
```
|
||||
|
||||
关闭后,管理端无需客户端证书,但登录仍要求用户名、密码和 TOTP,其他会话、CSRF、RBAC、
|
||||
失败锁定、限流与审计规则保持不变。恢复时将该值改回 `true` 并重启应用。不要为了临时关闭
|
||||
而删除客户端 CA、证书或轮换记录。
|
||||
|
||||
## CA 与证书
|
||||
|
||||
@@ -22,14 +35,15 @@
|
||||
|
||||
## 上游信任边界
|
||||
|
||||
OpenResty 仅在管理路径且 `$ssl_client_verify = SUCCESS` 时向 Ktor 设置固定头:
|
||||
OpenResty 在管理路径用 `$ssl_client_verify` 覆盖并转发证书验证结果。有效证书对应:
|
||||
|
||||
```text
|
||||
X-OSG-mTLS-Verified: SUCCESS
|
||||
```
|
||||
|
||||
客户端传入的同名头会被覆盖;其他路径会删除该头。Ktor 只能把这个头作为“边缘已验证”
|
||||
信号,不能信任客户端提供的证书相关头,也不能用 DN、CN 或证书正文做隐式授权。
|
||||
无证书时该值为 `NONE`。客户端传入的同名头会被覆盖;其他路径会删除该头。Ktor 只能把
|
||||
OpenResty 写入的这个头作为“边缘已验证”信号,不能信任客户端提供的证书相关头,也不能用
|
||||
DN、CN 或证书正文做隐式授权。
|
||||
后端端口必须继续只监听 `127.0.0.1:18080`,否则攻击者可绕过边缘伪造该头。
|
||||
mTLS 只证明客户端持有受信证书,管理接口仍应执行应用层身份认证、授权和审计。
|
||||
|
||||
@@ -52,7 +66,7 @@ ADMIN_BOOTSTRAP_ENABLED=true
|
||||
|
||||
## 验证
|
||||
|
||||
将测试域名解析到目标边缘后执行:
|
||||
`ADMIN_MTLS_REQUIRED=true` 时,将测试域名解析到目标边缘后执行:
|
||||
|
||||
```sh
|
||||
# 无证书:管理路径必须是 404。
|
||||
@@ -74,6 +88,10 @@ curl -i -H 'X-OSG-mTLS-Verified: SUCCESS' \
|
||||
还应使用由非管理 CA 签发或已过期的客户端证书确认返回 404,并在 Ktor 测试端点确认:
|
||||
管理请求只收到固定值 `SUCCESS`,普通 API 不收到 `X-OSG-mTLS-Verified`。
|
||||
|
||||
`ADMIN_MTLS_REQUIRED=false` 时,无证书访问 `/admin/` 应返回管理页面,
|
||||
`/v1/admin/auth/session` 应返回匿名会话状态。伪造 `X-OSG-mTLS-Verified: SUCCESS` 不会
|
||||
改变结果,因为 OpenResty 会将其覆盖为实际验证状态。
|
||||
|
||||
部署后可在受信设备运行不含登录凭据的自动验收:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -52,13 +52,10 @@ server {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Administrative endpoints are indistinguishable from missing routes unless
|
||||
# OpenResty verified a certificate issued by the dedicated admin client CA.
|
||||
# Always forward administrative paths to Ktor. Ktor decides whether mTLS is
|
||||
# required from ADMIN_MTLS_REQUIRED, while this edge overwrites the trust
|
||||
# signal so clients cannot spoof successful certificate verification.
|
||||
location ~ ^/(?:admin|v1/admin)(?:/|$) {
|
||||
if ($ssl_client_verify != SUCCESS) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
client_max_body_size 32k;
|
||||
limit_req zone=account_api burst=40 nodelay;
|
||||
# Defining a location-level header disables inheritance from the server
|
||||
@@ -76,8 +73,7 @@ server {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Request-ID $request_id;
|
||||
# Overwrite any client-supplied value; Ktor must trust only this header.
|
||||
proxy_set_header X-OSG-mTLS-Verified "SUCCESS";
|
||||
proxy_set_header X-OSG-mTLS-Verified $ssl_client_verify;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_hide_header Server;
|
||||
|
||||
@@ -470,9 +470,9 @@ WHERE version IS NOT NULL
|
||||
ORDER BY installed_rank;
|
||||
SQL
|
||||
)"
|
||||
EXPECTED_MIGRATIONS=$'1:1\n2:1\n3:1\n4:1\n5:1\n6:1\n7:1\n8:1\n9:1\n10:1\n11:1\n12:1'
|
||||
EXPECTED_MIGRATIONS=$'1:1\n2:1\n3:1\n4:1\n5:1\n6:1\n7:1\n8:1\n9:1\n10:1\n11:1\n12:1\n13:1\n14:1\n15:1\n16:1\n17:1'
|
||||
[[ "$MIGRATIONS" == "$EXPECTED_MIGRATIONS" ]] ||
|
||||
fail "Flyway history was not exactly successful V1-V12"
|
||||
fail "Flyway history was not exactly successful V1-V17"
|
||||
REFERRAL_REWARDS="$(
|
||||
mysql_root --batch --skip-column-names osg_account_smoke <<'SQL'
|
||||
SELECT CONCAT(inviter_reward_credits, ':', invitee_reward_credits)
|
||||
|
||||
@@ -17,6 +17,7 @@ GRANT SELECT ON osg_account_smoke.usage_records TO 'osg_smoke_runtime'@'%';
|
||||
GRANT SELECT ON osg_account_smoke.gateway_grants TO 'osg_smoke_runtime'@'%';
|
||||
GRANT SELECT ON osg_account_smoke.gateway_grant_scopes TO 'osg_smoke_runtime'@'%';
|
||||
GRANT SELECT ON osg_account_smoke.gateway_refresh_tokens TO 'osg_smoke_runtime'@'%';
|
||||
GRANT SELECT ON osg_account_smoke.gateway_complimentary_requests TO 'osg_smoke_runtime'@'%';
|
||||
GRANT SELECT ON osg_account_smoke.devicecheck_trial_claims TO 'osg_smoke_runtime'@'%';
|
||||
GRANT SELECT ON osg_account_smoke.app_attest_challenges TO 'osg_smoke_runtime'@'%';
|
||||
GRANT SELECT ON osg_account_smoke.app_attest_keys TO 'osg_smoke_runtime'@'%';
|
||||
@@ -48,6 +49,8 @@ GRANT INSERT ON osg_account_smoke.usage_records TO 'osg_smoke_runtime'@'%';
|
||||
GRANT INSERT, UPDATE ON osg_account_smoke.gateway_grants TO 'osg_smoke_runtime'@'%';
|
||||
GRANT INSERT ON osg_account_smoke.gateway_grant_scopes TO 'osg_smoke_runtime'@'%';
|
||||
GRANT INSERT, UPDATE ON osg_account_smoke.gateway_refresh_tokens TO 'osg_smoke_runtime'@'%';
|
||||
GRANT INSERT, UPDATE, DELETE ON osg_account_smoke.gateway_complimentary_requests
|
||||
TO 'osg_smoke_runtime'@'%';
|
||||
GRANT INSERT, UPDATE ON osg_account_smoke.devicecheck_trial_claims TO 'osg_smoke_runtime'@'%';
|
||||
GRANT INSERT, UPDATE ON osg_account_smoke.app_attest_challenges TO 'osg_smoke_runtime'@'%';
|
||||
GRANT INSERT, UPDATE ON osg_account_smoke.app_attest_keys TO 'osg_smoke_runtime'@'%';
|
||||
|
||||
Reference in New Issue
Block a user