a-novel/service-json-keys

GitHub: a-novel/service-json-keys

基于 JWK/JWE/JWS 标准的集中式密钥管理服务,通过 gRPC 私有 API 签名 Token、REST 公开 API 分发公钥,实现微服务架构下安全的密钥托管与本地验证。

Stars: 1 | Forks: 0

# JSON Keys 服务 A-Novel 平台的集中式签名密钥管理器:它保管所有私钥,通过私有 gRPC API 签名 token,并通过 REST 提供匹配的公钥,以便调用者在本地进行验证。 ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/a-novel/service-json-keys) ![GitHub repo file or directory count](https://img.shields.io/github/directory-file-count/a-novel/service-json-keys) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/a-novel/service-json-keys) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/a-novel/service-json-keys/main.yaml) [![codecov](https://codecov.io/gh/a-novel/service-json-keys/graph/badge.svg)](https://codecov.io/gh/a-novel/service-json-keys) ![Coverage graph](https://codecov.io/gh/a-novel/service-json-keys/graphs/sunburst.svg) ## 它的功能 服务注册命名的**用途**(`auth`、`auth-refresh` 等),每个用途都有各自的签名算法、轮换计划和声明参数。JSON Keys 保管所有私钥并代表调用者进行签名——密钥材料永远不会离开服务器。消费者只需获取一次匹配的公钥即可在本地验证 token,无需为每个 token 进行网络往返。 两个 API: - **私有 gRPC API** —— 签名、密钥检索、状态 —— 用于内部服务间通信。所有涉及私钥的操作都在此处。服务器没有应用层身份验证;访问控制是外部的(网络策略、ingress、service mesh)。 - **公开 REST API** —— 公钥获取、健康检查 —— 供任何验证 token 的人使用。 ## 部署 该服务以发布的 OCI 镜像和 PostgreSQL 数据库的形式运行。两个服务器都是无状态的,因此您可以在负载均衡器后根据需要扩展到任意数量的副本;所有状态都保存在 Postgres 中。 | 镜像 | 角色 | | ----------------------------------- | -------------------------------------------------------------------------------- | | `service-json-keys/grpc` | 私有签名 + 密钥管理 API。仅限内部网络。 | | `service-json-keys/rest` | 公钥获取 + 健康 API。 | | `service-json-keys/jobs/migrations` | 一次性 schema 迁移作业;在服务器启动前运行至完成。 | | `service-json-keys/jobs/rotatekeys` | 定时密钥轮换作业(参见[贡献指南](./CONTRIBUTING.md#key-rotation))。 | | `service-json-keys/database` | 预先调优的 PostgreSQL 镜像 —— 或者使用您自带的 Postgres。 | 将每个镜像固定到相同的发布标签——参见[最新发布](https://github.com/a-novel/service-json-keys/releases/latest)。生产环境部署首先运行 `database`,接着运行迁移作业至完成,然后运行任意数量的 `grpc` 和/或 `rest` 副本: ``` services: postgres-json-keys: image: ghcr.io/a-novel/service-json-keys/database:v2.5.0 networks: [api] environment: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres POSTGRES_DB: postgres POSTGRES_HOST_AUTH_METHOD: scram-sha-256 POSTGRES_INITDB_ARGS: --auth=scram-sha-256 volumes: - json-keys-postgres-data:/var/lib/postgresql/ migrations-json-keys: image: ghcr.io/a-novel/service-json-keys/jobs/migrations:v2.3.2 depends_on: postgres-json-keys: { condition: service_healthy } environment: POSTGRES_DSN: "postgres://postgres:postgres@postgres-json-keys:5432/postgres?sslmode=disable" networks: [api] service-json-keys: image: ghcr.io/a-novel/service-json-keys/grpc:v2.5.0 # or .../rest:v2.3.2 for the public REST API ports: ["${GRPC_PORT}:8080"] # the container always listens on 8080; map ${REST_PORT} for the rest image depends_on: postgres-json-keys: { condition: service_healthy } migrations-json-keys: { condition: service_completed_successfully } environment: POSTGRES_DSN: "postgres://postgres:postgres@postgres-json-keys:5432/postgres?sslmode=disable" APP_MASTER_KEY: "" networks: [api] networks: api: volumes: json-keys-postgres-data: ``` 通过添加一个重用相同数据库和迁移并使用 `rest` 镜像的第二个服务来运行这两个服务器。密钥轮换是一个独立的定时作业——在计时器上运行 `service-json-keys/jobs/rotatekeys` 镜像(参见[贡献指南](./CONTRIBUTING.md#key-rotation));如果没有它,活动的密钥最终会过期并停止签名。 ### 配置 每个变量均从进程环境中读取。 | 名称 | 描述 | 镜像 | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `POSTGRES_DSN` | PostgreSQL 连接字符串。**必填。** | 全部 | | `APP_MASTER_KEY` | 用于加密静态私钥的 32 字节十六进制编码密钥。所有涉及私钥的镜像均**必填**。**切勿轮换**,除非您能承受使所有现有密钥失效的后果——参见[贡献指南](./CONTRIBUTING.md#master-key-encryption)。 | `grpc`
`rest`
`standalone-grpc`
`standalone-rest`
`jobs/rotatekeys` | gRPC 服务器暴露私钥操作,必须运行在隔离的、受访问控制的网络中——服务器本身不对调用者进行身份验证。
可选配置(REST 调优,OpenTelemetry) REST 调优(镜像 `rest`、`standalone-rest`): | 名称 | 描述 | 默认值 | | ----------------------------- | ------------------------------------ | ---------------- | | `REST_MAX_REQUEST_SIZE` | 最大请求体大小,单位为字节。 | `2097152` (2MiB) | | `REST_TIMEOUT_READ` | 读取超时。 | `15s` | | `REST_TIMEOUT_READ_HEADER` | Header 读取超时。 | `3s` | | `REST_TIMEOUT_WRITE` | 写入超时。 | `30s` | | `REST_TIMEOUT_IDLE` | 空闲 keep-alive 超时。 | `60s` | | `REST_TIMEOUT_REQUEST` | 单次请求超时。 | `60s` | | `REST_CORS_ALLOWED_ORIGINS` | CORS 允许的来源。 | `*` | | `REST_CORS_ALLOWED_HEADERS` | CORS 允许的 Header。 | `*` | | `REST_CORS_ALLOW_CREDENTIALS` | CORS 允许凭证标志。 | `false` | | `REST_CORS_MAX_AGE` | CORS 最大有效期,单位为秒。 | `3600` | 数据库连接池(服务器镜像)。这些限制是**针对每个进程**的。数据库的 `max_connections` 必须覆盖每个副本以及迁移作业;标准的 `postgres` 默认值为 100。 | 名称 | 描述 | 默认值 | | ------------------------- | ----------------------------------------- | ------- | | `POSTGRES_MAX_OPEN_CONNS` | 到数据库的最大打开连接数。 | `20` | | `POSTGRES_MAX_IDLE_CONNS` | 空闲时保持打开的最大连接数。 | `20` | 日志和追踪——OpenTelemetry 支持 stdout 和 Google Cloud 导出器(所有服务器镜像): | 名称 | 描述 | 默认值 | | ------------------- | --------------------------------------------------------------------- | ------------------- | | `OTEL` | 启用 OTel 追踪;以下变量用于选择导出器。 | `false` | | `GCLOUD_PROJECT_ID` | Google Cloud 项目 ID。设置后,会将 OTel 导出器切换到 GCP。 | | | `APP_NAME` | 附加到追踪和日志的应用名称。 | `service-json-keys` |
## 使用客户端包 该服务附带了两个客户端。每个代码片段都是**最小可行调用**;完整的接口内容请查阅您的编辑器的智能提示、[pkg.go.dev](https://pkg.go.dev/github.com/a-novel/service-json-keys/v2) 和 [API 参考](https://a-novel.github.io/service-json-keys-v2)。 - **Go** 通过 gRPC 通信——在签名 token 或使用缓存的公钥验证它们的后端服务中使用它。 - **JavaScript / TypeScript** 通过 REST 通信——在仅需公钥进行本地验证的前端或 Node 服务中使用它。 ### Go (gRPC) ``` go get github.com/a-novel/service-json-keys/v2 ``` ``` package main import ( "context" "log" "github.com/a-novel-kit/golib/grpcf" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" servicejsonkeys "github.com/a-novel/service-json-keys/v2/pkg/go" ) type MyClaims struct { UserID string `json:"userID"` } func main() { ctx := context.Background() // In production, swap insecure.NewCredentials() for a TLS or mTLS credential — the // server has no application-layer auth, so transport security is the only thing // protecting private-key operations from a network adversary. client, err := servicejsonkeys.NewClient( "service-json-keys:8080", grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { log.Fatal(err) } defer client.Close() // Sign claims under the auth usage. payload, err := grpcf.MarshalJSONAsAny(MyClaims{UserID: "user-1"}) if err != nil { log.Fatal(err) } res, err := client.ClaimsSign(ctx, &servicejsonkeys.ClaimsSignRequest{ Usage: servicejsonkeys.KeyUsageAuth, Payload: payload, }) if err != nil { log.Fatal(err) } // Verify locally — no extra network call per token. verifier := servicejsonkeys.NewClaimsVerifier[MyClaims](client) claims, err := verifier.VerifyClaims(ctx, &servicejsonkeys.VerifyClaimsRequest{ Usage: servicejsonkeys.KeyUsageAuth, AccessToken: res.GetToken(), }) if err != nil { log.Fatal(err) } log.Printf("verified claims for user %s", claims.UserID) } ``` ### JavaScript / TypeScript (REST) 该包发布在 GitHub Packages 上,即使是公共包也需要具有 `read:packages` 权限范围的 Personal Access Token([原因](https://github.com/orgs/community/discussions/23386#discussioncomment-3240193))。将其添加到 `.npmrc`(项目根目录或 `$HOME`): ``` @a-novel:registry=https://npm.pkg.github.com @a-novel-kit:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${YOUR_PERSONAL_ACCESS_TOKEN} ``` ``` pnpm add @a-novel/service-json-keys-rest ``` ``` import { JsonKeysApi, jwkList } from "@a-novel/service-json-keys-rest"; const api = new JsonKeysApi("http://service-json-keys:8080"); // Fetch the active public keys for a usage; cache them client-side and verify locally. const keys = await jwkList(api, "auth"); ``` API 参考:[a-novel.github.io/service-json-keys-v2](https://a-novel.github.io/service-json-keys-v2)。 ## 本地运行 对于没有开发工具链的一次性实例,**standalone** 镜像将服务器和迁移捆绑在一个容器中。它们在每次启动时都会运行迁移——非常适合快速启动,但在多副本生产环境重启下是不安全的。 ``` services: postgres-json-keys: image: ghcr.io/a-novel/service-json-keys/database:v2.5.0 networks: [api] environment: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres POSTGRES_DB: postgres POSTGRES_HOST_AUTH_METHOD: scram-sha-256 POSTGRES_INITDB_ARGS: --auth=scram-sha-256 service-json-keys: image: ghcr.io/a-novel/service-json-keys/standalone-grpc:v2.5.0 # or standalone-rest ports: ["${GRPC_PORT}:8080"] # map ${REST_PORT} for the standalone-rest image depends_on: postgres-json-keys: { condition: service_healthy } environment: POSTGRES_DSN: "postgres://postgres:postgres@postgres-json-keys:5432/postgres?sslmode=disable" APP_MASTER_KEY: "" networks: [api] networks: api: ``` 要在服务本身上进行开发?请使用 `a-novel` CLI(`a-novel run start service-json-keys/grpc`)——参见[贡献指南](./CONTRIBUTING.md)。 ## 贡献 平台设置和日常命令位于[开发者入门指南](https://github.com/a-novel-kit/.github/blob/master/README.md)中。特定于服务的概念和本地交互位于 [CONTRIBUTING.md](./CONTRIBUTING.md) 中。
标签:EVTX分析, Go, gRPC, JWT, PostgreSQL, Python工具, RESTful API, Ruby工具, 提示词优化, 日志审计, 测试用例, 用户代理