(aws_iam) Support auto-deleting MFA devices for IAM users
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the feature
This feature would add an optional property `autoDeleteMfaDevices` to `iam.User`. When set to `true`, a `CustomResource` would be created for the user to automatically delete all MFA devices associated with that user `onDelete`.
### Use Case
When a user has an associated MFA device, it cannot be deleted. CloudFormation returns the following error message:
> 5:23:44 PM | DELETE_FAILED | AWS::IAM::User | blimmertest1C52AD1B
> Cannot delete entity, must remove tokens from principal first. (Service: AmazonIdentityManagement; Status Code: 409; Error Code: DeleteConflict; Request ID: d7aa61e8f631-4798-b8d7-64d829dnotreald3; Proxy: null)
This makes it difficult to manager users via aws-cdk, as most organizations require MFA to be enabled.
### Proposed Solution
I've worked around this by implementing my own custom resource. Here's the relevant code:
```ts
private createDeleteUsersHelper(): Provider {
const deleteUsersHelper = new NodejsFunction(this, "DeleteUsersHelperFunction", {
description: "Helper to delete CDK-managed IAM users. MFA devices must be removed before users can be deleted.",
entry: path.join(__dirname, "..", "custom-resource-providers", "delete-users-helper", "index.ts"),
timeout: Duration.seconds(10),
initialPolicy: [
new PolicyStatement({
sid: "AllowListMfaDevices",
effect: Effect.ALLOW,
actions: ["iam:ListMFADevices"],
resources: [managedUserArn("*")], // this returns a wildcard ARN for the path used by this CDK app (e.g., `arn:aws:iam::*:user/${MANAGED_PATH}/*`;
}),
new PolicyStatement({
sid: "AllowDeactivateMfaDevices",
effect: Effect.ALLOW,
actions: ["iam:DeactivateMFADevice"],
resources: [managedUserArn("*")],
}),
],
logRetention: RetentionDays.ONE_MONTH,
});
return new Provider(this, "CustomResourceProvider", {
onEventHandler: deleteUsersHelper,
logRetention: RetentionDays.ONE_MONTH,
});
}
```
Then when I create the user, I utilize this custom resource:
```ts
const user = new User(this, userName, {
userName: userName,
groups: usersForStage[userName],
path: MANAGED_PATH,
password: new Secret(this, `${userName}InitialPassword`, {
secretName: `${userName}InitialPassword`,
}).secretValue,
passwordResetRequired: true,
});
new CustomResource(this, `${userName}DeleteHelper`, {
resourceType: "Custom::DeleteUsersHelper",
serviceToken: this.deleteUsersHelper.serviceToken,
properties: {
User: user.userName,
},
});
```
And here's the custom resource handler:
```ts
import { DeactivateMFADeviceCommand, IAMClient, ListMFADevicesCommand } from "@aws-sdk/client-iam";
import { CloudFormationCustomResourceEvent } from "aws-lambda";
const iamClient = new IAMClient({});
export async function handler(event: CloudFormationCustomResourceEvent) {
switch (event.RequestType) {
case "Create":
return;
case "Update":
return;
case "Delete":
return onDelete(event.ResourceProperties?.User);
}
}
async function onDelete(user?: string) {
if (!user) {
throw new Error("No User property was provided.");
}
await deleteAllMfaDevices(user);
}
async function deleteAllMfaDevices(user: string) {
const { MFADevices } = await iamClient.send(
new ListMFADevicesCommand({
UserName: user,
}),
);
if (!MFADevices?.length) {
console.info(`${user} didn't have any MFA devices. Nothing to clean up before deleting the user.`);
return;
}
console.info(`${user} had ${MFADevices.length} to clean up`);
for (const mfaDevice of MFADevices) {
console.info(`Deleting MFA device ${mfaDevice.SerialNumber}`);
await iamClient.send(
new DeactivateMFADeviceCommand({
UserName: mfaDevice.UserName,
SerialNumber: mfaDevice.SerialNumber,
}),
);
}
}
```
### Other Information
If we wanted to use a singleton function for this, we'd need to figure out how to scope the policy properly. This seems like it might not be possible, so we might have to use one provider per-user unless anyone has any clever thoughts (maybe a tag?).
### Acknowledgements
- [X] I may be able to implement this feature request
- [ ] This feature might incur a breaking change
### CDK version used
2.19.0
### Environment details (OS name and version, etc.)
macOS 12.3.1
Contributor guide
Research direction
Start at the iam.User implementation and inspect existing custom-resource providers, then compare the proposed entry point custom-resource-providers/delete-users-helper/index.ts with the lifecycle behavior described here. Done means an optional autoDeleteMfaDevices property removes associated MFA devices during user deletion, with coverage for users with and without MFA devices.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- cloud, security
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100