AWS Database Blog

Preventing Accidental Table Deletion in DynamoDB

Edin Zulich, AWS NoSQL solutions architect

It’s easy to delete a table in Amazon DynamoDB, and that means that it’s easy to delete one by accident, too. Fortunately, you can minimize the risk of accidentally deleting a table using AWS Identity and Access Management (IAM), which provides authentication and access control for DynamoDB.

As a managed service, DynamoDB is fully integrated with IAM. You can use IAM to tailor access control to resources and operations in DynamoDB. Using IAM roles, policies, and groups, you can implement a security configuration to ensure that DynamoDB resources are only accessed and modified the way you want. This approach includes preventing accidental table deletion.

For our purposes—preventing accidental table deletion—we will use IAM roles to control access to the DynamoDB DeleteTable operation. This approach requires the user to take an extra step to delete a table: Switch to a special IAM role. In other words, users won’t be able to delete a table by simply invoking the DeleteTable operation. They will have to assume a different IAM role first. This step will prevent accidental deletion of a table.

In this blog post, we show how to disable the DeleteTable operation, create a special “delete table” role, attach it to an IAM group that should have DeleteTable permissions, and then use the role. We also show how to add a requirement to the role to use multi-factor authentication (MFA), and how to prevent role switching in the console.

Prerequisites

  1. To get the most out of this post, we recommend that you have some basic knowledge of IAM—at minimum, how to create policies, groups, and roles, and how to attach policies. This post doesn’t address these tasks step by step, but you can consult the IAM User Guide for more information.
  2. The steps described in this post require administrative privileges in your AWS account. You’ll need IAM permissions for creating roles, policies, and groups. You’ll also need permissions for managing IAM users.

To demonstrate this, we will use the following example: a DynamoDB table, Foo, and an IAM group, FooAdmin, that grants users access to it via the IAM policy FooAdminAccess. This policy allows the DeleteTable operation, which means that the members of FooAdmin can delete the table Foo.

In this example, an IAM user, bob, is a member of FooAdmin, and therefore has the permissions needed to delete the table Foo. For the sake of brevity, we will assume that no other users or groups have access to the table Foo or have permissions to delete it. We want the members of FooAdmin to still be allowed to delete the table Foo—just not by accident.

Here are the steps we will take to create a new role, where members of FooAdmin will have to switch to this role in order to delete the table, Foo:

  1. Disable the DeleteTable operation for members of the FooAdmin group.
  2. Create a dedicated role, DeleteTableFoo, to grant access to the DeleteTable action for the table Foo.
  3. Grant the FooAdmin group permissions to assume the DeleteTableFoo role.

Next, we’ll get into the relevant details of each step.

1. Disable the DeleteTable Operation

To prohibit the DeleteTable operation, we will use the following IAM policy:

{
    "Version": "2012-10-17",
    "Statement": [
	{
            "Action": [
                "dynamodb:DeleteTable"
            ],
            "Effect": "Deny",
            "Resource": "*"
	}
    ]
}

This policy explicitly denies the DeleteTable operation for all tables, as indicated by the “*”. This approach is recommended in production environments, where we don’t want to take any chances. It has the effect of taking away the DeleteTable operation for all tables. Thus, users covered by this policy no longer can delete any tables. We use an explicit “deny” because it takes precedence over “allow,” so we don’t have to worry about an existing “allow” creating an opening in our wall.

We can put this policy into effect in a few different ways:

  1. Add this policy to an existing policy that is already attached to our FooAdmin group, which is FooAdminAccess in our example.
  2. Create a new policy (call it DynamoDBNoDeleteTable) and attach it to FooAdmin group. IAM has a limit on the number of policies that can be attached to a group – 10. It’s good to keep that in mind.
  3. Create a new policy, as in 2., but attach it to a different group—a group that we use to apply common policies to all IAM users. In this case, we need to make sure that all IAM users are members of that group. By doing so, we ensure that all users are denied the DeleteTable operation, even in case a different admin group is created. The policy should also be added to any roles where we are using SAML.

Whichever option we choose, members of the FooAdmin group won’t be able to delete any tables. One benefit of having a dedicated policy for this statement is visibility—a standalone policy is easier to find and see, and therefore manage, than a statement buried inside a larger policy. Enforcing a “do not delete table” business rule becomes a matter of attaching our DynamoDBNoDeleteTable policy to the appropriate IAM group or groups, rather than editing one or more existing policies.

2. Create a Dedicated “Delete Table” Role
Now that we have denied access to the DeleteTable operation for all of our tables, we’ll create a role that will allow deleting our table, Foo. We will first create a policy that defines the access permissions for the role, stating that the DeleteTable operation is allowed for Foo. This kind of policy is called an access policy, because it defines access to resources and actions on them. We will also create a trust policy for the role, which defines the trusted entities that we will allow to assume the role. The trust policy will actually be created for us during the process of configuring and creating the role. We can modify the trust policy later, after it has been created.

2a. Create an Access Policy for the Role
First, we will create a policy named DeleteTableFoo that grants permission to the DeleteTable operation for the Foo table. The policy looks like this:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DeleteTableFoo",
            "Effect": "Allow",
            "Action": [
                "dynamodb:DeleteTable"
            ],
            "Resource": [
                "arn:aws:dynamodb:AWS-REGION:AWS-ACCT:table/Foo"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudwatch:DeleteAlarms"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

AWS-REGION and AWS-ACCT are placeholders for the actual AWS Region and account number, respectively.

The cloudwatch:DeleteAlarms action allows the deletion of all Amazon CloudWatch alarms associated with the table. If you used the AWS Management Console to create a table, the console created CloudWatch alarms for that table by default. If you later delete the table using the console, and select the option Delete all CloudWatch alarms for this table, without the cloudwatch:DeleteAlarms statement, the console prints a warning message. This statement suppresses that warning. (The table will still be deleted.)  CloudWatch does not have any specific resources, so “*” is always used for the resource parameter.

Note that the policy doesn’t grant any other permissions. Thus, users who assume the role must switch back to their “normal” role to do anything else. This approach prevents users from making any mistakes that might otherwise occur.

2b. Create the Role 

Next, we create the role and attach the policy we just created (DeleteTableFoo) to the new role.

In the Roles section of the IAM console, choose Create New Role. For the role name, type DeleteTableFoo. In the next step, choose Role for Cross-Account Access, and then Provide access between AWS accounts you own.  Type your AWS account number in the box provided.

In this example, we are providing access within the same account. However, you can provide multiple accounts with access to the role. You can do this later, if needed, by editing the role’s trust relationship.

For added security, choose the option to require multi-factor authentication (MFA). By choosing this option, you require that users must provide multi-factor authentication to receive access to the role.

Finally, attach the new DeleteTableFoo policy to the role. Review your work, and then choose Finish.

If you want to edit the role’s trust policy, choose the Trust Relationships tab in the role window, and then choose Edit Trust Relationship. Our trust policy looks like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::AWS-ACCOUNT:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

Note the following about the policy:

  • The principal in this policy is the AWS account that contains the DynamoDB resources we are working with. This account also includes the group and users for which we are implementing access controls. However, the principal can be a different trusted account, or even multiple trusted accounts. The principal can also be more specific than the account identifier or can even be a third party identity provider. For more information, see Creating a Role for a Third-Party Identity Provider (Federation) in the IAM User Guide.
  • The MFA condition in the example is configured to require the presence of MFA. You can also configure it to specify the duration of MFA authorization in seconds. To do this, you use the aws:MultiFactorAuthAge condition key, instead of aws:MultiFactorAuthPresent. Either way, the users given access to the role must use MFA to assume the role.

This trust policy is only half of the trust relationship involved in using roles. On the side of the account granting access to the role (the grantor side), the trust policy specifies the trusted entity (or entities). In other words, the trust policy specifies the account or accounts that will have access to the role. The second half of the trust relationship is the trusted entity side, which grants the users or groups within the trusted account permission to assume the role. This second half is detailed in “3. Grant Permissions to Assume the Role,” below.

Suppose that we want to allow switching to this role in the console. To do this, we attach one more policy to it: AmazonDynamoDBReadOnlyAccess. This AWS-managed policy is required for the DynamoDB console to work correctly.

If we don’t want to allow switching to this role in the console, we can explicitly disable this. To do so, we need to add an external ID to the trust policy. To add this, add a condition with an external ID of your choosing to the Condition part of the trust policy:

      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "MY-EXTERNAL-ID”
        },
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }

The presence of an external ID disables console access for the role, so the only ways to assume the role are by using the AWS Command Line Interface (AWS CLI) or API. We will show how to assume the role in the CLI later in this post.

3. Grant Permissions to Assume the Role 

We have to do one last thing before we can start using the role: Complete the trust relationship. We do this by granting permissions to assume the role. Because we want the FooAdmin group to have the permissions to assume the role, we attach the following policy to the FooAdmin group:

{
    "Version": "2012-10-17",
	"Statement": {
		"Effect": "Allow",
		"Action": "sts:AssumeRole",
		"Resource": "arn:aws:iam::AWS-ACCOUNT:role/DeleteTableFoo”
	}
}

At this point, users who are members of the FooAdmin group can assume the DeleteTableFoo role and delete the Foo table. If you also chose to require MFA for the role, then those users will be required to use MFA to log in and then switch to the role. If you chose to use an external ID to disable switching to the role in the console, your users will have to use the CLI or API to assume the role and then delete the table.

For more details about this topic, see Granting a User Permissions to Switch Roles in the IAM User Guide.

Using the Role

Now we have our special role, DeleteTableFoo, ready for use. Users in the FooAdmin group can now assume the role and delete the Foo table. We will show two ways to accomplish this: in the AWS console, and using the AWS CLI.

Console

To make it easier for users to get started, we can give them the “switch role” URL for the DeleteTableFoo role. You can find this in the Summary page for the role in the AWS console (choose IAM, Roles, then DeleteTableFoo). The role URL will look like this:

https://signin.aws.amazon.com/switchrole?account=my-account-foo&roleName=DeleteTableFoo

Note the URL parameters in the role: the account alias (my-account-foo) and the role name (DeleteTableFoo). You can use the format of this URL to manually construct the link. For more information, see Providing Information to the User in the IAM documentation.

Make sure to log in before using this URL. In this example, suppose that user bob logs in, and that bob is a member of the FooAdmin group. Then, when the user bob goes to this URL, the browser displays the Switch Role login page:

To proceed, choose Switch Role. If there are any errors, verify the following:

  • The account and role name are correct.
  • The role isn’t configured to require an external ID.
  • If the role is configured to require MFA, you’ve logged in using MFA.
  • You are logged in as a user who is a member of the group that has the role permissions.

After you assume this role for the first time, you will be able to use Role History to switch between roles, as shown following.

When you have assumed the role, the login menu displays the active role, along with the user name you are currently using, and the AWS account ID. There is also an option to switch “back to bob”—choose this option if you want to switch back to your original user ID.

Role History in the user login menu shows the roles that you have recently assumed:

You can now switch to the role again, go to the DynamoDB console, and delete the Foo table. Because we attached the AmazonDynamoDBReadOnlyAccess policy to the role, the DynamoDB console displays all the tables present. However, it will only let you do one thing: Delete the Foo table.

 AWS CLI

To use the AWS CLI, you must ensure that it is configured properly. (For more on how to do this, see Configuring the AWS CLI in the CLI documentation.) You can then use the CLI to assume the role and delete the Foo table. To do this, you need to configure a profile for the role.

The AWS CLI docs cover this topic in detail in Assuming a Role, but here is the short version:  You configure the profile in your  ~/.aws/config file as follows, replacing the AWS-ACCOUNT placeholder with your account number:

 [profile deletetablefoo]
 role_arn = arn:aws:iam::AWS-ACCOUNT:role/DeleteTableFoo
 source_profile = bob
 mfa_serial = arn:aws:iam::AWS-ACCOUNT:mfa/bob
 external_id = 12345

To get role_arn, go to the Summary page in the IAM console. You need to have permissions to access IAM in order to do this. Otherwise, you’ll have to ask your administrator for role_arn, and some of the other values here.

The property source_profile is the profile for my user, bob. Note that bob is in the FooAdmin group, and therefore has the privileges to assume the role. If bob happens to be my default profile, I can use source_profile = default instead.

If the role is configured to require MFA, you need to set the property mfa_serial. To get the value for this, go to the Security Credentials tab in the IAM console, navigate to Users and then bob, and copy the value of Assigned MFA device. Finally, if you decide to disable switching the role in the console, you need to set the property external_id to the value of the external ID you used in the role configuration.

That’s it—we are now set up to assume the role and delete the table in the CLI, like so:

$ aws dynamodb delete-table --table-name Foo --region us-west-2 --profile deletetablefoo

 Enter MFA code:

 {
    "TableDescription": {
        "TableArn": "arn:aws:dynamodb:us-west-2:XXXXXXXXXXXX:table/Foo”, 
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0, 
            "WriteCapacityUnits": 5, 
            "ReadCapacityUnits": 5
        }, 
        "TableSizeBytes": 0, 
        "TableName": “Foo”, 
        "TableStatus": "DELETING", 
        "ItemCount": 0
    }
 }

Success! We received the response with a TableDescription and its TableStatus field set to DELETING. Notice how we assumed the role by specifying the profile we just configured, deletetablefoo.

Also, note that we were prompted for the MFA code, which we are required to provide. If we had tried to delete the table without specifying the correct role profile, we would have received the following error:

$aws dynamodb delete-table --table-name Foo 

An error occurred (AccessDeniedException) when calling the DeleteTable operation: User: arn:aws:iam::XXXXXXXXXXXX:user/bob is not authorized to perform: dynamodb:DeleteTable on resource: arn:aws:dynamodb:us-east-1:XXXXXXXXXXXX:table/Foo

The default profile is the user bob, which does not have the permissions required to use the DeleteTable operation. Thus, the attempt fails as desired. (Failing to specify the correct MFA code will also result in an error.)

Conclusion

In this post, we learned how to use IAM effectively to protect our DynamoDB tables from accidental deletion. We used the IAM roles to control table deletion, and showed how to apply additional security by requiring MFA for the role. We also showed how to prevent role switching in the console, by simply adding an external ID to our role configuration.

In our example, we never granted any permissions directly to a user. Using IAM groups, rather than users, is considered a best practice—even if there is only one user in the group. Another best practice is the principle of least privilege, where you grant only the permissions required to get the job done.

It is also worth emphasizing that you can configure roles so they can be assumed by various trusted entities, including other accounts or federated users. To learn more, see Creating IAM Roles in the IAM documentation.

Finally, while this post is specific to DynamoDB, you can use roles to protect sensitive resources with any of the AWS services that work with IAM. For example, you might require special protections for specific Amazon EC2 instances and control over who can invoke StopInstances and TerminateInstance operations on them. Or we might want to create a role for a specific IAM task, such as adding a user to a certain group. These are just a few examples that take advantage of the power of roles to delegate access to resources in a way tailored for specific purposes.