Files
xinfra/server/README.md
T

14 KiB
Raw Blame History

AuthServer

最小 Gin + MySQL 权限服务实践。

当前能力

  • Gin HTTP 服务
  • MySQL / GORM 连接
  • 核心表 AutoMigrate
  • 默认环境和身份源初始化
  • 健康检查
  • 本地登录接口骨架
  • LDAP / 内部 SSO provider 预留
  • 权限判断接口骨架
  • 管理员用户、用户组、角色、权限、授权绑定 API
  • 业务线、namespace、cluster 管理 API
  • Rancher 同步预览接口
  • 审计日志落库
  • Vue 管理后台

启动

cp .env.example .env
go mod tidy
go run ./cmd/server

本地 Docker MySQL 8.0.46:

docker compose -f docker-compose.mysql.yml up -d

这个 MySQL 会映射到本机 3000 端口,.env.example 默认 DSN 已按这个端口配置:

MYSQL_DSN=auth:auth@tcp(127.0.0.1:3000)/authserver?charset=utf8mb4&parseTime=True&loc=Local

连接测试:

mysql -h127.0.0.1 -P3000 -uauth -pauth authserver

前端开发服务:

cd web
npm install
npm run dev

默认访问:

http://127.0.0.1:5173

Vite 会把 /api 代理到后端 http://127.0.0.1:8080。

如果 MySQL 还没有创建库和账号,可以用 root 执行:

mysql -uroot -p < docs/mysql-init.sql

如果你使用的是 Docker Desktop、OrbStack、Colima 或容器里的 MySQL,应用访问 MySQL 时来源 IP 可能不是 localhost,而是类似 192.168.65.1。这时 MySQL 账号需要允许远程来源,docs/mysql-init.sql 已经包含 'auth'@'%'。

默认启用 SSO:

SSO_ENABLED=true

如果只是本地开发测试,可以关闭 SSO,前端会显示用户名登录框。后端只校验用户名非空;用户不存在时会自动创建本地用户,第一位用户会自动成为管理员:

MYSQL_DSN='auth:auth@tcp(127.0.0.1:3306)/authserver?charset=utf8mb4&parseTime=True&loc=Local' \
JWT_SECRET='change-this-secret' \
SSO_ENABLED=false \
go run ./cmd/server

发版

发布脚本会在本地完成:

  • 构建后端和前端 Docker 镜像
  • 读取本地 .env.server,生成并应用线上 ConfigMap/Secret
  • 导出 authserver-images-<version>.tar
  • 上传到服务器
  • 导入 RKE2 使用的 containerd
  • 更新 authserver namespace 下的 deployment 并等待 rollout 完成

默认目标与你当前线上环境一致:

  • 主机:root@218.11.5.223
  • 私钥:/Users/mac/dev/xengineer-cs2.pem
  • 远端目录:/authserver
  • containerd socket:/run/k3s/containerd/containerd.sock

发布脚本默认把仓库根目录的 .env.server 当作服务器配置源;脚本会在发版时重新生成并应用 authserver-config 和 authserver-secret。本地开发用的 .env 不会被默认发布。

脚本默认不会拦截测试环境配置。如果你要对正式环境启用更严格的保护,可以显式打开:

STRICT_DEPLOY_ENV=true bash scripts/release.sh 1.0.5

开启后会拦截几类明显错误:

  • APP_ENV != prod
  • MYSQL_DSN 指向 127.0.0.1 或 localhost
  • PUBLIC_BASE_URL、SAML_ENTITY_ID、SAML_ACS_URL、WAYEN_LOGIN_URL、WAYEN_TARGET_URL 指向 127.0.0.1 或 localhost

执行方式:

bash scripts/release.sh 1.0.5

如果需要覆盖默认值,可以传环境变量:

ENV_FILE=.env.server \
DEPLOY_HOST=218.11.5.223 \
DEPLOY_KEY=/path/to/key.pem \
REMOTE_DIR=/authserver \
bash scripts/release.sh 1.0.5

如果只是想在服务器上重启已部署的服务,可以执行:

