From 5500cc09372c70f69b2496f4c77453094b00ffbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 23:39:14 +0000 Subject: [PATCH 1/5] Initial plan From 08c85d99ccf5956f723edbbe06946ca0ddc215ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 23:44:24 +0000 Subject: [PATCH 2/5] Add GitHub Actions workflow for team sync Co-authored-by: cpritchett <3866278+cpritchett@users.noreply.github.com> --- .github/workflows/sync-teams.yml | 185 +++++++++++++++++++++++++++++++ README.md | 52 ++++++++- 2 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/sync-teams.yml diff --git a/.github/workflows/sync-teams.yml b/.github/workflows/sync-teams.yml new file mode 100644 index 0000000..6d6bc3a --- /dev/null +++ b/.github/workflows/sync-teams.yml @@ -0,0 +1,185 @@ +--- +################################### +################################### +## GitHub Team Sync Workflow ## +################################### +################################### +name: Sync GitHub Teams + +# +# Documentation: +# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +# + +############################# +# Run on schedule or manual # +############################# +on: + # Run on a schedule (default: hourly at minute 0) + schedule: + - cron: '0 * * * *' + + # Allow manual trigger + workflow_dispatch: + inputs: + test_mode: + description: 'Run in test mode (show changes without applying)' + required: false + default: 'false' + type: choice + options: + - 'true' + - 'false' + +############### +# Set the Job # +############### +jobs: + sync: + name: Sync Teams + runs-on: ubuntu-latest + + steps: + ########################## + # Checkout the code base # + ########################## + - name: Checkout Code + uses: actions/checkout@v4 + + ############################### + # Set up Python environment # + ############################### + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + + ########################## + # Install dependencies # + ########################## + - name: Install pipenv + run: pip install pipenv + + - name: Install dependencies + run: pipenv install --deploy + + #################################### + # Create private key file from secret + #################################### + - name: Create private key file + run: | + mkdir -p .ssh + echo "${{ secrets.GITHUB_APP_PRIVATE_KEY }}" > .ssh/team-sync.pem + chmod 600 .ssh/team-sync.pem + + #################################### + # Create Google Workspace credentials file if needed + #################################### + - name: Create Google Workspace credentials file + if: env.USER_DIRECTORY == 'GOOGLE_WORKSPACE' && secrets.GOOGLE_WORKSPACE_SA_CREDS != '' + run: | + echo "${{ secrets.GOOGLE_WORKSPACE_SA_CREDS }}" > googleAuth.json + chmod 600 googleAuth.json + env: + USER_DIRECTORY: ${{ secrets.USER_DIRECTORY }} + + #################################### + # Create syncmap.yml if configured + #################################### + - name: Create syncmap.yml + if: secrets.SYNCMAP_YML != '' + run: | + echo "${{ secrets.SYNCMAP_YML }}" > syncmap.yml + + ########################## + # Run the sync # + ########################## + - name: Run team sync + env: + # GitHub App settings + APP_ID: ${{ secrets.APP_ID }} + PRIVATE_KEY_PATH: .ssh/team-sync.pem + WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }} + GHE_HOST: ${{ secrets.GHE_HOST }} + VERIFY_SSL: ${{ secrets.VERIFY_SSL }} + + # User Directory settings + USER_DIRECTORY: ${{ secrets.USER_DIRECTORY }} + USER_SYNC_ATTRIBUTE: ${{ secrets.USER_SYNC_ATTRIBUTE }} + + # LDAP settings (if using LDAP/Active Directory) + LDAP_SERVER_HOST: ${{ secrets.LDAP_SERVER_HOST }} + LDAP_SERVER_PORT: ${{ secrets.LDAP_SERVER_PORT }} + LDAP_BASE_DN: ${{ secrets.LDAP_BASE_DN }} + LDAP_USER_BASE_DN: ${{ secrets.LDAP_USER_BASE_DN }} + LDAP_GROUP_BASE_DN: ${{ secrets.LDAP_GROUP_BASE_DN }} + LDAP_USER_FILTER: ${{ secrets.LDAP_USER_FILTER }} + LDAP_USER_ATTRIBUTE: ${{ secrets.LDAP_USER_ATTRIBUTE }} + LDAP_USER_MAIL_ATTRIBUTE: ${{ secrets.LDAP_USER_MAIL_ATTRIBUTE }} + LDAP_GROUP_FILTER: ${{ secrets.LDAP_GROUP_FILTER }} + LDAP_GROUP_MEMBER_ATTRIBUTE: ${{ secrets.LDAP_GROUP_MEMBER_ATTRIBUTE }} + LDAP_BIND_USER: ${{ secrets.LDAP_BIND_USER }} + LDAP_BIND_PASSWORD: ${{ secrets.LDAP_BIND_PASSWORD }} + LDAP_SEARCH_PAGE_SIZE: ${{ secrets.LDAP_SEARCH_PAGE_SIZE }} + + # Azure AD settings (if using Azure AD) + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_APP_SCOPE: ${{ secrets.AZURE_APP_SCOPE }} + AZURE_API_ENDPOINT: ${{ secrets.AZURE_API_ENDPOINT }} + AZURE_USERNAME_ATTRIBUTE: ${{ secrets.AZURE_USERNAME_ATTRIBUTE }} + AZURE_USER_IS_UPN: ${{ secrets.AZURE_USER_IS_UPN }} + AZURE_USE_TRANSITIVE_GROUP_MEMBERS: ${{ secrets.AZURE_USE_TRANSITIVE_GROUP_MEMBERS }} + + # Google Workspace settings (if using Google Workspace) + GOOGLE_WORKSPACE_SA_CREDS_FILE: googleAuth.json + GOOGLE_WORKSPACE_ADMIN_EMAIL: ${{ secrets.GOOGLE_WORKSPACE_ADMIN_EMAIL }} + GOOGLE_WORKSPACE_USERNAME_CUSTOM_SCHEMA_NAME: ${{ secrets.GOOGLE_WORKSPACE_USERNAME_CUSTOM_SCHEMA_NAME }} + GOOGLE_WORKSPACE_USERNAME_FIELD: ${{ secrets.GOOGLE_WORKSPACE_USERNAME_FIELD }} + GOOGLE_WORKSPACE_USER_MAIL_ATTRIBUTE: ${{ secrets.GOOGLE_WORKSPACE_USER_MAIL_ATTRIBUTE }} + + # Okta settings (if using Okta) + OKTA_ORG_URL: ${{ secrets.OKTA_ORG_URL }} + OKTA_USERNAME_ATTRIBUTE: ${{ secrets.OKTA_USERNAME_ATTRIBUTE }} + OKTA_ACCESS_TOKEN: ${{ secrets.OKTA_ACCESS_TOKEN }} + OKTA_AUTH_METHOD: ${{ secrets.OKTA_AUTH_METHOD }} + OKTA_CLIENT_ID: ${{ secrets.OKTA_CLIENT_ID }} + OKTA_SCOPES: ${{ secrets.OKTA_SCOPES }} + OKTA_PRIVATE_KEY: ${{ secrets.OKTA_PRIVATE_KEY }} + + # OneLogin settings (if using OneLogin) + ONELOGIN_CLIENT_ID: ${{ secrets.ONELOGIN_CLIENT_ID }} + ONELOGIN_CLIENT_SECRET: ${{ secrets.ONELOGIN_CLIENT_SECRET }} + REGION: ${{ secrets.REGION }} + + # Keycloak settings (if using Keycloak) + KEYCLOAK_USERNAME: ${{ secrets.KEYCLOAK_USERNAME }} + KEYCLOAK_PASSWORD: ${{ secrets.KEYCLOAK_PASSWORD }} + KEYCLOAK_REALM: ${{ secrets.KEYCLOAK_REALM }} + KEYCLOAK_ADMIN_REALM: ${{ secrets.KEYCLOAK_ADMIN_REALM }} + KEYCLOAK_USE_GITHUB_IDP: ${{ secrets.KEYCLOAK_USE_GITHUB_IDP }} + + # Additional settings + CHANGE_THRESHOLD: ${{ secrets.CHANGE_THRESHOLD }} + OPEN_ISSUE_ON_FAILURE: ${{ secrets.OPEN_ISSUE_ON_FAILURE }} + REPO_FOR_ISSUES: ${{ secrets.REPO_FOR_ISSUES }} + ISSUE_ASSIGNEE: ${{ secrets.ISSUE_ASSIGNEE }} + ADD_MEMBER: ${{ secrets.ADD_MEMBER }} + REMOVE_ORG_MEMBERS_WITHOUT_TEAM: ${{ secrets.REMOVE_ORG_MEMBERS_WITHOUT_TEAM }} + SYNCMAP_ONLY: ${{ secrets.SYNCMAP_ONLY }} + EMU_SHORTCODE: ${{ secrets.EMU_SHORTCODE }} + + # Test mode (use workflow input if provided, otherwise use secret) + TEST_MODE: ${{ github.event.inputs.test_mode || secrets.TEST_MODE || 'false' }} + run: | + pipenv run python app.py + + ########################## + # Clean up secrets # + ########################## + - name: Clean up + if: always() + run: | + rm -f .ssh/team-sync.pem + rm -f googleAuth.json diff --git a/README.md b/README.md index 4faf5b9..42d8024 100644 --- a/README.md +++ b/README.md @@ -270,14 +270,62 @@ The custom map uses slugs that are lowercase. If you don't specify organization ## Usage Examples -### Start the application from Pipenv +### Option 1: Run from GitHub Actions (Recommended) + +This approach runs the sync as a scheduled GitHub Actions workflow, eliminating the need for a persistent server. + +1. **Enable the workflow**: The workflow file is located at `.github/workflows/sync-teams.yml` + +2. **Configure GitHub Secrets**: In your repository settings, add the following secrets: + + **Required secrets:** + - `APP_ID` - Your GitHub App ID + - `GITHUB_APP_PRIVATE_KEY` - Your GitHub App private key (entire PEM file content) + - `WEBHOOK_SECRET` - Webhook secret (can be any value when using Actions) + - `USER_DIRECTORY` - Backend type (e.g., `GOOGLE_WORKSPACE`, `LDAP`, `AAD`, `OKTA`) + + **For Google Workspace:** + - `GOOGLE_WORKSPACE_SA_CREDS` - Service account credentials JSON file content + - `GOOGLE_WORKSPACE_ADMIN_EMAIL` - Admin email for impersonation + - `GOOGLE_WORKSPACE_USERNAME_CUSTOM_SCHEMA_NAME` - Custom schema name (if syncing by username) + - `GOOGLE_WORKSPACE_USERNAME_FIELD` - Custom field name (if syncing by username) + - `USER_SYNC_ATTRIBUTE` - Set to `username` or `email` + + **For LDAP/Active Directory:** + - `LDAP_SERVER_HOST`, `LDAP_SERVER_PORT`, `LDAP_BASE_DN`, etc. (see `.env.example.ldap`) + + **For Azure AD:** + - `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, etc. (see `.env.example.aad`) + + **For Okta:** + - `OKTA_ORG_URL`, `OKTA_ACCESS_TOKEN`, etc. (see `.env.example.okta`) + + **Optional secrets:** + - `GHE_HOST` - GitHub Enterprise hostname (omit for github.com) + - `CHANGE_THRESHOLD` - Maximum number of changes allowed (default: 25) + - `OPEN_ISSUE_ON_FAILURE` - Set to `true` to create issues on failures + - `REPO_FOR_ISSUES` - Repository for failure issues (format: `owner/repo`) + - `ISSUE_ASSIGNEE` - GitHub username to assign issues to + - `TEST_MODE` - Set to `true` to preview changes without applying + - `ADD_MEMBER` - Set to `true` to add users to org if not already members + - `SYNCMAP_YML` - Custom mapping configuration (entire syncmap.yml content) + - `SYNCMAP_ONLY` - Set to `true` to sync only teams in syncmap.yml + +3. **Schedule**: The workflow runs hourly by default. Edit the cron expression in `.github/workflows/sync-teams.yml` to change the schedule. + +4. **Manual trigger**: You can manually trigger the sync from the Actions tab, with an option to enable test mode. + +### Option 2: Run as a webhook server + This example runs the app in a standard Flask environment. ```bash pipenv run flask run --host=0.0.0.0 --port=5000 ``` -Or you can run the app with Python directly. +### Option 3: Run as a one-time script + +Or you can run the app with Python directly for a one-time sync. ```bash pipenv run python app.py From 7b084d6adccdad8156da02e4fd868a40048af310 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 23:46:24 +0000 Subject: [PATCH 3/5] Fix workflow conditional and add syncmap cleanup Co-authored-by: cpritchett <3866278+cpritchett@users.noreply.github.com> --- .github/workflows/sync-teams.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync-teams.yml b/.github/workflows/sync-teams.yml index 6d6bc3a..212a7a1 100644 --- a/.github/workflows/sync-teams.yml +++ b/.github/workflows/sync-teams.yml @@ -76,12 +76,10 @@ jobs: # Create Google Workspace credentials file if needed #################################### - name: Create Google Workspace credentials file - if: env.USER_DIRECTORY == 'GOOGLE_WORKSPACE' && secrets.GOOGLE_WORKSPACE_SA_CREDS != '' + if: secrets.USER_DIRECTORY == 'GOOGLE_WORKSPACE' && secrets.GOOGLE_WORKSPACE_SA_CREDS != '' run: | echo "${{ secrets.GOOGLE_WORKSPACE_SA_CREDS }}" > googleAuth.json chmod 600 googleAuth.json - env: - USER_DIRECTORY: ${{ secrets.USER_DIRECTORY }} #################################### # Create syncmap.yml if configured @@ -183,3 +181,4 @@ jobs: run: | rm -f .ssh/team-sync.pem rm -f googleAuth.json + rm -f syncmap.yml From a0a43d25263934dc301618f38169b1067ac40917 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 23:47:33 +0000 Subject: [PATCH 4/5] Add explicit permissions to workflow for security Co-authored-by: cpritchett <3866278+cpritchett@users.noreply.github.com> --- .github/workflows/sync-teams.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/sync-teams.yml b/.github/workflows/sync-teams.yml index 212a7a1..0ddf391 100644 --- a/.github/workflows/sync-teams.yml +++ b/.github/workflows/sync-teams.yml @@ -39,6 +39,10 @@ jobs: name: Sync Teams runs-on: ubuntu-latest + # Limit permissions for security + permissions: + contents: read + steps: ########################## # Checkout the code base # From c0340254d2881e25162e0d86ee41210b785dd078 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 16 Nov 2025 13:39:53 +0000 Subject: [PATCH 5/5] Reorganize README to focus on GitHub Actions with Google Workspace, move detailed docs to docs/ folder Co-authored-by: cpritchett <3866278+cpritchett@users.noreply.github.com> --- README.md | 393 +++++++++------------------------ docs/additional-settings.md | 81 +++++++ docs/alternative-deployment.md | 106 +++++++++ docs/github-app-setup.md | 40 ++++ docs/google-workspace.md | 76 +++++++ docs/other-backends.md | 135 +++++++++++ 6 files changed, 541 insertions(+), 290 deletions(-) create mode 100644 docs/additional-settings.md create mode 100644 docs/alternative-deployment.md create mode 100644 docs/github-app-setup.md create mode 100644 docs/google-workspace.md create mode 100644 docs/other-backends.md diff --git a/README.md b/README.md index 42d8024..ede53d2 100644 --- a/README.md +++ b/README.md @@ -1,335 +1,148 @@ # GitHub Team Sync -This utility is intended to enable synchronization between GitHub and various LDAP and SAML providers. -This is particularly useful for large organizations with many teams that either use GitHub Enterprise Cloud, -do not use LDAP for authentication, or use a SAML provider other than what is natively supported. -It supports both GitHub.com, GitHub Enterprise Server (GHES) and GitHub, but it will need to live in a location that can access your LDAP servers. - -## Supported user directories -- LDAP -- Active Directory -- Azure AD -- Okta -- OneLogin -- Google Workspace -- Keycloak + +Automatically synchronize GitHub teams with Google Workspace groups using GitHub Actions. This utility eliminates the need for a persistent webhook server by running as a scheduled workflow. ## Features -This utility provides the following functionality: - -| Feature | Supported | Description | -| --- | --- | --- | -| Sync Users | Yes | Add or remove users from `Teams` in GitHub to keep in sync with Active Directory groups | -| Dynamic Config | Yes | Utilize a `settings` file to derive Active Directory and GitHub settings | -| LDAP SSL | Yes | SSL or TLS connections. | -| Failure notifications | Yes | Presently supports opening a GitHub issue when sync failed. The repo is configurable. | -| Sync on new team | Yes | Synchronize users when a new team is created | -| Sync on team edit | No | This event is not processed currently | -| Custom team/group maps | Yes | The team `slug` and group name will be matched automatically, unless you define a custom mapping with `syncmap.yml` | -| Force custom map | Yes | Sync only team defined in `syncmap.yml` | -| Dry run / Test mode | Yes | Run and print the differences, but make no changes | -| Nested teams/groups | No | Synchronize groups within groups. Presently, if a group is a member of another group, it is skipped | - -## Creating the GitHub App on your GitHub instance -1. On your GitHub instance, visit the `settings` page on the organization that you want to own the **GitHub** App, and navigate to the `GitHub Apps` section. - - You can access this page by visiting the following url: - `https:///organizations//settings/apps` -2. Create a new **GitHub App** with the following settings: - - **Webhook URL**: URL of the machine on which this app has been deployed (Example: `http://ip.of.machine:3000`) - - **Homepage URL**: URL of the machine on which this app has been deployed (Example: `http://ip.of.machine:3000`) - - **Webhook Secret**: The webhook secret that will be or has been defined as an environment variable in your deployment environment as `WEBHOOK_SECRET` - - **Permissions and Events**: This application will need to be able to manage teams on GitHub, so the `events` and `permissions` listed below will be required. For more information on how to create a GitHub App, please visit [https://developer.github.com/apps/building-github-apps/creating-a-github-app](https://developer.github.com/apps/building-github-apps/creating-a-github-app) -3. Once these have been configured, select the `Create GitHub App` button at the bottom of the page to continue -4. Make a note of the `APP ID` on your newly-created **GitHub App**. You will need to set this as an environment variable when configuring the app. -5. Generate and download a private key from the new App page, and store it in your deployment environment. You can either do this by saving the file directly in the environment and specifying its path with the environment variable `PRIVATE_KEY_PATH` -6. After you have created the **GitHub** App, you will need to install it to the desired **GitHub** Organizations. - - Select `Install App` - - Select `All Repositories` or the desired repositories you wish to watch - -### Permissions and Events - -#### Permissions - -| Category | Attribute | Permission | -| --- | --- | --- | -| Repository permissions | `Issues` | `Read & write` | -| Repository permissions | `Metadata` | `Read-only` | -| Organization permissions | `Members` | `Read & write` | -| User permissions | `Email addresses` | `Read-only` | - -#### Events - -| Event | Required? | Description | -| --- | --- | --- | -| `Team` | Optional | Trigger when a new team is `created`, `deleted`, `edited`, `renamed`, etc. | - -#### Azure AD Permissions -**Authentication methods** -- [ ] Username/Password -- [x] Service Principal -- [ ] Certificate -- [ ] Device Auth - -This app requires the following Azure permissions: - -- `GroupMember.Read.All` -- `User.Read.All` - -#### Keycloak Permissions -If you have `ADMIN_FINE_GRAINED_AUTHZ` enabled, you only need the following permission for the user realm: -- `view-users` - -#### Google Workspace Permissions -You must delegate domain-wide authority to the service account with the following scopes: -- `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` -You must provide a Google Workspace Admin account for the service account to impersonate. -It must have Admin API permissions greater or equal to the scopes listed above. +- **Automated Sync**: Scheduled synchronization between GitHub teams and Google Workspace groups +- **No Server Required**: Runs entirely within GitHub Actions +- **Test Mode**: Preview changes before applying them +- **Custom Mappings**: Define custom team-to-group mappings +- **Change Threshold**: Safety limit to prevent accidental bulk changes +- **Failure Notifications**: Automatic issue creation on sync failures -## Getting Started -To get started, ensure that you are using **Python 3.9** (or update your `Pipfile` to the version you're running, 3.4+). The following additional libraries are required: +## Quick Start (Google Workspace) -- [ ] Flask -- [ ] github3.py -- [ ] python-ldap3 -- [ ] APScheduler -- [ ] python-dotenv -- [ ] PyYAML -- [ ] msal -- [ ] asyncio -- [ ] okta -- [ ] onelogin -- [ ] python-keycloak +### Prerequisites -Install the required libraries. +1. **GitHub App**: Create a GitHub App with team management permissions ([detailed guide](docs/github-app-setup.md)) +2. **Google Workspace Service Account**: Configure with domain-wide delegation ([detailed guide](docs/google-workspace.md)) -```bash -pipenv install -``` -Once you have all of the requirements installed, be sure to edit the `.env` to match your environment. +### Google Workspace Permissions -### Sample `.env` for GitHub App settings -```env -## GitHub App settings -WEBHOOK_SECRET=development -APP_ID=12345 -PRIVATE_KEY_PATH=.ssh/team-sync.pem -GHE_HOST=github.example.com -``` +Your Google Workspace service account needs: +- `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` -### Sample `.env` for choosing your backend -```env -## AzureAD = AAD -## AD/LDAP = LDAP -## Okta = OKTA -## OneLogin = ONELOGIN -## Google Workspace = GOOGLE_WORKSPACE -USER_DIRECTORY=LDAP +### Configuration -## Sync users on username or email attribute -USER_SYNC_ATTRIBUTE=username +1. **Fork this repository** or enable GitHub Actions in your repository -``` +2. **Configure GitHub Secrets** in your repository settings (`Settings` → `Secrets and variables` → `Actions`): -### Sample `.env` for Active Directory - -```env -LDAP_SERVER_HOST=dc1.example.com -LDAP_SERVER_PORT=389 -LDAP_BASE_DN="DC=example,DC=com" -LDAP_USER_BASE_DN="CN=Users,DC=example,DC=example" -LDAP_GROUP_BASE_DN="OU=Groups,DC=example,DC=example" -LDAP_USER_FILTER="(objectClass=person)" -LDAP_USER_ATTRIBUTE=sAMAccountName -LDAP_USER_MAIL_ATTRIBUTE=mail -LDAP_GROUP_FILTER="(&(objectClass=group)(cn={group_name}))" -LDAP_GROUP_MEMBER_ATTRIBUTE=member -LDAP_BIND_USER="bind-user@example.com" -LDAP_BIND_PASSWORD="p4$$w0rd" -LDAP_SEARCH_PAGE_SIZE=1000 -``` + **Required:** + - `APP_ID` - Your GitHub App ID + - `GITHUB_APP_PRIVATE_KEY` - Your GitHub App private key (entire PEM file) + - `WEBHOOK_SECRET` - Any random string (not used in Actions but required) + - `USER_DIRECTORY` - Set to `GOOGLE_WORKSPACE` + - `GOOGLE_WORKSPACE_SA_CREDS` - Service account credentials (entire JSON file) + - `GOOGLE_WORKSPACE_ADMIN_EMAIL` - Admin email for impersonation (e.g., `admin@example.com`) + - `USER_SYNC_ATTRIBUTE` - Set to `username` or `email` -### Sample `.env` for OpenLDAP -```env -LDAP_SERVER_HOST=dc1.example.com -LDAP_SERVER_PORT=389 -LDAP_BASE_DN="dc=example,dc=com" -LDAP_USER_BASE_DN="ou=People,dc=example,dc=com" -LDAP_GROUP_BASE_DN="ou=Groups,dc=example,dc=com" -LDAP_USER_FILTER="(&(objectClass=person)({ldap_user_attribute}={username}))" -LDAP_USER_ATTRIBUTE=uid -LDAP_USER_MAIL_ATTRIBUTE=mail -LDAP_GROUP_FILTER="(&(objectClass=posixGroup)(cn={group_name}))" -LDAP_GROUP_MEMBER_ATTRIBUTE=memberUid -LDAP_BIND_USER="cn=admin,dc=example,dc=com" -LDAP_BIND_PASSWORD="p4$$w0rd" -LDAP_SEARCH_PAGE_SIZE=1000 -``` + **If syncing by username (requires custom schema in Google Workspace):** + - `GOOGLE_WORKSPACE_USERNAME_CUSTOM_SCHEMA_NAME` - Your custom schema name + - `GOOGLE_WORKSPACE_USERNAME_FIELD` - Your custom field name -### Sample `.env` for AzureAD -```env -AZURE_TENANT_ID="" -AZURE_CLIENT_ID="" -AZURE_CLIENT_SECRET="" -AZURE_APP_SCOPE="default" -AZURE_API_ENDPOINT="https://graph.microsoft.com/v1.0" -# can also be an extensionAttribute -AZURE_USERNAME_ATTRIBUTE=userPrincipalName -AZURE_USER_IS_UPN=true -# use transitive members of a group instead of direct members -AZURE_USE_TRANSITIVE_GROUP_MEMBERS=false -``` + **Optional:** + - `GHE_HOST` - GitHub Enterprise hostname (omit for GitHub.com) + - `CHANGE_THRESHOLD` - Max changes allowed per sync (default: `25`) + - `TEST_MODE` - Set to `true` to preview without applying changes + - `OPEN_ISSUE_ON_FAILURE` - Set to `true` to create issues on failures + - `REPO_FOR_ISSUES` - Where to create issues (format: `owner/repo`) + - `ISSUE_ASSIGNEE` - GitHub username to assign issues to + - `SYNCMAP_YML` - Custom mapping file content (optional) -### Sample `.env` for Okta -```env -OKTA_ORG_URL=https://example.okta.com -OKTA_USERNAME_ATTRIBUTE=github_username +3. **Enable the workflow**: The workflow (`.github/workflows/sync-teams.yml`) runs hourly by default. You can: + - Modify the schedule in the workflow file + - Manually trigger from the Actions tab + - Use test mode via manual trigger to preview changes -# token login -OKTA_ACCESS_TOKEN=asdfghkjliptojkjsj00294759 +### Manual Trigger -# OAuth login -OKTA_AUTH_METHOD=oauth -OKTA_CLIENT_ID=abcdefghijkl -OKTA_SCOPES='okta.users.read okta.groups.read' -OKTA_PRIVATE_KEY='{"kty": "RSA", ...}' -``` +1. Go to the **Actions** tab in your repository +2. Select **Sync GitHub Teams** workflow +3. Click **Run workflow** +4. Choose whether to run in test mode +5. Click **Run workflow** -### Sample `.env` for Keycloak -```env -KEYCLOAK_USERNAME=api-account -KEYCLOAK_PASSWORD=ExamplePassword -KEYCLOAK_REALM=ExampleCorp -KEYCLOAK_ADMIN_REALM=master -KEYCLOAK_USE_GITHUB_IDP=true -``` +## How It Works -### Sample `.env` for OneLogin -```env -ONELOGIN_CLIENT_ID='asdafsflkjlk13q33433445wee' -ONELOGIN_CLIENT_SECRET='ca3a86f982fjjkjjkfkhls' -REGION=US -``` +The workflow: +1. Checks out the repository +2. Sets up Python and installs dependencies +3. Creates credential files from secrets +4. Runs the sync script (`pipenv run python app.py`) +5. Cleans up sensitive files -### Sample `.env` for Google Workspace -```env -GOOGLE_WORKSPACE_SA_CREDS_FILE=googleAuth.json -GOOGLE_WORKSPACE_ADMIN_EMAIL=admin@example.com -GOOGLE_WORKSPACE_USERNAME_CUSTOM_SCHEMA_NAME=schema-name -GOOGLE_WORKSPACE_USERNAME_FIELD=field-name -``` +Team synchronization: +- Matches GitHub team slugs to Google Workspace group names +- Adds users from Google groups to corresponding GitHub teams +- Removes users from GitHub teams if they're not in the Google group +- Respects the change threshold to prevent accidental bulk changes -### Sample `.env` settings for additional settings -```env -## Additional settings -CHANGE_THRESHOLD=25 -OPEN_ISSUE_ON_FAILURE=true -REPO_FOR_ISSUES=github-demo/demo-repo -ISSUE_ASSIGNEE=githubber -SYNC_SCHEDULE=0 * * * * -TEST_MODE=false -SYNCMAP_ONLY=false -EMU_SHORTCODE=volcano - -### Automatically add users missing from the organization -ADD_MEMBER=false -## Automatically remove users from the organization that are not part of a team -REMOVE_ORG_MEMBERS_WITHOUT_TEAM=false -``` +## Custom Team Mappings -### Sample `.env` setting for flask app -```env -#################### -## Flask Settings ## -#################### -## Default: app, comment out to run once as a script -FLASK_APP=app -## Default: production -FLASK_ENV=development -## Default: 5000 -FLASK_RUN_PORT=5000 -## Default: 127.0.0.1 -FLASK_RUN_HOST=0.0.0.0 +By default, teams are matched by name (e.g., GitHub team `engineering` ↔ Google group `engineering`). -``` +To customize mappings, add the `SYNCMAP_YML` secret with content like: -### Sample `syncmap.yml` custom mapping file ```yaml ---- mapping: - - github: demo-team - directory: ldap super users - org: my github org - - github: demo-admin-2 - directory: some other group + - github: dev-team + directory: engineering-all-staff + org: my-org-name + - github: sre-team + directory: site-reliability-engineers ``` -The custom map uses slugs that are lowercase. If you don't specify organization name, it will synchronize all teams with same name in any organization. +See [Additional Settings](docs/additional-settings.md) for more options. -## Usage Examples +## Alternative User Directories -### Option 1: Run from GitHub Actions (Recommended) +This tool also supports: +- **LDAP / Active Directory** +- **Azure AD** +- **Okta** +- **OneLogin** +- **Keycloak** -This approach runs the sync as a scheduled GitHub Actions workflow, eliminating the need for a persistent server. +See [Alternative Backends](docs/other-backends.md) for configuration details. -1. **Enable the workflow**: The workflow file is located at `.github/workflows/sync-teams.yml` +## Alternative Deployment Methods -2. **Configure GitHub Secrets**: In your repository settings, add the following secrets: +While GitHub Actions is recommended, you can also deploy as: +- **Webhook Server**: Persistent Flask app responding to GitHub webhooks +- **One-Time Script**: Manual sync execution +- **Docker Container**: Containerized deployment - **Required secrets:** - - `APP_ID` - Your GitHub App ID - - `GITHUB_APP_PRIVATE_KEY` - Your GitHub App private key (entire PEM file content) - - `WEBHOOK_SECRET` - Webhook secret (can be any value when using Actions) - - `USER_DIRECTORY` - Backend type (e.g., `GOOGLE_WORKSPACE`, `LDAP`, `AAD`, `OKTA`) - - **For Google Workspace:** - - `GOOGLE_WORKSPACE_SA_CREDS` - Service account credentials JSON file content - - `GOOGLE_WORKSPACE_ADMIN_EMAIL` - Admin email for impersonation - - `GOOGLE_WORKSPACE_USERNAME_CUSTOM_SCHEMA_NAME` - Custom schema name (if syncing by username) - - `GOOGLE_WORKSPACE_USERNAME_FIELD` - Custom field name (if syncing by username) - - `USER_SYNC_ATTRIBUTE` - Set to `username` or `email` +See [Alternative Deployment](docs/alternative-deployment.md) for details. - **For LDAP/Active Directory:** - - `LDAP_SERVER_HOST`, `LDAP_SERVER_PORT`, `LDAP_BASE_DN`, etc. (see `.env.example.ldap`) +## Documentation - **For Azure AD:** - - `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, etc. (see `.env.example.aad`) +- [GitHub App Setup](docs/github-app-setup.md) - Creating and configuring the GitHub App +- [Google Workspace Configuration](docs/google-workspace.md) - Detailed Google Workspace setup +- [Alternative Backends](docs/other-backends.md) - LDAP, Azure AD, Okta, OneLogin, Keycloak +- [Additional Settings](docs/additional-settings.md) - Advanced configuration options +- [Alternative Deployment](docs/alternative-deployment.md) - Webhook server and script modes - **For Okta:** - - `OKTA_ORG_URL`, `OKTA_ACCESS_TOKEN`, etc. (see `.env.example.okta`) +## Troubleshooting - **Optional secrets:** - - `GHE_HOST` - GitHub Enterprise hostname (omit for github.com) - - `CHANGE_THRESHOLD` - Maximum number of changes allowed (default: 25) - - `OPEN_ISSUE_ON_FAILURE` - Set to `true` to create issues on failures - - `REPO_FOR_ISSUES` - Repository for failure issues (format: `owner/repo`) - - `ISSUE_ASSIGNEE` - GitHub username to assign issues to - - `TEST_MODE` - Set to `true` to preview changes without applying - - `ADD_MEMBER` - Set to `true` to add users to org if not already members - - `SYNCMAP_YML` - Custom mapping configuration (entire syncmap.yml content) - - `SYNCMAP_ONLY` - Set to `true` to sync only teams in syncmap.yml +**Workflow doesn't run:** +- Check that the workflow file exists in `.github/workflows/sync-teams.yml` +- Verify GitHub Actions is enabled for your repository +- Check the Actions tab for any errors -3. **Schedule**: The workflow runs hourly by default. Edit the cron expression in `.github/workflows/sync-teams.yml` to change the schedule. +**Sync fails:** +- Enable `TEST_MODE=true` to see what would change without applying +- Check that all required secrets are configured +- Verify Google Workspace service account has correct permissions +- Review workflow logs in the Actions tab -4. **Manual trigger**: You can manually trigger the sync from the Actions tab, with an option to enable test mode. - -### Option 2: Run as a webhook server - -This example runs the app in a standard Flask environment. - -```bash -pipenv run flask run --host=0.0.0.0 --port=5000 -``` - -### Option 3: Run as a one-time script - -Or you can run the app with Python directly for a one-time sync. - -```bash -pipenv run python app.py -``` +**Too many changes detected:** +- Increase `CHANGE_THRESHOLD` if the changes are legitimate +- Use `TEST_MODE=true` to investigate before applying ## Support diff --git a/docs/additional-settings.md b/docs/additional-settings.md new file mode 100644 index 0000000..9511ee2 --- /dev/null +++ b/docs/additional-settings.md @@ -0,0 +1,81 @@ +# Additional Settings + +These settings apply to all user directory backends. + +## Common Settings + +### Change Threshold +```env +CHANGE_THRESHOLD=25 +``` +Stop if number of changes exceeds this number. Default: 25 + +### Failure Notifications +```env +OPEN_ISSUE_ON_FAILURE=true +REPO_FOR_ISSUES=github-demo/demo-repo +ISSUE_ASSIGNEE=githubber +``` +Create an issue if the sync fails for any reason. + +### Sync Schedule +```env +SYNC_SCHEDULE=0 * * * * +``` +Sync schedule, cron style schedule. Default (hourly): `0 * * * *` + +Note: When using GitHub Actions, the schedule is controlled by the workflow file, not this environment variable. + +### Test Mode +```env +TEST_MODE=false +``` +Show the changes, but do not make any changes. Default: false + +### Organization Membership +```env +ADD_MEMBER=false +REMOVE_ORG_MEMBERS_WITHOUT_TEAM=false +``` +- `ADD_MEMBER`: Automatically add users missing from the organization +- `REMOVE_ORG_MEMBERS_WITHOUT_TEAM`: Automatically remove users from the organization that are not part of a team + +### Custom Team Mapping +```env +SYNCMAP_ONLY=false +``` +Set to `true` to sync only teams defined in `syncmap.yml`. + +### EMU Shortcode +```env +EMU_SHORTCODE=volcano +``` +Enterprise Managed Users shortcode (if applicable). + +## Custom Team/Group Mapping + +Create a `syncmap.yml` file to define custom mappings between GitHub teams and directory groups: + +```yaml +--- +mapping: + - github: demo-team + directory: ldap super users + org: my github org + - github: demo-admin-2 + directory: some other group + +# Only sync groups with matching prefixes (optional) +#group_prefix: +# - TEST- +# - DEMO- + +# Ignore specific users (optional) +ignore_users: + - userA + - userB +``` + +The custom map uses slugs that are lowercase. If you don't specify organization name, it will synchronize all teams with same name in any organization. + +When using GitHub Actions, you can provide the entire contents of `syncmap.yml` as the `SYNCMAP_YML` secret. diff --git a/docs/alternative-deployment.md b/docs/alternative-deployment.md new file mode 100644 index 0000000..328e455 --- /dev/null +++ b/docs/alternative-deployment.md @@ -0,0 +1,106 @@ +# Alternative Deployment Methods + +While GitHub Actions is the recommended deployment method, this application can also be deployed as a webhook server or run as a one-time script. + +## Option 1: Webhook Server + +This approach runs the app as a persistent Flask web server that responds to GitHub webhooks and runs scheduled syncs. + +### Setup + +1. Follow the [GitHub App setup guide](github-app-setup.md) +2. Create a `.env` file with your configuration (see backend-specific documentation) +3. Set Flask environment variables: + +```env +#################### +## Flask Settings ## +#################### +FLASK_APP=app +FLASK_ENV=production +FLASK_RUN_PORT=5000 +FLASK_RUN_HOST=0.0.0.0 +``` + +### Running + +Install dependencies: +```bash +pipenv install +``` + +Start the server: +```bash +pipenv run flask run --host=0.0.0.0 --port=5000 +``` + +### Requirements + +- Server must be accessible by GitHub webhooks +- Server must be kept running continuously +- Webhook URL configured in GitHub App settings + +## Option 2: One-Time Script + +Run the sync once and exit. Useful for testing or manual syncs. + +### Setup + +1. Create a `.env` file with your configuration +2. Do NOT set `FLASK_APP` environment variable + +### Running + +```bash +pipenv run python app.py +``` + +This will: +- Execute a full sync across all installed organizations +- Exit when complete +- Not start a web server +- Not respond to webhooks + +### Use Cases + +- Testing configuration +- Manual sync outside of schedule +- Debugging issues +- Running from cron jobs + +## Docker Deployment + +A Dockerfile is provided for containerized deployments. + +### Building + +```bash +docker build -t github-team-sync . +``` + +### Running + +```bash +docker run -d \ + --env-file .env \ + -p 5000:5000 \ + github-team-sync +``` + +## Dependencies + +All deployment methods require Python 3.9 and the following libraries (installed via pipenv): + +- Flask +- github3.py +- python-ldap3 +- APScheduler +- python-dotenv +- PyYAML +- msal +- asyncio +- okta +- onelogin +- python-keycloak +- google-api-python-client +- google-auth-oauthlib diff --git a/docs/github-app-setup.md b/docs/github-app-setup.md new file mode 100644 index 0000000..18414c1 --- /dev/null +++ b/docs/github-app-setup.md @@ -0,0 +1,40 @@ +# Creating the GitHub App + +## Setup Instructions + +1. On your GitHub instance, visit the `settings` page on the organization that you want to own the **GitHub** App, and navigate to the `GitHub Apps` section. + - You can access this page by visiting the following url: + `https:///organizations//settings/apps` + +2. Create a new **GitHub App** with the following settings: + - **Webhook URL**: URL of the machine on which this app has been deployed (Example: `http://ip.of.machine:3000`) - Not required for GitHub Actions deployment + - **Homepage URL**: URL of the machine on which this app has been deployed (Example: `http://ip.of.machine:3000`) + - **Webhook Secret**: The webhook secret that will be or has been defined as an environment variable in your deployment environment as `WEBHOOK_SECRET` + - **Permissions and Events**: This application will need to be able to manage teams on GitHub, so the `events` and `permissions` listed below will be required. For more information on how to create a GitHub App, please visit [https://developer.github.com/apps/building-github-apps/creating-a-github-app](https://developer.github.com/apps/building-github-apps/creating-a-github-app) + +3. Once these have been configured, select the `Create GitHub App` button at the bottom of the page to continue + +4. Make a note of the `APP ID` on your newly-created **GitHub App**. You will need to set this as an environment variable when configuring the app. + +5. Generate and download a private key from the new App page, and store it in your deployment environment. You can either do this by saving the file directly in the environment and specifying its path with the environment variable `PRIVATE_KEY_PATH` + +6. After you have created the **GitHub** App, you will need to install it to the desired **GitHub** Organizations. + - Select `Install App` + - Select `All Repositories` or the desired repositories you wish to watch + +## Permissions and Events + +### Permissions + +| Category | Attribute | Permission | +| --- | --- | --- | +| Repository permissions | `Issues` | `Read & write` | +| Repository permissions | `Metadata` | `Read-only` | +| Organization permissions | `Members` | `Read & write` | +| User permissions | `Email addresses` | `Read-only` | + +### Events + +| Event | Required? | Description | +| --- | --- | --- | +| `Team` | Optional | Trigger when a new team is `created`, `deleted`, `edited`, `renamed`, etc. | diff --git a/docs/google-workspace.md b/docs/google-workspace.md new file mode 100644 index 0000000..70a13dc --- /dev/null +++ b/docs/google-workspace.md @@ -0,0 +1,76 @@ +# Google Workspace Configuration + +## Prerequisites + +You must delegate domain-wide authority to the service account with the following scopes: +- `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` + +You must provide a Google Workspace Admin account for the service account to impersonate. +It must have Admin API permissions greater or equal to the scopes listed above. + +## Required Secrets + +When using GitHub Actions, configure these secrets in your repository settings: + +### GitHub App Credentials +- `APP_ID` - Your GitHub App ID +- `GITHUB_APP_PRIVATE_KEY` - Your GitHub App private key (entire PEM file content) +- `WEBHOOK_SECRET` - Webhook secret (can be any value when using Actions) + +### Google Workspace Credentials +- `USER_DIRECTORY` - Set to `GOOGLE_WORKSPACE` +- `GOOGLE_WORKSPACE_SA_CREDS` - Service account credentials JSON file content +- `GOOGLE_WORKSPACE_ADMIN_EMAIL` - Admin email for impersonation +- `USER_SYNC_ATTRIBUTE` - Set to `username` or `email` + +### If syncing by username (custom schema) +- `GOOGLE_WORKSPACE_USERNAME_CUSTOM_SCHEMA_NAME` - Custom schema name +- `GOOGLE_WORKSPACE_USERNAME_FIELD` - Custom field name + +### If syncing by email (default) +- `GOOGLE_WORKSPACE_USER_MAIL_ATTRIBUTE` - Email attribute (default: `primaryEmail`) + +## Optional Secrets + +- `GHE_HOST` - GitHub Enterprise hostname (omit for github.com) +- `CHANGE_THRESHOLD` - Maximum number of changes allowed (default: 25) +- `OPEN_ISSUE_ON_FAILURE` - Set to `true` to create issues on failures +- `REPO_FOR_ISSUES` - Repository for failure issues (format: `owner/repo`) +- `ISSUE_ASSIGNEE` - GitHub username to assign issues to +- `TEST_MODE` - Set to `true` to preview changes without applying +- `ADD_MEMBER` - Set to `true` to add users to org if not already members +- `SYNCMAP_YML` - Custom mapping configuration (entire syncmap.yml content) +- `SYNCMAP_ONLY` - Set to `true` to sync only teams in syncmap.yml + +## Sample `.env` for Local Development + +```env +######################### +## GitHub App Settings ## +######################### +WEBHOOK_SECRET=development +APP_ID=12345 +PRIVATE_KEY_PATH=.ssh/team-sync.pem +# Uncomment for GitHub Enterprise +#GHE_HOST=github.example.com + +############################### +## Google Workspace Settings ## +############################### +USER_DIRECTORY=GOOGLE_WORKSPACE +USER_SYNC_ATTRIBUTE=username + +GOOGLE_WORKSPACE_SA_CREDS_FILE=googleAuth.json +GOOGLE_WORKSPACE_ADMIN_EMAIL=admin@example.com +GOOGLE_WORKSPACE_USERNAME_CUSTOM_SCHEMA_NAME=schema-name +GOOGLE_WORKSPACE_USERNAME_FIELD=field-name + +######################### +## Additional Settings ## +######################### +CHANGE_THRESHOLD=25 +TEST_MODE=false +ADD_MEMBER=false +``` diff --git a/docs/other-backends.md b/docs/other-backends.md new file mode 100644 index 0000000..d582077 --- /dev/null +++ b/docs/other-backends.md @@ -0,0 +1,135 @@ +# Alternative User Directory Backends + +This application supports multiple user directory backends in addition to Google Workspace. + +## Azure AD + +### Required Permissions +**Authentication methods** +- [x] Service Principal + +This app requires the following Azure permissions: +- `GroupMember.Read.All` +- `User.Read.All` + +### Required Secrets +- `USER_DIRECTORY` - Set to `AAD` +- `AZURE_TENANT_ID` - Azure tenant ID +- `AZURE_CLIENT_ID` - Azure client ID +- `AZURE_CLIENT_SECRET` - Azure client secret +- `AZURE_APP_SCOPE` - Application scope (default: `default`) +- `AZURE_API_ENDPOINT` - API endpoint (default: `https://graph.microsoft.com/v1.0`) +- `AZURE_USERNAME_ATTRIBUTE` - Username attribute (default: `userPrincipalName`) +- `AZURE_USER_IS_UPN` - Set to `true` if username is UPN +- `AZURE_USE_TRANSITIVE_GROUP_MEMBERS` - Set to `true` to use transitive members + +### Sample `.env` +```env +USER_DIRECTORY=AAD +USER_SYNC_ATTRIBUTE=username + +AZURE_TENANT_ID="" +AZURE_CLIENT_ID="" +AZURE_CLIENT_SECRET="" +AZURE_APP_SCOPE="default" +AZURE_API_ENDPOINT="https://graph.microsoft.com/v1.0" +AZURE_USERNAME_ATTRIBUTE=userPrincipalName +AZURE_USER_IS_UPN=true +AZURE_USE_TRANSITIVE_GROUP_MEMBERS=false +``` + +## LDAP / Active Directory + +### Sample `.env` for Active Directory +```env +USER_DIRECTORY=LDAP +USER_SYNC_ATTRIBUTE=username + +LDAP_SERVER_HOST=dc1.example.com +LDAP_SERVER_PORT=389 +LDAP_BASE_DN="DC=example,DC=com" +LDAP_USER_BASE_DN="CN=Users,DC=example,DC=example" +LDAP_GROUP_BASE_DN="OU=Groups,DC=example,DC=example" +LDAP_USER_FILTER="(objectClass=person)" +LDAP_USER_ATTRIBUTE=sAMAccountName +LDAP_USER_MAIL_ATTRIBUTE=mail +LDAP_GROUP_FILTER="(&(objectClass=group)(cn={group_name}))" +LDAP_GROUP_MEMBER_ATTRIBUTE=member +LDAP_BIND_USER="bind-user@example.com" +LDAP_BIND_PASSWORD="p4$$w0rd" +LDAP_SEARCH_PAGE_SIZE=1000 +``` + +### Sample `.env` for OpenLDAP +```env +USER_DIRECTORY=LDAP +USER_SYNC_ATTRIBUTE=username + +LDAP_SERVER_HOST=dc1.example.com +LDAP_SERVER_PORT=389 +LDAP_BASE_DN="dc=example,dc=com" +LDAP_USER_BASE_DN="ou=People,dc=example,dc=com" +LDAP_GROUP_BASE_DN="ou=Groups,dc=example,dc=com" +LDAP_USER_FILTER="(&(objectClass=person)({ldap_user_attribute}={username}))" +LDAP_USER_ATTRIBUTE=uid +LDAP_USER_MAIL_ATTRIBUTE=mail +LDAP_GROUP_FILTER="(&(objectClass=posixGroup)(cn={group_name}))" +LDAP_GROUP_MEMBER_ATTRIBUTE=memberUid +LDAP_BIND_USER="cn=admin,dc=example,dc=com" +LDAP_BIND_PASSWORD="p4$$w0rd" +LDAP_SEARCH_PAGE_SIZE=1000 +``` + +## Okta + +### Sample `.env` +```env +USER_DIRECTORY=OKTA +USER_SYNC_ATTRIBUTE=username + +OKTA_ORG_URL=https://example.okta.com +OKTA_USERNAME_ATTRIBUTE=github_username + +# Token login +OKTA_ACCESS_TOKEN=asdfghkjliptojkjsj00294759 + +# OR OAuth login +OKTA_AUTH_METHOD=oauth +OKTA_CLIENT_ID=abcdefghijkl +OKTA_SCOPES='okta.users.read okta.groups.read' +OKTA_PRIVATE_KEY='{"kty": "RSA", ...}' +``` + +## OneLogin + +### Sample `.env` +```env +USER_DIRECTORY=ONELOGIN +USER_SYNC_ATTRIBUTE=username + +ONELOGIN_CLIENT_ID='asdafsflkjlk13q33433445wee' +ONELOGIN_CLIENT_SECRET='ca3a86f982fjjkjjkfkhls' +REGION=US +``` + +## Keycloak + +### Required Permissions +If you have `ADMIN_FINE_GRAINED_AUTHZ` enabled, you only need the following permission for the user realm: +- `view-users` + +### Sample `.env` +```env +USER_DIRECTORY=KEYCLOAK +USER_SYNC_ATTRIBUTE=username + +KEYCLOAK_USERNAME=api-account +KEYCLOAK_PASSWORD=ExamplePassword +KEYCLOAK_REALM=ExampleCorp +KEYCLOAK_ADMIN_REALM=master +KEYCLOAK_USE_GITHUB_IDP=true +``` + +## Additional Configuration + +For all backends, see [Additional Settings](additional-settings.md) for common configuration options.