VPC endpoint policy allows access only from trusted AWS accounts
VPC endpoint policies are assessed for restriction to configured trusted AWS accounts. If Principal values (including *) or account ARNs permit non-trusted principals, or conditions aren't sufficiently restrictive, the endpoint is identified. Endpoints without editable policies are excluded.
Risk
Non-trusted principals using your endpoint can access AWS services as if from your VPC, weakening segmentation. This enables unauthorized reads/writes and data exfiltration from resources tied to the endpoint, harming confidentiality and integrity, and potentially increasing costs.
Run this check with Prowler
Run in Prowler Cloudprowler aws --checks vpc_endpoint_connections_trust_boundariesRecommendation
Apply least privilege: restrict endpoint policies to your account and an explicit allowlist of trusted accounts. Avoid * principals unless coupled with strict conditions. Prevent transitive trust across network links, and use resource policies and monitoring as defense in depth to limit endpoint use.
Remediation
CLI
aws ec2 modify-vpc-endpoint --vpc-endpoint-id <example_resource_id> --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"*","Resource":"*","Condition":{"StringEquals":{"aws:PrincipalAccount":["<TRUSTED_ACCOUNT_ID_1>","<TRUSTED_ACCOUNT_ID_2>"]}}}]}'Terraform
# Add this to the existing VPC endpoint resource to restrict access
resource "aws_vpc_endpoint" "<example_resource_name>" {
# ...existing required arguments for the endpoint...
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = "*"
Action = "*"
Resource = "*"
Condition = { # CRITICAL: restrict by trusted accounts
StringEquals = {
"aws:PrincipalAccount" = [ # CRITICAL: only these accounts can use the endpoint
"<TRUSTED_ACCOUNT_ID_1>",
"<TRUSTED_ACCOUNT_ID_2>"
]
}
}
}]
})
}NativeIaC
# CloudFormation: restrict VPC endpoint access to trusted accounts only
Resources:
<example_resource_name>:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: <example_resource_id>
ServiceName: com.amazonaws.<region>.<service>
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal: "*"
Action: "*"
Resource: "*"
Condition: # CRITICAL: restrict by trusted accounts
StringEquals: # CRITICAL: only allow specified accounts
"aws:PrincipalAccount": # CRITICAL: limits usage to these accounts
- "<TRUSTED_ACCOUNT_ID_1>"
- "<TRUSTED_ACCOUNT_ID_2>"Other
Check Code
from re import compile
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.lib.policy import is_condition_block_restrictive
from prowler.providers.aws.services.vpc.vpc_client import vpc_client
class vpc_endpoint_connections_trust_boundaries(Check):
def execute(self):
findings = []
# Get trusted account_ids from prowler.config.yaml
trusted_account_ids = vpc_client.audit_config.get("trusted_account_ids", [])
# Always include the same account as trusted
trusted_account_ids.append(vpc_client.audited_account)
for endpoint in vpc_client.vpc_endpoints:
# Check VPC endpoint policy and avoid "com.amazonaws.vpce" endpoints since the policy cannot be modified
if (
endpoint.policy_document
and "com.amazonaws.vpce." not in endpoint.service_name
):
access_from_trusted_accounts = True
for statement in endpoint.policy_document["Statement"]:
# If one policy allows access from a non-trusted account
if not access_from_trusted_accounts:
break
if "*" == statement["Principal"]:
access_from_trusted_accounts = False
report = Check_Report_AWS(
metadata=self.metadata(), resource=endpoint
)
if "Condition" in statement:
for account_id in trusted_account_ids:
if is_condition_block_restrictive(
statement["Condition"], account_id
):
access_from_trusted_accounts = True
else:
access_from_trusted_accounts = False
break
if not access_from_trusted_accounts:
report.status = "FAIL"
report.status_extended = f"VPC Endpoint {endpoint.id} in VPC {endpoint.vpc_id} can be accessed from non-trusted accounts."
else:
report.status = "PASS"
report.status_extended = f"VPC Endpoint {endpoint.id} in VPC {endpoint.vpc_id} can only be accessed from trusted accounts."
findings.append(report)
if not access_from_trusted_accounts:
break
else:
if "AWS" in statement["Principal"]:
if isinstance(statement["Principal"]["AWS"], str):
principals = [statement["Principal"]["AWS"]]
else:
principals = statement["Principal"]["AWS"]
else:
# If the principal is not an AWS principal, we don't need to check it since it could be a service or a federated principal
principals = []
for principal_arn in principals:
report = Check_Report_AWS(
metadata=self.metadata(), resource=endpoint
)
if principal_arn == "*":
access_from_trusted_accounts = False
if "Condition" in statement:
for account_id in trusted_account_ids:
if is_condition_block_restrictive(
statement["Condition"], account_id
):
access_from_trusted_accounts = True
else:
access_from_trusted_accounts = False
break
if not access_from_trusted_accounts:
report.status = "FAIL"
report.status_extended = f"VPC Endpoint {endpoint.id} in VPC {endpoint.vpc_id} can be accessed from non-trusted accounts."
else:
report.status = "PASS"
report.status_extended = f"VPC Endpoint {endpoint.id} in VPC {endpoint.vpc_id} can only be accessed from trusted accounts."
findings.append(report)
if not access_from_trusted_accounts:
break
else:
# Account ID can be an ARN or just a 12-digit string
pattern = compile(r"^[0-9]{12}$")
match = pattern.match(principal_arn)
if not match:
account_id = principal_arn.split(":")[4]
else:
account_id = match.string
if account_id not in trusted_account_ids:
access_from_trusted_accounts = False
if "Condition" in statement:
for account_id in trusted_account_ids:
if is_condition_block_restrictive(
statement["Condition"], account_id
):
access_from_trusted_accounts = True
else:
access_from_trusted_accounts = False
break
if not access_from_trusted_accounts:
report.status = "FAIL"
report.status_extended = f"VPC Endpoint {endpoint.id} in VPC {endpoint.vpc_id} can be accessed from non-trusted accounts."
else:
report.status = "PASS"
report.status_extended = f"VPC Endpoint {endpoint.id} in VPC {endpoint.vpc_id} can only be accessed from trusted accounts."
findings.append(report)
if not access_from_trusted_accounts:
break
return findings