Menu
blog.headdesk.me
blog.headdesk.me

Terraform and segregated permissions

Posted on 2023/01/122023/01/12

Recently, there is an initiative to tighten up security control on terraform deployment. In this post, I will demo how to use provider alias and tell terraform to switch role for resource management

At present, my terraform code are ran by an IAM role with full access. Security team wants to tighten the control and implement the least privileged design. This will require some serious refactoring on terraform code.

Declare provider aliases in root module

In the root module, we must declare all the provider aliases. Each alias tells terraform to switch to an IAM role with limited permissions. The default “aws” provider will have read only access. Then I added a few more such as NetworkAdmin, IamAdmin, etc.

provider "aws" {
  region = var.aws-region
  assume_role {
    role_arn     = "arn:aws:iam::111122223333:role/iac-ReadOnly"
    session_name = "terraform-ReadOnly"
  }
}

provider "aws" {
  region = var.aws-region
  alias  = "NetworkAdmin"
  assume_role {
    role_arn     = "arn:aws:iam::111122223333:role/iac-NetworkAdmin"
    session_name = "terraform-NetworkAdmin"
  }
}

provider "aws" {
  region = var.aws-region
  alias  = "IamAdmin"
  assume_role {
    role_arn     = "arn:aws:iam::111122223333:role/iac-IamAdmin"
    session_name = "terraform-IamAdmin"
  }
}

provider "aws" {
  region = var.aws-region
  alias  = "CloudwatchAdmin"
  assume_role {
    role_arn     = "arn:aws:iam::111122223333:role/iac-CloudWatch"
    session_name = "terraform-Cloudwatch"
  }
}

provider "aws" {
  region = var.aws-region
  alias  = "Ec2Admin"
  assume_role {
    role_arn     = "arn:aws:iam::111122223333:role/iac-Ec2Admin"
    session_name = "terraform-Ec2Admin"
  }
}

terraform {
  required_version = ">= 1.3.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.40"
    }
  }
}

Provide the provider aliases to child modules

Next, tell child modules how to map the provider aliases in the providers block. This will make more sense once we are onto the next step. This allows child modules to manage resources with different provider aliases.

module "vpc" {
  providers = {
    aws.NetworkAdmin    = aws.NetworkAdmin
    aws.IamAdmin        = aws.IamAdmin
    aws.CloudwatchAdmin = aws.CloudwatchAdmin
  }

  source = "./vpc_subnets"

  application                      = "iac-test"
  aws-region                       = var.aws-region
  customer-name                    = var.customer-name
  default-tags                     = local.default-tags
  environment                      = var.environment
  project                          = var.project
  vpc-cidr                         = "10.77.0.0/16"
  vpcflowlog-cwl-loggroup-key-arn  = ""
  enable-flow-log                  = true
  number-of-private-subnets-per-az = 0
  number-of-public-subnets-per-az  = 1
}

Specify providers in each resource inside the child module

Next, in my child module, I need to specify which provider to use for each resource. This is not fun. Essentially, all existing modules need to be refactored, and I need to know which provider (or role) is needed to manage each resource.

resource "aws_flow_log" "vpc-flowlog" {
  provider = aws.NetworkAdmin

  count           = var.enable-flow-log ? 1 : 0
  iam_role_arn    = aws_iam_role.vpcflowlog-role.arn
  log_destination = aws_cloudwatch_log_group.vpcflowlog-loggroup[0].arn
  traffic_type    = "ALL"
  vpc_id          = aws_vpc.vpc.id
  tags = merge(
    var.default-tags,
    {
      Name = "${local.resource-prefix}-vpcflowlog"
    },
  )
}

resource "aws_cloudwatch_log_group" "vpcflowlog-loggroup" {
  provider = aws.CloudwatchAdmin

  count = var.enable-flow-log ? 1 : 0

  name_prefix = "vpcflowlog/${aws_vpc.vpc.id}/"
  kms_key_id  = var.vpcflowlog-cwl-loggroup-key-arn

  retention_in_days = var.vpcflowlog-retain-days
  tags              = var.default-tags
}

Design the roles

Now the missing piece is to come up with a list of IAM roles. Each role will have limited but sufficient permissions to perform their tasks. I use the job function roles as a start but quickly find them inadequate. For example, the NetworkAdministrator managed policy alliows iam:PassRole only if the flowlog role name matches AWS standard (flow-logs-*). In my module, flow logs are named with a different convention. An additional policy needs to be attached to allow iam:PassRole. Below is a snippet from the NetworkAdministrator managed policy, showing the resource arn restriction.

    {
        "Effect": "Allow",
        "Action": [
            "iam:GetRole",
            "iam:ListRoles",
            "iam:PassRole"
        ],
        "Resource": "arn:aws:iam::*:role/flow-logs-*"
    }

That’s just a proof of concept. To apply this to a real project, I imagine the amount of time will be 3-4 times more than using a single admin role. Also beware of circular dependency. I have experienced that in the past and I’ll write more once I have an example.

facebookShare on Facebook
TwitterTweet

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Full text search

Recent Posts

  • Terraform and segregated permissions
  • LVM Compression and Deduplication
  • Edit gpg encrypted file with vim
  • Lelit Elizabeth PL92T Pressure Tuning
  • jq transformation
  • aws (8)
  • coffee (1)
  • headfi (1)
  • linux (6)
  • others (58)
  • security (2)
  • tech (36)
  • wordpress (2)

apache aws awscli azure backup clearlinux cloud coffee docker DOCP ec2 EL8 epyc espresso featured gpg jenkins kernel lelit linux lvm meltdown memory MFA mikrotik php python rdp Redhat RHEL roasting rpm Ryzen site-to-site snapshot spectre tech terraform tuning ubuntu ubuntu upgrade vim vpn wordpress xdotool

©2023 blog.headdesk.me | Powered by SuperbThemes & WordPress