StackSet template for deploying Onelogin with AWS Control Tower
AWSTemplateFormatVersion: 2010-09-09 | |
Description: Create OneLogin IDP, Role to allow OL extract account role list and default roles on all accounts. | |
Metadata: | |
AWS::CloudFormation::Interface: | |
ParameterGroups: | |
- | |
Label: | |
default: "OneLogin SAML IDP" | |
Parameters: | |
- OLMetadataUrl | |
- OLExternalId | |
- | |
Label: | |
default: "Roles" | |
Parameters: | |
- EnableAdminRole | |
- AdminRoleName | |
- EnablePowerUserRole | |
- PowerUserRoleName | |
- EnableReadOnlyRole | |
- ReadOnlyRoleName | |
Mappings: | |
OLMapping: | |
IDPName: | |
Name: OLIdP | |
RoleName: | |
Name: OLGetRoles | |
Parameters: | |
OLMetadataUrl: | |
Type: String | |
Description: Publicly accessible HTTPS location where SAML metadata.xml can be downloaded. | |
OLExternalId: | |
Type: String | |
Description: (Optional) OneLogin External ID generated at the OL App Configuration tab used to in. Used in the Onelogin integration that extracts the roles from AWS. | |
AdminRoleName: | |
Type: String | |
Description: Role name for administrator access. | |
Default: AdministratorAccessRole | |
PowerUserRoleName: | |
Type: String | |
Description: Role name for administaror but IAM access. | |
Default: PowerUserAccessRole | |
ReadOnlyRoleName: | |
Type: String | |
Description: Role name for read-only access. | |
Default: ReadOnlyAccessRole | |
EnableAdminRole: | |
Type: String | |
Default: 'true' | |
Description: Create an administrative role. | |
AllowedValues: | |
- 'true' | |
- 'false' | |
EnablePowerUserRole: | |
Type: String | |
Default: 'true' | |
Description: Create an administrative but IAM role. | |
AllowedValues: | |
- 'true' | |
- 'false' | |
EnableReadOnlyRole: | |
Type: String | |
Default: 'true' | |
Description: Create a read-only role. | |
AllowedValues: | |
- 'true' | |
- 'false' | |
Conditions: | |
CreateAdminRole: !Equals | |
- !Ref EnableAdminRole | |
- 'true' | |
CreatePowerUserRole: !Equals | |
- !Ref EnablePowerUserRole | |
- 'true' | |
CreateReadOnlyRole: !Equals | |
- !Ref EnableReadOnlyRole | |
- 'true' | |
Resources: | |
AdminRole: | |
Type: AWS::IAM::Role | |
Condition: CreateAdminRole | |
Properties: | |
RoleName: !Ref AdminRoleName | |
AssumeRolePolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Principal: | |
Federated: | |
- !GetAtt SAMLIdp.SAMLProviderArn | |
Action: | |
- sts:AssumeRoleWithSAML | |
Condition: | |
StringEquals: | |
'SAML:aud': | |
'https://signin.aws.amazon.com/saml' | |
Path: / | |
ManagedPolicyArns: | |
- arn:aws:iam::aws:policy/AdministratorAccess | |
PowerUserRole: | |
Type: AWS::IAM::Role | |
Condition: CreatePowerUserRole | |
Properties: | |
RoleName: !Ref PowerUserRoleName | |
AssumeRolePolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Principal: | |
Federated: | |
- !GetAtt SAMLIdp.SAMLProviderArn | |
Action: | |
- sts:AssumeRoleWithSAML | |
Condition: | |
StringEquals: | |
'SAML:aud': | |
'https://signin.aws.amazon.com/saml' | |
Path: / | |
ManagedPolicyArns: | |
- arn:aws:iam::aws:policy/PowerUserAccess | |
ReadOnlyRole: | |
Type: AWS::IAM::Role | |
Condition: CreateReadOnlyRole | |
Properties: | |
RoleName: !Ref ReadOnlyRoleName | |
AssumeRolePolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Principal: | |
Federated: | |
- !GetAtt SAMLIdp.SAMLProviderArn | |
Action: | |
- sts:AssumeRoleWithSAML | |
Condition: | |
StringEquals: | |
'SAML:aud': | |
'https://signin.aws.amazon.com/saml' | |
Path: / | |
ManagedPolicyArns: | |
- arn:aws:iam::aws:policy/ReadOnlyAccess | |
OLListRolesRole: | |
Type: AWS::IAM::Role | |
Properties: | |
RoleName: !FindInMap [OLMapping, "RoleName", "Name"] | |
Policies: | |
- PolicyName: listroles | |
PolicyDocument: | |
Statement: | |
- Effect: Allow | |
Action: | |
- iam:ListRoles | |
- iam:ListAccountAliases | |
Resource: '*' | |
AssumeRolePolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Principal: | |
AWS: 842984801698 | |
Action: | |
- sts:AssumeRole | |
Condition: | |
StringEquals: | |
'sts:ExternalId': | |
!Ref OLExternalId | |
Path: / | |
SAMLIdpLambdaRole: | |
Type: 'AWS::IAM::Role' | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: 'lambda.amazonaws.com' | |
Action: | |
- 'sts:AssumeRole' | |
Path: '/' | |
ManagedPolicyArns: | |
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' | |
Policies: | |
- PolicyName: createupdate | |
PolicyDocument: | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'iam:CreateSAMLProvider' | |
- 'iam:UpdateSAMLProvider' | |
- 'iam:DeleteSAMLProvider' | |
Resource: !Join | |
- '' | |
- - 'arn:aws:iam::*:saml-provider/' | |
- !FindInMap [OLMapping, "IDPName", "Name"] | |
- PolicyName: list | |
PolicyDocument: | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'iam:ListSAMLProviders' | |
Resource: '*' | |
SAMLIdpLambda: | |
Type: "AWS::Lambda::Function" | |
Properties: | |
Handler: "index.handler" | |
Role: | |
Fn::GetAtt: | |
- "SAMLIdpLambdaRole" | |
- "Arn" | |
Code: | |
ZipFile: > | |
var cfr = require('./cfn-response'); | |
var AWS = require('aws-sdk'); | |
var iam = new AWS.IAM(); | |
var https = require('https'); | |
var fs = require('fs'); | |
exports.handler = function(event, context) { | |
console.log(event); | |
var resID = event.PhysicalResourceId || event.RequestId; | |
var metadataUrl = event.ResourceProperties.metadataUrl; | |
var idpName = event.ResourceProperties.idpName; | |
var filename = '/tmp/metadata.xml' | |
var file = fs.createWriteStream(filename); | |
if (event.RequestType == 'Create' || event.RequestType == 'Update') { | |
var request = https.get(metadataUrl, function(response) { | |
response.pipe(file); | |
file.on('finish', function() { | |
file.close( function () { | |
let rawdata = fs.readFileSync(filename, 'utf8'); | |
if (event.RequestType == 'Update') { | |
var paramslist = {}; | |
iam.listSAMLProviders(paramslist, function(err, data) { | |
if (err) cfr.send(event, context, cfr.FAILED, err, resID); | |
else { | |
var arn=''; | |
for (var provider in data.SAMLProviderList) { | |
if (data.SAMLProviderList[provider].Arn.split('/')[1] == idpName) { | |
console.log('Update provider. ' + data.SAMLProviderList[provider].Arn + ' already exists.') | |
arn = data.SAMLProviderList[provider].Arn; | |
} | |
} | |
if (arn == '') { | |
var params = { Name: idpName, SAMLMetadataDocument: rawdata }; | |
iam.createSAMLProvider(params, function(err, data) { | |
if (err) cfr.send(event, context, cfr.FAILED, err, resID); | |
else cfr.send(event, context, cfr.SUCCESS, data, resID); | |
}); | |
} else { | |
var params = { SAMLProviderArn: arn, SAMLMetadataDocument: rawdata }; | |
iam.updateSAMLProvider(params, function(err, data) { | |
if (err) cfr.send(event, context, cfr.FAILED, err, resID); | |
else cfr.send(event, context, cfr.SUCCESS, data, resID); | |
}); | |
} | |
} | |
}); | |
} else { | |
var params = { Name: idpName, SAMLMetadataDocument: rawdata }; | |
iam.createSAMLProvider(params, function(err, data) { | |
if (err) cfr.send(event, context, cfr.FAILED, err, resID); | |
else cfr.send(event, context, cfr.SUCCESS, data, resID); | |
}); | |
} | |
}); | |
}); | |
}).on('error', function(err) { | |
fs.unlink(filename); | |
cfr.send(event, context, cfr.FAILED, err); | |
}); | |
} else if (event.RequestType == 'Delete') { | |
var paramslist = {}; | |
iam.listSAMLProviders(paramslist, function(err, data) { | |
if (err) cfr.send(event, context, cfr.FAILED, err, resID); | |
else { | |
var deleteResource = false; | |
var arn; | |
console.log(data.SAMLProviderList); | |
for (var provider in data.SAMLProviderList) { | |
if (data.SAMLProviderList[provider].Arn.split('/')[1] == idpName) { | |
console.log('Delete provider. ' + data.SAMLProviderList[provider].Arn + ' already exists.') | |
deleteResource = true; | |
arn = data.SAMLProviderList[provider].Arn; | |
} | |
} | |
var params = { SAMLProviderArn: arn }; | |
if (deleteResource == true) { | |
iam.deleteSAMLProvider(params, function(err, data) { | |
if (err) cfr.send(event, context, cfr.FAILED, err, resID); | |
else cfr.send(event, context, cfr.SUCCESS, data, resID); | |
}); | |
} else { | |
cfr.send(event, context, cfr.SUCCESS, {success: 'Provider did not exist'}, resID); | |
} | |
} | |
}); | |
} else { | |
cfr.send(event, context, cfr.FAILED, { error: 'Method not supported'}, resID); | |
} | |
}; | |
Runtime: "nodejs10.x" | |
Timeout: "30" | |
SAMLIdp: | |
Type: Custom::SAMLIdp | |
Properties: | |
ServiceToken: !GetAtt SAMLIdpLambda.Arn | |
metadataUrl: !Ref OLMetadataUrl | |
idpName: !FindInMap [OLMapping, "IDPName", "Name"] |
This comment has been minimized.
This comment has been minimized.
@fss18 How do you see those load errors? How do you debug the lambda function? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Can you update line #314 and set runtime to nodejs10.x ?
CloudFormation takes nodejs10.x as runtime instead of nodejs10.13
ref: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
There are also a few libraries load error when I tried to run it using nodejs10.x, perhaps you could zip the Lambda with all dependencies into S3 perhaps?
ref: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html#nodejs-package-dependencies