Create AWS Shield Advanced Protection Groups

The aws:shield/protectionGroup:ProtectionGroup resource, part of the Pulumi AWS provider, groups Shield-protected resources for unified DDoS detection, mitigation, and reporting. This guide focuses on two capabilities: pattern-based resource selection and aggregation methods.

Protection groups require an active Shield Advanced subscription and reference existing Shield Protection resources or protected AWS resources. The examples are intentionally small. Combine them with your own Shield protections and monitoring infrastructure.

Protect all Shield-enabled resources in your account

Organizations with Shield Advanced often start by grouping all protected resources together for unified detection and reporting.

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

const example = new aws.shield.ProtectionGroup("example", {
    protectionGroupId: "example",
    aggregation: "MAX",
    pattern: "ALL",
});
import pulumi
import pulumi_aws as aws

example = aws.shield.ProtectionGroup("example",
    protection_group_id="example",
    aggregation="MAX",
    pattern="ALL")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := shield.NewProtectionGroup(ctx, "example", &shield.ProtectionGroupArgs{
			ProtectionGroupId: pulumi.String("example"),
			Aggregation:       pulumi.String("MAX"),
			Pattern:           pulumi.String("ALL"),
		})
		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.Shield.ProtectionGroup("example", new()
    {
        ProtectionGroupId = "example",
        Aggregation = "MAX",
        Pattern = "ALL",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.shield.ProtectionGroup;
import com.pulumi.aws.shield.ProtectionGroupArgs;
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 ProtectionGroup("example", ProtectionGroupArgs.builder()
            .protectionGroupId("example")
            .aggregation("MAX")
            .pattern("ALL")
            .build());

    }
}
resources:
  example:
    type: aws:shield:ProtectionGroup
    properties:
      protectionGroupId: example
      aggregation: MAX
      pattern: ALL

The pattern property set to “ALL” automatically includes every resource with an active Shield protection in your account. The aggregation property controls how Shield combines metrics across resources; “MAX” uses the highest value when detecting anomalies. The protectionGroupId provides a unique identifier for the group.

Group resources by type for targeted monitoring

Teams managing specific resource types often create protection groups that automatically include all resources of that type.

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

const example = new aws.shield.ProtectionGroup("example", {
    protectionGroupId: "example",
    aggregation: "SUM",
    pattern: "BY_RESOURCE_TYPE",
    resourceType: "ELASTIC_IP_ALLOCATION",
});
import pulumi
import pulumi_aws as aws

example = aws.shield.ProtectionGroup("example",
    protection_group_id="example",
    aggregation="SUM",
    pattern="BY_RESOURCE_TYPE",
    resource_type="ELASTIC_IP_ALLOCATION")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := shield.NewProtectionGroup(ctx, "example", &shield.ProtectionGroupArgs{
			ProtectionGroupId: pulumi.String("example"),
			Aggregation:       pulumi.String("SUM"),
			Pattern:           pulumi.String("BY_RESOURCE_TYPE"),
			ResourceType:      pulumi.String("ELASTIC_IP_ALLOCATION"),
		})
		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.Shield.ProtectionGroup("example", new()
    {
        ProtectionGroupId = "example",
        Aggregation = "SUM",
        Pattern = "BY_RESOURCE_TYPE",
        ResourceType = "ELASTIC_IP_ALLOCATION",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.shield.ProtectionGroup;
import com.pulumi.aws.shield.ProtectionGroupArgs;
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 ProtectionGroup("example", ProtectionGroupArgs.builder()
            .protectionGroupId("example")
            .aggregation("SUM")
            .pattern("BY_RESOURCE_TYPE")
            .resourceType("ELASTIC_IP_ALLOCATION")
            .build());

    }
}
resources:
  example:
    type: aws:shield:ProtectionGroup
    properties:
      protectionGroupId: example
      aggregation: SUM
      pattern: BY_RESOURCE_TYPE
      resourceType: ELASTIC_IP_ALLOCATION

When pattern is “BY_RESOURCE_TYPE”, the resourceType property filters to a specific AWS resource type like “ELASTIC_IP_ALLOCATION”. This automatically includes all Elastic IPs with Shield protection without listing individual ARNs. The “SUM” aggregation adds metrics across all resources in the group.

Select specific resources for custom grouping

Applications with critical resources that need specialized monitoring can create protection groups with an explicit list of ARNs.

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

