AWS基礎 #6 セキュリティの基本 — MFA、キーローテーション、最小権限
#2 IAM で権限モデルを、#5 で SSO まで見ました。この記事はその上に 運用で通用するセキュリティのガードレール を一気に整理します。
AWS のセキュリティ事故の 90% は次のいずれかです。
- ルート / ユーザーパスワードの窃取 (フィッシング) — MFA がないため
- アクセスキーが git / Slack / ログに漏洩
- 広すぎる権限が侵害時に被害を膨らませる
- CloudTrail / GuardDuty が OFF で事件を発見できない
この 4 つにガードレールを敷けば事故確率は 1 桁台に落ちます。
MFA — もっとも大きな 1 つ #
パスワード 1 行で終わる認証は 2026 年の運用水準ではありません。 フィッシング 1 回でパスワードは流れます。MFA (Multi-Factor Authentication) は 2 番目の要素 (普通はスマホアプリ) の 6 桁コードを追加で要求します。
MFA の種類 #
| 種類 | 何か | 推奨 |
|---|---|---|
| Virtual MFA (TOTP) | スマホアプリ (Google Authenticator、1Password、Authy) | 標準 — ほぼすべてのケース |
| ハードウェア MFA | YubiKey のような USB キー | ルート / 高権限 — 最強 |
| U2F / WebAuthn | ブラウザ + ハードウェアキー | 運用認証の格上げ |
| SMS | テキストメッセージ | 使用禁止 (SIM swap 攻撃) |
ルートは ハードウェア MFA が理想、一般ユーザーは virtual MFA で十分。
ルートユーザーの MFA 有効化 #
登録直後に最初の作業として即実施。
コンソール (ルートログイン) → 右上ユーザーメニュー → Security credentials
→ Multi-factor authentication (MFA) → Assign MFA device
→ Virtual MFA / Hardware MFA を選択
→ QR コードをスマホアプリでスキャン
→ 連続して 2 つのコードを入力 (アプリは 30 秒ごとに新コード)以降ルートログインのたびにパスワード + 6 桁コード。
IAM ユーザーに MFA 強制 #
ルートだけ ON にするのは不十分。すべての IAM ユーザーに強制。2 通りの方法。
方法 1: ポリシーで — 「MFA セッションでないとほぼすべてのアクションを拒否」
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSelfManageCredentials",
"Effect": "Allow",
"Action": [
"iam:ChangePassword",
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ResyncMFADevice"
],
"Resource": [
"arn:aws:iam::*:user/${aws:username}",
"arn:aws:iam::*:mfa/${aws:username}"
]
},
{
"Sid": "DenyAllExceptListedIfNoMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ResyncMFADevice",
"iam:ChangePassword",
"sts:GetSessionToken"
],
"Resource": "*",
"Condition": {
"BoolIfExists": { "aws:MultiFactorAuthPresent": "false" }
}
}
]
}このポリシーをすべてのユーザーが所属するグループに付ければ MFA なしでは事実上何もできなくなります。MFA 登録だけは例外です。
方法 2: SSO (#5)
IAM Identity Center ユーザーにはコンソール / CLI ログイン時に MFA が自動で強制されます。ポリシーを書く必要なし。
初回ログイン時 MFA 登録の強制フロー #
新規 IAM ユーザーが初めてログイン → 上のポリシーで MFA 登録以外は何もできない → 自分で MFA 登録 → その後は正常に使えます。この流れが標準です。
アクセスキーローテーション — 90 日が標準 #
#4 で見たアクセスキー。このキーは 時間が経つと漏洩リスクが累積 します。
ローテーションポリシー #
| 項目 | 推奨ローテーション周期 |
|---|---|
| ユーザーアクセスキー | 90 日 |
| CI / CD のキー | 60 日 (または OIDC に移行) |
| サービスアカウントキー | 30~60 日 |
| 一時認証情報 (SSO / Role) | ローテーション不要 (自動で短期発行) |
ローテーション手順 #
キーを 1 度に 2 つ持てる IAM の特性を活用します。
# 1) 新キー発行 (これでアクティブキーが 2 つ)
aws iam create-access-key --user-name curtis
# 2) すべての環境で新キーに置換 (CI 環境変数、~/.aws/credentials など)
# 3) 数日モニタリング — 古いキーの使用がないか
# CloudTrail または IAM credential report で確認
# 4) 古いキー無効化 (削除はまだしない — ロールバック用)
aws iam update-access-key --user-name curtis --access-key-id AKIA-OLD --status Inactive
# 5) 1 週間さらにモニタ → 本当に使われていなければ削除
aws iam delete-access-key --user-name curtis --access-key-id AKIA-OLDIAM Credential Report — ローテーション点検 #
CSV 1 枚ですべてのユーザーのキー / MFA / 活動状態を見ます。
aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 -d > report.csvuser
mfa_active
access_key_1_active
access_key_1_last_rotated
access_key_1_last_used_date
access_key_2_active
access_key_2_last_rotated
password_last_used90 日を超えたキー、MFA を有効化していないユーザー、1 ヶ月未使用ユーザー — 一目で見えます。
漏洩キー発見時 #
コードにキーを push してしまった場合、ボットが発見するまで通常は数分単位です。
即やるべきこと (時間順) #
aws iam update-access-key --user-name <user> --access-key-id <キーID> --status Inactiveaws iam create-access-key --user-name <user># コンソール → CloudTrail → Event history
# AccessKeyId で検索 → 意図しない使用があるかaws iam delete-access-key --user-name <user> --access-key-id <キーID># BFG Repo-Cleaner または git filter-repo で history からキーを除去
# (すでに push 済みなら history のクリーンアップだけでは安全 X — キー削除の方が大事)AWS が自動で助けてくれるしくみ #
AWS は GitHub などの公開リポジトリをスキャン していて、漏洩キーを発見すると:
- ユーザーに即メール
- 一部のケースでは自動でキーを無効化 + ポリシーをアタッチ
これは補助のセーフティネット — 自分で発見する方が速いです。
IAM Access Analyzer — 広すぎる権限を見つける #
自アカウントのポリシー / リソースポリシー (S3 バケットポリシー、KMS キーポリシーなど) を分析して 外部からアクセス可能なところ を見つけるツールです。無料です。
有効化 #
コンソール → IAM → Access Analyzer → Create analyzer
- Type: Account / Organization
- Name: my-account-analyzer有効化後 24 時間以内に 外部アクセス可能なリソース のリストが出ます。
何を拾ってくれるか #
| リソース | リスク |
|---|---|
| S3 バケット — public read | 誰でもオブジェクト読み取り |
| S3 バケット — 別アカウントに権限 | 意図した設定か確認が必要 |
| KMS キー — 外部使用 | 暗号化キーの漏洩 |
| IAM Role — 外部 trust | 別アカウントが借りられる |
| Lambda — 外部呼び出し権限 | 誰でも呼び出し可能 |
| RDS スナップショット / SQS / SNS / Secrets Manager / EBS / ECR — public | データ / メッセージ漏洩 |
ポリシー検証 #
新しいポリシーを作るとき Access Analyzer が 推奨事項 も見せてくれます。
- 使われていない権限
- 広すぎるワイルドカード
- 条件のない場合への追加推奨
Action Last Accessed — 未使用権限を見つける #
各 IAM ユーザー / ロールについて 最後に使ったアクション を見せてくれます。90 日未使用の権限は絞り込み候補。
aws iam generate-service-last-accessed-details --arn arn:aws:iam::123:role/MyRole
# (しばらくして)
aws iam get-service-last-accessed-details --job-id ...最小権限 — 通用するパターン #
「必要なだけ、必要なところだけ。」理想は簡単ですが、運用でどう実践するかが鍵です。
パターン 1: 広く始めて → 絞る #
最初から完璧な権限を組むのは難しいです。次の流れが現実的。
- 開始は
PowerUserAccess/ 各サービスの*FullAccess - 1 週間運用後に Access Analyzer の Action Last Accessed を確認
- 使っていないサービス / アクションを除去
- ワイルドカードを ARN に絞る
- 条件 (Condition) 追加
このサイクルを四半期ごとに繰り返します。
パターン 2: ユーザー ↔ ロール分離 #
#2 のパターン の再確認。
- 人 = SSO (#5)
- マシン = Role (インスタンスプロファイル、実行ロール、OIDC)
- CI/CD = OIDC + Role (GitHub Actions、GitLab)
永続アクセスキーがほぼ消える構造です。
パターン 3: Permission Boundary (権限境界) #
「このユーザーは何をしてもこの限度内だけ。」新人開発者に IAM 権限を与えつつ、彼が新しいポリシー / ユーザーを作って自分の権限を上げる事故を遮断。
{
"Effect": "Allow",
"Action": [
"ec2:*",
"s3:*",
"rds:*",
"logs:*"
],
"Resource": "*",
"Condition": {
"StringEquals": { "aws:RequestTag/env": "dev" }
}
}この境界が付いたユーザーは dev 環境のリソース以外は作れません。自分のポリシーでも同じです。
パターン 4: 環境分離 #
- アカウント分離 (Organizations) — もっとも強い防御線
- VPC 分離 — 同じアカウントでも prod VPC と dev VPC を分離 (中級 #1)
- タグ分離 —
env=prodタグで権限 / コスト分離 (#3 のタグ戦略)
パターン 5: Break-glass ロール #
平時は ReadOnly のみ、事故時に一時的に Admin に格上げ。SSO Permission Set を 2 つに分離。
| 項目 | 平時 | 事故時 |
|---|---|---|
| 使用 | ReadOnly | Break-glass-Admin (1 時間) |
| 通知 | — | Slack チャンネルへ自動通知 |
| 監査 | — | CloudTrail ですべてのアクション記録 |
CloudTrail — 誰が何をしたか #
CloudTrail は アカウント内のすべての API 呼び出し を記録します。登録直後に自動で有効化 (過去 90 日のイベントを無料で照会)。運用用は Trail を作って S3 に永続保存。
登録直後の点検 #
コンソール → CloudTrail → Trails
→ Multi-region Trail を 1 つ作成 (運用標準)
- 名前: my-trail
- Storage: 新規 S3 バケット
- Log file SSE-KMS encryption: ON
- Log file validation: ON (改ざん検知)CloudTrail の 2 種類のイベント #
| 種類 | 何か | コスト |
|---|---|---|
| Management events | API 呼び出し (RunInstances、DeleteBucket など) | 無料 (1 回) |
| Data events | S3 オブジェクトアクセス、Lambda 呼び出しなど | 有料 (量が多い) |
ほとんどの Trail は management のみ。data events は特定のバケット / 関数だけに ON。
よく使うクエリ #
コンソール Event history で検索:
- AccessKeyId — 漏洩キー使用の痕跡
- UserName — あるユーザーがやったこと
- EventName —
ConsoleLogin、DeleteBucket、RunInstances - 時間範囲
CloudTrail Lake または Athena で SQL 分析も可能です (大きな組織)。
GuardDuty — 脅威検知の自動化 #
GuardDuty は CloudTrail / VPC Flow Logs / DNS ログを機械学習で分析して怪しい活動を拾ってくれます。
拾ってくれる例 #
| パターン | 説明 |
|---|---|
| EC2 が異常な地域と通信 | 侵入 / C&C |
| アクセスキーが異常な地域で使用 | キー漏洩後の使用 |
| 仮想通貨マイニングのトラフィック | 侵入後のマイニング |
| EC2 の異常なポートスキャン | 横移動 |
| Tor exit node との通信 | 怪しいトラフィック |
| IAM ユーザーの異常行動 | 認証情報の盗用 |
有効化 #
コンソール → GuardDuty → Get started → Enable
- 30 日無料評価
- 無料評価後はデータ量ベース課金 (普通 \$10~50 / 月 / 小さな運用)運用段階では 必ず ON。価格対比で拾ってくれる事故が大きいです。
Security Hub — セキュリティ状態の統合 #
複数のセキュリティツールの結果を 1 ヶ所にまとめます。CIS Benchmark、AWS Foundational Security Best Practices などの標準点検を自動実行。
コンソール → Security Hub → Enable
- 推奨される標準すべてを ON
- GuardDuty / Access Analyzer / Inspector の結果が統合される登録直後にはやや過剰です — 1 四半期後にリソースが一定水準以上になったら ON。
よく出会う事故事例 #
事例 1: GitHub にアクセスキー push → 仮想通貨マイニング #
もっともありがちなシナリオ。分単位でボット発見、時間単位でコスト累積。
対応: 即キー無効化 → 新キー → CloudTrail で使用痕跡 → 可能なら口座自体を隔離。請求紛争時には AWS サポートに連絡 — ほとんどの無料利用枠の事故は遡及免除 (繰り返しは不可)。
予防: pre-commit hook (gitleaks、truffleHog)、GitHub secret scanning、ローカルにキー自体を置かない (SSO)。
事例 2: 広すぎる S3 バケットポリシー #
「会社の人みんなが見られるように」しようと Principal: "*" 追加 → public read になる → 検索エンジンにインデックス。
対応: Access Analyzer が拾ってくれる。S3 Block Public Access がアカウント / バケット単位で強制。
予防: S3 Block Public Access を常に ON、本当に public が必要な場合は CloudFront + Origin Access Control パターンです。
事例 3: MFA なしルートパスワードのフィッシング #
「AWS 請求アラーム」の偽メール → 偽ログインページ → パスワード入力。ルート MFA がなければそのまま侵害。
対応: パスワード即変更、すべてのリソース点検、AWS サポート連絡。
予防: ルート MFA 必須 (理想はハードウェア)、日常作業は IAM / SSO で。
事例 4: 退職者のアクセスキーが生きている #
退職処理後も IAM ユーザー / キーがそのまま。数ヶ月後に事故。
対応: 即ユーザー無効化 / 削除 + Trail 点検。
予防: 退職チェックリストに IAM 整理を含める。SSO なら IdP で無効化 1 ヶ所で完了。
事例 5: CloudTrail が OFF #
侵害調査を始めようとしたら事件時刻のログがない — 誰かが (意図的に) Trail を OFF にしたか最初から ON にしていなかった。
対応: もう遅い。可能なら他のログ (CloudWatch、GuardDuty findings) で部分復元。
予防: Trail 有効化 + Log file validation + S3 オブジェクトロック。SCP (Service Control Policy) で CloudTrail 無効化自体を拒否。
よく出会う落とし穴 #
1) MFA だけ ON にして終わり #
MFA が ON でもアクセスキーが漏れればキーはそのまま動作します (CLI 呼び出しは MFA と無関係、#4 の認証情報チェーン)。キーも併せてケアするか、SSO でキー自体を減らします。
2) ローテーションを約束だけして実施しない #
Credential Report で定期点検を自動化。90 日を超えたキーは Slack 通知。
3) iam:PassRole の広範な許可
#
iam:PassRole = * は事実上の権限昇格が可能です。Role ARN で絞ります。
4) 条件を強くしすぎて → 自分が締め出される #
aws:SourceIp で社内 IP のみ許可したのに自分が外出中ということもあります。コンソール IP ガードは最後に 適用し、迂回路 (VPN / 緊急ユーザー) を残します。
5) GuardDuty を OFF にする #
「コストもったいない」で OFF にする場合がありますが、1 件の事故が GuardDuty の 1 年分のコストより大きいです。運用では常に ON にします。
6) Security Hub の発見を見ない #
ON にして通知を見ないなら意味がありません。週次レビューや EventBridge で Slack に通知します。
まとめ #
今回つかんだもの:
- MFA — すべてのユーザーに必須。ルートはできればハードウェア、一般は virtual TOTP。SMS は禁止
- MFA 強制ポリシー — IAM ユーザーにポリシーで強制。SSO なら自動
- アクセスキーローテーション 90 日 — 新キー発行 → 置換 → 旧キー無効化 → 1 週間後に削除。Credential Report で点検
- 漏洩時 — 即無効化、CloudTrail で使用痕跡、新キー、その後削除
- IAM Access Analyzer — 外部アクセス可能リソース、ポリシー検証、Action Last Accessed (未使用権限)
- 最小権限パターン — 広く始めて絞る、ユーザー / ロール分離、権限境界、環境分離、Break-glass
- CloudTrail — multi-region Trail 1 つ + S3 + log validation。SCP で無効化を遮断
- GuardDuty — 機械学習による脅威検知。運用必須
- Security Hub — 標準点検の統合。リソースが一定水準以上で
- 事故事例 — キー push、広い S3 ポリシー、ルートフィッシング、退職者キー、Trail OFF
- 落とし穴 — MFA だけで安全 X、ローテーション約束のみ、PassRole ワイルドカード、強すぎる IP 条件、GuardDuty OFF、Hub 結果未管理
次回 — CloudWatch #
このシリーズの最後です。すべての運用の目である CloudWatch を整理します。
#7 CloudWatch 入門 — ログ / メトリクス では Logs / Metrics / Alarms / Dashboards の構成、ロググループと retention、Metric Filter、Logs Insights クエリの基礎まで扱います。