Seamlessly synchronize Google Workspace users and groups to AWS IAM Identity Center
SSO Sync is a powerful CLI tool and AWS Lambda function that enables automatic provisioning of Google Workspace (formerly G Suite) users and groups into AWS IAM Identity Center (formerly AWS SSO). Built with Go and powered by AWS SDK v2, it provides reliable, scalable, and secure identity synchronization.
- 🔄 Uni-directional Sync: Supports both
groupsandusers_groupssync methods (users_groupsin the CLI and the classicSSOSyncapp only) - 🎯 Advanced Filtering: Flexible user and group filtering with Google API query parameters
- 🛡️ Dry-Run Mode: Test synchronization without making actual changes
- ⚡ High Performance: Built with AWS SDK v2 for improved performance and reliability
- 🔧 Multiple Deployment Options: CLI, AWS Lambda, or AWS SAM deployment
- 📊 Comprehensive Logging: Structured logging with configurable levels and formats
- 🔐 Secure: AWS Secrets Manager integration for credential management
- 📈 Scalable: Supports large directories with user caching and pagination
Use one of the quick start templates to simplify your deployment. Use these templates, with the Sync from Git option, to make updates to your deployment far simpler.
| Template | Application | Google authentication |
|---|---|---|
quick-start/next-gen/single-account.yaml |
ssosync (next generation) |
Recommended. Keyless, Workload Identity Federation (release 2.11.0 or later): no stored key and no Google admin |
quick-start/next-gen/single-account-attribute-map.yaml |
ssosync |
As above, plus a custom attribute map |
quick-start/single-account.yaml, …/single-account-attribute-map.yaml |
SSOSync (classic) |
Legacy auth: a stored service-account key and a Google admin. These keep the 2.10.0 parameters (their patterns widened to accept apostrophes), so existing Sync from Git deployments of these paths upgrade to 2.11.0 unchanged |
From 2.11.0 ssosync is published to the AWS Serverless Application Repository as two applications, built from the same code and released with the same version number:
SSOSync (classic) |
ssosync (next generation) |
|
|---|---|---|
| Parameters | Those of 2.10.0, with the email, name and OU patterns widened to accept apostrophes (#401) | Keyless only: GoogleImpersonateServiceAccount, GoogleWIFConfig and RoleName; no GoogleCredentials, GoogleAdminEmail, SyncMethod or IncludeGroups |
| Google authentication | Legacy auth (key + admin) | Workload Identity Federation |
| Sync method | groups or users_groups |
groups only |
GoogleCustomerId |
Optional (my_customer by default) |
Required, the actual customer ID (my_customer is rejected) |
| Fixed resource names | SSOSync* secrets, AWSSyncSchedule rule, alias/SSOSync, unprefixed exports (FunctionARN, …) |
SSOSyncNG* secrets, SSOSyncNGSchedule rule, alias/SSOSyncNG, SSOSyncNG* exports (SSOSyncNGFunctionARN, …) |
| Quick-starts | quick-start/ |
quick-start/next-gen/ |
New features are added to ssosync; SSOSync keeps the 2.10.0 configuration. The CLI is unchanged and supports both authentication methods.
Moving a quick-start deployment to the next generation: complete the Workload Identity Federation steps first (use SSOSync_ExecutionRole as the role name, the next-gen default RoleName), then update the stack, replacing its template with the matching one in quick-start/next-gen/ (with Sync from Git, point the deployment file at the new path), and enter GoogleCustomerId, GoogleImpersonateServiceAccount and GoogleWIFConfig. CloudFormation creates the next-gen function (SSOSyncNG) and its resources, then deletes the classic ones; their names differ, so the two do not clash during the update. Anything that imports the classic exports must switch to the SSOSyncNG* ones. Once the next-gen function syncs, delete the service-account key in Google Cloud and, if nothing else uses it, the domain-wide delegation entry.
Want to dive straight in? Try this hands-on lab from the AWS Control Tower Workshop. The lab guides you through the complete setup process for both AWS and Google Workspace using the recommended Lambda deployment from the AWS Serverless Application Repository.
| Method | Best For | Setup Time |
|---|---|---|
AWS Serverless App Repository: ssosync (next generation) or SSOSync (classic), see Two Serverless applications |
Production use | 5 minutes |
| CLI Binary | Local testing, CI/CD | 2 minutes |
| AWS SAM | Custom deployments | 10 minutes |
As per the AWS SSO Homepage:
AWS Single Sign-On (SSO) makes it easy to centrally manage access to multiple AWS accounts and business applications and provide users with single sign-on access to all their assigned accounts and applications from one place.
Key part further down:
With AWS SSO, you can create and manage user identities in AWS SSO's identity store, or easily connect to your existing identity source including Microsoft Active Directory and Azure Active Directory (Azure AD).
AWS SSO can use other Identity Providers as well... such as Google Apps for Domains. Although AWS SSO supports a subset of the SCIM protocol for populating users, it currently only has support for Azure AD.
This project provides a CLI tool to pull users and groups from Google and push them into AWS SSO.
ssosync deals with removing users as well. The heavily commented code provides you with the detail of
what it is going to do.
Important
From v2.7.x the behavior when syncing users has changed for the following situation: If a user in the Google Workspace Directory has been deleted and recreated (and therefore its user id has changed but the email address remains the same).
- Since v2.4 the existing IAM Identity Store user would have the ExternalId field updated. However, in the situation where an email address has been re-assigned to a different user this would lead to them silently inheriting permission assignments made directly to the user. Therefore this option should be used with caution.
- From v2.7 the default bahvaiour will be to delete the existing user and create a new one. This behavior can be overriden with
--force-externalid-updateoption.
Important
Region and IdentityStoreID are no longer configurable parameters. Region is extracted programmatically from the SCIM Endpoint URL, and IdentityStoreID is resolved automatically via the IAM Identity Center ListInstances API. These parameters are still present in template.yaml to avoid breaking deployment tooling for anyone passing a parameter file to the CloudFormation Stack. They will be fully removed in v3.x.
Caution
When using ssosync with an instance of IAM Identity Center integrated with AWS Control Tower. AWS Control Tower creates a number of groups and users (directly via the Identity Store API), when an external identity provider is configured these users and groups are can not be used to log in. However it is important to remember that because ssosync implemements a uni-directional sync it will make the IAM Identity Store match the subset of your Google Workspaces directory you specify, including removing these groups and users created by AWS Control Tower. If you wish to retain unmatched local users/groups, then the retain-unmatched option can be enabled. This will retain 'local' users and groups (those where an ExternalId has not be set), users will be set to suspended, since these users will not be able to authenticate unless a matching user existings in the Google Workspace Directory. If a matching user is created in Google Workspace Directory, then it will be re-activated.
Warning
There are breaking changes for versions >= 0.02
Warning
>= 1.0.0-rc.5 groups to do not get deleted in AWS SSO when deleted in the Google Directory, and groups are synced by their email address
Warning
>= 2.0.0 this makes use of the Identity Store API which means:
- it should be run from a linked account, not the Organization management account.
- if deploying the lambda from the AWS Serverless Application Repository, deploy it into a linked account; the function's execution role carries the IAM permissions it needs.
- if you are running the project as a cli tool, then the environment needs credentials for a principal in that linked account with the appropriate Identity Store and
sso:ListInstancespermissions.
Note: guidance before v2.5.0 required deployment into the IAM Identity Center delegated administration account. From v2.5.0 this is no longer necessary — ssosync works from an ordinary linked account without delegated administration. See the Installation section below.
Warning
>= 2.1.0 make use of named IAM resources, so if deploying via CICD or IaC template will require CAPABILITY_NAMED_IAM to be specified.
Important
>= 2.1.0 switched to using provided.al2 powered by ARM64 instances.
Important
As of v2.2.0 multiple query patterns are supported for both Group and User matching, simply separate each query with a ,. For full sync of groups and/or users specify '*' in the relevant match field.
User match and group match can now be used in combination with the sync method of groups.
Nested groups will now be flattened into the top level groups.
External users are ignored.
Group owners are treated as regular group members.
User details are now cached to reduce the number of api calls and improve execution times on large directories.
- SCIM Protocol RFC
- AWS SSO - Connect to Your External Identity Provider
- AWS SSO - Automatic Provisioning
- AWS IAM Identity Center - Identity Store API
The recommended installation is:
- Setup IAM Identity Center, in the management account of your organization
- Create a linked account (for example an
Identityaccount) from which to run ssosync - Deploy the ssosync app (next generation; or the classic SSOSync app, see Two Serverless applications) from the AWS Serverless Application Repository into that linked account, preferably through a quick-start template
Note
From v2.5.0 ssosync no longer requires IAM Identity Center delegated administration. It operates through the Identity Store and SCIM APIs using only the IAM permissions granted to its execution role, so the best practice is now to deploy it in an ordinary linked account without delegating IAM Identity Center administration to that account. Deploying into the management account is still not recommended.
You can also:
You can go get github.com/awslabs/ssosync or grab a Release binary from the release page. The binary
can be used from your local computer, or you can deploy to AWS Lambda to run on a CloudWatch Event
for regular synchronization.
SSO Sync requires configuration from both Google Workspace and AWS sides.
ssosync reads your directory as a Google service account. There are two ways for ssosync to authenticate as that service account. Use exactly one:
| Workload Identity Federation (keyless) | Legacy auth (stored credentials) | |
|---|---|---|
| Credential ssosync holds | None long-lived: its AWS role is exchanged for short-lived Google credentials on every run | A downloaded service-account key (credentials.json) |
Works with the iam.disableServiceAccountKeyCreation org policy |
Yes | No |
| Directory access | The service account is assigned a read-only Workspace admin role and acts as itself | The service account acts on behalf of a Workspace admin (domain-wide delegation) |
| Configure with | --google-impersonate-service-account + --google-wif-config + --customer-id |
--google-credentials + --google-admin |
| Serverless application | ssosync (next generation), quick-start/next-gen/ |
SSOSync (classic), quick-start/ |
| Status | Recommended (2.11.0 or later); planned to become the only method from v3 | Supported in 2.x (and still the CLI default when --google-impersonate-service-account is not set); planned for removal in v3 |
With Workload Identity Federation (WIF), Google Cloud trusts your AWS account: ssosync presents its AWS role credentials (verified by Google through the AWS GetCallerIdentity API), receives a short-lived federated token, and uses it to impersonate the service account. The service account reads the directory with a read-only admin role assigned to it in Google Workspace, so there is no super admin to act on behalf of and no domain-wide delegation. No service-account key is ever created or stored, and nothing needs configuring on the AWS side beyond the role ssosync already runs as.
These steps follow Google's guide Configure Workload Identity Federation with AWS or Azure VMs (the AWS IAM credentials mechanism, with service account impersonation); refer to it for background and console screenshots. One point differs from that guide and is called out in step 6.
You need: a Google Cloud project where you hold Workload Identity Pool Admin (
roles/iam.workloadIdentityPoolAdmin) and Service Account Admin (roles/iam.serviceAccountAdmin) (or Owner); a Google Workspace super admin for steps 3 and 7; thegcloudCLI (install), or Cloud Shell; and the AWS account ID and IAM role name ssosync runs as (described below).
Tip: use Google Cloud Shell for the following steps. Click Activate Cloud Shell (the
>_icon) in the Google Cloud Console: it opens a terminal in your browser with thegcloudCLI already installed and signed in as you, so there is nothing to install, and every command below can be pasted into it as-is. Step 6 writeswif-config.jsonthere and shows how to download it. See Cloud Shell.
The commands use these values; work them out first:
PROJECT_ID: the Google Cloud project that will own the service account and the workload identity pool. Create one in the Google Cloud Console if you need a new project, or reuse an existing one; its ID is shown in the console's project picker.PROJECT_NUMBER: the project's numeric identifier (for example123456789012), which Google uses in workload identity pool names. It is different from the project ID. The block below looks it up fromPROJECT_ID, so you don't need to enter it. To check it yourself:- Console: the Project number on the project's Dashboard (Project info card), or in IAM & Admin > Settings.
- gcloud:
gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)'. If it prints nothing,PROJECT_IDis wrong or you lack access to the project; fix that before continuing, because steps 5 and 6 need the number.
POOL_IDandPROVIDER_ID: IDs you choose for the workload identity pool and its AWS provider (lowercase letters, digits and hyphens). They cannot be changed later.ssosyncfor both is fine for a single AWS account; with several accounts, give each its own provider ID (see step 4).AWS_ACCOUNT_ID: the 12-digit ID of the AWS account ssosync runs in (aws sts get-caller-identity --query Account --output text).AWS_ROLE_NAME: the IAM role ssosync runs as. Google matches it in the ARN of ssosync's AWS session,arn:aws:sts::<account>:assumed-role/<role name>/<session>.- If you are using the next-gen quick-start templates (
quick-start/next-gen/), useSSOSync_ExecutionRole. It is their defaultRoleName, so you can complete these steps before deploying. - Lambda (SAR deployment of the next-gen
ssosyncapp): set the template'sRoleNameparameter (for exampleSSOSync_ExecutionRole) so the role has a predictable name that does not change when the stack is redeployed, and use that name. IAM role names are global in the account, so give each ssosync deployment in an account its own. If you leaveRoleNameempty, CloudFormation generates a name (for exampleserverlessrepo-ssosync-SSOSyncRoleLocal-ABC123); deploy first, then read it from the stack'sRoleNameoutput, and redo steps 4–5 if the stack is ever recreated with a new one. - CLI: the role your AWS credentials belong to;
aws sts get-caller-identityshows it in theassumed-role/<role name>/...ARN.
- If you are using the next-gen quick-start templates (
Set them, replacing the example values:
PROJECT_ID="my-ssosync-project"
gcloud config set project "$PROJECT_ID"
PROJECT_NUMBER="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')"
POOL_ID="ssosync"
PROVIDER_ID="ssosync"
AWS_ACCOUNT_ID="<account_id>"
AWS_ROLE_NAME="SSOSync_ExecutionRole"SA_EMAIL, the service account's email address, is set in step 2 by the command that creates the account. Shell variables last only for the session: if Cloud Shell restarts, run this block again, and the lookup in step 2.
-
Enable the APIs
-
ssosync needs the Admin SDK API; federation and impersonation need the IAM, Security Token Service and IAM Service Account Credentials APIs:
gcloud services enable admin.googleapis.com iam.googleapis.com \ sts.googleapis.com iamcredentials.googleapis.com
-
-
Create the service account — without a key
-
Create it, keeping its email address in
SA_EMAIL:SA_EMAIL="$(gcloud iam service-accounts create "ssosync" --display-name "ssosync (keyless)" --format='value(email)')" echo "$SA_EMAIL"
--format='value(email)'makes the command print only the new account's email address; theechoshows it (for examplessosync@my-ssosync-project.iam.gserviceaccount.com). Keep a copy: it is also the value forGoogleImpersonateServiceAccountin step 8. -
If the account already exists, or
echoprints nothing, set it from the existing account instead:SA_EMAIL="$(gcloud iam service-accounts list --filter='email ~ ^ssosync@' --format='value(email)')" echo "$SA_EMAIL"
-
No project roles are required: its access to the directory is granted in Google Workspace (step 3), not in Google Cloud. Do not create a key for it.
-
-
Assign a read-only admin role to the service account in the Admin console
- Sign in to the Google Admin console as a super admin.
- Go to Account > Admin roles, click Create new role, name it (for example
ssosync read-only) and click Continue. - Under Admin API privileges, select only Users > Read and Groups > Read, then click Continue and Create role.
- Back in Admin roles, point to the new role and click Assign admin > Assign service accounts. Enter the service account email (
$SA_EMAIL, from step 2), then click Add > Assign role. - No domain-wide delegation entry is needed: the service account calls the Admin SDK as itself, with this role. The assignment can take a while to apply (Google allows up to 24 hours). See Assign a role to a service account.
-
Create the workload identity pool and AWS provider
-
Create the pool:
gcloud iam workload-identity-pools create "$POOL_ID" --location=global --display-name="ssosync"
-
Create the AWS provider in it:
gcloud iam workload-identity-pools providers create-aws "$PROVIDER_ID" \ --location=global --workload-identity-pool="$POOL_ID" \ --account-id="$AWS_ACCOUNT_ID" \ --attribute-mapping="google.subject=assertion.arn,attribute.account=assertion.account,attribute.aws_role=assertion.arn.extract('assumed-role/{role}/')" \ --attribute-condition="assertion.arn.startsWith('arn:aws:sts::${AWS_ACCOUNT_ID}:assumed-role/${AWS_ROLE_NAME}/')"
The mapping makes the role name available as
attribute.aws_role. The attribute condition is the important control: the provider accepts only sessions of that one role in that one account, so no other AWS principal can federate.
Several AWS accounts with the same directory (for example a dev account and a prod account): an AWS provider trusts exactly one account, so create one provider per account in the same pool. Repeat the provider command for each account with its own
PROVIDER_ID(for examplessosync-dev,ssosync-prod),AWS_ACCOUNT_IDand, if it differs,AWS_ROLE_NAME. The pool, the service account and its admin role (steps 2–3) are shared. Step 5's grant matches the role name in any provider of the pool, so it is needed once per distinct role name. Step 6 is per account: repeat it for each provider. -
-
Allow the AWS role to impersonate the service account
-
Grant Workload Identity User (
roles/iam.workloadIdentityUser) on the service account, to that role only:gcloud iam service-accounts add-iam-policy-binding "$SA_EMAIL" \ --role=roles/iam.workloadIdentityUser \ --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/attribute.aws_role/${AWS_ROLE_NAME}"
-
-
Create the credential configuration
⚠️ differs from Google's guide-
Write it to
wif-config.json:gcloud iam workload-identity-pools create-cred-config \ "projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/providers/${PROVIDER_ID}" \ --aws --output-file=wif-config.json -
Download the file to your computer, to deploy ssosync with it (step 8). In Cloud Shell, either run:
cloudshell download wif-config.json
or click More (⋮) > Download in the Cloud Shell toolbar and enter the file's path. Cloud Shell can only download from your home directory; it opens there, so the file is in it unless you changed directory (otherwise copy it there first,
cp wif-config.json ~/). From a local terminal,gcloud cloud-shell scp cloudshell:~/wif-config.json localhost:./wif-config.jsonalso works. See Manage files with Cloud Shell. -
This step is per AWS account: the configuration names the provider (
PROVIDER_ID), and each provider trusts one account. With several accounts (see the note in step 4), run it once per provider, settingPROVIDER_IDto that account's provider and writing a separate file each time, for example--output-file=wif-config-dev.jsonand--output-file=wif-config-prod.json. Give each account's deployment the file for its own provider; a configuration for another account's provider is rejected. -
Omit
--service-account: ssosync impersonates the service account itself (named by--google-impersonate-service-account), so the configuration must describe only the federation. A configuration created with--service-accountcontainsservice_account_impersonation_urland makes the impersonation happen twice. -
The file contains no secret: it says where to get AWS credentials and which pool/provider to use. On AWS Lambda the AWS credentials come from the function's environment (the metadata server it refers to is not used), so the same file works for Lambda and the CLI.
-
-
Find your Google Workspace customer ID
- In the Admin console, go to Account > Account settings > Profile (direct link) and copy the Customer ID (for example
C01abcd23). - It is required for keyless authentication: the service account is not a Workspace user, so the
my_customeralias does not identify your directory. ssosync, and the template, rejectmy_customerhere. This is--customer-id/CUSTOMER_ID/GoogleCustomerId.
- In the Admin console, go to Account > Account settings > Profile (direct link) and copy the Customer ID (for example
-
Configure ssosync
-
Quick start: deploy
quick-start/next-gen/single-account.yaml(ornext-gen/single-account-attribute-map.yamlfor a custom attribute map) withGoogleCustomerId(step 7),GoogleImpersonateServiceAccount($SA_EMAIL) andGoogleWIFConfig(the contents ofwif-config.json). ItsRoleNamedefaults toSSOSync_ExecutionRole, the name used in steps 4–5. There is no key and no admin to enter. To switch an existing deployment of the classicquick-start/single-account.yaml, replace its template with the next-gen one (see Two Serverless applications). -
Lambda / SAR: deploy the next-gen
ssosyncapp withGoogleImpersonateServiceAccountset to the service account email ($SA_EMAIL),GoogleWIFConfigto the contents ofwif-config.json(or the Arn of a secret or SSM parameter holding it),GoogleCustomerIdto the customer ID from step 7, andRoleNameto the role from steps 4–5. The classicSSOSyncapp does not support keyless authentication. -
CLI:
./ssosync \ --customer-id C01abcd23 \ --google-impersonate-service-account "$SA_EMAIL" \ --google-wif-config ./wif-config.json \ --endpoint https://scim.us-east-1.amazonaws.com/... \ --access-token AQoDYXdzE... \ --group-match "name:AWS*" --dry-run
The federation reads AWS credentials from
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKENand the region fromAWS_REGION(orAWS_DEFAULT_REGION) when they are set, and otherwise from the EC2 instance metadata server; it does not read~/.awsprofiles. For credentials from a named profile (including IAM Identity Center sign-in), export them first:eval "$(aws configure export-credentials --profile my-profile --format env)", and use that profile's role asAWS_ROLE_NAME(steps 4–5).
-
Troubleshooting:
Permission 'iam.serviceAccounts.getAccessToken' deniedmeans step 5 is missing or names a different role (compare it with the role inaws sts get-caller-identity).The given credential is rejected by the attribute conditionmeans the role, or account, does not match the provider created in step 4.Not Authorized to access this resource/apifrom the Admin SDK means the admin role (step 3) is not assigned to the service account, lacks a privilege, or has not taken effect yet (Google allows up to 24 hours), or the customer ID (step 7) is wrong. If you delete and recreate the service account, even with the same email, it loses both the step 5 grant and the step 3 role assignment: redo both. Run with--dry-run --log-level debuguntil the sync connects.
The service account authenticates with a downloaded JSON key (credentials.json). It remains supported in 2.x by the CLI, the classic SSOSync app and the classic quick-start/ templates; Workload Identity Federation is recommended instead. The steps below produce the two things ssosync needs: the credentials.json key file and the email address of an admin for the service account to act on behalf of.
These steps mirror the AWS Control Tower workshop, which you can follow for a fully guided walkthrough.
Note: steps 1–4 are performed in the Google Cloud Console and steps 5–6 in the Google Admin console. You need a Google Cloud account that can create projects/service accounts and a Google Workspace super admin to grant the domain-wide delegation in step 5.
-
Create (or select) a Google Cloud project
- Go to the Google Cloud Console and create a new project (or select an existing one) to own the service account.
-
Enable the Admin SDK API
- In the same project, go to APIs & Services > Enabled APIs & services > Enable APIs and Services.
- Search for Admin SDK API and click Enable. The service account cannot read the directory until this API is enabled on the project.
-
Create a service account and a JSON key
- Go to APIs & Services > Credentials (or IAM & Admin > Service Accounts) and click Create credentials > Service account.
- Give it a name (for example
ssosync) and click Create and continue. No project roles are required (the access is granted in Google Workspace, not in Google Cloud), so you can skip the optional role/grant steps and click Done. - Open the new service account, go to the Keys tab, and choose Add key > Create new key > JSON. A JSON key file downloads automatically.
- Rename the downloaded file to
credentials.json. This is the file ssosync uses (--google-credentials/GOOGLE_CREDENTIALS). Keep it secret; anyone with it can read your directory.
-
Copy the service account's Client ID
- Still on the service account, expand Advanced settings (or the Details tab) and copy the numeric Client ID (also called the Unique ID / OAuth2 client ID). You need it for the next step.
-
Grant domain-wide delegation in the Admin console
- Sign in to the Google Admin console as a super admin.
- Go to Security > Access and data control > API controls, then under Domain wide delegation select Manage Domain Wide Delegation.
- Click Add new and enter:
- Client ID: the Client ID you copied in step 4.
- OAuth scopes (comma-separated, read-only):
https://www.googleapis.com/auth/admin.directory.group.readonlyhttps://www.googleapis.com/auth/admin.directory.user.readonly
- Click Authorize. Only these two read-only scopes are needed; do not grant broader access. (Group memberships are read with
admin.directory.group.readonly;admin.directory.group.member.readonly, listed by earlier versions, is no longer requested and can be removed from an existing entry, but only after upgrading ssosync: earlier versions still request it and fail with401 unauthorized_clientonce it is removed.)
-
Choose the admin user to impersonate
- ssosync must impersonate an existing Google Workspace admin that can read the directory. Note that user's email address — it is the value you pass as
--google-admin(GOOGLE_ADMIN). A super admin always works; a custom admin role with the Users and Groups read privileges is sufficient.
- ssosync must impersonate an existing Google Workspace admin that can read the directory. Note that user's email address — it is the value you pass as
Tip:
credentials.jsonholds the service account key (the who ssosync authenticates as) andGOOGLE_ADMINis the admin it impersonates (the who it acts on behalf of). You need both.
-
Enable IAM Identity Center
- Go to AWS IAM Identity Center console
- Select Settings → Enable automatic provisioning
- Copy the SCIM Endpoint URL and Access Token
-
AWS Credentials
- Configure AWS credentials using any standard method:
- AWS credentials file (
~/.aws/credentials) - Environment variables (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) - IAM roles (for Lambda deployment)
- AWS credentials file (
- The executing role needs permissions for
sso:ListInstancesto auto-detect the Identity Store ID
- Configure AWS credentials using any standard method:
./ssosync --help./ssosync \
--google-admin admin@company.com \
--google-credentials ./credentials.json \
--endpoint https://scim.us-east-1.amazonaws.com/... \
--access-token AQoDYXdzE... \
--group-match "name:AWS*"# Sync specific groups with dry-run
./ssosync \
--group-match "name:Engineering*,email:aws-*" \
--sync-method groups \
--dry-run \
--log-level debug
# Sync all users and groups
./ssosync \
--user-match "*" \
--group-match "*" \
--sync-method users_groups
# Ignore specific users/groups
./ssosync \
--group-match "*" \
--ignore-users "service@company.com,bot@company.com" \
--ignore-groups "temp-group@company.com"All CLI flags can be set via environment variables with the SSOSYNC_ prefix:
export SSOSYNC_GOOGLE_ADMIN="admin@company.com"
export SSOSYNC_GOOGLE_CREDENTIALS="./credentials.json"
export SSOSYNC_SCIM_ENDPOINT="https://scim.us-east-1.amazonaws.com/..."
export SSOSYNC_SCIM_ACCESS_TOKEN="AQoDYXdzE..."
export SSOSYNC_GROUP_MATCH="name:AWS*"
export SSOSYNC_DRY_RUN="true"| Flag | Environment Variable | Description | Default |
|---|---|---|---|
--google-admin / -u |
SSOSYNC_GOOGLE_ADMIN |
Legacy auth only: Google Workspace admin to act on behalf of (domain-wide delegation; required for legacy auth as documented). Rejected with --google-impersonate-service-account (keyless never acts on behalf of an admin). Leave empty to have the service account act as itself with its own admin role, which requires --customer-id. |
"" |
--google-credentials / -c |
SSOSYNC_GOOGLE_CREDENTIALS |
Legacy auth: path to the Google service-account key JSON. Not read when --google-impersonate-service-account is set (giving both explicitly is an error). |
credentials.json |
--google-impersonate-service-account |
SSOSYNC_GOOGLE_IMPERSONATE_SERVICE_ACCOUNT |
Keyless auth: email of the service account to impersonate; selects Workload Identity Federation instead of the key. | "" |
--google-wif-config |
SSOSYNC_GOOGLE_WIF_CONFIG |
Keyless auth: path to the Workload Identity Federation credential configuration (external_account JSON). When unset, Application Default Credentials are used. |
"" |
--attribute-map-file |
SSOSYNC_ATTRIBUTE_MAP_FILE |
Path to a JSON file containing the attribute map (groups sync method only); when unset the built-in default mapping is used. See Attribute mapping. |
"" |
--customer-id |
SSOSYNC_CUSTOMER_ID |
Google Workspace customer ID. The actual ID (Cxxxxxxxx, not my_customer) is required with --google-impersonate-service-account and when --google-admin is empty. |
my_customer |
--endpoint / -e |
SSOSYNC_SCIM_ENDPOINT |
AWS SCIM endpoint URL | Required |
--access-token / -t |
SSOSYNC_SCIM_ACCESS_TOKEN |
AWS SCIM access token | Required |
--sync-method / -s |
SSOSYNC_SYNC_METHOD |
Sync method (groups or users_groups) |
groups |
--group-match / -g |
SSOSYNC_GROUP_MATCH |
Google Groups filter query, passed to Google verbatim; see Quoting values in queries | * |
--user-match / -m |
SSOSYNC_USER_MATCH |
Google Users filter query, passed to Google verbatim; see Quoting values in queries | "" |
--ignore-users |
SSOSYNC_IGNORE_USERS |
Comma-separated list of users to ignore | [] |
--ignore-groups |
SSOSYNC_IGNORE_GROUPS |
Comma-separated list of groups to ignore | [] |
--include-groups |
SSOSYNC_INCLUDE_GROUPS |
Include only these groups (users_groups method only) | [] |
--precache-ous |
SSOSYNC_PRECACHE_ORG_UNITS |
Comma-separated list of Google OrgUnit paths to precache | [] |
--cache-metrics |
SSOSYNC_CACHE_METRICS |
Gather cache hit/miss metrics and output them at the end of the run to help refine --precache-ous |
false |
--dry-run / -n |
SSOSYNC_DRY_RUN |
Enable dry-run mode | false |
--suspended |
SSOSYNC_SYNC_SUSPENDED |
Include suspended users when syncing | false |
--force-externalid-update |
SSOSYNC_FORCE_EXTERNAL_ID_UPDATE |
When a Google user shares the same primary email as an existing Identity Store user but has a different ID, update the existing user's ExternalId in place instead of deleting and recreating it. Enabling this option carrys the risk of silent permission inheritance, is an email address is re-assigned to a different user in the Google Workspace Directory. See Preserving assignments when migrating IdPs. |
false |
--retain-unmatched / -r |
SSOSYNC_RETAIN_UNMATCHED |
Retain 'local' users and groups that exist in the IAM Identity Store but cannot be matched to the Google Workspace directory, instead of deleting them. A 'local' user or group is one which has been created in the Identity Store directly and not synced via SCIM, from any external identity provider. See Retaining unmatched users and groups. | false |
--debug / -d |
SSOSYNC_DEBUG |
Enable verbose / debug logging (forces log level to debug) |
false |
--log-level |
SSOSYNC_LOG_LEVEL |
Log level (debug, info, warn, error) | info |
--log-format |
SSOSYNC_LOG_FORMAT |
Log format (text, json) | text |
Note:
--regionand--identity-store-idare no longer required. Region is extracted from the SCIM endpoint URL, and Identity Store ID is resolved automatically via the IAM Identity Center API.
When ssosync finds an Identity Store user whose primary email matches a Google Workspace user but whose ID differs, the default behaviour is to delete the existing Identity Store user and create a new one. This is intentional: it prevents a new Google user from inheriting the permission set and group assignments of a previous, unrelated user that happened to reuse the same email address.
Setting --force-externalid-update (or SSOSYNC_FORCE_EXTERNAL_ID_UPDATE=true) changes this behaviour. Instead of delete-and-recreate, ssosync updates the existing Identity Store user's ExternalId in place, which preserves that user's existing assignments and group memberships.
This is required when migrating from another external IdP (for example Microsoft Entra) to Google Workspace Directory, where the users already exist in the Identity Store and their assignments need to be retained.
If an existing Identity Store user matches a Google Workspace user by email address but has no ExternalId set, that user is adopted and updated to sync with its Google Workspace equivalent. This happens regardless of the --force-externalid-update flag, and no delete-and-recreate occurs because there is no prior external identity to preserve or displace.
Caution
Enabling this flag can cause a user to inherit privileges from the previously matched Identity Store user. Only enable it when you understand and accept that risk, such as during a controlled IdP migration.
ssosync performs a uni-directional sync: by default any user or group that exists in the IAM Identity Store but cannot be matched to the subset of your Google Workspace directory you are syncing is deleted so the Identity Store mirrors Google Workspace.
Setting --retain-unmatched (or SSOSYNC_RETAIN_UNMATCHED=true) blocks the deletion of 'local' users and groups. Users and groups that exist in the Identity Store (and don't have an ExternalId set) and have no match in the Google Workspace directory are left in place instead of being removed. Users are have an ExternalId set, are assumed to have been previous synced by ssosync and are there managed as usual. If a local user or group matches with one in Google Workspace directory, then the ExternalId is set and it becomes managed by ssosync.
This option does not prevent deletion when a Google Workspace user has the same email address as an Identity Store user but a different, non-empty ExternalId. By default, ssosync deletes the old Identity Store user and creates a new one to avoid carrying the old user's permissions forward. The replacement has the same email address but a different Identity Store ID, so compare its group memberships and account assignments after the sync. Use --force-externalid-update to update the existing user in place only when preserving those assignments is intended; this carries the permission-inheritance risk described above.
This is useful when the Identity Store also contains identities managed outside of ssosync (for example users and groups created by AWS Control Tower) that you do not want removed on each sync.
Caution
If you want to be able to test what changes would be performed by ssosync with a specific configuration against your existing Identity Store, then --dry-run is recommended. In this mode ssosync, simulates the changes it would make but doesn't make any changes at all to the identity store. This is highly recommended when making configuration changes.
ExternalIdunset- primary email address matches Outcome User is updated with their ExternalId (and any other fields that differ) from the Google Workspace Directory. They have been adopted and are managed by ssosync, and will be treated as a synced user in the next execution.
ExternalIdset but doesn't match- primary email address doesn't match Outcome the user is deleted.
ExternalIdset but doesn't match- primary email address matches
Outcome
Default is to delete the user and create a new user, this ensures the 'new user' doesn't inherit privileges from the previous user that happen to be assigned the same email address. This behavior can be overridden so the exist user is updated and it retains the privileges, if the
--force-externalid-updateis also set.
ExternalIdset and matches- primary email address is not inspected Outcome User is compared to the matching user in Google, if it differs in anyway (including primary email address) its updated to match.
ExternalIdunsetDisplayNamedoesn't match Outcome The group is retained .
ExternalIdunsetDisplayNamedoes match Outcome Groups is updated with their ExternalId from the Google Workspace Directory. They have been adopted and are managed by ssosync
ExternalIdset but doesn't match Outcome the group is deleted.
ExternalIdset and matchesDisplayNameis not inspected Outcome Group is compared to the matching group in Google, if it differs in anyway its updated to match.
The --group-match value is passed through to the Google Admin SDK groups.list query, so it supports the following query fields and operators:
| Field | Prefix (:) |
Exact (=) |
Example |
|---|---|---|---|
name |
✓ | ✓ | name:AWS* or name=Administrators |
email |
✓ | ✓ | email:aws-* or email=aws-admins@company.com |
memberKey |
— | ✓ | memberKey=user@company.com |
- Use
:for prefix/wildcard matching (e.g.name:AWS*) - Use
=for exact matching (e.g.name=Administrators) - Use
memberKey=<email>to match every group that a specific user (or group) is a direct member of. This is an exact match only; the value must be the full primary email address of the member and wildcards are not supported. WhenmemberKeyis used you must supply your real Google Workspace customer ID via--customer-id(SSOSYNC_CUSTOMER_ID); the defaultmy_customerplaceholder is not accepted for member-key queries. - Combine multiple patterns with
,(each pattern is run as a separate query and the results are merged) - Use
*alone to sync all groups (and, with thegroupssync method, their member users)
# Sync groups starting with "AWS"
--group-match "name:AWS*"
# Sync multiple patterns
--group-match "name:Admin*,email:aws-*"
# Sync specific group
--group-match "name=Administrators"
# Sync every group that a specific user belongs to
--group-match "memberKey=user@company.com"
# Sync all groups
--group-match "*"Google's directory query syntax only matches group name and email with prefix/wildcard patterns, so the common prefix/suffix convention (e.g. name:aws-*) requires you to name every group you want synced accordingly. If your groups don't follow a shared naming convention, you can use memberKey with a dedicated "marker" group instead, so any group can be synced regardless of its name.
-
Create a single marker group in Google Workspace, for example:
- Name:
Any group with this as a member will be synced to IAM Identity Center - Description:
Any group with this as a member will be synced to IAM Identity Center - Email:
aws-ssosync@your-domain.com
- Name:
-
Add this marker group as a member of every group you want ssosync to sync. (Groups can be members of other groups in Google Workspace.)
-
Set the group match to the marker group's email:
--group-match "memberKey=aws-ssosync@your-domain.com"
ssosync then selects every group that has the marker group as a member, no matter what those groups are named. To add or remove a group from the sync scope later, just add or remove the marker group from its membership.
Note:
memberKeyqueries require your real Google Workspace customer ID. Set--customer-id(SSOSYNC_CUSTOMER_ID) to your actual customer ID rather than leaving it as themy_customerdefault.
The --user-match parameter supports the following query fields and operators:
| Field | Prefix (:) |
Exact (=) |
Example |
|---|---|---|---|
name |
✓ | ✓ | name:John* or name='John Doe' |
email |
✓ | ✓ | email:admin* or email=john@company.com |
manager |
— | ✓ | manager=boss@company.com |
managerId |
✓ | ✓ | managerId:abc123* |
orgName |
✓ | ✓ | orgName=Engineering |
orgDepartment |
✓ | ✓ | orgDepartment:Sales* |
orgCostCenter |
✓ | ✓ | orgCostCenter=CC-1234 |
- Use
:for prefix/wildcard matching (e.g.name:John*) - Use
=for exact matching (e.g.name='John Doe'oremail=admin@company.com) - Single-quote values containing spaces or an apostrophe (e.g.
orgName='My Department'); see Quoting values in queries - Combine multiple patterns with
,or space separator - Use
*alone to sync all users
# Sync users with specific name pattern
--user-match "name:John*"
# Sync users by email prefix
--user-match "email:admin*"
# Sync users in a specific org unit
--user-match "orgName=Engineering"
# Sync users by department and cost center
--user-match "orgDepartment:Sales*,orgCostCenter=CC-1234"
# Sync users managed by a specific person
--user-match "manager=boss@company.com"
# Combine multiple query fields
--user-match "name:John*,email:admin*,orgName=Engineering"
# Sync all users
--user-match "*"ssosync passes the --group-match and --user-match values to Google verbatim; it does not quote or escape them. A value containing spaces or an apostrophe must be single-quoted, with the apostrophe escaped as \', for example name='Valentine\'s Day'. Commas still separate queries, so a value cannot contain a comma.
# Sync the group named "Valentine's Day"
--group-match "name='Valentine\'s Day'"For complete query syntax, see:
The attribute map controls how Google Workspace user attributes are mapped onto the AWS IAM Identity Center (SCIM) user fields. It applies to the groups sync method — the modern, default method (internally Sync). The legacy users_groups path is unaffected.
Groups map DisplayName from the Google group name only. AWS SCIM accepts only externalId, displayName and members for groups, so there is nothing else to map; a group's description is not SCIM-settable.
When no map is supplied, ssosync uses a built-in default. You can override it with your own JSON, either per-field (any block you omit keeps its default) or wholesale.
The attribute map is a JSON object with two optional blocks, user and group. Within each block, the key is the SCIM / interface target field path and the value is the Google source attribute path:
{
"user": { "<SCIM target field>": "<Google source attribute>", ... },
"group": { "<SCIM target field>": "<Google source attribute>", ... }
}A block that is omitted falls back to its built-in default. A block that is present replaces that block's mapping entirely (it is not merged into the default). The map is validated when it is loaded: any unsupported target field or source attribute, or any empty key/value, fails the load with a message listing the supported values.
The following JSON is exactly the built-in default. Copying it verbatim changes nothing — it reproduces the default behaviour — so it is a safe starting point to copy and then customise:
{
"user": {
"Name.GivenName": "name.givenName",
"Name.FamilyName": "name.familyName",
"Username": "primaryEmail",
"Active": "suspended",
"Title": "organizations[primary].title",
"Enterprise.Department": "organizations[primary].department",
"Enterprise.Organization": "organizations[primary].name",
"Enterprise.CostCenter": "organizations[primary].costCenter"
},
"group": {
"DisplayName": "name"
}
}Notes on the defaults:
Activeis the logical inverse of Google'ssuspendedflag (Active == !suspended). The map stores the raw sourcesuspended; ssosync applies the inversion.DisplayNamefor users is always derived as"GivenName FamilyName"after the name fields are resolved; it is not a configurable map entry.- Group
DisplayNamemaps from the Google group name (admin.Group.Name).
Important
The organization-derived defaults (Title and the three Enterprise.* fields) source from the user's primary organization (organizations[primary].*). The Google Admin console marks an organization primary but does not set its type, so [primary] resolves for console-managed directories where a [type=work] selector would match nothing. Because there is no first-entry fallback (see Selection rule), a user with no organization marked primary resolves to nothing for all four fields — and those fields are then cleared on the AWS user under clear-to-consistent. If your directory types its organizations and you want the work one, set your own map with organizations[type=work].*. Run --dry-run after upgrading to preview the change.
The validator's allow-lists are the single source of truth for what is valid. Target fields are split into those included in the default map (defaulted) and those supported but left out of the default (opt-in). Opt-in targets have no built-in source — you choose the source attribute when you map them.
User target fields
| Target field | Status | Default source |
|---|---|---|
Name.GivenName |
defaulted | name.givenName |
Name.FamilyName |
defaulted | name.familyName |
Username |
defaulted | primaryEmail |
Active |
defaulted | suspended (inverted) |
Title |
defaulted | organizations[primary].title |
Enterprise.Department |
defaulted | organizations[primary].department |
Enterprise.Organization |
defaulted | organizations[primary].name |
Enterprise.CostCenter |
defaulted | organizations[primary].costCenter |
Enterprise.Manager |
opt-in | — (you choose, e.g. relations[type=manager].value) |
Enterprise.Division |
opt-in | — |
Enterprise.EmployeeNumber |
opt-in | — |
ProfileURL |
opt-in | — |
Emails |
opt-in | — |
Addresses |
opt-in | — |
PhoneNumbers |
opt-in | — |
Supported Google source grammar
A user source value is one of three shapes:
-
Scalar — matched exactly. The supported scalar sources are:
name.givenName,name.familyName,primaryEmail,suspended. -
Multi-valued —
<field>[<selector>].<subfield>, where<selector>isprimaryortype=<value>(see Selection rule). The supported base fields and their sub-fields are:Base field Supported sub-fields organizationstitle,department,name,costCenter,domainrelationsvaluewebsitesvalueemailsaddressphonesvalueaddressesformattedAddress sub-fields other than
formatted(streetAddress,locality,region,postalCode,country) are not supported yet, so e.g.addresses[type=work].streetAddressis rejected. -
Custom schema —
customSchemas.<SchemaName>.<fieldName>, single-valued custom fields only; multi-valued custom attributes are ignored with a warning (see User-defined (custom) schema sources).
Group target fields / sources: DisplayName ← name (the only supported group mapping). Groups have no custom-schema sources.
Enterprise.Manager (opt-in, not defaulted) maps from a relation value such as relations[type=manager].value, which Google stores as the manager's email address (a proxy key, not a SCIM user-id reference). This works where your deployment's manager relation holds an email that the Identity Store can resolve. It is opt-in rather than defaulted because it depends on your directory populating that relation; add it to your map when you want manager data synced.
Google user-defined (custom) schema attributes can be used as sources with the syntax customSchemas.<SchemaName>.<fieldName>. The path is split on the first dot after the customSchemas. prefix: everything before that dot is the schema name, everything after is the field name. Schema and field names may contain hyphens and underscores (for example customSchemas.Single_Sign-On.samlRole); a field name may contain further dots, but a schema name may not.
Only single-valued custom fields are supported. A single-valued custom field is used as-is: customSchemas.EmploymentData.employeeId.
Multi-valued custom fields are not supported (a field defined with "multiValued" in the Google schema, which the Directory API returns as a list even when it holds one value). Google custom fields cannot be typed, so there is no type to select an element by, and ssosync does not pick an element by position, so that results stay stable from run to run. Multi-valued custom attributes are ignored, with a WARNING, in two places:
- When the map is loaded. A custom source with a selector, such as
customSchemas.EmploymentData.ids[type=payroll], asks for one element of a multi-valued field. ssosync logsattribute map: multi-valued custom-schema attributes are not supported; mapping ignoredwith thetargetandsource, and drops that entry. The rest of the map still loads. The[primary]selector is not valid for custom sources and fails the load. - During a sync. If a mapped custom field turns out to be multi-valued on a user, ssosync logs
attribute map: custom-schema field is multi-valued; multi-valued custom attributes are not supported, mapping ignoredwithreason=customFieldMultiValued, theschema,field,targetanduser. The source then resolves to nothing for that user. With debug logging on, the per-entry trace shows the same entry asresolved=""withreason=customFieldMultiValued.
Either way the target is left unmapped, so under clear-to-consistent it is cleared in AWS. If you need one of the values, keep it in a separate single-valued custom field and map that instead. Support for multi-valued custom attributes may be added later.
If a referenced custom schema or field is absent on a user, ssosync logs a WARNING and the source resolves to nothing — so the target field is then cleared under clear-to-consistent.
Custom sources can only feed the closed set of core/enterprise SCIM target fields listed under Supported fields: AWS IAM Identity Center SCIM does not accept arbitrary custom extension schemas as targets. Groups have no custom schemas.
Several Google fields are multi-valued (organizations, relations, phones, addresses, emails, websites). The source path selects a single entry deterministically:
- A type-qualified path such as
relations[type=manager].valueselects the first entry whose type matches (case-insensitive); if no entry matches, nothing is selected. - A
[primary]path selects the entry marked primary; if no entry is marked primary, nothing is selected.
There is no first-entry or index fallback, so the result is stable from one run to the next (no run-to-run churn). A consequence: a user who has organization data but no organization matching the selector (for the defaults, no work-typed organization) gets no Title/Department/Organization/CostCenter — and, per the next section, any such field already set on the AWS user is then cleared.
ssosync fetches users from the Google Admin Directory API with projection=full, so user-defined (custom) schema attributes are available to the mapper. Standard fields are returned identically to the default projection, so this changes no standard-field behaviour.
The sync makes the AWS user consistent with the mapped Google user (clear-to-consistent): any supported SCIM field that the map does not populate is cleared on the AWS user.
Caution
Always test a new or changed attribute map with --dry-run first. Because of clear-to-consistent, an error in the attribute map — a typo, a [type=X] selector that matches nothing, a missing custom field, or an ignored multi-valued custom field — resolves to nothing and clears the corresponding attribute on every affected AWS user. A dry run shows exactly which attributes would change before anything is written. This applies to adopting the defaults too, since the [primary] org selector clears org-sourced fields for users with no organization marked primary. See the dry-run note under Retaining unmatched users and groups.
Two logging aids help you validate a custom map alongside --dry-run:
- DEBUG logging. Enabling debug logs the raw Google source fields fetched for each user/group, the per-entry resolution (what each map entry resolved to, and why it resolved to nothing), and the full object produced at the entry and exit of the mapping function. This lets you compare the actual fetched Google data against your attribute map when a field maps unexpectedly. Debug output includes PII (names, emails, phone numbers, addresses, custom attributes) and is not redacted, so enable it only for troubleshooting.
- Pre-SCIM validation warnings. Just before sending each mapped user/group to the SCIM API, ssosync logs a
WARNwhen the object would likely be rejected by AWS — a required field is empty, a multi-valued field carries more than one value, the single email is not primary, oruserName/groupdisplayNameis over its length limit or contains disallowed control characters. This is warn-only: the object is still sent and AWS remains the authoritative acceptance decision.
Run --dry-run, read the pre-SCIM warnings, and (if a field still looks wrong) enable debug to compare source against map — together these are the way to validate a custom map safely before a live sync.
- CLI:
--attribute-map-file <path>points to a JSON file (mirrors--google-credentials). When unset, the built-in defaults are used. - Lambda: the
ATTRIBUTE_MAPenvironment variable. LikeGOOGLE_CREDENTIALS, it accepts a Secrets Manager secret ARN, an SSM Parameter Store ARN, or the inline attribute-map JSON. When unset, the built-in defaults are used.
- Go 1.24+
- Make
- AWS CLI (for deployment)
# Clone repository
git clone https://github.com/awslabs/ssosync.git
cd ssosync/
# Install development dependencies
make setup
# Run tests
make test
# Build locally
make go-build
# Run with development configuration
make devmake help # Show all available targets
make setup # Install all dependencies
make test # Run tests with coverage
make test-verbose # Run tests with verbose output
make test-coverage # Generate HTML coverage report
make lint # Run linters
make fmt # Format code
make go-build # Build application
make clean # Clean build artifacts
make ci # Run CI pipeline (fmt, vet, test)The project includes comprehensive tests with 61%+ coverage:
# Run all tests
make test
# Run tests with verbose output
make test-verbose
# Generate coverage report
make test-coverage
# Run integration tests (requires valid credentials)
go test -tags=integration ./internal -vDeploy from the AWS Serverless Application Repository: the next-gen ssosync app (keyless) or the classic SSOSync app (2.10.0 parameters). See Two Serverless applications; the quick-start templates are the simplest way to deploy and update either.
Both applications are generated from template.yaml, which keeps every parameter: make sar-variants applies the patches in cicd/build/package/ (legacy.patch or next-gen.patch, then a name patch) and writes the variants to dist/sar/.
| Pattern | Description | Use Case |
|---|---|---|
| App + Secrets | Default mode, creates app and secrets | Single account deployment |
| App Only | Creates app, expects existing secrets | Shared secrets across environments |
| Secrets Only | Creates secrets without app | Centralized secret management |
| Cross-Account | App in one account, secrets in another | Multi-account organizations |
# Build
sam build
# Deploy with guided setup
sam deploy --guided
# Or deploy with parameters
sam deploy \
--stack-name ssosync \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides \
GoogleAdminEmail=admin@company.com \
ScheduleExpression="rate(15 minutes)"When deployed as Lambda, configuration is managed through environment variables. Sensitive values can be stored in AWS Secrets Manager or SSM Parameter Store — simply set the environment variable to the resource ARN and ssosync will resolve the value automatically:
- Secrets Manager:
arn:aws:secretsmanager:<region>:<account>:secret:<name> - SSM Parameter Store (SecureString):
arn:aws:ssm:<region>:<account>:parameter/<name>
# Required secrets (stored in Secrets Manager or SSM Parameter Store)
SCIM_ENDPOINT=<secret-arn or ssm-parameter-arn>
SCIM_ACCESS_TOKEN=<secret-arn or ssm-parameter-arn>
CUSTOMER_ID=<secret-arn, ssm-parameter-arn, or plain value> # actual ID required for keyless or without GOOGLE_ADMIN
# Google authentication, one of:
# Legacy auth (stored credentials)
GOOGLE_CREDENTIALS=<secret-arn or ssm-parameter-arn>
GOOGLE_ADMIN=<secret-arn or ssm-parameter-arn>
# Workload Identity Federation (keyless)
GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=<secret-arn or service account email>
GOOGLE_WIF_CONFIG=<secret-arn, ssm-parameter-arn, or inline JSON>
# Optional attribute map (groups sync method only). Accepts a Secrets Manager ARN,
# an SSM Parameter Store ARN, or inline attribute-map JSON. Unset => built-in defaults.
# See the Attribute mapping section.
ATTRIBUTE_MAP=<secret-arn, ssm-parameter-arn, or inline JSON>
# Optional environment variables
LOG_LEVEL=info
LOG_FORMAT=json
SYNC_METHOD=groups
GROUP_MATCH=*
USER_MATCH=
IGNORE_USERS=
IGNORE_GROUPS=
INCLUDE_GROUPS=
PRECACHE_ORG_UNITS=
CACHE_METRICS=false
DRY_RUN=false
SYNC_SUSPENDED=false
FORCE_EXTERNAL_ID_UPDATE=false
RETAIN_UNMATCHED=falseNote:
REGIONandIDENTITY_STORE_IDenvironment variables are no longer required. Region is derived from the SCIM endpoint URL, and Identity Store ID is resolved via the IAM Identity Center API.
When deployed as Lambda, logs are automatically sent to CloudWatch:
# View logs
aws logs describe-log-groups --log-group-name-prefix /aws/lambda/ssosync
# Tail logs
aws logs tail /aws/lambda/ssosync-function --followError: AWS SSO SCIM API rate limits errors
Solution: Reduce sync frequency or implement exponential backoff
Error: cannot read secret: <secret-name>
Solution: Verify IAM permissions for Secrets Manager access
Error: Error Getting Deleted Users
Solution: Verify Google service account permissions and domain-wide delegation
- User Caching: Enable user caching for large directories
- Filtering: Use specific group/user filters to reduce API calls
- Batch Size: Adjust batch sizes for large sync operations
- Scheduling: Set appropriate sync intervals based on directory size
We welcome contributions! Please see our Contributing Guidelines for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Run the test suite:
make ci - Submit a pull request
- Follow Go best practices
- Maintain test coverage above 60%
- Use structured logging
- Document public APIs
- Follow semantic versioning
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- AWS Labs team for the original implementation
- Google Workspace Directory API team
- AWS IAM Identity Center team
- All contributors and community members
Need help? Check out our Issues page or start a Discussion.