Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
33cf47d
update regex tennant id has changed length
ChrisPates Jan 20, 2026
fb55723
Update runtime to provided.al2023
ChrisPates Mar 10, 2026
f473ed7
299 scheduleexpression allowedpattern rejects valid singular rate exp…
ChrisPates Mar 10, 2026
a834422
fix: Use `config.DefaultSyncMethod` as the default for `cfg.SyncMethod`
44smkn Feb 9, 2026
b28cac2
Bump golang.org/x/crypto from 0.40.0 to 0.45.0
dependabot[bot] Nov 20, 2025
1b60438
Delete users if inactive only (#304)
ChrisPates Mar 11, 2026
f899c6d
Bump google.golang.org/grpc from 1.74.2 to 1.79.3
dependabot[bot] Mar 19, 2026
28f19a0
Bump go.opentelemetry.io/otel from 1.39.0 to 1.41.0
dependabot[bot] Apr 24, 2026
52f5f79
309 precache field regex does not all for disabled (#312)
ChrisPates Apr 30, 2026
cc0c9e1
Update template.yaml
ChrisPates Apr 30, 2026
53518d6
Update template.yaml
ChrisPates Apr 30, 2026
c6b623d
309 precache field regex does not all for disabled (#312)
ChrisPates May 9, 2026
b7f823c
Merge branch 'master' of https://github.com/awslabs/ssosync
ChrisPates May 9, 2026
ce5d9d3
Force BugFix Release
ChrisPates May 9, 2026
177a3fa
Merge branch 'master' of https://github.com/awslabs/ssosync
ChrisPates May 9, 2026
77bab77
Update Pipeline to type: V2
ChrisPates May 9, 2026
ddafd84
Configurable LogRetention and QuickStart, plus bugfixes (#316)
ChrisPates May 21, 2026
eba2b14
fixes
ChrisPates May 21, 2026
6f271e1
Update README.md
ChrisPates May 21, 2026
561a1c6
Investigating Change in non-Delegated behavior
ChrisPates May 21, 2026
2a5dc86
Revert "Investigating Change in non-Delegated behavior"
ChrisPates May 21, 2026
93f385a
Update sync.go
ChrisPates May 21, 2026
77a9357
Update workflows
ChrisPates May 22, 2026
03578cb
Update release.yml
ChrisPates May 22, 2026
94a0c05
Update release.yml
ChrisPates May 22, 2026
033445c
Update release.yml
ChrisPates May 22, 2026
6a79623
Update release.yml
ChrisPates May 22, 2026
43bd79a
Update release.yml
ChrisPates May 22, 2026
46af4cf
Split workflows
ChrisPates May 22, 2026
7e0586d
feat: wildcard support in ignore-users and ignore-groups
kesor Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Go
uses: actions/setup-go@v5
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/quickstarts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# .github/workflows/quickstarts.yaml
name: quickstarts

on: release

permissions:
contents: write

jobs:
quickstart:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6

- name: Fetch SEMANTIC_VERSION
run : |
echo "SEMANTIC_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV

- name: Show SemanticVersion
run: |
echo "env.SemanticVersion: ${{ env.SEMANTIC_VERSION }}"

- name: Show current SemanticVersion
run: grep SemanticVersion quick-start/*.yaml

- name: Update Templates
run: |
git grep -lr -e 'SemanticVersion' -- quick-start | xargs sed -i -E 's/SemanticVersion: [0-9.]+/SemanticVersion: ${{ env.SEMANTIC_VERSION }}/g'

- name: Show Templates SemanticVersion
run: grep SemanticVersion quick-start/*.yaml

- name: Commit updated Quick Start Templates
uses: stefanzweifel/git-auto-commit-action@v7
with:
branch: ${{ github.event.release.target_commitish }}
commit_message: Update Quick Start templates
file_pattern: quick-start
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Go
uses: actions/setup-go@v5
Expand Down Expand Up @@ -44,7 +44,7 @@ jobs:
needs: [ test ]
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Unshallow
run: git fetch --prune --unshallow
Expand Down
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ SSO Sync is a powerful CLI tool and AWS Lambda function that enables automatic p

## ✨ Key Features

- **🔄 Bi-directional Sync**: Supports both `groups` and `users_groups` sync methods
- **🔄 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
Expand All @@ -25,6 +25,9 @@ SSO Sync is a powerful CLI tool and AWS Lambda function that enables automatic p
- **📈 Scalable**: Supports large directories with user caching and pagination

## 🚀 Quick Start
Use one of the [quick start](/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](https://catalog.workshops.aws/control-tower/en-US/authentication-authorization/google-workspace) 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](https://console.aws.amazon.com/lambda/home#/create/app?applicationId=arn:aws:serverlessrepo:us-east-2:004480582608:applications/SSOSync).

Expand Down Expand Up @@ -184,13 +187,21 @@ SSO Sync requires configuration from both Google Workspace and AWS sides.
--group-match "*" \
--sync-method users_groups

# Ignore specific users/groups
# Ignore specific users/groups (entries may use '*' as a wildcard)
./ssosync \
--group-match "*" \
--ignore-users "service@company.com,bot@company.com" \
--ignore-groups "temp-group@company.com"
--ignore-users "service@company.com,bot@company.com,*@contractors.example.com" \
--ignore-groups "temp-group@company.com,AWS*"
```

Ignored users and groups are protected in both directions: they are skipped
when Google is enumerated, and they are also skipped when picking AWS
entries to delete. This means a pattern like `*@contractors.example.com`
acts as a deletion guard for accounts that exist only in AWS.

Comment on lines +197 to +201
Only `*` is special — it matches any (possibly empty) substring. Every
other character, including `?`, `[`, `]`, and `\`, is matched literally.

### Environment Variables

All CLI flags can be set via environment variables with the `SSOSYNC_` prefix:
Expand Down Expand Up @@ -219,8 +230,8 @@ export SSOSYNC_DRY_RUN="true"
| `--sync-method` | `SSOSYNC_SYNC_METHOD` | Sync method (`groups` or `users_groups`) | `groups` |
| `--group-match` | `SSOSYNC_GROUP_MATCH` | Google Groups filter query | `*` |
| `--user-match` | `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 | `[]` |
| `--ignore-users` | `SSOSYNC_IGNORE_USERS` | Comma-separated list of users to ignore (supports `*` wildcard) | `[]` |
| `--ignore-groups` | `SSOSYNC_IGNORE_GROUPS` | Comma-separated list of groups to ignore (supports `*` wildcard) | `[]` |
| `--include-groups` | `SSOSYNC_INCLUDE_GROUPS` | Include only these groups (users_groups method only) | `[]` |
| `--dry-run` | `SSOSYNC_DRY_RUN` | Enable dry-run mode | `false` |
| `--log-level` | `SSOSYNC_LOG_LEVEL` | Log level (debug, info, warn, error) | `info` |
Expand Down Expand Up @@ -455,4 +466,4 @@ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENS

---

**Need help?** Check out our [Issues](https://github.com/awslabs/ssosync/issues) page or start a [Discussion](https://github.com/awslabs/ssosync/discussions).
**Need help?** Check out our [Issues](https://github.com/awslabs/ssosync/issues) page or start a [Discussion](https://github.com/awslabs/ssosync/discussions).
1 change: 1 addition & 0 deletions cicd/cloudformation/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Resources:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: SSOSync-Build
PipelineType: V2
RoleArn: !Sub ${CodePipelineRole.Arn}
ArtifactStore:
Type: S3
Expand Down
62 changes: 33 additions & 29 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func initConfig() {
"sync_method",
"region",
"identity_store_id",
"dry_run",
}

for _, e := range appEnvVars {
Expand All @@ -193,11 +194,11 @@ func initConfig() {
// config logger
logConfig(cfg)

if cfg.SyncSuspended {
cfg.UserFilter = " isArchived=false"
} else {
cfg.UserFilter = " isSuspended=false isArchived=false"
}
if cfg.SyncSuspended {
cfg.UserFilter = " isArchived=false"
} else {
cfg.UserFilter = " isSuspended=false isArchived=false"
}

}

Expand All @@ -208,25 +209,28 @@ func getEnvStr(key string, fallback string) string {
log.WithField(key, valueStr).Info("EnvVar")
return valueStr
}
return fallback
return fallback
}

func getEnvStrs (key string, fallback []string) []string {
if valueStr, ok := os.LookupEnv(key); ok {
log.WithField(key, valueStr).Info("EnvVar")
return strings.Split(valueStr, ",")
}
return fallback
func getEnvStrs(key string, fallback []string) []string {
if valueStr, ok := os.LookupEnv(key); ok {
log.WithField(key, valueStr).Info("EnvVar")
if valueStr == "" {
return nil
}
return strings.Split(valueStr, ",")
}
return fallback
}

func getEnvBool (key string, fallback bool) bool {
if valueStr, ok := os.LookupEnv(key); ok {
func getEnvBool(key string, fallback bool) bool {
if valueStr, ok := os.LookupEnv(key); ok {
log.WithField(key, valueStr).Info("EnvVar")
valueBool := strings.ToLower(valueStr) == "true"
valueBool := strings.ToLower(valueStr) == "true"
log.WithField(key, valueBool).Info("config")
return valueBool
}
return fallback
return valueBool
}
return fallback
}

func configLambda() {
Expand Down Expand Up @@ -255,17 +259,17 @@ func configLambda() {
cfg.Region = getSecretFromCache(getEnvStr("REGION", ""))
cfg.GoogleCredentials = getSecretFromCache(getEnvStr("GOOGLE_CREDENTIALS", ""))
cfg.SCIMAccessToken = getSecretFromCache(getEnvStr("SCIM_ACCESS_TOKEN", ""))

// Handle environment variables for other settings
cfg.LogLevel = getEnvStr("LOG_LEVEL", config.DefaultLogLevel)
cfg.LogFormat = getEnvStr("LOG_FORMAT", config.DefaultLogFormat)
cfg.SyncMethod = getEnvStr("SYNC_METHOD", config.DefaultLogFormat)
cfg.SyncMethod = getEnvStr("SYNC_METHOD", config.DefaultSyncMethod)
cfg.UserMatch = getEnvStr("USER_MATCH", "")
cfg.GroupMatch = getEnvStr("GROUP_MATCH", "*")
cfg.IgnoreGroups = getEnvStrs("IGNORE_GROUPS", []string{})
cfg.IgnoreUsers = getEnvStrs("IGNORE_USERS", []string{})
cfg.IncludeGroups = getEnvStrs("INCLUDE_GROUPS", []string{})
cfg.PrecacheOrgUnits = getEnvStrs("PRECACHE_ORG_UNITS", strings.Split(config.DefaultPrecacheOrgUnits, ","))
cfg.IgnoreGroups = getEnvStrs("IGNORE_GROUPS", nil)
cfg.IgnoreUsers = getEnvStrs("IGNORE_USERS", nil)
cfg.IncludeGroups = getEnvStrs("INCLUDE_GROUPS", nil)
cfg.PrecacheOrgUnits = getEnvStrs("PRECACHE_ORG_UNITS", nil)
cfg.DryRun = getEnvBool("DRY_RUN", false)
cfg.SyncSuspended = getEnvBool("SYNC_SUSPENDED", false)

Expand All @@ -285,20 +289,20 @@ func addFlags(_ *cobra.Command, cfg *config.Config) {
rootCmd.PersistentFlags().StringVarP(&cfg.LogFormat, "log-format", "", config.DefaultLogFormat, "log format")
rootCmd.PersistentFlags().StringVarP(&cfg.LogLevel, "log-level", "", config.DefaultLogLevel, "log level")
rootCmd.PersistentFlags().BoolVarP(&cfg.DryRun, "dry-run", "n", false, "Do *not* perform any actions, instead list what would happen")
rootCmd.PersistentFlags().BoolVarP(&cfg.SyncSuspended, "suspended", "", false, "included suspended users and their group memberships when syncing")
rootCmd.PersistentFlags().BoolVarP(&cfg.SyncSuspended, "suspended", "", false, "included suspended users and their group memberships when syncing")
rootCmd.Flags().StringVarP(&cfg.SCIMAccessToken, "access-token", "t", "", "AWS SSO SCIM API Access Token")
rootCmd.Flags().StringVarP(&cfg.SCIMEndpoint, "endpoint", "e", "", "AWS SSO SCIM API Endpoint")
rootCmd.Flags().StringVarP(&cfg.GoogleCredentials, "google-credentials", "c", config.DefaultGoogleCredentials, "path to Google Workspace credentials file")
rootCmd.Flags().StringVarP(&cfg.GoogleAdmin, "google-admin", "u", "", "Google Workspace admin user email")
rootCmd.Flags().StringSliceVar(&cfg.IgnoreUsers, "ignore-users", []string{}, "ignores these Google Workspace users")
rootCmd.Flags().StringSliceVar(&cfg.IgnoreGroups, "ignore-groups", []string{}, "ignores these Google Workspace groups")
rootCmd.Flags().StringSliceVar(&cfg.IncludeGroups, "include-groups", []string{}, "include only these Google Workspace groups, NOTE: only works when --sync-method 'users_groups'")
rootCmd.Flags().StringSliceVar(&cfg.IgnoreUsers, "ignore-users", nil, "ignores these Google Workspace users")
rootCmd.Flags().StringSliceVar(&cfg.IgnoreGroups, "ignore-groups", nil, "ignores these Google Workspace groups")
rootCmd.Flags().StringSliceVar(&cfg.IncludeGroups, "include-groups", nil, "include only these Google Workspace groups, NOTE: only works when --sync-method 'users_groups'")
rootCmd.Flags().StringVarP(&cfg.UserMatch, "user-match", "m", "", "Google Workspace Users filter query parameter, example: 'name:John*' 'name=John Doe,email:admin*', to sync all users in the directory specify '*'. For query syntax and more examples see: https://developers.google.com/admin-sdk/directory/v1/guides/search-users")
rootCmd.Flags().StringVarP(&cfg.GroupMatch, "group-match", "g", "*", "Google Workspace Groups filter query parameter, example: 'name:Admin*' 'name=AWS-Admins,email:aws*', to sync all groups (and their member users) specify '*'. For query syntax and more examples see: https://developers.google.com/admin-sdk/directory/v1/guides/search-groups")
rootCmd.Flags().StringVarP(&cfg.SyncMethod, "sync-method", "s", config.DefaultSyncMethod, "Sync method to use (users_groups|groups)")
rootCmd.Flags().StringVarP(&cfg.Region, "region", "r", "", "AWS Region where AWS SSO is enabled")
rootCmd.Flags().StringVarP(&cfg.IdentityStoreID, "identity-store-id", "i", "", "Identifier of Identity Store in AWS SSO")
rootCmd.Flags().StringSliceVar(&cfg.PrecacheOrgUnits, "precache-ous", strings.Split(config.DefaultPrecacheOrgUnits, ","), "A common separated list of Google Workspace OrgUnitPathis e.g.'/', to precache all users within the organization or '/OU_1/OU 2,/OU3'. To disable and use caching on the fly, 'DISABLED'.")
rootCmd.Flags().StringSliceVar(&cfg.PrecacheOrgUnits, "precache-ous", nil, "A common separated list of Google Workspace OrgUnitPathis e.g.'/', to precache all users within the organization or '/OU_1/OU 2,/OU3'. Precaching is disabled by default.")

}

Expand Down
31 changes: 16 additions & 15 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/awslabs/ssosync

go 1.24
go 1.24.0

require (
github.com/BurntSushi/toml v1.5.0
Expand All @@ -17,15 +17,15 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.10.0
golang.org/x/oauth2 v0.30.0
github.com/stretchr/testify v1.11.1
golang.org/x/oauth2 v0.34.0
google.golang.org/api v0.246.0
)

require (
cloud.google.com/go/auth v0.16.4 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.8.0 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 // indirect
Expand All @@ -37,6 +37,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.32.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.36.0 // indirect
github.com/aws/smithy-go v1.22.5 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
Expand All @@ -58,17 +59,17 @@ require (
github.com/spf13/pflag v1.0.7 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.7 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading