UNPKG

scimgateway

Version:

Using SCIM protocol as a gateway for user provisioning to other endpoints

1,105 lines (859 loc) 130 kB
# SCIM Gateway [![Build Status](https://app.travis-ci.com/jelhub/scimgateway.svg?branch=master)](https://app.travis-ci.com/github/jelhub/scimgateway) [![npm Version](https://img.shields.io/npm/v/scimgateway.svg?style=flat-square&label=latest)](https://www.npmjs.com/package/scimgateway)[![npm Downloads](https://img.shields.io/npm/dm/scimgateway.svg?style=flat-square)](https://www.npmjs.com/package/scimgateway) [![chat disqus](https://jelhub.github.io/images/chat.svg)](https://elshaug.xyz/docs/scimgateway#disqus_thread) [![GitHub forks](https://img.shields.io/github/forks/jelhub/scimgateway.svg?style=social&label=Fork)](https://github.com/jelhub/scimgateway) --- Author: Jarle Elshaug Validated through IdP's: - Symantec/Broadcom Identity Manager - Microsoft Entra ID - One Identity Manager/OneLogin - Okta - Omada - SailPoint/IdentityNow Latest news: - Entra ID [Federated Identity Credentials](https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0) is now supported. Identity federation allows SCIM Gateway to access Microsoft Entra protected resources without needing to manage secrets - External JWKS (JSON Web Key Set) is now supported by JWT Authentication. These are public and typically frequent rotated by modern identity providers - [Azure Relay](https://learn.microsoft.com/en-us/azure/azure-relay/relay-what-is-it) is now supported for secure and hassle-free outbound communication — with just one minute of configuration - [ETag](https://datatracker.ietf.org/doc/html/rfc7644#section-3.14) is now supported - [Bulk Operations](https://datatracker.ietf.org/doc/html/rfc7644#section-3.7) is now supported - Remote real-time log subscription for centralized logging and monitoring. Using browser `https://<host>/logger`, curl or custom client API - see configuration notes - By configuring the chainingBaseUrl, it is now possible to chain multiple gateways in sequence, such as `gateway1->gateway2->gateway3->endpoint`. In this setup, gateway beave much like a reverse proxy, validating authorization at each step unless PassThrough mode is enabled. Chaining is also supported in stream subscriber mode - Email, onError and sendMail() supports more secure RESTful OAuth for Microsoft Exchange Online (ExO) and Google Workspace Gmail, alongside traditional SMTP Auth for all mail systems. HelperRest supports a wide range of common authentication methods, including basicAuth, bearerAuth, tokenAuth, oauth, oauthSamlBearer, oauthJwtBearer and Auth PassTrough - Major version **v5.0.0** marks a shift from JavaScript to native TypeScript and prioritizes [Bun](https://bun.sh/) over Node.js. This upgrade requires some modifications to existing plugins. - **BREAKING**: [SCIM Stream](https://elshaug.xyz/docs/scim-stream) is the modern way of user provisioning letting clients subscribe to messages instead of traditional IGA top-down provisioning. SCIM Gateway now offers enhanced functionality with support for message subscription and automated provisioning using SCIM Stream - Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway. E.g., using Entra ID application OAuth - Supports OAuth Client Credentials authentication - Major version **v4.0.0** getUsers() and getGroups() replacing some deprecated methods. No limitations on filtering/sorting. Admin user access can be linked to specific baseEntities. New MongoDB plugin - ipAllowList for restricting access to allowlisted IP addresses or subnets e.g. Azure IP-range - General LDAP plugin configured for Active Directory - [PlugSSO](https://elshaug.xyz/docs/plugsso) using SCIM Gateway - Each authentication configuration allowing more than one admin user including option for readOnly - Codebase moved from callback of h... to the the promise(d) land of async/await - Supports configuration by environments and external files - Health monitoring through "/ping" URL, and option for error notifications by email - Entra ID user provisioning including license management e.g. Office 365, installed and configured within minutes! - Includes API Gateway for none SCIM/provisioning - becomes what you want it to become - Running SCIM Gateway as a Docker container ## Overview SCIM Gateway facilitates user management using the standardized REST-based SCIM 1.1 or 2.0 protocol, offering easier, more powerful, and consistent provisioning while avoiding vendor lock-in. Acting as a translator for incoming SCIM requests, the gateway seamlessly enables CRUD functionality (create, read, update, and delete) for users and groups. By implementing endpoint-specific protocols, it ensures provisioning across diverse destinations. With the gateway, your destinations effectively become SCIM endpoints, streamlining integration and simplifying user management. ![](https://jelhub.github.io/images/ScimGateway.svg) SCIM Gateway is based on popular asynchronous event driven framework [Bun](https://bun.sh/) or [Node.js](https://nodejs.dev/) using TypeScript/JavaScript. It is cloud and firewall friendly. Runs on almost all operating systems, and may load balance between hosts (horizontal) and cpu's (vertical). **Following example plugins are included:** * **Loki** (NoSQL Document-Oriented Database) SCIM Gateway becomes a standalone SCIM endpoint Demonstrates user provisioning towards document-oriented database Using [LokiJS](https://github.com/techfort/LokiJS) for a fast, in-memory document-oriented database (much like MongoDB/PouchDB) Default gives two predefined test users loaded using in-memory only (no persistence) Configuration `{"persistence": true}` gives persistence file store (no test users) Example of a fully functional SCIM Gateway plugin * **MongoDB** (NoSQL Document-Oriented Database) Same as plugin "Loki", but using external MongoDB Shows how to implement a highly configurable multi tenant or multi endpoint solution through `baseEntity` in URL * **SCIM** (REST Webservice) Demonstrates user provisioning towards REST-Based endpoint (type SCIM) Using plugin Loki as SCIM endpoint through HelperRest Can be used as SCIM version-gateway e.g. 1.1=>2.0 or 2.0=>1.1 * **Soap** (SOAP Webservice) Demonstrates user provisioning towards SOAP-Based endpoint Example WSDLs are included Using endpoint "Forwardinc" as an example (comes with Symantec/Broadcom/CA IM SDK - SDKWS) Shows how to implement a highly configurable multi tenant or multi endpoint solution through `baseEntity` in URL * **MSSQL** (MSSQL Database) Demonstrates user provisioning towards MSSQL database * **SAP HANA** (SAP HANA Database) Demonstrates SAP HANA specific user provisioning * **Entra ID** (REST Webservices) Entra ID user provisioning including license management (App Service plans) e.g. Office 365 Using Microsoft Graph API through HelperRest Using customized SCIM attributes according to Microsoft Graph API Includes Symantec/Broadcom ConnectorXpress metafile for creating provisioning "Azure - ScimGateway" endpoint type * **LDAP** (Directory) Fully functional LDAP plugin Pre-configured for Microsoft Active Directory Using endpointMapper (like plugin-entra-id) for attribute mapping flexibility * **API** (REST Webservices) Demonstrates API Gateway/plugin functionality using post/put/patch/get/delete combined with HelperRest None SCIM plugin, becomes what you want it to become. Methods included can also be used in standard SCIM plugins Endpoint complexity could be put in this plugin, and client could instead communicate through Gateway using your own simplified REST specification. One example of usage could be creation of tickets in ServiceDesk and also the other way, closing a ticket could automatically approve/reject corresponding workflow in IdP. ## Installation #### Install Bun [Bun](https://bun.sh/) is a prerequisite and must be installed on the server. Note, Bun installs by default in the current user’s `HOMEPATH\.bun`. To install it elsewhere, set `BUN_INSTALL=<install-path>` as a global or system environment variable before installing. The installation will add Bun to the current user’s path, but consider adding it to the global or system path for easier access across all users. #### Install SCIM Gateway Open a command window (run as administrator) Create your own package directory e.g. c:\my-scimgateway and install SCIM Gateway within this package. mkdir c:\my-scimgateway cd c:\my-scimgateway bun init -y bun install scimgateway bun pm trust scimgateway **c:\\my-scimgateway** will now be `<package-root>` index.ts, lib and config directories containing example plugins have been copied to your package from the original scimgateway package located under node_modules. Bun requires `bun pm trust scimgateway` for allowing postinstall copying these files. If internet connection is blocked, we could install on another machine and copy the `<package-root>` folder. #### Startup and verify default Loki plugin bun c:\my-scimgateway Start a browser http://localhost:8880/ping => Health check with a "hello" response http://localhost:8880/Users http://localhost:8880/Groups => Logon using gwadmin/password and two users and groups should be listed Start a new browser for remote log monitoring using url: http://localhost:8880/logger http://localhost:8880/Users/bjensen http://localhost:8880/Groups/Admins or http://localhost:8880/Users?filter=userName eq "bjensen" http://localhost:8880/Groups?filter=displayName eq "Admins" => Lists all attributes for specified user/group http://localhost:8880/Groups?filter=displayName eq "Admins"&excludedAttributes=members http://localhost:8880/Groups?filter=members.value eq "bjensen"&attributes=id,displayName,members.value http://localhost:8880/Users?filter=userName eq "bjensen"&attributes=userName,id,name.givenName http://localhost:8880/Users?filter=meta.created ge "2010-01-01T00:00:00Z"&attributes=userName,name.familyName,meta.created http://localhost:8880/Users?filter=emails.value co "@example.com"&attributes=userName,name.familyName,emails&sortBy=name.familyName&sortOrder=descending => Filtering and attribute examples "Ctrl + c" to stop the SCIM Gateway >Tip, take a look at bun test scripts located in `node_modules\scimgateway\test\lib` > If using Node.js instead of Bun, scimgateway must be downloaded from github because Node.js does not support native typescript used by modules. Startup will then be: `node --experimental-strip-types c:\scimgateway\index.ts` #### Upgrade SCIM Gateway Not needed after a fresh install The best and easiest way to upgrade is renaming existing scimgateway package folder, create a new one and do a fresh installation. After the installation we copy `index.ts, config and lib folder` (customized plugins) from previous installation to the new installation. You should also read the version history to see if custom plugins needs to be updated. Alternatives are: Upgrade to latest minor version: cd c:\my-scimgateway bun install scimgateway Note, always backup/copy c:\\my-scimgateway before upgrading. Custom plugins and corresponding configuration files will not be affected. To force a major upgrade (version x.\*.\* => y.\*.\*) that will brake compability with any existing custom plugins, we have to include the `@latest` suffix in the install command: `bun install scimgateway@latest` ##### Avoid (re-)adding the files created during `postinstall` For production we do not need example plugins to be incuded by the `postinstall` job Bun will by default exlude any `postinstall` jobs unless we have trusted the scimgateway package using the `bun pm trust scimgateway` that updates package.json `{ trustedDependencies: ["scimgateway"] }` For Node.js (and also Bun), we might set the property `scimgateway_postinstall_skip = true` in `.npmrc` or setting environment `SCIMGATEWAY_POSTINSTALL_SKIP = true` ## Configuration **index.ts** defines one or more plugins to be started by the `const plugins` setting. // example starting all default plugins: // const plugins = ['loki', 'scim', 'entra-id', 'ldap', 'mssql', 'api', 'mongodb', 'saphana', 'soap'] const plugins = ['loki'] for (const plugin of plugins) { try { await import(`./lib/plugin-${plugin}.ts`) } catch (err: any) { console.error(err) } } Each endpoint plugin needs a TypeScript file (.ts) and a configuration file (.json). **They both must have the same naming prefix**. For SAP Hana endpoint we have: >lib\plugin-saphana.ts >config\plugin-saphana.json Edit specific plugin configuration file according to your needs. Below shows an example of config\plugin-saphana.json { "scimgateway": { "port": 8884, "localhostonly": false, "chainingBaseUrl": null, "scim": { "version": "2.0", "skipTypeConvert" : false, "skipMetaLocation" false, "groupMemberOfUser": false "usePutSoftSync" : false }, "log": { "loglevel": { "file": "debug", "console": "error" }, "customMasking": [] }, "auth": { "basic": [ { "username": "gwadmin", "password": "password", "readOnly": false, "baseEntities": [] } ], "bearerToken": [ { "token": null, "readOnly": false, "baseEntities": [] } ], "bearerJwtAzure": [ { "tenantIdGUID": null, "readOnly": false, "baseEntities": [] } ], "bearerJwt": [ { "secret": null, "publicKey": null, "options": { "issuer": null }, "readOnly": false, "baseEntities": [] } ], "bearerOAuth": [ { "clientId": null, "clientSecret": null, "readOnly": false, "baseEntities": [] } ], "passThrough": { "enabled": false, "readOnly": false, "baseEntities": [] } }, "certificate": { "key": null, "cert": null, "ca": null, "pfx": { "bundle": null, "password": null } }, "ipAllowList": [], "email": { "auth": { "type": "oauth", "options": { "tenantIdGUID": null, "clientId": null, "clientSecret": null } }, "emailOnError": { "enabled": false, "from": null, "to": null } }, "stream": { "baseUrls": [], "certificate": { "ca": null }, "subscriber": { "enabled": false, "entity": { "undefined": { "nats": { "tenant": null, "subject": null, "jwt": null, "secret": null }, "deleteUserOnLastGroupRoleRemoval": false, "convertRolesToGroups": false, "generateUserPassword": false, "modifyOnly": false, "replaceDomains": [] } } }, "publisher": { "enabled": false, "entity": { "undefined": { "nats": { "tenant": null, "subject": null, "jwt": null, "secret": null } } } } } }, "endpoint": { "host": "hostname", "port": 30015, "username": "username", "password": "password", "saml_provider": "saml_provider_name" } } Configuration file have two main JSON objects: `scimgateway` and `endpoint` Definitions in `scimgateway` object have fixed attributes, but values can be modified. Sections not used/configured can be removed. This object is used by the core functionality of the SCIM Gateway. Definitions in `endpoint` object are customized according to our plugin code. Plugin typically need this information for communicating with endpoint - **port** - Gateway will listen on this port number. Clients (e.g. Provisioning Server) will be using this port number for communicating with the gateway - **localhostonly** - true or false. False means gateway accepts incoming requests from all clients. True means traffic from only localhost (127.0.0.1) is accepted. - **chainingBaseUrl** - baseUrl for chaining anohter gateway, syntax: `http(s)://host:port`. If defined, gateway beave much like a reverse proxy, validating authorization unless PassThrough mode is enabled. See `Configuration notes` for details - **idleTimeout** - default 120, sets the the number of seconds to wait before timing out a connection due to inactivity - **scim.version** - "1.1" or "2.0". Default is "2.0". - **scim.skipTypeConvert** - true or false, default false. Multivalue attributes supporting types e.g. emails, phoneNumbers, ims, photos, addresses, entitlements and x509Certificates (but not roles, groups and members) will be become "type converted objects" when sent to modifyUser and createUser. This for simplicity of checking attributes included and also for the endpointMapper method (used by plugin-ldap and plugin-entra-id), e.g.: "emails": { "work": {"value": "jsmith@example.com", "type": "work"}, "home": {"value": "", "type": "home", "operation": "delete"}, "undefined": {"value": "jsmith@hotmail.com"} } skipTypeConvert set to true gives attribute "as-is": array, allow duplicate types including blank, but values to be deleted have been marked with "operation": "delete" "emails": [ {"value": "jsmith@example.com", "type": "work"}, {"value": "john.smith.org", "type": "home", "operation": "delete"}, {"value": "jsmith@hotmail.com"} ] - **scim.skipMetaLocation** - true or false, default false. If set to true, `meta.location` which contains protocol and hostname from request-url, will be excluded from response e.g. `"{...,meta":{"location":"https://my-company.com/<...>"}}`. If using reverse proxy and not including headers `X-Forwarded-Proto` and `X-Forwarded-Host`, originator will be the proxy and we might not want to expose internal protocol and hostname being used by the proxy request. - **scim.groupMemberOfUser** - true or false, default false. If body contains groups and groupMemberOfUser=true, groups attribute will remain at user object (groups are member of user) instead of default user member of groups that will use modifyGroup method for maintaining group members. - **scim.usePutSoftSync** - true or false, default false. `PUT /Users/bjensen` will replace the user bjensen with body content. If set to `true`, only PUT body content will be replaced. Any additional existing user attributes and groups supported by plugin will remain as-is. - **log.loglevel.file** - off, debug, info, warn or error. Default off. Output to plugin-logfile e.g. `logs\plugin-saphana.log` - **log.loglevel.console** - off, debug, info, warn or error. Default off. Output to stdout and errors to stderr - **log.loglevel.push** - debug, info, warn or error. Default info. Push to stream used by remote real-time log subscription - **log.logDirectory** - custom defined log directory e.g. `/var/log/scimgateway` that will override default `<scimgateway path>/logs`. If not exist it will be created. - **log.customMasking** - array of attributes to be masked e.g. `"customMasking": ["SSN", "weight"]`. By default SCIM Gateway includes masking of some standard attributes like password. - **log.colorize** - default true, gives colorized and minimized console output, if redirected to stdout/stderr standard JSON formatted output and no colors. Set to false give standard JSON - **log.maxSize** - default 20 (MB) log file size - **log.maxFiles** - default 5, keep only the last 5 logs - note, new and rotated file on startup - **auth** - Contains one or more authentication/authorization methods used by clients for accessing gateway - may also include: - **auth.xx.readOnly** - true/false, true gives read only access - only allowing `GET` requests for corresponding admin user - **auth.xx.baseEntities** - array containing one or more `baseEntity` allowed for this user e.g. ["client-a"] - empty array allowing all. **Methods are disabled by setting corresponding admin user to null or remove methods not used** - **auth.basic** - Array of one ore more basic authentication objects - Basic Authentication with **username**/**password**. Note, we set a clear text password that will become encrypted when gateway is started. - **auth.bearerToken** - Array of one or more bearer token objects - Shared token/secret (supported by Entra ID). Clear text value will become encrypted when gateway is started. - **auth.bearerJwtAzure** - Array of one or more JWT used by Azure SyncFabric. **tenantIdGUID** must be set to Entra ID Tenant ID. - **auth.bearerJwt** - Array of one or more standard JWT objects. Using **secret**, **publicKey** or **wellKnownUri** for signature verification. publicKey should be set to the filename of public key or certificate pem-file located in `<package-root>\config\certs` or absolute path being used. Clear text secret will become encrypted when gateway is started. For JWKS (JSON Web Key Set), the **wellKnownUri** must be set to identity provider well-known URI which will be used for lookup the jwks_uri key. **options.issuer** should normally be set for validation when using secret or publicKey, for JWKS the issuer will be included automatically. Other options may also be included according to the JWT standard. - **auth.bearerOAuth** - Array of one or more Client Credentials OAuth configuration objects. **`clientId`** and **`clientSecret`** are mandatory. clientSecret value will become encrypted when gateway is started. OAuth token request url is **/oauth/token** e.g. `http://localhost:8880/oauth/token` - **auth.passThrough** - Setting **auth.passThrough.enabled=true** will bypass SCIM Gateway authentication. Gateway will instead pass ctx containing authentication header to the plugin. Plugin could then use this information for endpoint authentication and we don't have any password/token stored at the gateway. Note, this also requires plugin binary having `scimgateway.authPassThroughAllowed = true` and endpoint logic for handling/passing ctx.request.header.authorization - **certificate** - If not using TLS certificate, set "key", "cert" and "ca" to **null**. When using TLS, "key" and "cert" have to be defined with the filename corresponding to the primary-key and public-certificate. Both files must be located in the `<package-root>\config\certs` directory unless absolute path being defined e.g: "certificate": { "key": "key.pem", "cert": "cert.pem", "ca": "ca.pem" // if several: "ca": ["ca1.pem", "ca2.pem"] } Example of how to make a self signed certificate: openssl req -nodes -newkey rsa:2048 -x509 -sha256 -days 3650 -keyout key.pem -out cert.pem -subj "/O=My Company/OU=Application/CN=SCIM Gateway" -addext "subjectAltName=DNS:localhost,DNS:127.0.0.1,DNS:*.mycompany.com" -addext "extendedKeyUsage=serverAuth" -addext "keyUsage=digitalSignature" Note, when using Symantec/Broadcom Provisioning, the "certificate authority - CA" also must be imported on the Connector Server. For self-signed certificate, CA and the certificate (public key) is the same. PFX / PKCS#12 bundle can be used instead of key/cert/ca e.g: "pfx": { "bundle": "certbundle.pfx", "password": "password" } Note, we should normally use certificate (https) for communicating with SCIM Gateway unless we install gateway locally on the manager (e.g. on the provisioning Connector Server). When installed on the manager, we could use `http://localhost:port` or `http://127.0.0.1:port` which will not be passed down to the data link layer for transmission. We could then also set {"localhostonly": true} - **ipAllowList** - Array of one or more IPv4/IPv6 subnets (CIDR) allowed for incoming traffic. E.g. using Entra ID as IdP, we would like to restrict access to IP addresses used by Azure. Azure IP-range can be downloaded from: [https://azureipranges.azurewebsites.net](https://azureipranges.azurewebsites.net), enter **AzureActiveDirectory** in the search list and select JSON download. Copy the "addressPrefixes" array content and paste into ipAllowList array. CIDR single IP-host syntax is a.b.c.d/32. Note, front-end HTTP proxy or a load balancer must include client IP-address in the **X-Forwarded-For** header. Configuration example: "ipAllowList": [ "13.64.151.161/32", "13.66.141.64/27", ... "2603:1056:2000::/48", "2603:1057:2::/48" ] - **email** - Sending email from plugin or automated error notifications emailOnError. For emailOnError only the first error will be sent until sendInterval have passed. Supporting both SMTP Auth and modern REST OAuth. For OAuth, currently Microsoft Exchange Online (ExO) and Google Workspace Gmail are supported - see configuration notes - **email.auth** - Authentication configuration - **email.auth.type** - `oauth` or `smtp` - **email.auth.options** - Authentication options - note, different options for type oauth and smtp - **email.auth.options.tenantIdGUID (oauth/ExO)** - Entra tenant id or domain name - **email.auth.options.clientId (oauth/ExO)** - Entra OAuth application Client ID - **email.auth.options.clientSecret (oauth/ExO)** - Entra OAuth application Client Secret - **email.auth.options.serviceAccountKeyFile (oauth/Gmail)** - Google Service Account key json-file name located in the `package-root>\config\certs` directory unless absolute path being defined - **email.auth.options.host (smtp)** - Mailserver e.g. "smtp.gmail.com" - mandatory for smtp - **email.auth.options.port (smtp)** - Port used by mailserver e.g. 587, 25 or 465 - mandatory for smtp - **email.auth.options.username (smtp)** - Mail account for authentication normally same as sender of the email, e.g. "user@gmail.com" - **email.auth.options.password (smtp)** - Mail account password - **email.proxy** - Proxy configuration if using mailproxy - **email.proxy.host** - Proxy host e.g. `http://proxy-host:1234` - **email.proxy.username** - username if authentication is required - **email.proxy.password** - password if authentication is required - **email.emailOnError** - Contains configuration for sending error notifications by email. Note, only the first error will be sent until sendInterval have passed - **email.emailOnError.enabled** - true or false, value set to true will enable email notifications - **email.emailOnError.sendInterval** - Default 15. Mail notifications on error are deferred until sendInterval **minutes** have passed since the last notification - **email.emailOnError.from** - Sender email addresses e.g: "noreply@example.com". **Mandatory for oauth**. For smtp email.auth.options.username will be used - **email.emailOnError.to** - Comma separated list of recipients email addresses e.g: "someone@example.com" - **email.emailOnError.cc** - Optional comma separated list of cc mail addresses - **email.emailOnError.subject** - Optional mail subject, default `SCIM Gateway error message` - **azureRelay** - Azure Relay outbound listener - **azureRelay.enabled** - true or false, true will enable the Azure Relay listener - **azureRelay.connectionUrl** - `https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>` - `<namespace-name>` is the name of the Relay created and `<hybrid-connection-name>` is the name of the Hybrid Connection entity created in the Relay - **azureRelay.apiKey** - The `Private Key` found in the `Shared access policy` (RootManageSharedaccessKey) - **azureRelay.keyRule** - Optional, the `Shared access policy` name - default using `RootManageSharedaccessKey` - **stream** - See [SCIM Stream](https://elshaug.xyz/docs/scim-stream) for configuration details - **endpoint** - Contains endpoint specific configuration according to customized **plugin code**. ### Configuration notes - general - Custom Schemas, ServiceProviderConfig and ResourceType can be used if `./lib/scimdef-v2.json or scimdef-v1.json` exists. Original scimdef-v2.json/scimdef-v1.json can be copied from node_modules/scimgateway/lib to your plugin/lib and customized. - Using reverse proxy and we want ipAllowList and correct meta.location response, following headers must be set by proxy: `X-Forwarded-For`, `X-Forwarded-Proto` and `X-Forwarded-Host` - Setting environment variable `SEED` with some random characters will override default password seeding logic. This also allow copying configuration file with encrypted secrets from one machine to another. - All configuration can be set based on environment variables. Syntax will then be `"process.env.<ENVIRONMENT>"` where `<ENVIRONMENT>` is the environment variable used. E.g. scimgateway.port could have value "process.env.PORT", then using environment variable PORT. - All configuration values can be moved to a single external file having JSON dot notation content with plugin name as parent JSON object. Syntax in original configuration file used by the gateway will then be `"process.file.<path>"` where `<path>` is the file used. E.g. key endpoint.password could have value "process.file./var/run/vault/secrets.json" - All configuration values can be moved to multiple external files, each file containing one single value. Syntax in original configuration file used by the gateway will then be `"process.text.<path>"` where `<path>` is the file which contains raw (`UTF-8`) character value. E.g. key endpoint.password could have value "process.text./var/run/vault/endpoint.password". Example: { "scimgateway": { ... "port": "process.env.PORT", ... "loglevel": { "file": "process.env.LOG_LEVEL_FILE", ... "auth": { "basic": [ { "username": "process.file./var/run/vault/secrets.json", "password": "process.file./var/run/vault/secrets.json" }, ... ], "bearerJwt": [ "secret": "process.text./var/run/vault/jwt.secret", "publicKey": "process.text./var/run/vault/jwt.pub", ... ], ... }, "endpoint": { ... "username": "process.file./var/run/vault/secrets.json", "password": "process.file./var/run/vault/secrets.json", ... } } jwt.secret file content example: thisIsSecret secrets.json file content example for plugin-soap: { "plugin-soap.scimgateway.auth.basic[0].username": "gwadmin", "plugin-soap.scimgateway.auth.basic[0].password": "password", "plugin-soap.endpoint.username": "superuser", "plugin-soap.endpoint.password": "secret" } ### Configuration notes - Email, using Microsoft Exchange Online (ExO) - Entra ID application must have application permissions `Mail.Send` - To prevent the sending of emails from any defined mailboxes, an ExO `ApplicationAccessPolicy` must be defined through PowerShell. First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from Note, `mail enabled security group` cannot be created from portal, only from admin or admin.exchange console ##Connect to Exchange Install-Module -Name ExchangeOnlineManagement Connect-ExchangeOnline ##Create ApplicationAccessPolicy New-ApplicationAccessPolicy -AppId <AppClientID> -PolicyScopeGroupId <MailEnabledSecurityGrpId> -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes" ### Configuration notes - Email, using Google Workspace Gmail - https://console.cloud.google.com - IAM & Admin > Service Accounts > Create Service Account - Name=email-sender - Create and Continue - Grant this service account access to project - not needed - Grant users access to this service - not needed - IAM & Admin > Service Accounts > "email-sender" account > Keys - Add Key > Create new key > JSON - download json Service Account Key file, refere to configuration `email.auth.options.serviceAccountKeyFile` - https://admin.google.com - Security > Access and data control > API controls - Manage Domain Wide Delegation > Add new - Client ID = id of service account created - OAuth scope = `https://www.googleapis.com/auth/gmail.send` - https://admin.google.com - Billing > Subscriptions - verify Google Workspace license - Directory > Users > "user" - Licenses > Edit > enable Google Workspace license `email.emailOnError.from` mail address must have Google Workspace license ### Configuration notes - Gateway chainging and chainingBaseUrl By configuring the `chainingBaseUrl`, it is possible to chain multiple gateways in sequence, such as `gateway1->gateway2->gateway3->endpoint`. In this setup, gateway behave much like a reverse proxy, validating authorization at each step unless PassThrough mode is enabled. Chaining is also supported in stream subscriber mode { "scimgateway": { ... "chainingBaseUrl": "https:\\gateway2:8880", ... "auth": { ... "passThrough": { "enabled": false, "readOnly": false, "baseEntities": [] } ... } }, ... } Using above configuration example on gateway1, incoming requests will be routed to `https:\\gateway2:8880` The plugin and its associated authentication configuration can mirror the setup running on the final gateway. However, in chaining mode, the plugin binary is used solely for initializing and configuring the gateway. This allows for the use of a simplified `plugin-<name>.ts` binary containing only the essential mandatory components: // start - mandatory plugin initialization const ScimGateway: typeof import('scimgateway').ScimGateway = await (async () => { try { return (await import('scimgateway')).ScimGateway } catch (err) { const source = './scimgateway.ts' return (await import(source)).ScimGateway } })() const scimgateway = new ScimGateway() const config = scimgateway.getConfig() scimgateway.authPassThroughAllowed = false // end - mandatory plugin initialization Using `scimgateway.authPassThroughAllowed = true` and `plugin-<name>.json` configuration `scimgateway.auth.passThrough=true` enables Authentication PassTrhough ### Configuration notes - HelperRest used by plugins For REST endpoints, plugins may use HelperRest to simplify authentication and communication doRequest() executes REST request and return response `doRequest(<baseEntity>, <method>, <path>, <body>, <ctx>, <options>)` * baseEntity - 'undefined' if not used and must correspond with endpoint configuration that defines baseUrls and connection options. * method - GET, PATCH, PUT, DELETE * path - either full url or just the path that will be added to baseUrl. Using full url will override baseUrl. Using path is preferred because of auth caching logic and simplicity * body - optional body to be used * ctx - optional, passing authorization header if Auth PassThrough is enabled * opt - optional, connection options that will extend/override any endpoint.entity.undefined.connection definitions Configuration showing connection settings: { "scimgateway": { ... } "endpoint": { "entity": { "undefined": { "connection": { "baseUrls": [], "auth": { "type": "xxx", "options": { ... "jwtPayload": {}, "samlPayload": {}, "tls": {} // files located in ./config/certs } }, "options": { "headers": {}, "tls": {} // files located in ./config/certs }, "proxy": {} } } } } } * baseUrls - Endpoint URL. Several may be defined for failower. There are retry logic on connection failures * auth.type - defines authentication being used: `basic`, `oauth`, `token`, `bearer`, `oauthSamlBearer` or `oauthJwtBearer` * auth.options - for each valid type there are different options. tenantIdGUID is special for Entra ID and serviceAccountKeyFile is special for Google. Using these will simplify and reduce options to be included. Also note we do not need to include baseUrls when using tenantIdGUID/serviceAccountKeyFile as long as endpoint is Entra ID (Microsoft Graph) or Google. Example using basic auth: "connection": { "baseUrls": [ "https://localhost:8880" ], "auth": { "type": "basic", "options": { "username": "gwadmin", "password": "password" } }, "options": { "tls": { "rejectUnauthorized": false, "ca": "ca.pem" } } } Example Entra ID (plugin-entra-id) using clientId/clientSecret: "connection": { "baseUrls": [], "auth": { "type": "oauth", "options": { "tenantIdGUID": "<tenantId>", "clientId": "<clientId>", "clientSecret": "<clientSecret>" } } } Example Entra ID (plugin-entra-id) using certificate secret: "connection": { "baseUrls": [], "auth": { "type": "oauthJwtBearer", "options": { "tenantIdGUID": "<tenantId>", "clientId": "<clientId>", "tls": { "key": "key.pem", "cert": "cert.pem" } } } } Example Entra ID (plugin-entra-id) using federated credentials: "connection": { "baseUrls": [], "auth": { "type": "oauthJwtBearer", "options": { "tenantIdGUID": "<tenantId>", "fedCred": { "issuer": "<https://FQDN-scimgateway>", "subject": "<entra id application object id - client id>", "name": "<entra id federated credentials unique name>" } } } } // Note, fedCred configuration must match corresponding configuration in Entra ID Application - Certificates & Secrets - Federated credentials - scenario "Other issuer" // example issuer: "https://scimgateway.my-company.com" note, this scimgateway base URL must be reachable from the internet // example name: "plugin-entra-id" Example using general OAuth: "connection": { "baseUrls": [<"endpointUrl">], "auth": { "type": "oauth", "options": { "tokenUrl": "<tokenUrl>" "clientId": "<clientId>", "clientSecret": "<clientSecret>" } } } Please see code editor method HelperRest doRequest() IntelliSense for type and option details ### Configuration notes - Remote real-time log subscription Using remote real-time log subscription we may implement custom logic like monitoring and centralized logging - browser and url: https://host/logger - curl with -u or -H "Authorization: Bearer secret" ``` curl -Ns http://localhost:8880/logger -u gwadmin:password | awk ' /^data: / {sub(/^data: /,""); printf "%s", $0; last=1; next} /^$/ {if (last) print ""; last=0} ' ``` - custom client API (see example below) - not supported by Azure Relay We may configure read-only user/secret for log collection purpose "auth": { "basic": [ { "username": "gwadmin", "password": "password", "readOnly": false, "baseEntities": [] }, { "username": "gwread", "password": "password", "readOnly": true, "baseEntities": [] } ], "bearerToken": [ { "token": "secret", "readOnly": true, "baseEntities": [] } ], ... } Remote log subscription is configured by log.loglevel.push and the push logger has default loglevel set to `info` Example using debug loglevel: "log": { "loglevel": { "push": "debug" } } Example code implementing remote real-time log subscription and custom message handling ``` // // usage: bun <scriptname.ts> // update url and the auth according to environment used // const username = "gwadmin" const password = "password" const url = "http://localhost:8880/logger" const headers = new Headers({ Authorization: "Basic " + btoa(`${username}:${password}`), Accept: "text/event-stream" }) // message handling and custom logic // we could also do JSON.parse(message) and granular filtering on log "level" const messageHandler = async (message: string) => { console.log(message) } async function startup() { while (true) { try { const resp = await fetch(url, { headers }); if (!resp.ok || !resp.body) { console.error(`❌ Response error: ${resp.status} ${resp.statusText}`) await Bun.sleep(10_000) continue } console.log('✅ Now awaiting log events...\n') const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader() while (true) { const { value, done } = await reader.read() if (done) break if (!value.startsWith('data: ')) continue const i = value.indexOf("\n\n") if (i < 1) continue const msg = value.slice(6, i) messageHandler(msg) } console.error("⚠️ Connection closed"); await Bun.sleep(10_000) } catch (err: any) { console.error("❌ Connection error:", err?.message || err) await Bun.sleep(10_000) } } } startup() ``` ### Configuration notes - Azure Relay Using Azure technology we have different options for setting up a communication tunnel to SCIM Gateway: - `Microsoft Entra Application Proxy + Microsoft Entra Application Proxy Connector` (SCIM Gateway located on-premises or using Azure private VNet/IP) - `Azure Application Gateway` - Layer 7 (SCIM Gateway located in Azure) - `Azure Relay` (SCIM Gateway located on-premises or in Azure) SCIM Gateway have builtin [Azure Relay](https://learn.microsoft.com/en-us/azure/azure-relay/relay-what-is-it) support which gives secure and hassle-free outbound communication — with just one minute of configuration Azure pricing for using Azure Relay is approx. 10$ per month for each listener (SCIM Gateway plugin) **Using out-of-the-box Azure Relay:** - Prerequisite: SCIM Gateway having outbound internet access (https/443) - In Azure create a `Relay` - `<namespace-name>` - In the Relay, create an entity of type `Hybrid Connection` - `<hybrid-connection-name>` **one for each SCIM Gateway plugin** - The `Requires Client Authorization` option **should be unchecked (not activated)**, unless we are using custom IdP/API having logic for including SAS-token in the communication header - Shared access policies - RootManageSharedaccessKey - Primary Key (copy this one) Instead of RootManageSharedaccessKey policy in the `<namespace-name>`, we could create dedicated policy in the sub level `<hybrid-connection-name>` and use this policy name in plugin configuration `scimgateway.azureRelay.keyRule` SCIM Gateway plugin configuration: ``` { "scimgateway: { ... "azureRelay": { "enabled": true, "connectionUrl": "https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>", "apiKey": "<primary-key>" }, ... }, ... } ```` `connectionUrl` will be the SCIM base URL used by IdP/API for accessing SCIM Gateway Example: GET `https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>/Users` GET `https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>/<baseEntity>/Users` If several SCIM Gateway´s (same plugin) connect listeners using the same Azure Relay connectionUrl, there will be load-balancing and round-robin distribution ## Manual startup Gateway can be started from a command window running in administrative mode 3 ways to start: bun c:\my-scimgateway bun c:\my-scimgateway\index.ts <package-root>bun . <kbd>Ctrl</kbd>+<kbd>c</kbd> to stop ## Automatic startup - Windows Task Scheduler Start Windows Task Scheduler (taskschd.msc), right click on "Task Scheduler Library" and choose "Create Task" General tab: ----------- Name = SCIM Gateway User account = SYSTEM Run with highest privileges Triggers tab: ------------- Begin the task = At startup Actions tab: ------------ Action = Start a program Program/script = <install path>\bun.exe Arguments = c:\my-scimgateway Settings - tab: --------------- Stop the task if runs longer than = Disabled (greyed out) Verification: - Right click task - **Run**, verify process node.exe (SCIM Gateway) can be found in the task manager (not the same as task scheduler). Also verify logfiles `<pakage-root>\logs` - Right click task - **End**, verify process node.exe have been terminated and disappeared from task manager - **Reboot** server and verify SCIM Gateway have been automatically started ## Running as a isolated virtual Docker container On Linux systems we may also run SCIM Gateway as a Docker image (using docker-compose) * Docker Pre-requisites: **docker-ce docker-compose** - Install SCIM Gateway within your own package and copy provided docker files: mkdir /opt/my-scimgateway cd /opt/my-scimgateway bun init -y bun install scimgateway bun pm trust scimgateway cp ./config/docker/* . **docker-compose.yml** <== Here is where you would set the exposed port and environment **Dockerfile** <== Main dockerfile **DataDockerfile** <== Handles volume mapping **docker-compose-debug.yml** <== Debugging **docker-compose-mssql.yml** <== Example including MSSQL docker image - Create a scimgateway user on your Linux VM. adduser scimgateway - Create a directory on your VM host for the scimgateway configs: mkdir /home/scimgateway/config - Copy your updated configuration file e.g. /opt/my-scimgateway/config/plugin-loki.json to /home/scimgateway/config. Use scp to perform the copy. NOTE: /home/scimgateway/config is where all important configuration and loki datastore will reside outside of the running docker container. If you upgrade scimgateway you won't lose your configurations and data. - Build docker images and start it up docker-compose up --build -d NOTE: Add the -d flag to run the command above detached. Be sure to confirm that port 8880 is available with a simple http request If using default plugin-loki and we have configured `{"persistence": true}`, we could confirm scimgateway created loki.db: su scimgateway cd /home/scimgateway/config ls loki.db To list running containers information: `docker ps` To list available images: `docker images` To view the logs: `docker logs scimgateway` To execute command within your running container: `docker exec scimgateway <bash command>` To stop scimgateway: `docker-compose stop` To restart scimgateway: `docker-compose start` To debug running container (using Visual Studio Code): `docker-compose -f docker-compose.yml -f docker-compose-debug.yml up -d` Start Visual Studio Code and follow [these](https://code.visualstudio.com/docs/nodejs/nodejs-debugging) debugging instructions To upgrade scimgateway docker image (remove the old stuff before running docker-compose up --build): docker rm scimgateway docker rm $(docker ps -a -q); docker rmi $(docker images -q -f "dangling=true") ## Entra ID as IdP using SCIM Gateway Entra ID could do automatic user provisioning by synchronizing users towards SCIM Gateway, and gateway plugins will update endpoints. Plugin configuration file must include **SCIM Version "2.0"** (scimgateway.scim.version) and either **Bearer Token** (scimgateway.auth.bearerToken[x].token) or **Entra ID Tenant ID GUID** (scimgateway.auth.bearerJwtAzure[x].tenantIdGUID) or both: scimgateway: { "scim": { "version": "2.0", ... }, ... "auth": { "bearerToken": [ { "token": "shared-secret" } ], "bearerJwtAzure": [ { "tenantIdGUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } ] } ... } `token` configuration must correspond with "Secret Token" defined in Entra ID `tenantIdGUID` configuration must correspond with Entra ID Tenant ID In Azure Portal: `Azure-Microsoft Entra ID-Enterprise Application-<My Application>-Provisioning-Secret Token` Note, when "Secret Token" is left blank, Azure will use JWT (tenantIdGUID) `Azure-Microsoft Entra ID-Overview-Tenant ID` User mappings attributes between AD and SCIM also needs to be configured `Azure-Microsoft Entra ID-Enterprise Application-<My Application>-Provisioning-Edit attribute mappings-Mappings` Entra ID default SCIM attribute mapping for **USER** must have: userPrincipalName mapped to userName (matching precedence #1) Entra ID default SCIM attribute mapping for **GROUP** must have: displayName mapped to displayName (matching precedence #1) members mapped to members Some notes related to Entra ID: - Entra ID SCIM [documentation](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/use-scim-to-provision-users-and-groups) - For using OAuth/JWT credentials, Entra ID configuration "Secret Token" (bearer token) should be blank. Plugin configuration must then include bearerJwtAzure.tenantIdGUID. Click "Test Connection" in Azure to verify - Entra ID do a regular check for a "non" existing user/group. This check seems to be a "keep alive" to verify connection. - Entra ID first checks if user/group exists, if not exist they will be created (no explore of all users like CA Identity Manager