AWS Cloud Operations & Migrations Blog

Create a security partition for your applications using AWS Service Catalog and AWS Lambda

Some of the customers I work with want to create complete application separation for each application. They don’t want any two applications running on AWS to communicate using APIs or to network with each other’s AWS resources. In other words, they want each application to “stay in its own lane” as competitive swimmers do. In this blog post, I’ll show you how to dynamically update AWS Identity and Access Management (IAM) policies for AWS resource IAM roles. This allows AWS API access between AWS resources so that, for example, an Amazon EC2 instance can talk to an Amazon S3 bucket for a specific application, all through a self-service capability. Developers will no longer need to open tickets to have API access between their AWS resources.

For this blog post, I’ll show you how to accomplish this by using AWS Service Catalog and an AWS Lambda-backed Custom Resource in AWS CloudFormation. However, before I discuss the use case and its challenges, I’ll cover some AWS Service Catalog basics. Using AWS Service Catalog, you define a portfolio, which contains a dedicated set of predefined, constrained products. The products are versioned containers for AWS CloudFormation templates. A portfolio is exposed to each application team and is associated with products. Application developers repeatedly provision the products according to their needs, while being granted the least privilege set of permissions.

Now back to our use case. When your application developers create their application infrastructure by launching AWS Service Catalog products, you benefit from enhanced governance on your AWS resources while the developers (and the development process) benefit from self-service advantages. However, setting AWS API access between the provisioned AWS resources might be tricky in some scenarios, especially when AWS resources are created independently as different products, as in the following example.

Let’s follow the provisioning process and the developer experience when the developer creates infrastructure using AWS Service Catalog.

 

  1. The developer launches the “EC2 Instance” product.
  2. The developer launches the “S3 Bucket” product.
  3. The developer connects to the EC2 instance using SSH.
  4. The developer copies a file from the EC2 Instance to the S3 Bucket, but the copy request is denied.

A developer who has no AWS API access other than to launch AWS Service Catalog products must open a service request for the IT team to grant his Amazon EC2 instance to access the Amazon S3 bucket. The IT team fixes the issue by modifying the “EC2 Instance” product to give the provisioned EC2 instances access to any Amazon S3 bucket.

This example introduces two issues:

  • Self-service is broken – The developer needs the IT team to solve the AWS API access issue. This adds excess steps to the value stream, reduces business value, and increases time to market.
  • The least privilege principle is broken – After the fix, each provisioned EC2 instance will have AWS API access to any Amazon S3 bucket across applications.

To solve these issues all AWS resources that are created by one application team (from one portfolio) need to have “out of the box” AWS API access to each other, while AWS API access to any other AWS resource will be prohibited. From now on I will refer to this concept as a security partition.

Next, we’ll dive into the technical details, and I’ll show you how to implement this security partition in the AWS Service Catalog environment.

Solution

The following diagram depicts the solution architecture.

 

Let’s follow the diagram sequentially, left to right:

  1. The developer launches an AWS Service Catalog product.
  2. An AWS CloudFormation stack is created.
  3. The AWS resources are provisioned.
  4. AWS CloudFormation triggers the AWS Lambda function (using a CloudFormation custom resource).
  5. The Lambda function concludes a list of all the provisioned (or deleted) resources and does the following:
    • Updates the security partition IAM policy to include (or exclude) the resources.
    • Attaches the security partition IAM role to resources that can assume role.

To manage the AWS API access between resources within a security partition, security partitions have three dedicated controls:

  • Security partition IAM policy – An IAM Customer Managed Policy Document that includes allowed and denied AWS API calls on a specific list of resources. Resources are dynamically added and removed from this document based on launching, updating, and terminating of AWS Service Catalog products.
  • Security partition IAM role – Resources (e.g., EC2 instance, Lambda function) assume this role to have access to the other resources in the security partition. This role uses the security partition IAM policy.
  • Portfolio ID – This is the identifier of the security partition. The Lambda function uses this identifier to associate the resources with the correct security partition IAM policy and role.

In the illustration that follows, you can see these controls in action.

  1. Each application team has a dedicated AWS Service Catalog portfolio like the one on the left side of the illustration.
  2. Application developers provision a group of resources from their dedicated portfolio.
  3. The “Portfolio Id” tag identifies the resources group.
  4. A new S3 bucket is provisioned and is named S3_08.
  5. The new S3 bucket Amazon Resource Name is added to the security partition IAM policy statement DedicatedForResourcesS3.
  6. This security partition IAM policy is already associated with the IAM instance profile role of EC2_04.
  7. Instance EC2_04 has full access to S3_08 bucket.

Deployment

Step 1: Solution deployment

  1. Find the solution package in GitHub under aws-service-catalog-portfolio-partition.
  2. Download or clone the repository to your local device.
  3. Package the Lambda function code (for more details see creating a Lambda package) and upload the resulting zip file to an S3 bucket.
    cd aws-service-catalog-portfolio-partition/code/
    zip -r ../code.zip .
    aws s3 cp ../code.zip s3://<Bucket Name>/
  4. Create a CloudFormation stack with the template template.yml. When creating the stack, supply the Amazon S3 bucket name and S3 key name of the Lambda package. Alternately, use the console to create the stack directly.
    cd aws-service-catalog-portfolio-partition/deployment/
    aws cloudformation create-stack --stack-name sc-security-partition --template-body file://template.yml --parameters ParameterKey=LambdaCodeS3Bucket,ParameterValue=<BucketName> ParameterKey=LambdaCodeS3Key,ParameterValue=<KeyName> --capabilities CAPABILITY_IAM
  5. This template provisions two Lambda functions and an Amazon DynamoDB table.
    The Amazon Resource Name (ARN) of the Lambda function that backs the custom resource is exported under the name PartitionPhaseAFunctionArn. It will be used by the SetSecurityPartition custom resource.

    ---
    Outputs:
     PartitionPhaseAFunctionArn:
       Description: Lambda function to serve the custom resource of portfolio as security partition
       Export:
         Name: PartitionPhaseAFunctionArn

Step 2: Onboard portfolios and products

To enforce the security partition on a newly created portfolio, you need to revise your products to include the SetSecurityPartition custom resource. For each product associated with the portfolio you have to modify its CloudFormation template to include the custom resource in the following format:

SetSecurityPartition:
   Type: Custom::SetSecurityPartition
   DependsOn: <List of all the Logical Resources Names provisioned in this template>
   Properties:
     ServiceToken: !ImportValue PartitionPhaseAFunctionArn

Step 3: Customize the security partition

You can specify a closed set of allowed actions per resource type that must adhere to a least privilege principle. To do that open the configuration file,aws-service-catalog-portfolio-partition/code/configuration/resource_types_actions_allowed.json For example:

{
 "AWS::EC2::Instance": ["ec2:StartInstance"],
 "AWS::Lambda::Function": ["lambda:UpdateFunctionCode"]
}

For a resource type not listed, the default value will be “*”, which grants all actions. To apply the configuration change, you must to re-package the Lambda function code and update the deployed Lambda function code with that package.

Step 4: Supported resource types

When it comes to IAM, different AWS resource types might have different behavior and thus a specific implementation applies to different resource types. The security partition package maintains a whitelist of supported resource types. Also, to be supported, each resource type must implement the resources/base.py interface. You can find detailed instructions in the GitHub repository documentation.

After a new resource type is whitelisted and a new object is implemented accordingly, you have to update the Lambda functions execution roles to allow further required actions. To conclude the required additional actions, list the AWS API calls which are used in the new object code. Currently, only a few resource types are supported.

"AWS::EC2::Instance",
"AWS::Lambda::Function",
"AWS::DynamoDB::Table",
"AWS::S3::Bucket"

Conclusion

In this blog post, I presented the concept of maintaining a security partition around resources that are provisioned in the context of an AWS Service Catalog portfolio. I walked you through the deployment and usage of the solution.

Applying this solution in your organization fosters security while maintaining the self-service culture that eventually can increase business value from your developers’ work and reduce time to market for your organization.

About the Author

Ronen Dancziger is a DevOps consultant with AWS Professional Services. He strives to help AWS customers to minimize the lead time between the developer’s idea to a valuable feature in production.