Repository navigation
Conversation
Add `cloud access request`, which files a control-plane access request carrying a PKCE S256 challenge and a CSRF state, opens the approval URL in a browser, waits on a loopback redirect (with status polling as a fallback), claims the issued key with the PKCE verifier, and stores the key id + secret with the existing credential storage. The secret is never printed. Credentials, base URL, and organization are resolved through CloudClient pointed at a configurable control-plane base (default http://localhost:2023). CloudClient now carries the OAuth bearer token so bespoke control-plane requests can reuse the resolved identity.
The openapi control-plane app wraps every /v1 response as
{ "result": <payload>, "requestId": <string> }, so the access-request
handshake must read the payload from `result` instead of the top level.
Add a generic Envelope<T> and unwrap it in parse_response, and type the
file response's expiresAt as epoch-millis i64 (not a string). Add
envelope-deserialization tests for the file, poll, and claim payloads.
The poll fallback read AccessRequestStatus directly from the response body, which is
the {result,requestId,status} envelope, so it never matched an approved status and
polled until timeout. Unwrap Envelope<AccessRequestStatus> like the other calls.
Caught by a live end-to-end run against the local control plane.
Separate Cloud client construction from credential selection so access negotiation can choose its user identity explicitly. Keep ordinary command credential behavior intact while providing a reusable construction seam for the dedicated OAuth-only access flow.
Use refreshed user OAuth for access negotiation regardless of saved, flag or environment Basic credentials. Preserve explicit URL precedence over the saved OAuth endpoint and Cloud default. Fail with login guidance when OAuth is unavailable rather than falling back to Basic authentication; leave the PKCE handshake unchanged.
Explain the access flow’s OAuth identity requirement and endpoint precedence. Clarify that saved or explicitly supplied Basic credentials do not select the requester identity and that unusable OAuth requires login instead of fallback.
Add a typed organization page reader accepting an opaque cursor. Preserve the existing first-page API for callers while exposing pagination metadata needed for complete OAuth membership selection.
Resolve access organizations from complete OAuth memberships, with explicit ID, exact name and singleton-only automatic selection. Preserve legacy selectors and reject conflicting explicit selectors before authentication or network access using the actual command grammar. Fail closed on malformed, duplicate or cyclic page results without default-org fallback.
Add a Bearer-only SDK reader for caller-assignable roles on the existing roles route. Preserve complete policy effects, resources and SQL tags in dedicated transport DTOs while retaining the administrative client contract. Keep absent fields distinct from empty arrays under SDK conventions and document mandatory consumer validation before discovery output. Add offline request and model coverage.
Expose cloud access roles using refreshed OAuth and the existing complete organization resolver. Validate the entire role menu before any success output. Emit a direct full-policy JSON array or a human-readable organization and scope table, with honest labels for unknown values and no catalog fallback. Update parent help and documentation; cover output, auth, selector and malformed-menu behavior with offline tests. Live discovery remains a separate checkpoint.
Accept repeated or comma-separated role UUID tokens on access request. Reject malformed input before OAuth or network access, canonicalize UUID identity, preserve first occurrence order, and enforce the 100-role limit after deduplication. Send optional requestedRoleIds alongside the unchanged PKCE handshake so users can propose discovered roles without treating them as grants. Preserve omission for manual selection and fail server rejections without subset retries. Document the grammar and cover parser boundaries, canonical request bodies, pre-network errors, and credential preservation with offline tests.
Distinguish pending and retryable HTTP 429/5xx responses from denied, expired, authentication, malformed, and other terminal responses. Preserve the existing approval timeout and fail promptly instead of hiding persistent failures until that timeout. Sanitize polling and claim diagnostics so echoed claim codes, verifiers, and secret values cannot reach stderr. Reject empty claimed credentials before storage while keeping the existing claim result and deadline contracts unchanged. Cover status transitions and echo safety with offline tests.
Add request-scoped --no-browser while preserving automatic launch by default and keeping callback and polling approval paths active. Always send the approval URL and progress to stderr so JSON stdout remains one unchanged final success object. Document manual handoff and the deferred claim-deadline distinction. Exercise both approval paths, terminal failures, malformed claims, credential preservation, and echoed-secret safety using isolated offline subprocesses; use an injected fake launcher to prove browser suppression without opening a real browser.
Validate server epoch deadlines and reconcile authoritative status at the end of the pending wait. Treat callbacks only as wakeups, require a matching usable GET claim permit, and let already-approved requests claim beyond pending expiry without extending the claim window. Bound access HTTP operations to 30 seconds and remaining phase time, guard clock rollback with immutable monotonic ceilings, and never retry an ambiguous claim. Fail closed on missing deadline support and malformed filing/status envelopes before installing credentials. Preserve OAuth, PKCE, browser handoff and the existing four-field success output; cover boundaries, callback/poll parity, stalled transports and credential safety with offline tests.
Consume the server-saved proposal from filing and require status responses to preserve its canonical UUID identities and order. Reject absent, malformed, duplicate or inconsistent role arrays instead of fabricating compatibility values or silently filtering server data. Use only the successful claim's nonempty actual grants for reporting, allowing human additions and trims without imposing the proposal's size limit. Add organization, request, requested-role and granted-role fields to final output, prepare JSON before the single credential write, and preserve existing credential and OAuth behavior. Cover callback/poll parity, old-server failures and secret-safe credential preservation with offline tests and document the additive contract.
Compile the experimental access-negotiation flow (assignable-role discovery and the scoped-API-key PKCE approval handshake) only under --features experimental-access-negotiation, so published binaries carry none of it: the subcommand is absent from help and parsing in default builds, and the access-only client surface (RequestAuth, request_auth, for_access, from_access_tokens) is compiled out with it. The clickhouse-cloud-api assignable-role models, reader and analyzer allowlist entry stay ungated as general library capability, and the library tests are unchanged. The access section of cli_request_shape_test.rs moves into a cfg'd module and runs only when the feature is enabled (20 tests, all green either way; the default run compiles 624 tests instead of 644). Verified: help smoke in both builds; all four clippy configurations (default/no-default-features x feature off/on) clean with -D warnings; fmt clean; classifier inventory tests pass; only the pre-existing test_auth_config_lookup failure remains.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Credential-bearing redirects, runtime reliability issues, and incompatible test fixtures remain unresolved, while CI does not execute the experimental tests.
5 open issues
What changed in this PR
Adds experimental Cloud access negotiation for #1131, building on #1082’s custom OAuth support. The flow discovers assignable roles and obtains scoped API keys through human approval.
Changes:
- Adds OAuth-only role discovery, organization selection, and validated role proposals.
- Implements browser or manual approval handoff, bounded polling, separate claim deadlines, and grant reporting.
- Adds typed API models, feature-gated tests, documentation, and certificate dependency updates.
| File | Description |
|---|---|
skills-draft/clickhousectl-least-privilege/SKILL.md |
Adds agent guidance for least-privilege access requests. |
README.md |
Documents access negotiation and credential behavior. |
crates/clickhousectl/tests/cli_request_shape_test.rs |
Adds feature-gated access workflow tests. |
crates/clickhousectl/src/main.rs |
Validates selectors and proposals before execution. |
crates/clickhousectl/src/cloud/permissions.rs |
Registers access command authorization declarations. |
crates/clickhousectl/src/cloud/mod.rs |
Routes access commands through OAuth-only handling. |
crates/clickhousectl/src/cloud/client.rs |
Adds access-specific identity and endpoint resolution. |
crates/clickhousectl/src/cloud/cli.rs |
Defines access commands and selector conflict checks. |
crates/clickhousectl/src/cloud/access.rs |
Implements role discovery and the approval/claim flow. |
crates/clickhousectl/Cargo.toml |
Adds the disabled-by-default experimental feature. |
crates/clickhouse-openapi-analyzer/src/config.rs |
Allowlists the distinct role-discovery helper. |
crates/clickhouse-cloud-api/tests/models_test.rs |
Tests assignable-role transport compatibility. |
crates/clickhouse-cloud-api/tests/client_test.rs |
Tests role-discovery authentication and responses. |
crates/clickhouse-cloud-api/src/models/rbac.rs |
Adds assignable-role and policy models. |
crates/clickhouse-cloud-api/src/models.rs |
Exports the new models. |
crates/clickhouse-cloud-api/src/client/organizations.rs |
Adds Bearer-only assignable-role discovery. |
crates/clickhouse-cloud-api/README.md |
Documents discovery contracts and caller validation. |
Cargo.lock |
Updates certificate-related dependencies. |
Review effort: Balanced
Comment on lines
+520
to
+521
| let http = crate::http::client_builder() | ||
| .build() |
| # and the scoped-API-key PKCE approval handshake). Off by default so the | ||
| # published binary carries none of it; opt in locally with | ||
| # `--features experimental-access-negotiation`. | ||
| experimental-access-negotiation = [] |
Comment on lines
+769
to
+774
| let mut buffer = [0u8; 2048]; | ||
| let read = stream.read(&mut buffer).await.ok()?; | ||
| if read == 0 { | ||
| return None; | ||
| } | ||
| let text = String::from_utf8_lossy(&buffer[..read]); |
| url_override.unwrap_or(&tokens.api_url), | ||
| ), | ||
| }, | ||
| None, |
Comment on lines
+981
to
+984
| let mut value = serde_json::json!({ | ||
| "access_token": "test-token", "refresh_token": "unused", | ||
| "expires_at": 4102444800_i64, | ||
| }); |
Collaborator
Author
|
Wrong pr |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Implements the Cloud access request workflow end to end, stacked on #1082
(
local-idp). From an OAuth login it discovers the caller-assignable roles,proposes the least-privilege set for a task, and hands the proposal to a human
for approval:
{result}envelope handling and terminal-failure polling fixesthe live token; complete organization selection from OAuth memberships
(
--org-id/--org-name, singleton auto-selection, cyclic-page protection)clickhouse-cloud-api(
organization_assignable_roles_get_list), with no administrative-catalog fallbackand claim deadlines, and validated-proposal/grant reporting
Per-commit detail is in the individual commit messages; the branch is a clean
linear sequence on top of #1082.
Testing
cargo fmt --allcargo clippy -p clickhousectl -- -D warningscargo test -p clickhousectlcargo clippy -p clickhouse-cloud-api -p clickhouse-openapi-analyzer --all-targets -- -D warningscargo test -p clickhouse-cloud-api -p clickhouse-openapi-analyzercargo check -p clickhousectl --no-default-features+ no-default-features clippyTracking issue: #1131