diff --git a/README.md b/README.md index b013b52..beac479 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,15 @@ Comparison based on AWS Documentation [1](https://docs.aws.amazon.com/IAM/latest | :--: | :--: | :--: | | S3 Bucket| :white_check_mark: | :white_check_mark: | | S3 Directory Buckets | :x: | :white_check_mark: | -| S3 Bucket ACLs | :white_check_mark: | :white_check_mark | -| S3 Glacier | :x: | :x: | +| S3 Access Points | :x: | :white_check_mark: | +| S3 Bucket ACLs | :white_check_mark: | :white_check_mark: | +| S3 Glacier | :white_check_mark: | :x: | | IAM | :white_check_mark: | :white_check_mark: | | KMS | :white_check_mark: | :white_check_mark: | | Secrets Manager | :white_check_mark: | :white_check_mark: | -| Lambda | :x: | :white_check_mark: | +| Lambda | :white_check_mark: | :white_check_mark: | | SNS | :x: | :white_check_mark: | -| SQS | :x: | :white_check_mark: | +| SQS | :white_check_mark: | :white_check_mark: | | RDS Snapshots | :x: | :white_check_mark: | | RDS Cluster Snapshots | :x: | :white_check_mark: | | ECR | :x: | :white_check_mark: | @@ -52,7 +53,7 @@ Comparison based on AWS Documentation [1](https://docs.aws.amazon.com/IAM/latest | DynamoDB streams | :x: | :white_check_mark: | | DynamoDB tables | :x: | :white_check_mark: | | EBS Snapshots | :x: | :white_check_mark: | -| EventBridge | :x: | :x: | +| EventBridge | :white_check_mark: | :x: | | EventBridge Schema | :x: | :x: | | Mediastore | :x: | :x: | | Glue | :x: | :x: | @@ -66,7 +67,7 @@ Comparison based on AWS Documentation [1](https://docs.aws.amazon.com/IAM/latest | SES v2 | :x: | :x: | | Incident Manager | :x: | :x: | | Incident Manager Contacts | :x: | :x: | - +| VPC endpoints | :x: | :x: | ## How to run diff --git a/awsxenos/config.yaml b/awsxenos/config.yaml index 2644441..f6cf4a1 100644 --- a/awsxenos/config.yaml +++ b/awsxenos/config.yaml @@ -1,11 +1,19 @@ plugins: - - module: .s3 + - module: s3 class: S3 - - module: .s3 + - module: s3 class: S3ACL - - module: .iam + - module: s3 + class: S3Glacier + - module: iam class: IAM - - module: .kms + - module: kms class: KMS - - module: .secretsmanager + - module: secretsmanager class: SecretsManager + - module: eventbridge + class: EventBus + - module: lambda + class: LambdaResource + - module: sqs + class: SQS \ No newline at end of file diff --git a/awsxenos/scan.py b/awsxenos/scan.py index 537460d..3a49223 100755 --- a/awsxenos/scan.py +++ b/awsxenos/scan.py @@ -87,7 +87,8 @@ def get_all_accounts(self) -> Accounts: def load_fetch(module_path: str, class_name: str) -> Callable: """Dynamically load "fetch"" from a given file/module and class""" - module = importlib.import_module(module_path, package="awsxenos") + path = f".services.{module_path}" + module = importlib.import_module(path, package="awsxenos") cls = getattr(module, class_name) instance = cls() fn = getattr(instance, "fetch") @@ -118,6 +119,7 @@ def load_and_run(config_file, accounts) -> Findings: results.update(future.result()) except Exception as e: # TODO: Better handling, add logger + print(e) results[name] = str(e) # Store the exception if the function call fails return results @@ -136,9 +138,8 @@ def cli(): "-c", "--config", dest="config", - action="store_false", - default="config.yaml", - help="Include service roles in the report", + action="store", + help="Config location", ) parser.add_argument( "-w", @@ -152,12 +153,12 @@ def cli(): reporttype = args.reporttype write_output = args.write_output - prescan = PreScan() if not args.config: config_path = f"{package_path.resolve().parent}/config.yaml" else: config_path = args.config + prescan = PreScan() results = load_and_run(config_path, prescan.accounts) r = Report(results, prescan.known_accounts) diff --git a/awsxenos/services/__init__.py b/awsxenos/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/awsxenos/services/eventbridge.py b/awsxenos/services/eventbridge.py new file mode 100644 index 0000000..878bb72 --- /dev/null +++ b/awsxenos/services/eventbridge.py @@ -0,0 +1,22 @@ +import json +from typing import DefaultDict, Set + +import boto3 # type: ignore + +from awsxenos.finding import Findings, Resources, Service + +"""EventBridge Bus Resource Policies""" + + +class EventBus(Service): + + def fetch(self, accounts: DefaultDict[str, Set]) -> Findings: # type: ignore + return super().collate(accounts, self.get_eb_policies()) + + def get_eb_policies(self) -> Resources: + buses = Resources() + eb = boto3.client("events") + for bus in eb.list_event_buses(): + if "Policy" in bus: + buses[bus["Arn"]] = json.loads(bus["Policy"]) + return buses diff --git a/awsxenos/iam.py b/awsxenos/services/iam.py similarity index 100% rename from awsxenos/iam.py rename to awsxenos/services/iam.py diff --git a/awsxenos/kms.py b/awsxenos/services/kms.py similarity index 100% rename from awsxenos/kms.py rename to awsxenos/services/kms.py diff --git a/awsxenos/services/lambda.py b/awsxenos/services/lambda.py new file mode 100644 index 0000000..14232ac --- /dev/null +++ b/awsxenos/services/lambda.py @@ -0,0 +1,40 @@ +import json +from typing import DefaultDict, Set + +import boto3 # type: ignore +from botocore.exceptions import ClientError # type: ignore + +from awsxenos.finding import Findings, Resources, Service + +"""Lambda Resource Policies""" + + +class LambdaResource(Service): + + def fetch(self, accounts: DefaultDict[str, Set]) -> Findings: # type: ignore + return super().collate(accounts, self.get_lambda_policies()) + + def get_lambda_policies(self) -> Resources: + lambdas = Resources() + lam = boto3.client("lambda") + paginator = lam.get_paginator("list_functions") + for lam_resp in paginator.paginate(): + for func in lam_resp["Functions"]: + try: + lambdas[func["FunctionArn"]] = json.loads( + lam.get_policy(FunctionName=func["FunctionName"])["Policy"] + ) + except ClientError as err: + lambdas[func["FunctionArn"]] = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": f"{err}", + "Effect": "Allow", + "Principal": {"AWS": ["arn:aws:iam::111122223333:root"]}, + "Action": ["lambda:*"], + "Resource": f'{func["FunctionArn"]}', + } + ], + } + return lambdas diff --git a/awsxenos/s3.py b/awsxenos/services/s3.py similarity index 87% rename from awsxenos/s3.py rename to awsxenos/services/s3.py index 6492a5e..fc1c350 100644 --- a/awsxenos/s3.py +++ b/awsxenos/services/s3.py @@ -128,3 +128,24 @@ def get_acls(self) -> Resources: print(e) continue return bucket_acls + + +"""S3 Glacier Vault Policies""" + + +class S3Glacier(Service): + + def fetch(self, accounts: DefaultDict[str, Set]) -> Findings: # type: ignore + return super().collate(accounts, self.get_vault_policies()) + + def get_vault_policies(self) -> Resources: + vaults = Resources() + glacier = boto3.client("glacier") + paginator = glacier.get_paginator("list_vaults") + glacier_iterator = paginator.paginate() + for glacier_resp in glacier_iterator: + for vault in glacier_resp["VaultList"]: + vaults[vault["VaultARN"]] = json.loads( + glacier.get_vault_access_policy(vaultName=vault["VaultName"])["policy"]["Policy"] + ) + return vaults diff --git a/awsxenos/secretsmanager.py b/awsxenos/services/secretsmanager.py similarity index 100% rename from awsxenos/secretsmanager.py rename to awsxenos/services/secretsmanager.py diff --git a/awsxenos/services/sqs.py b/awsxenos/services/sqs.py new file mode 100644 index 0000000..c1306ea --- /dev/null +++ b/awsxenos/services/sqs.py @@ -0,0 +1,33 @@ +import json +from typing import DefaultDict, Optional, Set + +import boto3 # type: ignore + +from awsxenos.finding import Findings, Resources, Service + +"""SQS Access/Resource Policy""" + + +class SQS(Service): + + def fetch( # type: ignore + self, + accounts: DefaultDict[str, Set], + exclude_service: Optional[bool] = True, + exclude_aws: Optional[bool] = True, + ) -> Findings: + return super().collate(accounts, self.get_sqs_policies(exclude_service, exclude_aws)) + + def get_sqs_policies(self, exclude_service: Optional[bool] = True, exclude_aws: Optional[bool] = True) -> Resources: + queues = Resources() + sqs = boto3.client("sqs") + paginator = sqs.get_paginator("list_queues") + for sqs_resp in paginator.paginate(): + for queue in sqs_resp["QueueUrls"]: + queues[queue["QueueUrl"]] = json.loads( + sqs.get_queue_attributes(QueueUrl=queue["QueueUrl"], AttributeNames=["Policy"])["Attributes"][ + "Policy" + ] + ) + + return queues diff --git a/requirements.txt b/requirements.txt index 4689504..f73e569 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ policyuniverse==1.5.1.20231109 boto3==1.34.101 -jinja2==3.0.1 +jinja2==3.1.3 pyyaml==6.0.1