/terraform-aws-action-helper

aws-action-helper is a Terraform module which helps implementing AWS IAM policies by providing abstracting actions (or permissions) as usable object in Terraform.

Primary LanguageHCLApache License 2.0Apache-2.0

AWS action helper

aws-action-helper is a Terraform module which helps implementing AWS IAM policies by providing abstracting actions (or permissions) as usable object in Terraform. All information is based on https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html.

It comes with a python script allowing to update the module on the fly if AWS services are added or changed. This allows to track updates to services and especially IAM policies.

Rational

During a project of mine I was frustated that there was no good way to manage the names of actions that can be used within an IAM policy. As the project team does not prefer data sources, I implemented a solution as Terraform module.

The benefit of this module is that you can now manage all actions yourself. Thus, you may never add new actions accidentally and restrict permissions managed by Terraform to what is inside your source code.

The main focus of this module is to bundle and compact a list of permission types such as all write actions for a given service. If you need to pick single actions you are better off without this module. In a bigger project this would result in a lot of additional stub code to be able to access the required outputs.

Preparing your terraform setup

Consider wrapping this module to provide it as a dependency by defining outputs, instead of loading it multiple times in your project. This can decrease the required disk space for using this module.

Limits and known issues

Using this module might come with some unexpected limits and challenges.

For instance, the AWS service names are generated by a technical file from AWS and may or may not reflect the published service name. As an example, the well known service iam will be provided as identityandaccessmanagementiam, billing is provided as billing_ (Note: Was migrated back to billing) or amazonmq as mq. This happens naturally as the technical data is likely to change and not controlled by this project. The generating script (see below) will try to handle a few cases, but never all e.g. by dropping the prefix of a service starting with either aws or amazon.

There is also the challenge of the source of information here as the script relies on the documentation and its format published by AWS. An alternative source is the AWS policy generator which is managed by AWS.

It can also be said that this solution might not be the best but it is a working one. The goal of this module is to support developers with static information in a manner that is compatible with the declarative nature of Terraform and the HCL semantics.

As for the requirement of the Terraform version, I decided to set it to >= 1.3, <= 1.5.5 as this is the last known open source version of Terraform using the MPL license. It is still possible that this module works with newer or older versions of Terraform.

Module examples

For usage examples reference the /examples directory.

Below you can find detailed information for some built-in features.

Minification

The module allows for minification of action names which is commonly used when summarizing IAM policies. Currently the only useful placeholder is *.

The replacement can be fully customizied by overriding the regular expression for the lookup and the replacement. By default, the module will try to lookup the first uppercase word and replace the rest with an *.

Example:

module "aws_action_helper" {
  source = "../../modules/ec2"

  use_prefix = var.use_prefix

  filter_actions = var.filter_actions
  filtering      = {
    starts_with = "Create"
    contains    = ""
    ends_with   = ""
  }

  minify_strings     = var.minify_strings
  minify_regex       = "/([A-Z][^A-Z]+).+/" # Find first uppercase word
  minify_replacement = "$1*"                # Replace tail with *
}

Returns:

actions = {
  "list"                   = []
  "permissions_management" = [
    "ec2:Create*",
  ]
  "read"                   = []
  "tagging"                = [
    "ec2:Create*",
  ]
  "write"                  = [
    "ec2:Create*",
  ]
}

Filtering

Another included feature is the filtering of actions before creating output. Action names can be filtered by:

  1. starts_with: The actions name starts with this string
  2. contains: The actions name contains this substring
  3. ends_with: The actions name end with this string

The order of evaluation matches the order of the above list. So be careful if you want to mix filters.

Additionally, minification can also be used with filtering. However, the default minification may yield unwanted results or at least not the best optimization.

starts_with

Example:

module "aws_action_helper" {
  source = "../../modules/ec2"

  use_prefix = var.use_prefix

  filter_actions = var.filter_actions
  filtering      = {
    starts_with = "Get"
    contains    = ""
    ends_with   = ""
  }

  minify_strings     = var.minify_strings
  minify_regex       = var.minify_regex
  minify_replacement = var.minify_replacement
}

Returns:

actions = {
  "list"                   = [
    "ec2:Get*",
  ]
  "permissions_management" = []
  "read"                   = [
    "ec2:Get*",
  ]
  "tagging"                = []
  "write"                  = []
}

contains

Example:

module "aws_action_helper" {
  source = "../../modules/ec2"

  use_prefix = var.use_prefix

  filter_actions = var.filter_actions
  filtering      = {
    starts_with = ""
    contains    = "Instance"
    ends_with   = ""
  }

  # Note: Using default minification results in side effects
  minify_strings     = false
  minify_regex       = var.minify_regex
  minify_replacement = var.minify_replacement
}

Returns:

actions = {
  "list"                   = [
    "ec2:DescribeClassicLinkInstances",
    "ec2:DescribeFleetInstances",
    "ec2:DescribeIamInstanceProfileAssociations",
    "ec2:DescribeInstanceAttribute",
    "ec2:DescribeInstanceCreditSpecifications",
    "ec2:DescribeInstanceEventNotificationAttributes",
    "ec2:DescribeInstanceEventWindows",
    "ec2:DescribeInstanceStatus",
    "ec2:DescribeInstanceTypeOfferings",
    "ec2:DescribeInstanceTypes",
    "ec2:DescribeInstances",
    "ec2:DescribeReservedInstances",
    "ec2:DescribeReservedInstancesListings",
    "ec2:DescribeReservedInstancesModifications",
    "ec2:DescribeReservedInstancesOfferings",
    "ec2:DescribeSpotFleetInstances",
    "ec2:DescribeSpotInstanceRequests",
  ]
  "permissions_management" = []
  "read"                   = [
    "ec2:DescribeScheduledInstanceAvailability",
    "ec2:DescribeScheduledInstances",
    "ec2:GetInstanceTypesFromInstanceRequirements",
    "ec2:GetReservedInstancesExchangeQuote",
  ]
  "tagging"                = []
  "write"                  = [
    "ec2:AcceptReservedInstancesExchangeQuote",
    "ec2:AssociateIamInstanceProfile",
    "ec2:AssociateInstanceEventWindow",
    "ec2:BundleInstance",
    "ec2:CancelReservedInstancesListing",
    "ec2:CancelSpotInstanceRequests",
    "ec2:ConfirmProductInstance",
    "ec2:CreateInstanceEventWindow",
    "ec2:CreateInstanceExportTask",
    "ec2:CreateReservedInstancesListing",
    "ec2:DeleteInstanceEventWindow",
    "ec2:DeleteQueuedReservedInstances",
    "ec2:DeregisterInstanceEventNotificationAttributes",
    "ec2:DisassociateIamInstanceProfile",
    "ec2:DisassociateInstanceEventWindow",
    "ec2:ImportInstance",
    "ec2:ModifyInstanceAttribute",
    "ec2:ModifyInstanceCapacityReservationAttributes",
    "ec2:ModifyInstanceCreditSpecification",
    "ec2:ModifyInstanceEventStartTime",
    "ec2:ModifyInstanceEventWindow",
    "ec2:ModifyInstanceMaintenanceOptions",
    "ec2:ModifyInstanceMetadataOptions",
    "ec2:ModifyInstancePlacement",
    "ec2:ModifyReservedInstances",
    "ec2:MonitorInstances",
    "ec2:PurchaseReservedInstancesOffering",
    "ec2:PurchaseScheduledInstances",
    "ec2:RebootInstances",
    "ec2:RegisterInstanceEventNotificationAttributes",
    "ec2:ReplaceIamInstanceProfileAssociation",
    "ec2:ReportInstanceStatus",
    "ec2:RequestSpotInstances",
    "ec2:ResetInstanceAttribute",
    "ec2:RunInstances",
    "ec2:RunScheduledInstances",
    "ec2:SendSpotInstanceInterruptions",
    "ec2:StartInstances",
    "ec2:StopInstances",
    "ec2:TerminateInstances",
    "ec2:UnmonitorInstances",
  ]
}

ends_with

Example:

module "aws_action_helper" {
  source = "../../modules/ec2"

  use_prefix = var.use_prefix

  filter_actions = var.filter_actions
  filtering      = {
    starts_with = ""
    contains    = ""
    ends_with   = "Instances"
  }

  # Note: Using default minification results in side effects
  minify_strings     = false
  minify_regex       = var.minify_regex
  minify_replacement = var.minify_replacement
}

Returns:

actions = {
  "list"                   = [
    "ec2:DescribeClassicLinkInstances",
    "ec2:DescribeFleetInstances",
    "ec2:DescribeInstances",
    "ec2:DescribeReservedInstances",
    "ec2:DescribeSpotFleetInstances",
  ]
  "permissions_management" = []
  "read"                   = [
    "ec2:DescribeScheduledInstances",
  ]
  "tagging"                = []
  "write"                  = [
    "ec2:DeleteQueuedReservedInstances",
    "ec2:ModifyReservedInstances",
    "ec2:MonitorInstances",
    "ec2:PurchaseScheduledInstances",
    "ec2:RebootInstances",
    "ec2:RequestSpotInstances",
    "ec2:RunInstances",
    "ec2:RunScheduledInstances",
    "ec2:StartInstances",
    "ec2:StopInstances",
    "ec2:TerminateInstances",
    "ec2:UnmonitorInstances",
  ]
}

Module generation

This repository contains a python script in the .generate/ directory to add or update submodules for this project using cookiecutter.

Please note that the script is fragile in regards to getting the information about service and actions. As of now, AWS decided to not release the information about action names - or in general permissions - in a way that can be programmatically accessed. Thus a script was written to parse the current documentation page of all services which luckily is created in a rather persistent manner.

Usage

TODO: Update required to reflect current status.

Ensure to install all python dependencies first:

python3 -m pip install -r requirements.txt

To generate or cache the latest information about a service use the following call:

cd .generate/
./generate_module.py --docs-page list_amazonec2

This call will only write a cache file for upcoming calls of the script.

The --docs-page argument represents the filename of the docs page which has to be accessed by the script. If you ran the example, you can find a list of all pages in the generated file service_list.json. Otherwise you need to take a look at the AWS documentation HTML source.

If you want to update or generate a new module, simply add the--generate parameter:

cd .generate/
./generate_module.py --docs-page list_amazonec2 --generate

This will create or replace a directory in the modules/ directory.

Contributing

If you find any bugs, have recommendations or want to add/update a module, feel free to add a pull request or create an issue.

I prefer test driven development as well as clean code, so if you need to update any code, please try to stick to the current code style and provide testing.

License

This project uses the Apache License Version 2.0. See LICENSE for more information.