bash scripts/restart-authserver.sh

也支持只重启单个 deployment:

bash scripts/restart-authserver.sh backend
bash scripts/restart-authserver.sh nginx

接口

方法 路径 说明
GET /healthz 服务健康检查
GET /readyz 数据库连接检查
GET /auth/api/v1/config 前端登录模式配置,返回 sso_enabled
POST /auth/api/v1/login 本地开发测试登录,仅 SSO_ENABLED=false 时可用,只需用户名
POST /api/v1/login/ldap LDAP 登录预留
GET /api/v1/login/:provider SSO 跳转预留
GET /api/v1/login/:provider/callback SSO 回调预留
POST /api/v1/authz/check 权限判断
GET /api/v1/users/me 当前用户
GET /api/v1/wayen/login 根据 token 邮箱登录 Wayen 并跳转
GET /api/v1/saml/metadata SAML SP metadata
GET /api/v1/login/internal-sso 发起 SAML SSO 登录
POST /api/v1/saml/acs SAML ACS 回调,当前仅调试打印
GET /auth/.well-known/openid-configuration OIDC Discovery 配置
GET /auth/oauth/authorize OAuth2 Authorization Code 授权入口,供 Wayne 使用
POST /auth/oauth/token OAuth2 code 换 access token
GET /auth/oauth/userinfo OAuth2 bearer token 查询当前用户
GET /auth/oauth/jwks OIDC JWKS 公钥

SAML Metadata

服务会根据 .env 和 certs/sp.crt 动态生成 SP metadata,不需要手工维护 certs/spmeta。 发起 SSO 登录时会实时请求 .env 里的 SAML_IDP_METADATA_URL,不再读取本地 certs/idpmeta。

本地后端如果运行在 8083,建议配置:

HTTP_ADDR=:8083
PUBLIC_BASE_URL=http://localhost:8083
SAML_ENTITY_ID=http://localhost:8083/api/v1/saml/metadata
SAML_ACS_URL=http://localhost:8083/api/v1/saml/acs
SAML_IDP_METADATA_URL=http://sso-internal.dev.qiniu.io/saml2/meta
SAML_SP_CERT_FILE=certs/sp.crt
SAML_SP_KEY_FILE=certs/sp.key

给 IdP 配置的 SP metadata 地址:

http://localhost:8083/api/v1/saml/metadata

调试 SSO 登录入口:

http://localhost:8083/api/v1/login/internal-sso

ACS 会处理 IdP 回调:

POST /api/v1/saml/acs

收到 IdP 回调后,会把 SAMLResponse 解码;如果 Assertion 被加密,会用 SAML_SP_KEY_FILE 对应私钥解密,并在后端控制台打印解密后的摘要和 Assertion:

  • Response ID
  • InResponseTo
  • Issuer
  • Destination
  • NameID
  • Attributes
  • 解密后的 Assertion XML

当前 SAML 用户落库规则:

  • eduPersonPrincipalName 同时作为 users.username 和 users.email。
  • NameID 写入 users.external_id。
  • 首次登录会自动创建 source=saml 的用户。
  • 不再创建 identity_providers / user_identities 表记录。
  • 登录成功后签发 JWT,写入 authserver_token HttpOnly cookie,并带着 token 跳回前端。

当前阶段还没有做:

  • SAML 签名验证

Wayne OAuth2 对接

方案 1 下 AuthServer 作为 OAuth2 Provider,Wayne 作为 OAuth2 Client。Wayne 登录时不再校验 AuthServer 里的 Wayne 密码,也不需要 AuthServer 存储 Wayne 资源组信息;AuthServer 只负责 SAML 登录、签发 token、返回 name/email/display,Wayne 收到 userinfo 后会在 Wayne 自己数据库里创建或更新本地用户,Wayne 的资源组和权限仍由 Wayne 自己维护。

AuthServer 端配置:

OAUTH_WAYNE_CLIENT_ID=wayne
OAUTH_WAYNE_CLIENT_SECRET=change-this-wayne-client-secret
OAUTH_WAYNE_REDIRECT_URI=http://127.0.0.1:8080/login/oauth2/oauth2
WAYEN_OAUTH_REF=/portal/namespace/1/app
OAUTH_CODE_TTL_SECONDS=120

AuthServer 暴露给 Wayne 的 OAuth2 地址:

GET  /auth/oauth/authorize
POST /auth/oauth/token
GET  /auth/oauth/userinfo

OIDC Discovery 里的 endpoint 默认由 OIDC_ISSUER 拼接,也可以按 endpoint 单独覆盖。浏览器需要访问 OIDC_AUTHORIZATION_ENDPOINT,后端系统通常访问 OIDC_TOKEN_ENDPOINT、OIDC_USERINFO_ENDPOINT 和 OIDC_JWKS_URI。

OIDC_ISSUER=http://auth.example.com/auth
OIDC_AUTHORIZATION_ENDPOINT=http://auth.example.com/auth/oauth/authorize
OIDC_TOKEN_ENDPOINT=http://auth-internal.example.com/auth/oauth/token
OIDC_USERINFO_ENDPOINT=http://auth-internal.example.com/auth/oauth/userinfo
OIDC_JWKS_URI=http://auth-internal.example.com/auth/oauth/jwks
CLOUDDM_TARGET_URL=http://authserver-nginx/internal/clouddm

CLOUDDM_TARGET_URL 用于 AuthServer 后端请求 CloudDM /requestJumpUrl。在 k8s 内建议指向 AuthServer nginx 的内部代理路径,由 nginx 转发到 CloudDM Service,并把 Host 固定成 CloudDM 公网入口,确保 CloudDM 生成浏览器可访问的 callback。

Wayne app.conf 示例:

[auth.oauth2]
enabled = true
redirect_url = http://127.0.0.1:8080
client_id = wayne
client_secret = change-this-wayne-client-secret
auth_url = http://218.11.5.223/auth/oauth/authorize
token_url = http://218.11.5.223/auth/oauth/token
api_url = http://218.11.5.223/auth/oauth/userinfo
api_mapping = name:name,email:email,display:display
scopes = profile,email

Wayne 会把回调地址拼成:

{redirect_url}/login/oauth2/oauth2

因此 OAUTH_WAYNE_REDIRECT_URI 必须和 Wayne 实际回调地址完全一致。浏览器访问 Wayne OAuth 登录入口后,如果 AuthServer 还没有登录态,会先跳内部 SAML;SAML 成功后再回到 OAuth authorize,签发 code 给 Wayne。 WAYEN_OAUTH_REF 是 AuthServer 发起 Wayne 登录时写入 Wayne next 参数的登录完成页,默认 /portal/namespace/1/app,对应 Wayne DemoNamespaceId = 1 的默认 namespace。不要配置成 oauth 或 /oauth,否则 Wayne 回调会把它当成前端路由跳到 /oauth。

Wayne 授权代理接口

AuthServer 的 Wayne 授权代理接口要求调用方传目标 Wayne userId。后端会从当前 authserver_token 里取 email 作为操作者 operatorName,目标用户 ID 由请求体或路径参数提供。

对外接口:

GET    /auth/api/v1/wayne/namespaces
GET    /auth/api/v1/wayne/groups
GET    /auth/api/v1/wayne/users/:userid/roles
GET    /auth/api/v1/wayne/namespaces/:namespaceid/operator-permissions
GET    /auth/api/v1/wayne/apps/:appid/operator-permissions
PUT    /auth/api/v1/wayne/namespaces/:namespaceid/roles
DELETE /auth/api/v1/wayne/namespaces/:namespaceid/roles
PUT    /auth/api/v1/wayne/apps/:appid/roles
DELETE /auth/api/v1/wayne/apps/:appid/roles

示例:

PUT /auth/api/v1/wayne/namespaces/1/roles
Authorization: Bearer <authserver_token>
Content-Type: application/json

{
  "userId": 2001,
  "groupIds": [10, 11],
  "replace": false,
  "requestId": "req-001",
  "reason": "grant namespace access"
}

AuthServer 转发到 Wayne internal API 时会使用请求体里的 userId:

PUT /api/v1/internal/namespaces/1/users/2001/roles

并覆盖请求体中的 operatorName 为 token email,忽略外部传入的 operatorUserId。userId 只用于 Wayne path,不会透传到 Wayne 请求体。

相关配置:

WAYNE_INTERNAL_API_BASE_URL=http://wayne-backend.demo.svc.cluster.local:8080
WAYNE_SERVICE_NAME=xinfra
WAYNE_SERVICE_API_SECRET_KEY=<wayne-service-secret>

Wayne internal API 签名规则:

bodyHash = SHA256_HEX(rawBody)
payload = METHOD + "\n" + URI + "\n" + timestamp + "\n" + nonce + "\n" + bodyHash
signature = HMAC_SHA256_HEX(secret, payload)
X-Wayne-Signature = "sha256=" + signature

管理员可查看当前 SAML metadata 配置:

GET /api/v1/admin/saml/metadata/config

管理员接口

管理员接口需要 Authorization: Bearer <token>,并且 token 里的用户必须是 is_admin=true。

方法 路径 说明
GET /api/v1/admin/users 用户列表
POST /api/v1/admin/users 创建用户,可带本地密码
PUT /api/v1/admin/users/:id 更新用户
DELETE /api/v1/admin/users/:id 删除用户
GET /api/v1/admin/groups 用户组列表
POST /api/v1/admin/groups 创建用户组
POST /api/v1/admin/groups/:id/members 用户加入组
DELETE /api/v1/admin/groups/:id/members/:userID 用户移出组
GET /api/v1/admin/roles 角色列表
POST /api/v1/admin/roles 创建角色
PUT /api/v1/admin/roles/:id/permissions 设置角色权限
GET /api/v1/admin/permissions 权限点列表
POST /api/v1/admin/permissions 创建权限点
GET /api/v1/admin/business-lines 业务线列表
POST /api/v1/admin/business-lines 创建业务线
GET /api/v1/admin/namespaces namespace 列表
POST /api/v1/admin/namespaces 创建 namespace
GET /api/v1/admin/environments 环境列表
GET /api/v1/admin/clusters 集群列表
POST /api/v1/admin/clusters 创建集群
GET /api/v1/admin/wayen/credentials Wayen 凭据列表,不返回密码
POST /api/v1/admin/wayen/credentials 按邮箱保存 Wayen 密码
DELETE /api/v1/admin/wayen/credentials/:id 删除 Wayen 凭据
GET /api/v1/admin/role-bindings 授权绑定列表
POST /api/v1/admin/role-bindings 创建授权绑定
DELETE /api/v1/admin/role-bindings/:id 删除授权绑定
POST /api/v1/admin/rancher/sync Rancher 同步预览
GET /api/v1/admin/rancher/sync-status Rancher 同步状态
GET /api/v1/admin/saml/metadata/config SAML metadata 配置

Rancher 对齐流程

当前 Rancher 同步接口是 dry-run,不会真实调用 Rancher API。推荐先按下面流程把本系统数据配齐:

  1. 创建环境和集群:rke2-dev、rke2-test、rke2-prod。
  2. 创建业务线:例如 pay。
  3. 创建 namespace:例如 pay,归属支付业务线。
  4. 创建权限点:例如 deployment:deploy、deployment:rollback。
  5. 创建角色:例如 prod-deployer。
  6. 给角色绑定权限点。
  7. 创建用户组:例如 pay-prod-deployer。
  8. 把 SSO/LDAP 用户加入用户组。
  9. 创建授权绑定:group + role + cluster + namespace。
  10. 调用 /api/v1/admin/rancher/sync 查看将要同步到 Rancher 的映射。

核心授权绑定应该长这样:

{
  "subject_type": "group",
  "subject_id": 1,
  "role_id": 1,
  "scope_type": "cluster_namespace",
  "cluster_id": 3,
  "namespace_id": 1
}

这表示某个用户组在 prod 集群的 pay namespace 下拥有指定角色。