Configure AWS Security Group Rules

The aws:ec2/securityGroupRule:SecurityGroupRule resource, part of the Pulumi AWS provider, defines individual ingress or egress rules that control traffic to and from security groups. This guide focuses on three capabilities: CIDR-based traffic rules, VPC endpoint integration, and AWS-managed prefix lists.

This resource is maintained for backwards compatibility. For new projects, consider using aws.vpc.SecurityGroupIngressRule and aws.vpc.SecurityGroupEgressRule, which handle multiple CIDR blocks more reliably and avoid rule conflicts. The examples are intentionally small. Combine them with your own security groups and network infrastructure.

Allow traffic from CIDR blocks on TCP ports

Most rules start by allowing inbound traffic from specific IP ranges, establishing baseline network access for applications.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.ec2.SecurityGroupRule("example", {
    type: "ingress",
    fromPort: 0,
    toPort: 65535,
    protocol: aws.ec2.ProtocolType.TCP,
    cidrBlocks: [exampleAwsVpc.cidrBlock],
    ipv6CidrBlocks: [exampleAwsVpc.ipv6CidrBlock],
    securityGroupId: "sg-123456",
});
import pulumi
import pulumi_aws as aws

example = aws.ec2.SecurityGroupRule("example",
    type="ingress",
    from_port=0,
    to_port=65535,
    protocol=aws.ec2.ProtocolType.TCP,
    cidr_blocks=[example_aws_vpc["cidrBlock"]],
    ipv6_cidr_blocks=[example_aws_vpc["ipv6CidrBlock"]],
    security_group_id="sg-123456")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ec2.NewSecurityGroupRule(ctx, "example", &ec2.SecurityGroupRuleArgs{
			Type:     pulumi.String("ingress"),
			FromPort: pulumi.Int(0),
			ToPort:   pulumi.Int(65535),
			Protocol: pulumi.String(ec2.ProtocolTypeTCP),
			CidrBlocks: pulumi.StringArray{
				exampleAwsVpc.CidrBlock,
			},
			Ipv6CidrBlocks: pulumi.StringArray{
				exampleAwsVpc.Ipv6CidrBlock,
			},
			SecurityGroupId: pulumi.String("sg-123456"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Ec2.SecurityGroupRule("example", new()
    {
        Type = "ingress",
        FromPort = 0,
        ToPort = 65535,
        Protocol = Aws.Ec2.ProtocolType.TCP,
        CidrBlocks = new[]
        {
            exampleAwsVpc.CidrBlock,
        },
        Ipv6CidrBlocks = new[]
        {
            exampleAwsVpc.Ipv6CidrBlock,
        },
        SecurityGroupId = "sg-123456",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.SecurityGroupRule;
import com.pulumi.aws.ec2.SecurityGroupRuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new SecurityGroupRule("example", SecurityGroupRuleArgs.builder()
            .type("ingress")
            .fromPort(0)
            .toPort(65535)
            .protocol("tcp")
            .cidrBlocks(exampleAwsVpc.cidrBlock())
            .ipv6CidrBlocks(exampleAwsVpc.ipv6CidrBlock())
            .securityGroupId("sg-123456")
            .build());

    }
}
resources:
  example:
    type: aws:ec2:SecurityGroupRule
    properties:
      type: ingress
      fromPort: 0
      toPort: 65535
      protocol: tcp
      cidrBlocks:
        - ${exampleAwsVpc.cidrBlock}
      ipv6CidrBlocks:
        - ${exampleAwsVpc.ipv6CidrBlock}
      securityGroupId: sg-123456

The type property sets the traffic direction (ingress for inbound). The fromPort and toPort define the port range, while protocol specifies TCP. The cidrBlocks and ipv6CidrBlocks properties list the IP ranges that can connect. The securityGroupId identifies which security group receives this rule.

Route egress traffic to VPC endpoints

Applications connecting to AWS services through VPC endpoints reference the endpoint’s prefix list rather than hardcoding IP ranges.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// ...
const myEndpoint = new aws.ec2.VpcEndpoint("my_endpoint", {});
const allowAll = new aws.ec2.SecurityGroupRule("allow_all", {
    type: "egress",
    toPort: 0,
    protocol: "-1",
    prefixListIds: [myEndpoint.prefixListId],
    fromPort: 0,
    securityGroupId: "sg-123456",
});
import pulumi
import pulumi_aws as aws

# ...
my_endpoint = aws.ec2.VpcEndpoint("my_endpoint")
allow_all = aws.ec2.SecurityGroupRule("allow_all",
    type="egress",
    to_port=0,
    protocol="-1",
    prefix_list_ids=[my_endpoint.prefix_list_id],
    from_port=0,
    security_group_id="sg-123456")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// ...
		myEndpoint, err := ec2.NewVpcEndpoint(ctx, "my_endpoint", nil)
		if err != nil {
			return err
		}
		_, err = ec2.NewSecurityGroupRule(ctx, "allow_all", &ec2.SecurityGroupRuleArgs{
			Type:     pulumi.String("egress"),
			ToPort:   pulumi.Int(0),
			Protocol: pulumi.String("-1"),
			PrefixListIds: pulumi.StringArray{
				myEndpoint.PrefixListId,
			},
			FromPort:        pulumi.Int(0),
			SecurityGroupId: pulumi.String("sg-123456"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    // ...
    var myEndpoint = new Aws.Ec2.VpcEndpoint("my_endpoint");

    var allowAll = new Aws.Ec2.SecurityGroupRule("allow_all", new()
    {
        Type = "egress",
        ToPort = 0,
        Protocol = "-1",
        PrefixListIds = new[]
        {
            myEndpoint.PrefixListId,
        },
        FromPort = 0,
        SecurityGroupId = "sg-123456",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.VpcEndpoint;
import com.pulumi.aws.ec2.SecurityGroupRule;
import com.pulumi.aws.ec2.SecurityGroupRuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        // ...
        var myEndpoint = new VpcEndpoint("myEndpoint");

        var allowAll = new SecurityGroupRule("allowAll", SecurityGroupRuleArgs.builder()
            .type("egress")
            .toPort(0)
            .protocol("-1")
            .prefixListIds(myEndpoint.prefixListId())
            .fromPort(0)
            .securityGroupId("sg-123456")
            .build());

    }
}
resources:
  allowAll:
    type: aws:ec2:SecurityGroupRule
    name: allow_all
    properties:
      type: egress
      toPort: 0
      protocol: '-1'
      prefixListIds:
        - ${myEndpoint.prefixListId}
      fromPort: 0
      securityGroupId: sg-123456
  # ...
  myEndpoint:
    type: aws:ec2:VpcEndpoint
    name: my_endpoint

When you create a VPC endpoint, AWS assigns it a prefix list ID that represents the service’s IP ranges. The prefixListIds property references this list, allowing traffic to the endpoint. Setting protocol to “-1” permits all protocols, while fromPort and toPort of 0 allow all ports.

Reference AWS service prefix lists by region

AWS publishes managed prefix lists for services like S3, automatically updating as service IP ranges change.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const current = aws.getRegion({});
const s3 = current.then(current => aws.ec2.getPrefixList({
    name: `com.amazonaws.${current.region}.s3`,
}));
const s3GatewayEgress = new aws.ec2.SecurityGroupRule("s3_gateway_egress", {
    description: "S3 Gateway Egress",
    type: "egress",
    securityGroupId: "sg-123456",
    fromPort: 443,
    toPort: 443,
    protocol: aws.ec2.ProtocolType.TCP,
    prefixListIds: [s3.then(s3 => s3.id)],
});
import pulumi
import pulumi_aws as aws

current = aws.get_region()
s3 = aws.ec2.get_prefix_list(name=f"com.amazonaws.{current.region}.s3")
s3_gateway_egress = aws.ec2.SecurityGroupRule("s3_gateway_egress",
    description="S3 Gateway Egress",
    type="egress",
    security_group_id="sg-123456",
    from_port=443,
    to_port=443,
    protocol=aws.ec2.ProtocolType.TCP,
    prefix_list_ids=[s3.id])
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		current, err := aws.GetRegion(ctx, &aws.GetRegionArgs{}, nil)
		if err != nil {
			return err
		}
		s3, err := ec2.GetPrefixList(ctx, &ec2.GetPrefixListArgs{
			Name: pulumi.StringRef(fmt.Sprintf("com.amazonaws.%v.s3", current.Region)),
		}, nil)
		if err != nil {
			return err
		}
		_, err = ec2.NewSecurityGroupRule(ctx, "s3_gateway_egress", &ec2.SecurityGroupRuleArgs{
			Description:     pulumi.String("S3 Gateway Egress"),
			Type:            pulumi.String("egress"),
			SecurityGroupId: pulumi.String("sg-123456"),
			FromPort:        pulumi.Int(443),
			ToPort:          pulumi.Int(443),
			Protocol:        pulumi.String(ec2.ProtocolTypeTCP),
			PrefixListIds: pulumi.StringArray{
				pulumi.String(s3.Id),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var current = Aws.GetRegion.Invoke();

    var s3 = Aws.Ec2.GetPrefixList.Invoke(new()
    {
        Name = $"com.amazonaws.{current.Apply(getRegionResult => getRegionResult.Region)}.s3",
    });

    var s3GatewayEgress = new Aws.Ec2.SecurityGroupRule("s3_gateway_egress", new()
    {
        Description = "S3 Gateway Egress",
        Type = "egress",
        SecurityGroupId = "sg-123456",
        FromPort = 443,
        ToPort = 443,
        Protocol = Aws.Ec2.ProtocolType.TCP,
        PrefixListIds = new[]
        {
            s3.Apply(getPrefixListResult => getPrefixListResult.Id),
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetRegionArgs;
import com.pulumi.aws.ec2.Ec2Functions;
import com.pulumi.aws.ec2.inputs.GetPrefixListArgs;
import com.pulumi.aws.ec2.SecurityGroupRule;
import com.pulumi.aws.ec2.SecurityGroupRuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        final var current = AwsFunctions.getRegion(GetRegionArgs.builder()
            .build());

        final var s3 = Ec2Functions.getPrefixList(GetPrefixListArgs.builder()
            .name(String.format("com.amazonaws.%s.s3", current.region()))
            .build());

        var s3GatewayEgress = new SecurityGroupRule("s3GatewayEgress", SecurityGroupRuleArgs.builder()
            .description("S3 Gateway Egress")
            .type("egress")
            .securityGroupId("sg-123456")
            .fromPort(443)
            .toPort(443)
            .protocol("tcp")
            .prefixListIds(s3.id())
            .build());

    }
}
resources:
  s3GatewayEgress:
    type: aws:ec2:SecurityGroupRule
    name: s3_gateway_egress
    properties:
      description: S3 Gateway Egress
      type: egress
      securityGroupId: sg-123456
      fromPort: 443
      toPort: 443
      protocol: tcp
      prefixListIds:
        - ${s3.id}
variables:
  current:
    fn::invoke:
      function: aws:getRegion
      arguments: {}
  s3:
    fn::invoke:
      function: aws:ec2:getPrefixList
      arguments:
        name: com.amazonaws.${current.region}.s3

The getPrefixList data source finds AWS-managed prefix lists by name. The name follows the pattern “com.amazonaws.{region}.{service}”. The description property documents the rule’s purpose. This configuration allows HTTPS egress to S3 on port 443.

Beyond these examples

These snippets focus on specific security group rule features: CIDR-based ingress and egress rules, and VPC endpoint and AWS service prefix list integration. They’re intentionally minimal rather than full network security configurations.

The examples reference pre-existing infrastructure such as security groups (all examples reference existing group IDs), and VPC endpoints (for prefix list examples). They focus on configuring individual rules rather than provisioning security groups or VPCs.

To keep things focused, common rule patterns are omitted, including:

  • Security group self-referencing (self property)
  • Cross-security-group rules (sourceSecurityGroupId)
  • Rule descriptions for documentation
  • ICMP protocol configuration

These omissions are intentional: the goal is to illustrate how each rule type is wired, not provide drop-in security modules. See the Security Group Rule resource reference for all available configuration options.

Let's configure AWS Security Group Rules

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Resource Selection & Best Practices
Should I use aws.ec2.SecurityGroupRule for new projects?
No. This resource struggles with managing multiple CIDR blocks and lacks support for unique IDs, tags, and descriptions. Use aws.vpc.SecurityGroupEgressRule and aws.vpc.SecurityGroupIngressRule instead, with one CIDR block per rule.
Can I mix aws.ec2.SecurityGroupRule with other security group resources?
No. Using aws.ec2.SecurityGroupRule alongside aws.vpc.SecurityGroupEgressRule, aws.vpc.SecurityGroupIngressRule, or inline rules in aws.ec2.SecurityGroup causes rule conflicts, perpetual differences, and overwritten rules. Choose one approach and stick with it.
Configuration Requirements
What's required to configure the traffic source?
You must provide one of: cidrBlocks, ipv6CidrBlocks, prefixListIds, sourceSecurityGroupId, or self. All are marked optional, but at least one is required to define the traffic source.
Can I use both CIDR blocks and source security group ID in the same rule?
No. cidrBlocks and ipv6CidrBlocks cannot be specified with sourceSecurityGroupId or self. These source options are mutually exclusive.
What properties can't I change after creating a rule?
These properties are immutable: type, fromPort, toPort, protocol, securityGroupId, sourceSecurityGroupId, cidrBlocks, ipv6CidrBlocks, prefixListIds, and self. Changing any of these requires replacing the rule.
What's the difference between ingress and egress rules?
Set type to ingress for inbound traffic rules or egress for outbound traffic rules.
Protocol & Port Behavior
What happens when I set protocol to 'all' or -1?
The EC2 API creates a security group rule with all ports open, regardless of your fromPort and toPort values. This API behavior can’t be controlled by Pulumi and may generate warnings.
Advanced Features
How do I use prefix lists with security group rules?
Use the prefixListIds property. You can reference VPC endpoint prefix lists directly (e.g., myEndpoint.prefixListId) or use the aws.ec2.getPrefixList data source to find AWS-managed prefix lists like S3.
Can I reference security groups across VPC peering connections?
Yes, but with certain restrictions. Refer to the VPC Peering User Guide for details on cross-VPC security group references.
Import Behavior
What happens if I only import some of a rule's CIDR blocks?
Partially importing a rule’s permissions and then making changes creates an additional rule for the updated permissions. The unimported permissions remain in the original rule. Import all permissions to avoid duplicate rules.

Using a different cloud?

Explore networking guides for other cloud providers: