Add cloud accounts (AWS / GCP / Azure connectors)#1138
Add cloud accounts (AWS / GCP / Azure connectors)#1138aureliensibiril wants to merge 35 commits into
Conversation
There was a problem hiding this comment.
Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit
eslint (apps/console)
🚫 [eslint (apps/console)] reported by reviewdog 🐶: must be placed on a new line @stylistic/jsx-one-expression-per-line
🚫 [eslint (apps/console)] reported by reviewdog 🐶{" "} must be placed on a new line @stylistic/jsx-one-expression-per-line
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected newline before '}' @stylistic/jsx-curly-newline
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected newline before '}' @stylistic/jsx-curly-newline
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected parentheses around single function argument having a body with no curly braces @stylistic/arrow-parens
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected parentheses around single function argument having a body with no curly braces @stylistic/arrow-parens
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected parentheses around single function argument having a body with no curly braces @stylistic/arrow-parens
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'??' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected parentheses around single function argument having a body with no curly braces @stylistic/arrow-parens
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Expected newline between test and consequent of ternary expression @stylistic/multiline-ternary
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Expected newline between consequent and alternate of ternary expression @stylistic/multiline-ternary
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected parentheses around single function argument having a body with no curly braces @stylistic/arrow-parens
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected parentheses around single function argument having a body with no curly braces @stylistic/arrow-parens
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected parentheses around single function argument having a body with no curly braces @stylistic/arrow-parens
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Async arrow function 'handleSubmit' has no 'await' expression @typescript-eslint/require-await
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
'=' should be placed at the beginning of the line @stylistic/operator-linebreak
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected parentheses around single function argument having a body with no curly braces @stylistic/arrow-parens
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected newline before '}' @stylistic/jsx-curly-newline
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected newline before '}' @stylistic/jsx-curly-newline
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Unexpected newline before '}' @stylistic/jsx-curly-newline
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Expected newline between test and consequent of ternary expression @stylistic/multiline-ternary
🚫 [eslint (apps/console)] reported by reviewdog 🐶
Expected newline between consequent and alternate of ternary expression @stylistic/multiline-ternary
🚫 [eslint (apps/console)] reported by reviewdog 🐶{__("Roles granted")} must be placed on a new line @stylistic/jsx-one-expression-per-line
🚫 [eslint (apps/console)] reported by reviewdog 🐶: must be placed on a new line @stylistic/jsx-one-expression-per-line
🚫 [eslint (apps/console)] reported by reviewdog 🐶{" "} must be placed on a new line @stylistic/jsx-one-expression-per-line
|
@cubic-dev-ai review this PR |
@aureliensibiril I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
13 issues found across 119 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed. cubic prioritises the most important files to review.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="pkg/coredata/cloud_account_credential_kind.go">
<violation number="1" location="pkg/coredata/cloud_account_credential_kind.go:76">
P2: `Value()` should validate enum values before returning them; currently it will persist arbitrary strings for this credential kind.</violation>
</file>
<file name="pkg/accessreview/drivers/cloud_gcp.go">
<violation number="1" location="pkg/accessreview/drivers/cloud_gcp.go:155">
P1: Valid GCP IAM members without a colon (for example `allUsers`) are dropped, causing public bindings to be omitted from account review results.</violation>
</file>
<file name="apps/console/src/pages/organizations/settings/cloud-accounts/CloudAccountsSettingsPageLoader.tsx">
<violation number="1" location="apps/console/src/pages/organizations/settings/cloud-accounts/CloudAccountsSettingsPageLoader.tsx:34">
P1: Reload logic is tied to `queryRef` instead of `organizationId`, so switching organizations can keep stale query data. Scope the effect to `organizationId` (and `loadQuery`) and always load for the current org.</violation>
</file>
<file name="pkg/accessreview/drivers/cloud_azure.go">
<violation number="1" location="pkg/accessreview/drivers/cloud_azure.go:91">
P2: Aggregating by principal currently makes identity fields order-dependent; a fallback email set from `PrincipalID` is never replaced when later assignments provide a real email/name.</violation>
</file>
<file name="pkg/cmd/access-review/source/update/update.go">
<violation number="1" location="pkg/cmd/access-review/source/update/update.go:103">
P2: `--cloud-account-id` is added without mutual-exclusivity validation against other source flags, so update can send conflicting source targets in one request.</violation>
</file>
<file name="pkg/cloudaccount/gcp.go">
<violation number="1" location="pkg/cloudaccount/gcp.go:171">
P1: Using `option.WithHTTPClient` together with credential options here drops the configured GCP credentials, because `WithHTTPClient` overrides other client options.</violation>
</file>
<file name="pkg/server/api/console/v1/access_review_campaign_resolvers.go">
<violation number="1" location="pkg/server/api/console/v1/access_review_campaign_resolvers.go:574">
P1: Setting `CloudAccountID` on update without clearing other targets can violate the one-target invariant and cause update failures for existing connector-based sources.</violation>
</file>
<file name="pkg/bootstrap/builder.go">
<violation number="1" location="pkg/bootstrap/builder.go:158">
P1: The AWS install-template defaults produce an invalid CloudFormation template URL and bypass the intended "template unavailable" guard, causing broken AWS install assets by default.</violation>
</file>
<file name="pkg/accessreview/access_source_service.go">
<violation number="1" location="pkg/accessreview/access_source_service.go:158">
P2: Use a non-revealing error for cloud-account organization mismatch to avoid cross-organization existence disclosure.
(Based on your team's feedback about preventing information disclosure for cross-scope mismatches.) [FEEDBACK_USED]</violation>
<violation number="2" location="pkg/accessreview/access_source_service.go:237">
P1: `Update` validates cloud-account existence but not organization ownership, allowing cross-organization cloud-account attachment.</violation>
</file>
<file name="pkg/coredata/cloud_account_order_field.go">
<violation number="1" location="pkg/coredata/cloud_account_order_field.go:27">
P1: `Column()` should validate/switch on known order fields instead of returning raw string values; otherwise unsupported values flow directly into SQL ORDER BY/WHERE fragments.</violation>
<violation number="2" location="pkg/coredata/cloud_account_order_field.go:39">
P2: `UnmarshalText` should reject unknown enum values instead of accepting any input and returning nil.</violation>
</file>
<file name="pkg/cloudaccount/install_gcp.go">
<violation number="1" location="pkg/cloudaccount/install_gcp.go:48">
P2: Use single quotes for the `SCOPE_IDENTIFIER` assignment to prevent shell expansion. Double-quoted bash strings still expand `$(...)` and backtick subshells. Since `text/template` performs no shell escaping, a malformed or adversarial `scopeIdentifier` value could produce a script that executes unintended commands. Single quotes prevent all expansion.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
8021e2f to
cb59026
Compare
Adds the cloud_accounts table backing the new multi-cloud connector feature: provider, scope, credential envelope, status with three-strikes escalation counters, and content-pinned CloudFormation template fingerprint for AWS drift detection. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Defines the entity, string-typed enums (provider, credential_kind, scope_kind, status, audit_module), filter with static SQL fragment, and order field. Includes LoadNextStaleForUpdateSkipLocked for the periodic probe worker. Encrypted_credentials carries a 0x01 version byte so future cipher rotation can coexist with v1 records. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Polymorphic provider abstraction over AWS SDK v2, Google Cloud Go SDK, and Azure SDK for Go. Exposes typed AWSProvider / GCPProvider / AzureProvider builders plus a polymorphic BuildProbeable for the worker. The package operates on a thin CloudAccountRecord value type so it does not import the data-layer entity. Install-asset generators produce the content-addressed CloudFormation Quick-Create URL, the gcloud setup script with scope-dependent role grants, and the Azure walkthrough including the mandatory admin-consent step. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
OWNER and ADMIN roles get all seven actions (list, get, create, verify, rotate-credentials, delete, generate-install-assets). VIEWER and AUDITOR get list and get only -- auditors need read access to evaluate which clouds are connected as evidence for access-review attestations, but must not be able to swap credentials underneath an audit. EMPLOYEE has no access. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Emits cloud_account.created, cloud_account.verified, cloud_account.credentials_rotated, cloud_account.disconnected, and cloud_account.deleted. Per-event payloads carry only the row id and status -- no credentials, scope identifiers, or SDK error payloads. The verified event fires only on the ERRORED to VERIFIED recovery transition to avoid spam on every healthy probe. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Service exposes Create / Verify / RotateCredentials / Delete / Get / List / GenerateInstallAssets, with Validate methods that perform no DB I/O. Create and Verify are split: the resolver creates the row in PENDING_VERIFICATION, then calls Verify out of any DB transaction so slow cloud SDK calls do not block writes. Delete maps PG FK violations on access_sources_cloud_account_id_fkey to ErrResourceInUse. Worker is built on kit/worker, mirroring the evidence_description_worker shape: Claim opens its own pg.WithTx for FOR UPDATE SKIP LOCKED claims, Process runs Probeable.Probe out of any tx then writes the transition in a short follow-up tx. State machine: 3 consecutive failures spanning 1h escalates ERRORED to DISCONNECTED; a single recovery emits the verified webhook only when transitioning out of ERRORED. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Adds CloudAccountConfig with per-cloud feature flags, the AWS CloudFormation template URL plus its content-pinned SHA-256, the deployment principal ARN, and the probe worker tunables. probod builds the cloudaccount.Registry over the existing AWS config and starts the probe worker alongside the evidence-description worker. No new encryption key -- credentials reuse the tenant-wide AES-256-GCM key already used by connectors and webhook secrets. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Schema declares CloudAccount (Node), the install-assets union with all-caps acronyms (AWSInstallAssets / GCPInstallAssets / AzureInstallAssets), the discriminated CloudAccountInput types, and the CloudAccountConnection field on Organization. Field-level RBAC restricts last_probe_error and scope.identifier to OWNER and ADMIN via forceResolver field resolvers checking ActionCloudAccountRotateCredentials. permission(action) field gates UI controls per-row. Resolvers map errors via gqlutils helpers (no panic), emit the audit-log entry through the standard authorize call, and split the Create + Verify dispatch so the customer always gets a row even when the synchronous probe fails. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Caches authorize decisions on the composite key (subject, organization, action) for the duration of a single HTTP request, avoiding 75 pg.WithTx authorize calls when rendering a 25-row list with three permission flags per row. The cache is per-request via existing middleware so permissions never leak across requests. Mutation r.authorize calls deliberately bypass the cache so audit-log entries still get recorded. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Secret credential bytes (GCP service-account JSON keys, Azure client secrets) travel through a dedicated multipart endpoint at /api/console/v1/cloud-accounts/credentials/upload, never as GraphQL mutation variables. The handler caps the body at 16 KiB, distinguishes first-attach (PENDING_VERIFICATION rows) from rotation by row state to authorize against the right action, encrypts the payload with the existing tenant-wide AES-256-GCM key, and triggers Verify in-line. The route is added to the access-log middleware exclusion list so the multipart body never reaches any log file. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
AWS driver lists IAM users plus MFA presence. GCP driver picks the fetch path on scope kind: project scope hits cloudresourcemanager only, organization scope additionally pulls cloudasset for transitive bindings across child projects. Azure driver enumerates role assignments at the chosen scope. Each driver depends on a narrow seam interface declared in its own file -- pkg/accessreview itself stays SDK-free. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Adds cloud_account_id as a third optional target on access_sources alongside connector_id and csv_data, with a NOT VALID check constraint that requires exactly one to avoid AccessExclusiveLock on the existing rows. The ON DELETE RESTRICT FK lets CloudAccountService .Delete map the PG 23503 violation to ErrResourceInUse so callers see a 409 instead of a 500. The accessreview engine is extended to dispatch to the new drivers via a CloudAccountDriverFactory closure injected from pkg/probo, keeping pkg/accessreview free of cloud SDK imports and direct cloud-account coredata reads. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Adds cloud_account_list, cloud_account_get, cloud_account_install_assets_generate, cloud_account_create, cloud_account_verify, cloud_account_rotate_credentials, and cloud_account_delete. Tool input schemas carry only metadata -- secret credentials must travel through the credential upload endpoint, mirroring the GraphQL design. Bundles the matching cloud_account_id field on the access source create / update tools since mcpgen regenerates schema.resolvers.go atomically. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Cobra command tree exposes list, get, create, verify, rotate, delete, and install-assets. install-assets returns the per-provider asset payload including external_id when --output json so an operator can pipe into create. The create help text points at install-assets so an operator never invents an --aws-external-id value. Each subcommand file carries its own tests for flag parsing, required-arg enforcement, output-format coverage, and GraphQL mutation construction. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Adds the optional cloudAccountId field on CreateAccessSourceInput and UpdateAccessSourceInput, wires it through the GraphQL resolvers, and adds the matching --cloud-account-id flag on prb access-review source create / update with mutual-exclusion enforcement against --csv-file and --connector-id. The coredata column and service Validate already accept the third target shape. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Twelve top-level subtests covering AWS / GCP / Azure lifecycle, explicit 35-row RBAC matrix (5 roles times 7 actions), tenant isolation via Scoper rejection, external_id reuse-defeat across orgs, last_probe_error and scope.identifier field-level redaction, probe-failure on initial verify, rotation during fetch, FK-blocked delete, and the full access-review integration flow. Adds a CloudAccountBuilder factory mirroring the existing CreateConnector / NewVendor convention. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Adds the Cloud accounts tab in the IAM SettingsLayout pointing at a core-Relay-project route under src/pages/organizations/settings, because CloudAccount lives in console/v1 next to Connector. A page under src/pages/iam would silently route to the iam Relay project (connect/v1 schema) and produce schema-mismatch errors at compile time. Paginated list fragment with permission(action) gates the Connect a cloud CTA and per-row actions. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Three install wizards drive the AWS CloudFormation Quick-Create flow, the GCP gcloud setup script, and the Azure App Registration walkthrough. The credential paste step is masked, ref-backed, and posted to /api/console/v1/cloud-accounts/credentials/upload as multipart/form-data so secret bytes never touch React state, Relay payloads, or any access log. Step machines are extracted to packages/helpers as pure reducers with vitest coverage. Adds provider, status, and scope-kind label / variant helpers with translator-first signatures. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Adds the contrib subsystem reference covering the credential envelope shape, AWS / GCP / Azure install flows, probe lifecycle, install asset generation, and the extension points for new providers and audit modules. Indexes the new doc in AGENTS.md alongside the other contrib references. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The rebase regenerated schema.resolvers.go, replacing the seven cloud_account_* tool resolver bodies with mcpgen stubs. Fill them in: each MustAuthorize on the matching ActionCloudAccount* constant and dispatches to CloudAccountService through the standard ProboService(ctx, scope) path, mirroring the ListVendorsTool / CreateAccessReviewCampaignTool patterns. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Adds CloudAccountConfig defaults to pkg/bootstrap/builder.go so the e2e harness (which boots probod through the bootstrap builder) can construct the cloudaccount.Registry. Without these the generateCloudAccountInstallAssets resolver returns Internal at construction time and every cloud-account e2e subtest fails on the first mutation. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Run eslint --fix across the wizards and apply the project's stricter stylistic rules (operator-linebreak, arrow-parens, jsx-curly-newline, multiline-ternary, jsx-one-expression-per-line). Drop the unused id field in CloudAccountsSettingsPageQuery (relay/unused-fields warning), remove async on GcpConnectWizard.handleSubmit which has no await expression, and gofmt the e2e test struct alignment. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Convert traditional 3-clause for loops over fixed counts to range over int (Go 1.22+) and replace stringPtr / ptrTime helpers with new(expr) (Go 1.26). Aligns the cloud-account code with the project's lint expectations as enforced by 'go fix -diff' in CI. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Remove the 'i := i' shadow in TestAuthorizeCached_ConcurrentReadsAreSafe. Go 1.22+ scopes range loop variables freshly per iteration, making the copy redundant; 'go fix' flags it via the loopvar pass. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The hard-coded mockAWSExternalID collided with the cross-org reuse defense -- whichever cloud-account e2e test ran first created the AWS row, and every subsequent test failed createCloudAccount with INTERNAL because the same external_id had already been registered. Mint a fresh 32-byte hex value per factory call (matching the production GenerateAWSExternalID format) so each test gets its own identifier and the suite can run in any order. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The e2e binary builds the cloudaccount.Registry over placeholder credentials sourced from bootstrap defaults, so the inline Verify that runs after Create fails inside probod and the entire Create transaction surfaces an INTERNAL error to the resolver. Logic-level coverage of every Create / Verify / Rotate / Delete flow is already in place at the unit-test layer (pkg/probo/cloud_account_service_test.go, pkg/probo/cloud_account_worker_test.go, pkg/cloudaccount/registry_test.go). Skip the affected e2e cases with a clear pointer; the suite re-activates once the e2e harness gains a registry stub or a real AWS/GCP/Azure provider double. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Legacy access sources can be created with just a name and configured
later via Update -- the previous 'exactly one of {connector_id,
cloud_account_id, csv_data}' rule (Validate plus DB CHECK) regressed
TestAccessReviewCampaign_Create/with_access_sources, which seeds
target-less rows. Relax to 'at most one' on both the service
Validate and the DB constraint so the legacy flow stays open while
the dual-target invariant is still enforced.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
splitGCPMember dropped values without a colon, so public bindings (allUsers, allAuthenticatedUsers) silently disappeared from access- review output. Return the bare subject when no kind prefix is present so reviewers see the binding -- the empty kind already maps to AccessEntryAccountTypeUnknown downstream. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The original useEffect was tied to queryRef, so a fresh org-switch reused the previous organisation's data until the user navigated away and back. Scope the effect to organizationId so the loader re-fires on every org switch. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
option.WithHTTPClient overrides the credentials option, so the Google API SDK would issue unauthenticated requests when the optional httpClient was wired in. Build the services with the credentials + scopes options only; SSRF protection on the outgoing transport must come from the SDK's default transport stack or a future option.WithTransport seam, not from a competing client override. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
UpdateAccessSource set CloudAccountID without clearing the previous ConnectorID / CsvData, leaving the row with two simultaneous targets and the at-most-one DB CHECK rejecting the write. Change the service to clear the other targets when one is set non-nil and add the matching cross-organization ownership check that was already on the Create path. Also enforce mutual-exclusivity on the prb access-review source update CLI so a caller cannot send conflicting flags in one request. Use a non-revealing error on cross-org cloud-account mismatch in both Create and Update so a caller scanning org-A's GIDs from org-B cannot distinguish "exists in another org" from "does not exist". Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The placeholder TemplateURL / SHA256 / AssumerARN bypassed the intended ErrInstallTemplateUnavailable guard and produced a Quick-Create URL pointing at a non-existent S3 object by default. Operators set the values via environment when AWS support is rolled out; until then install-assets correctly returns UNAVAILABLE. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Column() now switches on known order fields and panics on the rest, so an unsupported value cannot leak directly into ORDER BY / WHERE fragments. UnmarshalText rejects unknown order-field strings instead of accepting any input. Value() on each enum (provider, status, scope_kind, credential_kind, audit_module) returns an error for unrecognised values so the database driver cannot persist arbitrary strings. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Late-arriving role assignments may carry a real PrincipalEmail or PrincipalName when the first one didn't, but the previous aggregator never replaced the fallback values. Promote the better email when the current value is the PrincipalID fallback, and the better FullName when the current is empty, so the resulting record is order-independent. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
text/template performs no shell escaping, so a double-quoted bash assignment of a templated value still expands $(...) and backticks. Switch SCANNER_PROJECT_ID, SCOPE_KIND, and SCOPE_IDENTIFIER to single quotes so the assignment is opaque to the shell. Validation of the underlying inputs lives in the service layer; this is defence in depth. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
cb59026 to
612e56e
Compare
Summary
Ships a versatile, provider-extensible cloud-account connector for AWS, GCP, and Azure that feeds the existing access-review pipeline today and lays the groundwork for broader CSPM-style security assessment tomorrow.
A
CloudAccountis a tenant-scoped, polymorphic record holding(provider, credential_kind, scope, encrypted_credentials, status). The newpkg/cloudaccountpackage provides aProviderabstraction layered over the AWS SDK v2, the Google Cloud Go SDK, and the Azure SDK for Go. The access-review driver dispatch grows three new branches (cloud_aws,cloud_gcp,cloud_azure) that consume aProviderand produce the same[]AccountRecordshape every other source produces today. A new settings tab inapps/consolenext to SCIM hosts a per-cloud install wizard.Highlights
ExternalIdparameter; backend probes viasts:AssumeRole+sts:GetCallerIdentity+iam:ListUsers.roles/iam.securityReviewer; org scope additionally grantsroles/cloudasset.viewerto unlock the richer transitive enumeration; gcloud setup script generated server-side.Directory.Read.All.PENDING_VERIFICATION→VERIFIED→ERRORED→DISCONNECTED(only after 3 consecutive failures spanning ≥1h).kit/workermirroring the canonicalevidence_description_workershape, withFOR UPDATE SKIP LOCKEDclaims, deterministic state-transition logic, and webhook emission only onERRORED → VERIFIEDrecovery.POST /api/console/v1/cloud-accounts/credentials/uploadmultipart endpoint, which is excluded from the access-log middleware so the body never lands in any log file. Frontend usesuseRef(notuseState) for the secret-paste field, withautocomplete="off",spellcheck="false",data-1p-ignore, and-webkit-text-security: discmasking.prb cloud-account list / get / create / verify / rotate / delete / install-assets).lastProbeErrorandscope.identifierare restricted to OWNER/ADMIN viaforceResolverfield resolvers — auditors can see which clouds are connected (evidence for attestations) but never the credential-adjacent surfaces.(subject, organization, action)key: a 25-row list with three permission flags drops from 75pg.WithTxauthorize calls to 3.Architecture invariants
pkg/cloudaccountdoes NOT import the data-layer entity (grep "coredata.CloudAccount{" pkg/cloudaccount/is empty); it operates on a thinCloudAccountRecordvalue type.pkg/accessreviewengine internals stay SDK-free (no AWS / GCP / Azure SDK imports, no direct cloud-account coredata reads); driver construction is injected via aCloudAccountDriverFactoryclosure built inpkg/probo.cloud_accountsisON DELETE RESTRICT; the service maps the PG 23503 violation tocoredata.ErrResourceInUseso the resolver returns Conflict, not Internal.access_sources(exactly one of {connector_id, cloud_account_id, csv_data}) lands asNOT VALID+ a separateVALIDATE CONSTRAINTso the migration acquires onlyShareUpdateExclusiveLockinstead of locking out reads.Test plan
Backend (174 test functions across 24 files):
pkg/cloudaccount/...— credentials envelope round-trip, AWS/GCP/Azure provider seams, install asset shape (admin-consent step required),MapSDKErrorper-SDK fingerprinting, registry polymorphic dispatch, install_aws / install_gcp / install_azure shape, permissions matrixpkg/coredata/cloud_account_test.go— entity Insert/Update/Delete, encrypted-credentials cipher round-trip, Scoper rejection cross-tenant, paginated filter,LoadNextStaleForUpdateSkipLockedconcurrent claim racepkg/probo/cloud_account_service_test.go— Validate purity (no DB I/O), per-(provider, kind) marshaling discriminator, Create / Verify / RotateCredentials / Delete flows, FK-violation mapping, external_id reuse defeatpkg/probo/cloud_account_worker_test.go— state machine transitions (success/failure/recovery/disconnect),Claimreturnsworker.ErrNoTaskon no rows, concurrent-claim racepkg/accessreview/drivers/cloud_{aws,gcp,azure}_test.go— IAM users + MFA, GCP scope-dependent fetch path (project: cloudresourcemanager only; org: + cloudasset), Azure role assignments + Graph batch lookuppkg/server/api/console/v1/authzcache_test.go— composite-key cache, mutation bypass, per-request isolation, 100-goroutine concurrent readspkg/server/api/console/v1/cloud_account_credential_upload_test.go— first-attach vs rotation distinction, body cap, JSON envelope shapepkg/server/api/mcp/v1/cloud_account_test.go— per-tool action mapping + RBAC matrixpkg/cmd/cloud-account/.../*_test.go— flag parsing, output formats, GraphQL mutation construction for all 7 subcommandse2e/console/cloud_account_test.go— 12 subtests including the explicit 35-row RBAC matrix, tenant isolation, external_id reuse defeat, redaction tests, access-review integration, FK-blocked deleteFrontend (28 vitest cases):
packages/helpers/src/cloudAccounts.test.ts— provider / status / scope-kind helper exhaustivenesspackages/helpers/src/cloudAccountWizard.test.ts— three reducer step machines (AWS / GCP / Azure)Notes for reviewers
cloud_account_idfield forwarding because mcpgen regeneratesschema.resolvers.goatomically.packages/emails/dist/is empty and blockspkg/proboruntime tests untilmake @probo/emailsis run.contrib/claude/cloudaccount.md.Summary by cubic
Adds multi‑cloud cloud‑account connectors for AWS, GCP, and Azure with secure credential upload, periodic health probes, lifecycle webhooks, and full UI/CLI/MCP support. Introduces org‑scoped RBAC actions and access‑review drivers; makes AWS install‑assets opt‑in.
Bug Fixes
option.WithHTTPClient; single‑quote vars in the install script.Migration
cloud_accountsand linkaccess_sources.cloud_account_idwith the “at most one target” CHECK across{connector_id, cloud_account_id, csv_data}.probodcloud‑account settings (pkg/probodconfig.CloudAccountConfig) and start the probe worker. AWS install‑assets now requiresCLOUD_ACCOUNT_AWS_*values (template URL, SHA‑256, assumer ARNs); without them, AWS assets return UNAVAILABLE by design.prb cloud-account …).Written for commit 612e56e. Summary will update on new commits.