const current = aws.getRegion({});
const currentGetCallerIdentity = aws.getCallerIdentity({});
const example = new aws.ec2.Eip("example", {domain: "vpc"});
const exampleProtection = new aws.shield.Protection("example", {
    name: "example",
    resourceArn: pulumi.all([current, currentGetCallerIdentity, example.id]).apply(([current, currentGetCallerIdentity, id]) => `arn:aws:ec2:${current.region}:${currentGetCallerIdentity.accountId}:eip-allocation/${id}`),
});
const exampleProtectionGroup = new aws.shield.ProtectionGroup("example", {
    protectionGroupId: "example",
    aggregation: "MEAN",
    pattern: "ARBITRARY",
    members: [pulumi.all([current, currentGetCallerIdentity, example.id]).apply(([current, currentGetCallerIdentity, id]) => `arn:aws:ec2:${current.region}:${currentGetCallerIdentity.accountId}:eip-allocation/${id}`)],
}, {
    dependsOn: [exampleProtection],
});
import pulumi
import pulumi_aws as aws

current = aws.get_region()
current_get_caller_identity = aws.get_caller_identity()
example = aws.ec2.Eip("example", domain="vpc")
example_protection = aws.shield.Protection("example",
    name="example",
    resource_arn=example.id.apply(lambda id: f"arn:aws:ec2:{current.region}:{current_get_caller_identity.account_id}:eip-allocation/{id}"))
example_protection_group = aws.shield.ProtectionGroup("example",
    protection_group_id="example",
    aggregation="MEAN",
    pattern="ARBITRARY",
    members=[example.id.apply(lambda id: f"arn:aws:ec2:{current.region}:{current_get_caller_identity.account_id}:eip-allocation/{id}")],
    opts = pulumi.ResourceOptions(depends_on=[example_protection]))
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-aws/sdk/v7/go/aws/shield"
	"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
		}
		currentGetCallerIdentity, err := aws.GetCallerIdentity(ctx, &aws.GetCallerIdentityArgs{}, nil)
		if err != nil {
			return err
		}
		example, err := ec2.NewEip(ctx, "example", &ec2.EipArgs{
			Domain: pulumi.String("vpc"),
		})
		if err != nil {
			return err
		}
		exampleProtection, err := shield.NewProtection(ctx, "example", &shield.ProtectionArgs{
			Name: pulumi.String("example"),
			ResourceArn: example.ID().ApplyT(func(id string) (string, error) {
				return fmt.Sprintf("arn:aws:ec2:%v:%v:eip-allocation/%v", current.Region, currentGetCallerIdentity.AccountId, id), nil
			}).(pulumi.StringOutput),
		})
		if err != nil {
			return err
		}
		_, err = shield.NewProtectionGroup(ctx, "example", &shield.ProtectionGroupArgs{
			ProtectionGroupId: pulumi.String("example"),
			Aggregation:       pulumi.String("MEAN"),
			Pattern:           pulumi.String("ARBITRARY"),
			Members: pulumi.StringArray{
				example.ID().ApplyT(func(id string) (string, error) {
					return fmt.Sprintf("arn:aws:ec2:%v:%v:eip-allocation/%v", current.Region, currentGetCallerIdentity.AccountId, id), nil
				}).(pulumi.StringOutput),
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			exampleProtection,
		}))
		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 currentGetCallerIdentity = Aws.GetCallerIdentity.Invoke();

    var example = new Aws.Ec2.Eip("example", new()
    {
        Domain = "vpc",
    });

    var exampleProtection = new Aws.Shield.Protection("example", new()
    {
        Name = "example",
        ResourceArn = Output.Tuple(current, currentGetCallerIdentity, example.Id).Apply(values =>
        {
            var current = values.Item1;
            var currentGetCallerIdentity = values.Item2;
            var id = values.Item3;
            return $"arn:aws:ec2:{current.Apply(getRegionResult => getRegionResult.Region)}:{currentGetCallerIdentity.Apply(getCallerIdentityResult => getCallerIdentityResult.AccountId)}:eip-allocation/{id}";
        }),
    });

    var exampleProtectionGroup = new Aws.Shield.ProtectionGroup("example", new()
    {
        ProtectionGroupId = "example",
        Aggregation = "MEAN",
        Pattern = "ARBITRARY",
        Members = new[]
        {
            Output.Tuple(current, currentGetCallerIdentity, example.Id).Apply(values =>
            {
                var current = values.Item1;
                var currentGetCallerIdentity = values.Item2;
                var id = values.Item3;
                return $"arn:aws:ec2:{current.Apply(getRegionResult => getRegionResult.Region)}:{currentGetCallerIdentity.Apply(getCallerIdentityResult => getCallerIdentityResult.AccountId)}:eip-allocation/{id}";
            }),
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            exampleProtection,
        },
    });

});
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.inputs.GetCallerIdentityArgs;
import com.pulumi.aws.ec2.Eip;
import com.pulumi.aws.ec2.EipArgs;
import com.pulumi.aws.shield.Protection;
import com.pulumi.aws.shield.ProtectionArgs;
import com.pulumi.aws.shield.ProtectionGroup;
import com.pulumi.aws.shield.ProtectionGroupArgs;
import com.pulumi.resources.CustomResourceOptions;
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 currentGetCallerIdentity = AwsFunctions.getCallerIdentity(GetCallerIdentityArgs.builder()
            .build());

        var example = new Eip("example", EipArgs.builder()
            .domain("vpc")
            .build());

        var exampleProtection = new Protection("exampleProtection", ProtectionArgs.builder()
            .name("example")
            .resourceArn(example.id().applyValue(_id -> String.format("arn:aws:ec2:%s:%s:eip-allocation/%s", current.region(),currentGetCallerIdentity.accountId(),_id)))
            .build());

        var exampleProtectionGroup = new ProtectionGroup("exampleProtectionGroup", ProtectionGroupArgs.builder()
            .protectionGroupId("example")
            .aggregation("MEAN")
            .pattern("ARBITRARY")
            .members(example.id().applyValue(_id -> String.format("arn:aws:ec2:%s:%s:eip-allocation/%s", current.region(),currentGetCallerIdentity.accountId(),_id)))
            .build(), CustomResourceOptions.builder()
                .dependsOn(exampleProtection)
                .build());

    }
}
resources:
  example:
    type: aws:ec2:Eip
    properties:
      domain: vpc
  exampleProtection:
    type: aws:shield:Protection
    name: example
    properties:
      name: example
      resourceArn: arn:aws:ec2:${current.region}:${currentGetCallerIdentity.accountId}:eip-allocation/${example.id}
  exampleProtectionGroup:
    type: aws:shield:ProtectionGroup
    name: example
    properties:
      protectionGroupId: example
      aggregation: MEAN
      pattern: ARBITRARY
      members:
        - arn:aws:ec2:${current.region}:${currentGetCallerIdentity.accountId}:eip-allocation/${example.id}
    options:
      dependsOn:
        - ${exampleProtection}
