Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[resotocore][feat] Towards AWS WAF Security #1867

Merged
merged 11 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
aquamatthias marked this conversation as resolved.
Show resolved Hide resolved
# 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
Loading