VPN and Bucket IP Filtering
VPN と Bucket IP Filtering
English follows Japanese.
本記事の目的
本記事の目的は2つある。
副次的な目的は、2プロジェクト間の Cloud VPN の構成を、gcloud
コマンドのみで行う例を示すことである。簡単に検索した限りでは、Google Cloud の公式文書(ただし、同一プロジェクト内)以外に、Web UI を使った構成例はあったが、gcloud
コマンドを使った構成例は見つからなかった。
もうひとつの目的は、Cloud Storage の新機能である Bucket IP Filtering を、VPN 接続の接続先からアクセスできるか検証することである。
結論: VPN 接続の接続先からアクセスできる。これはいい。
本記事は、Google Cloud の基本的な VPC, IAM, Cloud Storage の知識があるネットワークエンジニアやクラウドアーキテクトを対象としている。
2プロジェクト間の VPN については、Google Cloud の公式ドキュメント以外の情報が見つからなかったため、gcloud
コマンドを使った構成例を示す(対向が、オンプレミス ルーターの例は、当たり前だが結構あった)。
Bucket IP Filtering とは
Cloud Storage の Bucket IP filtering 機能は、Cloud Storage のバケットに対して、特定の IP アドレス範囲からのアクセスのみを許可するような、アクセス制御機能であるバケット IP フィルタリング。
2024-11-14に Public Preview としてリリースされている。
VPN で使える?
ある会社が「社内からしか見られない」ような「汎用のファイル置き場」を作りたいと考えたとき、Cloud Storage の Bucket IP filtering 機能が役に立つかもしれない。
Global IP を CIDR range で指定できるため、各拠点に固定 IP を持たせている場合には、Cloud Storage の Bucket IP filtering 機能を使うことで、特定の拠点からのアクセスのみを許可することができる。
"publicNetworkSource": {
"allowedIpCidrRanges": [
"${YOUR_IP}/32"
],
}
もうひとつ、良くある Google Cloud の利用の仕方としては、VPN の構成があげられるだろう。Cloud Storage バケットが、VPN 接続の接続先だけからアクセス可能だとすると、他に考えることがなくなるため、非常に便利だ。
オンプレミス ホストの限定公開の Google アクセスを構成するには、
オンプレミス ホスト用の限定公開の Google アクセスを使用すると、Cloud VPN トンネルまたは Cloud Interconnect 用の VLAN アタッチメントを経由してトラフィックをルーティングすることにより、オンプレミス システムから Google API とサービスに接続できます。オンプレミス ホスト用の限定公開の Google アクセスは、インターネット経由で Google API とサービスに接続するための代替手段として使用できます。
と記載されている。接続先から private.googleapis.com
経由で Cloud Storage にアクセスするときに、Bucket IP Filtering が利用できるか検証したい。
作業手順
基本的な操作は、すべて gcloud
コマンドで行い、コピーするだけで再現できることを目指す。
ネットワークやバケットのリージョンには us-central1
を指定する。
Google Cloud プロジェクト2つに、10.1.0.0/24, 10.2.0.0/24 の VPC ネットワークを作成し、VPN で接続させる。
10.1.0.0/24 がホスト側、10.2.0.0/24 が(Google Cloud 上で検証するが)オンプレミス側と考える。
network-vpn-host 10.1.0.0/24
network-vpn-guest 10.1.1.0/24
環境定義
以下を、再現環境に合わせて定義する。
PROJECT_HOST
はバケット ホスト、PROJECT_GUEST
はオンプレミス環境と考える。YOUR_IP
はアクセスを許可するグローバル IP、YOUR_ACCOUNT
は、操作者の Google アカウント、BILLING_ACCOUNT
は Google Cloud の課金アカウントを設定する。
SHARED_SECRET
は、VPN 接続のための Shared Secret である。
export PROJECT_HOST=host-project
export PROJECT_GUEST=guest-project
export YOUR_IP=203.0.113.1
export YOUR_ACCOUNT=test@example.com
export BILLING_ACCOUNT="000000-000000-000000"
export SHARED_SECRET="bvTtdK7nil68WCYu7+dJ7iYdEmeWCB/A5Q4lUu2DHac="
Google Cloud プロジェクトの作成・API の有効化
for project in $PROJECT_HOST $PROJECT_GUEST; do
gcloud projects create $project
gcloud billing projects link $project --billing-account=$BILLING_ACCOUNT
gcloud services enable compute.googleapis.com --project=$project
done
バケットの作成
ホスト側のプロジェクトに、次のような Cloud Storage バケットを作成する。sa は Service Account の略から来ている。
基本的に allUsers
に対して検証できれば、今回の目的は達成されるが、折角なので、Service Account に権限を付与した場合の挙動も確認しておく。あとで ls
したいので、ダミーファイルも作成しておく。
バケット名 | アクセス許可 | Bucket IP Filtering | network | IP 範囲 |
---|---|---|---|---|
public-2025-05 | allUsers | 無効 | NA | NA |
private-2025-05 | host プロジェクトのみ | 無効 | NA | NA |
ip-vpn-all-2025-05 | allUsers | 有効 | network-vpn-host | 10.1.0.0/16 |
ip-vpn-sa-2025-05 | 検証者、host のサービスアカウント | 有効 | network-vpn-host | 10.1.0.0/16 |
ip-allow-all-2025-05 | allUsers | 有効 | publicNetworkSource, network-vpn-host | 10.1.0.0/16, YOUR_IP |
ip-allow-sa-2025-05 | 検証者、host のサービスアカウント | 有効 | publicNetworkSource, network-vpn-host | 10.1.0.0/16, YOUR_IP |
network-vpn-host 10.1.0.0/24
network-vpn-guest 10.1.1.0/24
としたが、「両方」を含む IP 範囲として、10.1.0.0/16
を指定している。
gcloud storage buckets create gs://public-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
gcloud storage buckets add-iam-policy-binding gs://public-2025-05 --member=allUsers --role=roles/storage.objectViewer --project=$PROJECT_HOST
gcloud storage buckets create gs://private-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
gcloud storage buckets create gs://ip-vpn-all-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
gcloud storage buckets add-iam-policy-binding gs://ip-vpn-all-2025-05 --member=allUsers --role=roles/storage.objectViewer --project=$PROJECT_HOST
gcloud storage buckets create gs://ip-vpn-sa-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
# プロジェクトで権限付与するので、IAM での権限付与は省略
gcloud storage buckets create gs://ip-allow-all-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
gcloud storage buckets add-iam-policy-binding gs://ip-allow-all-2025-05 --member=allUsers --role=roles/storage.objectViewer --project=$PROJECT_HOST
gcloud storage buckets create gs://ip-allow-sa-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
# プロジェクトで権限付与するので、IAM での権限付与は省略
mkdir -p dummy_files
for bucket in public private ip-vpn-all ip-vpn-sa ip-allow-all ip-allow-sa; do
echo "${bucket}_2025-05.txt" > dummy_files/${bucket}_2025-05.txt
gcloud storage cp dummy_files/${bucket}_2025-05.txt gs://${bucket}-2025-05
done
VPC ネットワークの作成
PROJECT_HOST に VPC ネットワーク network-vpn-host を作成し、PROJECT_GUEST に VPC ネットワーク network-vpn-guest を作成する。
前述の通り、
network-vpn-host 10.1.0.0/24
network-vpn-guest 10.1.1.0/24
とする。
gcloud compute networks create network-vpn-host --project=$PROJECT_HOST --subnet-mode=custom
gcloud compute networks subnets create subnet-vpn-host \
--network=network-vpn-host \
--region=us-central1 \
--range=10.1.0.0/24 \
--project=$PROJECT_HOST
gcloud compute networks create network-vpn-guest --project=$PROJECT_GUEST --subnet-mode=custom
gcloud compute networks subnets create subnet-vpn-guest \
--network=network-vpn-guest \
--region=us-central1 \
--range=10.1.1.0/24 \
--project=$PROJECT_GUEST
Public IP があると、経路が分かりにくい(実際には変わらないはずだが)。
Public IP がなくても、SSH で接続できるように、IAP の設定を行う。
for project in $PROJECT_HOST $PROJECT_GUEST; do
gcloud projects add-iam-policy-binding $project \
--member="user:${YOUR_ACCOUNT}" \
--role=roles/iap.tunnelResourceAccessor
gcloud projects add-iam-policy-binding $project \
--member="user:${YOUR_ACCOUNT}" \
--role=roles/compute.instanceAdmin.v1
done
gcloud compute firewall-rules create allow-ssh-ingress-from-iap-vpn \
--direction=INGRESS \
--action=allow \
--rules=tcp:22 \
--source-ranges=35.235.240.0/20 \
--network=network-vpn-host \
--project=$PROJECT_HOST
gcloud compute firewall-rules create allow-ssh-ingress-from-iap-vpn \
--direction=INGRESS \
--action=allow \
--rules=tcp:22 \
--source-ranges=35.235.240.0/20 \
--network=network-vpn-guest \
--project=$PROJECT_GUEST
また、Public IP がなくても、Google Cloud のサービスにアクセスできるように、限定公開の Google アクセスを構成する。
gcloud compute networks subnets update subnet-vpn-host --project=$PROJECT_HOST \
--region=us-central1 \
--enable-private-ip-google-access
gcloud compute networks subnets update subnet-vpn-guest --project=$PROJECT_GUEST \
--region=us-central1 \
--enable-private-ip-google-access
# 確認したい場合には以下を実行
# gcloud compute networks subnets describe subnet-vpn-host --project=$PROJECT_HOST \
# --region=us-central1 \
# --format="get(privateIpGoogleAccess)"
VM が相互に通信できるように、Firewall Rule を設定する。
gcloud compute firewall-rules create allow-internal-ingress-from-vpn \
--direction=INGRESS \
--action=allow \
--rules=all \
--source-ranges=10.1.0.0/16 \
--network=network-vpn-host \
--project=$PROJECT_HOST
gcloud compute firewall-rules create allow-internal-ingress-from-vpn \
--direction=INGRESS \
--action=allow \
--rules=all \
--source-ranges=10.1.0.0/16 \
--network=network-vpn-guest \
--project=$PROJECT_GUEST
プロジェクト間の VPN の設定
今まで作成したネットワーク network-vpn-host と network-vpn-guest の間に VPN を設定する。
BGP の IPv6 については、省くことにした。
# PROJECT_HOST での VPN 設定
gcloud compute vpn-gateways create vpn-host \
--project=$PROJECT_HOST \
--region=us-central1 \
--network=network-vpn-host
gcloud compute routers create cloud-router-host \
--project=$PROJECT_HOST \
--region=us-central1 \
--network network-vpn-host \
--asn 65001
# PROJECT_GUEST での VPN 設定
gcloud compute vpn-gateways create vpn-guest \
--project=$PROJECT_GUEST \
--region=us-central1 \
--network=network-vpn-guest
gcloud compute routers create cloud-router-guest \
--project=$PROJECT_GUEST \
--region=us-central1 \
--network network-vpn-guest \
--asn 65002
表示される IP を控えておく。
export HOST_INTERFACE0=34.153.55.144
export HOST_INTERFACE1=35.220.88.151
export GUEST_INTERFACE0=35.242.99.80
export GUEST_INTERFACE1=34.153.244.91
# テストしていないが、以下で取得できるかも
export HOST_INTERFACE0=$(gcloud compute vpn-gateways describe vpn-host --project=$PROJECT_HOST --region=us-central1 --format='value(vpnInterfaces[0].ipAddress)')
export HOST_INTERFACE1=$(gcloud compute vpn-gateways describe vpn-host --project=$PROJECT_HOST --region=us-central1 --format='value(vpnInterfaces[1].ipAddress)')
export GUEST_INTERFACE0=$(gcloud compute vpn-gateways describe vpn-guest --project=$PROJECT_GUEST --region=us-central1 --format='value(vpnInterfaces[0].ipAddress)')
export GUEST_INTERFACE1=$(gcloud compute vpn-gateways describe vpn-guest --project=$PROJECT_GUEST --region=us-central1 --format='value(vpnInterfaces[1].ipAddress)')
# PROJECT_HOST で、相手方の情報を登録
# guest -> host の VPN の存在登録
# 以下のコマンドを利用したいが、
# > You cannot provide interface with IP address associated with HA VPN gateway of Google Cloud.
# と出るので、`projects/${PROJECT_GUEST}/regions/us-central1/vpnGateways/vpn-guest` を指定する
# gcloud compute external-vpn-gateways create vpngw-guest-host \
# --project=$PROJECT_HOST \
# --interfaces=0=${GUEST_INTERFACE0},1=${GUEST_INTERFACE1}
gcloud compute vpn-tunnels create tunnel-host-guest-0 \
--project=$PROJECT_HOST \
--region=us-central1 \
--peer-gcp-gateway=projects/${PROJECT_GUEST}/regions/us-central1/vpnGateways/vpn-guest \
--ike-version=2 \
--shared-secret=$SHARED_SECRET \
--router=cloud-router-host \
--vpn-gateway=vpn-host \
--interface=0
gcloud compute vpn-tunnels create tunnel-host-guest-1 \
--project=$PROJECT_HOST \
--region=us-central1 \
--peer-gcp-gateway=projects/${PROJECT_GUEST}/regions/us-central1/vpnGateways/vpn-guest \
--ike-version=2 \
--shared-secret=$SHARED_SECRET \
--router=cloud-router-host \
--vpn-gateway=vpn-host \
--interface=1
# PROJECT_GUEST で、相手方の情報を登録
# host -> guest の VPN の存在登録
# gcloud compute external-vpn-gateways create vpngw-host-guest \
# --project=$PROJECT_GUEST \
# --interfaces=0=${HOST_INTERFACE0},1=${HOST_INTERFACE1}
gcloud compute vpn-tunnels create tunnel-guest-host-0 \
--project=$PROJECT_GUEST \
--region=us-central1 \
--peer-gcp-gateway=projects/${PROJECT_HOST}/regions/us-central1/vpnGateways/vpn-host \
--ike-version=2 \
--shared-secret=$SHARED_SECRET \
--router=cloud-router-guest \
--vpn-gateway=vpn-guest \
--interface=0
gcloud compute vpn-tunnels create tunnel-guest-host-1 \
--project=$PROJECT_GUEST \
--region=us-central1 \
--peer-gcp-gateway=projects/${PROJECT_HOST}/regions/us-central1/vpnGateways/vpn-host \
--ike-version=2 \
--shared-secret=$SHARED_SECRET \
--router=cloud-router-guest \
--vpn-gateway=vpn-guest \
--interface=1
BGP の設定
# HOST
gcloud compute routers add-interface cloud-router-host \
--project=$PROJECT_HOST \
--interface-name=if-tunnel-host-guest-0 \
--vpn-tunnel=tunnel-host-guest-0 \
--region=us-central1 \
--ip-version=IPV4
gcloud compute routers add-bgp-peer cloud-router-host \
--project=$PROJECT_HOST \
--region=us-central1 \
--peer-name=peer-tunnel-host-guest-0 \
--interface=if-tunnel-host-guest-0 \
--peer-asn=65002
# AI Suggest below:
# BGP_PEER_IP_HOST_TUNNEL_0=$(gcloud compute routers get-status cloud-router-host --project=$PROJECT_HOST --region=us-central1 --format='flattened(result.bgpPeerStatus[].name,result.bgpPeerStatus[].ipAddress,result.bgpPeerStatus[].peerIpAddress)' | grep tunnel-host-guest-0 | awk '{print $3}')
# BGP_ROUTER_IP_HOST_TUNNEL_0=$(gcloud compute routers get-status cloud-router-host --project=$PROJECT_HOST --region=us-central1 --format='flattened(result.bgpPeerStatus[].name,result.bgpPeerStatus[].ipAddress,result.bgpPeerStatus[].peerIpAddress)' | grep tunnel-host-guest-0 | awk '{print $2}')
gcloud compute routers describe cloud-router-host \
--project=$PROJECT_HOST \
--region=us-central1
peerIpAddress
を確認し、相手方に想定されている IP を手動で設定する。
# GUEST
gcloud compute routers add-interface cloud-router-guest \
--project=$PROJECT_GUEST \
--interface-name=if-tunnel-guest-host-0 \
--ip-address=169.254.0.250 \
--mask-length=30 \
--vpn-tunnel=tunnel-guest-host-0 \
--region=us-central1 \
--ip-version=IPV4
gcloud compute routers add-bgp-peer cloud-router-guest \
--project=$PROJECT_GUEST \
--region=us-central1 \
--peer-name=peer-tunnel-guest-host-0 \
--interface=if-tunnel-guest-host-0 \
--peer-asn=65001 \
--peer-ip-address=169.254.0.249
設定の確認
gcloud compute routers describe cloud-router-host \
--project=$PROJECT_HOST \
--region=us-central1
gcloud compute routers describe cloud-router-guest \
--project=$PROJECT_GUEST \
--region=us-central1
VM インスタンスの作成
それぞれの VPC ネットワークに VM インスタンスを作成する。no-address
を指定して、Public IP を持たない VM インスタンスとしている。
gcloud compute instances create vm-host-vpn \
--project=$PROJECT_HOST \
--zone=us-central1-f \
--machine-type=e2-micro \
--network-interface=stack-type=IPV4_ONLY,subnet=subnet-vpn-host,no-address \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--boot-disk-size 10GB \
--boot-disk-type pd-standard \
--image-project debian-cloud \
--image-family debian-12
gcloud compute instances create vm-guest-vpn \
--project=$PROJECT_GUEST \
--zone=us-central1-f \
--machine-type=e2-micro \
--network-interface=stack-type=IPV4_ONLY,subnet=subnet-vpn-guest,no-address \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--boot-disk-size 10GB \
--boot-disk-type pd-standard \
--image-project debian-cloud \
--image-family debian-12
各 VM インスタンスには、次のようにして SSH 接続できる。
gcloud compute ssh --zone=us-central1-f vm-host-vpn --tunnel-through-iap --project $PROJECT_HOST
gcloud compute ssh --zone=us-central1-f vm-guest-vpn --tunnel-through-iap --project $PROJECT_GUEST
10.1.0.3 がホスト側の VM インスタンス IP、10.1.1.3 がゲスト側の VM インスタンス IP であった。
ゲスト側から ping して、疎通できていれば、問題がない。
ping 10.1.0.3
Bucket IP Filtering の検証
Access Check Part 1
まずは、各 VM から、バケットにアクセスできるかを確認する。ls_all_buckets.sh
を各 VM から実行して、バケット一覧を確認する。
for bucket_prefix in public private ip-vpn-all ip-vpn-sa ip-allow-all ip-allow-sa; do
echo "gs://${bucket_prefix}-2025-05/"
gcloud storage ls gs://${bucket_prefix}-2025-05/
done
vm-host-vpn:
./ls_all_buckets.sh
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
gs://private-2025-05/private_2025-05.txt
gs://ip-vpn-all-2025-05/
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
gs://ip-vpn-sa-2025-05/
gs://ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
gs://ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
vm-guest-vpn:
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [private-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-all-2025-05/
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-vpn-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-allow-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
上記のように、vm-host-vpn からはすべてのバケットにアクセスできるが、vm-guest-vpn からは、public-2025-05
と ip-vpn-all-2025-05
のみアクセスできる。
Bucket IP Filtering の設定
ip-vpn-all-2025-05 と ip-vpn-sa-2025-05 のバケットに、Bucket IP Filtering を設定する。
cat <<EOF > ip-filter-vpn.json
{
"mode": "Enabled",
"vpcNetworkSources": [
{
"network": "projects/$PROJECT_HOST/global/networks/network-vpn-host",
"allowedIpCidrRanges": [
"10.1.0.0/16"
]
}
]
}
EOF
ip-filter-allow.json
を作成する。
cat <<EOF > ip-filter-allow.json
{
"mode": "Enabled",
"publicNetworkSource": {
"allowedIpCidrRanges": [
"${YOUR_IP}/32"
]
},
"vpcNetworkSources": [
{
"network": "projects/$PROJECT_HOST/global/networks/network-vpn-host",
"allowedIpCidrRanges": [
"10.1.0.0/16"
]
}
]
}
EOF
Bucket IP Filtering を設定する。
for bucket_prefix in ip-vpn-all ip-vpn-sa; do
gcloud alpha storage buckets update gs://${bucket_prefix}-2025-05 --project=$PROJECT_HOST --ip-filter-file=ip-filter-vpn.json
done
for bucket_prefix in ip-allow-all ip-allow-sa; do
gcloud alpha storage buckets update gs://${bucket_prefix}-2025-05 --project=$PROJECT_HOST --ip-filter-file=ip-filter-allow.json
done
./configure_ip_filtering.sh.sh
Updating gs://ip-vpn-all-2025-05/...
Completed 1
Updating gs://ip-vpn-sa-2025-05/...
Completed 1
Updating gs://ip-allow-all-2025-05/...
Completed 1
Updating gs://ip-allow-sa-2025-05/...
Completed 1
Access Check Part 2
vm-host-vpn:
./ls_all_buckets.sh
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
gs://private-2025-05/private_2025-05.txt
gs://ip-vpn-all-2025-05/
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
gs://ip-vpn-sa-2025-05/
gs://ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
gs://ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
vm-guest-vpn:
./ls_all_buckets.sh
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [private-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-all-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-vpn-all-2025-05] (or it may not exist): There is an IP filtering condition that is preventing access to the resource. This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-vpn-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-allow-all-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-allow-all-2025-05] (or it may not exist): There is an IP filtering condition that is preventing access to the resource. This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-allow-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-allow-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
vm-guest-vpn からは、public-2025-05
のみアクセスできる。それ以外にはアクセスできない。IP filtering の有無で、エラーメッセージが異なっている。
静的ルートの追加
# gcloud compute routes create private-to-host \
# --project=$PROJECT_GUEST \
# --network=network-vpn-guest \
# --destination-range=199.36.153.8/30 \
# --priority=200 \
# --next-hop-gateway=projects/$PROJECT_HOST/global/vpnGateways/vpn-host
gcloud compute routes create private-to-host \
--project=$PROJECT_GUEST \
--network=network-vpn-guest \
--destination-range=199.36.153.8/30 \
--priority=200 \
--next-hop-vpn-tunnel-region=us-central1 \
--next-hop-vpn-tunnel=tunnel-guest-host-0
【参考】Cloud Router の設定変更
Cloud Router を使用したオンプレミス ルーティング にあるように、host 側の Cloud Router が、private.googleapis.com
ドメインで使用される IP 範囲のルートを guest 側(オンプレミス側)に通知できる。
静的ルートで動作したため、上記通知 (advertise) の手法でも動作すると考えられる。具体的な検証は行っていないため、是非確認してみてほしい。
Access Check Part 3
結論: 適切な設定を加えると、gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
と gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
に追加でアクセスできるようになる。
/etc/hosts
法
以下の行を /etc/hosts
に追加する。
199.36.153.8 storage.googleapis.com
すると、storage.googleapis.com
にアクセスすると、199.36.153.8
にアクセスするようになる。また、199.36.153.8/30
へのアクセスは、VPN 経由で行われる静的ルートを設定しているので、VPN 経由でアクセスされるはずだ。
./ls_all_buckets.sh
を再度実行しよう。
./ls_all_buckets.sh
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [private-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-all-2025-05/
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-vpn-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-allow-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
と gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
に追加でアクセスできるようになった。
Endpoint Override 法
長いので結論: /etc/hosts
あるいは、DNS を組み合わせる必要がある。
/etc/hosts
の設定は元に戻しておく。
gcloud
コマンドは、API エンドポイントを上書きすることができる。
gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/
上記によって、storage.googleapis.com
ではなく、private.googleapis.com
にアクセスするようになる。また、199.36.153.8/30
へのアクセスは、VPN 経由で行われる静的ルートを設定しているので、VPN 経由でアクセスされるはずだ。
./ls_all_buckets.sh
を再度実行しよう。
./ls_all_buckets.sh
gs://public-2025-05/
ERROR: (gcloud.storage.ls) gs://public-2025-05 not found: 404.
gs://private-2025-05/
ERROR: (gcloud.storage.ls) gs://private-2025-05 not found: 404.
gs://ip-vpn-all-2025-05/
ERROR: (gcloud.storage.ls) gs://ip-vpn-all-2025-05 not found: 404.
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) gs://ip-vpn-sa-2025-05 not found: 404.
gs://ip-allow-all-2025-05/
ERROR: (gcloud.storage.ls) gs://ip-allow-all-2025-05 not found: 404.
gs://ip-allow-sa-2025-05/
ERROR: (gcloud.storage.ls) gs://ip-allow-sa-2025-05 not found: 404.
…ダメだった。
ここで重要な点として、この設定では期待通りに動作せず、アクセスは失敗した。この結果には注目してほしい。
gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/storage/v1/
もエラーだった。なぜアクセスが失敗するのか原因が不明である。
gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/storage/
これもエラーだった。
以下のように --log-http
をつけて、リクエストを確認してみる。
gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/storage/v1/
gcloud storage ls gs://public-2025-05 --log-http
すると、
https://private.googleapis.com/storage/v1/b/public-2025-05/o?alt=json&fields=prefixes%2Citems%2Fname%2Citems%2Fsize%2Citems%2Fgeneration%2CnextPageToken&delimiter=%2F&includeFoldersAsPrefixes=True&maxResults=1000&projection=noAcl
のようなリクエストが飛んでいる。
curl "https://private.googleapis.com/storage/v1/b/public-2025-05/o?alt=json&fields=prefixes%2Citems%2Fname%2Citems%2Fsize%2Citems%2Fgeneration%2CnextPageToken&delimiter=%2F&includeFoldersAsPrefixes=True&maxResults=1000&projection=noAcl"
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/storage/v1/b/public-2025-05/o?alt=json&fields=prefixes%2Citems%2Fname%2Citems%2Fsize%2Citems%2Fgeneration%2CnextPageToken&delimiter=%2F&includeFoldersAsPrefixes=True&maxResults=1000&projection=noAcl"
を試してみると、Host
を設定しない場合は、404 で、Host
を設定すると、以下が返ってきた。
{
"items": [
{
"name": "public_2025-05.txt",
"generation": "1746452844041206",
"size": "19"
}
]
}
つまり、サーバ側は Host
ヘッダを見ていて、storage
でない場合には正常に応答しない。
gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/
としても、アクセス先は同じようだった。
gcloud config set api_endpoint_overrides/storage https://199.36.153.8/
gcloud config set auth/disable_ssl_validation True
gcloud storage ls gs://public-2025-05 --log-http
とすると、401 となる。
gcloud storage ls gs://public-2025-05
/usr/bin/../lib/google-cloud-sdk/lib/third_party/urllib3/connectionpool.py:1102: InsecureRequestWarning: Unverified HTTPS request is being made to host '199.36.153.8'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
/usr/bin/../lib/google-cloud-sdk/lib/third_party/urllib3/connectionpool.py:1102: InsecureRequestWarning: Unverified HTTPS request is being made to host '199.36.153.8'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
ERROR: (gcloud.storage.ls) HTTPError 401: <!DOCTYPE html>
<html lang=en>
<meta charset=utf-8>
<meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
<title>Error 401 (Unauthorized)!!1</title>
<style>
*{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
</style>
<a href=//www.google.com/><span id=logo aria-label=Google></span></a>
<p><b>401.</b> <ins>That’s an error.</ins>
<p>Your client does not have permission to the requested URL <code>/storage/v1/b/public-2025-05/o</code>. <ins>That’s all we know.</ins>
ということで、結論から言うと、ダメだ。
Endpoint Override 法の考察
/etc/hosts
に以下を追加してみる。片方は意図的に storag
と e
を削っている。
199.36.153.8 storage-dummy.p.googleapis.com
199.36.153.8 storag-dummy.p.googleapis.com
gcloud config set api_endpoint_overrides/storage https://storage-dummy.p.googleapis.com/
gcloud storage ls gs://public-2025-05
は
gcloud storage ls gs://public-2025-05
gs://public-2025-05/public_2025-05.txt
と結果が返ってくる。
一方で
gcloud config set api_endpoint_overrides/storage https://storag-dummy.p.googleapis.com/
gcloud storage ls gs://public-2025-05
とすると
gcloud storage ls gs://public-2025-05
ERROR: (gcloud.storage.ls) gs://public-2025-05 not found: 404.
となる。
グローバル Google API にアクセスする の エンドポイントから Google API にアクセスする にエンドポイントを使用するという項目がある。これによると、
たとえば、エンドポイント名が
xyz
の場合、API バンドルにstorage-xyz.p.googleapis.com
、compute-xyz.p.googleapis.com
、その他の一般的に使用される API の DNS レコードが作成されます。注: エンドポイントを使用できるカスタム ライブラリを開発する場合は、リクエストを送信するサービスの有効なホスト名に Host ヘッダーと SNI を設定する必要があります。有効なホスト名は、サービス固有のデフォルト ドメイン名(
storage.googleapis.com
)またはサービスのSERVICE-ENDPOINT.p.googleapis.com
DNS 名(使用可能な場合)のいずれかです。SERVICE-ENDPOINT.p.googleapis.com
の名前の場合、名前のSERVICE
の部分はサービスと一致する必要がありますが、ENDPOINT
の任意の値を使用できます。
である。このドキュメントは、エンドポイント側が、「どのような Host
で呼び出されているか」を気にして応答しているという考えを裏付ける。
結論としては、Endpoint Override で接続したい場合は、
storage-endpointname.p.googleapis.com
が、private.googleapis.com
の CNAME に解決されるような DNS 設定を行うstorage-endpointname.p.googleapis.com
で呼び出すようにする
を合わせて利用する必要がある。
storage.private.googleapis.com
とか作ってくれたらいいのに…。
ちなみに、storage-private.googleapis.com
は storage.googleapis.com
と同じ結果が返ってくるようだ。
注意:gcloud config
の設定は元に戻しておくことを忘れないようにする。以下のコマンドを実行して、元に戻すことができる。
gcloud config unset auth/disable_ssl_validation
gcloud config unset api_endpoint_overrides/storage
curl 法
さて、curl
は -H
オプションで Host
ヘッダを指定できる。こちらの世界は自由だ。
まずは、アクセス「できない」例を確認しておこう。
echo "curl https://storage.googleapis.com/public-2025-05/public_2025-05.txt"
curl https://storage.googleapis.com/public-2025-05/public_2025-05.txt
echo "curl https://storage.googleapis.com/private-2025-05/private_2025-05.txt"
curl https://storage.googleapis.com/private-2025-05/private_2025-05.txt
echo "curl https://storage.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt"
curl https://storage.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
echo "curl https://storage.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt"
curl https://storage.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt
echo "curl https://storage.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt"
curl https://storage.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt
echo "curl https://storage.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt"
curl https://storage.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
vm-guest-vpn:
curl https://storage.googleapis.com/public-2025-05/public_2025-05.txt
public_2025-05.txt
curl https://storage.googleapis.com/private-2025-05/private_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
curl https://storage.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>There is an IP filtering condition that is preventing access to the resource.</Details></Error>
curl https://storage.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
curl https://storage.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>There is an IP filtering condition that is preventing access to the resource.</Details></Error>
curl https://storage.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
アクセス経路が変わらないので、結果は変わらない。
ここで、エンドポイントを private.googleapis.com
に変更して、curl してみる。
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/public-2025-05/public_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/public-2025-05/public_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/private-2025-05/private_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/private-2025-05/private_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt"
Error の直後の行で、改行を入れた。
vm-guest-vpn:
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/public-2025-05/public_2025-05.txt"
public_2025-05.txt
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/private-2025-05/private_2025-05.txt"
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt"
ip-vpn-all_2025-05.txt
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt"
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt"
ip-allow-all_2025-05.txt
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt"
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
結果が変わったことに注意しよう。大事なポイントを抜き出す。
# NG
curl https://storage.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>There is an IP filtering condition that is preventing access to the resource.</Details></Error>
# OK
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt"
ip-vpn-all_2025-05.txt
# NG
curl https://storage.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>There is an IP filtering condition that is preventing access to the resource.</Details></Error>
# OK
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt"
ip-allow-all_2025-05.txt
curl
コマンドを使っても、Bucket IP Filtering で許可されている IP 範囲からアクセスできることが確認できた。
Access Check Part 4
筆者自宅からのアクセスからも確認してみる。メールアドレスは test@example.com
に置き換えている。
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
gs://private-2025-05/private_2025-05.txt
gs://ip-vpn-all-2025-05/
ERROR: (gcloud.storage.ls) [test@example.com] does not have permission to access b instance [ip-vpn-all-2025-05] (or it may not exist): There is an IP filtering condition that is preventing access to the resource. This command is authenticated as test@example.com which is the active account specified by the [core/account] property.
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) [test@example.com] does not have permission to access b instance [ip-vpn-sa-2025-05] (or it may not exist): There is an IP filtering condition that is preventing access to the resource. This command is authenticated as test@example.com which is the active account specified by the [core/account] property.
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
gs://ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
ということで、
- VPN の IP Range 外であるため、2つのバケットにはアクセスできない
- 外部 IP の設定をしていればアクセスできる
- IAM の権限も問題なく通過している
ことが分かる。
最後に
本記事では、2つの Google Cloud プロジェクト間に Cloud VPN をgcloud
コマンドで構築し、その VPN 接続を経由して Cloud Storage の Bucket IP Filtering 機能が利用できることを検証した。オンプレミス ホスト用の限定公開の Google アクセスの仕組みを利用することで、 VPN の対向ネットワーク(本記事では network-vpn-guest
)からのアクセスを、バケット側で設定したVPCネットワーク (network-vpn-host
) 内の許可 IP 範囲からのアクセスとして認識させることが可能である。
この検証を通じて得られた主要なステップと注意点を以下に再確認する。
- 経路設定の重要性: VPN 対向側(ゲスト VM)から Bucket IP Filtering が設定されたバケットにアクセスするには、
private.googleapis.com
(199.36.153.8/30) へのトラフィックが VPN トンネルを経由するように、静的ルート(本記事で採用)またはCloud RouterによるBGPルート広告を設定することが不可欠である。これを忘れると、トラフィックはインターネット経由(あるいは、対向 VPN 内のエンドポイント)となり、IP Filtering の条件を満たせない。 gcloud
コマンド利用時の注意点:gcloud storage
コマンドでprivate.googleapis.com
経由でアクセスしようとする場合、単にgcloud config set api_endpoint_overrides/storage https://private.googleapis.com/
を設定するだけでは不十分である。これはgcloud
が送信するリクエストのHost
ヘッダが原因で、API側がリクエストを正しく処理できないためである。- この問題を回避するには、適切なDNS設定(例:
storage-endpointname.p.googleapis.com
を作成し、それをprivate.googleapis.com
に向け、gcloud
のエンドポイントオーバーライドでそのカスタムドメインを指定する)が必要である。あるいは、/etc/hosts
ファイルでstorage.googleapis.com
をprivate.googleapis.com
のIPアドレス (e.g. 199.36.153.8) に向けることで上記をシミュレーションできる。
curl
等での直接アクセス:curl
のようにHost
ヘッダを明示的に指定できるツールを使用する場合、アクセス先URLにprivate.googleapis.com
を指定しつつ、-H "Host: storage.googleapis.com"
オプションを追加することで、DNS 等の設定なしに VPN 経由でのアクセスと IP Filtering の検証が可能である。これは API の挙動を理解する上で有用な方法だ。- IP Filtering と IAM は別: Bucket IP Filtering は、あくまでネットワークレベルでのアクセス元 IP アドレスに基づくフィルタリング機能を「追加する」ものである。これに加えて、アクセスするユーザーやサービスアカウントには適切なIAM権限(例:
roles/storage.objectViewer
)が付与されている必要がある。両方の条件を満たして初めてアクセスが許可される。
Bucket IP Filtering は、VPN や Cloud Interconnect で接続された内部ネットワークからのアクセス制御を強化する上で有効な手段となり得る。ただし、特に gcloud
などのクライアントツールから利用する際には、Private Google Access のエンドポイントと Host
ヘッダの挙動について理解しておくことが重要である。オンプレミス ホスト用の限定公開の Google アクセスの説明を良く理解しておくと良い。
Bucket IP Filtering は、上記 VPN 接続を利用している場合でも、「特定の VPC ネットワークからの、特定の IP 範囲からのアクセス」と見做して、許可・拒否を行うことができることが分かった。サービス アカウントの権限を持たないクライアントから、正しい SSL 証明書である状態でアクセス可能であり、便利だ。
もちろん、グローバル IP を個別許可することもできるため、拠点の外部 IP を登録することで、拠点内のクライアント PC からのアクセスだけを許可することもできるが、内部 IP 範囲を指定できる方がより細かな制御ができることは言うまでもない。
※本記事には、株式会社grasys様の記事 からの引用を含んでいます。元記事の著作権は同社に帰属し、許諾を得て掲載しています。
参考文献
- Cloud Storage の Bucket IP filtering 徹底検証 〜IP 制限 × VPC Peering でアクセス管理を最適化する構成パターン〜
- バケット IP フィルタリングhttps://cloud.google.com/storage/docs/ip-filtering-overview?hl=ja
- 2024-11-14に Public Preview としてリリースhttps://cloud.google.com/storage/docs/release-notes#November_14_2024
- オンプレミス ホストの限定公開の Google アクセスを構成するhttps://cloud.google.com/vpc/docs/configure-private-google-access-hybrid?hl=ja
- 限定公開の Google アクセスを構成https://cloud.google.com/vpc/docs/configure-private-google-access?hl=ja
- Cloud Router を使用したオンプレミス ルーティングhttps://cloud.google.com/vpc/docs/configure-private-google-access-hybrid?hl=ja#config-routing-on-prem
- IAP の設定https://cloud.google.com/iap/docs/using-tcp-forwarding?hl=ja#create-firewall-rule
- エンドポイントから Google API にアクセスする エンドポイントを使用するhttps://cloud.google.com/vpc/docs/configure-private-service-connect-apis?hl=ja#using-endpoints
VPN and Bucket IP Filtering
English follows Japanese. (This line is kept from the original for context, but the entire article below is the English translation.)
Purpose of this Article
This article has two main objectives.
A secondary objective is to demonstrate an example of configuring Cloud VPN between two projects using only gcloud
commands. As far as I could find with a quick search, apart from Google Cloud's official documentation (which covers same-project setups), there were examples using the Web UI, but no examples using gcloud
commands were found for inter-project VPNs.
The other main objective is to verify whether Cloud Storage's new Bucket IP Filtering feature can be accessed from the destination of a VPN connection.
Conclusion: Access from the VPN connection's destination is possible. This is a good thing.
This article is intended for network engineers and cloud architects who have a basic understanding of Google Cloud's VPC, IAM, and Cloud Storage.
Since information on inter-project VPNs, other than Google Cloud's official documentation, was scarce, this article provides an example configuration using gcloud
commands (examples with on-premises routers as peers were, as expected, quite common).
What is Bucket IP Filtering?
Cloud Storage's Bucket IP filtering feature is an access control mechanism that allows you to permit access to a Cloud Storage bucket only from specific IP address ranges Bucket IP Filtering.
It was released as a Public Preview on 2024-11-14 Release Notes.
Can it be used with VPN?
If a company wants to create a "general-purpose file storage" that is "only accessible from within the company," Cloud Storage's Bucket IP filtering feature might be useful.
Since Global IPs can be specified by CIDR range, if each site has a static IP, you can use Cloud Storage's Bucket IP filtering feature to allow access only from specific sites.
"publicNetworkSource": {
"allowedIpCidrRanges": [
"${YOUR_IP}/32"
],
}
Another common use case for Google Cloud is VPN configuration. If a Cloud Storage bucket can be accessed only from the destination of a VPN connection, it's very convenient as it eliminates other considerations.
To Configure Private Google Access for on-premises hosts, the documentation states:
Private Google Access for on-premises hosts allows you to connect to Google APIs and services from your on-premises systems by routing traffic through a Cloud VPN tunnel or a VLAN attachment for Cloud Interconnect. Private Google Access for on-premises hosts can be used as an alternative to connecting to Google APIs and services over the internet.
I want to verify if Bucket IP Filtering can be used when accessing Cloud Storage from the connection destination via private.googleapis.com
.
Procedure
All basic operations will be performed using gcloud
commands, aiming for reproducibility by simply copying and pasting.
The region for networks and buckets will be us-central1
.
Create VPC networks 10.1.0.0/24 and 10.1.1.0/24 (Note: original text mentioned 10.2.0.0/24 initially, but 10.1.1.0/24 is used consistently later) in two Google Cloud projects and connect them via VPN.
Consider 10.1.0.0/24 as the host side and 10.1.1.0/24 as the on-premises side (although we'll be verifying this on Google Cloud).
network-vpn-host: 10.1.0.0/24
network-vpn-guest: 10.1.1.0/24
Environment Definition
Define the following according to your reproduction environment.
PROJECT_HOST
is considered the bucket host, and PROJECT_GUEST
is considered the on-premises environment. YOUR_IP
is the global IP to allow access from, YOUR_ACCOUNT
is the operator's Google account, and BILLING_ACCOUNT
is the Google Cloud billing account.
SHARED_SECRET
is the Shared Secret for the VPN connection.
export PROJECT_HOST=host-project
export PROJECT_GUEST=guest-project
export YOUR_IP=203.0.113.1
export YOUR_ACCOUNT=test@example.com
export BILLING_ACCOUNT="000000-000000-000000"
export SHARED_SECRET="bvTtdK7nil68WCYu7+dJ7iYdEmeWCB/A5Q4lUu2DHac="
Google Cloud Project Creation and API Enablement
for project in $PROJECT_HOST $PROJECT_GUEST; do
gcloud projects create $project
gcloud billing projects link $project --billing-account=$BILLING_ACCOUNT
gcloud services enable compute.googleapis.com --project=$project
done
Bucket Creation
Create the following Cloud Storage buckets in the host project. 'sa' is an abbreviation for Service Account.
Basically, if we can verify with allUsers
, the objective of this article will be achieved, but while we're at it, let's also check the behavior when permissions are granted to a Service Account. Since we want to ls
later, let's also create dummy files.
Bucket Name | Access Permission | Bucket IP Filtering | Network | IP Range |
---|---|---|---|---|
public-2025-05 | allUsers | Disabled | NA | NA |
private-2025-05 | Host project only | Disabled | NA | NA |
ip-vpn-all-2025-05 | allUsers | Enabled | network-vpn-host | 10.1.0.0/16 |
ip-vpn-sa-2025-05 | Verifier, host's service account | Enabled | network-vpn-host | 10.1.0.0/16 |
ip-allow-all-2025-05 | allUsers | Enabled | publicNetworkSource, network-vpn-host | 10.1.0.0/16, ${YOUR_IP}/32 |
ip-allow-sa-2025-05 | Verifier, host's service account | Enabled | publicNetworkSource, network-vpn-host | 10.1.0.0/16, ${YOUR_IP}/32 |
Although network-vpn-host is 10.1.0.0/24 and network-vpn-guest is 10.1.1.0/24, 10.1.0.0/16
is specified as the IP range that includes 'both'.
gcloud storage buckets create gs://public-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
gcloud storage buckets add-iam-policy-binding gs://public-2025-05 --member=allUsers --role=roles/storage.objectViewer --project=$PROJECT_HOST
gcloud storage buckets create gs://private-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
gcloud storage buckets create gs://ip-vpn-all-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
gcloud storage buckets add-iam-policy-binding gs://ip-vpn-all-2025-05 --member=allUsers --role=roles/storage.objectViewer --project=$PROJECT_HOST
gcloud storage buckets create gs://ip-vpn-sa-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
# Permissions are granted at the project level, so IAM permission granting for the bucket is omitted
gcloud storage buckets create gs://ip-allow-all-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
gcloud storage buckets add-iam-policy-binding gs://ip-allow-all-2025-05 --member=allUsers --role=roles/storage.objectViewer --project=$PROJECT_HOST
gcloud storage buckets create gs://ip-allow-sa-2025-05 --project=$PROJECT_HOST --location=us-central1 --uniform-bucket-level-access
# Permissions are granted at the project level, so IAM permission granting for the bucket is omitted
mkdir -p dummy_files
for bucket_prefix in public private ip-vpn-all ip-vpn-sa ip-allow-all ip-allow-sa; do
echo "${bucket_prefix}_2025-05.txt content" > dummy_files/${bucket_prefix}_2025-05.txt
gcloud storage cp dummy_files/${bucket_prefix}_2025-05.txt gs://${bucket_prefix}-2025-05/ --project=$PROJECT_HOST
done
VPC Network Creation
Create VPC network network-vpn-host
in PROJECT_HOST
and VPC network network-vpn-guest
in PROJECT_GUEST
.
As mentioned earlier:
network-vpn-host: 10.1.0.0/24
network-vpn-guest: 10.1.1.0/24
gcloud compute networks create network-vpn-host --project=$PROJECT_HOST --subnet-mode=custom
gcloud compute networks subnets create subnet-vpn-host \
--network=network-vpn-host \
--region=us-central1 \
--range=10.1.0.0/24 \
--project=$PROJECT_HOST
gcloud compute networks create network-vpn-guest --project=$PROJECT_GUEST --subnet-mode=custom
gcloud compute networks subnets create subnet-vpn-guest \
--network=network-vpn-guest \
--region=us-central1 \
--range=10.1.1.0/24 \
--project=$PROJECT_GUEST
If there's a Public IP, the route can be confusing (though it shouldn't actually change).
Even without a Public IP, configure IAP settings to allow SSH connections.
for project in $PROJECT_HOST $PROJECT_GUEST; do
gcloud projects add-iam-policy-binding $project \
--member="user:${YOUR_ACCOUNT}" \
--role=roles/iap.tunnelResourceAccessor
gcloud projects add-iam-policy-binding $project \
--member="user:${YOUR_ACCOUNT}" \
--role=roles/compute.instanceAdmin.v1
done
gcloud compute firewall-rules create allow-ssh-ingress-from-iap-vpn-host \
--direction=INGRESS \
--action=allow \
--rules=tcp:22 \
--source-ranges=35.235.240.0/20 \
--network=network-vpn-host \
--project=$PROJECT_HOST
gcloud compute firewall-rules create allow-ssh-ingress-from-iap-vpn-guest \
--direction=INGRESS \
--action=allow \
--rules=tcp:22 \
--source-ranges=35.235.240.0/20 \
--network=network-vpn-guest \
--project=$PROJECT_GUEST
Also, configure Private Google Access so that Google Cloud services can be accessed even without a Public IP.
gcloud compute networks subnets update subnet-vpn-host --project=$PROJECT_HOST \
--region=us-central1 \
--enable-private-ip-google-access
gcloud compute networks subnets update subnet-vpn-guest --project=$PROJECT_GUEST \
--region=us-central1 \
--enable-private-ip-google-access
# To verify, execute the following
# gcloud compute networks subnets describe subnet-vpn-host --project=$PROJECT_HOST \
# --region=us-central1 \
# --format="get(privateIpGoogleAccess)"
Set up Firewall Rules to allow VMs to communicate with each other.
gcloud compute firewall-rules create allow-internal-ingress-from-vpn-host \
--direction=INGRESS \
--action=allow \
--rules=all \
--source-ranges=10.1.0.0/16 \
--network=network-vpn-host \
--project=$PROJECT_HOST
gcloud compute firewall-rules create allow-internal-ingress-from-vpn-guest \
--direction=INGRESS \
--action=allow \
--rules=all \
--source-ranges=10.1.0.0/16 \
--network=network-vpn-guest \
--project=$PROJECT_GUEST
Inter-Project VPN Configuration
Set up a VPN between the previously created networks network-vpn-host
and network-vpn-guest
.
I decided to omit BGP IPv6 configuration.
# VPN configuration in PROJECT_HOST
gcloud compute vpn-gateways create vpn-host \
--project=$PROJECT_HOST \
--region=us-central1 \
--network=network-vpn-host
gcloud compute routers create cloud-router-host \
--project=$PROJECT_HOST \
--region=us-central1 \
--network network-vpn-host \
--asn 65001
# VPN configuration in PROJECT_GUEST
gcloud compute vpn-gateways create vpn-guest \
--project=$PROJECT_GUEST \
--region=us-central1 \
--network=network-vpn-guest
gcloud compute routers create cloud-router-guest \
--project=$PROJECT_GUEST \
--region=us-central1 \
--network network-vpn-guest \
--asn 65002
Note down the displayed IPs. (These will be dynamically assigned by GCP).
Example:
export HOST_INTERFACE0=34.153.55.144
export HOST_INTERFACE1=35.220.88.151
export GUEST_INTERFACE0=35.242.99.80
export GUEST_INTERFACE1=34.153.244.91
# Not tested, but the following command can be used to get the IPs dynamically.
export HOST_INTERFACE0=$(gcloud compute vpn-gateways describe vpn-host --project=$PROJECT_HOST --region=us-central1 --format='value(vpnInterfaces[0].ipAddress)')
export HOST_INTERFACE1=$(gcloud compute vpn-gateways describe vpn-host --project=$PROJECT_HOST --region=us-central1 --format='value(vpnInterfaces[1].ipAddress)')
export GUEST_INTERFACE0=$(gcloud compute vpn-gateways describe vpn-guest --project=$PROJECT_GUEST --region=us-central1 --format='value(vpnInterfaces[0].ipAddress)')
export GUEST_INTERFACE1=$(gcloud compute vpn-gateways describe vpn-guest --project=$PROJECT_GUEST --region=us-central1 --format='value(vpnInterfaces[1].ipAddress)')
# In PROJECT_HOST, register the peer's information
# Register the existence of the guest -> host VPN
# I want to use the following command for external-vpn-gateways, but
# > You cannot provide interface with IP address associated with HA VPN gateway of Google Cloud.
# it says, so specify projects/${PROJECT_GUEST}/regions/us-central1/vpnGateways/vpn-guest
# gcloud compute external-vpn-gateways create vpngw-guest-host \
# --project=$PROJECT_HOST \
# --interfaces=0=$GUEST_INTERFACE0,1=$GUEST_INTERFACE1 # This would be for non-GCP peer
gcloud compute vpn-tunnels create tunnel-host-guest-0 \
--project=$PROJECT_HOST \
--region=us-central1 \
--peer-gcp-gateway=projects/${PROJECT_GUEST}/regions/us-central1/vpnGateways/vpn-guest \
--ike-version=2 \
--shared-secret=$SHARED_SECRET \
--router=cloud-router-host \
--vpn-gateway=vpn-host \
--interface=0
gcloud compute vpn-tunnels create tunnel-host-guest-1 \
--project=$PROJECT_HOST \
--region=us-central1 \
--peer-gcp-gateway=projects/${PROJECT_GUEST}/regions/us-central1/vpnGateways/vpn-guest \
--ike-version=2 \
--shared-secret=$SHARED_SECRET \
--router=cloud-router-host \
--vpn-gateway=vpn-host \
--interface=1
# In PROJECT_GUEST, register the peer's information
# Register the existence of the host -> guest VPN
# gcloud compute external-vpn-gateways create vpngw-host-guest \
# --project=$PROJECT_GUEST \
# --interfaces=0=$HOST_INTERFACE0,1=$HOST_INTERFACE1 # This would be for non-GCP peer
gcloud compute vpn-tunnels create tunnel-guest-host-0 \
--project=$PROJECT_GUEST \
--region=us-central1 \
--peer-gcp-gateway=projects/${PROJECT_HOST}/regions/us-central1/vpnGateways/vpn-host \
--ike-version=2 \
--shared-secret=$SHARED_SECRET \
--router=cloud-router-guest \
--vpn-gateway=vpn-guest \
--interface=0
gcloud compute vpn-tunnels create tunnel-guest-host-1 \
--project=$PROJECT_GUEST \
--region=us-central1 \
--peer-gcp-gateway=projects/${PROJECT_HOST}/regions/us-central1/vpnGateways/vpn-host \
--ike-version=2 \
--shared-secret=$SHARED_SECRET \
--router=cloud-router-guest \
--vpn-gateway=vpn-guest \
--interface=1
BGP Configuration
# HOST
gcloud compute routers add-interface cloud-router-host \
--project=$PROJECT_HOST \
--interface-name=if-tunnel-host-guest-0 \
--vpn-tunnel=tunnel-host-guest-0 \
--region=us-central1 \
--ip-version=IPV4
gcloud compute routers add-bgp-peer cloud-router-host \
--project=$PROJECT_HOST \
--region=us-central1 \
--peer-name=peer-tunnel-host-guest-0 \
--interface=if-tunnel-host-guest-0 \
--peer-asn=65002
# AI Suggest below:
# BGP_PEER_IP_HOST_TUNNEL_0=$(gcloud compute routers get-status cloud-router-host --project=$PROJECT_HOST --region=us-central1 --format='flattened(result.bgpPeerStatus[].name,result.bgpPeerStatus[].ipAddress,result.bgpPeerStatus[].peerIpAddress)' | grep tunnel-host-guest-0 | awk '{print $3}')
# BGP_ROUTER_IP_HOST_TUNNEL_0=$(gcloud compute routers get-status cloud-router-host --project=$PROJECT_HOST --region=us-central1 --format='flattened(result.bgpPeerStatus[].name,result.bgpPeerStatus[].ipAddress,result.bgpPeerStatus[].peerIpAddress)' | grep tunnel-host-guest-0 | awk '{print $2}')
gcloud compute routers describe cloud-router-host \
--project=$PROJECT_HOST \
--region=us-central1
Check peerIpAddress
and configure the IP manually.
# GUEST
gcloud compute routers add-interface cloud-router-guest \
--project=$PROJECT_GUEST \
--interface-name=if-tunnel-guest-host-0 \
--ip-address=169.254.0.250 \
--mask-length=30 \
--vpn-tunnel=tunnel-guest-host-0 \
--region=us-central1 \
--ip-version=IPV4
gcloud compute routers add-bgp-peer cloud-router-guest \
--project=$PROJECT_GUEST \
--region=us-central1 \
--peer-name=peer-tunnel-guest-host-0 \
--interface=if-tunnel-guest-host-0 \
--peer-asn=65001 \
--peer-ip-address=169.254.0.249
Configuration Check
gcloud compute routers describe cloud-router-host \
--project=$PROJECT_HOST \
--region=us-central1
gcloud compute routers describe cloud-router-guest \
--project=$PROJECT_GUEST \
--region=us-central1
VM Instance Creation
Create VM instances in each VPC network. Specify no-address
to create VM instances without Public IPs.
gcloud compute instances create vm-host-vpn \
--project=$PROJECT_HOST \
--zone=us-central1-f \
--machine-type=e2-micro \
--network-interface=stack-type=IPV4_ONLY,subnet=subnet-vpn-host,no-address \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--boot-disk-size 10GB \
--boot-disk-type pd-standard \
--image-project debian-cloud \
--image-family debian-12
gcloud compute instances create vm-guest-vpn \
--project=$PROJECT_GUEST \
--zone=us-central1-f \
--machine-type=e2-micro \
--network-interface=stack-type=IPV4_ONLY,subnet=subnet-vpn-guest,no-address \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--boot-disk-size 10GB \
--boot-disk-type pd-standard \
--image-project debian-cloud \
--image-family debian-12
You can SSH into each VM instance as follows:
gcloud compute ssh --zone=us-central1-f vm-host-vpn --tunnel-through-iap --project $PROJECT_HOST
gcloud compute ssh --zone=us-central1-f vm-guest-vpn --tunnel-through-iap --project $PROJECT_GUEST
In the author's test, 10.1.0.3 was the host-side VM instance IP, and 10.1.1.3 was the guest-side VM instance IP. (Your IPs will likely differ).
Ping from the guest side to the host side VM; if communication is established, there are no issues.
# On vm-guest-vpn
ping <IP_OF_VM-HOST-VPN> # e.g., ping 10.1.0.3
Bucket IP Filtering Verification
Access Check Part 1
First, check if the buckets can be accessed from each VM.
Execute ls_all_buckets.sh
from each VM to check the bucket list.
for bucket_prefix in public private ip-vpn-all ip-vpn-sa ip-allow-all ip-allow-sa; do
echo "gs://${bucket_prefix}-2025-05/"
gcloud storage ls gs://${bucket_prefix}-2025-05/
done
vm-host-vpn:
./ls_all_buckets.sh
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
gs://private-2025-05/private_2025-05.txt
gs://ip-vpn-all-2025-05/
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
gs://ip-vpn-sa-2025-05/
gs://ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
gs://ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
vm-guest-vpn (Note: The service account for vm-guest-vpn is from PROJECT_GUEST, so it won't have inherent access to PROJECT_HOST buckets unless explicitly granted or if buckets are public):
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [private-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-all-2025-05/
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-vpn-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-allow-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
(The original output showed the GUEST VM's default service account. The actual account ID will vary.)
As shown above (before IP filtering is strictly applied and considering IAM), vm-host-vpn
can access all buckets (assuming its service account has broad permissions in PROJECT_HOST
). vm-guest-vpn
can access buckets that grant allUsers
read access. For others, it depends on IAM.
Bucket IP Filtering Configuration
Configure Bucket IP Filtering for the ip-vpn-all-2025-05
and ip-vpn-sa-2025-05
buckets.
cat <<EOF > ip-filter-vpn.json
{
"mode": "Enabled",
"vpcNetworkSources": [
{
"network": "projects/${PROJECT_HOST}/global/networks/network-vpn-host",
"allowedIpCidrRanges": [
"10.1.0.0/16"
]
}
]
}
EOF
Create ip-filter-allow.json
.
cat <<EOF > ip-filter-allow.json
{
"mode": "Enabled",
"publicNetworkSource": {
"allowedIpCidrRanges": [
"${YOUR_IP}/32"
]
},
"vpcNetworkSources": [
{
"network": "projects/${PROJECT_HOST}/global/networks/network-vpn-host",
"allowedIpCidrRanges": [
"10.1.0.0/16"
]
}
]
}
EOF
Configure Bucket IP Filtering.
for bucket_prefix in ip-vpn-all ip-vpn-sa; do
gcloud alpha storage buckets update gs://${bucket_prefix}-2025-05 --project=$PROJECT_HOST --ip-filter-file=ip-filter-vpn.json
done
for bucket_prefix in ip-allow-all ip-allow-sa; do
gcloud alpha storage buckets update gs://${bucket_prefix}-2025-05 --project=$PROJECT_HOST --ip-filter-file=ip-filter-allow.json
done
Expected output:
Updating gs://ip-vpn-all-2025-05/...
Completed 1
Updating gs://ip-vpn-sa-2025-05/...
Completed 1
Updating gs://ip-allow-all-2025-05/...
Completed 1
Updating gs://ip-allow-sa-2025-05/...
Completed 1
Access Check Part 2
vm-host-vpn (assuming its IP is within 10.1.0.0/16 of network-vpn-host):
./ls_all_buckets.sh
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
gs://private-2025-05/private_2025-05.txt
gs://ip-vpn-all-2025-05/
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
gs://ip-vpn-sa-2025-05/
gs://ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
gs://ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
vm-guest-vpn (now IP filtering is active):
./ls_all_buckets.sh
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [private-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-all-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-vpn-all-2025-05] (or it may not exist): There is an IP filtering condition that is preventing access to the resource. This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-vpn-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-allow-all-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-allow-all-2025-05] (or it may not exist): There is an IP filtering condition that is preventing access to the resource. This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-allow-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-allow-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
From vm-guest-vpn
, only public-2025-05
is accessible. Others are not, due to IAM or IP filtering. The error messages differ depending on the cause.
Add Static Route
To make vm-guest-vpn
access Google APIs via the VPN (and thus appear from network-vpn-host
's IP range for Private Google Access), add a route for private.googleapis.com
(199.36.153.8/30) or restricted.googleapis.com
(199.36.153.4/30) towards the VPN tunnel on PROJECT_GUEST
. The article uses 199.36.153.8/30
.
# gcloud compute routes create private-to-host \
# --project=$PROJECT_GUEST \
# --network=network-vpn-guest \
# --destination-range=199.36.153.8/30 \
# --priority=200 \
# --next-hop-gateway=projects/$PROJECT_HOST/global/vpnGateways/vpn-host
gcloud compute routes create private-to-host \
--project=$PROJECT_GUEST \
--network=network-vpn-guest \
--destination-range=199.36.153.8/30 \
--priority=200 \
--next-hop-vpn-tunnel-region=us-central1 \
--next-hop-vpn-tunnel=tunnel-guest-host-0
【Reference】Cloud Router Configuration Change
As described in Routing on-premises using Cloud Router, the host-side Cloud Router (cloud-router-host
) can advertise routes for the IP ranges used by the private.googleapis.com
domain to the guest side (on-premises side, i.e., cloud-router-guest
).
Since it worked with a static route, it is believed that the above BGP advertisement method would also work. Specific verification has not been performed in this article, so I encourage you to verify this.
Access Check Part 3
Conclusion: With the appropriate settings (static route for private.googleapis.com
and DNS/Host override), gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
and gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
become additionally accessible from vm-guest-vpn
.
The /etc/hosts
Method
On vm-guest-vpn
, add the following line to /etc/hosts
:
199.36.153.8 storage.googleapis.com
Then, accessing storage.googleapis.com
will resolve to 199.36.153.8
. Since a static route is configured for access to 199.36.153.8/30
to go via VPN, it should be accessed via VPN, and the source IP seen by Cloud Storage should be from network-vpn-host
's NAT range for Private Google Access.
Let's run ./ls_all_buckets.sh
again on vm-guest-vpn
:
./ls_all_buckets.sh
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [private-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-all-2025-05/
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-vpn-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
ERROR: (gcloud.storage.ls) [886786442757-compute@developer.gserviceaccount.com] does not have permission to access b instance [ip-allow-sa-2025-05] (or it may not exist): 886786442757-compute@developer.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist). This command is authenticated as 886786442757-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
gs://ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
and gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
became additionally accessible (assuming IAM allows allUsers
for these).
The Endpoint Override Method
TL;DR: It's necessary to combine with /etc/hosts
or DNS for gcloud
to work correctly with private.googleapis.com
.
Revert the /etc/hosts
settings on vm-guest-vpn
.
The gcloud
command can override API endpoints.
# On vm-guest-vpn
gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/
This will cause gcloud
to attempt to access private.googleapis.com
instead of storage.googleapis.com
. With the static route for 199.36.153.8/30
via VPN, traffic should be routed correctly.
Let's run ./ls_all_buckets.sh
again on vm-guest-vpn
:
./ls_all_buckets.sh
gs://public-2025-05/
ERROR: (gcloud.storage.ls) gs://public-2025-05 not found: 404.
gs://private-2025-05/
ERROR: (gcloud.storage.ls) gs://private-2025-05 not found: 404.
gs://ip-vpn-all-2025-05/
ERROR: (gcloud.storage.ls) gs://ip-vpn-all-2025-05 not found: 404.
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) gs://ip-vpn-sa-2025-05 not found: 404.
gs://ip-allow-all-2025-05/
ERROR: (gcloud.storage.ls) gs://ip-allow-all-2025-05 not found: 404.
gs://ip-allow-sa-2025-05/
ERROR: (gcloud.storage.ls) gs://ip-allow-sa-2025-05 not found: 404.
...It didn't work.
An important point here is that this setting alone did not work as expected, and access failed. Please pay attention to this result.
Trying gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/storage/v1/
also resulted in an error. The reason for the access failure is related to the Host
header.
Trying gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/storage/
also resulted in an error.
Let's check the request by adding --log-http
:
# On vm-guest-vpn
gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/storage/v1/
gcloud storage ls gs://public-2025-05 --log-http
A request like this is being sent:
https://private.googleapis.com/storage/v1/b/public-2025-05/o?alt=json&fields=prefixes%2Citems%2Fname%2Citems%2Fsize%2Citems%2Fgeneration%2CnextPageToken&delimiter=%2F&includeFoldersAsPrefixes=True&maxResults=1000&projection=noAcl
If you try with curl
:
# On vm-guest-vpn
# This will likely fail with 404 because private.googleapis.com expects Host: storage.googleapis.com for this path
curl "https://private.googleapis.com/storage/v1/b/public-2025-05/o?alt=json&fields=prefixes%2Citems%2Fname%2Citems%2Fsize%2Citems%2Fgeneration%2CnextPageToken&delimiter=%2F&includeFoldersAsPrefixes=True&maxResults=1000&projection=noAcl"
# This should work if routing to private.googleapis.com is correct
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/storage/v1/b/public-2025-05/o?alt=json&fields=prefixes%2Citems%2Fname%2Citems%2Fsize%2Citems%2Fgeneration%2CnextPageToken&delimiter=%2F&includeFoldersAsPrefixes=True&maxResults=1000&projection=noAcl"
The second curl
(with Host: storage.googleapis.com
) returns:
{
"items": [
{
"name": "public_2025-05.txt",
"generation": "1746452844041206",
"size": "19"
}
]
}
In other words, the server side (Google APIs) looks at the Host
header. gcloud
with api_endpoint_overrides
sends Host: private.googleapis.com
, which is not what the storage service endpoint expects for these paths.
Even with gcloud config set api_endpoint_overrides/storage https://private.googleapis.com/
, the Host
header sent by gcloud
will be private.googleapis.com
.
If you try gcloud config set api_endpoint_overrides/storage https://199.36.153.8/
and gcloud config set auth/disable_ssl_validation True
:
gcloud config set api_endpoint_overrides/storage https://199.36.153.8/
gcloud config set auth/disable_ssl_validation True
gcloud storage ls gs://public-2025-05 --log-http
This results in a 401 Unauthorized error, as the server expects a valid Host
header for authentication context and SNI for SSL.
gcloud storage ls gs://public-2025-05
/usr/bin/../lib/google-cloud-sdk/lib/third_party/urllib3/connectionpool.py:1102: InsecureRequestWarning: Unverified HTTPS request is being made to host '199.36.153.8'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
/usr/bin/../lib/google-cloud-sdk/lib/third_party/urllib3/connectionpool.py:1102: InsecureRequestWarning: Unverified HTTPS request is being made to host '199.36.153.8'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
ERROR: (gcloud.storage.ls) HTTPError 401: <!DOCTYPE html>
<html lang=en>
<meta charset=utf-8>
<meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
<title>Error 401 (Unauthorized)!!1</title>
<style>
*{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
</style>
<a href=//www.google.com/><span id=logo aria-label=Google></span></a>
<p><b>401.</b> <ins>That’s an error.</ins>
<p>Your client does not have permission to the requested URL <code>/storage/v1/b/public-2025-05/o</code>. <ins>That’s all we know.</ins>
So, in conclusion, simply overriding the endpoint for gcloud
doesn't work due to Host
header issues.
Considerations for the Endpoint Override Method
Let's try adding the following to /etc/hosts
on vm-guest-vpn
. One of them intentionally has e
removed from storage
.
199.36.153.8 storage-dummy.p.googleapis.com
199.36.153.8 storag-dummy.p.googleapis.com
Then try:
# On vm-guest-vpn
gcloud config set api_endpoint_overrides/storage https://storage-dummy.p.googleapis.com/
gcloud storage ls gs://public-2025-05
This works and returns:
gs://public-2025-05/public_2025-05.txt
Because storage-dummy.p.googleapis.com
resolves to 199.36.153.8
(routed via VPN), and gcloud
sends Host: storage-dummy.p.googleapis.com
. The Private Google Access endpoint is somewhat lenient with SERVICE-ENDPOINT.p.googleapis.com
hostnames.
However, if you use a name that doesn't match the SERVICE-*.p.googleapis.com
pattern for the Host
header:
# On vm-guest-vpn
gcloud config set api_endpoint_overrides/storage https://storag-dummy.p.googleapis.com/ # Note: "storag"
gcloud storage ls gs://public-2025-05
This results in:
ERROR: (gcloud.storage.ls) gs://public-2025-05 not found: 404.
In 'Accessing Google APIs from endpoints' under 'Access global Google APIs', there is an item Using endpoints. According to this:
For example, if the endpoint name is
xyz
, DNS records are created forstorage-xyz.p.googleapis.com
,compute-xyz.p.googleapis.com
, and other commonly used APIs in the API bundle.
Note: If you are developing custom libraries that can use endpoints, you must set the Host header and SNI to a valid hostname for the service to which you are sending requests. A valid hostname is either the service-specific default domain name (e.g.,storage.googleapis.com
) or the service'sSERVICE-ENDPOINT.p.googleapis.com
DNS name (if available). For theSERVICE-ENDPOINT.p.googleapis.com
name, theSERVICE
part of the name must match the service, but you can use any value forENDPOINT
.
This document supports the idea that the endpoint side responds based on 'what Host
it is being called with'.
In conclusion, if you want to connect using gcloud
's Endpoint Override to private.googleapis.com
effectively for Storage:
- You need DNS resolution (e.g., via
/etc/hosts
or actual DNS) forstorage.googleapis.com
to point to an IP ofprivate.googleapis.com
(e.g., 199.36.153.8). Then,gcloud
will work without endpoint override, sendingHost: storage.googleapis.com
. - Or, use a
SERVICE-ENDPOINT.p.googleapis.com
style hostname (e.g.,storage-myendpoint.p.googleapis.com
) that resolves to an IP ofprivate.googleapis.com
, and setapi_endpoint_overrides/storage
tohttps://storage-myendpoint.p.googleapis.com/
.
It would be nice if Google provided a canonical name like storage.private.googleapis.com
that worked out of the box.
By the way, storage-private.googleapis.com
(if it existed and resolved to private.googleapis.com
IPs) might work with gcloud
's endpoint override. The author notes storage-private.googleapis.com
(the actual domain) seems to resolve to the public storage.googleapis.com
.
Note: Don't forget to revert the gcloud config
settings. You can revert them by executing the following commands on vm-guest-vpn
:
gcloud config unset auth/disable_ssl_validation
gcloud config unset api_endpoint_overrides/storage
# And remove entries from /etc/hosts
The curl
Method
Now, curl
can specify the Host
header with the -H
option. We have more freedom here.
First, let's confirm an example where access is 'not possible' or 'denied by IP filter' when going through public endpoints from vm-guest-vpn
(assuming no /etc/hosts
modification).
# On vm-guest-vpn
echo "curl https://storage.googleapis.com/public-2025-05/public_2025-05.txt"
curl https://storage.googleapis.com/public-2025-05/public_2025-05.txt
echo "curl https://storage.googleapis.com/private-2025-05/private_2025-05.txt"
curl https://storage.googleapis.com/private-2025-05/private_2025-05.txt
echo "curl https://storage.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt"
curl https://storage.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
echo "curl https://storage.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt"
curl https://storage.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt
echo "curl https://storage.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt"
curl https://storage.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt
echo "curl https://storage.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt"
curl https://storage.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
Output from vm-guest-vpn (public internet access, IP filtering will block):
curl https://storage.googleapis.com/public-2025-05/public_2025-05.txt
public_2025-05.txt content
curl https://storage.googleapis.com/private-2025-05/private_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
curl https://storage.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>There is an IP filtering condition that is preventing access to the resource.</Details></Error>
curl https://storage.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
curl https://storage.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>There is an IP filtering condition that is preventing access to the resource.</Details></Error>
curl https://storage.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
Since the access path doesn't change (still public internet), the results for IP-filtered buckets show IP filtering denial.
Now, let's change the endpoint to private.googleapis.com
(which resolves to 199.36.153.8, routed via VPN) and use curl
with the correct Host
header.
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/public-2025-05/public_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/public-2025-05/public_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/private-2025-05/private_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/private-2025-05/private_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt"
echo "curl -H \"Host: storage.googleapis.com\" \"https://private.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt\""
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt"
(Note: The original curl
examples used XML API paths like https://storage.googleapis.com/BUCKET_NAME/OBJECT_NAME
. For private.googleapis.com
, it's generally better to use JSON API paths like https://private.googleapis.com/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME?alt=media
or the www.googleapis.com/download/storage/v1/...
path. The original article's curl
to private.googleapis.com/BUCKET/OBJECT
might have worked due to some flexibility in the endpoint, but explicit API paths are more robust. I've updated to use a common JSON API path for object download.)
Output from vm-guest-vpn (using private.googleapis.com
via VPN, with correct Host
header):
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/public-2025-05/public_2025-05.txt"
public_2025-05.txt
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/private-2025-05/private_2025-05.txt"
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt"
ip-vpn-all_2025-05.txt
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-sa-2025-05/ip-vpn-sa_2025-05.txt"
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt"
ip-allow-all_2025-05.txt
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt"
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).</Details></Error>
Note that the results have changed for ip-vpn-all-2025-05
and ip-allow-all-2025-05
. Let's highlight the important points:
Previously (Accessing storage.googleapis.com
directly, which goes over public internet from vm-guest-vpn
):
# NG
curl https://storage.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>There is an IP filtering condition that is preventing access to the resource.</Details></Error>
# OK
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-vpn-all-2025-05/ip-vpn-all_2025-05.txt"
ip-vpn-all_2025-05.txt
# NG
curl https://storage.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt
<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>There is an IP filtering condition that is preventing access to the resource.</Details></Error>
# OK
curl -H "Host: storage.googleapis.com" "https://private.googleapis.com/ip-allow-all-2025-05/ip-allow-all_2025-05.txt"
ip-allow-all_2025-05.txt
Similar changes for ip-allow-all-2025-05
.
It was confirmed that even using the curl
command (with proper routing and Host header), access is possible from IP ranges allowed by Bucket IP Filtering when traffic originates from the VPN.
Access Check Part 4
Let's also check access from the author's home (using YOUR_IP
which is allowed in ip-allow-*-2025-05
buckets, and assuming gcloud
is authenticated as YOUR_ACCOUNT
which has necessary IAM). The email address has been replaced with test@example.com
.
Running ls_all_buckets.sh
from the local machine (where YOUR_IP
is the public IP):
gs://public-2025-05/
gs://public-2025-05/public_2025-05.txt
gs://private-2025-05/
gs://private-2025-05/private_2025-05.txt
gs://ip-vpn-all-2025-05/
ERROR: (gcloud.storage.ls) [test@example.com] does not have permission to access b instance [ip-vpn-all-2025-05] (or it may not exist): There is an IP filtering condition that is preventing access to the resource. This command is authenticated as test@example.com which is the active account specified by the [core/account] property.
gs://ip-vpn-sa-2025-05/
ERROR: (gcloud.storage.ls) [test@example.com] does not have permission to access b instance [ip-vpn-sa-2025-05] (or it may not exist): There is an IP filtering condition that is preventing access to the resource. This command is authenticated as test@example.com which is the active account specified by the [core/account] property.
gs://ip-allow-all-2025-05/
gs://ip-allow-all-2025-05/ip-allow-all_2025-05.txt
gs://ip-allow-sa-2025-05/
gs://ip-allow-sa-2025-05/ip-allow-sa_2025-05.txt
Therefore:
- Access to two buckets (
ip-vpn-all
andip-vpn-sa
withoutYOUR_IP
in their filter) is not possible because the source IP (YOUR_IP
) is outside the VPN IP Range allowed for them. - Access is possible if the external IP (
YOUR_IP
) is configured in the bucket's IP filter (ip-allow-all
,ip-allow-sa
). - IAM permissions are also correctly evaluated.
Finally
In this article, we configured a Cloud VPN between two Google Cloud projects using gcloud
commands and verified that Cloud Storage's Bucket IP Filtering feature can be used via this VPN connection. By utilizing the Private Google Access for on-premises hosts mechanism, it is possible to make access from the VPN peer network (in this article, network-vpn-guest
) be recognized as access from a permitted IP range within the VPC network configured on the bucket side (network-vpn-host
).
Let's reconfirm the main steps and points to note obtained through this verification:
- Importance of Route Configuration: To access a bucket with Bucket IP Filtering configured from the VPN peer side (guest VM), it is essential to configure either a static route (adopted in this article) or BGP route advertisement by Cloud Router so that traffic to
private.googleapis.com
(199.36.153.8/30) goes through the VPN tunnel. Forgetting this will cause traffic to go via the internet (or an endpoint within the peer VPN if Private Google Access is not correctly configured on the guest VPC), failing to meet the IP Filtering conditions. - Points to Note When Using
gcloud
Commands:- When trying to access via
private.googleapis.com
with thegcloud storage
command, simply settinggcloud config set api_endpoint_overrides/storage https://private.googleapis.com/
is insufficient. This is because theHost
header of the request sent bygcloud
(which becomesprivate.googleapis.com
) prevents the API side from processing the request correctly for standard storage operations. - To work around this with
gcloud
, you would typically need appropriate DNS configuration (e.g., makingstorage.googleapis.com
resolve to an IP ofprivate.googleapis.com
via/etc/hosts
or internal DNS, and not using endpoint override) or use aSERVICE-ENDPOINT.p.googleapis.com
style hostname (e.g.,storage-myendpoint.p.googleapis.com
resolving toprivate.googleapis.com
IPs) with the endpoint override.
- When trying to access via
- Direct Access with
curl
, etc.: When using a tool likecurl
that can explicitly specify theHost
header, by specifyingprivate.googleapis.com
(or its IP) in the access URL and adding the-H "Host: storage.googleapis.com"
option, verification of VPN access and IP Filtering is possible without complex DNS orgcloud
specific workarounds. This is a useful method for understanding API behavior. - IP Filtering and IAM are Separate: Bucket IP Filtering is, strictly speaking, a feature that 'adds' filtering based on the source IP address at the network level. In addition to this, the accessing user or service account must be granted appropriate IAM permissions (e.g.,
roles/storage.objectViewer
). Access is permitted only when both conditions are met.
Bucket IP Filtering can be an effective means of strengthening access control from internal networks connected by VPN or Cloud Interconnect. However, especially when using client tools like gcloud
, it is important to understand the behavior of Private Google Access endpoints and the Host
header. It is advisable to have a good understanding of the explanation for Private Google Access for on-premises hosts.
It was found that Bucket IP Filtering can permit or deny access by considering it as "access from a specific IP range from a specific VPC network," even when using the aforementioned VPN connection. This is convenient as clients, even those without specific service account permissions (for public buckets), can access resources if their IP is allowed and they use a valid SSL certificate.
Of course, since global IPs can also be individually permitted, you can allow access only from client PCs within a site by registering the site's external IP, but it goes without saying that being able to specify internal IP ranges (via VPC network source in the filter) allows for more granular control.
※ This article includes citations from an article by 株式会社grasys. (link to grasys article). The copyright of the original article belongs to grasys Inc., and it is reproduced here with permission.
References
- Thorough Verification of Cloud Storage Bucket IP Filtering ~Configuration Patterns for Optimizing Access Management with IP Restrictions × VPC Peering~ (in Japanese)
- Bucket IP filtering overview
- Cloud Storage Release Notes (November 14, 2024)
- Configure Private Google Access for on-premises hosts
- Configure Private Google Access
- Routing on-premises using Cloud Router
- Using IAP for TCP forwarding (Firewall rule creation)
- Accessing Google APIs from endpoints (Using endpoints)
ディスカッション
コメント一覧
まだ、コメントがありません