How to Use AWS Config to Detect Non-Compliant Security Group Rules
Someone on your team opens port 22 to 0.0.0.0/0 on a production security group — maybe for a quick debug session, maybe by accident. Without automated detection, that change sits unnoticed until your next manual audit or, worse, until something goes wrong. AWS Config gives you a managed rule that flags this exact pattern and, wired to SNS, can alert your team within minutes of the change landing.
TL;DR: AWS Config SSH Compliance Detection
| Step | What You're Doing | Why It Matters |
|---|---|---|
| 1 | Enable AWS Config with EC2 recording | Config can't evaluate what it doesn't record |
| 2 | Deploy restricted-ssh managed rule | AWS-managed logic, no Lambda required |
| 3 | Create SNS topic + subscription | Humans need to know when compliance breaks |
| 4 | Wire EventBridge rule to SNS | Config compliance change events route through EventBridge |
| 5 | Verify end-to-end with a test SG | Confirm the pipeline before you trust it in production |
How AWS Config Compliance Detection Works
AWS Config continuously records configuration changes to supported AWS resources. When a security group rule changes, Config captures the new configuration state and evaluates it against any rules you've associated with that resource type. The evaluation result — COMPLIANT or NON_COMPLIANT — is stored in Config's timeline and simultaneously emits a compliance change event to EventBridge.
The restricted-ssh managed rule is a periodic and change-triggered rule that evaluates whether any inbound rule on a security group allows unrestricted SSH access (port 22, protocol TCP, source 0.0.0.0/0 or ::/0). It runs on EC2 security group resources and requires no custom Lambda function — AWS owns the evaluation logic.
- Security group change — an engineer modifies an inbound rule via console, CLI, or IaC.
- Config recorder captures the new configuration item and triggers rule evaluation.
- restricted-ssh rule evaluates the inbound rules; marks the SG
NON_COMPLIANTif port 22 is open to0.0.0.0/0or::/0. - Compliance change event is emitted to EventBridge (event source:
aws.config). - EventBridge rule matches the event and routes it to the SNS topic.
- SNS delivers the notification to your subscribed endpoint (email, Slack via Lambda, PagerDuty, etc.).
Prerequisites
- AWS Config enabled in the target region with an EC2 configuration recorder active.
- An S3 bucket and optionally an SNS delivery channel configured for Config (required for Config setup, separate from the alert SNS topic).
- IAM permissions to create Config rules, SNS topics, and EventBridge rules.
If Config isn't recording EC2 resources yet, the managed rule will deploy but never evaluate anything. That's the silent failure mode most teams hit first.
Step 1: Verify AWS Config Is Recording Security Groups
Before deploying the rule, confirm the Config recorder is active and includes EC2 resources. A rule with no recorder is a rule that never fires — and you won't get an error, just silence.
aws configservice describe-configuration-recorders \
--region us-east-1
Check that the output shows "recordingGroup" with either "allSupported": true or EC2:SecurityGroup explicitly included, and that the recorder status is "recording": true:
aws configservice describe-configuration-recorder-status \
--region us-east-1
If Config isn't enabled, you'll need to set up a delivery channel and recorder. The following creates a recorder that captures all supported resource types:
🔽 Click to expand: Enable AWS Config recorder (if not already active)
# Create the Config service-linked role if it doesn't exist
aws iam create-service-linked-role \
--aws-service-name config.amazonaws.com
# Create a delivery channel (requires an existing S3 bucket)
aws configservice put-delivery-channel \
--delivery-channel '{
"name": "default",
"s3BucketName": "my-config-bucket-123456789012",
"configSnapshotDeliveryProperties": {
"deliveryFrequency": "TwentyFour_Hours"
}
}' \
--region us-east-1
# Create and start the configuration recorder
aws configservice put-configuration-recorder \
--configuration-recorder '{
"name": "default",
"roleARN": "arn:aws:iam::123456789012:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig",
"recordingGroup": {
"allSupported": true,
"includeGlobalResourceTypes": false
}
}' \
--region us-east-1
aws configservice start-configuration-recorder \
--configuration-recorder-name default \
--region us-east-1
Step 2: Deploy the restricted-ssh AWS Config Managed Rule
The restricted-ssh rule is an AWS-managed Config rule. You don't write evaluation logic — you just activate it. It evaluates AWS::EC2::SecurityGroup resources and marks any security group NON_COMPLIANT if it contains an inbound rule permitting TCP port 22 from 0.0.0.0/0 or ::/0.
aws configservice put-config-rule \
--config-rule '{
"ConfigRuleName": "restricted-ssh",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "INCOMING_SSH_DISABLED"
}
}' \
--region us-east-1
The SourceIdentifier for this rule is INCOMING_SSH_DISABLED — that's the documented identifier in the AWS Config Managed Rules reference. After deployment, Config will run an initial evaluation against all existing security groups in the region.
Verify the rule is active:
aws configservice describe-config-rules \
--config-rule-names restricted-ssh \
--region us-east-1
Look for "ConfigRuleState": "ACTIVE" in the response. If it shows EVALUATING, wait a minute and re-run — the initial evaluation sweep takes time proportional to how many security groups exist in the account.
Step 3: Create the SNS Topic for Compliance Alerts
This SNS topic is the alert destination. It's separate from any SNS topic used by Config's delivery channel. Create it, then subscribe your notification endpoint.
# Create the topic
aws sns create-topic \
--name config-ssh-compliance-alerts \
--region us-east-1
The command returns the topic ARN — note it, you'll need it in the next two steps. It will look like arn:aws:sns:us-east-1:123456789012:config-ssh-compliance-alerts.
# Subscribe an email endpoint
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:123456789012:config-ssh-compliance-alerts \
--protocol email \
--notification-endpoint security-team@example.com \
--region us-east-1
The email address will receive a confirmation request. The subscription stays in PendingConfirmation state until confirmed — EventBridge can still publish to the topic, but unconfirmed email subscriptions won't receive messages.
Step 4: Create the SNS Topic Policy
EventBridge needs permission to publish to your SNS topic. Without this policy, the EventBridge rule will silently fail to deliver — no error in EventBridge, no message in SNS, no alert. This is the most common wiring mistake.
aws sns set-topic-attributes \
--topic-arn arn:aws:sns:us-east-1:123456789012:config-ssh-compliance-alerts \
--attribute-name Policy \
--attribute-value '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEventBridgePublish",
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "sns:Publish",
"Resource": "arn:aws:sns:us-east-1:123456789012:config-ssh-compliance-alerts",
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:events:us-east-1:123456789012:rule/*"
}
}
}
]
}' \
--region us-east-1
Step 5: Create the EventBridge Rule to Route Config Compliance Events
AWS Config emits a compliance change event to EventBridge whenever a resource's compliance status changes. The event source is aws.config and the detail type is Config Rules Compliance Change. You filter on the specific rule name and the NON_COMPLIANT status so you're not flooded with COMPLIANT notifications every time someone fixes a rule.
# Create the EventBridge rule with an event pattern filter
aws events put-rule \
--name config-ssh-noncompliant-alert \
--event-pattern '{
"source": ["aws.config"],
"detail-type": ["Config Rules Compliance Change"],
"detail": {
"configRuleName": ["restricted-ssh"],
"newEvaluationResult": {
"complianceType": ["NON_COMPLIANT"]
}
}
}' \
--state ENABLED \
--region us-east-1
# Add the SNS topic as the target
aws events put-targets \
--rule config-ssh-noncompliant-alert \
--targets '[
{
"Id": "sns-compliance-alert",
"Arn": "arn:aws:sns:us-east-1:123456789012:config-ssh-compliance-alerts"
}
]' \
--region us-east-1
The event pattern uses newEvaluationResult.complianceType to match only transitions into NON_COMPLIANT. If you omit this filter, you'll also receive events when the security group is remediated and returns to COMPLIANT — which is noisy but sometimes useful for audit trails.
- Event pattern matches only
aws.configevents from therestricted-sshrule withNON_COMPLIANTstatus. - Target is the SNS topic ARN — EventBridge publishes the raw Config event JSON as the message body.
- SNS topic policy permits
events.amazonaws.comto callsns:Publish— without this, delivery fails silently.
Step 6: Verify End-to-End With a Test Security Group
Don't trust the setup until you've seen it fire. Create a throwaway security group, add the offending rule, and watch the pipeline respond. This also tells you the actual detection latency in your environment — Config change-triggered rules typically evaluate within a few minutes of the resource change, but this depends on Config's internal scheduling.
# Create a test security group in the default VPC
aws ec2 create-security-group \
--group-name test-ssh-open \
--description 'Temporary test for Config rule verification' \
--region us-east-1
Note the returned GroupId (e.g., sg-0abc123def456), then add the non-compliant rule:
aws ec2 authorize-security-group-ingress \
--group-id sg-0abc123def456 \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0 \
--region us-east-1
After a few minutes, check the compliance status directly:
aws configservice get-compliance-details-by-resource \
--resource-type AWS::EC2::SecurityGroup \
--resource-id sg-0abc123def456 \
--region us-east-1
You should see "ComplianceType": "NON_COMPLIANT" in the results. If the email subscription was confirmed, you should also have received an SNS notification by this point.
Clean up after verification:
aws ec2 revoke-security-group-ingress \
--group-id sg-0abc123def456 \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0 \
--region us-east-1
aws ec2 delete-security-group \
--group-id sg-0abc123def456 \
--region us-east-1
The Misdiagnosis That Wastes an Hour
Here's a pattern that comes up repeatedly: you deploy the rule, add the test inbound rule, wait ten minutes, and nothing happens. No compliance change in Config, no SNS message. The instinct is to blame EventBridge routing or the SNS topic policy.
The actual cause is almost always that the Config recorder isn't recording AWS::EC2::SecurityGroup resources — either because Config was set up with a custom resource type list that excluded EC2, or because the recorder was stopped at some point and never restarted. Config evaluates resources based on configuration items it has recorded. If it never recorded the security group change, the rule never ran.
The fix is to re-check recorder status and resource coverage, not to redeploy the EventBridge rule:
aws configservice describe-configuration-recorder-status \
--region us-east-1
aws configservice describe-configuration-recorders \
--region us-east-1
If "recording": false appears in the status output, restart the recorder and trigger a manual rule evaluation to catch up:
aws configservice start-configuration-recorder \
--configuration-recorder-name default \
--region us-east-1
aws configservice start-config-rules-evaluation \
--config-rule-names restricted-ssh \
--region us-east-1
The Config recorder is like a security camera. The managed rule is the motion-detection algorithm. If the camera is off, the algorithm never sees anything — and it won't tell you the camera is off.
IAM Permissions Required
The principal deploying this setup needs the following permissions. Read/List actions on Config and EC2 require "Resource": "*" as these actions don't support resource-level restrictions in IAM.
🔽 Click to expand: IAM policy for deployment principal
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ConfigRuleManagement",
"Effect": "Allow",
"Action": [
"config:PutConfigRule",
"config:DescribeConfigRules",
"config:DescribeConfigurationRecorders",
"config:DescribeConfigurationRecorderStatus",
"config:StartConfigRulesEvaluation",
"config:GetComplianceDetailsByResource",
"config:PutDeliveryChannel",
"config:PutConfigurationRecorder",
"config:StartConfigurationRecorder"
],
"Resource": "*"
},
{
"Sid": "SNSManagement",
"Effect": "Allow",
"Action": [
"sns:CreateTopic",
"sns:Subscribe",
"sns:SetTopicAttributes"
],
"Resource": "arn:aws:sns:us-east-1:123456789012:config-ssh-compliance-alerts"
},
{
"Sid": "EventBridgeManagement",
"Effect": "Allow",
"Action": [
"events:PutRule",
"events:PutTargets",
"events:DescribeRule"
],
"Resource": "arn:aws:events:us-east-1:123456789012:rule/config-ssh-noncompliant-alert"
},
{
"Sid": "EC2TestSGManagement",
"Effect": "Allow",
"Action": [
"ec2:CreateSecurityGroup",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:RevokeSecurityGroupIngress",
"ec2:DeleteSecurityGroup",
"ec2:DescribeSecurityGroups"
],
"Resource": "*"
},
{
"Sid": "IAMServiceLinkedRole",
"Effect": "Allow",
"Action": "iam:CreateServiceLinkedRole",
"Resource": "arn:aws:iam::123456789012:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig"
}
]
}
Extending This Pattern: Beyond Port 22
The restricted-ssh rule covers port 22 specifically. For port 3389 (RDP), AWS provides a separate managed rule with the SourceIdentifier RESTRICTED_INCOMING_TRAFFIC, which accepts parameters to specify which ports to restrict. Check the AWS Config Managed Rules documentation for the current parameter schema before deploying — parameter names and valid values are documented per rule.
For custom port combinations or more complex logic (e.g., flagging any inbound rule that opens a port range wider than a defined threshold), you'd need a custom Config rule backed by a Lambda function. The EventBridge-to-SNS wiring in steps 3–5 remains identical regardless of whether the rule is managed or custom.
One non-obvious interaction worth knowing: if you're using AWS Organizations with delegated Config administration, managed rules deployed from the management account or delegated administrator propagate to member accounts as conformance pack rules. The compliance events still flow through EventBridge in each member account, not centrally — so the SNS alert wiring needs to exist in each account, or you need an EventBridge cross-account event bus setup to centralize them.
Wrap-Up: Detecting Non-Compliant Security Group Rules With AWS Config
The full pipeline — Config recorder → restricted-ssh managed rule → EventBridge compliance change event → SNS alert — gives you automated, near-real-time detection of port 22 exposure without writing any custom evaluation code. The recorder being active and covering EC2 resources is the single most important prerequisite; everything downstream depends on it.
For production use, consider pairing this with AWS Config's auto-remediation feature, which can trigger an SSM Automation document to automatically revoke the offending rule. That's a separate setup, but the compliance detection pipeline you've built here is the prerequisite for it.
- AWS Config Managed Rules Reference
- Monitoring AWS Config with EventBridge
- AWS Config Auto Remediation
Glossary
| Term | Definition |
|---|---|
| Configuration Item | A point-in-time snapshot of a supported AWS resource's configuration, recorded by the AWS Config recorder. |
| Managed Rule | A pre-built AWS Config evaluation rule maintained by AWS, identified by a SourceIdentifier string. No Lambda function required. |
| Compliance Change Event | An EventBridge event emitted by AWS Config when a resource's evaluation result transitions between COMPLIANT, NON_COMPLIANT, or NOT_APPLICABLE. |
| Configuration Recorder | The AWS Config component that detects and records resource configuration changes. Must be active and scoped to include the resource types you want evaluated. |
| restricted-ssh | The AWS Config managed rule (SourceIdentifier: INCOMING_SSH_DISABLED) that flags security groups permitting unrestricted inbound TCP port 22 access. |
Comments
Post a Comment