2026年9月8日

2026年9月8日

WordPressの管理画面をGoogle CloudのIAP(Identity-Aware Proxy)で守る設定手順

Identity-Aware Proxy(IAP)は、Google Cloud のロードバランサの手前で利用者の Google アカウントを確認し、許可した人のリクエストだけをアプリに通す仕組みです。WordPress の管理画面(/wp-admin/)とログイン画面(wp-login.php)を IAP の内側に置くと、許可した社員以外はログイン画面にすら届かなくなります。この記事では、Compute Engine 上の WordPress を外部アプリケーションロードバランサで公開している構成を前提に、管理画面だけを IAP で守る手順を説明します(2026年11月時点の Google Cloud のドキュメントに基づきます)。

構成の全体像

公開ページは誰でも見られるようにしたまま、管理系の URL だけを IAP に通すため、同じインスタンスグループを指すバックエンドサービスを2つ用意し、ロードバランサの URL マップでパスごとに振り分けます。

パス 振り分け先 IAP
/wp-admin/*、/wp-login.php 管理用バックエンドサービス(例: wp-admin-iap) 有効
/wp-admin/admin-ajax.php、/wp-admin/admin-post.php 公開用バックエンドサービス(例: wp-public) 無効
それ以外(公開ページ、/wp-json/ など) 公開用バックエンドサービス 無効

admin-ajax.php と admin-post.php は /wp-admin/ の下にありますが、テーマやプラグインが公開ページの機能(検索、フォーム送信など)にも使うため、公開側に残します。どちらも WordPress のログイン Cookie と nonce で守られています。

この構成では、IAP を通った人も WordPress のログインは別に必要です。IAP は「ログイン画面に届く人を絞る」役割で、WordPress のユーザーと権限はこれまでどおり WordPress で管理します。

一方で、wp-login.php を IAP の内側に入れるため、訪問者による会員登録、パスワードのリセット、パスワード保護した投稿の閲覧(wp-login.php?action=postpass を使う)も、許可した人以外は使えなくなります。社内の人だけがログインするサイトに向いた構成です。

前提と準備

  • 課金が有効な Google Cloud プロジェクトと、Compute Engine の WordPress を公開している外部アプリケーションロードバランサ(フロントエンドに HTTPS が必要)
  • ロードバランサの IP アドレスを指すドメイン名
  • IAP は Google が管理する OAuth クライアントを使います。コンソールから有効にする場合は、OAuth 同意画面の設定を求められることがあります。初期状態では、自分の組織(Google Workspace など)のユーザーだけがアクセスできます
  • 作業前に、WordPress のファイルとデータベースのバックアップを取る

以前の版のこの記事に載せていた gcloud iap resource-rules create や gcloud iap access-lists create のようなコマンド、wp-config.php に login_url を書き足す手順は、Google Cloud と WordPress のどちらにも存在しないため削除しました。

手順1:IAP の API を有効にし、管理用バックエンドサービスを作る

gcloud config set project PROJECT_ID
gcloud services enable iap.googleapis.com

次に、既存の公開用バックエンドサービス(ここでは wp-public)と同じインスタンスグループ・ヘルスチェック・プロトコルで、管理用のバックエンドサービス wp-admin-iap を作ります。設定値は既存のものに合わせるのが確実なので、まず中身を確認します。

gcloud compute backend-services describe wp-public --global

コンソールで作る場合は、「ロードバランシング」から既存のロードバランサを編集し、バックエンドの設定で新しいバックエンドサービスを追加して、同じインスタンスグループとヘルスチェックを選びます。

手順2:URL マップでパスを振り分ける

URL マップにパスマッチャーを追加し、管理系のパスを wp-admin-iap へ、それ以外を wp-public へ送ります。パスのルールは、一致するもののうち最も長いパスが優先されるため、/wp-admin/admin-ajax.php は /wp-admin/* より優先されます。

gcloud compute url-maps add-path-matcher WP_URL_MAP \
    --global \
    --path-matcher-name=wp-paths \
    --default-service=wp-public \
    --path-rules='/wp-admin/*=wp-admin-iap,/wp-login.php=wp-admin-iap,/wp-admin/admin-ajax.php=wp-public,/wp-admin/admin-post.php=wp-public' \
    --new-hosts=example.com

WP_URL_MAP はロードバランサの URL マップ名、example.com はサイトのドメインに置き換えます。すでにそのホストのルールがある場合は、--new-hosts の代わりに --existing-host=example.com を使います。反映後、公開ページが今までどおり表示されることを確認してから次に進みます。

手順3:管理用バックエンドサービスで IAP を有効にし、アクセスを許可する

# 管理用のバックエンドサービスだけで IAP を有効にする
gcloud compute backend-services update wp-admin-iap --global --iap=enabled

# 管理画面を使う人(またはグループ)に「IAP-secured Web App User」を付与する
gcloud iap web add-iam-policy-binding \
    --resource-type=backend-services \
    --service=wp-admin-iap \
    --member='group:wp-admins@example.com' \
    --role='roles/iap.httpsResourceAccessor'

コンソールでは、「セキュリティ」→「Identity-Aware Proxy」で対象のバックエンドサービスを選び、「プリンシパルを追加」で「Cloud IAP」→「IAP-secured Web App User」を付与します。人ごとではなく Google グループに付与しておくと、入退社のときにグループの管理だけで済みます。

付与されていないアカウントで https://example.com/wp-admin/ を開くと、Google のログインの後にアクセスを拒否する画面が出ます。付与したアカウントでは、WordPress のログイン画面が表示されれば成功です。

手順4:ファイアウォールでロードバランサ以外からの接続を止める

IAP はロードバランサを通るリクエストにだけ働きます。VM に外部 IP アドレスがあり、80 番ポートが誰にでも開いていると、IAP を迂回して wp-login.php に直接届いてしまいます。VM への HTTP は、ロードバランサの送信元の範囲だけに許可します。

gcloud compute firewall-rules create allow-lb-to-wordpress \
    --network=default \
    --direction=INGRESS \
    --action=allow \
    --rules=tcp:80 \
    --source-ranges=130.211.0.0/22,35.191.0.0/16 \
    --target-tags=wordpress

130.211.0.0/22 と 35.191.0.0/16 は、グローバル外部アプリケーションロードバランサがバックエンドに接続するときとヘルスチェックの送信元です。ネットワーク名とタグは自分の環境に合わせ、これ以外の送信元から 80 / 443 番に入れる既存のルールがあれば削除します。外部 IP アドレスを外せる構成なら、外しておくとさらに迂回されにくくなります。

SSH も IAP 経由にする

VM の外部 IP を外した場合や、SSH を開けたくない場合は、IAP の TCP 転送で SSH できます。送信元 35.235.240.0/20 から 22 番ポートへの接続を許可し、使う人に「IAP-secured Tunnel User」(roles/iap.tunnelResourceAccessor)を付与します。

gcloud compute firewall-rules create allow-ssh-from-iap \
    --network=default \
    --direction=INGRESS \
    --action=allow \
    --rules=tcp:22 \
    --source-ranges=35.235.240.0/20 \
    --target-tags=wordpress

gcloud compute ssh INSTANCE_NAME --zone=ZONE --tunnel-through-iap

手順5:WordPress 側で HTTPS を正しく認識させる

ロードバランサで HTTPS を終端し、VM には HTTP で届く構成では、WordPress が自分を HTTP だと判断し、ログイン後のリダイレクトが繰り返されることがあります。ロードバランサが付ける X-Forwarded-Proto ヘッダーを見て、HTTPS として扱うようにします。

<?php
// wp-config.php の「編集が必要なのはここまでです」より前に書く
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
	$_SERVER['HTTPS'] = 'on';
}

このヘッダーは利用者が偽装できるため、手順4のようにロードバランサ以外からの接続を止めていることが前提です。

手順6:署名付き JWT を検証して、迂回を防ぐ

ファイアウォールの設定ミスや、誤って IAP を無効にした場合に備えて、アプリ側でも IAP を通ったリクエストかを確かめると安全です。IAP は、通したリクエストに x-goog-iap-jwt-assertion ヘッダーで署名付きの JWT を付けます。WordPress では、管理画面とログイン画面へのリクエストでこれを検証します。

検証で確認する内容は次のとおりです。

  • 署名:アルゴリズムは ES256。公開鍵は https://www.gstatic.com/iap/verify/public_key-jwk で公開されている
  • 発行者(iss):https://cloud.google.com/iap
  • 対象(aud):/projects/PROJECT_NUMBER/global/backendServices/SERVICE_ID
  • 有効期限(exp)など

aud に入れるプロジェクト番号とバックエンドサービスの ID(どちらも数字)は次のコマンドで確認します。

gcloud projects describe PROJECT_ID --format='value(projectNumber)'
gcloud compute backend-services describe wp-admin-iap --global --format='value(id)'

JWT の検証には、PHP のライブラリ firebase/php-jwt を使います。サーバーで次を実行し、wp-content/mu-plugins/iap/ にライブラリを入れます。

mkdir -p wp-content/mu-plugins/iap
cd wp-content/mu-plugins/iap
composer require firebase/php-jwt

次のコードを wp-content/mu-plugins/iap-admin-guard.php として保存し、WPDR_IAP_AUDIENCE の PROJECT_NUMBER と SERVICE_ID を、上で確認した数字に置き換えます。

<?php
/**
 * Plugin Name: IAP JWT check for wp-admin
 * Description: wp-admin と wp-login.php へのリクエストに、IAP の署名付き JWT を要求する。
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// wp-content/mu-plugins/iap/ で composer require firebase/php-jwt を実行しておく。
require_once __DIR__ . '/iap/vendor/autoload.php';

use Firebase\JWT\JWK;
use Firebase\JWT\JWT;

// IAP を有効にしたバックエンドサービスの値に置き換える。
const WPDR_IAP_AUDIENCE = '/projects/PROJECT_NUMBER/global/backendServices/SERVICE_ID';

function wpdr_iap_get_jwks() {
	$jwks = get_transient( 'wpdr_iap_jwks' );
	if ( is_array( $jwks ) ) {
		return $jwks;
	}
	$res = wp_remote_get( 'https://www.gstatic.com/iap/verify/public_key-jwk', array( 'timeout' => 5 ) );
	if ( is_wp_error( $res ) || 200 !== wp_remote_retrieve_response_code( $res ) ) {
		return null;
	}
	$jwks = json_decode( wp_remote_retrieve_body( $res ), true );
	if ( ! is_array( $jwks ) || empty( $jwks['keys'] ) ) {
		return null;
	}
	set_transient( 'wpdr_iap_jwks', $jwks, HOUR_IN_SECONDS );
	return $jwks;
}

/**
 * 正しい JWT ならメールアドレスを、そうでなければ false を返す。
 */
function wpdr_iap_verify( $assertion, array $jwks, $audience ) {
	try {
		JWT::$leeway = 30;
		$payload     = JWT::decode( $assertion, JWK::parseKeySet( $jwks ) );
	} catch ( Exception $e ) {
		return false;
	}
	if ( ! isset( $payload->iss, $payload->aud, $payload->email ) ) {
		return false;
	}
	if ( 'https://cloud.google.com/iap' !== $payload->iss || $audience !== $payload->aud ) {
		return false;
	}
	return (string) $payload->email;
}

add_action(
	'init',
	function () {
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			return;
		}
		$script = isset( $_SERVER['SCRIPT_NAME'] ) ? basename( (string) $_SERVER['SCRIPT_NAME'] ) : '';
		if ( in_array( $script, array( 'admin-ajax.php', 'admin-post.php' ), true ) ) {
			return; // ロードバランサで公開側に回しているファイルは対象外
		}
		if ( ! is_admin() && 'wp-login.php' !== $script ) {
			return; // 公開ページは対象外
		}
		$assertion = isset( $_SERVER['HTTP_X_GOOG_IAP_JWT_ASSERTION'] ) ? (string) $_SERVER['HTTP_X_GOOG_IAP_JWT_ASSERTION'] : '';
		$jwks      = wpdr_iap_get_jwks();
		if ( '' === $assertion || null === $jwks || false === wpdr_iap_verify( $assertion, $jwks, WPDR_IAP_AUDIENCE ) ) {
			wp_die( 'Forbidden', 'Forbidden', array( 'response' => 403 ) );
		}
	}
);

このコードは、/wp-admin/ の画面と wp-login.php へのリクエストで JWT を検証し、正しくなければ 403 を返します。公開ページ、admin-ajax.php、admin-post.php、WP-CLI は対象外です。公開鍵は1時間キャッシュします。

導入するときは、次の順で進めると締め出されません。

  1. ステージングで、IAP を通したアクセスでは管理画面が開け、VM に直接アクセスしたときは 403 になることを確かめる
  2. 本番に入れたら、すぐに管理画面が開けることを確かめる
  3. 開けなくなった場合は、SSH で wp-content/mu-plugins/iap-admin-guard.php の名前を変えるか削除すると元に戻る

ログで確認する

IAP を通ったアクセスや拒否の記録は、Cloud Audit Logs の「データアクセス」ログとして残せます。初期状態では記録されないため、必要な場合は「IAM と管理」→「監査ログ」で Cloud Identity-Aware Proxy のデータアクセスログを有効にし、ログ エクスプローラーで次の条件で絞り込みます。

protoPayload.serviceName="iap.googleapis.com"

ログの authorizationInfo の granted で、IAP がアクセスを許可したかどうかを確認できます。ログの保存量に応じて料金がかかることがあるので、保存期間とあわせて決めてください。

まとめ

  • 公開ページはそのままに、/wp-admin/* と wp-login.php だけを IAP 付きのバックエンドサービスへ振り分ける
  • admin-ajax.php と admin-post.php は公開側に残す
  • IAP の有効化は gcloud compute backend-services update --iap=enabled、アクセス許可は roles/iap.httpsResourceAccessor
  • ロードバランサ以外からの接続をファイアウォールで止め、SSH は IAP の TCP 転送を使う
  • アプリ側でも x-goog-iap-jwt-assertion の署名・発行者・対象を検証して、迂回に備える

お気軽にご相談ください

お見積りへ お問い合わせへ
▲