Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions apps/docs/content/docs/integrations/google-service-account.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Google service accounts can access shared resources directly or impersonate a Go

For example, you could build a workflow that iterates through a list of employees, impersonates each one to read their Google Docs, and uploads the contents to a shared knowledge base — all without requiring any of those users to sign in.

For Google Cloud APIs such as BigQuery and Vertex AI, you can skip the JSON key entirely: see [Keyless Google Cloud access](#keyless-google-cloud-access).
For Google Cloud APIs such as BigQuery and Vertex AI, and for Gmail, Drive, Docs, Sheets, and Calendar through domain-wide delegation, you can skip the JSON key entirely: see [Keyless Google Cloud access](#keyless-google-cloud-access) and [Keyless domain-wide delegation](#keyless-domain-wide-delegation).

## Prerequisites

Expand Down Expand Up @@ -180,7 +180,7 @@ A credential can serve multiple supported Google services when their APIs and sc
A Google service account credential can also be **keyless**: Sim stores no key. Instead, your Google Cloud project trusts Sim through [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers). On each run, Sim signs a short-lived token for this credential's own subject, exchanges it at Google's Security Token Service for a federated token, and uses that to impersonate your service account. The service account's IAM roles decide what workflows can do, exactly as with a JSON key.

<Callout type="info">
Keyless credentials work with Google Cloud APIs that the service account calls as itself: **BigQuery**, and **Vertex AI** in every block that runs a Vertex model (pick the credential in its **Google Cloud Account** field). They can't use domain-wide delegation, so Gmail, Drive, Sheets, Calendar, and other Google Workspace blocks don't offer them, and a keyless credential refuses an **Impersonated Account**. Use a JSON key for those.
Keyless credentials work with Google Cloud APIs that the service account calls as itself: **BigQuery**, and **Vertex AI** in every block that runs a Vertex model (pick the credential in its **Google Cloud Account** field). With [keyless domain-wide delegation](#keyless-domain-wide-delegation) they also work with **Gmail**, **Google Drive**, **Google Docs**, **Google Sheets**, and **Google Calendar**, acting as one Workspace user. Other Google Workspace blocks need a JSON key.
</Callout>

<Steps>
Expand Down Expand Up @@ -233,12 +233,46 @@ Self-hosted deployments need `WORKLOAD_IDENTITY_SIGNING_KEY` set and an `https`

### From the API or CLI [#keyless-api-cli]

Run `sim credentials keyless-subject google-service-account`, trust the printed subject as above, then add `projectNumber`, `poolId`, `providerId`, and `serviceAccountEmail` to its `keylessConfig` and pass it to `sim credentials create google-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. As in the dialog, keyless works only for Google Cloud services such as BigQuery and Vertex AI, not Google Workspace blocks.
Run `sim credentials keyless-subject google-service-account`, trust the printed subject as above, then add `projectNumber`, `poolId`, `providerId`, and `serviceAccountEmail` to its `keylessConfig` and pass it to `sim credentials create google-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. For domain-wide delegation, also add `"delegation": {"user": "alice@yourcompany.com", "services": ["gmail", "google-drive"]}`, listing any of `gmail`, `google-drive`, `google-docs`, `google-sheets`, and `google-calendar`.

### Keyless domain-wide delegation [#keyless-domain-wide-delegation]

A keyless credential can also act as one Google Workspace user in Gmail, Google Drive, Google Docs, Google Sheets, and Google Calendar, without a JSON key. Google's [domain-wide delegation guidance](https://knowledge.workspace.google.com/admin/apps/domain-wide-delegation-best-practices) recommends this: on each run, Sim exchanges its token at STS as above, then asks the IAM Service Account Credentials API to [sign](https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signJwt) the same delegation assertion a JSON key would sign, with your user as its subject, and exchanges it at Google's OAuth token endpoint.

<Steps>
<Step>
Set up the keyless credential as above, from the Gmail, Google Drive, Google Docs, Google Sheets, or Google Calendar integration. Under **Domain-wide delegation**, choose the services the credential may use, and enter the **Workspace user** it acts as.
</Step>
<Step>
Grant the subject `roles/iam.serviceAccountTokenCreator` on the service account instead of `roles/iam.workloadIdentityUser`; the setup commands do this when delegation is on. Signing needs the `iam.serviceAccounts.signJwt` permission, which Service Account Token Creator includes and Workload Identity User does not. Token Creator also grants permissions Sim doesn't use, such as `iam.serviceAccounts.signBlob`; for least privilege, grant a [custom role](https://cloud.google.com/iam/docs/creating-custom-roles) with just `iam.serviceAccounts.signJwt` and `iam.serviceAccounts.getAccessToken` (the second keeps BigQuery and Vertex AI working with the same credential).
</Step>
<Step>
In the Google Cloud console, enable each service's API (see the [scopes reference](#scopes-reference)) in the project that owns the service account, and copy the service account's numeric **Unique ID** (its OAuth 2 client ID).
</Step>
<Step>
Have a Google Workspace super administrator authorize that client ID in the Admin console under **Security** → **Access and data control** → **API controls** → **Manage Domain Wide Delegation**, for the scopes the dialog lists under **Scopes to authorize**.
</Step>
<Step>
Save in Sim. For each service, Sim signs in as the Workspace user and makes one read (the Gmail profile, Drive's account details, or the first calendar in the calendar list), so a missing role or authorization, or a disabled Gmail, Drive, or Calendar API, shows up now rather than in a run. Google Docs and Google Sheets use Drive's scopes and are checked through the Drive API only: enable the Google Docs API and Google Sheets API yourself, since a disabled one shows up only in a run.
</Step>
</Steps>

<Callout type="warn">
A keyless credential acts only as the Workspace user you entered. A workflow that sets a different **Impersonated Account** is refused; create one credential per user. To add a service or change the user, reconnect the credential and authorize any new scopes first; reconnecting opens on the credential's current user and services.
</Callout>

### Troubleshooting keyless access

- **"Google Cloud refused Sim's token for this workload identity provider":** the project number, pool ID, or provider ID doesn't match; the provider is disabled, has allowed audiences set, or uses a different issuer; or its attribute condition doesn't allow this credential's subject.
- **"Google Cloud could not fetch the signing keys from Sim's issuer":** the issuer isn't reachable over public HTTPS (self-hosted deployments).
- **"The service account … does not let this credential impersonate it":** run the `add-iam-policy-binding` command with the exact member shown, and enable the IAM Service Account Credentials API.
- **"This service account also trusts other Sim credentials":** remove any `principalSet` binding for the pool from the service account and keep the attribute condition.
- **A block fails with "This credential cannot be used here":** the block is a Google Workspace block or sets an **Impersonated Account**. Use a JSON key credential for it.
- **"… does not let this credential sign for it, which domain-wide delegation needs":** grant `roles/iam.serviceAccountTokenCreator` on the service account to the exact `principal://…/subject/SUBJECT` member, and enable the IAM Service Account Credentials API.
- **"Google Workspace has not authorized this service account's domain-wide delegation":** authorize the service account's client ID for every scope the dialog lists in the Admin console. New authorizations can take a while to apply.
- **"Your Google Workspace admin policies block one or more of these scopes":** a Workspace administrator must allow the service account's client ID under **API controls**, third-party app access.
- **"Google reports the service account … is disabled" (or "was deleted"):** enable the service account in Google Cloud under **IAM & Admin > Service Accounts**, or restore a deleted one within 30 days.
- **"Google refused to sign in as …":** the address isn't an active user in your Workspace domain.
- **"… but the Gmail API refused the request" (or Drive, Calendar):** enable that API in the project that owns the service account, and check the user has the service.
- **A block fails with "This credential cannot be used here":** the block needs scopes the credential's delegation doesn't authorize (or it isn't BigQuery or Vertex AI), or it sets an **Impersonated Account** other than the credential's Workspace user. Reconnect the credential with that block's service, or use a JSON key.

A keyless credential is used only for the scopes listed under **Scopes to authorize**, and Sim refuses anything else before contacting Google. Like Google, Sim authorizes by scope, not by block: Drive, Docs, and Sheets share those scopes, so a credential delegated for any of them also serves the other two and their file pickers, which ask for read-only Drive access that the authorized `drive` scope covers. Read-only Gmail access, which the Gmail label picker and knowledge base connector ask for, is served with the authorized `gmail.modify` scope, and the Google Calendar knowledge base connector's read-only events access with the authorized `calendar` scope. Knowledge base connectors that need other scopes, such as the admin directory scopes, need a JSON key.
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ import {
getClientCredentialAccountDescriptor,
} from '@/lib/credentials/client-credential-accounts/descriptors'
import {
GOOGLE_KEYLESS_SERVICES,
googleDelegationServiceFor,
googleKeylessOffer,
isGoogleKeylessService,
} from '@/lib/credentials/google-workload-identity'
import type { ServiceAccountSecretFields } from '@/lib/credentials/service-account-secret'
import {
Expand All @@ -54,7 +55,8 @@ import { ClientCredentialsFields } from '@/app/workspace/[workspaceId]/integrati
import { DatabricksAccountModal } from '@/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/databricks-account-modal'
import {
buildGoogleKeylessConfig,
EMPTY_GOOGLE_KEYLESS_INPUT,
emptyGoogleKeylessInput,
GOOGLE_DELEGATION_SERVICE_NAMES,
GoogleKeylessFields,
type GoogleKeylessInput,
isGoogleKeylessInputComplete,
Expand Down Expand Up @@ -180,8 +182,9 @@ interface ConnectServiceAccountModalProps {
/** Existing description, used to seed reconnect-capable modals. */
credentialDescription?: string
/**
* The OAuth service the dialog was opened for (`google-bigquery`). A Google service account
* offers keyless setup only for a Google Cloud service.
* The OAuth service or provider id the dialog was opened for (`google-bigquery`, `gmail` or
* `google-email`). A Google service account offers keyless setup only for a Google Cloud
* service or a Workspace service it can delegate.
*/
oauthServiceId?: string
/** Called with the new credential id after a successful create (token-paste providers). */
Expand Down Expand Up @@ -643,9 +646,10 @@ type GoogleAuthType = 'json_key' | 'workload_identity'
/**
* Google service-account flow. Accepts the raw JSON key (paste or drag/drop)
* and validates against the shared `serviceAccountJsonSchema` so the same
* shape errors render here as in the server route. For a Google Cloud service
* (or a keyless credential being reconnected) on a deployment that can sign
* workload-identity tokens, it also offers keyless Workload Identity Federation.
* shape errors render here as in the server route. For a Google Cloud service,
* a Workspace service it can delegate (or a keyless credential being
* reconnected) on a deployment that can sign workload-identity tokens, it also
* offers keyless Workload Identity Federation.
*/
function GoogleServiceAccountModal(props: GoogleServiceAccountModalProps) {
if (!props.open) return null
Expand All @@ -670,13 +674,14 @@ function GoogleServiceAccountModalForm({
oauthServiceId,
onCreated,
}: GoogleServiceAccountModalProps) {
const cloudService = Boolean(oauthServiceId && GOOGLE_KEYLESS_SERVICES.has(oauthServiceId))
const delegableService = googleDelegationServiceFor(oauthServiceId)
const keylessService = Boolean(oauthServiceId && isGoogleKeylessService(oauthServiceId))
const keyless = useKeylessSetup({
provider: 'google',
workspaceId,
organizationId,
credentialId,
enabled: cloudService || Boolean(credentialId),
enabled: keylessService || Boolean(credentialId),
})
const keylessSubmit = useSavedConnectionSubmit({
providerId: GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID,
Expand All @@ -696,7 +701,11 @@ function GoogleServiceAccountModalForm({
const authType: GoogleAuthType = offerKeyless
? (selectedAuthType ?? keyless.reconnectAuthType ?? 'json_key')
: 'json_key'
const [keylessInput, setKeylessInput] = useState<GoogleKeylessInput>(EMPTY_GOOGLE_KEYLESS_INPUT)
// Untouched, the form follows the reconnected credential's stored delegation, which
// arrives with the keyless setup the keyless fields wait for.
const [editedKeylessInput, setKeylessInput] = useState<GoogleKeylessInput | null>(null)
const keylessInput =
editedKeylessInput ?? emptyGoogleKeylessInput(oauthServiceId, keyless.reconnectDelegation)
const [jsonInput, setJsonInput] = useState('')
const [uploadedFileName, setUploadedFileName] = useState<string | null>(null)
const [displayName, setDisplayName] = useState(initialDisplayName ?? '')
Expand Down Expand Up @@ -791,7 +800,7 @@ function GoogleServiceAccountModalForm({
const isDisabled =
cloudOnly ||
(authType === 'workload_identity'
? isPending || !keyless.ready || !isGoogleKeylessInputComplete(keylessInput)
? isPending || !keyless.ready || !isGoogleKeylessInputComplete(keylessInput, delegableService)
: !jsonInput.trim() || isPending)
const submitError = authType === 'workload_identity' ? keylessSubmit.error : error

Expand Down Expand Up @@ -825,7 +834,7 @@ function GoogleServiceAccountModalForm({
required
hint={
authType === 'workload_identity'
? 'No key is stored. Sim signs a short-lived token your workload identity provider trusts, then impersonates the service account. Google Cloud APIs only, without domain-wide delegation.'
? 'No key is stored. Sim signs a short-lived token your workload identity provider trusts, then impersonates the service account: Google Cloud APIs, and Google Workspace services through domain-wide delegation.'
: undefined
}
/>
Expand All @@ -844,8 +853,9 @@ function GoogleServiceAccountModalForm({

{cloudOnly && (
<ChipModalDescription>
This keyless credential works only with Google Cloud services (BigQuery, Vertex AI).
Reconnect it from one of those integrations or from the credential’s own page.
This keyless credential works only with Google Cloud services (BigQuery, Vertex AI) and,
through domain-wide delegation, {GOOGLE_DELEGATION_SERVICE_NAMES}. Reconnect it from one
of those integrations or from the credential’s own page.
</ChipModalDescription>
)}

Expand Down
Loading
Loading