6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloud Run の公開サービス棚卸し、allUsers だけ見ていると漏れます

6
Last updated at Posted at 2026-08-14

この記事でやること

Cloud Run のサービスを「公開(未認証アクセス可)」にする方法は、大きく2つに分けられます。

方法①:「そもそも入口で身分証を確認しない」
サービス自体の設定で「認証チェックをしない」と指定するやり方です。誰かに許可を出したわけではないので、IAM ポリシーには何も書かれません

方法②:「全員に入場許可を出す」
allUsers(=インターネット上の誰でも)に対して、呼び出し権限を与えるやり方です。これまで一般的だった方法で、IAM ポリシーに「allUsers に許可」という記録が残ります

結果はどちらも「誰でもアクセスできる」で同じです。しかし設定が記録される場所が違います

自分は allUsers バインディングを検索して公開サービスを洗い出そうとしたのですが、「公開されているはずなのに見つからない」という事態にハマりました、、

原因は方法①でした。IAM ポリシーには何も書かれていないので、いくら allUsers を探しても見つからないわけです。

そこでこの記事では、3つの Cloud Run サービス(公開2種類+非公開1種類)を実際に立てて、それぞれの検出方法で何が拾えて、何が見落とされるのかを検証してみます。

最後に、公開サービスを漏れなく棚卸しするためのコマンドも紹介します。

対象読者

  • Cloud Run を運用していて、公開リソースの棚卸しをしている人
  • gcloud asset や IAM ポリシー検索でセキュリティチェックを自動化している人

結論

先に結果を出しておきます。3つのサービスを立てて、各検出手段を試した結果です。

① Invoker IAMチェック無効 ② allUsers に付与 非公開
実際のアクセス(無認証) 200 200 403
サービスの IAM ポリシー allUsers あり
policy:allUsers 検索 ❌ 検出できない ✅ 検出できる
Asset Inventory の検索API ❌ クエリ不可 ✅ 検出できる
gcloud asset list + フィルタ ✅ 検出できる ✅ 検出できる
コンソールのセキュリティタブ ✅ 表示される ✅ 警告が出る

ポイントは3つです。

  1. ①と非公開は、IAM ポリシー上まったく同じに見えます(どちらも空)。それでも一方は誰でもアクセスできます
  2. ①は Google が「推奨」としている方式です。裏技でも設定ミスでもありません
  3. コンソールを開けば分かります。しかしスクリプトで棚卸ししているなら①は引っかかりません

対策は記事の最後にまとめます。組織ポリシーで禁止することもできます(デフォルトでは有効になっていないはずです)。

Cloud Run の「公開」には2種類ある

まず前提の整理です。公式ドキュメントには、公開サービスを作る方法が2つ挙げられています。

方法 実体
Invoker IAM チェックを無効化(推奨と明記) サービス側の設定(invokerIamDisabled
allUsers に Cloud Run Invoker ロールを付与 IAM ポリシーのバインディング

①はサービス自体の設定で「invoker の IAM チェックをそもそも行わない」ようにするものです。IAM ポリシーには何も追加されません。

そして公式ドキュメントは、こちらを推奨としています。理由も書かれていて、ドメイン制限共有(DRS)の組織ポリシーが適用されているプロジェクトでは、allUsers へのIAM付与自体が失敗するためです。つまり②が使えない環境では①を使うことになります。

①を有効にする方法はいくつかあります。

①を有効にしてデプロイ
gcloud run deploy SERVICE_NAME --no-invoker-iam-check
YAMLでの表現
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
    run.googleapis.com/invoker-iam-disabled: 'true'

Terraform では invoker_iam_disabled 引数として指定できます。

検証環境を作る

3つのサービスを立てます。中身はどれも同じ hello world です。

① Invoker IAMチェックを無効化して公開
gcloud run deploy pub-noiam \
  --source . \
  --region asia-northeast1 \
  --no-invoker-iam-check \
  --quiet
② allUsers で公開(従来の方法)
gcloud run deploy pub-allusers \
  --source . \
  --region asia-northeast1 \
  --allow-unauthenticated \
  --quiet
③ 非公開(対照群)
gcloud run deploy private-svc \
  --source . \
  --region asia-northeast1 \
  --no-allow-unauthenticated \
  --quiet

デプロイ時の出力に既に差が出ています。②だけ ✓ Setting IAM Policy... というステップがあり、①にはありません。①がIAMを触っていないことが、ここでも分かります。

まず実態を確認する

3つのサービスについて、サービス設定・IAMポリシー・実際のHTTPアクセスを並べて見ます。

for s in pub-noiam pub-allusers private-svc; do
  echo "===== $s ====="
  curl -sS -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    "https://run.googleapis.com/v2/projects/${PROJECT_ID}/locations/asia-northeast1/services/${s}" \
    | python3 -c "import json,sys; d=json.load(sys.stdin); print('invokerIamDisabled:', d.get('invokerIamDisabled', False))"
  echo "-- IAM policy --"
  gcloud run services get-iam-policy "$s" --region asia-northeast1 --format="value(bindings.members)" 2>&1 | head -3
  url=$(gcloud run services describe "$s" --region asia-northeast1 --format="value(status.url)")
  echo "-- HTTP (no auth): $(curl -s -o /dev/null -w '%{http_code}' $url)"
done
実行結果
===== pub-noiam =====
invokerIamDisabled: True
-- IAM policy --
-- HTTP (no auth): 200

===== pub-allusers =====
invokerIamDisabled: False
-- IAM policy --
['allUsers']
-- HTTP (no auth): 200

===== private-svc =====
invokerIamDisabled: False
-- IAM policy --
-- HTTP (no auth): 403

ここが出発点です。pub-noiamprivate-svc は、IAM ポリシーの見え方が完全に同一(どちらも空)。それでも一方は 200、もう一方は 403 です。

IAM ポリシーだけを見ている限り、この2つは区別できません。

検出手段を1つずつ試す

① IAMポリシー検索(policy:allUsers

公開リソースの棚卸しで、一般的な手法だと思います。

gcloud asset search-all-iam-policies \
  --scope="projects/${PROJECT_ID}" \
  --query="policy:allUsers" \
  --format="table(resource)"
実行結果
RESOURCE
//run.googleapis.com/projects/PROJECT_ID/locations/asia-northeast1/services/pub-allusers

pub-allusers しか出てきません。 pub-noiam は同じく誰でもアクセスできる状態なのに、1件も引っかかりません。

そもそもIAMポリシーに何も書かれていないので、当然といえば当然です。しかしこの方法を棚卸しに使っている場合、公開サービスの見落としが起きます

② Asset Inventory のリソース検索

では、リソース側の構成を見ればどうか。

gcloud asset search-all-resources \
  --scope="projects/${PROJECT_ID}" \
  --asset-types="run.googleapis.com/Service" \
  --read-mask="name,versionedResources" \
  --format="json" | python3 -c "
import json,sys
for r in json.load(sys.stdin):
    s = json.dumps(r.get('versionedResources',''))
    print(r.get('name','').split('/')[-1], '->', 'あり' if 'invokerIamDisabled' in s else 'なし')
"
実行結果
private-svc -> なし
pub-allusers -> なし
pub-noiam -> なし

出てきません。ならばクエリで直接指定できないか、と試すとこうなります。

gcloud asset search-all-resources \
  --scope="projects/${PROJECT_ID}" \
  --asset-types="run.googleapis.com/Service" \
  --query="run.googleapis.com/invoker-iam-disabled:true"
実行結果
ERROR: (gcloud.asset.search-all-resources) INVALID_ARGUMENT: Unsupported field:
'run.googleapis.com/invoker-iam-disabled'. Supported fields include:
'name, displayName, description, location, networkTags, project, folders,
organization, ... labels, kmsKey, createTime, updateTime, state, tagKeys, ...'

サポート外のフィールドとして明示的に拒否されます。 labelstagKeys は検索できるのに、この値は検索できません。

つまり Asset Inventory の検索APIでは、この条件で絞り込むことができないわけです。

gcloud asset list なら痕跡がある

一方、リソースの完全な構成を取得する gcloud asset list では話が変わります。

gcloud asset list \
  --project="$PROJECT_ID" \
  --asset-types="run.googleapis.com/Service" \
  --content-type=resource \
  --format="json" | python3 -c "
import json,sys
for a in json.load(sys.stdin):
    d = a.get('resource',{}).get('data',{})
    ann = d.get('metadata',{}).get('annotations',{})
    print(a['name'].split('/')[-1], '| annotation:',
          ann.get('run.googleapis.com/invoker-iam-disabled','(なし)'))
"
実行結果
private-svc | annotation: (なし)
pub-allusers | annotation: (なし)
pub-noiam | annotation: true

pub-noiam だけ true が入っています。
検索APIでは引けませんが、全件取得してクライアント側でフィルタすれば検出できます

④ コンソールは正しく表示される

念のためコンソールも確認しました。結論から言うと、コンソールは両方式とも正しく警告してくれます

まず pub-noiam。「パブリック アクセスを許可」が選択された状態で、「認証チェックは実行されません」と説明されています。

SCR-20260814-plrp.png

比較用に、非公開の private-svc はこうなります。「認証が必要」が選択され、IAM にチェックが入っています。

SCR-20260814-pltw.png

そして pub-allusers。ラジオボタンとしては private-svc と同じ「認証が必要」が選択されています(invokerIamDisabled が false なので)。ただし黄色の警告バナーが出て、allUsers に権限が付与されているため一般公開されている旨が明示され、修正用のボタンまで用意されています。

SCR-20260814-plvo.png

つまりコンソールを1つずつ開けば、どちらの方式でも気づけます

問題は、サービスが数十個ある環境でそれを毎回やるのかという話です。棚卸しをスクリプト化しているなら、①の検出は前述のとおり工夫が必要になります。

漏れなく棚卸しするには

2つの方式を両方カバーする必要があります。

① Invoker IAMチェック無効のサービスを洗い出す

gcloud asset list \
  --project="$PROJECT_ID" \
  --asset-types="run.googleapis.com/Service" \
  --content-type=resource \
  --format="json" | python3 -c "
import json,sys
hits=[]
for a in json.load(sys.stdin):
    d=a.get('resource',{}).get('data',{})
    ann=d.get('metadata',{}).get('annotations',{})
    if ann.get('run.googleapis.com/invoker-iam-disabled')=='true':
        hits.append(a['name'].replace('//run.googleapis.com/',''))
print('=== Invoker IAMチェックが無効なサービス ===')
[print(' -',h) for h in hits] if hits else print('(なし)')
"
実行結果
=== Invoker IAMチェックが無効なサービス ===
 - projects/PROJECT_ID/locations/asia-northeast1/services/pub-noiam

組織全体を対象にするなら --project--organization=ORGANIZATION_ID に変えます。

② allUsers で公開されているサービスを洗い出す

こちらは従来どおりです。

gcloud asset search-all-iam-policies \
  --scope="projects/${PROJECT_ID}" \
  --query="policy:allUsers" \
  --format="table(resource)"

この2つを両方回して、はじめて公開サービスの全体像が掴めます。

組織ポリシーで禁止することもできる

そもそも①を使わせない、という選択肢もあります。公式ドキュメントによれば、constraints/run.managed.requireInvokerIam というマネージド制約で、Invoker IAM チェックの無効化を制限できます。

ただしこの制約はデフォルトでは適用されていません。明示的に有効化する必要があります。

お片付け

検証で作ったサービスを消しておきます。

for s in pub-noiam pub-allusers private-svc; do
  gcloud run services delete "$s" --region asia-northeast1 --quiet
done

まとめ

  • Cloud Run の公開方式は2つあり、Invoker IAM チェックの無効化(Google推奨)はIAMポリシーを変更しない
  • そのため policy:allUsers を検索する棚卸しでは検出できない。Asset Inventory の検索APIでもクエリできない
  • gcloud asset list で全件取得し、run.googleapis.com/invoker-iam-disabled アノテーションを見れば検出できる
  • コンソールは両方式とも正しく警告してくれる。盲点になるのはスクリプトによる棚卸し
  • 予防するなら constraints/run.managed.requireInvokerIam(デフォルト無効)

「公開リソースを洗い出す」処理を書いている場合は、allUsers の検索だけで済ませていないか確認してみてください。

参考

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?