Skip to content

Commit 550f6bf

Browse files
1101-1aquamatthias
andauthored
[aws][feat]: Add more policies to collect (#2202)
Co-authored-by: Matthias Veit <aquamatthias@users.noreply.github.com>
1 parent 47c6253 commit 550f6bf

10 files changed

Lines changed: 96 additions & 17 deletions

File tree

plugins/aws/fix_plugin_aws/resource/backup.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
from datetime import datetime
33
from typing import Any, ClassVar, Dict, Optional, List, Type
4+
from json import loads as json_loads
45

56
from attrs import define, field
67

@@ -18,6 +19,7 @@
1819
from fixlib.graph import Graph
1920
from fixlib.json_bender import F, Bender, S, ForallBend, Bend
2021
from fixlib.types import Json
22+
from fixlib.json import sort_json
2123

2224
log = logging.getLogger("fix.plugins.aws")
2325
service_name = "backup"
@@ -345,13 +347,15 @@ class AwsBackupVault(BackupResourceTaggable, AwsResource):
345347
min_retention_days: Optional[int] = field(default=None, metadata={"description": "The Backup Vault Lock setting that specifies the minimum retention period that the vault retains its recovery points. If this parameter is not specified, Vault Lock does not enforce a minimum retention period. If specified, any backup or copy job to the vault must have a lifecycle policy with a retention period equal to or longer than the minimum retention period. If the job's retention period is shorter than that minimum retention period, then the vault fails the backup or copy job, and you should either modify your lifecycle settings or use a different vault. Recovery points already stored in the vault prior to Vault Lock are not affected."}) # fmt: skip
346348
max_retention_days: Optional[int] = field(default=None, metadata={"description": "The Backup Vault Lock setting that specifies the maximum retention period that the vault retains its recovery points. If this parameter is not specified, Vault Lock does not enforce a maximum retention period on the recovery points in the vault (allowing indefinite storage). If specified, any backup or copy job to the vault must have a lifecycle policy with a retention period equal to or shorter than the maximum retention period. If the job's retention period is longer than that maximum retention period, then the vault fails the backup or copy job, and you should either modify your lifecycle settings or use a different vault. Recovery points already stored in the vault prior to Vault Lock are not affected."}) # fmt: skip
347349
lock_date: Optional[datetime] = field(default=None, metadata={"description": "The date and time when Backup Vault Lock configuration becomes immutable, meaning it cannot be changed or deleted. If you applied Vault Lock to your vault without specifying a lock date, you can change your Vault Lock settings, or delete Vault Lock from the vault entirely, at any time. This value is in Unix format, Coordinated Universal Time (UTC), and accurate to milliseconds. For example, the value 1516925490.087 represents Friday, January 26, 2018 12:11:30.087 AM."}) # fmt: skip
350+
vault_policy: Optional[Json] = field(default=None)
348351

349352
@classmethod
350353
def called_collect_apis(cls) -> List[AwsApiSpec]:
351354
return [
352355
cls.api_spec,
353356
AwsApiSpec(service_name, "list-tags"),
354357
AwsApiSpec(service_name, "list-recovery-points-by-backup-vault"),
358+
AwsApiSpec(service_name, "get-backup-vault-access-policy"),
355359
]
356360

357361
@classmethod
@@ -394,11 +398,23 @@ def add_tags(backup_plan: AwsBackupVault) -> None:
394398
for tag in tags:
395399
backup_plan.tags.update(tag)
396400

401+
def add_vault_policy(vault: AwsBackupVault) -> None:
402+
with builder.suppress(f"{service_name}.get-backup-vault-access-policy"):
403+
if raw_policy := builder.client.get(
404+
service_name,
405+
"get-backup-vault-access-policy",
406+
"Policy",
407+
BackupVaultName=vault.name,
408+
expected_errors=["ResourceNotFoundException"],
409+
):
410+
vault.vault_policy = sort_json(json_loads(raw_policy), sort_list=True) # type: ignore
411+
397412
for js in json:
398413
if instance := cls.from_api(js, builder):
399414
builder.add_node(instance, js)
400415
builder.submit_work(service_name, collect_recovery_points, instance)
401416
builder.submit_work(service_name, add_tags, instance)
417+
builder.submit_work(service_name, add_vault_policy, instance)
402418

403419

404420
@define(eq=False, slots=False)

plugins/aws/fix_plugin_aws/resource/dynamodb.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import datetime
22
from typing import ClassVar, Dict, List, Optional, Type, Any
33
from attrs import define, field
4+
from json import loads as json_loads
45

56
from fix_plugin_aws.aws_client import AwsClient
67
from fix_plugin_aws.resource.base import AwsApiSpec, AwsResource, GraphBuilder, parse_json
@@ -11,6 +12,7 @@
1112
from fixlib.graph import Graph
1213
from fixlib.json_bender import S, Bend, Bender, ForallBend, bend
1314
from fixlib.types import Json
15+
from fixlib.json import sort_json
1416

1517
service_name = "dynamodb"
1618

@@ -418,6 +420,7 @@ class AwsDynamoDbTable(DynamoDbTaggable, AwsResource):
418420
dynamodb_archival_summary: Optional[AwsDynamoDbArchivalSummary] = field(default=None)
419421
dynamodb_table_class_summary: Optional[AwsDynamoDbTableClassSummary] = field(default=None)
420422
dynamodb_continuous_backup: Optional[AwsDynamoDbContinuousBackup] = field(default=None)
423+
dynamodb_policy: Optional[Json] = field(default=None)
421424

422425
@classmethod
423426
def called_collect_apis(cls) -> List[AwsApiSpec]:
@@ -426,10 +429,20 @@ def called_collect_apis(cls) -> List[AwsApiSpec]:
426429
AwsApiSpec(service_name, "describe-table"),
427430
AwsApiSpec(service_name, "list-tags-of-resource"),
428431
AwsApiSpec(service_name, "describe-continuous-backups"),
432+
AwsApiSpec(service_name, "get-resource-policy"),
429433
]
430434

431435
@classmethod
432436
def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) -> None:
437+
def add_dynamodb_policy(table: AwsDynamoDbTable) -> None:
438+
with builder.suppress(f"{service_name}.get-bucket-policy"):
439+
if raw_policy := builder.client.get(
440+
service_name,
441+
"get-resource-policy",
442+
"Policy",
443+
ResourceArn=table.arn,
444+
):
445+
table.dynamodb_policy = sort_json(json_loads(raw_policy), sort_list=True) # type: ignore
433446

434447
def add_backup_description(table: AwsDynamoDbTable) -> None:
435448
if continuous_backup := builder.client.get(
@@ -446,6 +459,7 @@ def add_instance(table: str) -> None:
446459
builder.add_node(instance, table_description)
447460
builder.submit_work(service_name, add_tags, instance)
448461
builder.submit_work(service_name, add_backup_description, instance)
462+
builder.submit_work(service_name, add_dynamodb_policy, instance)
449463

450464
def add_tags(table: AwsDynamoDbTable) -> None:
451465
tags = builder.client.list(service_name, "list-tags-of-resource", "Tags", ResourceArn=table.arn)
@@ -515,17 +529,30 @@ class AwsDynamoDbGlobalTable(DynamoDbTaggable, AwsResource):
515529
arn: Optional[str] = field(default=None)
516530
dynamodb_replication_group: List[AwsDynamoDbReplicaDescription] = field(factory=list)
517531
dynamodb_global_table_status: Optional[str] = field(default=None)
532+
dynamodb_policy: Optional[Json] = field(default=None)
518533

519534
@classmethod
520535
def called_collect_apis(cls) -> List[AwsApiSpec]:
521536
return [
522537
cls.api_spec,
523538
AwsApiSpec(service_name, "describe-global-table"),
524539
AwsApiSpec(service_name, "list-tags-of-resource"),
540+
AwsApiSpec(service_name, "get-resource-policy"),
525541
]
526542

527543
@classmethod
528544
def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) -> None:
545+
def add_dynamodb_policy(table: AwsDynamoDbGlobalTable) -> None:
546+
with builder.suppress(f"{service_name}.get-bucket-policy"):
547+
if raw_policy := builder.client.get(
548+
service_name,
549+
"get-resource-policy",
550+
"Policy",
551+
ResourceArn=table.arn,
552+
expected_errors=["PolicyNotFoundException"],
553+
):
554+
table.dynamodb_policy = sort_json(json_loads(raw_policy), sort_list=True) # type: ignore
555+
529556
def add_instance(table: Dict[str, str]) -> None:
530557
table_description = builder.client.get(
531558
service_name,
@@ -537,6 +564,7 @@ def add_instance(table: Dict[str, str]) -> None:
537564
if instance := cls.from_api(table_description, builder):
538565
builder.add_node(instance, table_description)
539566
builder.submit_work(service_name, add_tags, instance)
567+
builder.submit_work(service_name, add_dynamodb_policy, instance)
540568

541569
def add_tags(table: AwsDynamoDbGlobalTable) -> None:
542570
tags = builder.client.list(service_name, "list-tags-of-resource", "Tags", ResourceArn=table.arn)

plugins/aws/fix_plugin_aws/resource/ecr.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import logging
33
from typing import ClassVar, Dict, Optional, List, Type, Any
4+
from json import loads as json_loads
45

56
from attrs import define, field
67
from boto3.exceptions import Boto3Error
@@ -53,9 +54,21 @@ class AwsEcrRepository(AwsResource):
5354
encryption_configuration: Optional[AwsEcrEncryptionConfiguration] = field(default=None, metadata={"description": "The encryption configuration for the repository. This determines how the contents of your repository are encrypted at rest."}) # fmt: skip
5455
repository_visibility: Optional[str] = field(default=None, metadata={"description": "The repository is either public or private."}) # fmt: skip
5556
lifecycle_policy: Optional[Json] = field(default=None, metadata={"description": "The repository lifecycle policy."}) # fmt: skip
57+
repository_policy: Optional[Json] = field(default=None, metadata={"description": "The repository policy."}) # fmt: skip
5658

5759
@classmethod
5860
def collect_resources(cls, builder: GraphBuilder) -> None:
61+
def add_repository_policy(repository: AwsEcrRepository) -> None:
62+
with builder.suppress(f"{service_name}.get-repository-policy"):
63+
if raw_policy := builder.client.get(
64+
service_name,
65+
"get-repository-policy",
66+
"policyText",
67+
repositoryName=repository.name,
68+
expected_errors=["RepositoryPolicyNotFoundException", "RepositoryNotFoundException"],
69+
):
70+
repository.repository_policy = sort_json(json_loads(raw_policy), sort_list=True) # type: ignore
71+
5972
def fetch_lifecycle_policy(repository: AwsEcrRepository) -> None:
6073
with builder.suppress(f"{service_name}.get-lifecycle-policy"):
6174
if policy := builder.client.get(
@@ -79,8 +92,9 @@ def collect(visibility: str, spec: AwsApiSpec) -> None:
7992
for js in items:
8093
if instance := cls.from_api(js, builder):
8194
instance.repository_visibility = visibility
82-
builder.submit_work(service_name, fetch_lifecycle_policy, instance)
8395
builder.add_node(instance, js)
96+
builder.submit_work(service_name, fetch_lifecycle_policy, instance)
97+
builder.submit_work(service_name, add_repository_policy, instance)
8498
except Boto3Error as e:
8599
msg = f"Error while collecting {cls.__name__} in region {builder.region.name}: {e}"
86100
builder.core_feedback.error(msg, log)
@@ -98,7 +112,12 @@ def collect(visibility: str, spec: AwsApiSpec) -> None:
98112

99113
@classmethod
100114
def called_collect_apis(cls) -> List[AwsApiSpec]:
101-
return [cls.api_spec, cls.public_spec, AwsApiSpec("ecr", "get-lifecycle-policy", None)]
115+
return [
116+
cls.api_spec,
117+
cls.public_spec,
118+
AwsApiSpec(service_name, "get-lifecycle-policy"),
119+
AwsApiSpec(service_name, "get-repository-policy"),
120+
]
102121

103122

104123
# @define(eq=False, slots=False)

plugins/aws/fix_plugin_aws/resource/efs.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,11 @@ def fetch_file_system_policy(fs: AwsEfsFileSystem) -> None:
149149
if policy := builder.client.get(
150150
service_name,
151151
"describe-file-system-policy",
152+
"Policy",
152153
FileSystemId=fs.id,
153-
expected_errors=["PolicyNotFound"],
154+
expected_errors=["PolicyNotFound", "FileSystemNotFound"],
154155
):
155-
fs.file_system_policy = sort_json(json.loads(policy["Policy"]), sort_list=True)
156+
fs.file_system_policy = sort_json(json.loads(policy), sort_list=True) # type: ignore
156157

157158
for js in js_list:
158159
if instance := cls.from_api(js, builder):

plugins/aws/fix_plugin_aws/resource/kinesis.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import ClassVar, Dict, Optional, List, Any
2+
from json import loads as json_loads
23

34
from attrs import define, field
45

@@ -11,6 +12,7 @@
1112
from fixlib.graph import Graph
1213
from fixlib.json_bender import Bender, S, Bend, bend, ForallBend
1314
from fixlib.types import Json
15+
from fixlib.json import sort_json
1416
from typing import Type
1517

1618
service_name = "kinesis"
@@ -132,17 +134,30 @@ class AwsKinesisStream(AwsResource):
132134
kinesis_enhanced_monitoring: List[AwsKinesisEnhancedMetrics] = field(factory=list)
133135
kinesis_encryption_type: Optional[str] = field(default=None)
134136
kinesis_key_id: Optional[str] = field(default=None)
137+
kinesis_policy: Optional[Json] = field(default=None)
135138

136139
@classmethod
137140
def called_collect_apis(cls) -> List[AwsApiSpec]:
138141
return [
139142
cls.api_spec,
140143
AwsApiSpec(service_name, "describe-stream"),
141144
AwsApiSpec(service_name, "list-tags-for-stream"),
145+
AwsApiSpec(service_name, "get-resource-policy"),
142146
]
143147

144148
@classmethod
145149
def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) -> None:
150+
def add_kinesis_policy(kinesis: AwsKinesisStream) -> None:
151+
with builder.suppress(f"{service_name}.get-resource-policy"):
152+
if raw_policy := builder.client.get(
153+
service_name,
154+
"get-resource-policy",
155+
"Policy",
156+
ResourceARN=kinesis.arn,
157+
expected_errors=["AccessDeniedException"],
158+
):
159+
kinesis.kinesis_policy = sort_json(json_loads(raw_policy), sort_list=True) # type: ignore
160+
146161
def add_instance(stream_name: str) -> None:
147162
# this call is paginated and will return a list
148163
stream_descriptions = builder.client.list(
@@ -156,6 +171,7 @@ def add_instance(stream_name: str) -> None:
156171
if stream := AwsKinesisStream.from_api(js, builder):
157172
builder.add_node(stream, js)
158173
builder.submit_work(service_name, add_tags, stream)
174+
builder.submit_work(service_name, add_kinesis_policy, stream)
159175

160176
def add_tags(stream: AwsKinesisStream) -> None:
161177
tags = builder.client.list(stream.api_spec.service, "list-tags-for-stream", "Tags", StreamName=stream.name)

plugins/aws/fix_plugin_aws/resource/sns.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from typing import ClassVar, Dict, List, Optional, Type, Any
33
from attrs import define, field
44

5-
65
from fix_plugin_aws.aws_client import AwsClient
76
from fix_plugin_aws.resource.base import AwsApiSpec, AwsResource, GraphBuilder
87
from fix_plugin_aws.resource.cloudwatch import AwsCloudwatchQuery, normalizer_factory

plugins/aws/test/resources/backup_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test_backup_plans() -> None:
2323

2424

2525
def test_backup_vaults() -> None:
26-
round_trip_for(AwsBackupVault)
26+
round_trip_for(AwsBackupVault, "vault_policy")
2727

2828

2929
def test_backup_recovery_points() -> None:

plugins/aws/test/resources/dynamodb_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88

99
def test_tables() -> None:
10-
first, builder = round_trip_for(AwsDynamoDbTable)
10+
first, builder = round_trip_for(AwsDynamoDbTable, "dynamodb_policy")
1111
assert len(builder.resources_of(AwsDynamoDbTable)) == 1
1212
assert len(first.tags) == 1
1313

1414

1515
def test_tagging_tables() -> None:
16-
table, _ = round_trip_for(AwsDynamoDbTable)
16+
table, _ = round_trip_for(AwsDynamoDbTable, "dynamodb_policy")
1717

1818
def validate_update_args(**kwargs: Any) -> Any:
1919
if kwargs["action"] == "list-tags-of-resource":
@@ -37,7 +37,7 @@ def validate_delete_args(**kwargs: Any) -> Any:
3737

3838

3939
def test_delete_tables() -> None:
40-
table, _ = round_trip_for(AwsDynamoDbTable)
40+
table, _ = round_trip_for(AwsDynamoDbTable, "dynamodb_policy")
4141

4242
def validate_delete_args(**kwargs: Any) -> Any:
4343
assert kwargs["action"] == "delete-table"
@@ -48,12 +48,12 @@ def validate_delete_args(**kwargs: Any) -> Any:
4848

4949

5050
def test_global_tables() -> None:
51-
first, builder = round_trip_for(AwsDynamoDbGlobalTable)
51+
first, builder = round_trip_for(AwsDynamoDbGlobalTable, "dynamodb_policy")
5252
assert len(builder.resources_of(AwsDynamoDbGlobalTable)) == 1
5353

5454

5555
def test_tagging_global_tables() -> None:
56-
table, _ = round_trip_for(AwsDynamoDbGlobalTable)
56+
table, _ = round_trip_for(AwsDynamoDbGlobalTable, "dynamodb_policy")
5757

5858
def validate_update_args(**kwargs: Any) -> Any:
5959
if kwargs["action"] == "list-tags-of-resource":
@@ -77,7 +77,7 @@ def validate_delete_args(**kwargs: Any) -> Any:
7777

7878

7979
def test_delete_global_tables() -> None:
80-
table, _ = round_trip_for(AwsDynamoDbGlobalTable)
80+
table, _ = round_trip_for(AwsDynamoDbGlobalTable, "dynamodb_policy")
8181

8282
def validate_delete_args(**kwargs: Any) -> Any:
8383
assert kwargs["action"] == "delete-table"

plugins/aws/test/resources/ecr_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33

44

55
def test_ecr_repositories() -> None:
6-
first, builder = round_trip_for(AwsEcrRepository)
6+
first, builder = round_trip_for(AwsEcrRepository, "repository_policy")
77
assert len(builder.resources_of(AwsEcrRepository)) == 3

plugins/aws/test/resources/kinesis_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
from fix_plugin_aws.resource.kinesis import AwsKinesisStream
2+
from fix_plugin_aws.aws_client import AwsClient
23
from fixlib.graph import Graph
34
from test.resources import round_trip_for
45
from typing import Any, cast
56
from types import SimpleNamespace
6-
from fix_plugin_aws.aws_client import AwsClient
77

88

99
def test_kinesis_stream() -> None:
10-
res, builder = round_trip_for(AwsKinesisStream)
10+
res, builder = round_trip_for(AwsKinesisStream, "kinesis_policy")
1111
assert len(builder.resources_of(AwsKinesisStream)) == 1
1212
assert len(res.tags) == 1
1313

1414

1515
def test_tagging() -> None:
16-
stream, _ = round_trip_for(AwsKinesisStream)
16+
stream, _ = round_trip_for(AwsKinesisStream, "kinesis_policy")
1717

1818
def validate_update_args(**kwargs: Any) -> None:
1919
assert kwargs["action"] == "add-tags-to-stream"
@@ -33,7 +33,7 @@ def validate_delete_args(**kwargs: Any) -> None:
3333

3434

3535
def test_deletion() -> None:
36-
stream, _ = round_trip_for(AwsKinesisStream)
36+
stream, _ = round_trip_for(AwsKinesisStream, "kinesis_policy")
3737

3838
def validate_delete_args(**kwargs: Any) -> None:
3939
assert kwargs["action"] == "delete-stream"

0 commit comments

Comments
 (0)