Repository: cloud-custodian/cloud-custodian

Issue:
add security-group filter to efs-mount-target resource
Should work just like the security-group filter for ec2. 
Will need to call the describe-mount-target-security-groups api


Discussion:

Resolving patch:
diff --git a/c7n/resources/efs.py b/c7n/resources/efs.py
--- a/c7n/resources/efs.py
+++ b/c7n/resources/efs.py
@@ -13,13 +13,17 @@
 # limitations under the License.
 from __future__ import absolute_import, division, print_function, unicode_literals
 import functools
+import logging
 
 from c7n.actions import Action
 from c7n.manager import resources
+from c7n.filters.vpc import SecurityGroupFilter, SubnetFilter
 from c7n.query import QueryResourceManager, ChildResourceManager
 from c7n.tags import universal_augment, register_universal_tags
 from c7n.utils import local_session, type_schema, get_retry, generate_arn
 
+log = logging.getLogger('custodian.efs')
+
 
 @resources.register('efs')
 class ElasticFileSystem(QueryResourceManager):
@@ -74,6 +78,43 @@ class resource_type(object):
         filter_type = 'scalar'
 
 
+@ElasticFileSystemMountTarget.filter_registry.register('subnet')
+class Subnet(SubnetFilter):
+
+    RelatedIdsExpression = "SubnetId"
+
+
+@ElasticFileSystemMountTarget.filter_registry.register('security-group')
+class SecurityGroup(SecurityGroupFilter):
+
+    efs_group_cache = None
+
+    RelatedIdsExpression = ""
+
+    def get_related_ids(self, resources):
+
+        if self.efs_group_cache:
+            group_ids = set()
+            for r in resources:
+                group_ids.update(
+                    self.efs_group_cache.get(r['MountTargetId'], ()))
+            return list(group_ids)
+
+        client = local_session(self.manager.session_factory).client('efs')
+        groups = {}
+        group_ids = set()
+        retry = get_retry(('Throttled',), 12)
+
+        for r in resources:
+            groups[r['MountTargetId']] = retry(
+                client.describe_mount_target_security_groups,
+                MountTargetId=r['MountTargetId'])['SecurityGroups']
+            group_ids.update(groups[r['MountTargetId']])
+
+        self.efs_group_cache = groups
+        return list(group_ids)
+
+
 @ElasticFileSystem.action_registry.register('delete')
 class Delete(Action):
 


Test patch:
diff --git a/tests/data/placebo/test_efs_mount_secgroup/ec2.DescribeSecurityGroups_1.json b/tests/data/placebo/test_efs_mount_secgroup/ec2.DescribeSecurityGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_efs_mount_secgroup/ec2.DescribeSecurityGroups_1.json
@@ -0,0 +1,53 @@
+{
+    "status_code": 200, 
+    "data": {
+        "SecurityGroups": [
+            {
+                "IpPermissionsEgress": [
+                    {
+                        "IpProtocol": "-1", 
+                        "PrefixListIds": [], 
+                        "IpRanges": [
+                            {
+                                "CidrIp": "0.0.0.0/0"
+                            }
+                        ], 
+                        "UserIdGroupPairs": [], 
+                        "Ipv6Ranges": []
+                    }
+                ], 
+                "Description": "default VPC security group", 
+                "IpPermissions": [
+                    {
+                        "IpProtocol": "-1", 
+                        "PrefixListIds": [], 
+                        "IpRanges": [], 
+                        "UserIdGroupPairs": [
+                            {
+                                "UserId": "117134021140", 
+                                "GroupId": "sg-ccf3a8a4"
+                            }
+                        ], 
+                        "Ipv6Ranges": []
+                    }
+                ], 
+                "GroupName": "default", 
+                "VpcId": "vpc-e9678981", 
+                "OwnerId": "117134021140", 
+                "GroupId": "sg-ccf3a8a4"
+            }
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "f689bf9c-7b9f-4145-a74d-d0c7dbf622a8", 
+            "HTTPHeaders": {
+                "transfer-encoding": "chunked", 
+                "vary": "Accept-Encoding", 
+                "server": "AmazonEC2", 
+                "content-type": "text/xml;charset=UTF-8", 
+                "date": "Tue, 28 Nov 2017 20:08:22 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeFileSystems_1.json b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeFileSystems_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeFileSystems_1.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "e167c3af-d477-11e7-81e4-a7e35a529114", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e167c3af-d477-11e7-81e4-a7e35a529114", 
+                "date": "Tue, 28 Nov 2017 20:08:20 GMT", 
+                "content-length": "373", 
+                "content-type": "application/json"
+            }
+        }, 
+        "FileSystems": [
+            {
+                "SizeInBytes": {
+                    "Value": 6144
+                }, 
+                "Name": "TestSecGroups", 
+                "CreationToken": "console-e953c8e7-d309-4235-9c98-f8091c444e74", 
+                "Encrypted": false, 
+                "CreationTime": {
+                    "hour": 11, 
+                    "__class__": "datetime", 
+                    "month": 11, 
+                    "second": 26, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 28, 
+                    "minute": 54
+                }, 
+                "PerformanceMode": "generalPurpose", 
+                "FileSystemId": "fs-66f3051f", 
+                "NumberOfMountTargets": 3, 
+                "LifeCycleState": "available", 
+                "OwnerId": "117134021140"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_1.json b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_1.json
@@ -0,0 +1,19 @@
+{
+    "status_code": 200, 
+    "data": {
+        "SecurityGroups": [
+            "sg-ccf3a8a4"
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "e26e5df2-d477-11e7-8a75-6d6654102ba7", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e26e5df2-d477-11e7-8a75-6d6654102ba7", 
+                "date": "Tue, 28 Nov 2017 20:08:22 GMT", 
+                "content-length": "34", 
+                "content-type": "application/json"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_2.json b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_2.json
@@ -0,0 +1,19 @@
+{
+    "status_code": 200, 
+    "data": {
+        "SecurityGroups": [
+            "sg-ccf3a8a4"
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "e2998ca3-d477-11e7-8a75-6d6654102ba7", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e2998ca3-d477-11e7-8a75-6d6654102ba7", 
+                "date": "Tue, 28 Nov 2017 20:08:22 GMT", 
+                "content-length": "34", 
+                "content-type": "application/json"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_3.json b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargetSecurityGroups_3.json
@@ -0,0 +1,19 @@
+{
+    "status_code": 200, 
+    "data": {
+        "SecurityGroups": [
+            "sg-ccf3a8a4"
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "e2b13354-d477-11e7-8a75-6d6654102ba7", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e2b13354-d477-11e7-8a75-6d6654102ba7", 
+                "date": "Tue, 28 Nov 2017 20:08:22 GMT", 
+                "content-length": "34", 
+                "content-type": "application/json"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargets_1.json b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargets_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_efs_mount_secgroup/elasticfilesystem.DescribeMountTargets_1.json
@@ -0,0 +1,45 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "e212f7d7-d477-11e7-81e4-a7e35a529114", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e212f7d7-d477-11e7-81e4-a7e35a529114", 
+                "date": "Tue, 28 Nov 2017 20:08:21 GMT", 
+                "content-length": "677", 
+                "content-type": "application/json"
+            }
+        }, 
+        "MountTargets": [
+            {
+                "MountTargetId": "fsmt-a47385dd", 
+                "NetworkInterfaceId": "eni-771c4828", 
+                "FileSystemId": "fs-66f3051f", 
+                "LifeCycleState": "available", 
+                "SubnetId": "subnet-5cd42634", 
+                "OwnerId": "117134021140", 
+                "IpAddress": "172.31.3.4"
+            }, 
+            {
+                "MountTargetId": "fsmt-a67385df", 
+                "NetworkInterfaceId": "eni-3e8b2915", 
+                "FileSystemId": "fs-66f3051f", 
+                "LifeCycleState": "available", 
+                "SubnetId": "subnet-28116065", 
+                "OwnerId": "117134021140", 
+                "IpAddress": "172.31.34.199"
+            }, 
+            {
+                "MountTargetId": "fsmt-a77385de", 
+                "NetworkInterfaceId": "eni-e72ebfb2", 
+                "FileSystemId": "fs-66f3051f", 
+                "LifeCycleState": "available", 
+                "SubnetId": "subnet-3f447544", 
+                "OwnerId": "117134021140", 
+                "IpAddress": "172.31.20.211"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_efs_mount_secgroup/tagging.GetResources_1.json b/tests/data/placebo/test_efs_mount_secgroup/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_efs_mount_secgroup/tagging.GetResources_1.json
@@ -0,0 +1,28 @@
+{
+    "status_code": 200, 
+    "data": {
+        "PaginationToken": "", 
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:elasticfilesystem:us-east-2:117134021140:file-system/fs-66f3051f", 
+                "Tags": [
+                    {
+                        "Value": "TestSecGroups", 
+                        "Key": "Name"
+                    }
+                ]
+            }
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "e1d4b663-d477-11e7-b7f3-99db73f5e6cc", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e1d4b663-d477-11e7-b7f3-99db73f5e6cc", 
+                "date": "Tue, 28 Nov 2017 20:08:20 GMT", 
+                "content-length": "188", 
+                "content-type": "application/x-amz-json-1.1"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_efs.py b/tests/test_efs.py
--- a/tests/test_efs.py
+++ b/tests/test_efs.py
@@ -18,6 +18,9 @@
 import uuid
 import time
 
+from operator import itemgetter
+
+
 class ElasticFileSystem(BaseTest):
 
     @functional
@@ -51,6 +54,25 @@ def test_mount_target_loading(self):
         resources = p.run()
         self.assertEqual(len(resources), 2)
 
+    def test_mount_target_security_group(self):
+        factory = self.replay_flight_data('test_efs_mount_secgroup')
+        p = self.load_policy({
+            'name': 'test-mount-secgroup',
+            'resource': 'efs-mount-target',
+            'filters': [
+                {'type': 'security-group',
+                 'key': 'GroupId',
+                 'value': 'sg-ccf3a8a4'},
+                # Use the same filter twice to excercise cache code
+                {'type': 'security-group',
+                 'key': 'GroupId',
+                 'value': 'sg-ccf3a8a4'},
+            ]}, session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 3)
+        resources = sorted(resources, key=itemgetter('MountTargetId'))
+        self.assertEqual(resources[0]['MountTargetId'], 'fsmt-a47385dd')
+
     def test_delete(self):
         factory = self.replay_flight_data('test_efs_delete')
         p = self.load_policy({