皆さんこんにちは。okamoです。
実際に私のところへ寄せられた相談にお答えする「実録:ITよろず相談」シリーズの第3回目です。
今回は、エンジニアなら誰もが一度は胃を痛めたことがあるであろう、あの件名のメールたちについてお話しします。
- 【至急】リニューアル後にgoogle検索結果がリンク切れになる件
- 【至急】DBサーバ高負荷の件(攻撃の疑い)
- 【至急】リダイレクト設定追加のお願い(QR掲載URL)
- 【至急】Basic認証設定後、検証環境でwebhook通知がエラーになる件
むむっ、「至急」なメールばっかりなんですけど……。
至急なら、10分でやろうホトトギス。
ということで、あんなのやこんなの実録対応(あるある相談対応)です。 急ぎの時は**「エッジ関数」、つまりCloudFront Functions**で対応しました。
CloudFront Functions(エッジ関数)とは?
ひとことで言うと**「CDN(CloudFront)の入り口で、通信をチョロッと書き換える軽量なプログラム」**です。
- とにかく早い(爆速): デプロイが数秒〜数十秒で完了。ミリ秒単位で動作。
- サーバーに負荷をかけない: オリジン(EC2など)に到達する前に処理できる。
- インフラ側の設定だけで完結: アプリのコード(PHPやRailsなど)をいじらなくて済む。
今回は短時間で対応するため、状況に応じて組み替えしやすいテンプレートを用意しました。
実際の対応事例
先ほどの相談も、このテンプレートを使えば以下のように解決できます。
- 【至急】リニューアル後にgoogle検索結果がリンク切れになる件
- →
5. redirectRuleで解決!
- →
- 【至急】DBサーバ高負荷の件(攻撃の疑い)
- →
2. emergencyBlockRuleと3. killSwitchRuleで解決!
- →
- 【至急】リダイレクト設定追加のお願(QR掲載URL)
- →
5. redirectRuleで解決!
- →
- 【至急】Basic認証設定後、検証環境でwebhook通知がエラーになる件
- →
1. skipAuthRuleと6. basicAuthRuleの順番の妙で解決!
- →
CloudFront Functions 用 テンプレートコード
処理を「ルール」として関数定義し、配列(Array)に追加することで適用順序を制御できる設計にしています。
/**
* CloudFront Functions 用 テンプレート
*
* 処理を「ルール」として定義し、Arrayに追加することで適用順序を制御します。
*/
function handler(event) {
var request = event.request;
var clientIp = event.viewer.ip; // 接続元のIPアドレス
// --- 設定ここから ---
// 1. 【パス除外ルール】特定のパス(Webhookなど)は以降の全チェックをスキップしてオリジンへ通す
var skipAuthRule = function(req, next) {
var excludePaths = ['/webhook_path/', '/api/public/'];
for (var i = 0; i < excludePaths.length; i++) {
if (req.uri.startsWith(excludePaths[i])) {
return req; // 以降の処理をせず、オリジンへ直接送信
}
}
return next(req);
};
// 2. 【緊急ブロックルール】特定のIPやユーザーエージェントを拒否(WEBアタック対策)
var emergencyBlockRule = function(req, next) {
var blockedIps = ['1.2.3.4']; // 攻撃元のIPをここに追記
var blockedUa = 'BadBot'; // 攻撃元のUAの一部をここに指定
var ua = (req.headers['user-agent'] && req.headers['user-agent'].value) || '';
if (blockedIps.indexOf(clientIp) !== -1 || ua.indexOf(blockedUa) !== -1) {
return {
statusCode: 403,
statusDescription: 'Forbidden'
};
}
return next(req);
};
// 3. 【エンドポイント無効化】脆弱性が発見されたパスを即座に遮断(応急処置)
var killSwitchRule = function(req, next) {
var vulnerablePaths = ['/login/credential_stuffing_target', '/search?q=xss'];
for (var i = 0; i < vulnerablePaths.length; i++) {
if (req.uri === vulnerablePaths[i]) {
return {
statusCode: 404, // 存在しないことにする
statusDescription: 'Not Found'
};
}
}
return next(req);
};
// 4. 【メンテナンスルール】503を表示。ただし関係者IPは除外
var maintenanceRule = function(req, next) {
var isMaintenance = false; // 有効にする場合は true に
var allowIps = ['111.222.333.444']; // 関係者のIP
if (isMaintenance && allowIps.indexOf(clientIp) === -1) {
return {
statusCode: 503,
statusDescription: 'Service Unavailable',
headers: {
'content-type': { value: 'text/html; charset=UTF-8' },
'retry-after': { value: '3600' }
},
body: {
encoding: "text",
data: `<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>システムメンテナンスのお知らせ</title>
</head>
<body>
<h1>システムメンテナンスのお知らせ</h1>
</body>
</html>`,
}
};
}
return next(req);
};
// 5. 【リダイレクトルール】QR間違い、Googleリンク切れ(301)の高速修正
var redirectRule = function(req, next) {
var redirectMap = {
'/old-qr-path': 'https://example.com/new-path',
'/category/114': 'https://example.com/category/115',
'/item/0000001': 'https://example.com/item/0000006'
};
if (redirectMap[req.uri]) {
return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
'location': { value: redirectMap[req.uri] }
}
};
}
return next(req);
};
// 6. 【Basic認証ルール】開発環境などの保護
var basicAuthRule = function(req, next) {
var authHeader = req.headers['authorization'];
var expectedValue = 'Basic dXNlcjpwYXNz'; // user:pass をbase64化したもの
if (!authHeader || authHeader.value !== expectedValue) {
return {
statusCode: 401,
statusDescription: 'Unauthorized',
headers: {
'www-authenticate': { value: 'Basic' }
}
};
}
return next(req);
};
// --- 実行処理(適用したい順に並べる) ---
var processes = [
skipAuthRule, // 最初にWebhookなどを逃がす
emergencyBlockRule, // 次に攻撃者を弾く
killSwitchRule, // 脆弱なパスを塞ぐ
maintenanceRule, // メンテナンスなら止める
redirectRule, // リダイレクト対象なら飛ばす
basicAuthRule // 最後にBasic認証
];
// 各関数をチェーン状に結合するラッパー
var wrapper = processes.reverse().reduce(function(next, currentProcess) {
return function(req) {
return currentProcess(req, next);
};
}, function(req) {
return req; // すべてのチェックを通過した場合はオリジンへ
});
return wrapper(request);
}
この構成のポイントと使い所
1. 開発環境と本番環境での「パーツ入れ替え」
processes 配列の中身を書き換えるだけで、機能のオンオフができます。
- 本番環境:
basicAuthRuleを配列から消す。 - 緊急時:
maintenanceRuleやemergencyBlockRuleを配列の先頭の方に追加する。
2. 10分で対応するための「Redirectルール」
Googleのリンク切れやQRコードのミスは、サーバー側のプログラム(PHPやRailsなど)を直してデプロイすると時間がかかります。エッジ関数なら、このテンプレートの redirectMap に1行追加して保存・公開するだけなので、数分で反映が完了します。
3. 脆弱性への応急処置(バーチャルパッチ)
SQLインジェクションや特定のパラメータを狙った攻撃がログで見つかった際、サーバー側を修正するまでの「数時間」を稼ぐために、killSwitchRule でそのパスへのアクセスを 404 や 403 で即座に遮断します。
4. メンテナンス画面とIP制限の組み合わせ
CloudFront Functions の event.viewer.ip を使うことで、一般客には503(HTML)を見せつつ、会社のIPからだけは動作確認を継続するという運用がこれだけで完結します。
二次災害をおこさないために
便利で強力だからこそ、以下を徹底して本番反映していきます。
- 検証環境に反映適用、動作チェック
- 本番環境AWSコンソール上でテスト(Test機能があります)
- 本番反映後も動作チェック
かゆいところに手が届く、柔軟に対応可能、急ぎの時に助かる。それがCloudFront Functionsです。
道具をうまく使うと、みんな幸せになれる
至急対応が発生したとき、CloudFront関数という「道具」を正しいタイミングで使うと、関わる全員が幸せになれます。
- 👤 システムユーザー(顧客・マーケター)の幸せ
- 「QRコード間違えた!」「リンク切れで広告費がムダに!」といったビジネス上の致命傷を、最短数分で止血してもらえる。機会損失を防げる。
- 💻 アプリエンジニアの幸せ
- 開発スプリント中に突然「至急、リダイレクト処理をアプリ側に1行足して今日リリースして!」という割り込みタスクが来なくなる。本来の開発業務に集中できる。
- 🛡️ インフラエンジニアの幸せ
- サーバーの負荷や脆弱性攻撃を、バックエンドに到達する前にスマートに弾ける。テンプレートを用意しておけば、焦らず安全・確実に10分でヒーローになれる。
※注意:エッジ関数は「魔法の杖」ではありません
ここで紹介したのは、あくまで「10分で止血する」ための**応急処置(バンデージ)**です。 エッジ側に複雑なビジネスロジックを持たせすぎると、後から「なぜかこのURLだけリダイレクトされる…?」といったブラックボックス化(負債)を生む原因になります。
おわりに
「至急!」というタイトルのメールが来ると、どうしても現場はピリピリしてしまいます。アプリエンジニアを急かしてバグを生んでしまったり、ユーザーをお待たせして不満を生んでしまったり。
しかし、CloudFront Functionsのような「エッジの道具」を適切なタイミングでサッと使えるようにしておくだけで、そのピリピリを「10分で終わりましたよ」という安心感に変えることができます。
道具は使いよう。インフラもアプリもユーザーも、みんなが平和に、幸せになれる運用を目指していきたいですね! みなさんの現場でも、ぜひこのテンプレートをお守り代わりに使ってみてください。
コメントを投稿するにはログインが必要です。