aws / aws/aws-cdk

(aws_iam) Support auto-deleting MFA devices for IAM users

Open
#19,806 1 comment 11 reactions 0 assignees View on GitHub
@aws-cdk/aws-iam effort/medium feature-request p2
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.