Securing resource tags used for authorization using a service control policy in AWS Organizations

In this post, I explain how you can use attribute-based access controls (ABAC) in Amazon Web Services (AWS) to help provision simple, maintainable access controls to different projects, teams, and workloads as your organization grows. ABAC gives you access to granular permissions and employee-attribute based authorization. By using ABAC, you need fewer AWS Identity and Access Management (IAM) roles and have less administrative overhead. The attributes used by ABAC in AWS are called tags, which are metadata associated with the principals (users or roles) and resources within your AWS account. Securing tags designated for authorization is important because they’re used to grant access to your resources. This post describes an approach to secure these tags across all of your AWS accounts through the use of AWS Organizations service control policies (SCPs) so that only authorized users can modify a resource’s tags. Organizations helps you centrally govern your accounts as you grow and scale your workloads on AWS; SCPs are a feature of Organizations that restricts what services and actions are allowed in your accounts. Together they provide flexible account management and security features that help you centrally secure your business.

Let’s say you want to give users a standard way to access their accounts and resources in AWS. For authentication, you want to continue using your existing identity provider (IdP). For authorization, you’ll need a method that can be scaled to meet the needs of your organization. To accomplish this, you can use ABAC in AWS, which requires fewer roles, helps you control permissions for many resources at once, and allows for simple access rules to different projects and teams. For example, all developers in your organization can have a project name assigned to their identities as a tag. The tag will be used as the basis for access to AWS resources via ABAC. Because access is granted based on these tags, they need to be secured from unauthorized changes.

Securing authorization tags

Using ABAC as your access control strategy for AWS depends on securing the tags associated with identities in your AWS organization’s accounts as well as with the resources those identities need to access. When users sign in, the authentication process looks something like this:

Figure 1: Using tags for secure access to resources

Figure 1: Using tags for secure access to resources

  1. Their identities are passed into AWS through federation.
  2. Federation is implemented through SAML assertions.
  3. Their identities each assume an AWS IAM role (via AWS Security Token Service).
  4. The project attribute is passed to AWS as a session tag named access-project (Rely on employee attributes from your corporate directory to create fine-grained permissions in AWS has full details).
  5. The session tag acts as a principal tag, and if its value matches the access-project authorization tag of the resource, the user is given access to that resource.

To ensure that the role’s session tags persist even after assuming subsequent roles, you also need to set the TransitiveTagKeys SAML attribute.

Now that identity tags are persisted, you need to ensure that the resource tags used for authorization are secured. For sake of brevity, let’s call these authorization tags. Let’s assume that you intend to use ABAC for all the accounts in your AWS organization and control access through an SCP. SCPs ensure that resource authorization tags are initially set to an authorized value, and secure the tags against modification once set.

Authorization tag access control requirements

Before you implement a policy to secure your resource’s authorization tag from unintended modification, you need to define the requirements that the policy will enforce:

  • After the authorization tag is set on a resource:
    • It cannot be modified except by an IAM administrator.
    • Only a principal whose authorization tag key and value pair exactly match the key and value pair of the resource can modify the non-authorization tags for that resource.
  • If no authorization tag has been set on a resource, and if the authorization tag is present on the principal:
    • The resource’s authorization tag key and value pair can only be set if exactly matching the key and value tag of the principal.
    • All other non-authorization tags can be modified.
  • If any authorization tags are missing from a principal, the principal shouldn’t be allowed to perform any tag operations.

Review an SCP for securing resource tags

Let’s take a look at an SCP that fulfills the access control requirements listed above. SCPs are similar to IAM permission policies, however, an SCP never grants permissions. Instead, SCPs are IAM policies that specify the maximum permissions for an organization, organizational unit (OU), or account. Therefore, the recommended solution is to also assign identity policies that allow modification of tags to all roles that need the ability to create and delete tags. You can then assign your SCP to an OU that includes all of the accounts that need resource authorization tags secured. In the example that follows, the AWS Services That Work with IAM documentation page would be used to identify the first set of resources with ABAC support that your organization’s developers will use. These resources include Amazon Elastic Compute Cloud (Amazon EC2), IAM users and roles, Amazon Relational Database Service (Amazon RDS), and Amazon Elastic File System (Amazon EFS). The SCP itself consists of three statements, which I review in the following section.

