Terraforming Resource Control Policies

Dustin Whited - Nov 18 - - Dev Community

AWS released a new type of organizational authorizational policy called Resource Control Policies (RCPs). This policy is considered a "resource-centric" policy, complementary to Service Control Policies "principal-centric" preventative guardrails.

See the AWS release blog for more info about RCPs: https://aws.amazon.com/blogs/aws/introducing-resource-control-policies-rcps-a-new-authorization-policy/

Terraforming

One of the first questions I ask with a new AWS feature or service is "how do I automate it?" For RCPs, I asked a similar question and found that the AWS provider in Terraform had support as of version 5.76.0.

First, ensure you have the policy type enabled on your organization via "ALL" or "RESOURCE_CONTROL_POLICY" like shown below. This will enable RCPs as well as attach RCPFullAWSAccess.

resource "aws_organizations_organization" "org" {
  feature_set          = "ALL"
  enabled_policy_types = [
    "AISERVICES_OPT_OUT_POLICY",
    "SERVICE_CONTROL_POLICY",
    "RESOURCE_CONTROL_POLICY",
    ]
}
Enter fullscreen mode Exit fullscreen mode

Ensure your provider is up to 5.76. You could also use "~> 5.0".

See Version Constraints documentation for more info on how this works https://developer.hashicorp.com/terraform/language/expressions/version-constraints


terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "~> 5.76"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you can create a RCP. I'm using the confused deputy prevention example provided from aws-samples. Note that RCPs have similar limits to SCPs. You can only attach 5 per entity (OU and account), RCPFullAWSAccess counts against the limit, and they have a character limit of 5120 that includes whitespace.

data "aws_iam_policy_document" "rcp1" {
  statement {
    effect = "Deny"

    actions = [
      "s3:*",
      "sqs:*",
      "kms:*",
      "secretsmanager:*",
      "sts:*",
    ]

    resources = ["*"]

    principals {
      type        = "*"
      identifiers = ["*"]
    }

    condition {
      test     = "StringNotEqualsIfExists"
      variable = "aws:SourceOrgID"

      values = [
        aws_organizations_organization.org.id
      ]
    }

    condition {
      test     = "Null"
      variable = "aws:SourceAccount"

      values = [
        "false"
      ]
    }

    condition {
      test     = "Bool"
      variable = "aws:PrincipalIsAWSService"

      values = [
        "true"
      ]
    }
  }
}

resource "aws_organizations_policy" "rcp1" {
  name    = "rcp1"
  content = data.aws_iam_policy_document.rcp1.json
  type    = "RESOURCE_CONTROL_POLICY"
}
Enter fullscreen mode Exit fullscreen mode

If you are combining multiple RCPs into a single aws_iam_policy_document, you may want to use minified_json instead of json on the content argument.

resource "aws_organizations_policy" "rcp1" {
  name    = "rcp1"
  content = data.aws_iam_policy_document.rcp1.minified_json
  type    = "RESOURCE_CONTROL_POLICY"
}
Enter fullscreen mode Exit fullscreen mode

And finally, attach it to an entity. In this case, I am attaching it to an OU I have called "dev" under the organization root.

resource "aws_organizations_organizational_unit" "dev" {
  name      = "dev"
  parent_id = aws_organizations_organization.org.roots[0].id
}

resource "aws_organizations_policy_attachment" "rcp1_dev" {
  policy_id = aws_organizations_policy.rcp1.id
  target_id = aws_organizations_organizational_unit.dev.id
}
Enter fullscreen mode Exit fullscreen mode

It's very nice to see Terraform provider support so quickly for a new release, especially on governance features.

Happy RCP'ing!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .