Repository navigation
feat(spec): signed connector instance auth, with aliyun-acs3-hmac-sha256 signing every request (ADR-0143 D2.3, slice 1 of #26710) - #28273
Conversation
…sha256 scheme and the request-signer seam Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cxRP1AqTeP2PWAhwGK
…s request signer at materialization Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cxRP1AqTeP2PWAhwGK
…ith a signed instance's request signer and say so Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cxRP1AqTeP2PWAhwGK
…ed spec artefacts and the changeset Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cxRP1AqTeP2PWAhwGK
…r the signer registry to ADR-0143 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cxRP1AqTeP2PWAhwGK
📓 Docs Drift CheckThis PR changes 4 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ee460953535dfd33a91d881427d347b67e139170 && git checkout ee460953535dfd33a91d881427d347b67e139170
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 361ea242450cf22cbfd58c2aa21f12c0d5c0c9d1 8c6135c75d148c6ac914999f7656a33f57c264d3 && git checkout -B drift-repro 361ea242450cf22cbfd58c2aa21f12c0d5c0c9d1 && git merge --no-ff 8c6135c75d148c6ac914999f7656a33f57c264d3
node scripts/docs-audit/affected-docs.mjs --json 361ea242450cf22cbfd58c2aa21f12c0d5c0c9d1
|
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract-review-claim: The lane seat runs the |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Seat ACCEPT · PR #28273 · head
|
Requested by Elon · project thread
domain:server#3 · os-elon-musk · session_016WV4cxRP1AqTeP2PWAhwGK
Refs #26710
Clause-②: yes (widening: one member added to the instance-auth union; optional members added to the provider context and the materialization; an optional third parameter on
connectorFetchOptions; two exports added to@objectstack/spec/integration. Nothing accepted before is refused)Metadata-protocol: added
维护者速读
auth新增signed(请求签名)类型,第一个签名方案是阿里云 OpenAPI V3 签名(aliyun-acs3-hmac-sha256)。元数据只写方案名、公开的 key id 和密钥引用credentialRef;rest和openapi两个连接器对每一次请求(包括重试)重新签名。secret/privateKey/secretKey/accessKeySecret)、手写signature,以及provider: 'mcp'上的signed,发布时按名拒绝。数据源借用signed实例返回 422。不确认会签名的 provider,服务端拒绝启动该实例,绝不发出未签名请求。bearer/api-key/basic的请求逐字节不变;入站认证不动;OAuth2 换取令牌不在本片(connectors: outbound OAuth in the open tier — connection-scoped authorization-code / client-credentials / app-credential tokens, refreshed per dispatch, for the openapi, mcp and rest providers #26702)。packages/spec/src/shared/connector-auth.zod.ts(新形状和拒绝)、packages/spec/src/integration/connector-fetch-policy.ts(每次尝试重新签名)、packages/services/service-automation/src/plugin.ts(密钥绑定到签名器、确认缺失即拒绝)。pnpm --filter @objectstack/service-automation exec vitest run src/request-signing,看到阿里云官方示例的规范请求、待签字符串和签名逐字节复现,错误密钥得到不同签名。What changes
@objectstack/specConnectorInstanceAuthSchemagainssigned(ADR-0143 D2.3). It is a union onschemewith onestrictObjectper scheme. This slice ships one scheme:{ type: 'signed', scheme: 'aliyun-acs3-hmac-sha256', keyId, credentialRef }.keyIdis the public key identifier. The signing key is only ever acredentialRef.ConnectorSigningSchemeSchemais the closed scheme list, and a pin keeps it equal to the union's members.DeclarativeConnectorEntrySchema, both authoring doors) refuses:auth.scheme. The message names the value it got and the schemes that ship.secret,privateKey,secretKeyoraccessKeySecret, by name, with thecredentialRefprescription.signature.accessKeyIdis answered withkeyId.signedonprovider: 'mcp', atauth.type.connector-provider.ts:ConnectorRequestSignerhassign(request), which returns the headers to set and optionally a canonical URL, plus an optionalverifyResponse(response)for schemes that sign their responses (ADR-0143 Q4 condition 2: the seam carries it from the first slice).ConnectorProviderContext.requestSigneris set only for asignedinstance, andauthis thenundefined.ConnectorMaterialization.requestSignerAppliedis the provider's acknowledgement.connectorFetchOptions(policy, base, signer?): with a signer, it wrapsfetchImpl, so every attemptresilientFetchmakes is signed fresh. A retry gets a new nonce and date.resilient-fetch.tsis not touched.readConnectorResponse(response, signer?)verifies the raw body before parsing when the scheme verifies responses. With no signer, both functions behave exactly as the code they replace. Neither usesnode:crypto: signers live on the server.@objectstack/service-automationA
signedinstance'scredentialRefresolves through the same chain as the static kinds (connectors:sys_connector_credential+ open-tier resolver chain (env → credential object → vault), reconcile on credential change, install-local prompt for missingcredentialRefs (ruling A on #27230) #27237). An unresolved reference degrades the instance with the reference named, as before.The key is bound to its scheme's signer in
src/request-signing/(a registry with an exhaustive switch over the scheme enum, so a scheme without a signer does not compile). The provider receives only the signer.The service refuses to serve a
signedinstance on a provider whose materialization does not acknowledge the signer. That is a configuration fault naming the instance, the scheme and the provider. Its connection is closed, and it never dispatches unsigned (ADR-0049).A key that cannot sign (an empty one) is a configuration fault, and its message never contains the key.
aliyun-acs3-hmac-sha256builds the canonical request:host,content-typeandx-acs-*headersIt signs it with HMAC-SHA256 and sets
x-acs-date,x-acs-signature-nonce,x-acs-content-sha256andAuthorization.A datasource that borrows a
signedinstance throughexternal.connectoris refused with422 VALIDATION_ERROR, because a driver cannot sign per request.@objectstack/connector-rest,@objectstack/connector-openapictx.requestSignerto the transport and acknowledge it.openapiinstance's remote spec URL is fetched unsigned, as before. Only API calls are signed.Docs:
content/docs/automation/connectors.mdxgains a "Request signing:signed" section and a failure-table row.packages/spec/docs/SYNC_ARCHITECTURE.mdnames the kind. The variant-docs entry and the ADR anchor for the signer registry are registered. The generated references are regenerated withcheck:generated --fix.Why
ADR-0143 D2.3 opens per-request signing on declarative instances, with a closed scheme list that grows only with implemented signers and vectors. Q4 ruled A, so the first members are
alipay-v3,aliyun-acs3-hmac-sha256andtencentcloud-tc3-hmac-sha256. This slice is Aliyun because Alibaba Cloud publishes a complete worked example, so the seam is proven end to end against a byte-exact vector.tencentcloud-tc3-hmac-sha256(S2) andalipay-v3with response verification (S3,Fixes #26710) each join the enum together with their signer.Verification
packages/services/service-automation/src/request-signing/aliyun-acs3-hmac-sha256.test.tsreproduces Alibaba Cloud's published V3 signature example byte for byte: canonical request, string to sign, signature andAuthorization. The source is Alibaba Cloud's "V3 request structure and signature" document (help.aliyun.com 2593177), with its own placeholder key id and secret. A wrong key gives a different signature, and the canonical encoding of the query and path is pinned (11 tests).packages/spec/src/integration/connector-auth-signed.test.ts. Each refusal is pinned by issuecodeandpath, and there is a CONTROL accept onrestandopenapi.connector-auth-grant-kinds.test.tsstill refuses unknown kinds.bearer,api-keyandbasicparse as before.connector-fetch-policy.test.ts:readConnectorResponseverifies the raw body, and a verification failure throws.connector-materialization.test.ts:authis undefined, and the key is absent from the context.bearergets no signer.connector-credential-source.test.tscovers the 422.rest-provider.test.tsandopenapi-provider.test.tscover every attempt signed (a retry included), the acknowledgement, a verification failure rejecting, the static CONTROL without acknowledgement, and static plus signer refused.connector-rest-plugin.test.tsruns a real host with the rest provider. Two calls carry ACS3Authorizationheaders with different nonces and signatures, and the key appears in no call and no descriptor.pnpm --filter <pkg> typecheckis green for spec, service-automation, connector-rest and connector-openapi.dispatch-gates --commandsderives (115) ran and exited 0, reconciled with--ran: 115 run, 0 NOT-MEASURED, after a dependency-closure build and the pinned teardown fixture fetch.pnpm lintexits 0.Skills-docs:
grep -rlnE "credentialRef|app-credential|api-key" skillshitsskills/objectstack-automation/{SKILL.md,references/integration-recipes.md,evals/integrations-declared.json}, which list the instance auth kinds. Thesignedtext rides #28226 (the skills follow-up for the ADR-0143 kinds), because askills/**edit would make this slice Tier H.content/docs/automation/connectors.mdxcarries it here.Human Minimum
packages/specholds no runtime logic. (The signing wrap extends the existingconnectorFetchOptionstransport helper, which already lives in spec besideresilientFetch. It holds no crypto; signers live inservice-automation.)pnpm lintpasses.main, one pull request (slice 1 of 3 of connectors: request-signing auth kind for the openapi and rest providers (RSA2 / HMAC canonical-request signers: Alipay, WeChat Pay, Aliyun, Tencent Cloud, e签宝) — the vendor-auth half that #26702 does not cover #26710).packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4cxRP1AqTeP2PWAhwGK