scimgateway 5.5.1 → 5.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -7
- package/lib/helper-rest.ts +20 -23
- package/lib/scimgateway.ts +21 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -738,16 +738,16 @@ Example Entra ID (plugin-entra-id) using federated credentials:
|
|
|
738
738
|
"options": {
|
|
739
739
|
"tenantIdGUID": "<tenantId>",
|
|
740
740
|
"fedCred": {
|
|
741
|
-
"issuer": "<https://FQDN-scimgateway
|
|
741
|
+
"issuer": "<https://FQDN-scimgateway>",
|
|
742
742
|
"subject": "<entra id application object id - client id>",
|
|
743
743
|
"name": "<entra id federated credentials unique name>"
|
|
744
744
|
}
|
|
745
745
|
}
|
|
746
746
|
}
|
|
747
747
|
}
|
|
748
|
-
|
|
749
|
-
// example issuer: "https://scimgateway.my-company.com
|
|
750
|
-
//
|
|
748
|
+
// Note, fedCred configuration must match corresponding configuration in Entra ID Application - Certificates & Secrets - Federated credentials - scenario "Other issuer"
|
|
749
|
+
// example issuer: "https://scimgateway.my-company.com" note, this scimgateway base URL must be reachable from the internet
|
|
750
|
+
// example name: "plugin-entra-id"
|
|
751
751
|
|
|
752
752
|
|
|
753
753
|
Example using general OAuth:
|
|
@@ -1491,6 +1491,17 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1491
1491
|
|
|
1492
1492
|
## Change log
|
|
1493
1493
|
|
|
1494
|
+
### v5.5.2
|
|
1495
|
+
|
|
1496
|
+
[Improved]
|
|
1497
|
+
|
|
1498
|
+
- Entra ID Federated Identity Credentials introduced in v5.5.0, the issuer configuration should be scimgateway base URL
|
|
1499
|
+
old: `"issuer": "<https://FQDN-scimgateway>/oauth"`
|
|
1500
|
+
new: `"issuer": "<https://FQDN-scimgateway>"`
|
|
1501
|
+
|
|
1502
|
+
Change log v5.5.0 have been corrected with the new issuer having base URL only
|
|
1503
|
+
|
|
1504
|
+
|
|
1494
1505
|
### v5.5.1
|
|
1495
1506
|
|
|
1496
1507
|
[Fixed]
|
|
@@ -1510,7 +1521,7 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1510
1521
|
"options": {
|
|
1511
1522
|
"tenantIdGUID": "<Entra ID tenantIdGUID",
|
|
1512
1523
|
"fedCred": {
|
|
1513
|
-
"issuer": "<https://FQDN-scimgateway
|
|
1524
|
+
"issuer": "<https://FQDN-scimgateway>",
|
|
1514
1525
|
"subject": "<entra id application object id - client id>",
|
|
1515
1526
|
"name": "<entra id federated credentials unique name>"
|
|
1516
1527
|
}
|
|
@@ -1524,14 +1535,14 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1524
1535
|
"options": {
|
|
1525
1536
|
"tenantIdGUID": "11111111-2222-3333-4444-555555555555",
|
|
1526
1537
|
"fedCred": {
|
|
1527
|
-
"issuer": "https://scimgateway.my-company.com
|
|
1538
|
+
"issuer": "https://scimgateway.my-company.com",
|
|
1528
1539
|
"subject": "99999999-8888-7777-6666-555555555555",
|
|
1529
1540
|
"name": "plugin-entra-id"
|
|
1530
1541
|
}
|
|
1531
1542
|
}
|
|
1532
1543
|
}
|
|
1533
1544
|
|
|
1534
|
-
Note: Federated credentials defined for the application in Entra ID must match the corresponding `issuer`, `subject`, and `name` values defined in the SCIM Gateway endpoint configuration. An example of this can be using `plugin-entra-id` and other plugins that interact with endpoints or applications protected by Entra ID.
|
|
1545
|
+
Note: Federated credentials (scenario "Other issuer") defined for the application in Entra ID must match the corresponding `issuer`, `subject`, and `name` values defined in the SCIM Gateway endpoint configuration. An example of this can be using `plugin-entra-id` and other plugins that interact with endpoints or applications protected by Entra ID.
|
|
1535
1546
|
|
|
1536
1547
|
Also note: SCIM Gateway must be reachable from the internet (as defined by the `issuer` URL). This requires allowing inbound internet communication — or alternatively, Azure Relay can be used for outbound-only communication.
|
|
1537
1548
|
|
package/lib/helper-rest.ts
CHANGED
|
@@ -154,27 +154,9 @@ export class HelperRest {
|
|
|
154
154
|
|
|
155
155
|
if (tenantIdGUID) { // Microsoft Entra ID
|
|
156
156
|
if (this.config_entity[baseEntity]?.connection?.auth?.options?.fedCred?.issuer) { // federated credentials
|
|
157
|
-
const name = JSON.stringify(this.config_entity[baseEntity]?.connection?.auth?.options?.fedCred?.name) // ensure not using none valid json key
|
|
158
|
-
if (!this.scimgateway.jwk) this.scimgateway.jwk = {}
|
|
159
|
-
if (!this.scimgateway.jwk[baseEntity]) this.scimgateway.jwk[baseEntity] = {}
|
|
160
|
-
if (!this.scimgateway.jwk[baseEntity][name]) {
|
|
161
|
-
const { publicKey, privateKey } = await jose.generateKeyPair('RS256')
|
|
162
|
-
this.scimgateway.jwk[baseEntity][name] = { publicKey, privateKey }
|
|
163
|
-
const ttl = 5 * 60 // 5 minutes
|
|
164
|
-
;(async () => {
|
|
165
|
-
// rotate - delete JWK after 5 minutes, will be regenerated on next token request
|
|
166
|
-
// entra id only lookup well-known uri and corresponding jwks_uri on token request validation if kid not found in entra cached JWKS
|
|
167
|
-
setTimeout(async () => {
|
|
168
|
-
delete this.scimgateway.jwk[baseEntity][name]
|
|
169
|
-
}, ttl * 1000)
|
|
170
|
-
})()
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
this.scimgateway.jwk[baseEntity].issuer = this.config_entity[baseEntity]?.connection?.auth?.options?.fedCred?.issuer // updates .well-known
|
|
174
|
-
|
|
175
157
|
const now = Date.now()
|
|
176
158
|
const jwtPayload: jose.JWTPayload = {
|
|
177
|
-
iss: this.config_entity[baseEntity]?.connection?.auth?.options?.fedCred?.issuer, // entra id federated credentials issuer e.g. https://scimgateway.my-company.com
|
|
159
|
+
iss: this.config_entity[baseEntity]?.connection?.auth?.options?.fedCred?.issuer, // entra id federated credentials issuer - scimgateway base URL, e.g. https://scimgateway.my-company.com
|
|
178
160
|
sub: this.config_entity[baseEntity]?.connection?.auth?.options?.fedCred?.subject, // entra id application object id - client id
|
|
179
161
|
name: this.config_entity[baseEntity]?.connection?.auth?.options?.fedCred?.name, // entra id federated credentials unique name e.g. plugin-entra-id
|
|
180
162
|
aud: 'api://AzureADTokenExchange', // entra id federated credentials audience
|
|
@@ -188,7 +170,8 @@ export class HelperRest {
|
|
|
188
170
|
...jwtPayload,
|
|
189
171
|
}
|
|
190
172
|
|
|
191
|
-
const
|
|
173
|
+
const { publicKey, privateKey } = await jose.generateKeyPair('RS256')
|
|
174
|
+
const jwk = await jose.exportJWK(publicKey)
|
|
192
175
|
const kid = createHash('sha256') // kid required for JWKS
|
|
193
176
|
.update(JSON.stringify(jwk))
|
|
194
177
|
.digest('base64url')
|
|
@@ -206,8 +189,22 @@ export class HelperRest {
|
|
|
206
189
|
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
|
207
190
|
client_assertion: await new jose.SignJWT(jwtClaims)
|
|
208
191
|
.setProtectedHeader(jwtHeaders)
|
|
209
|
-
.sign(
|
|
192
|
+
.sign(privateKey),
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// keep JWK for 5 minutes, will be regenerated on next token request
|
|
196
|
+
// entra id only lookup well-known uri and corresponding jwks_uri on token request validation if kid not found in entra cached JWKS
|
|
197
|
+
if (!this.scimgateway.jwk) this.scimgateway.jwk = {}
|
|
198
|
+
if (!this.scimgateway.jwk[kid]) {
|
|
199
|
+
this.scimgateway.jwk[kid] = { publicKey, privateKey }
|
|
200
|
+
const ttl = 5 * 60
|
|
201
|
+
;(async () => {
|
|
202
|
+
setTimeout(async () => {
|
|
203
|
+
delete this.scimgateway.jwk[kid]
|
|
204
|
+
}, ttl * 1000)
|
|
205
|
+
})()
|
|
210
206
|
}
|
|
207
|
+
this.scimgateway.jwk.issuer = this.config_entity[baseEntity]?.connection?.auth?.options?.fedCred?.issuer // all baseEntities should use same issuer
|
|
211
208
|
} else { // standard certificate
|
|
212
209
|
if (!this.config_entity[baseEntity]?.connection?.auth?.options?.tls?.cert) {
|
|
213
210
|
throw new Error(`auth type '${this.config_entity[baseEntity]?.connection?.auth?.type}' - missing options.tls.key/cert configuration`)
|
|
@@ -918,13 +915,13 @@ export class HelperRest {
|
|
|
918
915
|
* }
|
|
919
916
|
*
|
|
920
917
|
* // Microsoft Entra ID - using Federated credentials
|
|
921
|
-
* // Note, fedCred configuration must match corresponding configuration in Entra ID Application -
|
|
918
|
+
* // Note, fedCred configuration must match corresponding configuration in Entra ID Application - Certificates & Secrets - Federated credentials - scenario "Other issuer"
|
|
922
919
|
* {
|
|
923
920
|
* "type": "oauthJwtBearer",
|
|
924
921
|
* "options": {
|
|
925
922
|
* "tenantIdGUID": "<Entra ID tenantIdGUID",
|
|
926
923
|
* "fedCred": {
|
|
927
|
-
* "issuer": "<https://FQDN-scimgateway
|
|
924
|
+
* "issuer": "<https://FQDN-scimgateway", // scimgateway base URL, e.g. https://scimgateway.my-company.com
|
|
928
925
|
* "subject": "<entra id application object id - client id>",
|
|
929
926
|
* "name": "<entra id federated credentials unique name>" // e.g. plugin-entra-id
|
|
930
927
|
* }
|
package/lib/scimgateway.ts
CHANGED
|
@@ -486,7 +486,7 @@ export class ScimGateway {
|
|
|
486
486
|
getMethod: 'getAppRoles',
|
|
487
487
|
}
|
|
488
488
|
/** handlers supported url paths */
|
|
489
|
-
const handlers = ['users', 'groups', 'bulk', 'serviceplans', 'approles', 'api', 'schemas', 'resourcetypes', 'serviceproviderconfig', 'serviceproviderconfigs', 'oauth', 'logger']
|
|
489
|
+
const handlers = ['users', 'groups', 'bulk', 'serviceplans', 'approles', 'api', 'schemas', 'resourcetypes', 'serviceproviderconfig', 'serviceproviderconfigs', 'oauth', '.well-known', 'logger']
|
|
490
490
|
|
|
491
491
|
try {
|
|
492
492
|
if (!fs.existsSync(configDir + '/wsdls')) fs.mkdirSync(configDir + '/wsdls')
|
|
@@ -972,53 +972,44 @@ export class ScimGateway {
|
|
|
972
972
|
)
|
|
973
973
|
}
|
|
974
974
|
|
|
975
|
-
// oauth well-known:
|
|
975
|
+
// oauth well-known: /.well-known/openid-configuration
|
|
976
976
|
// this.jwk is managed by helper-rest oauthJwtBearer - Entra ID Federated Identity
|
|
977
|
-
// {issuer: <scimgateway-baseUrl
|
|
978
|
-
// example issuer: https://scimgateway.my-company.com
|
|
977
|
+
// { issuer: <scimgateway-baseUrl>, kid: { privateKey, publicKey } }
|
|
978
|
+
// example issuer: https://scimgateway.my-company.com
|
|
979
979
|
const getHandlerOauthWellKnown = async (ctx: Context) => {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
if (!this.jwk || !this.jwk[baseEntity] || !this.jwk[baseEntity].issuer) {
|
|
980
|
+
logger.debug(`${gwName}[${pluginName}] [oauth] .well-known request`)
|
|
981
|
+
if (!this.jwk || (Object.keys(this.jwk).length < 1)) {
|
|
984
982
|
ctx.response.body = '{}'
|
|
985
983
|
ctx.response.status = 200
|
|
986
984
|
return ctx
|
|
987
985
|
}
|
|
988
|
-
|
|
989
|
-
const issuer = this.jwk[baseEntity].issuer // dynamic set by helper-rest oauthJwtBearer e.g. 'https://scimgateway.my-company.com/oauth'
|
|
986
|
+
const issuer = this.jwk.issuer
|
|
990
987
|
let body = {
|
|
991
988
|
issuer,
|
|
992
|
-
jwks_uri: issuer + '/
|
|
989
|
+
jwks_uri: issuer + '/.well-known/jwks.json',
|
|
993
990
|
}
|
|
994
991
|
ctx.response.body = JSON.stringify(body)
|
|
995
992
|
ctx.response.status = 200
|
|
996
993
|
}
|
|
997
994
|
|
|
998
|
-
// oauth JWKS: /
|
|
995
|
+
// oauth JWKS: /.well-known/jwks.json
|
|
999
996
|
// this.jwk is managed by helper-rest oauthJwtBearer - Entra ID Federated Identity
|
|
1000
|
-
// {issuer: <scimgateway-baseUrl
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
if (!this.jwk || !this.jwk[baseEntity]) {
|
|
997
|
+
// { issuer: <scimgateway-baseUrl>, kid: { privateKey, publicKey } }
|
|
998
|
+
const getHandlerOauthJwks = async (ctx: Context) => {
|
|
999
|
+
logger.debug(`${gwName}[${pluginName}] [oauth] jwks_uri request`)
|
|
1000
|
+
if (!this.jwk || (Object.keys(this.jwk).length < 1)) {
|
|
1006
1001
|
ctx.response.body = '{"keys":[]}'
|
|
1007
1002
|
ctx.response.status = 200
|
|
1008
1003
|
return ctx
|
|
1009
1004
|
}
|
|
1010
|
-
|
|
1011
1005
|
const keys: Array<Record<string, any>> = []
|
|
1012
|
-
for (const
|
|
1013
|
-
const keyObj = this.jwk[
|
|
1014
|
-
if (typeof keyObj !== 'object' || keyObj === null) continue
|
|
1015
|
-
const jwk = await jose.exportJWK(this.jwk[
|
|
1016
|
-
jwk.kid =
|
|
1017
|
-
.update(JSON.stringify(jwk))
|
|
1018
|
-
.digest('base64url')
|
|
1006
|
+
for (const kid in this.jwk) {
|
|
1007
|
+
const keyObj = this.jwk[kid]
|
|
1008
|
+
if (typeof keyObj !== 'object' || keyObj === null) continue
|
|
1009
|
+
const jwk = await jose.exportJWK(this.jwk[kid].publicKey)
|
|
1010
|
+
jwk.kid = kid // needed for JWKS
|
|
1019
1011
|
keys.push(jwk)
|
|
1020
1012
|
}
|
|
1021
|
-
|
|
1022
1013
|
let body = {
|
|
1023
1014
|
keys,
|
|
1024
1015
|
}
|
|
@@ -2673,13 +2664,13 @@ export class ScimGateway {
|
|
|
2673
2664
|
return ctx
|
|
2674
2665
|
}
|
|
2675
2666
|
}
|
|
2676
|
-
if (ctx.request.method === 'GET' && ctx.path.endsWith('
|
|
2667
|
+
if (ctx.request.method === 'GET' && ctx.path.endsWith('/.well-known/openid-configuration')) {
|
|
2677
2668
|
await getHandlerOauthWellKnown(ctx)
|
|
2678
2669
|
if (!ctx.response.status) ctx.response.status = 404
|
|
2679
2670
|
return ctx
|
|
2680
2671
|
}
|
|
2681
|
-
if (ctx.request.method === 'GET' && ctx.path.endsWith('/
|
|
2682
|
-
await
|
|
2672
|
+
if (ctx.request.method === 'GET' && ctx.path.endsWith('/.well-known/jwks.json')) {
|
|
2673
|
+
await getHandlerOauthJwks(ctx)
|
|
2683
2674
|
if (!ctx.response.status) ctx.response.status = 404
|
|
2684
2675
|
return ctx
|
|
2685
2676
|
}
|
package/package.json
CHANGED