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. The ARBITRARY pattern requires existing Shield Protection resources. The examples are intentionally small. Combine them with your own Shield protections and monitoring infrastructure.
Monitor all Shield-protected resources as one group
Organizations with Shield Advanced often start by grouping all protected resources together for unified visibility into DDoS events.
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
When pattern is set to ALL, Shield automatically includes every resource with an active Shield protection in your account. The aggregation property controls how Shield combines metrics across resources: MAX takes the highest value, useful for detecting the largest attack surface. The protectionGroupId provides a unique identifier for the group.
Group resources by type for targeted monitoring
Teams managing specific resource types 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
The BY_RESOURCE_TYPE pattern requires the resourceType property, which specifies which AWS resource type to include (like ELASTIC_IP_ALLOCATION, CLOUDFRONT_DISTRIBUTION, or APPLICATION_LOAD_BALANCER). Shield automatically discovers and includes all resources of that type. The SUM aggregation adds metrics across resources, useful for understanding total traffic or attack volume.
Select specific resources for custom grouping
Applications with critical resources create protection groups with explicit ARN lists for precise control.
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 array, 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 resources, smoothing out individual spikes.
Beyond these examples
These snippets focus on specific protection group features: pattern-based resource selection and aggregation methods. They’re intentionally minimal rather than full DDoS protection strategies.
The examples assume pre-existing infrastructure such as an AWS Shield Advanced subscription and Shield Protection resources for the ARBITRARY pattern. They focus on configuring the protection group rather than provisioning the underlying protections.
To keep things focused, common protection group patterns are omitted, including:
- Tags for organization and cost tracking
- Multiple protection groups for different teams or applications
- Integration with CloudWatch alarms or SNS notifications
- Cross-region protection group strategies
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 FREEFrequently Asked Questions
Configuration & Patterns
You have three options for selecting protected resources:
- ALL - Protects all resources (no
membersorresourceTypeneeded) - ARBITRARY - Protects specific resources (requires
membersarray with resource ARNs) - BY_RESOURCE_TYPE - Protects all resources of a type (requires
resourceTypelikeELASTIC_IP_ALLOCATION)
dependsOn ensures the Shield Protection resource exists before creating the protection group that references it in the members array.Aggregation & Detection
MAX, MEAN, and SUM. Choose based on how you want Shield to detect and report events across your grouped resources.Immutability & Updates
protectionGroupId is immutable and changing it forces resource replacement. Plan your naming carefully.