Triggering aws automation when user added or removed to Entra AD
Sometimes we need to automate certain tasks — like creating and assigning permission sets to users when they join the organization, and…
Triggering aws automation when user added or removed to Entra AD
Sometimes we need to automate certain tasks — like creating and assigning permission sets to users when they join the organization, and cleaning them up when they leave. Since we’re using Entra ID (formerly Azure AD) as our external identity provider, one straightforward approach is to manage access through groups.
We can create a group in Entra ID, assign it a permission set in AWS Identity Center, and any user added to that group will automatically get the corresponding AWS access. This works well for shared or role-based access.
However, it’s worth noting that there’s typically a delay — new users added in Entra ID can take up to an hour to appear in AWS Identity Center.
The challenge comes when we need to create permission sets that are specific to each user, such as personalized naming (e.g., prod_john.doe) or access that's unique to an individual's role. In those cases, the group-based approach isn’t enough, and we need more tailored automation to handle those scenarios.
- New user automation
We can automate the process of creating permission sets with predefined policies and assigning them to users using EventBridge and a Lambda function.
On the EventBridge side, we need to configure a rule that listens for the API call indicating a user has been created or added. Once this event is detected, it will trigger the Lambda function, which handles the creation of the required permission sets and assignments.
Below is an example of the event pattern you can use to trigger the Lambda:
{
"source": ["aws.sso"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["sso.amazonaws.com", "sso-directory.amazonaws.com"],
"eventName": ["CreateUser"]
}
}
2.Deleting permission sets
likewise we may need to delete the permission sets when a user is leaving or no permission sets needed related to him. On that case use below event from cloudtrail to trigger lambda to delete the permission sets.
{
"source": ["aws.sso"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["sso.amazonaws.com", "sso-directory.amazonaws.com"],
"eventName": ["DisableUser"]
}
}
When a user is removed from Entra ID (formerly Azure AD), two different API calls are involved: DisableUser and DeleteUser.
The DisableUser event represents a soft delete—AWS Identity Center keeps the user in a disabled state for up to 30 days. During this period, the user will no longer have access, but their identity and assignments are still retained temporarily as a precaution.
After this grace period, a DeleteUser API call is triggered. This is a hard delete, and the user is permanently removed from Identity Center, along with all metadata, making it much harder to track what permissions the user had.
For automation purposes, it’s best to trigger clean-up workflows — such as removing permission sets — based on the DisableUser event. Waiting for the DeleteUser event makes it difficult to retrieve and clean up permissions because the user no longer exists in the system.
Below is a sample script you can use to list which permission sets are assigned to each user or group:
import boto3, json
idstoreclient = boto3.client('identitystore')
ssoadminclient = boto3.client('sso-admin')
orgsclient= boto3.client('organizations')
users={}
groups={}
permissionSets={}
Accounts=[]
Instances= (ssoadminclient.list_instances()).get('Instances')
InstanceARN=Instances[0].get('InstanceArn')
IdentityStoreId=Instances[0].get('IdentityStoreId')
#Dictionary mapping User IDs to usernames
def mapUserIDs():
ListUsers=idstoreclient.list_users(IdentityStoreId=IdentityStoreId)
ListOfUsers=ListUsers['Users']
while 'NextToken' in ListUsers.keys():
ListUsers=idstoreclient.list_users(IdentityStoreId=IdentityStoreId,NextToken=ListUsers['NextToken'])
ListOfUsers.extend(ListUsers['Users'])
for eachUser in ListOfUsers:
users.update({eachUser.get('UserId'):eachUser.get('UserName')})
mapUserIDs()
#Dictionary mapping Group IDs to display names
def mapGroupIDs():
ListGroups=idstoreclient.list_groups(IdentityStoreId=IdentityStoreId)
ListOfGroups=ListGroups['Groups']
while 'NextToken' in ListGroups.keys():
ListGroups=idstoreclient.list_groups(IdentityStoreId=IdentityStoreId,NextToken=ListGroups['NextToken'])
ListOfGroups.extend(ListGroups['Groups'])
for eachGroup in ListOfGroups:
groups.update({eachGroup.get('GroupId'):eachGroup.get('DisplayName')})
mapGroupIDs()
#Dictionary mapping permission set ARNs to permission set names
def mapPermissionSetIDs():
ListPermissionSets=ssoadminclient.list_permission_sets(InstanceArn=InstanceARN)
ListOfPermissionSets=ListPermissionSets['PermissionSets']
while 'NextToken' in ListPermissionSets.keys():
ListPermissionSets=ssoadminclient.list_permission_sets(InstanceArn=InstanceARN,NextToken=ListPermissionSets['NextToken'])
ListOfPermissionSets.extend(ListPermissionSets['PermissionSets'])
for eachPermissionSet in ListOfPermissionSets:
permissionSetDescription=ssoadminclient.describe_permission_set(InstanceArn=InstanceARN,PermissionSetArn=eachPermissionSet)
permissionSetDetails=permissionSetDescription.get('PermissionSet')
permissionSets.update({permissionSetDetails.get('PermissionSetArn'):permissionSetDetails.get('Name')})
mapPermissionSetIDs()
#Listing Permissionsets provisioned to an account
def GetPermissionSetsProvisionedToAccount(AccountID):
ListOfPermissionSetsProvisionedToAccount=[]
PermissionSetsProvisionedToAccount=ssoadminclient.list_permission_sets_provisioned_to_account(InstanceArn=InstanceARN,AccountId=AccountID)
try:
ListOfPermissionSetsProvisionedToAccount = PermissionSetsProvisionedToAccount['PermissionSets']
while 'NextToken' in PermissionSetsProvisionedToAccount.keys():
PermissionSetsProvisionedToAccount=ssoadminclient.list_permission_sets_provisioned_to_account(InstanceArn=InstanceARN,AccountId=AccountID,NextToken=PermissionSetsProvisionedToAccount['NextToken'])
ListOfPermissionSetsProvisionedToAccount.extend(PermissionSetsProvisionedToAccount['PermissionSets'])
return(ListOfPermissionSetsProvisionedToAccount)
except:
return(ListOfPermissionSetsProvisionedToAccount)
#To retrieve the assignment of each permissionset/user/group/account assignment
def ListAccountAssignments(AccountID):
PermissionSetsList=GetPermissionSetsProvisionedToAccount(AccountID)
Assignments=[]
for permissionSet in PermissionSetsList:
AccountAssignments=ssoadminclient.list_account_assignments(InstanceArn=InstanceARN,AccountId=AccountID,PermissionSetArn=permissionSet)
Assignments.extend(AccountAssignments['AccountAssignments'])
while 'NextToken' in AccountAssignments.keys():
AccountAssignments=ssoadminclient.list_aaccount_assignments(InstanceArn=InstanceARN,AccountId=AccountID,PermissionSetArn=permissionSet,NextToken=AccountAssignments['NextToken'])
Assignments.extend(AccountAssignments['AccountAssignments'])
return(Assignments)
#To list all the accounts in the organization
def ListAccountsInOrganization():
AccountsList=orgsclient.list_accounts()
ListOfAccounts=AccountsList['Accounts']
while 'NextToken' in AccountsList.keys():
AccountsList=orgsclient.list_accounts(NextToken=AccountsList['NextToken'])
ListOfAccounts.extend(AccountsList['Accounts'])
for eachAccount in ListOfAccounts:
Accounts.append(str(eachAccount.get('Id')))
return(Accounts)
#To translate set datatype to json
class SetEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return json.JSONEncoder.default(self, obj)
def GetListOfAssignmentsForPermissionSets():
ListOfAccountIDs=ListAccountsInOrganization()
entries=[]
PermissionSetListForAssignments={}
for eachAccountID in ListOfAccountIDs:
GetAccountAssignments=ListAccountAssignments(eachAccountID)
for eachAssignment in GetAccountAssignments:
if(permissionSets.get(eachAssignment.get('PermissionSetArn'))) not in PermissionSetListForAssignments.keys():
SetOfUsersandGroups={'Users':set(),'Groups':set()}
PermissionSetListForAssignments[permissionSets.get(eachAssignment.get('PermissionSetArn'))]=SetOfUsersandGroups
SetOfUsersandGroups=PermissionSetListForAssignments.get(permissionSets.get(eachAssignment.get('PermissionSetArn')))
if(eachAssignment.get('PrincipalType')=='GROUP'):
setOfGroups=SetOfUsersandGroups.get('Groups')
setOfGroups.add(groups.get(eachAssignment.get('PrincipalId')))
SetOfUsersandGroups.update({'Groups':setOfGroups})
PermissionSetListForAssignments.update({permissionSets.get(eachAssignment.get('PermissionSetArn')):SetOfUsersandGroups})
else:
setOfUsers=SetOfUsersandGroups.get('Users')
setOfUsers.add(users.get(eachAssignment.get('PrincipalId')))
SetOfUsersandGroups.update({'Users':setOfUsers})
PermissionSetListForAssignments.update({permissionSets.get(eachAssignment.get('PermissionSetArn')):SetOfUsersandGroups})
with open("AssignmentsForPermissionSets.json", "w") as outfile:
json.dump(PermissionSetListForAssignments, outfile, cls=SetEncoder)
print("Done!AssignmentsForPermissionSets.json generated successfully!")
GetListOfAssignmentsForPermissionSets()
You receive the output as a JSON file titled AssignmentsForPermissionSets. This contains the extracted information of all the users and groups that are assigned to all the permission sets in IAM Identity Center. Here’s an example output:
{ "AdministratorAccess": {
"Users": [
"Charlie",
"Ted"
],
"Groups": [
"Admins",
"Developers"
]
},
"PowerUserAccess": {
"Users": [
"Chandler",
"Joey"
],
"Groups": [
"Developers",
"Testers"
]
},
"SystemAdministrator": {
"Users": [
"Sherlock"
],
"Groups": [
"DevOps"
]
}
} 메타데이터
- post_id
- 975eb3435f9f
- slug
- triggering-aws-automation-when-user-added-or-removed-to-entra-ad-975eb3435f9f
- url
- https://medium.com/@vinusan129/triggering-aws-automation-when-user-added-or-removed-to-entra-ad-975eb3435f9f
- canonical_url
- https://medium.com/@vinusan129/triggering-aws-automation-when-user-added-or-removed-to-entra-ad-975eb3435f9f
- author_url
- https://medium.com/@vinusan129
- status
- ok
- fetched_at
- 2026-06-25 07:00:49