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
| Resource | Service | Why |
|---|---|---|
aws_instance | Amazon EC2 | instance_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 approvalA 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.
| Parameter | Type | Default | Description |
|---|---|---|---|
denied | list(string) | ["p3.*", "p4.*", "x1.*", "x2.*", "u-*"] | Denied instance type patterns |
message | string | "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, andapplyall proceed normally. - Reads literal values only.
instance_type = var.instance_type, alocal, adatalookup, 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_typeyou set in your root module or pass into amoduleblock 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 namedp4dandp4de),x2.*matches no released x2 instance (x2gd,x2idn,x2iedn,x2iezn), andp3dn.24xlarge, thex1efamily, and the whole p5 family are not matched either. - Only checks
instance_typeonaws_instance. Does not checkaws_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
.tfand.tofufiles 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
| Scenario | Result |
|---|---|
| A module hardcodes a denied instance type | The rule records a finding against that resource address. The module is delivered unchanged and the deployment proceeds. |
A module sets instance_type from a variable | No finding. The value is not knowable when the rule runs, so the resource is skipped. |
| AWS releases a new expensive family | Not matched. The deny list is a fixed set of patterns and has to be extended before the new family is recognised. |
Related
- Rulesets in the console - where an org admin adds this rule to the Baseline or a ruleset
- Rule catalog - every rule, with what each one changes