AWS
Azure
GCP
Security
Tutorials
How to Find Internet-Facing Cloud Assets in AWS, Azure and GCP
You have 1,247 resources running across AWS, 834 in Azure, and 423 in GCP. Which ones are accessible from the internet right now?
Most teams can't answer this question accurately. The reality is worse - that EC2 instance someone spun up for testing three months ago still has a public IP and a security group allowing SSH from 0.0.0.0/0. The Azure storage account created for a proof of concept? Still has public blob access enabled. The GCP Cloud SQL instance? Public IP with authorized networks set to allow any IP address.
Internet-facing assets are your primary attack surface. Data breaches often start with exposed resources that nobody knew were public. A misconfigured S3 bucket, a VM with an overly permissive security group, or a database with a public endpoint - these are the entry points attackers look for first.
This guide shows you how to use CloudQuery to identify every internet-facing resource across AWS, Azure, and GCP. You'll get runnable SQL queries that check compute instances, storage, databases, and load balancers. By the end, you'll have complete visibility into your public attack surface across all three major cloud providers.
What Makes a Cloud Asset Internet-Facing? #
An internet-facing asset is any cloud resource that can be accessed directly from the public internet. This means the resource has both a public IP address or endpoint AND permissive network security controls that allow inbound traffic from outside your network.
Having just a public IP doesn't make a resource internet-facing. An EC2 instance with an Elastic IP but security groups that block all inbound traffic isn't accessible from the internet. The combination matters - public accessibility plus permissive rules equals exposure.
Common internet-facing patterns include:
Compute resources: VMs or instances with public IP addresses and security groups/NSGs/firewall rules that allow traffic from 0.0.0.0/0 or specific ports like 22 (SSH), 3389 (RDP), or 80/443 (HTTP/HTTPS).
Storage: S3 buckets with public read access, Azure Blob Storage with public container access, or GCP Cloud Storage buckets with
allUsers or allAuthenticatedUsers IAM bindings.Databases: RDS instances, Azure SQL databases, or Cloud SQL instances configured with public endpoints and overly broad authorized network ranges.
Load balancers: Application load balancers, Azure Load Balancers, or GCP load balancers with internet-facing schemes that accept traffic from any source.
The risk comes from unintended exposure. A developer creates a test database with a public endpoint for quick access. A storage bucket gets set to public during a demo. An old security group rule allowing 0.0.0.0/0 never gets cleaned up. These misconfigurations accumulate over time, expanding your attack surface without anyone noticing.
Cloud Provider Terminology Comparison #
Each cloud provider uses different terminology for the same concepts. AWS calls them security groups, Azure calls them NSGs, and GCP calls them firewall rules - but they all control network access to your resources.
Here's how terminology maps across providers:
Understanding these differences helps when you're writing queries across multiple clouds. CloudQuery normalizes the data structure, but the underlying concepts from each provider show through in table names and field names.
How CloudQuery Helps Find Internet-Facing Assets #
CloudQuery syncs infrastructure configuration data from cloud provider APIs into a queryable PostgreSQL-compatible database. Instead of writing separate scripts for AWS CLI, Azure CLI, and gcloud, you write SQL queries that work across all three.
When you run a CloudQuery sync, it calls APIs like EC2's DescribeInstances, Azure's VirtualMachines List, and GCP's instances.list. The data gets stored in tables like
aws_ec2_instances, azure_compute_virtual_machines, and gcp_compute_instances.You can use CloudQuery with the CloudQuery Platform (managed service) or run the CloudQuery CLI locally. Both approaches give you the same SQL interface and support for 100+ source integrations.
For this guide, we'll query data from the AWS Source plugin, Azure Source plugin, and GCP Source plugin.
Prerequisites #
Before running these queries, you need:
- CloudQuery account or CLI installed
- Read-only access to your AWS, Azure, and GCP environments
- Basic SQL query knowledge
For authentication setup:
You'll also need to configure your syncs to include the specific tables we query in each section.
Finding Internet-Facing Assets in AWS #
Configure your AWS sync to include these tables:
EC2 Instances with Public IPs and Permissive Security Groups #
This query finds EC2 instances that have public IP addresses and security groups allowing inbound traffic from anywhere:
SELECT
i.instance_id,
i.public_ip_address,
i.region,
sg.group_id,
sg.group_name,
perm->>'FromPort' AS from_port,
perm->>'ToPort' AS to_port,
perm->>'IpProtocol' AS protocol,
ip_range->>'CidrIp' AS cidr
FROM aws_ec2_instances AS i
CROSS JOIN JSONB_ARRAY_ELEMENTS(i.security_groups) AS instance_sg
JOIN aws_ec2_security_groups AS sg
ON sg.group_id = instance_sg->>'GroupId'
CROSS JOIN JSONB_ARRAY_ELEMENTS(sg.ip_permissions) AS perm
CROSS JOIN JSONB_ARRAY_ELEMENTS(perm->'IpRanges') AS ip_range
WHERE i.public_ip_address IS NOT NULL
AND (ip_range->>'CidrIp' = '0.0.0.0/0' OR ip_range->>'CidrIp' = '::/0');
This query joins instances with their security groups, then checks for rules allowing traffic from 0.0.0.0/0 (all IPv4) or ::/0 (all IPv6). Results show which instances are reachable from the internet and on which ports.
Publicly Accessible S3 Buckets #
Find S3 buckets with policies that allow public access:
SELECT
b.name AS bucket_name,
b.arn,
b.region,
p.policy
FROM aws_s3_buckets AS b
LEFT JOIN aws_s3_bucket_policies AS p ON b.arn = p.bucket_arn
LEFT JOIN aws_s3_bucket_public_access_blocks AS pab ON pab._cq_parent_id = b._cq_id
WHERE (
p.policy::text LIKE '%"Principal":"*"%'
OR p.policy::text LIKE '%"Principal":{"AWS":"*"}%'
)
AND (
(pab.public_access_block_configuration -> 'BlockPublicAcls')::boolean = false
OR (pab.public_access_block_configuration -> 'BlockPublicPolicy')::boolean = false
OR (pab.public_access_block_configuration -> 'IgnorePublicAcls')::boolean = false
OR (pab.public_access_block_configuration -> 'RestrictPublicBuckets')::boolean = false
);
This checks bucket policies for wildcard principals and verifies that public access block settings aren't preventing public access by joining with the
aws_s3_bucket_public_access_blocks table. S3 buckets with public policies are a common source of data leaks.RDS Instances with Public Endpoints #
Identify databases configured to be publicly accessible:
SELECT
db_instance_identifier,
endpoint_address,
endpoint_port,
engine,
region,
vpc_security_groups
FROM aws_rds_instances
WHERE publicly_accessible = true;
The
publicly_accessible flag indicates the database has a public endpoint. You'll want to review whether these databases actually need internet access or if they can use private endpoints instead.Internet-Facing Load Balancers #
Find application load balancers accepting traffic from the internet:
SELECT
load_balancer_name,
dns_name,
scheme,
type,
region,
availability_zones
FROM aws_elbv2_load_balancers
WHERE scheme = 'internet-facing';
Load balancers with
scheme = 'internet-facing' accept connections from the internet. This is often intentional for web applications, but you should verify that each one is supposed to be public.Finding Internet-Facing Assets in Azure #
Configure your Azure sync to include these tables:
VMs with Public IPs and Open NSG Rules #
Find Azure VMs with public IPs and NSG rules allowing internet traffic:
SELECT DISTINCT
vm.name AS vm_name,
vm.location,
pip.properties->>'ipAddress' AS public_ip,
nsg.name AS nsg_name,
rule->>'destinationPortRange' AS port_range,
rule->>'protocol' AS protocol
FROM azure_compute_virtual_machines AS vm
CROSS JOIN JSONB_ARRAY_ELEMENTS(vm.network_interfaces) AS vm_nic
JOIN azure_network_interfaces AS nic
ON nic.id = vm_nic->>'id'
CROSS JOIN JSONB_ARRAY_ELEMENTS(nic.ip_configurations) AS ip_config
JOIN azure_network_public_ip_addresses AS pip
ON pip.id = ip_config->'properties'->'publicIPAddress'->>'id'
JOIN azure_network_security_groups AS nsg
ON nsg.id = nic.properties->'networkSecurityGroup'->>'id'
CROSS JOIN JSONB_ARRAY_ELEMENTS(nsg.security_rules) AS rule
WHERE pip.properties->>'ipAddress' IS NOT NULL
AND rule->>'access' = 'Allow'
AND rule->>'direction' = 'Inbound'
AND (rule->>'sourceAddressPrefix' = '*'
OR rule->>'sourceAddressPrefix' = '0.0.0.0/0'
OR rule->>'sourceAddressPrefix' = 'Internet');
This query properly correlates VMs with their network interfaces, then joins to public IPs and NSGs through the correct relationships. It checks NSG rules for inbound allow rules from any source. Azure uses
* or Internet as wildcards in addition to 0.0.0.0/0.Storage Accounts with Public Blob Access #
Identify storage accounts allowing public access to blob containers:
SELECT
sa.name AS storage_account_name,
sa.location,
sa.allow_blob_public_access,
c.name AS container_name,
c.public_access
FROM azure_storage_accounts AS sa
JOIN azure_storage_blob_containers AS c
ON c.storage_account_id = sa.id
WHERE sa.allow_blob_public_access = true
AND c.public_access != 'None';
Azure Blob Storage has two levels of control - account-level
allow_blob_public_access and container-level public_access. Both must be set to allow public access, which this query checks.SQL Servers with Public Endpoints #
Find Azure SQL servers accepting connections from the internet:
SELECT
name,
fully_qualified_domain_name,
location,
public_network_access,
administrator_login
FROM azure_sql_servers
WHERE public_network_access = 'Enabled';
When
public_network_access is enabled, the SQL server can accept connections from public IPs. Check firewall rules to see which IP ranges are authorized.Public-Facing Load Balancers #
Identify load balancers with public frontend IPs:
SELECT
lb.name AS load_balancer_name,
lb.location,
pip.ip_address AS public_ip,
fip->>'name' AS frontend_name
FROM azure_network_load_balancers AS lb
CROSS JOIN JSONB_ARRAY_ELEMENTS(lb.frontend_ip_configurations) AS fip
JOIN azure_network_public_ip_addresses AS pip
ON pip.id = fip->>'publicIPAddress'
WHERE pip.ip_address IS NOT NULL;
This finds load balancers with public IPs in their frontend configurations, indicating they accept traffic from the internet.
Finding Internet-Facing Assets in GCP #
Configure your GCP sync to include these tables:
Compute Instances with External IPs and Open Firewall Rules #
Find GCP instances with external IPs and permissive firewall rules:
SELECT DISTINCT
i.name AS instance_name,
i.zone,
nic->>'networkIP' AS internal_ip,
access_config->>'natIP' AS external_ip,
fw.name AS firewall_name,
fw.allowed AS allowed_ports,
fw.source_ranges
FROM gcp_compute_instances AS i
CROSS JOIN JSONB_ARRAY_ELEMENTS(i.network_interfaces) AS nic
CROSS JOIN JSONB_ARRAY_ELEMENTS(nic->'accessConfigs') AS access_config
JOIN gcp_compute_firewalls AS fw
ON SPLIT_PART(fw.network, '/networks/', 2) = SPLIT_PART(nic->>'network', '/networks/', 2)
WHERE access_config->>'natIP' IS NOT NULL
AND fw.direction = 'INGRESS'
AND fw.source_ranges @> '["0.0.0.0/0"]'::jsonb;
GCP calls public IPs "external IPs" and configures them through access configs on network interfaces. This query extracts the network name from both the firewall and instance network fields (handling GCP's multiple URL formats) and checks for instances with external IPs and firewall rules allowing traffic from 0.0.0.0/0.
Storage Buckets with Public Access #
Identify Cloud Storage buckets granting access to all users:
SELECT
b.name AS bucket_name,
b.location,
binding->>'role' AS role,
member AS member
FROM gcp_storage_buckets AS b
CROSS JOIN JSONB_ARRAY_ELEMENTS(b.iam_policy->'bindings') AS binding
CROSS JOIN JSONB_ARRAY_ELEMENTS_TEXT(binding->'members') AS member
WHERE member IN ('allUsers', 'allAuthenticatedUsers');
GCP uses IAM policies for bucket access. The members
allUsers (anyone on the internet) and allAuthenticatedUsers (anyone with a Google account) indicate public access.Cloud SQL Instances with Public IPs #
Find Cloud SQL instances configured with public IP addresses:
SELECT
name,
database_version,
region,
ip_addresses,
settings->'ipConfiguration'->>'ipv4Enabled' AS ipv4_enabled,
settings->'ipConfiguration'->'authorizedNetworks' AS authorized_networks
FROM gcp_sql_instances
WHERE (settings->'ipConfiguration'->>'ipv4Enabled')::boolean = true;
When
ipv4Enabled is true, the Cloud SQL instance has a public IP. Check authorized_networks to see which IP ranges can connect - 0.0.0.0/0 means anyone.Public-Facing Load Balancers #
Identify forwarding rules with external IP addresses:
SELECT
name,
ip_address,
ip_protocol,
port_range,
region,
target,
load_balancing_scheme
FROM gcp_compute_forwarding_rules
WHERE load_balancing_scheme = 'EXTERNAL';
Forwarding rules with
load_balancing_scheme = 'EXTERNAL' accept traffic from the internet. The ip_address field shows the public IP that clients connect to.Multi-Cloud Query: All Internet-Facing Compute Resources #
Here's a UNION query combining compute instances from all three providers into a single view:
SELECT
'AWS' AS cloud_provider,
'EC2' AS resource_type,
instance_id AS resource_id,
region AS location,
public_ip_address AS public_ip
FROM aws_ec2_instances
WHERE public_ip_address IS NOT NULL
UNION ALL
SELECT
'Azure' AS cloud_provider,
'VM' AS resource_type,
vm.name AS resource_id,
vm.location,
pip.properties->>'ipAddress' AS public_ip
FROM azure_compute_virtual_machines AS vm
CROSS JOIN JSONB_ARRAY_ELEMENTS(vm.network_interfaces) AS vm_nic
JOIN azure_network_interfaces AS nic
ON nic.id = vm_nic->>'id'
CROSS JOIN JSONB_ARRAY_ELEMENTS(nic.ip_configurations) AS ip_config
JOIN azure_network_public_ip_addresses AS pip
ON pip.id = ip_config->'properties'->'publicIPAddress'->>'id'
WHERE pip.properties->>'ipAddress' IS NOT NULL
UNION ALL
SELECT
'GCP' AS cloud_provider,
'Compute Engine' AS resource_type,
name AS resource_id,
zone AS location,
access_config->>'natIP' AS public_ip
FROM gcp_compute_instances
CROSS JOIN JSONB_ARRAY_ELEMENTS(network_interfaces) AS nic
CROSS JOIN JSONB_ARRAY_ELEMENTS(nic->'accessConfigs') AS access_config
WHERE access_config->>'natIP' IS NOT NULL;
This gives you a consolidated view of all compute resources with public IPs across your entire multi-cloud environment. Add WHERE clauses to the individual queries to filter for permissive security rules.
What to Do After Finding Internet-Facing Assets #
Finding internet-facing resources is the first step. Here's what to do with the results:
Audit and classify every resource. Go through the list and determine which resources have a legitimate business need to be internet-facing. Web servers and public APIs need to be accessible. Internal databases and development instances don't.
Remediate unnecessary exposure. For resources that don't need internet access, remove public IPs or tighten security controls. Change security group rules from 0.0.0.0/0 to specific IP ranges. Enable S3 public access blocks. Use private endpoints for databases. Set up bastion hosts or VPNs for administrative access instead of opening SSH or RDP to the world.
Monitor continuously. Run these queries on a schedule - daily or hourly depending on how fast your environment changes. Alert when new internet-facing resources appear. Configuration drift happens constantly as teams deploy new resources. Continuous monitoring catches misconfigurations before they become security incidents.
Document exceptions. For resources that must remain internet-facing, tag or label them with justification. Create exceptions in your monitoring so you're not constantly investigating known-good exposures. This also helps with compliance audits.
Implement preventive controls. Use policy-as-code tools like AWS CloudFormation Guard, Azure Policy, or GCP Organization Policies to prevent resources from being created with public access in the first place. Shift security left by catching misconfigurations at deployment time rather than discovering them later.
Key Takeaways #
- Internet-facing assets are your primary attack surface - exposed VMs, storage, and databases are common breach entry points
- CloudQuery provides unified visibility across AWS, Azure, and GCP through SQL queries instead of separate CLI tools
- Each cloud provider uses different terminology (Security Groups vs NSGs vs Firewall Rules) but the concepts are similar
- Common exposure patterns include compute instances with public IPs and 0.0.0.0/0 rules, storage with public access policies, and databases with public endpoints
- Continuous monitoring catches configuration drift - run these queries on a schedule to alert on new public resources
- Not every public IP means internet-facing - you need both public addressing and permissive security rules
- Remediation involves removing unnecessary public access, tightening rules to specific IP ranges, and using private endpoints where appropriate
Check out the CloudQuery Hub for source integrations and the CloudQuery documentation for authentication setup guides for each cloud provider.
Frequently Asked Questions #
What is an internet-facing cloud asset? #
An internet-facing cloud asset is any resource that can be accessed directly from the public internet. This includes VMs with public IP addresses and permissive security rules, storage buckets with public read access, databases with public endpoints, and load balancers configured to accept internet traffic. The key requirement is both public accessibility (IP or DNS name) and network security controls that allow inbound connections from outside your network.
How often should I scan for internet-facing assets? #
You should scan continuously by running CloudQuery syncs on a schedule. For production environments, sync hourly or daily depending on how frequently your infrastructure changes. Set up alerts to notify security teams when new internet-facing resources appear. Configuration drift happens constantly as developers deploy new resources or modify existing ones. Continuous monitoring catches misconfigurations within hours instead of months.
Can CloudQuery monitor multiple cloud accounts? #
Yes. CloudQuery can sync from multiple AWS accounts, Azure subscriptions, and GCP projects simultaneously. Configure separate source instances in your CloudQuery configuration file, each with credentials for different accounts. All the data goes into the same database, which allows you to query across your entire multi-cloud, multi-account environment with a single SQL query.
What's the difference between public IP and internet-facing? #
A resource with a public IP address isn't automatically internet-facing. It also needs permissive network security controls. An EC2 instance might have an Elastic IP, but if its security groups block all inbound traffic, it's not reachable from the internet. CloudQuery queries check both conditions - public IP presence AND security rules allowing traffic from 0.0.0.0/0 or other broad CIDR ranges. Both must be true for a resource to be truly internet-facing.
How do I automate remediation of exposed resources? #
Use CloudQuery's data to trigger automated workflows. Set up scheduled queries that run every hour and compare results to previous runs. When a new internet-facing resource appears, trigger webhooks or integration platforms (n8n, Zapier, AWS Lambda) to either automatically remediate the issue or create tickets for teams to review. Some organizations automatically remove public access for specific resource types while sending alerts for others that require manual review.
Does CloudQuery support other cloud providers? #
Yes. CloudQuery supports 100+ source integrations including Oracle Cloud, Alibaba Cloud, DigitalOcean, Kubernetes, and many SaaS platforms. Check the CloudQuery Hub for the complete list. The same SQL query approach works across all providers - sync data from any source and query it with standard SQL.
What compliance frameworks require tracking internet-facing assets? #
Most security and compliance frameworks require organizations to identify and control internet-accessible resources. CIS Benchmarks have specific recommendations for restricting public access. SOC 2 requires documenting and monitoring system boundaries. PCI DSS requires restricting public access to cardholder data environments. HIPAA requires controlling access to protected health information. GDPR includes requirements for appropriate security measures for personal data. Tracking internet-facing assets helps meet all these requirements.
Can I export the results to a dashboard? #
Yes. CloudQuery stores data in PostgreSQL-compatible databases, which means you can connect any visualization tool. Grafana, Apache Superset, Metabase, Power BI, Tableau, and Looker all work with CloudQuery data. Create dashboards showing internet-facing asset counts by cloud provider, resource type, or business unit. Set up panels that track changes over time to visualize whether your attack surface is growing or shrinking.