variables:
  current:
    fn::invoke:
      function: aws:getRegion
      arguments: {}
  currentGetCallerIdentity:
    fn::invoke:
      function: aws:getCallerIdentity
      arguments: {}

The “ARBITRARY” pattern requires the members property, which lists specific resource ARNs to include. This example constructs an EIP ARN from region, account ID, and allocation ID. The dependsOn ensures the Shield Protection exists before creating the group. The “MEAN” aggregation averages metrics across the specified resources.

Beyond these examples

These snippets focus on specific protection group features: pattern-based resource selection and aggregation methods for event detection. They’re intentionally minimal rather than full DDoS protection configurations.

The examples assume pre-existing infrastructure such as Shield Advanced subscription, Shield Protection resources (for ARBITRARY pattern), and protected resources like EIPs or CloudFront distributions. They focus on configuring the protection group rather than provisioning the underlying protections.

To keep things focused, common protection group patterns are omitted, including:

  • Tagging for organization and cost tracking
  • Integration with CloudWatch alarms or SNS notifications
  • Cross-account protection group management
  • Dynamic membership updates as resources are added/removed

These omissions are intentional: the goal is to illustrate how each protection group pattern is wired, not provide drop-in DDoS protection modules. See the Shield ProtectionGroup resource reference for all available configuration options.

Let's create AWS Shield Advanced Protection Groups

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Pattern Configuration
What are the different pattern types for protection groups?
There are three pattern types: ALL protects all resources, ARBITRARY protects specific resources you list, and BY_RESOURCE_TYPE protects all resources of a specific type.
When do I need to set the members property?
Set members only when using pattern: ARBITRARY to specify resource ARNs. You must not set it for ALL or BY_RESOURCE_TYPE patterns.
When do I need to set the resourceType property?
Set resourceType only when using pattern: BY_RESOURCE_TYPE to specify the resource type. You must not set it for ALL or ARBITRARY patterns.
What aggregation methods are available?
You can use MAX, MEAN, or SUM to define how Shield combines resource data for detection and mitigation.
Resource Management
Can I rename a protection group after creation?
No, protectionGroupId is immutable and changing it forces resource replacement.
Why does the ARBITRARY pattern example use dependsOn?
When using ARBITRARY pattern with specific resources, the protection group depends on individual Shield protections being created first for those resources.
What's the purpose of a Shield protection group?
Protection groups improve detection accuracy and reduce false positives by handling protected resources as a collective unit.

Using a different cloud?

Explore security guides for other cloud providers: