Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
/securitycenter/**/* @GoogleCloudPlatform/python-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers @GoogleCloudPlatform/gcp-security-command-center
/service_extensions/**/* @GoogleCloudPlatform/service-extensions-samples-reviewers @GoogleCloudPlatform/python-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers
/tpu/**/* @GoogleCloudPlatform/python-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers
/vpc/**/* @GoogleCloudPlatform/python-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers
/vmwareengine/**/* @GoogleCloudPlatform/python-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers
/webrisk/**/* @GoogleCloudPlatform/python-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers

Expand Down
92 changes: 92 additions & 0 deletions vpc/get_google_ip_ranges/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Google Cloud VPC: Get Google APIs and Services IP Ranges

This sample shows how to retrieve Google's public IP range feeds and compute the IP address ranges (CIDRs) used exclusively for Google APIs and default domain services (such as `*.googleapis.com`).

It calculates the IP set difference `(goog.json - cloud.json)` to separate default Google API domains from customer Google Cloud resource IPs, which is useful for configuring VPC firewall rules, custom routing, and [Private Google Access](https://cloud.google.com/vpc/docs/private-google-access).

## Setup

1. Create and activate a Python virtual environment:

```bash
python3 -m venv env
source env/bin/activate
```

2. Install the dependencies needed to run the sample:

```bash
pip install -r requirements.txt
```

## How to Run

Run the sample script directly with Python:

```bash
python get_google_ip_ranges.py
```

### Example Output

```text
Fetched https://www.gstatic.com/ipranges/goog.json (published: 2026-08-11T13:04:46.50592)
Fetched https://www.gstatic.com/ipranges/cloud.json (published: 2026-08-11T13:04:46.50592)
IP ranges for Google APIs and services default domains:
8.8.4.0/24
8.8.8.0/24
8.35.200.0/21
...
2600:1900::/34
2600:1901:1::/48
```

## Testing

### Run Tests with Pytest

1. Install test dependencies:

```bash
pip install -r requirements-test.txt
```

2. Run the test suite:

```bash
pytest
```

### Run Tests and Linting with Nox

This sample uses [Nox](https://github.com/wntrblm/nox) for automated testing and linting.

1. Install `nox`:

```bash
pip install nox
```

2. Run code style linting:

```bash
nox -s lint
```

3. Run code formatting with Black:

```bash
nox -s blacken
```

4. Run tests against a specific Python version:

```bash
nox -s py-3.13
```

## References

* [Google Cloud Sample Authoring Guide](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md)
* [Private Google Access Configuration](https://cloud.google.com/vpc/docs/configure-private-google-access#ip-addr-defaults)
* [Google IP Address Ranges](https://cloud.google.com/vpc/docs/private-google-access#ip-addr-defaults)
106 changes: 106 additions & 0 deletions vpc/get_google_ip_ranges/get_google_ip_ranges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# [START vpc_get_google_ip_ranges]
from __future__ import annotations

import json
from urllib.error import URLError
from urllib.request import urlopen
Comment thread
ersin-ertan marked this conversation as resolved.

import netaddr

# Google publishes two official IP range feeds:
# - goog: Complete list of all Google-owned IP ranges (Search, YouTube, APIs, etc.)
# - cloud: External IP ranges allocated for customer Google Cloud resources
IPRANGE_URLS: dict[str, str] = {
"goog": "https://www.gstatic.com/ipranges/goog.json",
"cloud": "https://www.gstatic.com/ipranges/cloud.json",
}


def get_data(url: str) -> netaddr.IPSet | None:
"""Fetches JSON IP range data from a URL and converts prefixes into an IPSet.

Args:
url: The public URL of the JSON IP ranges feed.

Returns:
A netaddr.IPSet containing all parsed IPv4 and IPv6 network prefixes,
or None if the network request or JSON parsing fails.
"""
# Step 1: Download and parse the JSON feed
try:
with urlopen(url, timeout=30) as response:
data = json.load(response)
except (URLError, json.JSONDecodeError, TimeoutError) as error:
print(f"ERROR: Failed to fetch or parse {url}: {error}")
return None
Comment thread
ersin-ertan marked this conversation as resolved.

# Verify root payload is a JSON object
if not isinstance(data, dict):
print(f"ERROR: Expected JSON object from {url}, got {type(data).__name__}")
return None
Comment thread
ersin-ertan marked this conversation as resolved.

creation_time = data.get("creationTime", "Unknown")
print(f"Fetched {url} (published: {creation_time})")

# Step 2: Extract both IPv4 and IPv6 CIDR prefixes into an IPSet
cidr_set = netaddr.IPSet()
prefixes = data.get("prefixes", [])
if isinstance(prefixes, list):
for prefix_entry in prefixes:
if not isinstance(prefix_entry, dict):
continue
try:
if "ipv4Prefix" in prefix_entry:
cidr_set.add(prefix_entry["ipv4Prefix"])
if "ipv6Prefix" in prefix_entry:
cidr_set.add(prefix_entry["ipv6Prefix"])
except (netaddr.AddrFormatError, ValueError) as error:
print(f"WARNING: Skipping invalid prefix in {url}: {error}")

return cidr_set


def main() -> None:
"""Calculates and displays CIDR ranges for Google APIs and default domain services.

By subtracting customer Google Cloud IP ranges ('cloud') from all Google IP
ranges ('goog'), we isolate the IP ranges used exclusively for Google APIs
and default services (such as *.googleapis.com).
"""
# Step 1: Fetch and parse IP ranges for both 'goog' and 'cloud' feeds
cidrs: dict[str, netaddr.IPSet] = {}
for feed_name, url in IPRANGE_URLS.items():
feed_cidrs = get_data(url)

if feed_cidrs is None:
raise ValueError(f"ERROR: Could not process data from {url}")

cidrs[feed_name] = feed_cidrs

# Step 2: Compute set difference (goog - cloud)
# This removes customer cloud IPs, leaving only default Google APIs/services
default_domain_cidrs = cidrs["goog"] - cidrs["cloud"]

# Step 3: Print the aggregated CIDR list
print("IP ranges for Google APIs and services default domains:")
for cidr in default_domain_cidrs.iter_cidrs():
print(cidr)


if __name__ == "__main__":
main()
# [END vpc_get_google_ip_ranges]
Loading