diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aba890..64b9221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ CHANGELOG AWS-TOWER ----- +4.2.2 +----- + +2022/08/24 + +### New feature + - Add 'Endpoint Service has untrusted account in principals' rule + - Add option `-p|--list-profiles` to list available profiles + +### Changes + - Detect SSH keys issues if creation time > 6 months + - Remove `-n|--name` option + +### Fixtures + - Handle boto3 errors with pretty output (sso and more) + 4.2.1 ----- diff --git a/README.md b/README.md index ac2e7db..e019671 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ positional arguments: audit Audit AWS account to find security issues iam Display IAM info for an AWS account -optional arguments: +options: -h, --help show this help message and exit --version show program's version number and exit --no-color Disable colors @@ -57,7 +57,7 @@ usage: aws_tower_cli.py audit [-h] [-t {APIGW,CLOUDFRONT,EC2,EKS,ELB,IAM,RDS,S3, positional arguments: profile A valid profile name configured in the ~/.aws/config file -optional arguments: +options: -h, --help show this help message and exit -t {APIGW,CLOUDFRONT,EC2,EKS,ELB,IAM,RDS,S3,VPC}, --type {APIGW,CLOUDFRONT,EC2,EKS,ELB,IAM,RDS,S3,VPC} Types to display (default: display everything) @@ -79,7 +79,7 @@ usage: aws_tower_cli.py discover [-h] [-t {APIGW,CLOUDFRONT,EC2,EKS,ELB,IAM,RDS, positional arguments: profile A valid profile name configured in the ~/.aws/config file -optional arguments: +options: -h, --help show this help message and exit -t {APIGW,CLOUDFRONT,EC2,EKS,ELB,IAM,RDS,S3,VPC}, --type {APIGW,CLOUDFRONT,EC2,EKS,ELB,IAM,RDS,S3,VPC} Types to display (default: display everything) @@ -98,7 +98,7 @@ usage: aws_tower_cli.py iam [-h] [-s SOURCE] [-a ACTION] [--min-rights {admin,po positional arguments: profile A valid profile name configured in the ~/.aws/config file -optional arguments: +options: -h, --help show this help message and exit -s SOURCE, --source SOURCE Source arn diff --git a/aws_tower_cli.py b/aws_tower_cli.py index d00c413..56e0032 100755 --- a/aws_tower_cli.py +++ b/aws_tower_cli.py @@ -28,7 +28,7 @@ # from pdb import set_trace as st CONSOLE = console.Console() -VERSION = '4.2.1' +VERSION = '4.2.2' def audit_handler(session, args, meta_types, cache): """ diff --git a/config/rules.yaml.sample b/config/rules.yaml.sample index 49b1a78..311cdb6 100644 --- a/config/rules.yaml.sample +++ b/config/rules.yaml.sample @@ -1,5 +1,5 @@ --- -version: 4.2.1-1 +version: 4.2.2 types: security_group: description: Check each rule on each security group and on each source @@ -1086,6 +1086,44 @@ types: - type: attribute name: attribute_value value: public + - message: + text: '[{service_name}] Endpoint Service has untrusted account in principals' + args: + service_name: + type: attribute + key: name + severity: high + rules: + - type: has_attribute_equal + description: Check if the asset is an VPC + conditions: + - type: constant + name: attribute_value + value: VPC + data_sources: + - type: attribute + name: attribute_value + value: aws_service + - type: has_attribute_equal + description: Check if is_endpoint_service is True + conditions: + - type: constant + name: attribute_value + value: true + data_sources: + - type: attribute + name: attribute_value + value: is_endpoint_service + - type: has_attribute_equal + description: Check if has_untrusted_accounts is True + conditions: + - type: constant + name: attribute_value + value: true + data_sources: + - type: attribute + name: attribute_value + value: has_untrusted_accounts - message: text: '[{service_name}] VPN endpoint' args: diff --git a/libs/asset_type_vpc.py b/libs/asset_type_vpc.py index ab97052..f4d7c72 100644 --- a/libs/asset_type_vpc.py +++ b/libs/asset_type_vpc.py @@ -12,7 +12,7 @@ from config import variables from .asset_type import AssetType -from .tools import get_tag, log_me, search_filter_in +from .tools import get_account_in_arn, get_tag, log_me, search_filter_in # Debug # from pdb import set_trace as st @@ -38,6 +38,8 @@ def __init__( self.dst_region_id = '' # VPC Endpoint Services self.is_endpoint_service = is_endpoint_service + self.has_untrusted_accounts = False + self.untrusted_accounts = set() # VPC VPN Endpoints self.is_vpn = is_vpn self.endpoint = 'unknown' @@ -56,6 +58,8 @@ def report(self, report, brief=False): f'{self.src_account_id}:{self.src_region_id} <-> {self.dst_account_id}:{self.dst_region_id}' elif self.is_endpoint_service and self.public: asset_report['Public'] = '[red]True[/red]' + elif self.is_endpoint_service and self.has_untrusted_accounts: + asset_report['Untrusted Accounts'] = f'[red]{list(self.untrusted_accounts)}[/red]' elif self.is_vpn: asset_report['Endpoint'] = self.endpoint asset_report['Port'] = self.port @@ -78,6 +82,8 @@ def report_brief(self): message = '' if self.public: message = '[red][/red]' + if self.has_untrusted_accounts: + message += ' [red]Untrusted Accounts[/red]' elif self.is_vpn: message = f'{self.port} {self.endpoint}' return f'{message}{self.display_brief_audit()}' @@ -177,6 +183,11 @@ def parse_raw_data(assets, authorizations, raw_data, name_filter, public_only, c asset_name = f'endpoint_service:{vpc_endpoint_service["ServiceId"]}' asset = VPC(name=asset_name, is_endpoint_service=True) asset.public = True in [ '*' in i['Principal'] for i in raw_data['vpc_endpoint_services_perm_raw'][vpc_endpoint_service['ServiceId']]['AllowedPrincipals']] + for allowed_principal in raw_data['vpc_endpoint_services_perm_raw'][vpc_endpoint_service['ServiceId']]['AllowedPrincipals']: + aws_account_id = get_account_in_arn(allowed_principal['Principal']) + if aws_account_id not in trusted_accounts_list: + asset.untrusted_accounts.add(aws_account_id) + asset.has_untrusted_accounts = asset.untrusted_accounts != set() cache.save_asset(f'VPC_ES_{vpc_endpoint_service["ServiceId"]}', asset) if public_only and not asset.public: asset = None diff --git a/libs/tools.py b/libs/tools.py index d9fb979..572c15c 100644 --- a/libs/tools.py +++ b/libs/tools.py @@ -157,6 +157,14 @@ def generate_layer(rules_path): t['score'] += 1 print(json.dumps(layer)) +def get_account_in_arn(arn): + """ + Extracts the aws account id in the arn, if exists + """ + if len(arn.split(':')) >= 5: + return arn.split(':')[4] + return '000000000000' + def get_network(subnet_id, subnets_raw): """ Get simple name for vpc and subnet