Skip to content
awslabsPublic

About

Populate AWS SSO directly with your G Suite users and groups using either a CLI or AWS Lambda

Resources

Code of conduct

Contributing

Security policy

Stars

674 stars

Watchers

12 watching

Forks

SSO Sync

Github Action gopherbadger-tag-do-not-edit Go Report Card License Apache 2 AWS SDK v2 Taylor Swift Built with Kiro

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.

✨ Key Features

  • 🔄 Uni-directional Sync: Supports both groups and users_groups sync methods
  • 🎯 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

🚀 Quick Start

Use one of the quick start templates to simplify the your deployment. Use these templates, with the Sync from Git option, to make updates to your deployment far simpler.

🚀 Step-by-Step Guide

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.

Installation Options

Method Best For Setup Time
AWS Serverless App Repository Production use 5 minutes
CLI Binary Local testing, CI/CD 2 minutes
AWS SAM Custom deployments 10 minutes

Why?

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 Notices

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-update option.

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:ListInstances permissions.

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.

References

Installation

The recommended installation is:

  • Setup IAM Identity Center, in the management account of your organization
  • Create a linked account (for example an Identity account) from which to run ssosync
  • Deploy the SSOSync app from the AWS Serverless Application Repository into that linked account

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.

⚙️ Configuration

SSO Sync requires configuration from both Google Workspace and AWS sides.

Google Workspace Setup

ssosync reads your directory as a service account that impersonates a Google Workspace admin via domain-wide delegation. The steps below produce the two things ssosync needs: a credentials.json key file and the email address of an admin for the service account to impersonate.

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.readonly
        • https://www.googleapis.com/auth/admin.directory.group.member.readonly
        • https://www.googleapis.com/auth/admin.directory.user.readonly
    • Click Authorize. Only these three read-only scopes are needed; do not grant broader access.
  6. 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.

Tip: credentials.json holds the service account key (the who ssosync authenticates as) and GOOGLE_ADMIN is the admin it impersonates (the who it acts on behalf of). You need both.

AWS Setup

  1. Enable IAM Identity Center

    • Go to AWS IAM Identity Center console
    • Select Settings → Enable automatic provisioning
    • Copy the SCIM Endpoint URL and Access Token
  2. 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)
    • The executing role needs permissions for sso:ListInstances to auto-detect the Identity Store ID

🚀 Usage

CLI Usage

./ssosync --help

Basic Example

./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*"

Advanced Examples

# 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"

Environment Variables

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"

Configuration Options

Flag Environment Variable Description Default
--google-admin / -u SSOSYNC_GOOGLE_ADMIN Google Workspace admin email Required
--google-credentials / -c SSOSYNC_GOOGLE_CREDENTIALS Path to Google credentials JSON credentials.json
--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 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 *
--user-match / -m SSOSYNC_USER_MATCH Google Users filter query ""
--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: --region and --identity-store-id are no longer required. Region is extracted from the SCIM endpoint URL, and Identity Store ID is resolved automatically via the IAM Identity Center API.

Preserving assignments when migrating IdPs

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.

Retaining unmatched users and groups

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.

Usecases
local user - Matched
  • ExternalId unset
  • 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.
synced user - unMatched
  • ExternalId set but doesn't match
  • primary email address doesn't match Outcome the user is deleted.
synced user - miss-Matched
  • ExternalId set 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-update is also set.
synced user - Matched
  • ExternalId set 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.
local group - unMatched
  • ExternalId unset
  • DisplayName doesn't match Outcome The group is retained .
local group - Matched
  • ExternalId unset
  • DisplayName does match Outcome Groups is updated with their ExternalId from the Google Workspace Directory. They have been adopted and are managed by ssosync
synced group - unMatched
  • ExternalId set but doesn't match Outcome the group is deleted.
synced group - Matched
  • ExternalId set and matches
  • DisplayName is not inspected Outcome Group is compared to the matching group in Google, if it differs in anyway its updated to match.

