Identity Management had a curious beginning in the early 1500s in England, where parish churches kept elaborate written records “for the purpose of preventing bigamy and consanguineous marriage.” It was the invention of the automobile 400 years later that furthered the creation of personal identification when in 1903 two US states issued the first driver’s licenses, then it took another 55 years before California became the first state to use photographs on driver’s licenses. Long before computers were invented, we’ve used identity systems to sanction marriages, driving privileges, provide social benefits, and pay taxes. Today we live in a world where privacy and financial security necessitates strict identity verification and access management, and as such we can barely go a day without using some form of identification.

There are three themes if you look back through the history of identity. First, over the last 120 years there’s been a significant increase in the importance of a person’s identity. Also, personal identity attributes are more numerous, unique, and specific. Lastly, the systems we access must be able to validate us. By the way, how did a Police officer in the 1940s validate a driver’s license without a photo?

Frank Sintra Driver's license

Hopefully this bizarre detour into the history of Identity Access Management (IAM) helped broaden your appreciation of how our personal identities have evolved. Identity today is vastly more complex than Frank Sintra’s driver license, but thankfully that complexity allows for precise security and access to powerful tools.

In this post, the goal is to empower software developers to move quickly. Whether they are building proof-of-concepts applications, processing terabytes of data, or any number of other tasks that support shipping code to customers. The DevOps philosophy is about enabling speed and rapid delivery so teams respond faster to customers’ needs and build competitive advantage.

Why build a Developer Sandbox?

In many enterprises, shared AWS Accounts emerge from the organization’s internal structures such as business units and operating budgets. Businesses often lack automated account provisioning tools available when using AWS Landing Zone and AWS Control Tower.

In a shared AWS account, it can be difficult to protect customer data and work-in-progress if someone else can stop, restart, or terminate any EC2 instance. Unfettered access to create resources can lead to an explosion of costs.  When teams know their cloud expenses are tracked they’ll be more likely to launch only what they need, and terminate unused resources. Developer Sandboxes put smart boundaries in place to protect data, control costs, and protect your team’s work.

Software development organizations are responsible for efficiently building and shipping their products. Through Agile and Scrum practices, these teams have become dynamic collaborations between Engineers, Designers, Quality Assurance, and Product Managers. These job roles don’t always map cleanly to a clear permissions set. During the pre-production phase, it’s perfectly reasonable that software teams might share similar permissions to improve collaboration as they rapidly iterate the product.

Attribute-based Access controls have a number of advantages over Role-based access control:

  • Reduces DevOps Engineers need to edit policies to provide access to new resources as your organization scales.
  • Fewer policies to manage since ABAC policies are easier to generalize and more cross-functional in nature.
  • Rapid provisioning of new resources as projects and teams are automatically granted access to what they need
  • Matching Tags between Principals and Resources allows for highly granular permissions so they have the least privilege needed.
  • Employee attributes can be sourced from corporate directories with SAML-based or web identity providers.

As more organizations utilize cross-functional projects, DevOps and Software teams now must work together to build access controls based on those projects. For example, granting user John access to managed EC2 instances without conditions provided vast access to the service across the entire account including all existing EC2 instances. Our solution example instead, gives user Jane permissions to manage EC2 resources on Project A only. If Jane also joins Project B as a read-only member it doesn’t cause a conflict between those permission sets because we are providing access via the user’s tag-based context.

How do I build secure developer sandboxes with AWS IAM?

By adding context with tags, organizations can take advantage of an AWS recommended authorization strategy called Attribute-based access control (ABAC). This strategy is based on two AWS best practices: the principle of least privilege by granting only the permissions required to perform the task, and defining the condition under which users can access resources. Basically, this authorization strategy starts with adding tags to Principals (users and roles), then ABAC policies authorize a principal’s actions based on their tags. This effectively permits solutions where a single policy is created for a set of Task-specific permissions such as Sandboxed EC2 Actions. This single policy can in essence be applied to hundreds of projects and thousands of users. Additionally, the tagging of Users can be delegated to the Project and Team owners because the ABAC permissions are so specific and restricted.

How an organization manages to add these attributes to its Users and Roles is beyond the scope of what this post will cover. However, below are a few ideas on how they might go about managing attributes.

  • Using existing Corporate Directory systems, such as Active Directory and Web Identity providers to pass through those attributes
  • Add attributes assignment through automated user provisioning, or corporate HR or approval systems that ensure organizational standards are maintained.
  • Enable Support Teams and Project Owners to manage tags with scripts

Solution Overview

As an overview, this post will present three solutions for configuring ABAC with AWS IAM Policies. All solutions provide the same level of access control to the User, specifically the ability to tag, create, start, and stop EC2 Instances. The solutions start simply and gradually demonstrate more complex policies. The first two solutions are simple to understand and easy to deploy and are quick-win solutions that improve authorization controls without extensive planning. These two solutions do not require adding tags to users and roles.

The first solution is Project or Team solution that’s meant to help members of a Group access tagged EC2 Resources. The second solution provides Individuals access to create private EC2 Instances restricted for their personal use. The third solution exclusively uses Condition Context Keys which makes it the most flexible solution by allowing new projects without policy changes. Unlike the first two solutions, tags are required on both the Resources and Principals.

Attribute-based Access Control requires carefully matching Actions with Condition in our policies while this won’t be a comprehensive lesson in IAM Policy. It is important to explain what the Principals can do with each Action, and how Conditions filter in which situations those Actions are allowed.

Actions Reference:

  • ec2:RunInstances is the action which allows the creation of new instances.
  • ec2:CreateTags allows adding or overwriting tags on instances.
  • These actions, ec2:StartInstances & ec2:StopInstances, give Users the ability to start and stop existing instances.

Condition Reference:

  • Paired with the ec2:CreateTags action, is the ec2:CreateAction condition key which provides tagging permissions to the action RunInstances.
  • When creating new EC2 Instances the aws:RequestTag is used. It requests a new tag.
  • The ec2:ResourceTag applies to existing tags. Something is only a resource if it exists.
  • Allowed Tag keys must be specified in the aws:TagKeys condition.

Lastly, each policy in the proof of concept application has a section for “PermissionsForRunningTestsOnly” which are total unnecessary in production, but required for running the automated tests. Also, each proof of concept application gives the users EC2 Read-only permissions with the AWS Managed Policy “AmazonEC2ReadOnlyAccess”.

Solution 1: Project Based EC2 Access

This Project Solution allows users to create, start, and stop new EC2 instances tagged with project=valhalla. Users are given access through the Valhalla Project Group which has a policy attached that provides the users access to manage those resources.

This solution doesn’t require any changes to the Users and can be deployed quickly. For smaller organizations, this solution seems to be a good starting point for getting started with ABAC.

How are tags added to EC2 instances? Tags must be added when the instance is being created so you can create EC2 instances manually in the console, using the aws cli, or automate them using scripts.

Solution 1 - Project Based Access Diagram

