Integration Examples

Examples showing how to use Security Groups provisioned by the SG Provisioner with common AWS services and workflows.

Table of Contents


Discovering Security Group IDs

After deploying with create-security-groups, each tier’s SG ID is stored in SSM Parameter Store at:

/sg/{sg-name}/{tier}/SecurityGroupId

Retrieve All SG IDs for a Deployment

aws ssm get-parameters-by-path \
  --path /sg/globalbank-prod-c001-us-west-2-sg/ \
  --region us-west-2 \
  --query 'Parameters[].{Name:Name,Value:Value}' \
  --output table

Retrieve a Specific Tier’s SG ID

# Web tier
aws ssm get-parameter \
  --name /sg/globalbank-prod-c001-us-west-2-sg/web/SecurityGroupId \
  --region us-west-2 \
  --query 'Parameter.Value' --output text

# App tier
aws ssm get-parameter \
  --name /sg/globalbank-prod-c001-us-west-2-sg/app/SecurityGroupId \
  --region us-west-2 \
  --query 'Parameter.Value' --output text

# DB tier
aws ssm get-parameter \
  --name /sg/globalbank-prod-c001-us-west-2-sg/db/SecurityGroupId \
  --region us-west-2 \
  --query 'Parameter.Value' --output text

Retrieve SG IDs via Boto3

import boto3

ssm = boto3.client('ssm', region_name='us-west-2')

def get_sg_ids(sg_name: str) -> dict:
    """Retrieve all SG IDs for a deployment from Parameter Store."""
    response = ssm.get_parameters_by_path(
        Path=f'/sg/{sg_name}/',
        Recursive=True
    )
    return {
        param['Name'].split('/')[-2]: param['Value']
        for param in response['Parameters']
    }

sg_ids = get_sg_ids('globalbank-prod-c001-us-west-2-sg')
print(sg_ids)
# {'web': 'sg-0abc123', 'app': 'sg-0def456', 'db': 'sg-0ghi789'}

EC2 Integration

When launching an EC2 instance, the AMI defines what OS and software it will run, the instance type defines compute resources, and the subnet places it inside a specific network segment within the VPC. The security group acts as a virtual firewall attached to the instance — controlling what traffic is allowed in and out. By using the SG provisioned by SG Provisioner, the instance automatically inherits all the rules defined in the scenario:

  • Web tier — accepts HTTPS/HTTP from the internet (0.0.0.0/0), egress on port 443

  • App tier — only accepts traffic from the web tier on port 8080, can reach the DB tier on the configured DB port

  • DB tier — only accepts traffic from the app tier on the DB port, no outbound internet access

Launch Instance with Provisioned Security Groups

# Get app tier SG ID
APP_SG=$(aws ssm get-parameter \
  --name /sg/globalbank-prod-c001-us-west-2-sg/app/SecurityGroupId \
  --region us-west-2 \
  --query 'Parameter.Value' --output text)

# Launch EC2 instance in app tier
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.medium \
  --subnet-id subnet-0a1b2c3d \
  --security-group-ids $APP_SG \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=app-server}]' \
  --region us-west-2

Launch Web-Facing Instance

WEB_SG=$(aws ssm get-parameter \
  --name /sg/globalbank-prod-c001-us-west-2-sg/web/SecurityGroupId \
  --region us-west-2 \
  --query 'Parameter.Value' --output text)

aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.small \
  --subnet-id subnet-public-1 \
  --security-group-ids $WEB_SG \
  --associate-public-ip-address \
  --region us-west-2

RDS Integration

When creating an RDS instance, the DB subnet group defines which subnets within the VPC the database can be placed in — these are typically isolated database subnets with no direct internet access. The security group controls which resources can connect to the database. By using the DB tier SG provisioned by SG Provisioner, only the app tier is allowed to connect on the configured database port.

Create RDS Instance Using DB Tier Security Group

# Get DB tier SG ID
DB_SG=$(aws ssm get-parameter \
  --name /sg/globalbank-prod-c001-us-west-2-sg/db/SecurityGroupId \
  --region us-west-2 \
  --query 'Parameter.Value' --output text)

# Create DB subnet group
aws rds create-db-subnet-group \
  --db-subnet-group-name globalbank-prod-db-subnets \
  --db-subnet-group-description "DB subnets for globalbank prod" \
  --subnet-ids subnet-db-1 subnet-db-2 \
  --region us-west-2

# Create RDS instance
aws rds create-db-instance \
  --db-instance-identifier globalbank-prod-postgres \
  --db-instance-class db.t3.medium \
  --engine postgres \
  --master-username admin \
  --master-user-password <password> \
  --db-subnet-group-name globalbank-prod-db-subnets \
  --vpc-security-group-ids $DB_SG \
  --no-publicly-accessible \
  --region us-west-2

Create RDS via Boto3

import boto3

ssm = boto3.client('ssm', region_name='us-west-2')
rds = boto3.client('rds', region_name='us-west-2')

db_sg = ssm.get_parameter(
    Name='/sg/globalbank-prod-c001-us-west-2-sg/db/SecurityGroupId'
)['Parameter']['Value']

rds.create_db_instance(
    DBInstanceIdentifier='globalbank-prod-postgres',
    DBInstanceClass='db.t3.medium',
    Engine='postgres',
    MasterUsername='admin',
    MasterUserPassword='<password>',
    DBSubnetGroupName='globalbank-prod-db-subnets',
    VpcSecurityGroupIds=[db_sg],
    PubliclyAccessible=False
)

ECS/Fargate Integration

Amazon ECS on AWS Fargate integrates with Security Groups by treating each running task as a unique network entity within your VPC. Because Fargate uses the awsvpc network mode, every task is assigned its own Elastic Network Interface (ENI), allowing you to apply granular firewall rules directly to individual tasks rather than a shared host.

Key Integration Mechanics:

  • ENI-Level Enforcement — When a Fargate task starts, AWS attaches an ENI to the underlying managed instance. The Security Group rules are applied to this ENI, controlling all inbound and outbound traffic for the containers within that specific task.

  • Service-Level Configuration — Security Groups are defined at the ECS Service or Task level via the networkConfiguration parameter, specifying which SG IDs should be associated with the task’s ENI.

  • Micro-segmentation — Since each task has its own Security Group association, you can implement a least-privilege model. For example, allowing traffic only from a specific Load Balancer’s Security Group to your application task on a specific port.

Implementation Details:

  • Inbound Rules — Explicitly allow the ports your containerized application listens on (e.g., port 8080). If using an Application Load Balancer (ALB), best practice is to allow traffic only from the ALB’s Security Group.

  • Outbound Rules — By default Security Groups allow all outbound traffic. Restrict this to only allow connections to specific internal services (e.g., RDS database) or external APIs.

  • Dynamic Updates — You can update Security Groups on an existing ECS Service. ECS will gradually replace old tasks with new ones using the updated network configuration.

Feature

ECS on Fargate

ECS on EC2

Enforcement Point

Task’s own ENI

Host EC2 instance or Task ENI (if awsvpc)

Isolation

Strong — each task is in its own VM boundary

Tasks share the host’s OS and network stack

Management

AWS manages the underlying network infrastructure

User manages EC2 instance security groups

Create ECS Service with Provisioned Security Groups

# Get app tier SG ID
APP_SG=$(aws ssm get-parameter \
  --name /sg/globalbank-prod-c001-us-west-2-sg/app/SecurityGroupId \
  --region us-west-2 \
  --query 'Parameter.Value' --output text)

# Create ECS service
aws ecs create-service \
  --cluster globalbank-prod-cluster \
  --service-name fraud-detection-service \
  --task-definition fraud-detection:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={
    subnets=[subnet-private-1,subnet-private-2],
    securityGroups=[$APP_SG],
    assignPublicIp=DISABLED
  }" \
  --region us-west-2

ECS Task Definition with Security Group Reference

{
  "family": "fraud-detection",
  "networkMode": "awsvpc",
  "containerDefinitions": [
    {
      "name": "fraud-api",
      "image": "123456789012.dkr.ecr.us-west-2.amazonaws.com/fraud-api:latest",
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "tcp"
        }
      ]
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024"
}

Lambda Integration

Integrating AWS Lambda functions with Security Groups is necessary when your function needs to interact with resources inside a VPC, such as an RDS database or an EC2 instance. By default, Lambda functions run in a service-managed VPC and do not have security groups.

Core Integration Concepts:

  • VPC Configuration Required — To use security groups, you must configure the Lambda function to connect to a specific VPC by providing subnet IDs and security group IDs.

  • ENI Creation — When connected to a VPC, AWS Lambda creates Elastic Network Interfaces (ENIs) for each combination of subnet and security group specified in your configuration.

  • Outbound Rules Only — For the Lambda function itself, security groups primarily control outbound connections (egress). Because Lambda functions are invoked by the service rather than receiving direct network connections, they do not have open inbound ports.

  • Referencing Groups — A common best practice is to assign a dedicated “source” security group to the Lambda function and then update the “target” resource’s security group (e.g., your RDS database or app tier) to allow inbound traffic from that specific Lambda security group ID. Do not reuse the app tier SG for Lambda — Lambda acts as a client connecting to the app or DB tier, not as a member of those tiers.

Important Considerations:

  • No Default Internet Access — Once a function is associated with a VPC, it loses direct internet access. To restore it, you must route traffic through a NAT Gateway in a public subnet.

  • Execution Role Permissions — Your function’s IAM execution role must have permissions to manage network interfaces (ec2:CreateNetworkInterface, ec2:DescribeNetworkInterfaces, and ec2:DeleteNetworkInterface) to complete the VPC integration.

  • Shared Infrastructure — Lambda uses Hyperplane ENIs, which are managed resources that sit in your VPC and allow multiple execution environments to securely access your private resources.

Deploy Lambda in VPC with Provisioned Security Groups

Note: Lambda functions should use a dedicated security group rather than reusing the app or DB tier SGs. The example below uses the app tier SG ID as the target — allowing the Lambda’s dedicated SG to connect to the app tier. In practice, create a separate Lambda SG and reference it as the source in your app/DB tier inbound rules.

APP_SG=$(aws ssm get-parameter \
  --name /sg/globalbank-prod-c001-us-west-2-sg/app/SecurityGroupId \
  --region us-west-2 \
  --query 'Parameter.Value' --output text)

aws lambda create-function \
  --function-name fraud-scorer \
  --runtime python3.13 \
  --role arn:aws:iam::123456789012:role/lambda-execution-role \
  --handler handler.lambda_handler \
  --zip-file fileb://function.zip \
  --vpc-config SubnetIds=subnet-private-1,subnet-private-2,SecurityGroupIds=$APP_SG \
  --region us-west-2

Lambda via Boto3

import boto3

ssm = boto3.client('ssm', region_name='us-west-2')
lambda_client = boto3.client('lambda', region_name='us-west-2')

app_sg = ssm.get_parameter(
    Name='/sg/globalbank-prod-c001-us-west-2-sg/app/SecurityGroupId'
)['Parameter']['Value']

lambda_client.create_function(
    FunctionName='fraud-scorer',
    Runtime='python3.13',
    Role='arn:aws:iam::123456789012:role/lambda-execution-role',
    Handler='handler.lambda_handler',
    Code={'ZipFile': open('function.zip', 'rb').read()},
    VpcConfig={
        'SubnetIds': ['subnet-private-1', 'subnet-private-2'],
        'SecurityGroupIds': [app_sg]
    }
)

SageMaker Integration

Integrating Amazon SageMaker with security groups allows you to apply network-level controls to your machine learning workflows by treating SageMaker resources like standard Amazon EC2 instances within your Virtual Private Cloud (VPC).

Core Integration Mechanism:

When you run SageMaker resources (such as Studio, notebook instances, or training jobs) within a VPC, SageMaker creates Elastic Network Interfaces (ENIs) in your specified private subnets. You associate security groups with these ENIs to control inbound and outbound traffic. Security groups are essential for several SageMaker components to ensure secure infrastructure.

Key Use Cases for Security Groups:

  • SageMaker Studio & Notebooks: Control access to internal data sources (like Amazon Redshift or Amazon RDS) or shared services like a private PyPi repository.

  • Training Jobs: For distributed training, security groups must allow all IPv4 traffic between training nodes within the same security group to facilitate communication.

  • Hosting Endpoints: Restrict which clients or services can invoke your ML models by managing inbound rules on the endpoint’s security group.

  • Internet Control: When Direct Internet Access is disabled, security groups, alongside NAT Gateways or Interface Endpoints (AWS PrivateLink), govern how SageMaker reaches external resources.

Best Practices for SageMaker Security Groups:

  • Stateful Rules: Remember that security groups are stateful; an allowed inbound request automatically allows the outbound response, regardless of outbound rules.

  • Least Privilege: Avoid “allow all” rules (e.g., 0.0.0.0/0). Instead, whitelist specific security group IDs as the source for inbound traffic to ensure only authorized VPC resources can communicate with your SageMaker instances.

  • Interface Endpoints: Use security groups to restrict traffic to VPC Endpoints, ensuring traffic to AWS services like S3 or CloudWatch never leaves the Amazon network.

SageMaker Training Job in VPC

import boto3

ssm = boto3.client('ssm', region_name='us-west-2')
sagemaker = boto3.client('sagemaker', region_name='us-west-2')

app_sg = ssm.get_parameter(
    Name='/sg/globalbank-prod-c001-us-west-2-sg/app/SecurityGroupId'
)['Parameter']['Value']

sagemaker.create_training_job(
    TrainingJobName='fraud-model-training',
    AlgorithmSpecification={
        'TrainingImage': '123456789012.dkr.ecr.us-west-2.amazonaws.com/fraud-trainer:latest',
        'TrainingInputMode': 'File'
    },
    RoleArn='arn:aws:iam::123456789012:role/sagemaker-execution-role',
    VpcConfig={
        'SecurityGroupIds': [app_sg],
        'Subnets': ['subnet-private-1', 'subnet-private-2']
    },
    OutputDataConfig={
        'S3OutputPath': 's3://globalbank-prod-c001-us-west-2-s3/models/'
    },
    ResourceConfig={
        'InstanceType': 'ml.m5.xlarge',
        'InstanceCount': 1,
        'VolumeSizeInGB': 50
    },
    StoppingCondition={'MaxRuntimeInSeconds': 3600}
)

Cross-Provisioner Integration

The SG Provisioner is designed to work alongside the VPC Provisioner and S3 Provisioner. Resources from each provisioner are discoverable via SSM Parameter Store.

Discover Resources from All Provisioners

# VPC resources (from VPC Provisioner)
aws ssm get-parameters-by-path \
  --path /vpc/globalbank-prod-c001-us-west-2-vpc/ \
  --region us-west-2 \
  --query 'Parameters[].{Name:Name,Value:Value}'

# Security Group IDs (from SG Provisioner)
aws ssm get-parameters-by-path \
  --path /sg/globalbank-prod-c001-us-west-2-sg/ \
  --region us-west-2 \
  --query 'Parameters[].{Name:Name,Value:Value}'

# S3 bucket details (from S3 Provisioner)
aws ssm get-parameters-by-path \
  --path /s3/globalbank-prod-c001-us-west-2-s3/ \
  --region us-west-2 \
  --query 'Parameters[].{Name:Name,Value:Value}'

Use VPC from VPC Provisioner in SG Config

security_groups:
  vpc_source: parameter-store
  vpc_parameter_store_path: /vpc/globalbank-prod-c001-us-west-2-vpc/VPCId

Full Stack Integration via Boto3

import boto3

ssm = boto3.client('ssm', region_name='us-west-2')

def get_stack_resources(company_prefix: str, env: str, tenant: str, region: str) -> dict:
    """Retrieve all provisioned resources for a deployment."""
    base = f'{company_prefix}-{env}-{tenant}-{region}'
    
    def get_params(path):
        response = ssm.get_parameters_by_path(Path=path, Recursive=True)
        return {p['Name'].split('/')[-2]: p['Value'] for p in response['Parameters']}

    return {
        'vpc': get_params(f'/vpc/{base}-vpc/'),
        'sg': get_params(f'/sg/{base}-sg/'),
        's3': get_params(f'/s3/{base}-s3/')
    }

resources = get_stack_resources('globalbank', 'prod', 'c001', 'us-west-2')
print(resources['sg'])
# {'web': 'sg-0abc123', 'app': 'sg-0def456', 'db': 'sg-0ghi789'}

CI/CD Pipeline Integration

Integrating security into a CI/CD pipeline, often called DevSecOps, involves moving security checks from the end of the development cycle to every stage of the automated workflow. Using Infrastructure as Code (IaC) is a primary method for managing Security Groups—the virtual firewalls that control inbound and outbound traffic to your resources.

Core Strategies for Security Group Integration

  • Infrastructure as Code (IaC) Scanning: Treat Security Group definitions as code (e.g., Terraform or CloudFormation). Use tools to scan these templates for misconfigurations like overly permissive “0.0.0.0/0” rules or open management ports (SSH/RDP) before they are deployed.

  • Automated Policy Enforcement: Implement “Security-as-Code” by defining baseline security policies. If a pull request includes a Security Group change that violates these policies, the pipeline should automatically fail the build and block the deployment.

  • Principle of Least Privilege: Grant the CI/CD pipeline only the minimum permissions necessary to modify Security Groups. Use dedicated IAM roles rather than broad administrative access to reduce the blast radius if the pipeline is compromised.

Integration at Pipeline Stages

Pipeline Stage

Security Action for Security Groups

Commit

Linting & Static Analysis: Run tools like cfn-guard or TFLint to check for security anti-patterns in your IaC files.

Build

Unit Testing: Validate that Security Group rules match intended logic (e.g., ensuring web servers only accept traffic on ports 80/443).

Test

