How To Manage Amazon Inspector in AWS Organizations Using Terraform

Anthony Wat - Jun 9 - - Dev Community

Introduction

Over the past two months, I have published numerous blog posts on managing different AWS security services in AWS Organizations using Terraform. In this blog post, I will cover one remaining AWS service, AWS Inspector, for native vulnerability management. The Terraform resources for Inspector are a bit quirky, so I will show some slightly more advanced techniques to keep the configuration neat and configurable. With that said, let's review the objective.

About the use case

Amazon Inspector is a vulnerability management service that continuously scans AWS workloads for software vulnerabilities and unintended network exposure. Supported compute services include Amazon EC2 instances, container images in Amazon ECR, and AWS Lambda functions.

Similar to other AWS security services, Inspector supports managing multiple accounts with AWS Organizations via the delegated administrator feature. Once an account in the organization is designated as a delegated administrator, it can manage member accounts and view aggregated findings.

Since it is increasingly common to establish an AWS landing zone using AWS Control Tower, we will use the standard account structure in a Control Tower landing zone to demonstrate how to configure Inspector in Terraform:

Control Tower standard OU and account structure

The relevant accounts for our use case in the landing zone are:

The objective is to delegate Inspector administrative duties from the Management account to the Audit account, after which all organization configurations are managed in the Audit account. Let's walk through how to do this using Terraform.

Designating an Inspector administrator account

The Inspector delegated administrator is configured in the Management account, so we need a provider associated with it in Terraform. To keep things simple, we will take a multi-provider approach by defining two providers, one for the Management account and another for the Audit account, using AWS CLI profiles as follows:

provider "aws" {
  alias   = "management"
  # Use "aws configure" to create the "management" profile with the Management account credentials
  profile = "management" 
}

provider "aws" {
  alias   = "audit"
  # Use "aws configure" to create the "audit" profile with the Audit account credentials
  profile = "audit" 
}
Enter fullscreen mode Exit fullscreen mode

⚠ Since Inspector is a regional service, you must apply this Terraform configuration on each region that you are using. Consider using the region argument in your provider definition and a variable to make your Terraform configuration rerunnable in other regions.

We can designate the delegated administrator using the aws_inspector2_delegated_admin_account resource. However, this does not enable Inspector in the delegated administrator account, so we also need to use the aws_inspector2_enabler resource. What I learned from testing the aws_inspector2_enabler resource is that you cannot provide both the delegated account and the member accounts in the account_ids argument, so we need a dedicated aws_inspector2_enabler resource for the Audit account. According to the resource source code, this is to address a legacy Inspector issue.

The resulting Terraform configuration should look like the following (pay special attention to the provider argument in each resource):

data "aws_caller_identity" "audit" {
  provider = aws.audit
}

resource "aws_inspector2_enabler" "audit" {
  provider       = aws.audit
  account_ids    = [data.aws_caller_identity.audit.account_id]
}

resource "aws_inspector2_delegated_admin_account" "audit" {
  provider   = aws.management
  account_id = data.aws_caller_identity.audit.account_id
  depends_on = [aws_inspector2_enabler.audit]
}
Enter fullscreen mode Exit fullscreen mode

Configuring Inspector activation for new member accounts

To allow more control over which scan types are enabled, we can define the following variables and use them with the relevant resources:

# Variable definition (.tfvars)

variable "enable_ec2" {
  description = "Whether Amazon EC2 scans should be enabled for both existing and new member accounts in the organization."
  type        = bool
  default     = true
}

variable "enable_ecr" {
  description = "Whether Amazon ECR scans should be enabled for both existing and new member accounts in the organization."
  type        = bool
  default     = true
}

variable "enable_lambda" {
  description = "Whether Lambda Function scans should be enabled for both existing and new member accounts in the organization."
  type        = bool
  default     = true
}

variable "enable_lambda_code" {
  description = "Whether Lambda code scans should be enabled for both existing and new member accounts in the organization."
  type        = bool
  default     = true
}
Enter fullscreen mode Exit fullscreen mode

In an organizational setup, Inspector can auto-enable on new member accounts. In Terraform, this can be configured using the aws_inspector2_organization_configuration resource. Leveraging the variables above, the resource can be defined as follows:

resource "aws_inspector2_organization_configuration" "this" {
  provider = aws.audit
  auto_enable {
    ec2         = var.enable_ec2
    ecr         = var.enable_ecr
    lambda      = var.enable_lambda
    lambda_code = var.enable_lambda_code && var.enable_lambda
  }
  depends_on = [aws_inspector2_delegated_admin_account.audit]
}
Enter fullscreen mode Exit fullscreen mode

Note that for AWS Lambda code scanning (lambda_code), AWS Lambda standard scanning (lambda) is a prerequisite, so we need to check both variables to enable it.

Now let's address the existing member accounts.

Activating scanning for existing member accounts

Unlike GuardDuty, the Inspector organization configuration does not support auto-enablement for existing member accounts, so we need to separately manage the member accounts. The strategy is to get the list of active member accounts from the organization, which we can use with the Inspector Terraform resources, including the aws_inspector2_enabler resource. We can exclude the Audit account since that is managed separately. To get the list of member accounts in the organization, we can use the aws_organizations_organization data source.

Furthermore, the aws_inspector2_enabler resource's resource_types argument takes a list of strings that represent the scan types to enable. Since the variables we defined earlier are boolean variables, we need a bit of function magic to create the list of scans to enable based on the variables.

The Terraform configuration that addresses the above requirements can be defined as follows:

data "aws_organizations_organization" "this" {
  provider = aws.management
}

locals {
  enabler_resource_types = compact([
    var.enable_ec2 ? "EC2" : null,
    var.enable_ecr ? "ECR" : null,
    var.enable_lambda ? "LAMBDA" : null,
    var.enable_lambda_code && var.enable_lambda ? "LAMBDA_CODE" : null,
  ])

  member_account_ids = [for account in data.aws_organizations_organization.this.accounts : account.id if account.status == "ACTIVE" && account.id != data.aws_caller_identity.audit.account_id]
}
Enter fullscreen mode Exit fullscreen mode

Member accounts are not automatically associated with the delegated administrator account, so they must first be associated using the aws_inspector2_member_association resource.

Using the for_each meta-argument, we can define a single resource to associate all member accounts with the previously defined member_account_ids local value:

resource "aws_inspector2_member_association" "members" {
  provider   = aws.audit
  for_each   = toset(local.member_account_ids)
  account_id = each.key
  depends_on = [aws_inspector2_delegated_admin_account.audit]
}
Enter fullscreen mode Exit fullscreen mode

Lastly, we can enable Inspector scans in the member accounts using the aws_inspector2_enabler resource. Although the account_ids argument can take the list of member accounts, it is more flexible to have one resource per account. Thus, using for_each and the local values, the resource can be defined as follows:

resource "aws_inspector2_enabler" "members" {
  provider       = aws.audit
  for_each       = toset(local.member_account_ids)
  account_ids    = [each.key]
  resource_types = local.enabler_resource_types
  depends_on     = [aws_inspector2_member_association.members]
}
Enter fullscreen mode Exit fullscreen mode

✅ You can find the complete Terraform in the GitHub repository that accompanies this blog post.

Now that the Terraform configuration is fully defined, you can apply it to establish the Audit account as the delegated administration and centrally manage Inspector settings for both new and existing accounts.

Caveats about deactivating Inspector in member accounts

Among the AWS security services, Inspector has the least sophisticated API for organizational management. The mix between auto-enablement for new member accounts and explicit enablement for existing member accounts complicates how they are managed in Terraform, particularly if you are trying to disable Inspector via terraform destroy.

Consider the case where a new member account is added and auto-enablement is applied to this account. If you run terraform destroy as-is, Terraform is not aware of the new member account, and thus Inspector cannot be deactivated in this account. You must manually deactivate the account in each applied region.

Alternatively, you can first run terraform apply so that the aws_inspector2_member_association and aws_inspector2_enabler resource instances are created, then run terraform destroy to properly clean up. While this method works, you must keep track of when new member accounts are added so that you know when to run terraform apply to reconcile the Terraform resources with the updated organization.

In any case, be aware of this caveat and take one of the two approaches if you ever need to clean up Inspector resources.

Summary

In this blog post, you learned how to manage Amazon Inspector in AWS Organizations using Terraform. With a delegated administrator, Inspector can be auto-enabled for new member accounts, while existing member accounts are dynamically associated and configured with the desired scan types. If you have also configured AWS Security Hub to operate at the organization level, you can manage Inspector findings across accounts and regions, thereby streamlining your security operations.

If you are interested in this type of content, be sure to read other posts on the Avangards Blog, where I share tips and deep dives on AWS, Terraform, and beyond. Thank you, and enjoy the rest of your day!

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