What is allowed and denied?

  • Create new EC2 instances with project=valhalla
  • Start and Stop EC2 instances project=valhalla
  • Unable to create new EC2 instances without project=valhalla
  • Unable to start or stop any Instance not tagged with project=valhalla
  • Unable to add or overwrite tags on instances not tagged with project=valhalla
{ "Version": "2012-10-17", "Statement": [{ "Sid": "VisualEditor0", "Effect": "Allow", "Action": "ec2:RunInstances", "Resource": [ "arn:aws:ec2:*:*:instance/*" ], "Condition": { "StringEquals": { "aws:RequestTag/project": "vahalla" }, "ForAllValues:StringEquals": { "aws:TagKeys": "project" } } }, { "Effect": "Allow", "Action": [ "ec2:CreateTags" ], "Resource": "arn:aws:ec2:*:*:*/*", "Condition": { "StringEquals": { "ec2:CreateAction": "RunInstances" } } }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": "ec2:CreateTags", "Resource": "arn:aws:ec2:*:*:instance/*", "Condition": { "StringEquals": { "ec2:ResourceTag/project": "vahalla" }, "ForAllValues:StringEquals": { "aws:TagKeys": "project" }, "StringEqualsIfExists": { "aws:RequestTag/project": "vahalla" } } }, { "Sid": "VisualEditor2", "Effect": "Allow", "Action": [ "ec2:StartInstances", "ec2:StopInstances" ], "Resource": "*", "Condition": { "StringEquals": { "ec2:ResourceTag/project": "vahalla" } } }, { "Sid": "VisualEditor3", "Effect": "Allow", "Action": "ec2:RunInstances", "Resource": [ "arn:aws:ec2:*:*:subnet/*", "arn:aws:ec2:*:*:key-pair/*", "arn:aws:ec2:*:*:launch-template/*", "arn:aws:ec2:*::snapshot/*", "arn:aws:ec2:*:*:volume/*", "arn:aws:ec2:*:*:security-group/*", "arn:aws:ec2:*:*:placement-group/*", "arn:aws:ec2:*:*:network-interface/*", "arn:aws:ec2:*::image/*" ] }, { "Sid": "PermissionsForRunningTestsOnly", "Effect": "Allow", "Action": [ "sts:DecodeAuthorizationMessage", "cloudformation:ListExports" ], "Resource": "*" } ]
}

Each solution has a proof of concept application that can be deployed with AWS CDK and a suite of tests that can be run to validate the solution.

Solution 2: Individual Access Control

This solution only allows a developer to Create, Start, Stop, and Terminate their EC2 Instances, but does not allow them to edit anyone else’s instances.

How do the tags get created on the EC2 instance? As the user is creating the instance, they must add their username such as username=john.doe.dev to every instance they create.

Solution 2 - Individual Access Diagram

What is allowed and denied?

  • Create new EC2 instances with username=<your username>
  • Start and Stop EC2 instances username=<your username>
  • Unable to create new ec2 instances without username=<your username>
  • Unable to modify any Instance not tagged with username=<your username>
  • Unable to add or overwrite tags on instances not tagged with username=<your username>
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "ec2:RunInstances", "Resource": "arn:aws:ec2:*:*:instance/*", "Condition": { "StringEqualsIgnoreCase": { "aws:RequestTag/username": "${aws:username}" }, "ForAllValues:StringEquals": { "aws:TagKeys": "username" } } }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": "ec2:CreateTags", "Resource": "arn:aws:ec2:*:*:*/*", "Condition": { "StringEquals": { "ec2:CreateAction": "RunInstances" } } }, { "Sid": "VisualEditor2", "Effect": "Allow", "Action": "ec2:CreateTags", "Resource": "arn:aws:ec2:*:*:instance/*", "Condition": { "StringEqualsIgnoreCase": { "ec2:ResourceTag/username": "${aws:username}", "aws:RequestTag/username": "${aws:username}" }, "ForAllValues:StringEquals": { "aws:TagKeys": "username" } } }, { "Sid": "VisualEditor3", "Effect": "Allow", "Action": [ "ec2:StartInstances", "ec2:StopInstances", "ec2:TerminateInstances" ], "Resource": "*", "Condition": { "StringEqualsIgnoreCase": { "ec2:ResourceTag/username": "${aws:username}" } } }, { "Sid": "VisualEditor4", "Effect": "Allow", "Action": "ec2:RunInstances", "Resource": [ "arn:aws:ec2:*:*:subnet/*", "arn:aws:ec2:*:*:key-pair/*", "arn:aws:ec2:*:*:launch-template/*", "arn:aws:ec2:*::snapshot/*", "arn:aws:ec2:*:*:volume/*", "arn:aws:ec2:*:*:security-group/*", "arn:aws:ec2:*:*:placement-group/*", "arn:aws:ec2:*:*:network-interface/*", "arn:aws:ec2:*::image/*" ] }, { "Sid": "PermissionsForRunningTestsOnly", "Effect": "Allow", "Action": [ "cloudformation:ListExports", "sts:DecodeAuthorizationMessage", "iam:GetUser" ], "Resource": "*" } ]
}

See the Individual EC2 Access Access proof of concept application.

Solution 3: Highly Flexible and Granular Access Control

Solution 1 used project tags on the EC2 Instance, then Solution 2 just used the username attribute. This Highly Flexible solution is enhanced over the previous two by using policies that require matching tags for both Resources and Principals. Administrators of large and complex environments will find this solution the most suitable as scale increased the need for dynamic polices.

Our Example Developer user account has the following tags:

  • access-project=elysian
  • access-team=webdev
  • cost-center=2600

All three tags on the Resource must match all three tags on the User. If any don’t match, then authorization fails. Tags can be multi-valued for example Key: access-project Value: elysian vahalla heaven. Also be aware that Tag key’s are case insensitive and Tag values are case sensitive.

Solution 3 - Flexible Attribute based Access Control Diagram

What is allowed and denied?

  • Create new EC2 instances by adding Resource Tags that match their User Tags
  • Start and Stop EC2 instances matching tags
  • Unable to create new ec2 instances without matching tags
  • Unable to modify any Instance not tagged with matching tags
  • Unable to add or overwrite tags on instances not tagged with matching tags.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "ec2:RunInstances", "Resource": "arn:aws:ec2:*:*:instance/*", "Condition": { "StringEqualsIgnoreCase": { "aws:RequestTag/access-project": "${aws:PrincipalTag/access-project}", "aws:RequestTag/access-team": "${aws:PrincipalTag/access-team}", "aws:RequestTag/cost-center": "${aws:PrincipalTag/cost-center}" }, "ForAllValues:StringEquals": { "aws:TagKeys": ["access-project", "access-team", "cost-center"] } } }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": "ec2:CreateTags", "Resource": "arn:aws:ec2:*:*:*/*", "Condition": { "StringEquals": { "ec2:CreateAction": "RunInstances" } } }, { "Sid": "VisualEditor2", "Effect": "Allow", "Action": "ec2:CreateTags", "Resource": "arn:aws:ec2:*:*:instance/*", "Condition": { "StringEqualsIgnoreCase": { "ec2:ResourceTag/access-project": "${aws:PrincipalTag/access-project}", "ec2:ResourceTag/access-team": "${aws:PrincipalTag/access-team}", "ec2:ResourceTag/cost-center": "${aws:PrincipalTag/cost-center}" }, "ForAllValues:StringEquals": { "aws:TagKeys": ["access-project", "access-team", "cost-center"] }, "StringEqualsIgnoreCase": { "aws:RequestTag/access-project": "${aws:PrincipalTag/access-project}", "aws:RequestTag/access-team": "${aws:PrincipalTag/access-team}", "aws:RequestTag/cost-center": "${aws:PrincipalTag/cost-center}" } } }, { "Sid": "VisualEditor3", "Effect": "Allow", "Action": [ "ec2:StartInstances", "ec2:StopInstances" ], "Resource": "*", "Condition": { "StringEqualsIgnoreCase": { "ec2:ResourceTag/access-project": "${aws:PrincipalTag/access-project}", "ec2:ResourceTag/access-team": "${aws:PrincipalTag/access-team}", "ec2:ResourceTag/cost-center": "${aws:PrincipalTag/cost-center}" } } }, { "Sid": "VisualEditor4", "Effect": "Allow", "Action": "ec2:RunInstances", "Resource": [ "arn:aws:ec2:*:*:subnet/*", "arn:aws:ec2:*:*:key-pair/*", "arn:aws:ec2:*:*:launch-template/*", "arn:aws:ec2:*::snapshot/*", "arn:aws:ec2:*:*:volume/*", "arn:aws:ec2:*:*:security-group/*", "arn:aws:ec2:*:*:placement-group/*", "arn:aws:ec2:*:*:network-interface/*", "arn:aws:ec2:*::image/*" ] }, { "Sid": "PermissionsForRunningTestsOnly", "Effect": "Allow", "Action": [ "cloudformation:ListExports", "sts:DecodeAuthorizationMessage" ], "Resource": "*" } ]
}

Permission Boundary

In addition, this solution creates a permission boundary policy and attaches it to the Test User. Permission Boundaries specify the maximum permissions allowed by a user, and prevents users from accidentally or intentionally being given more access than authorized. Boundary policies are customer managed policies which are attached directly to the User.

{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "cloudformation:ListExports", "sts:DecodeAuthorizationMessage" ], "Resource": "*" }, { "Effect": "Allow", "Action": "ec2:*", "Resource": [ "arn:aws:ec2:*:*:subnet/*", "arn:aws:ec2:*:*:key-pair/*", "arn:aws:ec2:*:*:instance/*", "arn:aws:ec2:*::snapshot/*", "arn:aws:ec2:*:*:launch-template/*", "arn:aws:ec2:*:*:volume/*", "arn:aws:ec2:*:*:security-group/*", "arn:aws:ec2:*:*:placement-group/*", "arn:aws:ec2:*:*:network-interface/*", "arn:aws:ec2:*::image/*" ] }, { "Effect": "Allow", "Action": "ec2:Describe*", "Resource": "*" }, { "Effect": "Allow", "Action": "elasticloadbalancing:Describe*", "Resource": "*" }, { "Effect": "Allow", "Action": [ "cloudwatch:ListMetrics", "cloudwatch:GetMetricStatistics", "cloudwatch:Describe*" ], "Resource": "*" }, { "Effect": "Allow", "Action": "autoscaling:Describe*", "Resource": "*" } ]
}

See and deploy the Flexible ABAC proof of concept application.

Summary

These policies are a good starting point for building a Developer Sandbox using Attribute based Access Control. Those looking to further enhance these policies should consider limiting machine images, restricting regions, and creating project administrators who can terminate instances or manage tags.

Attribute based Access Control required careful planning, validation, and deployment. When put in place, Developer Sandboxes offer a flexible solution that improves management and extends delegation to the Project Owners. By doing this it reduces friction within our internal processes and accelerates delivery. Smart sandboxing also protects data, accelerates team collaboration, and helps control costs with improved accountability and reporting.

Further Reading Reference

AWS Organization Tag Policies enables standardized tagging using Tag Policies which can be applied across multiple AWS accounts. AWS Resource Groups improves compliance when Tag Policies are implemented to find and enforce rules on noncompliant tags.

What Is ABAC for AWS?

AWS IAM Policy – Global Condition Keys

Using Tags for Attribute-Based Access Control in AWS

Using SAML Session Tags for ABAC for developers that authenticate with a SAML based identity provider.

Featured image photo by Liam Tucker on Unsplash

The post Building Developer Sandboxes on AWS with Attribute-based access control (ABAC) appeared first on Stelligent.