Dynamic Scanning (DAST): Once deployed to a staging area, use tools to verify that only the expected ports are actually reachable.

Deploy

Drift Detection: Continuously monitor for manual changes to Security Groups made outside the CI/CD process and alert or revert them automatically.

Implementation Tips

  • Secure Secrets: Never hardcode credentials within your Security Group templates or pipeline scripts; use secure vaults like AWS Parameter Store or GitHub Secrets.

  • Signed Commits: Require developers to sign their commits to ensure that unauthorized actors haven’t injected malicious Security Group rules into the repository.

  • Isolated Environments: Perform your pipeline builds in isolated nodes and restrict access to the CI/CD platform by IP address where possible to prevent unauthorized network changes.

GitHub Actions

name: Deploy Security Groups
on:
  push:
    branches: [main]
    paths: ['sg/configs/**']

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
          aws-region: us-west-2

      - name: Validate configuration
        run: |
          docker run --rm \
            -v ~/.aws:/home/sguser/.aws:ro \
            -v $(pwd)/sg/configs:/app/configs:ro \
            -v $(pwd)/sg/reports:/app/reports \
            sg-provisioner:latest \
            --config globalbank-prod-c001-us-west-2-sg.yaml \
            --action validate-config

      - name: Generate template
        run: |
          docker run --rm \
            -v ~/.aws:/home/sguser/.aws:ro \
            -v $(pwd)/sg/configs:/app/configs:ro \
            -v $(pwd)/sg/templates:/app/templates \
            -v $(pwd)/sg/reports:/app/reports \
            sg-provisioner:latest \
            --config globalbank-prod-c001-us-west-2-sg.yaml \
            --action create-prov-template

      - name: Preview changes
        run: |
          docker run --rm \
            -v ~/.aws:/home/sguser/.aws:ro \
            -v $(pwd)/sg/configs:/app/configs:ro \
            -v $(pwd)/sg/templates:/app/templates \
            -v $(pwd)/sg/reports:/app/reports \
            sg-provisioner:latest \
            --config globalbank-prod-c001-us-west-2-sg.yaml \
            --action show-changes

      - name: Deploy security groups
        run: |
          docker run --rm \
            -v ~/.aws:/home/sguser/.aws:ro \
            -v $(pwd)/sg/configs:/app/configs:ro \
            -v $(pwd)/sg/templates:/app/templates \
            -v $(pwd)/sg/reports:/app/reports \
            sg-provisioner:latest \
            --config globalbank-prod-c001-us-west-2-sg.yaml \
            --action create-security-groups \
            --force

SDK Examples

AWS CLI — Verify Deployment

# Check CloudFormation stack
aws cloudformation describe-stacks \
  --stack-name globalbank-prod-c001-us-west-2-sg-stack \
  --region us-west-2 \
  --query 'Stacks[0].{Status:StackStatus,Created:CreationTime}'

# List all security groups managed by SG Provisioner
aws ec2 describe-security-groups \
  --filters "Name=tag:ManagedBy,Values=sg-provisioner-tool" \
  --region us-west-2 \
  --query 'SecurityGroups[].{Name:GroupName,Id:GroupId,VPC:VpcId}'

# Check SSM parameters
aws ssm get-parameters-by-path \
  --path /sg/globalbank-prod-c001-us-west-2-sg/ \
  --region us-west-2 \
  --query 'Parameters[].{Name:Name,Value:Value}'

Boto3 — Full Deployment Verification

import boto3

def verify_sg_deployment(sg_name: str, region: str = 'us-west-2') -> dict:
    """Verify a SG Provisioner deployment is healthy."""
    ec2 = boto3.client('ec2', region_name=region)
    ssm = boto3.client('ssm', region_name=region)
    cfn = boto3.client('cloudformation', region_name=region)

    # Check CloudFormation stack
    stack = cfn.describe_stacks(
        StackName=f'{sg_name}-stack'
    )['Stacks'][0]

    # Check SSM parameters
    params = ssm.get_parameters_by_path(
        Path=f'/sg/{sg_name}/',
        Recursive=True
    )['Parameters']

    # Check security groups exist
    sg_ids = [p['Value'] for p in params]
    sgs = ec2.describe_security_groups(
        GroupIds=sg_ids
    )['SecurityGroups']

    return {
        'stack_status': stack['StackStatus'],
        'sg_count': len(sgs),
        'parameters': {p['Name'].split('/')[-2]: p['Value'] for p in params}
    }

result = verify_sg_deployment('globalbank-prod-c001-us-west-2-sg')
print(result)