Why introducing AWS Lambda function
The main reason the integration is leveraging a Lamdba function is to overcome the lack of support in Okta for OAuth client credentials.
Source:
https://developer.okta.com/docs/concepts/scim/faqs/
Disclaimer
The Endpoint Credential Manager (ECM) Software Development Kit
Allows developers to create Custom ECM Plugins. The SDK comes with a Plugin example, which has been used as a starting point to create a new Plugin.
Any sample or proof of concept code (“Code”) provided on the Community is provided “as is” and without any express or implied warranties. This means that we do not promise that it will work for your specific needs or that it is error-free. Such Code is community supported and not supported directly by BeyondTrust, and it is not intended to be used in a production environment. BeyondTrust and its contributors are not liable for any damage you or others might experience from using the Code, including but not limited to, loss of data, loss of profits, or any interruptions to your business, no matter what the cause is, even if advised of the possibility of such damage.
Q: How should I be managing authentication to my SCIM API?
Okta recommends using the OAuth 2.0 Authorization Code grant flow. Okta doesn't support the Client Credentials or Resource Owner Password Credentials Authorization grant flows for SCIM. The Authorization Code grant flow is more common in SaaS and cloud integrations and is also more secure.
Comment: Okta is stating that Authorization Code grant flow is more secure, but it means that when Okta SCIM App needs to authenticate to Password Safe, it would actually be Password Safe that would have to authenticate back to Okta, using Client ID and Client Secret generated by Okta. This translate into PAM trusting the Directory, when it should be the other around, from a Security Best Practices point of view.
Another reason is to enable the support for Okta Group Push. Password Safe requires a description attribute at creation time, and Okta only provides a displayName attribute, which makes the Okta Push provisioning request to fail, being an invalid request missing the mandatory attribute description.
AWS Lambda function
Prerequisites and configuration
- AWS instance or AWS free tier: https://aws.amazon.com/free
- Nodejs axios layer: https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html
- Function code:
import axios from 'axios';
export const handler = async (event) => {
// ####### Extract host and endpoint from Request Url ####################
console.log('event INCOMING = '+JSON.stringify(event));
const method = event.requestContext.http.method;
console.log('event method = '+method);
const endpoint = 'https:/'+event.rawPath;
let url = '';
if(event.rawQueryString){
url = endpoint+'?'+event.rawQueryString;
} else{
url = endpoint;
}
var host = event.rawPath.substring(1);
host = host.substring(0,host.indexOf('/'));
console.log('host = '+host+' endpoint = '+endpoint);
// ####### Extract values from base64 token ####################
const base64token = event.headers.authorization.substring(7);
const base64payload = Buffer.from(base64token, 'base64').toString('ascii');
console.log('base64token = '+base64token+'. base64payload = '+base64payload);
const base64obj = JSON.parse(base64payload);
const authUrl = base64obj.authURL;
const client_id = base64obj.client_id;
const client_secret = base64obj.client_secret;
const target = base64obj.target;
console.log('target = '+target+' authUrl = '+authUrl+'. client_id = '+client_id+' client_secret = '+client_secret);
// Change Header host value for gateway
event.headers.host = host;
// ####### Authenticate ####################
const data = 'grant_type=client_credentials';
const authorization = 'Basic ' + Buffer.from(client_id+':'+client_secret).toString('base64');
const authConfig = {
method: 'post',
rejectUnauthorized: false,
url: authUrl,
data: data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': authorization
}
};
console.log('authConfig = '+JSON.stringify(authConfig));
let authRes = await axios(authConfig);
console.log('authRes = '+JSON.stringify(authRes.data));
const access_token = authRes.data.access_token;
// ####### GET/POST/PATCH/PUT/DELETE data from target #########
If (target == "PasswordSafe" && endpoint == 'https://'+host+'/scim/v2/Groups' && method == 'POST'){
console.log('We have a Password Safe Create Group call body : '+JSON.stringify(event.body));
const body = JSON.parse(event.body);
const configG = {
method: method,
url: url,
headers: {
'content-type': event.headers-'content-type'],
accept: event.headers.accept,
authorization: 'Bearer '+access_token,
},
"data": {
"schemas": /
"urn:ietf:params:scim:schemas:core:2.0:Group"
],
"displayName": body.displayName,
"description": body.displayName,
"members": /]
}
};
console.log('Password Safe Create Group: configG = '+JSON.stringify(configG));
let resG = await axios(configG);
// Response - For testing only
resG.headersb'content-type'] = 'application/scim+json';
console.log('axios res data = '+JSON.stringify(resG.data));
console.log('axios res headers = '+JSON.stringify(resG.headers));
const response = {
statusCode: 200,
body: JSON.stringify(resG.data),
headers: resG.headers
};
return response;
} else{
if(event.headers 'content-type']) {'Content-Type = '+console.log(event.headerso'content-type']);}
let config = {};
if(event.body) {
console.log('body = '+JSON.stringify(event.body));
config = {
method: method,
url: url,
headers: {
'content-type': event.headers-'content-type'],
accept: event.headers.accept,
authorization: 'Bearer '+access_token,
},
data: JSON.parse(event.body)
};
} else {
config = {
method: method,
url: url,
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer '+access_token
}
};
}
console.log('axios config = '+JSON.stringify(config));
let res = await axios(config);
// Response – For Okta
res.headerst'content-type'] = 'application/scim+json';
console.log('axios res data = '+JSON.stringify(res.data));
console.log('axios res headers = '+JSON.stringify(res.headers));
const response = {
statusCode: 200,
body: JSON.stringify(res.data),
headers: res.headers
};
return response;
}
};



Before configuration Okta SCIM App
SCIM 2.0 Base Url and OAuth Bearer Token
The SCIM 2.0 Base Url is the Function URL with the Password Safe Url (minus https://) appended to it, for example:



To create the Bearer Token for Okta, we must first create a JSON document:
{"target":"PasswordSafe","authURL":"https://pws_instance.ps.beyondtrustcloud.com/scim/oauth/token","client_id":"b0d00123456abcde4a7611b9","client_secret":"63eee16d6b3456789012vfdes1bc5e6"}
Then we need to create a base64 encoded version, e.g. by using https://base64encode.org :
eyJ0YXJnZXQiOiJQYXNzd29yZFNhZmUiLCJhdXRoVVJMIjoiaHR0cHM6Ly9wd3NfaW5zdGFuY2UucHMuYmV5b25kdHJ1c3RjbG91ZC5jb20vc2NpbS9vYXV0aC90b2tlbiIsImNsaWVudF9pZCI6ImIwZDAwMTIzNDU2YWJjZGU0YTc2MTFiOSIsImNsaWVudF9zZWNyZXQiOiI2M2VlZTE2ZDZiMzQ1Njc4OTAxMnZmZGVzMWJjNWU2In0=
Configure Okta SCIM App
SCIM 2.0 Base Url and OAuth Bearer Token









Supported Use Cases
Overview





