feat(kas)!: ConnectRPC transport + well-known discovery (CWT)#39
Conversation
Port of opentdf-rs #86 to OpenTDFKit. ConnectRPC unary-JSON transport at /kas.AccessService/*, well-known discovery, SSRF-validated endpoints, no-redirect URLSession, Connect error-envelope parsing, opaque CWT/JWT bearer passthrough. EC public-key request shape validated against the Go opentdf/platform PublicKeyRequest proto. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduces OpenTDFConfiguration, KasConfig, and IdpConfig structs for decoding the /.well-known/opentdf-configuration document, plus static builders forKasConnect() and forKasLegacyRest() for synthesizing configurations without a well-known endpoint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds KASDiscoveryError enum and validateKasURL() function that enforces HTTPS (HTTP allowed only for loopback), rejects private/link-local/unspecified IPs (IPv4, IPv6 ULA/link-local, and IPv4-mapped IPv6 literals). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…host Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce KasTransport enum and KasEndpoints struct with a from(_:) resolver that prefers ConnectRPC URLs, falls back to legacy REST, and validates both resolved URLs (HTTPS/scheme/SSRF) before returning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate CLI KASRewrapClient instantiation from legacy kasURL-based init to configuration-based init, resolving platform config via well-known discovery at the platform root with ConnectRPC fallback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the transitional init(kasURL:) bridge. KASRewrapClient now requires a resolved OpenTDFConfiguration (well-known discovery or forKasConnect). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add KASConnectTransportTests using MockURLProtocol to verify the Connect transport path: EC public-key POST request headers/parsing and 401 error envelope surfacing as authenticationFailed with code+message reason string. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 33565676 | Triggered | Generic High Entropy Secret | 328f8a2 | OpenTDFKitTests/PlatformConnectIntegrationTests.swift | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
testKeyUnwrappingWithValidKeys sealed the wrapped key with an empty HKDF salt (the Standard TDF convention) but called unwrapKey() with no salt arg, which since 0ee0797 defaults to CryptoConstants.hkdfSalt (the NanoTDF v12 session-key salt). The salt mismatch produced different AES-GCM keys, so tag verification failed with authenticationFailure — a stale test, not a product bug (unwrapKey's default matches the real KAS rewrap_dek derivation). Replace the single ambiguous test with a DRY helper and two explicit, self-consistent round-trips: - testKeyUnwrappingNanoTDFDefaultSalt: seal+unwrap with the v12 default salt - testKeyUnwrappingStandardTDFEmptySalt: seal+unwrap with empty salt Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…an FP) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a .swiftformat config (--swiftversion 6.2 matching Package.swift, --disable noForceUnwrapInTests since its autocorrect breaks non-throwing benchmark functions, --exclude .build) so local 'swiftformat .' and the CI lint step agree. CI now runs 'swiftformat --lint .' driven by the config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mechanical reformat to satisfy the (previously red) lint check. No behavior change: build and the full test suite are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- NanoTDF.getPayloadPlaintext: move the orphaned doc comment back onto the method (it had been separated from its declaration by sharedCryptoHelper, which is why swiftformat downgraded it to // — reorder keeps /// and lint green) - isLoopbackHost: normalize a trailing FQDN dot so 'localhost.' is treated as loopback (parity with the Rust loopback detection) - KasEndpoints.from: reject an empty kas.uri up front (an empty identity would make matchesKasURL match nothing and fail every rewrapTDF), with a test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Review ✅ Approved 3 resolved / 3 findingsMigrates KAS client transport to ConnectRPC with well-known discovery and addresses all previously identified SSRF, URL matching, and documentation findings. Includes repository-wide SwiftFormat application and resolved legacy test regressions.
✅ 3 resolved✅ Security: SSRF loopback check bypassed by
|
| Auto-apply | Compact |
|
|
Important
Your trial ends in 4 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.
Was this helpful? React with 👍 / 👎 | Gitar
Summary
Ports opentdf-rs #86 to OpenTDFKit: migrates the native KAS client transport from REST
/kas/v2/*to ConnectRPC unary-JSON at/kas.AccessService/*, driven by/.well-known/opentdf-configurationdiscovery. Bearer tokens are now opaque passthrough — a JWT or a base64url-encoded CWT (the platform decides validation).Breaking:
KASRewrapClient.initnow requires anOpenTDFConfigurationinstead of a basekasURL.What's included
OpenTDFKit/KASDiscovery.swift—OpenTDFConfiguration/KasConfig/IdpConfigCodable types (incl.access_token_format: application/cwt);KasEndpoints.from(_:)resolution (Connect-preferred, REST-fallback) that validates both resolved URLs;validateKasURLwith an SSRF guard (IPv4/IPv6 private, link-local, unspecified, and IPv4-mapped-IPv6 literals viainet_pton);ConnectError/parseConnectError;fetchWellKnown; andforKasConnect/forKasLegacyRestbuilders.KASRewrapClient— breakinginit(configuration:oauthToken:) throws; aNoRedirectDelegate(Swift equivalent ofredirect::Policy::none()) applied to all bearer-carrying calls; rewrap posts to the resolved Connect URL with Connect error-envelope parsing;authenticationFailed(String?)surfaces the Connect reason; transport-branched EC public-key fetch; NanoTDF request-body KAS identity derived from the parsed header with aconfig.kas.urifallback.KASDiscoveryTests(config decode, endpoint resolution, full SSRF matrix, Connect error parsing, well-known fetch via aMockURLProtocol);KASConnectTransportTests(Connect public-key POST + rewrap 401-envelope surfacing); opt-inPlatformConnectIntegrationTests(3 live tests, gated onKAS_INTEGRATION_TESTS=1).Corrections vs. a literal Rust copy (both verified)
parsedHeader.kas(config fallback), mirroring Rust'srewrap_nanotdf(header, kas_url).{"algorithm":"ec:secp256r1"}— confirmed against the Goopentdf/platformPublicKeyRequest { algorithm, fmt, v }proto, since Rust's empty{}returns RSA.Connect-Protocol-Version: 1is sent only on the.connecttransport.Test Plan
swift build— cleanswift test— 205 tests, 11 skipped, 1 failure (testKeyUnwrappingWithValidKeys, pre-existing on base77920b3, an unrelated bug in staticunwrapKey)KAS_INTEGRATION_TESTS=1 swift test --filter PlatformConnectIntegrationTests— 3/3 passed againsthttps://platform.arkavo.net(well-known discovery, Connect PublicKey PEM, Connect rewrap → 401)🤖 Generated with Claude Code
Also bundled in this PR (per maintainer request to combine)
testKeyUnwrappingWithValidKeyssealed with an empty HKDF salt but unwrapped with the v12 default salt (since0ee0797). Split into two explicit, self-consistent round-trips (NanoTDF default salt + Standard TDF empty salt). This turns thetestcheck green for the first time since 2026-03-20."invalid-bearer-token"placeholder..swiftformatconfig (--swiftversion 6.2matching Package.swift,--disable noForceUnwrapInTestssince its autocorrect breaks non-throwing benchmark functions,--exclude .build) and appliedswiftformatacross the repo (21 files) — thelintcheck had been red onmainsince 2026-01-18. CI now lints via the config.Known pre-existing issue (NOT addressed here)
StreamingBenchmarkTests.testMemoryUsageWith2MBChunkfails its 70MB memory assertion (peaks ~213MB for a 50MB file) in both debug and release — verified identical on pre-reformat code, so unrelated to this PR. It's a flaky benchmark that runs in thetestjob; recommend recalibrating the threshold or excluding benchmark suites from thetestjob in a follow-up.