Repository: cloud-custodian/cloud-custodian

Issue:
Add modify-security-groups action to aws.lambda and aws.app-elb
Hi c7n team,

We'd like to be able to use the same action as aws.ec2 but for Lambda and ALBs for changing security groups. Currently we're having to use manual scripts for these resource types. Is this on your roadmap?

Thanks!


Discussion:
s/team/community... pull requests welcome.
Hold that thought, taking a crack at it myself... :)
this might be helpful re dev docs https://cloudcustodian.io/docs/developer/tests.html#developer-tests

Resolving patch:
diff --git a/c7n/resources/appelb.py b/c7n/resources/appelb.py
--- a/c7n/resources/appelb.py
+++ b/c7n/resources/appelb.py
@@ -21,7 +21,7 @@
 import six
 
 from collections import defaultdict
-from c7n.actions import ActionRegistry, BaseAction
+from c7n.actions import ActionRegistry, BaseAction, ModifyVpcSecurityGroupsAction
 from c7n.exceptions import PolicyValidationError
 from c7n.filters import (
     Filter, FilterRegistry, DefaultVpcBase, MetricsFilter, ValueFilter)
@@ -760,6 +760,24 @@ def process(self, load_balancers):
                     **args)
 
 
+@AppELB.action_registry.register('modify-security-groups')
+class AppELBModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction):
+
+    permissions = ("elasticloadbalancing:SetSecurityGroups",)
+
+    def process(self, albs):
+        client = local_session(self.manager.session_factory).client('elbv2')
+        groups = super(AppELBModifyVpcSecurityGroups, self).get_groups(albs)
+
+        for idx, i in enumerate(albs):
+            try:
+                client.set_security_groups(
+                    LoadBalancerArn=i['LoadBalancerArn'],
+                    SecurityGroups=groups[idx])
+            except client.exceptions.LoadBalancerNotFoundException:
+                continue
+
+
 @AppELB.filter_registry.register('healthcheck-protocol-mismatch')
 class AppELBHealthCheckProtocolMismatchFilter(Filter,
                                               AppELBTargetGroupFilterBase):


