How To Manage Amazon Inspector in AWS Organizations Using Terraform
Learn how to activate Inspector scans in Amazon Inspector via delegated administration in Terraform.
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:
The relevant accounts for our use case in the landing zone are:
The Management account for the organization where AWS Organizations is configured. For details, refer to Managing multiple accounts in Amazon Inspector with Organizations.
The Audit account where security and compliance services are typically centralized in a Control Tower landing zone.
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"
}
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]
}
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
}
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]
}
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]
}
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]
}
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]
}
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!