Filtering Examples

Group Filtering

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. When memberKey is used you must supply your real Google Workspace customer ID via --customer-id (SSOSYNC_CUSTOMER_ID); the default my_customer placeholder 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 the groups sync 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 "*"
Syncing groups regardless of naming strategy with memberKey

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.

  1. 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
  2. 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.)

  3. 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: memberKey queries require your real Google Workspace customer ID. Set --customer-id (SSOSYNC_CUSTOMER_ID) to your actual customer ID rather than leaving it as the my_customer default.

User Filtering

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 or email=admin@company.com)
  • Use " for exact values containing spaces (e.g. orgName="My Department")
  • 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 "*"

For complete query syntax, see:

Attribute mapping

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.

JSON syntax

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.

Default map

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:

  • Active is the logical inverse of Google's suspended flag (Active == !suspended). The map stores the raw source suspended; ssosync applies the inversion.
  • DisplayName for users is always derived as "GivenName FamilyName" after the name fields are resolved; it is not a configurable map entry.
  • Group DisplayName maps 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.

Supported fields

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> is primary or type=<value> (see Selection rule). The supported base fields and their sub-fields are:

    Base field Supported sub-fields
    organizations title, department, name, costCenter, domain
    relations value
    websites value
    emails address
    phones value
    addresses formatted

    Address sub-fields other than formatted (streetAddress, locality, region, postalCode, country) are not supported yet, so e.g. addresses[type=work].streetAddress is 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.

User-defined (custom) schema sources

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 logs attribute map: multi-valued custom-schema attributes are not supported; mapping ignored with the target and source, 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 ignored with reason=customFieldMultiValued, the schema, field, target and user. The source then resolves to nothing for that user. With debug logging on, the per-entry trace shows the same entry as resolved="" with reason=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.

Selection rule for multi-valued Google fields

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].value selects 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.

Projection

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.

Diagnostics and safety

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 WARN when 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, or userName/group displayName is 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.

Supplying the map

  • CLI: --attribute-map-file <path> points to a JSON file (mirrors --google-credentials). When unset, the built-in defaults are used.
  • Lambda: the ATTRIBUTE_MAP environment variable. Like GOOGLE_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.

🔧 Development

Prerequisites

  • Go 1.24+
  • Make
  • AWS CLI (for deployment)

Setup Development Environment

# 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 dev

Available Make Targets

make 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)

Testing

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 -v

🚀 AWS Lambda Deployment

Serverless Application Repository (Recommended)

Deploy directly from the AWS Serverless Application Repository.

Deployment Patterns

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

Manual SAM Deployment

# 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)"

Lambda Environment Variables

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)
GOOGLE_ADMIN=<secret-arn or ssm-parameter-arn>
GOOGLE_CREDENTIALS=<secret-arn or ssm-parameter-arn>
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>

# 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=false

Note: REGION and IDENTITY_STORE_ID environment 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.

📊 Monitoring & Troubleshooting

CloudWatch Logs

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 --follow

Common Issues

Rate Limiting

Error: AWS SSO SCIM API rate limits errors

Solution: Reduce sync frequency or implement exponential backoff

Authentication Errors

Error: cannot read secret: <secret-name>

Solution: Verify IAM permissions for Secrets Manager access

Google API Errors

Error: Error Getting Deleted Users

Solution: Verify Google service account permissions and domain-wide delegation

Performance Optimization

  • 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

🤝 Contributing

We welcome contributions! Please see our Contributing Guidelines for details.

Development Workflow

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Run the test suite: make ci
  6. Submit a pull request

Code Standards

  • Follow Go best practices
  • Maintain test coverage above 60%
  • Use structured logging
  • Document public APIs
  • Follow semantic versioning

📄 License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

🙏 Acknowledgments

  • 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.

About

Populate AWS SSO directly with your G Suite users and groups using either a CLI or AWS Lambda

Resources

Code of conduct

Contributing

Security policy

Stars

674 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages