← Back to DevBytes

Elastic Beanstalk Security: IAM Policies and Network Security

Understanding Elastic Beanstalk Security

AWS Elastic Beanstalk is a Platform as a Service (PaaS) that abstracts away much of the infrastructure management. However, the underlying resources—EC2 instances, load balancers, S3 buckets, and more—still require careful security configuration. Two foundational pillars of Elastic Beanstalk security are IAM policies (who and what can access and manage your environment) and network security (how traffic reaches your application and flows between components). This tutorial walks you through both, with practical code examples you can apply directly to your own environments.

IAM Policies and Roles for Elastic Beanstalk

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Elastic Beanstalk relies on AWS Identity and Access Management (IAM) to grant permissions to both the service itself and the resources it creates. There are two primary IAM roles every environment uses:

The Elastic Beanstalk Service Role

When you create an environment, Elastic Beanstalk needs a service role to perform management operations. The default role is aws-elasticbeanstalk-service-role. You can create it automatically via the console or manually with the policy below. Without it, the environment cannot scale, update, or even be created correctly.


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "elasticloadbalancing:Describe*",
        "elasticloadbalancing:CreateLoadBalancer",
        "elasticloadbalancing:DeleteLoadBalancer",
        "elasticloadbalancing:ModifyLoadBalancerAttributes",
        "elasticloadbalancing:ConfigureHealthCheck",
        "ec2:Describe*",
        "ec2:CreateSecurityGroup",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:AuthorizeSecurityGroupEgress",
        "ec2:RevokeSecurityGroupIngress",
        "ec2:DeleteSecurityGroup",
        "autoscaling:Describe*",
        "autoscaling:CreateAutoScalingGroup",
        "autoscaling:UpdateAutoScalingGroup",
        "autoscaling:DeleteAutoScalingGroup",
        "autoscaling:PutScalingPolicy",
        "autoscaling:DeletePolicy",
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket",
        "cloudformation:DescribeStack*",
        "cloudformation:CreateStack",
        "cloudformation:UpdateStack",
        "cloudformation:DeleteStack",
        "sqs:CreateQueue",
        "sqs:DeleteQueue"
      ],
      "Resource": "*"
    }
  ]
}

To create the service role using the AWS CLI:


aws iam create-role --role-name aws-elasticbeanstalk-service-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "elasticbeanstalk.amazonaws.com"},
      "Action": "sts:AssumeRole",
      "Condition": {"StringEquals": {"sts:ExternalId": "elasticbeanstalk"}}
    }]
  }'

aws iam put-role-policy --role-name aws-elasticbeanstalk-service-role \
  --policy-name ElasticBeanstalkServiceRolePolicy \
  --policy-document file://service-role-policy.json

The ExternalId condition is a best practice to prevent the confused deputy problem, ensuring only Elastic Beanstalk can assume this role.

The Instance Profile Role

Every EC2 instance in your environment must have an instance profile that grants it permissions to interact with other AWS services. The default is aws-elasticbeanstalk-ec2-role. A typical policy allows access to S3 (for application versions and logs), CloudWatch Logs, and optionally RDS, DynamoDB, or Secrets Manager.


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogStreams"
      ],
      "Resource": [
        "arn:aws:s3:::elasticbeanstalk-*",
        "arn:aws:s3:::elasticbeanstalk-*/*",
        "arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:Describe*",
        "autoscaling:Describe*",
        "elasticloadbalancing:Describe*",
        "sqs:GetQueueUrl",
        "sqs:ReceiveMessage",
        "sqs:DeleteMessage"
      ],
      "Resource": "*"
    }
  ]
}

You attach the policy to the role and then specify the instance profile when creating the environment. The CLI command for attaching the managed policy AWS-ElasticBeanstalk-WebTier (which covers many common needs) is:


aws iam attach-role-policy --role-name aws-elasticbeanstalk-ec2-role \
  --policy-arn arn:aws:iam::aws:policy/AWS-ElasticBeanstalk-WebTier

Important: Never use overly broad policies like AdministratorAccess for the instance role. Follow least privilege and only grant what your application code truly needs.

IAM Policies for Developers and CI/CD

Your team members or CI/CD pipelines need permissions to interact with Elastic Beanstalk itself—creating applications, deploying versions, and managing environments. The AWS managed policy AWSElasticBeanstalkFullAccess is a good starting point, but you should tailor it to your organization's boundaries.

A custom policy for a developer to deploy to a specific environment might look like:


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "elasticbeanstalk:CreateApplicationVersion",
        "elasticbeanstalk:UpdateEnvironment",
        "elasticbeanstalk:DescribeEnvironments",
        "elasticbeanstalk:DescribeEnvironmentResources",
        "elasticbeanstalk:DescribeEvents",
        "elasticbeanstalk:RequestEnvironmentInfo",
        "elasticbeanstalk:RetrieveEnvironmentInfo",
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:elasticbeanstalk:us-east-1:123456789012:application/my-app",
        "arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/my-app/*",
        "arn:aws:s3:::elasticbeanstalk-us-east-1-123456789012",
        "arn:aws:s3:::elasticbeanstalk-us-east-1-123456789012/*"
      ]
    }
  ]
}

This scopes the user to only one application and its environments, plus the necessary S3 bucket for storing application versions. You can further restrict with conditions on elasticbeanstalk:EnvironmentName or tags.

Using IAM Conditions with Elastic Beanstalk

Elastic Beanstalk supports IAM condition keys such as elasticbeanstalk:EnvironmentName, elasticbeanstalk:ApplicationName, and aws:SourceArn. For example, to restrict environment termination to a specific VPC, you could combine an IAM policy with a condition that checks the VPC ID from the environment's configuration. However, the most common use is restricting access to specific applications:


{
  "Effect": "Allow",
  "Action": "elasticbeanstalk:*",
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "elasticbeanstalk:ApplicationName": "my-app"
    }
  }
}

This ensures the user can only act on the application my-app, regardless of the environment. For network-oriented restrictions, you'd typically use VPC and subnet conditions in the EC2 or load balancer actions, but those are enforced when the service role tries to create resources, so it's often better to use service control policies (SCPs) in AWS Organizations.

Network Security for Elastic Beanstalk

Elastic Beanstalk environments run inside a Virtual Private Cloud (VPC) that you define. Proper network design is critical to protect your application from unauthorized access and to comply with internal security policies. The key components are subnets, security groups, and network ACLs.

Choosing and Designing the VPC

Always launch your Elastic Beanstalk environment inside a VPC—never in the EC2-Classic (legacy) mode. A well-architected VPC includes:

When you create the environment, specify the VPC ID, and choose the subnets for the load balancer and instances. This is done via the console or CLI options:


aws elasticbeanstalk create-environment --application-name my-app \
  --environment-name my-env \
  --solution-stack-name "64bit Amazon Linux 2023 v5.8.0 running Node.js 18" \
  --option-settings \
    Namespace=aws:ec2:vpc,OptionName=VPCId,Value=vpc-0abcd1234 \
    Namespace=aws:ec2:vpc,OptionName=Subnets,Value=subnet-1111,subnet-2222 \
    Namespace=aws:ec2:vpc,OptionName=ELBSubnets,Value=subnet-3333,subnet-4444 \
    Namespace=aws:ec2:vpc,OptionName=AssociatePublicIpAddress,Value=false

The AssociatePublicIpAddress option set to false ensures instances in private subnets don't get public IPs, forcing all inbound traffic through the load balancer.

Security Groups Configuration

Elastic Beanstalk automatically creates security groups for the load balancer and the EC2 instances, but you can and should refine them. The default behavior allows the load balancer to accept HTTP on port 80 from anywhere (0.0.0.0/0). For production, restrict the source CIDR to your expected user base, or use HTTPS only.

You can customize security groups using .ebextensions configuration files. Here's an example that adds a security group for an internal RDS database and allows the EC2 instances to communicate with it:


# .ebextensions/network-security.config
option_settings:
  aws:ec2:vpc:
    VPCId: vpc-0abcd1234
    Subnets: subnet-1111,subnet-2222
    ELBSubnets: subnet-3333,subnet-4444
    SecurityGroups: sg-0myinstancesg
  aws:elbv2:loadbalancer:
    SecurityGroups: sg-0myloadbalancersg

Resources:
  instanceSecurityGroupIngressToRDS:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: sg-0myinstancesg
      SourceSecurityGroupId: sg-0myrdssg
      IpProtocol: tcp
      FromPort: 3306
      ToPort: 3306
      Description: Allow MySQL from RDS security group

Note the use of option_settings to set the security groups for the EC2 instances and the load balancer. The custom resource adds an ingress rule. You can also modify the auto-generated security group's ingress rules via the same mechanism, but specifying your own groups gives you full control.

Network ACLs as a Second Layer

Security groups are stateful; network ACLs are stateless and provide an additional boundary at the subnet level. For a private subnet hosting your application instances, you might configure inbound rules that only allow traffic from the load balancer security group's subnet and outbound to the internet via the NAT. However, NACLs are optional and can become complex. A common pattern is:

NACLs are configured at the subnet level outside of Elastic Beanstalk, but they affect all resources in those subnets, so plan accordingly.

Enforcing HTTPS and TLS

Network security isn't only about IP filtering; encrypting data in transit is mandatory. Elastic Beanstalk supports HTTPS termination at the Application Load Balancer or Classic Load Balancer. You can upload an SSL certificate via ACM (AWS Certificate Manager) and configure the load balancer listener:


option_settings:
  aws:elbv2:listener:443:
    Protocol: HTTPS
    SSLCertificateArns: arn:aws:acm:us-east-1:123456789012:certificate/abc123-...
    DefaultProcess: default
    ListenerEnabled: true
  aws:elbv2:listener:80:
    Protocol: HTTP
    DefaultProcess: redirect
    ListenerEnabled: true

To redirect HTTP to HTTPS, define a listener action in the load balancer rules. You can set the DefaultProcess to redirect and provide a custom rule in .ebextensions:


Resources:
  redirectHttpToHttps:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      ListenerArn: {Ref: AWSEBV2LoadBalancerListener80}
      Priority: 1
      Conditions:
        - Field: host-header
          Values: ['*']
      Actions:
        - Type: redirect
          RedirectConfig:
            Protocol: HTTPS
            Port: 443
            Host: '#{host}'
            Path: '/#{path}'
            Query: '#{query}'
            StatusCode: HTTP_301

This ensures all plain HTTP requests are redirected to HTTPS, protecting data in transit.

Restricting Inbound Traffic with Source IPs or Security Groups

For internal applications, you may want to limit access to a specific IP range or a set of security groups. Using the aws:elbv2:loadbalancer:SecurityGroups option, you can assign a security group that only allows ingress from trusted CIDRs. Alternatively, if you're using an internal load balancer (in private subnets), you can restrict access via security group references. This is ideal for microservices where only other VPC resources should call your API.


# Set the load balancer scheme to internal
option_settings:
  aws:ec2:vpc:
    ELBSubnets: subnet-private1,subnet-private2
  aws:elbv2:loadbalancer:
    SecurityGroups: sg-internal-lb
    Scheme: internal

Now the load balancer has no public IP and is only reachable from within the VPC, providing strong network isolation.

Best Practices for Elastic Beanstalk Security

Conclusion

Security in Elastic Beanstalk is a shared responsibility. While AWS manages the underlying platform, you must correctly configure IAM policies to control access and network boundaries to protect your data. By crafting precise IAM roles with least privilege, placing your instances in private subnets, enforcing encrypted traffic, and using security groups and NACLs as layered defenses, you build a robust environment that can safely run production workloads. The code examples and patterns provided here give you a practical starting point—adapt them to your own context, and always test changes in a staging environment before rolling to production. With these foundations, your Elastic Beanstalk applications will be both scalable and secure.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles