Sign in
Menu
Sign up
Docs
Operational rulesRule catalog

Restrict instance types

For platform engineers deciding whether to apply resource_restrict_instance_types: what it changes, how to enable it, and how to check it worked.

Records every aws_instance in the module source whose instance_type is a literal matching a denied family. Reports; does not block.

What this rule does

Reports — it does not rewrite. Every resource in the module source is scanned, and a literal instance_type value matching a denied pattern is recorded as a finding. The module is delivered byte-for-byte unchanged, and terraform init still succeeds.

Patterns are shell-style globs matched against the whole value, so p3.* matches p3.2xlarge but not p3dn.24xlarge.

Denied patterns: p3.*, p4.*, x1.*, x2.*, u-*

Affected resources

ResourceServiceWhy
aws_instanceAmazon EC2instance_type determines compute cost

What a finding looks like

The rule reads the module source. A literal value that matches a denied pattern produces a finding:

resource "aws_instance" "this" {
  instance_type = "p3.8xlarge"
}
! resource.aws_instance.this: GPU and high-memory instance types are not permitted without approval

A value outside the deny list produces nothing:

resource "aws_instance" "this" {
  instance_type = "m5.xlarge"
}

Either way the module source is delivered unchanged. Nothing is blocked, rewritten, or added.

Why this rule exists

AWS instance families built for GPU computing (p3, p4, p5), memory-intensive workloads (x1, x2), and high-memory bare metal (u-*) run at many times the hourly rate of a general-purpose instance. A module that hardcodes one of them keeps charging for as long as nobody looks.

This rule gives you the signal at download time. It reads the module source and records any aws_instance whose instance_type is written in as a literal and matches a denied pattern. It is a detection control, not an enforcement one: the module arrives exactly as published and terraform apply still runs. What you get is a finding you can act on before the module reaches production.

Default configuration

These are the values the rule ships with. Parameters never travel in a module source URL, so a rule named there runs with exactly these defaults. Your organization sets them per rule, in its Baseline or in a ruleset.

ParameterTypeDefaultDescription
deniedlist(string)["p3.*", "p4.*", "x1.*", "x2.*", "u-*"]Denied instance type patterns
messagestring"GPU and high-memory instance types are not permitted without approval"Validation error message

The HCL this rule is written in, with its parameters and the transformers it runs, is published at resource_restrict_instance_types.

How to enable

On a framework host, add the rule on top of your organization's Baseline with ?add_rules= in the HTTPS module source:

module "ec2_instance" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/ec2-instance/aws?version=x.y.z&add_rules=resource_restrict_instance_types"
}

On your organization's host, an org admin adds the rule to the Baseline, which reaches every module served from that host, or to a named ruleset that a module selects with ?ruleset=:

module "ec2_instance" {
  source = "https://bobthecorp.compliance.tf/terraform-aws-modules/ec2-instance/aws?version=x.y.z&ruleset=<ruleset-name>"
}

On upstream terraform-aws-modules, including this one, the instance type comes from a variable, so the rule records nothing. It is for in-house or forked modules that write a literal instance_type.

Replace x.y.z with the release you use and bobthecorp with your organization's alias. Which parameters each host accepts, and which it refuses with a 400, is on Rulesets in the console.

How to check it worked

Read compliancetf-manifest.json in the module you received. Its entry for this rule carries an outcome of enforced, waived_by_consent, not_configured or not_applicable; a missing outcome was never classified and is not enforcement. Do not infer anything from a successful terraform init: the module source is delivered unchanged either way.

Known limits

  • Reports only. The module source is delivered unchanged, no validation block is added, and terraform init, plan, and apply all proceed normally.
  • Reads literal values only. instance_type = var.instance_type, a local, a data lookup, an interpolation, or a function call is skipped, because the value is not knowable at download time. Most terraform-aws-modules take the instance type from a variable, so the rule finds nothing in them.
  • Reads the module source, not your own configuration. An instance_type you set in your root module or pass into a module block is never examined.
  • The default patterns are globs matched against the whole value, and several match less than their names suggest: p4.* matches no released p4 instance (they are named p4d and p4de), x2.* matches no released x2 instance (x2gd, x2idn, x2iedn, x2iezn), and p3dn.24xlarge, the x1e family, and the whole p5 family are not matched either.
  • Only checks instance_type on aws_instance. Does not check aws_launch_template, aws_autoscaling_group, aws_sagemaker_endpoint_configuration, or ECS task size.
  • Does not restrict other expensive resource types (e.g., large RDS instances, Redshift nodes, OpenSearch domains).
  • Only .tf and .tofu files are read. Modules written in JSON syntax (.tf.json, .tofu.json) are left untouched.
  • Only the module's own directory and its first-level modules/* submodules are processed. Modules that this module calls by source URL are separate downloads and are handled on their own request.
  • Works with Terraform 1.x and OpenTofu 1.6 and later. The rule reads ordinary HCL, and anything it changes stays ordinary HCL.

Failure modes

ScenarioResult
A module hardcodes a denied instance typeThe rule records a finding against that resource address. The module is delivered unchanged and the deployment proceeds.
A module sets instance_type from a variableNo finding. The value is not knowable when the rule runs, so the resource is skipped.
AWS releases a new expensive familyNot matched. The deny list is a fixed set of patterns and has to be extended before the new family is recognised.

On this page

Ask AI about this

Help improve this page