Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/c7n/resources/efs.py: 44%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

239 statements  

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3import json 

4from botocore.exceptions import ClientError 

5 

6from c7n.actions import Action, BaseAction, RemovePolicyBase 

7from c7n.exceptions import PolicyValidationError 

8from c7n.filters.kms import KmsRelatedFilter 

9from c7n.filters import Filter, CrossAccountAccessFilter 

10from c7n.manager import resources 

11from c7n.filters.vpc import SecurityGroupFilter, SubnetFilter, NetworkLocation 

12from c7n.filters.policystatement import HasStatementFilter 

13from c7n.query import ( 

14 QueryResourceManager, ChildResourceManager, TypeInfo, DescribeSource, ConfigSource 

15) 

16from c7n.tags import universal_augment 

17from c7n.utils import local_session, type_schema, get_retry 

18from .aws import shape_validate 

19from c7n.filters.backup import ConsecutiveAwsBackupsFilter 

20 

21 

22class EFSDescribe(DescribeSource): 

23 

24 def augment(self, resources): 

25 return universal_augment(self.manager, resources) 

26 

27 

28@resources.register('efs') 

29class ElasticFileSystem(QueryResourceManager): 

30 

31 class resource_type(TypeInfo): 

32 service = 'efs' 

33 enum_spec = ('describe_file_systems', 'FileSystems', None) 

34 id = 'FileSystemId' 

35 name = 'Name' 

36 date = 'CreationTime' 

37 dimension = 'FileSystemId' 

38 arn_type = 'file-system' 

39 permission_prefix = arn_service = 'elasticfilesystem' 

40 filter_name = 'FileSystemId' 

41 filter_type = 'scalar' 

42 universal_taggable = True 

43 config_type = cfn_type = 'AWS::EFS::FileSystem' 

44 arn = 'FileSystemArn' 

45 permissions_augment = ("elasticfilesystem:ListTagsForResource",) 

46 

47 source_mapping = { 

48 'describe': EFSDescribe, 

49 'config': ConfigSource 

50 } 

51 

52 

53@resources.register('efs-mount-target') 

54class ElasticFileSystemMountTarget(ChildResourceManager): 

55 

56 class resource_type(TypeInfo): 

57 service = 'efs' 

58 parent_spec = ('efs', 'FileSystemId', None) 

59 enum_spec = ('describe_mount_targets', 'MountTargets', None) 

60 permission_prefix = 'elasticfilesystem' 

61 name = id = 'MountTargetId' 

62 arn = False 

63 cfn_type = 'AWS::EFS::MountTarget' 

64 supports_trailevents = True 

65 

66 

67@ElasticFileSystemMountTarget.filter_registry.register('subnet') 

68class Subnet(SubnetFilter): 

69 

70 RelatedIdsExpression = "SubnetId" 

71 

72 

73@ElasticFileSystemMountTarget.filter_registry.register('security-group') 

74class SecurityGroup(SecurityGroupFilter): 

75 

76 efs_group_cache = None 

77 

78 RelatedIdsExpression = "" 

79 

80 def get_related_ids(self, resources): 

81 

82 if self.efs_group_cache: 

83 group_ids = set() 

84 for r in resources: 

85 group_ids.update( 

86 self.efs_group_cache.get(r['MountTargetId'], ())) 

87 return list(group_ids) 

88 

89 client = local_session(self.manager.session_factory).client('efs') 

90 groups = {} 

91 group_ids = set() 

92 retry = get_retry(('Throttled',), 12) 

93 

94 for r in resources: 

95 groups[r['MountTargetId']] = retry( 

96 client.describe_mount_target_security_groups, 

97 MountTargetId=r['MountTargetId'])['SecurityGroups'] 

98 group_ids.update(groups[r['MountTargetId']]) 

99 

100 self.efs_group_cache = groups 

101 return list(group_ids) 

102 

103 

104@ElasticFileSystemMountTarget.filter_registry.register('network-location', NetworkLocation) 

105@ElasticFileSystem.filter_registry.register('kms-key') 

106class KmsFilter(KmsRelatedFilter): 

107 

108 RelatedIdsExpression = 'KmsKeyId' 

109 

110 

111@ElasticFileSystem.action_registry.register('delete') 

112class Delete(Action): 

113 

114 schema = type_schema('delete') 

115 permissions = ('elasticfilesystem:DescribeMountTargets', 

116 'elasticfilesystem:DeleteMountTarget', 

117 'elasticfilesystem:DeleteFileSystem') 

118 

119 def process(self, resources): 

120 client = local_session(self.manager.session_factory).client('efs') 

121 self.unmount_filesystems(resources) 

122 retry = get_retry(('FileSystemInUse',), 12) 

123 for r in resources: 

124 retry(client.delete_file_system, FileSystemId=r['FileSystemId']) 

125 

126 def unmount_filesystems(self, resources): 

127 client = local_session(self.manager.session_factory).client('efs') 

128 for r in resources: 

129 if not r['NumberOfMountTargets']: 

130 continue 

131 for t in client.describe_mount_targets( 

132 FileSystemId=r['FileSystemId'])['MountTargets']: 

133 client.delete_mount_target(MountTargetId=t['MountTargetId']) 

134 

135 

136@ElasticFileSystem.action_registry.register('configure-lifecycle-policy') 

137class ConfigureLifecycle(BaseAction): 

138 """Enable/disable lifecycle policy for efs. 

139 

140 :example: 

141 

142 .. code-block:: yaml 

143 

144 policies: 

145 - name: efs-apply-lifecycle 

146 resource: efs 

147 actions: 

148 - type: configure-lifecycle-policy 

149 state: enable 

150 rules: 

151 - 'TransitionToIA': 'AFTER_7_DAYS' 

152 

153 """ 

154 schema = type_schema( 

155 'configure-lifecycle-policy', 

156 state={'enum': ['enable', 'disable']}, 

157 rules={ 

158 'type': 'array', 

159 'items': {'type': 'object'}}, 

160 required=['state']) 

161 

162 permissions = ('elasticfilesystem:PutLifecycleConfiguration',) 

163 shape = 'PutLifecycleConfigurationRequest' 

164 

165 def validate(self): 

166 if self.data.get('state') == 'enable' and 'rules' not in self.data: 

167 raise PolicyValidationError( 

168 'rules are required to enable lifecycle configuration %s' % (self.manager.data)) 

169 if self.data.get('state') == 'disable' and 'rules' in self.data: 

170 raise PolicyValidationError( 

171 'rules not required to disable lifecycle configuration %s' % (self.manager.data)) 

172 if self.data.get('rules'): 

173 attrs = {} 

174 attrs['LifecyclePolicies'] = self.data['rules'] 

175 attrs['FileSystemId'] = 'PolicyValidator' 

176 return shape_validate(attrs, self.shape, 'efs') 

177 

178 def process(self, resources): 

179 client = local_session(self.manager.session_factory).client('efs') 

180 op_map = {'enable': self.data.get('rules'), 'disable': []} 

181 for r in resources: 

182 try: 

183 client.put_lifecycle_configuration( 

184 FileSystemId=r['FileSystemId'], 

185 LifecyclePolicies=op_map.get(self.data.get('state'))) 

186 except client.exceptions.FileSystemNotFound: 

187 continue 

188 

189 

190@ElasticFileSystem.filter_registry.register('lifecycle-policy') 

191class LifecyclePolicy(Filter): 

192 """Filters efs based on the state of lifecycle policies 

193 

194 :example: 

195 

196 .. code-block:: yaml 

197 

198 policies: 

199 - name: efs-filter-lifecycle 

200 resource: efs 

201 filters: 

202 - type: lifecycle-policy 

203 state: present 

204 value: AFTER_7_DAYS 

205 

206 """ 

207 schema = type_schema( 

208 'lifecycle-policy', 

209 state={'enum': ['present', 'absent']}, 

210 value={'type': 'string'}, 

211 required=['state']) 

212 

213 permissions = ('elasticfilesystem:DescribeLifecycleConfiguration',) 

214 

215 def process(self, resources, event=None): 

216 resources = self.fetch_resources_lfc(resources) 

217 if self.data.get('value'): 

218 config = {'TransitionToIA': self.data.get('value')} 

219 if self.data.get('state') == 'present': 

220 return [r for r in resources if config in r.get('c7n:LifecyclePolicies')] 

221 return [r for r in resources if config not in r.get('c7n:LifecyclePolicies')] 

222 else: 

223 if self.data.get('state') == 'present': 

224 return [r for r in resources if r.get('c7n:LifecyclePolicies')] 

225 return [r for r in resources if r.get('c7n:LifecyclePolicies') == []] 

226 

227 def fetch_resources_lfc(self, resources): 

228 client = local_session(self.manager.session_factory).client('efs') 

229 for r in resources: 

230 try: 

231 lfc = client.describe_lifecycle_configuration( 

232 FileSystemId=r['FileSystemId']).get('LifecyclePolicies') 

233 r['c7n:LifecyclePolicies'] = lfc 

234 except client.exceptions.FileSystemNotFound: 

235 continue 

236 return resources 

237 

238 

239@ElasticFileSystem.filter_registry.register('check-secure-transport') 

240class CheckSecureTransport(Filter): 

241 """Find EFS that does not enforce secure transport 

242 

243 :Example: 

244 

245 .. code-block:: yaml 

246 

247 - name: efs-securetransport-check-policy 

248 resource: efs 

249 filters: 

250 - check-secure-transport 

251 

252 To configure an EFS to enforce secure transport, set up the appropriate 

253 Effect and Condition for its policy document. For example: 

254 

255 .. code-block:: json 

256 

257 { 

258 "Sid": "efs-statement-b3f6b59b-d938-4001-9154-508f67707073", 

259 "Effect": "Deny", 

260 "Principal": { "AWS": "*" }, 

261 "Action": "*", 

262 "Condition": { 

263 "Bool": { "aws:SecureTransport": "false" } 

264 } 

265 } 

266 """ 

267 

268 schema = type_schema('check-secure-transport') 

269 permissions = ('elasticfilesystem:DescribeFileSystemPolicy',) 

270 

271 policy_annotation = 'c7n:Policy' 

272 

273 def get_policy(self, client, resource): 

274 if self.policy_annotation in resource: 

275 return resource[self.policy_annotation] 

276 try: 

277 result = client.describe_file_system_policy( 

278 FileSystemId=resource['FileSystemId']) 

279 except client.exceptions.PolicyNotFound: 

280 return None 

281 resource[self.policy_annotation] = json.loads(result['Policy']) 

282 return resource[self.policy_annotation] 

283 

284 def securetransport_check_policy(self, client, resource): 

285 policy = self.get_policy(client, resource) 

286 if not policy: 

287 return True 

288 

289 statements = policy['Statement'] 

290 if isinstance(statements, dict): 

291 statements = [statements] 

292 

293 for s in statements: 

294 try: 

295 effect = s['Effect'] 

296 secureTransportValue = s['Condition']['Bool']['aws:SecureTransport'] 

297 if ((effect == 'Deny' and secureTransportValue == 'false') or 

298 (effect == 'Allow' and secureTransportValue == 'true')): 

299 return False 

300 except (KeyError, TypeError): 

301 pass 

302 

303 return True 

304 

305 def process(self, resources, event=None): 

306 c = local_session(self.manager.session_factory).client('efs') 

307 results = [r for r in resources if self.securetransport_check_policy(c, r)] 

308 self.log.info( 

309 "%d of %d EFS policies don't enforce secure transport", 

310 len(results), len(resources)) 

311 return results 

312 

313 

314@ElasticFileSystem.filter_registry.register('has-statement') 

315class EFSHasStatementFilter(HasStatementFilter): 

316 

317 def __init__(self, data, manager=None): 

318 super().__init__(data, manager) 

319 self.policy_attribute = 'c7n:Policy' 

320 

321 def process(self, resources, event=None): 

322 resources = [self.policy_annotate(r) for r in resources] 

323 return super().process(resources, event) 

324 

325 def policy_annotate(self, resource): 

326 client = local_session(self.manager.session_factory).client('efs') 

327 if self.policy_attribute in resource: 

328 return resource 

329 try: 

330 result = client.describe_file_system_policy( 

331 FileSystemId=resource['FileSystemId']) 

332 resource[self.policy_attribute] = result['Policy'] 

333 except client.exceptions.PolicyNotFound: 

334 resource[self.policy_attribute] = None 

335 return resource 

336 return resource 

337 

338 def get_std_format_args(self, fs): 

339 return { 

340 'fs_arn': fs['FileSystemArn'], 

341 'account_id': self.manager.config.account_id, 

342 'region': self.manager.config.region 

343 } 

344 

345 

346@ElasticFileSystem.filter_registry.register('cross-account') 

347class EFSCrossAccountFilter(CrossAccountAccessFilter): 

348 """Filter EFS file systems which have cross account permissions 

349 

350 :example: 

351 

352 .. code-block:: yaml 

353 

354 policies: 

355 - name: efs-cross-account 

356 resource: aws.efs 

357 filters: 

358 - type: cross-account 

359 """ 

360 permissions = ('elasticfilesystem:DescribeFileSystemPolicy',) 

361 

362 def process(self, resources, event=None): 

363 def _augment(r): 

364 client = local_session( 

365 self.manager.session_factory).client('efs') 

366 try: 

367 r['Policy'] = client.describe_file_system_policy( 

368 FileSystemId=r['FileSystemId'])['Policy'] 

369 return r 

370 except ClientError as e: 

371 if e.response['Error']['Code'] == 'AccessDeniedException': 

372 self.log.warning( 

373 "Access denied getting policy elasticfilesystems:%s", 

374 r['FileSystemId']) 

375 

376 self.log.debug("fetching policy for %d elasticfilesystems" % len(resources)) 

377 with self.executor_factory(max_workers=3) as w: 

378 resources = list(filter(None, w.map(_augment, resources))) 

379 

380 return super(EFSCrossAccountFilter, self).process( 

381 resources, event) 

382 

383 

384@ElasticFileSystem.action_registry.register('remove-statements') 

385class RemovePolicyStatement(RemovePolicyBase): 

386 """Action to remove policy statements from EFS 

387 

388 :example: 

389 

390 .. code-block:: yaml 

391 

392 policies: 

393 - name: remove-efs-cross-account 

394 resource: efs 

395 filters: 

396 - type: cross-account 

397 actions: 

398 - type: remove-statements 

399 statement_ids: matched 

400 """ 

401 

402 schema = type_schema( 

403 'remove-statements', 

404 required=['statement_ids'], 

405 statement_ids={'oneOf': [ 

406 {'enum': ['matched']}, 

407 {'type': 'array', 'items': {'type': 'string'}}]}) 

408 

409 permissions = ( 

410 'elasticfilesystem:DescribeFileSystems', 'elasticfilesystem:DeleteFileSystemPolicy' 

411 ) 

412 

413 def process(self, resources): 

414 results = [] 

415 client = local_session(self.manager.session_factory).client('efs') 

416 for r in resources: 

417 try: 

418 results += filter(None, [self.process_resource(client, r)]) 

419 except Exception: 

420 self.log.exception( 

421 "Error processing elasticfilesystem:%s", r['FileSystemId']) 

422 return results 

423 

424 def process_resource(self, client, resource): 

425 if 'Policy' not in resource: 

426 try: 

427 resource['Policy'] = client.describe_file_system_policy( 

428 FileSystemId=resource['FileSystemId']).get('Policy') 

429 except ClientError as e: 

430 if e.response['Error']['Code'] != "FileSystemNotFound": 

431 raise 

432 

433 if not resource['Policy']: 

434 return 

435 

436 p = json.loads(resource['Policy']) 

437 statements, found = self.process_policy( 

438 p, resource, CrossAccountAccessFilter.annotation_key) 

439 

440 if not found: 

441 return 

442 

443 if not statements: 

444 client.delete_file_system_policy(FileSystemId=resource['FileSystemId']) 

445 else: 

446 client.put_file_system_policy( 

447 FileSystemId=resource['FileSystemId'], 

448 Policy=json.dumps(p) 

449 ) 

450 return {'Name': resource['FileSystemId'], 

451 'State': 'PolicyRemoved', 

452 'Statements': found} 

453 

454 

455ElasticFileSystem.filter_registry.register('consecutive-aws-backups', ConsecutiveAwsBackupsFilter)