Skip to content

Commit

Permalink
[resotocore][feat] Towards AWS WAF Security (#1867)
Browse files Browse the repository at this point in the history
  • Loading branch information
aquamatthias authored Dec 21, 2023
1 parent a48a12e commit c151817
Show file tree
Hide file tree
Showing 33 changed files with 772 additions and 17 deletions.
6 changes: 6 additions & 0 deletions plugins/aws/resoto_plugin_aws/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
service_quotas,
sns,
sqs,
ssm,
ecr,
secretsmanager,
)
from resoto_plugin_aws.resource.base import AwsAccount, AwsApiSpec, AwsRegion, AwsResource, GraphBuilder

Expand Down Expand Up @@ -75,6 +78,7 @@
+ ec2.resources
+ efs.resources
+ ecs.resources
+ ecr.resources
+ eks.resources
+ elasticbeanstalk.resources
+ elasticache.resources
Expand All @@ -85,8 +89,10 @@
+ kms.resources
+ lambda_.resources
+ rds.resources
+ secretsmanager.resources
+ service_quotas.resources
+ sns.resources
+ ssm.resources
+ sqs.resources
+ redshift.resources
)
Expand Down
6 changes: 6 additions & 0 deletions plugins/aws/resoto_plugin_aws/resource/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) ->
# In case additional work needs to be done, override this method.
for js in json:
if instance := cls.from_api(js, builder):
# post process
instance.post_process(builder, js)
builder.add_node(instance, js)

@classmethod
Expand All @@ -234,6 +236,10 @@ def called_collect_apis(cls) -> List[AwsApiSpec]:
def called_mutator_apis(cls) -> List[AwsApiSpec]:
return []

def post_process(self, builder: GraphBuilder, source: Json) -> None:
# Default behavior: do nothing
pass

def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None:
# Default behavior: add resource to the namespace
pass
Expand Down
25 changes: 23 additions & 2 deletions plugins/aws/resoto_plugin_aws/resource/cloudformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from resoto_plugin_aws.aws_client import AwsClient
from resoto_plugin_aws.resource.base import AwsResource, AwsApiSpec, GraphBuilder
from resoto_plugin_aws.utils import ToDict
from resotolib.baseresources import BaseStack
from resotolib.baseresources import BaseStack, ModelReference
from resotolib.graph import ByNodeId, BySearchCriteria, Graph
from resotolib.json_bender import Bender, S, Bend, ForallBend, F
from resotolib.types import Json
Expand Down Expand Up @@ -95,6 +95,7 @@ class AwsCloudFormationStack(AwsResource, BaseStack):
" updated, or deleted together as a single unit."
)
api_spec: ClassVar[AwsApiSpec] = AwsApiSpec(service_name, "describe-stacks", "Stacks")
reference_kinds: ClassVar[ModelReference] = {"successors": {"default": ["aws_resource"]}}
mapping: ClassVar[Dict[str, Bender]] = {
"id": S("StackId"),
"tags": S("Tags", default=[]) >> ToDict(),
Expand Down Expand Up @@ -134,6 +135,26 @@ class AwsCloudFormationStack(AwsResource, BaseStack):
stack_parent_id: Optional[str] = field(default=None)
stack_root_id: Optional[str] = field(default=None)
stack_drift_information: Optional[AwsCloudFormationStackDriftInformation] = field(default=None)
_stack_resources: Optional[List[Json]] = None

def post_process(self, builder: GraphBuilder, source: Json) -> None:
def stack_resources() -> None:
# list all stack resources - we will create edges in connect_in_graph
self._stack_resources = builder.client.list(
service_name, "list-stack-resources", "StackResourceSummaries", StackName=self.name
)

builder.submit_work(service_name, stack_resources)

def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None:
if self._stack_resources:
for resource in self._stack_resources:
if (rid := resource.get("PhysicalResourceId")) and (rt := resource.get("ResourceType")):
# we translate the resource type to the internal kind: AWS::IAM::User --> aws_iam_user
# there are a lot of exceptions in AWS, that we need to handle.
kind = rt.replace("::", "_").lower()
# what cloudformation calls "PhysicalResourceId" is the name of the resource
builder.add_edge(self, name=rid, kind=kind)

def _modify_tag(self, client: AwsClient, key: str, value: Optional[str], mode: Literal["delete", "update"]) -> bool:
tags = dict(self.tags)
Expand Down Expand Up @@ -202,7 +223,7 @@ def delete_resource(self, client: AwsClient, graph: Graph) -> bool:

@classmethod
def called_collect_apis(cls) -> List[AwsApiSpec]:
return [cls.api_spec, AwsApiSpec(service_name, "list-stacks")]
return [cls.api_spec, AwsApiSpec(service_name, "list-stacks"), AwsApiSpec(service_name, "list-stack-resources")]

@classmethod
def called_mutator_apis(cls) -> List[AwsApiSpec]:
Expand Down
20 changes: 19 additions & 1 deletion plugins/aws/resoto_plugin_aws/resource/ec2.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import base64
import logging
from datetime import datetime
from typing import ClassVar, Dict, Optional, List, Type, Any
Expand Down Expand Up @@ -1341,13 +1342,27 @@ class AwsEc2Instance(EC2Taggable, AwsResource, BaseInstance):
instance_ipv6_address: Optional[str] = field(default=None)
instance_tpm_support: Optional[str] = field(default=None)
instance_maintenance_options: Optional[str] = field(default=None)
instance_user_data: Optional[str] = field(default=None)

@classmethod
def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) -> None:
def fetch_user_data(instance: AwsEc2Instance) -> None:
if (
result := builder.client.get(
service_name,
"describe-instance-attribute",
"UserData",
InstanceId=instance.id,
Attribute="userData",
)
) and (data := result.get("Value")):
instance.instance_user_data = base64.b64decode(data).decode("utf-8")

for reservation in json:
for instance_in in reservation["Instances"]:
mapped = bend(cls.mapping, instance_in)
instance = AwsEc2Instance.from_json(mapped)
builder.submit_work(service_name, fetch_user_data, instance)
builder.add_node(instance, instance_in)

@classmethod
Expand Down Expand Up @@ -1465,7 +1480,10 @@ def delete_resource(self, client: AwsClient, graph: Graph) -> bool:

@classmethod
def called_mutator_apis(cls) -> List[AwsApiSpec]:
return super().called_mutator_apis() + [AwsApiSpec(service_name, "terminate-instances")]
return super().called_mutator_apis() + [
AwsApiSpec(service_name, "terminate-instances"),
AwsApiSpec(service_name, "describe-instance-attribute"),
]


# endregion
Expand Down
73 changes: 73 additions & 0 deletions plugins/aws/resoto_plugin_aws/resource/ecr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import ClassVar, Dict, Optional, List, Type

from attrs import define, field

from resoto_plugin_aws.resource.base import AwsResource, AwsApiSpec
from resoto_plugin_aws.utils import ToDict
from resotolib.json_bender import Bender, S, Bend

service_name = "ecs"


@define(eq=False, slots=False)
class AwsEcrEncryptionConfiguration:
kind: ClassVar[str] = "aws_ecr_encryption_configuration"
mapping: ClassVar[Dict[str, Bender]] = {"encryption_type": S("encryptionType"), "kms_key": S("kmsKey")}
encryption_type: Optional[str] = field(default=None, metadata={"description": "The encryption type to use. If you use the KMS encryption type, the contents of the repository will be encrypted using server-side encryption with Key Management Service key stored in KMS. When you use KMS to encrypt your data, you can either use the default Amazon Web Services managed KMS key for Amazon ECR, or specify your own KMS key, which you already created. For more information, see Protecting data using server-side encryption with an KMS key stored in Key Management Service (SSE-KMS) in the Amazon Simple Storage Service Console Developer Guide. If you use the AES256 encryption type, Amazon ECR uses server-side encryption with Amazon S3-managed encryption keys which encrypts the images in the repository using an AES-256 encryption algorithm. For more information, see Protecting data using server-side encryption with Amazon S3-managed encryption keys (SSE-S3) in the Amazon Simple Storage Service Console Developer Guide."}) # fmt: skip
kms_key: Optional[str] = field(default=None, metadata={"description": "If you use the KMS encryption type, specify the KMS key to use for encryption. The alias, key ID, or full ARN of the KMS key can be specified. The key must exist in the same Region as the repository. If no key is specified, the default Amazon Web Services managed KMS key for Amazon ECR will be used."}) # fmt: skip


@define(eq=False, slots=False)
class AwsEcrRepository(AwsResource):
kind: ClassVar[str] = "aws_ecr_repository"
api_spec: ClassVar[AwsApiSpec] = AwsApiSpec("ecr", "describe-repositories", "repositories")
mapping: ClassVar[Dict[str, Bender]] = {
"id": S("repositoryName"),
"tags": S("Tags", default=[]) >> ToDict(),
"name": S("repositoryName"),
"ctime": S("createdAt"),
"repository_arn": S("repositoryArn"),
"registry_id": S("registryId"),
"repository_uri": S("repositoryUri"),
"image_tag_mutability": S("imageTagMutability"),
"image_scan_on_push": S("imageScanningConfiguration", "scanOnPush"),
"encryption_configuration": S("encryptionConfiguration") >> Bend(AwsEcrEncryptionConfiguration.mapping),
}
repository_arn: Optional[str] = field(default=None, metadata={"description": "The Amazon Resource Name (ARN) that identifies the repository. The ARN contains the arn:aws:ecr namespace, followed by the region of the repository, Amazon Web Services account ID of the repository owner, repository namespace, and repository name. For example, arn:aws:ecr:region:012345678910:repository-namespace/repository-name."}) # fmt: skip
registry_id: Optional[str] = field(default=None, metadata={"description": "The Amazon Web Services account ID associated with the registry that contains the repository."}) # fmt: skip
repository_uri: Optional[str] = field(default=None, metadata={"description": "The URI for the repository. You can use this URI for container image push and pull operations."}) # fmt: skip
image_tag_mutability: Optional[str] = field(default=None, metadata={"description": "The tag mutability setting for the repository."}) # fmt: skip
image_scan_on_push: Optional[bool] = field(default=None, metadata={"description": "The image is scanned on every push."}) # fmt: skip
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


# @define(eq=False, slots=False)
# class AwsEcrImageIdentifier:
# kind: ClassVar[str] = "aws_ecr_image_identifier"
# mapping: ClassVar[Dict[str, Bender]] = {"image_digest": S("imageDigest"), "image_tag": S("imageTag")}
# image_digest: Optional[str] = field(default=None, metadata={"description": "The sha256 digest of the image manifest."}) # fmt: skip
# image_tag: Optional[str] = field(default=None, metadata={"description": "The tag used for the image."}) # fmt: skip
#
#
# @define(eq=False, slots=False)
# class AwsEcrImage(AwsResource):
# kind: ClassVar[str] = "aws_ecr_image"
# api_spec: ClassVar[AwsApiSpec] = AwsApiSpec("ecr", "describe-images", "images")
# mapping: ClassVar[Dict[str, Bender]] = {
# "id": S("id"),
# "tags": S("Tags", default=[]) >> ToDict(),
# "name": S("Tags", default=[]) >> TagsValue("Name"),
# "registry_id": S("registryId"),
# "repository_name": S("repositoryName"),
# "image_id": S("imageId") >> Bend(AwsEcrImageIdentifier.mapping),
# "image_manifest": S("imageManifest"),
# "image_manifest_media_type": S("imageManifestMediaType"),
# }
# registry_id: Optional[str] = field(default=None, metadata={"description": "The Amazon Web Services account ID associated with the registry containing the image."}) # fmt: skip
# repository_name: Optional[str] = field(default=None, metadata={"description": "The name of the repository associated with the image."}) # fmt: skip
# image_id: Optional[AwsEcrImageIdentifier] = field(default=None, metadata={"description": "An object containing the image tag and image digest associated with an image."}) # fmt: skip
# image_manifest: Optional[str] = field(default=None, metadata={"description": "The image manifest associated with the image."}) # fmt: skip
# image_manifest_media_type: Optional[str] = field(default=None, metadata={"description": "The manifest media type of the image."}) # fmt: skip


resources: List[Type[AwsResource]] = [AwsEcrRepository]
Loading

0 comments on commit c151817

Please sign in to comment.