Deny modification and deletion of tags if a resource’s authorization tags don’t match the principal’s

This first policy statement is below. It addresses what needs to be enforced after the authorization tag is set on a resource. It denies modification of any tag—including the resource’s authorization tags—if the resource’s authorization tag doesn’t match the principal’s tag. Note that the resource’s authorization tag must exist. Let’s review the components of the statement.

{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyModifyTagsIfResAuthzTagAndPrinTagNotMatchedEc2", "Effect": "Deny", "Action": [ "ec2:CreateTags", "ec2:DeleteTags" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "ec2:ResourceTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "ec2:ResourceTag/access-project": false } } },

  • Line 6:
     "Effect": "Deny",

    First, specify the Effect to be Deny to deny the following actions under certain conditions.

  • Lines 7-10:
     "Action": [ "ec2:CreateTags", "ec2:DeleteTags"

    Specify the policy actions to be denied, which are ec2:CreateTags and ec2:DeleteTags. When combined with line 6, this denies modification of tags. The ec2:CreateTags action is included as it also grants the permission to overwrite tags, and we want to prevent that.

  • Lines 14-22:
     "Condition": { "StringNotEquals": { "ec2:ResourceTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "ec2:ResourceTag/access-project": false }

    Specify the conditions for which this statement—to deny policy actions—is in effect.

    The first condition operator is StringNotEquals. According to policy evaluation logic, if multiple condition keys are attached to an operator, each needs to evaluate as true in order for StringNotEquals to also evaluate as true.

  • Line 16:
     "ec2:ResourceTag/access-project": "${aws:PrincipalTag/access-project}",

    Evaluate if the access-project EC2 resource tag’s key and value pairs don’t match the principal’s.

  • Line 17:
     "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin"

    Evaluate if the principal is not the iam-admin role for the account. aws:PrincipalArn is a global condition key for comparing the Amazon Resource Name (ARN) of the principal that made the request with the ARN that you specify in the policy.

  • Line 19:

    The second condition operator is a Null condition. It evaluates if the attached condition key’s tag exists in the request context. If the key’s right side value is set to false, the expression will return true if the tag exists and has a value. The following table illustrates the evaluation logic:

    Condition key and righthand side value Description Evaluation of expression if tag value exists Evaluation of expression if tag value doesn’t exist
    ec2:ResourceTag/access-project: true If my access-project resource tag is null (set to true) FALSE TRUE
    ec2:ResourceTag/access-project: false If my access-project resource tag is not null (set to false) TRUE FALSE
  • Line 20:
     "ec2:ResourceTag/access-project": false

    Deny only when the access-project tag is present on the resource. This is needed because tagging actions shouldn’t be denied for new resources that might not yet have the access-project authorization tag set, which is covered next in the DenyModifyResAuthzTagIfPrinTagNotMatched statement.

Note that all elements of this condition are evaluated with a logical AND. For a deny to occur, both the StringNotEquals and Null operators must evaluate as true.

Deny modification and deletion of tags if requesting that a resource’s authorization tag be set to any value other than the principal’s

This second statement addresses what to do if there’s no authorization tag, or if the tag is on the principal but not on the resource. It denies modification of any tag, including the resource’s authorization tags, if the resource’s access-project tag is one of the requested tags to be modified and its value doesn’t match the principal’s access-project tag. The statement is needed when the resource might not have an authorization tag yet, and access shouldn’t be granted without a tag. Fortunately, access can be based on what the principal is requesting the resource authorization tag to be set to, which must be equal to the principal’s tag value.

{ "Sid": "DenyModifyResAuthzTagIfPrinTagNotMatched", "Effect": "Deny", "Action": [ "ec2:CreateTags", "ec2:DeleteTags" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:RequestTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "ForAnyValue:StringEquals": { "aws:TagKeys": [ "access-project" ] } } },

  • Line 36:
     "aws:RequestTag/access-project": "${aws:PrincipalTag/access-project}",

    Check to see if the desired access-project tag value for your resource, aws:RequestTag/access-project, isn’t equal to the principal’s access-project tag value. aws:RequestTag is a global condition key that can be used for tag actions to compare the tag key-value pair that was passed in the request with the tag pair that you specify in the policy.

  • Line 39-43:
     "ForAnyValue:StringEquals": { "aws:TagKeys": [ "access-project" ] } 

    This ensures that a deny can only occur if the access-project tag is among the tags in the request context, which would be the case if the resource’s authorization tag was one of the requested tags to be modified.

Deny modification and deletion of tags if the principal’s access-project tag does not exist

The third statement addresses the requirement that if any authorization tags are missing from a principal, the principal shouldn’t be allowed to perform any tag operations. This policy statement will deny modification of any tag if the principal’s access-project tag doesn’t exist. The reason for this is that if authorization is intended to be based on the principal’s authorization tag, it must be present for any tag modification operation to be allowed.

{ "Sid": "DenyModifyTagsIfPrinTagNotExists", "Effect": "Deny", "Action": [ "ec2:CreateTags", "ec2:DeleteTags" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "aws:PrincipalTag/access-project": true } } }

You’ve now created a basic SCP that protects against unintended modification of Amazon EC2 resource tags used for authorization. You will also need to create identity policies for your project’s roles that enable users to tag resources. You will also want to test your EC2 instances and ensure that your tags cannot be modified except by authorized principals. The next step is to add IAM, Amazon RDS, and Amazon EFS resources to this SCP.

Adding IAM users and roles to the SCP

Now that you’re confident you’ve done what you can to secure your Amazon EC2 tags, you can to do the same for your IAM resources, specifically users and roles. This is important because user and role resources can also be principals, and have authorization based off their tags. For example, not all roles will be assumed by employees and receive session tags. Applications can also assume a role and thus have authorization based on a non-session tag. To account for that possibility, you can add the following IAM actions to the prior statement, which denies modification and deletion of tags if a resource’s authorization tags don’t match the principal:

{ "Sid": "DenyModifyTagsIfPrinTagNotExists", "Effect": "Deny", "Action": [ "ec2:CreateTags", "ec2:DeleteTags", "iam:TagRole", "iam:TagUser", "iam:UntagRole", "iam:UntagUser" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "aws:PrincipalTag/access-project": true } } }

You can use the following statements to add the IAM tagging actions to the previous statements that (1) deny modification and deletion of tags if requesting that a resource’s authorization tag be set to a different value than the principal’s and (2) denying modification and deletion of tags if the principal’s project tag doesn’t exist.

{ "Sid": "DenyModifyResAuthzTagIfPrinTagNotMatched", "Effect": "Deny", "Action": [ "ec2:CreateTags", "ec2:DeleteTags", "iam:TagRole", "iam:TagUser", "iam:UntagRole", "iam:UntagUser" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:RequestTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "ForAnyValue:StringEquals": { "aws:TagKeys": [ "access-project" ] } }
{ "Sid": "DenyModifyTagsIfPrinTagNotExists", "Effect": "Deny", "Action": [ "ec2:CreateTags", "ec2:DeleteTags", "iam:TagRole", "iam:TagUser", "iam:UntagRole", "iam:UntagUser" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "aws:PrincipalTag/access-project": true } }

Adding resources that support the global aws:ResourceTag to the SCP

Now we will add Amazon RDS and Amazon EFS resources to complete the SCP. RDS and EFS are different with regards to tagging because they support the global aws:ResourceTag condition key instead of a service specific key such as ec2:ResourceTag. Instead of requiring you to create multiple statements similar to the code used above to deny modification and deletion of tags if a resource’s authorization tags don’t match the principal, you can create a single reusable policy statement that you can continue to add more actions to, as shown in the following code.

{ "Sid": "DenyModifyTagsIfResAuthzTagAndPrinTagNotMatched", "Effect": "Deny", "Action": [ "elasticfilesystem:TagResource", "elasticfilesystem:UntagResource", "rds:AddTagsToResource", "rds:RemoveTagsFromResource" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:ResourceTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "aws:ResourceTag/access-project": false } }

Review the full SCP to secure authorization tags

Let’s review the final SCP. It includes all the services you’ve targeted, as shown in the code sample below. This policy is 2,859 characters in length, and uses non-Unicode characters, which means that it uses a byte of data for each character. The policy therefore uses 2,859 bytes of the current 5,120 byte quota for an SCP. This means that roughly 4 to 18 more services can be added to this policy, depending on if the services use their own service-specific resource tag condition key or the global aws:ResourceTag key. Keep these numbers and limits in mind when adding additional resources, and remember that a maximum of five SCPs can be associated with an OU. It is unwise to consume the entirety of your available SCP policy space, as you’ll want to ensure you can make later changes and have growing room for your future business requirements.

This SCP doesn’t enforce tag-on-create, which lets you require that tags be applied when a resource is created, as shown in Example service control polices. Enforcing tag-on-create is necessary if you need resources to be accessible only from appropriately tagged identities from the time resources are created. Enforcement can be done through an SCP or by creating identity policies for the roles in each account that require it.

If necessary, you can use another employee attribute in addition to access-project, but that will use more of the available quota of bytes. Adding another attribute can potentially double the bytes used in a policy, especially for services that require use of a service-specific resource tag condition key. Using Amazon EC2 as an example, you would need to create separate statements just for the added employee attribute, duplicating the policy statements in the first three code samples.

Here’s the final SCP in its entirety:

{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyModifyTagsIfResAuthzTagAndPrinTagNotMatchedEc2", "Effect": "Deny", "Action": [ "ec2:CreateTags", "ec2:DeleteTags" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "ec2:ResourceTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "ec2:ResourceTag/access-project": false } } }, { "Sid": "DenyModifyTagsIfResAuthzTagAndPrinTagNotMatchedIam", "Effect": "Deny", "Action": [ "iam:TagRole", "iam:TagUser", "iam:UntagRole", "iam:UntagUser" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "iam:ResourceTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "iam:ResourceTag/access-project": false } } }, { "Sid": "DenyModifyTagsIfResAuthzTagAndPrinTagNotMatched", "Effect": "Deny", "Action": [ "elasticfilesystem:TagResource", "elasticfilesystem:UntagResource", "rds:AddTagsToResource", "rds:RemoveTagsFromResource" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:ResourceTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "aws:ResourceTag/access-project": false } } }, { "Sid": "DenyModifyResAuthzTagIfPrinTagNotMatched", "Effect": "Deny", "Action": [ "elasticfilesystem:TagResource", "elasticfilesystem:UntagResource", "ec2:CreateTags", "ec2:DeleteTags", "iam:TagRole", "iam:TagUser", "iam:UntagRole", "iam:UntagUser", "rds:AddTagsToResource", "rds:RemoveTagsFromResource" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:RequestTag/access-project": "${aws:PrincipalTag/access-project}", "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "ForAnyValue:StringEquals": { "aws:TagKeys": [ "access-project" ] } } }, { "Sid": "DenyModifyTagsIfPrinTagNotExists", "Effect": "Deny", "Action": [ "elasticfilesystem:TagResource", "elasticfilesystem:UntagResource", "ec2:CreateTags", "ec2:DeleteTags", "iam:TagRole", "iam:TagUser", "iam:UntagRole", "iam:UntagUser", "rds:AddTagsToResource", "rds:RemoveTagsFromResource" ], "Resource": [ "*" ], "Condition": { "StringNotEquals": { "aws:PrincipalArn": "arn:aws:iam::123456789012:role/org-admins/iam-admin" }, "Null": { "aws:PrincipalTag/access-project": true } } } ]

Next Steps

Now that you have an SCP that protects your resources’ authorization tags, you’ll also want to consider ensuring that identities cannot be assigned unauthorized tags when assuming roles, as shown in Using SAML Session Tags for ABAC. Additionally, you’ll also want detective and responsive controls to verify that identities are permitted to only access their own resources, and that responsive action can be taken if unintended access occurs.


You’ve created a multi-account organizational guardrail to protect the tags used for your ABAC implementation. Through the use of employee attributes, transitive session tags, and a service control policy you’ve created a preventive control that will deny anyone except an IAM administrator from modifying tags used for authorization once set on a resource.

If you have feedback about this post, submit comments in the Comments section below. If you have questions about this post, start a new thread on the AWS IAM forum or contact AWS Support.

Want more AWS Security how-to content, news, and feature announcements? Follow us on Twitter.

Michael Chan

Michael Chan

Michael is a Developer Advocate for AWS Identity. Prior to this, he was a Professional Services Consultant who assisted customers with their journey to AWS. He enjoys understanding customer problems and working backwards to provide practical solutions.