Test patch:
diff --git a/tests/data/placebo/test_appelb_add_security_group/ec2.DescribeSecurityGroups_1.json b/tests/data/placebo/test_appelb_add_security_group/ec2.DescribeSecurityGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_add_security_group/ec2.DescribeSecurityGroups_1.json
@@ -0,0 +1,140 @@
+{
+    "status_code": 200,
+    "data": {
+        "SecurityGroups": [
+            {
+                "Description": "Isolation SG",
+                "GroupName": "test-isolation-group",
+                "IpPermissions": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "192.168.0.1/31",
+                                "Description": "testcidr"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [
+                            {
+                                "PrefixListId": "pl-63a5400b"
+                            }
+                        ],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-01a19f602ecaf25f4",
+                "IpPermissionsEgress": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "20.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [],
+                "VpcId": "vpc-3d05f145"
+            },
+            {
+                "Description": "test-sg-2",
+                "GroupName": "test-sg-2",
+                "IpPermissions": [
+                    {
+                        "FromPort": 443,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "127.0.0.1/32"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 443,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-0b650189861ce4f6c",
+                "IpPermissionsEgress": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [],
+                "VpcId": "vpc-3d05f145"
+            },
+            {
+                "Description": "sample controller SG",
+                "GroupName": "sg_controllers",
+                "IpPermissions": [
+                    {
+                        "FromPort": 443,
+                        "IpProtocol": "udp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 443,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-c573e6b3",
+                "IpPermissionsEgress": [
+                    {
+                        "IpProtocol": "-1",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "UserIdGroupPairs": [
+                            {
+                                "GroupId": "sg-c573e6b3",
+                                "UserId": "123412341234"
+                            }
+                        ]
+                    }
+                ],
+                "VpcId": "vpc-3d05f145"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "414d164f-7241-4721-be33-42585bb438d7",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "accept-encoding",
+                "date": "Fri, 12 Jul 2019 17:53:43 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_add_security_group/ec2.DescribeSecurityGroups_2.json b/tests/data/placebo/test_appelb_add_security_group/ec2.DescribeSecurityGroups_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_add_security_group/ec2.DescribeSecurityGroups_2.json
@@ -0,0 +1,140 @@
+{
+    "status_code": 200,
+    "data": {
+        "SecurityGroups": [
+            {
+                "Description": "Isolation SG",
+                "GroupName": "test-isolation-group",
+                "IpPermissions": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "192.168.0.1/31",
+                                "Description": "testcidr"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [
+                            {
+                                "PrefixListId": "pl-63a5400b"
+                            }
+                        ],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-01a19f602ecaf25f4",
+                "IpPermissionsEgress": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "20.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [],
+                "VpcId": "vpc-3d05f145"
+            },
+            {
+                "Description": "test-sg-2",
+                "GroupName": "test-sg-2",
+                "IpPermissions": [
+                    {
+                        "FromPort": 443,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "127.0.0.1/32"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 443,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-0b650189861ce4f6c",
+                "IpPermissionsEgress": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [],
+                "VpcId": "vpc-3d05f145"
+            },
+            {
+                "Description": "sample controller SG",
+                "GroupName": "sg_controllers",
+                "IpPermissions": [
+                    {
+                        "FromPort": 443,
+                        "IpProtocol": "udp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 443,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-c573e6b3",
+                "IpPermissionsEgress": [
+                    {
+                        "IpProtocol": "-1",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "UserIdGroupPairs": [
+                            {
+                                "GroupId": "sg-c573e6b3",
+                                "UserId": "123412341234"
+                            }
+                        ]
+                    }
+                ],
+                "VpcId": "vpc-3d05f145"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "414d164f-7241-4721-be33-42585bb438d7",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "accept-encoding",
+                "date": "Fri, 12 Jul 2019 17:53:43 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeLoadBalancers_1.json b/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeLoadBalancers_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeLoadBalancers_1.json
@@ -0,0 +1,55 @@
+{
+    "status_code": 200,
+    "data": {
+        "LoadBalancers": [
+            {
+                "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:123412341234:loadbalancer/app/test-abc/543530edaee12345",
+                "DNSName": "internal-test-abc-398177229.us-east-1.elb.amazonaws.com",
+                "CanonicalHostedZoneId": "ABCDXDOTRQ7X77",
+                "CreatedTime": {
+                    "__class__": "datetime",
+                    "year": 2018,
+                    "month": 4,
+                    "day": 16,
+                    "hour": 18,
+                    "minute": 9,
+                    "second": 43,
+                    "microsecond": 630000
+                },
+                "LoadBalancerName": "test-abc",
+                "Scheme": "internal",
+                "VpcId": "vpc-3d05f145",
+                "State": {
+                    "Code": "active"
+                },
+                "Type": "application",
+                "AvailabilityZones": [
+                    {
+                        "ZoneName": "us-east-1b",
+                        "SubnetId": "subnet-085d1234"
+                    },
+                    {
+                        "ZoneName": "us-east-1a",
+                        "SubnetId": "subnet-da895678"
+                    }
+                ],
+                "SecurityGroups": [
+                    "sg-01a19f602ecaf25f4"
+                ],
+                "IpAddressType": "ipv4"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "438cdb64-a990-11e9-8978-ad60691c507d",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "438cdb64-a990-11e9-8978-ad60691c507d",
+                "content-type": "text/xml",
+                "content-length": "8807",
+                "vary": "accept-encoding",
+                "date": "Thu, 18 Jul 2019 19:14:28 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeLoadBalancers_2.json b/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeLoadBalancers_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeLoadBalancers_2.json
@@ -0,0 +1,56 @@
+{
+    "status_code": 200,
+    "data": {
+        "LoadBalancers": [
+            {
+                "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:123412341234:loadbalancer/app/test-abc/543530edaee12345",
+                "DNSName": "internal-test-abc-398177229.us-east-1.elb.amazonaws.com",
+                "CanonicalHostedZoneId": "ABCDXDOTRQ7X77",
+                "CreatedTime": {
+                    "__class__": "datetime",
+                    "year": 2018,
+                    "month": 4,
+                    "day": 16,
+                    "hour": 18,
+                    "minute": 9,
+                    "second": 43,
+                    "microsecond": 630000
+                },
+                "LoadBalancerName": "test-abc",
+                "Scheme": "internal",
+                "VpcId": "vpc-3d05f145",
+                "State": {
+                    "Code": "active"
+                },
+                "Type": "application",
+                "AvailabilityZones": [
+                    {
+                        "ZoneName": "us-east-1b",
+                        "SubnetId": "subnet-085d1234"
+                    },
+                    {
+                        "ZoneName": "us-east-1a",
+                        "SubnetId": "subnet-da895678"
+                    }
+                ],
+                "SecurityGroups": [
+                    "sg-01a19f602ecaf25f4",
+                    "sg-c573e6b3"
+                ],
+                "IpAddressType": "ipv4"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "438cdb64-a990-11e9-8978-ad60691c507d",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "438cdb64-a990-11e9-8978-ad60691c507d",
+                "content-type": "text/xml",
+                "content-length": "8807",
+                "vary": "accept-encoding",
+                "date": "Thu, 18 Jul 2019 19:14:28 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeTags_1.json b/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeTags_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.DescribeTags_1.json
@@ -0,0 +1,28 @@
+{
+    "status_code": 200,
+    "data": {
+        "TagDescriptions": [
+            {
+                "ResourceArn": "arn:aws:elasticloadbalancing:us-east-1:123412341234:loadbalancer/app/test-abc/543530edaee12345",
+                "Tags": [
+                    {
+                        "Key": "testkey",
+                        "Value": "testval"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "43a8ee4d-a990-11e9-aebf-591f37d83b01",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "43a8ee4d-a990-11e9-aebf-591f37d83b01",
+                "content-type": "text/xml",
+                "content-length": "6372",
+                "vary": "accept-encoding",
+                "date": "Thu, 18 Jul 2019 19:14:28 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.SetSecurityGroups_1.json b/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.SetSecurityGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_add_security_group/elasticloadbalancing.SetSecurityGroups_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200,
+    "data": {
+        "SecurityGroupIds": [
+            "sg-01a19f602ecaf25f4",
+            "sg-c573e6b3"
+        ],
+        "ResponseMetadata": {
+            "RequestId": "43e7a555-a990-11e9-a7d0-654218cb3bcd",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "43e7a555-a990-11e9-a7d0-654218cb3bcd",
+                "content-type": "text/xml",
+                "content-length": "378",
+                "date": "Thu, 18 Jul 2019 19:14:28 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_remove_matched_security_groups/ec2.DescribeSecurityGroups_1.json b/tests/data/placebo/test_appelb_remove_matched_security_groups/ec2.DescribeSecurityGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_remove_matched_security_groups/ec2.DescribeSecurityGroups_1.json
@@ -0,0 +1,140 @@
+{
+    "status_code": 200,
+    "data": {
+        "SecurityGroups": [
+            {
+                "Description": "Isolation SG",
+                "GroupName": "test-isolation-group",
+                "IpPermissions": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "192.168.0.1/31",
+                                "Description": "testcidr"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [
+                            {
+                                "PrefixListId": "pl-63a5400b"
+                            }
+                        ],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-01a19f602ecaf25f4",
+                "IpPermissionsEgress": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "20.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [],
+                "VpcId": "vpc-3d05f145"
+            },
+            {
+                "Description": "test-sg-2",
+                "GroupName": "test-sg-2",
+                "IpPermissions": [
+                    {
+                        "FromPort": 443,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "127.0.0.1/32"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 443,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-0b650189861ce4f6c",
+                "IpPermissionsEgress": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [],
+                "VpcId": "vpc-3d05f145"
+            },
+            {
+                "Description": "sample controller SG",
+                "GroupName": "sg_controllers",
+                "IpPermissions": [
+                    {
+                        "FromPort": 443,
+                        "IpProtocol": "udp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 443,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-c573e6b3",
+                "IpPermissionsEgress": [
+                    {
+                        "IpProtocol": "-1",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "UserIdGroupPairs": [
+                            {
+                                "GroupId": "sg-c573e6b3",
+                                "UserId": "123412341234"
+                            }
+                        ]
+                    }
+                ],
+                "VpcId": "vpc-3d05f145"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "414d164f-7241-4721-be33-42585bb438d7",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "accept-encoding",
+                "date": "Fri, 12 Jul 2019 17:53:43 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_remove_matched_security_groups/ec2.DescribeSecurityGroups_2.json b/tests/data/placebo/test_appelb_remove_matched_security_groups/ec2.DescribeSecurityGroups_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_remove_matched_security_groups/ec2.DescribeSecurityGroups_2.json
@@ -0,0 +1,140 @@
+{
+    "status_code": 200,
+    "data": {
+        "SecurityGroups": [
+            {
+                "Description": "Isolation SG",
+                "GroupName": "test-isolation-group",
+                "IpPermissions": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "192.168.0.1/31",
+                                "Description": "testcidr"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [
+                            {
+                                "PrefixListId": "pl-63a5400b"
+                            }
+                        ],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-01a19f602ecaf25f4",
+                "IpPermissionsEgress": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "20.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [],
+                "VpcId": "vpc-3d05f145"
+            },
+            {
+                "Description": "test-sg-2",
+                "GroupName": "test-sg-2",
+                "IpPermissions": [
+                    {
+                        "FromPort": 443,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "127.0.0.1/32"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 443,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-0b650189861ce4f6c",
+                "IpPermissionsEgress": [
+                    {
+                        "FromPort": 80,
+                        "IpProtocol": "tcp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 80,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [],
+                "VpcId": "vpc-3d05f145"
+            },
+            {
+                "Description": "sample controller SG",
+                "GroupName": "sg_controllers",
+                "IpPermissions": [
+                    {
+                        "FromPort": 443,
+                        "IpProtocol": "udp",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "ToPort": 443,
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "OwnerId": "123412341234",
+                "GroupId": "sg-c573e6b3",
+                "IpPermissionsEgress": [
+                    {
+                        "IpProtocol": "-1",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "10.0.0.0/8"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "UserIdGroupPairs": [
+                            {
+                                "GroupId": "sg-c573e6b3",
+                                "UserId": "123412341234"
+                            }
+                        ]
+                    }
+                ],
+                "VpcId": "vpc-3d05f145"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "414d164f-7241-4721-be33-42585bb438d7",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "accept-encoding",
+                "date": "Fri, 12 Jul 2019 17:53:43 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeLoadBalancers_1.json b/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeLoadBalancers_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeLoadBalancers_1.json
@@ -0,0 +1,56 @@
+{
+    "status_code": 200,
+    "data": {
+        "LoadBalancers": [
+            {
+                "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:123412341234:loadbalancer/app/test-abc/543530edaee12345",
+                "DNSName": "internal-test-abc-398177229.us-east-1.elb.amazonaws.com",
+                "CanonicalHostedZoneId": "ABCDXDOTRQ7X77",
+                "CreatedTime": {
+                    "__class__": "datetime",
+                    "year": 2018,
+                    "month": 4,
+                    "day": 16,
+                    "hour": 18,
+                    "minute": 9,
+                    "second": 43,
+                    "microsecond": 630000
+                },
+                "LoadBalancerName": "test-abc",
+                "Scheme": "internal",
+                "VpcId": "vpc-3d05f145",
+                "State": {
+                    "Code": "active"
+                },
+                "Type": "application",
+                "AvailabilityZones": [
+                    {
+                        "ZoneName": "us-east-1b",
+                        "SubnetId": "subnet-085d1234"
+                    },
+                    {
+                        "ZoneName": "us-east-1a",
+                        "SubnetId": "subnet-da895678"
+                    }
+                ],
+                "SecurityGroups": [
+                    "sg-01a19f602ecaf25f4",
+                    "sg-c573e6b3"
+                ],
+                "IpAddressType": "ipv4"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "438cdb64-a990-11e9-8978-ad60691c507d",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "438cdb64-a990-11e9-8978-ad60691c507d",
+                "content-type": "text/xml",
+                "content-length": "8807",
+                "vary": "accept-encoding",
+                "date": "Thu, 18 Jul 2019 19:14:28 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeLoadBalancers_2.json b/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeLoadBalancers_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeLoadBalancers_2.json
@@ -0,0 +1,55 @@
+{
+    "status_code": 200,
+    "data": {
+        "LoadBalancers": [
+            {
+                "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:123412341234:loadbalancer/app/test-abc/543530edaee12345",
+                "DNSName": "internal-test-abc-398177229.us-east-1.elb.amazonaws.com",
+                "CanonicalHostedZoneId": "ABCDXDOTRQ7X77",
+                "CreatedTime": {
+                    "__class__": "datetime",
+                    "year": 2018,
+                    "month": 4,
+                    "day": 16,
+                    "hour": 18,
+                    "minute": 9,
+                    "second": 43,
+                    "microsecond": 630000
+                },
+                "LoadBalancerName": "test-abc",
+                "Scheme": "internal",
+                "VpcId": "vpc-3d05f145",
+                "State": {
+                    "Code": "active"
+                },
+                "Type": "application",
+                "AvailabilityZones": [
+                    {
+                        "ZoneName": "us-east-1b",
+                        "SubnetId": "subnet-085d1234"
+                    },
+                    {
+                        "ZoneName": "us-east-1a",
+                        "SubnetId": "subnet-da895678"
+                    }
+                ],
+                "SecurityGroups": [
+                    "sg-01a19f602ecaf25f4"
+                ],
+                "IpAddressType": "ipv4"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "438cdb64-a990-11e9-8978-ad60691c507d",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "438cdb64-a990-11e9-8978-ad60691c507d",
+                "content-type": "text/xml",
+                "content-length": "8807",
+                "vary": "accept-encoding",
+                "date": "Thu, 18 Jul 2019 19:14:28 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeTags_1.json b/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeTags_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.DescribeTags_1.json
@@ -0,0 +1,28 @@
+{
+    "status_code": 200,
+    "data": {
+        "TagDescriptions": [
+            {
+                "ResourceArn": "arn:aws:elasticloadbalancing:us-east-1:123412341234:loadbalancer/app/test-abc/543530edaee12345",
+                "Tags": [
+                    {
+                        "Key": "testkey",
+                        "Value": "testval"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "43a8ee4d-a990-11e9-aebf-591f37d83b01",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "43a8ee4d-a990-11e9-aebf-591f37d83b01",
+                "content-type": "text/xml",
+                "content-length": "6372",
+                "vary": "accept-encoding",
+                "date": "Thu, 18 Jul 2019 19:14:28 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.SetSecurityGroups_1.json b/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.SetSecurityGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_remove_matched_security_groups/elasticloadbalancing.SetSecurityGroups_1.json
@@ -0,0 +1,19 @@
+{
+    "status_code": 200,
+    "data": {
+        "SecurityGroupIds": [
+            "sg-01a19f602ecaf25f4"
+        ],
+        "ResponseMetadata": {
+            "RequestId": "43e7a555-a990-11e9-a7d0-654218cb3bcd",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "43e7a555-a990-11e9-a7d0-654218cb3bcd",
+                "content-type": "text/xml",
+                "content-length": "378",
+                "date": "Thu, 18 Jul 2019 19:14:28 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_appelb.py b/tests/test_appelb.py
--- a/tests/test_appelb.py
+++ b/tests/test_appelb.py
@@ -649,3 +649,100 @@ def test_rds_health_events_filter(self):
         )
         resources = policy.run()
         self.assertEqual(len(resources), 1)
+
+
+class TestModifyVpcSecurityGroupsAction(BaseTest):
+
+    def test_appelb_remove_matched_security_groups(self):
+
+        # Test conditions:
+        #   - list contains only one ALB, 'test-abc'
+        #   - has two SGs attached before, 1 SG after
+        #   - test checks name of ALB is correct and
+        #   - that SGs change and are expected values
+
+        session_factory = self.replay_flight_data(
+            "test_appelb_remove_matched_security_groups"
+        )
+
+        p = self.load_policy(
+            {
+                "name": "appelb-remove-matched-security-groups",
+                "resource": "app-elb",
+                "filters": [
+                    {
+                        "type": "value",
+                        "key": "LoadBalancerName",
+                        "value": "test-abc",
+                        "op": "eq",
+                    },
+                    {
+                        "type": "security-group",
+                        "key": "GroupName",
+                        "value": ".*controllers",
+                        "op": "regex",
+                    },
+                ],
+                "actions": [
+                    {
+                        "type": "modify-security-groups",
+                        "remove": "matched",
+                        "isolation-group": "sg-01a19f602ecaf25f4",
+                    }
+                ],
+            },
+            session_factory=session_factory,
+        )
+
+        resources = p.run()
+        client = session_factory().client('elbv2')
+        response = client.describe_load_balancers()
+        clean_resources = response['LoadBalancers']
+
+        self.assertEqual(len(resources), 1)
+        self.assertIn("test", resources[0]["LoadBalancerName"])
+        self.assertEqual(len(resources[0]["SecurityGroups"]), 2)
+        self.assertIn("sg-c573e6b3", resources[0]["SecurityGroups"])
+        # check result is expected
+        self.assertEqual(len(clean_resources[0]["SecurityGroups"]), 1)
+        self.assertNotIn("sg-c573e6b3", clean_resources[0]["SecurityGroups"])
+
+    def test_appelb_add_security_group(self):
+
+        # Test conditions:
+        #   - list contains only one ALB, 'test-abc'
+        #   - has one SG attached before, 2 SGs after
+        #   - test checks name of ALB is correct and
+        #   - that SGs change and are expected values
+
+        session_factory = self.replay_flight_data("test_appelb_add_security_group")
+
+        p = self.load_policy(
+            {
+                "name": "add-sg-to-appelb",
+                "resource": "app-elb",
+                "filters": [
+                    {
+                        "type": "value",
+                        "key": "LoadBalancerName",
+                        "value": "test-abc",
+                        "op": "eq",
+                    },
+                ],
+                "actions": [{"type": "modify-security-groups", "add": "sg-c573e6b3"}],
+            },
+            session_factory=session_factory,
+        )
+
+        resources = p.run()
+        client = session_factory().client('elbv2')
+        response = client.describe_load_balancers()
+        clean_resources = response['LoadBalancers']
+
+        self.assertEqual(len(resources), 1)
+        self.assertEqual("test-abc", resources[0]["LoadBalancerName"])
+        self.assertEqual(len(resources[0]["SecurityGroups"]), 1)
+        self.assertNotIn("sg-c573e6b3", resources[0]["SecurityGroups"])
+        # check SG was added
+        self.assertEqual(len(clean_resources[0]["SecurityGroups"]), 2)
+        self.assertIn("sg-c573e6b3", clean_resources[0]["SecurityGroups"])