はじめに
暑いですね。こんな日はクールなナイスガイのClaudeとともに何か楽しいことをしたいですね。
この記事では、合計3時間ほどで簡易実装からAWS Customer Playbook FrameworkとのGitHub API統合までClaudeやってくれた記録を残していきます。
AWS Customer Playbook Frameworkとは
AWS Summitでたまたま視界に入ったこのフレームワーク、当日詳しく話を聞けなかったので、自分で調べてみました。
概要と目的
aws-customer-playbook-frameworkは、Amazon Web Servicesを使用する際の様々なシナリオに対するセキュリティプレイブックのサンプルテンプレートを提供するAWS公式リポジトリです。このフレームワークは、顧客がセキュリティプレイブックを作成、開発、統合するための例示的なフレームワークとして設計されています。
利用可能なプレイブック(23種類)
- IAM認証情報の侵害対応
- ランサムウェア対応(EC2、RDS、S3別)
- S3公開アクセス対応
- ネットワーク変更への対応
- DDoS攻撃対応
- フォレンジック調査手順
などなどさまざまなサービス、状況に合わせたプレイブックがあります。
プレイブック構造(6つのセクション)
- Threat(脅威) - 対象となる脅威の説明
- Endgame(目標) - セキュリティ観点での望ましい結果
- Response steps(対応手順) - NIST 800-61r2に基づく時系列の対応手順
- Simulation(シミュレーション) - アラートをトリガーする手順
- Incident classification(インシデント分類) - MITRE ATT&CKに基づく分類
- Incident handling process(処理プロセス) - 各専門分野の詳細ガイダンス
NIST準拠の体系的アプローチ
これらのプレイブックは、NIST Special Publication 800-61r2 Computer Security Incident Handling Guideに従ったインシデント対応ライフサイクルに基づいて構築されています。
と、参考になりそうなフレームワークですが
日本語版はおそらく機械翻訳なのでギリ理解はできるのですが、やっぱりもう少し読みやすくあってほしい。。
てことで、
巷で話題のMCPとやらで応用すれば、いい感じに相棒になってくれそう!!
ということで、MCPについても学び始めました。
Model Context Protocol (MCP) ってなんだ?
このスライドがわかりやすいので、これをみてみてください!
引用すると
生成AIにモデルに文脈情報を渡しやすくするための技術規格ですね。
もっとざっくり言えば、AIが色々なアプリやサービスと話すための「共通言語」 ですね。
AWS Customer Playbook Advisor MCPをClaudeでつくる
AWS Customer Playbook Advisor MCP と命名したMCPを、AWS公式の aws-customer-playbook-framework
リポジトリと連携し、リアルタイムでセキュリティガイダンスを提供するシステムにしていきます。
が、詳しいことはわからないのでClaudeと一緒に作っていきます。
まずは要件定義をClaudeでつくる
最近AIに何かを頼む時は、前提となる知識を調べてもらってから、依頼をしています。
今回であれば、
MCPってなにか解説して
MCPを自作する方法を調べてから教えて
のようにいきなり依頼せず前提となる知識を揃えてから作りたいものが何かを教えます。
もちろん1プロンプトでできないこともないですが、1つ1つやっていくと確実性が高まると同時に自分の知識としてもついていくのでおすすめです。
ということで、諸々情報を整理してから実際に作ってもらいました。
が、実際に作成されたものはいろんな要素をカバーしすぎて、個人で使うだけで言えばやりすぎ感のある要件定義書になってしまったので、もう少しシンプルな設計にしてなどを言って、綺麗にまとめてもらいました。
実際の要件定義書
# AWS セキュリティプレイブックアドバイザー MCP - 要件定義書(MVP)
## 1. プロジェクト概要
### プロジェクト名
AWS Customer Playbook Advisor MCP
### リポジトリ
https://github.com/xxxxx/aws-customer-playbook-advisor-mcp
### プロジェクト説明
AWSの公式セキュリティプレイブックフレームワーク(aws-customer-playbook-framework)を知識ベースとして活用し、セキュリティ担当者にリアルタイムで予防的セキュリティガイダンスを提供するMCPサーバー。
## 2. 目的・背景
### 目的
- AWSセキュリティのベストプラクティスを手軽にアクセス可能にする
- セキュリティインシデントの事前予防を支援する
- aws-customer-playbook-frameworkの実用的な活用を促進する
- セキュリティ知識の民主化を実現する
### 背景
- AWSセキュリティのベストプラクティスは散在している
- セキュリティ担当者は迅速な判断を求められる
- 公式プレイブックの存在は知られているが活用されていない
- MCPプロトコルによりAIアシスタントとの統合が可能になった
## 3. 機能要件(MVP)
### 3.1 主要機能
#### 予防的ガイダンス提供 (`get_prevention_guidance`)
- **入力**: AWSサービス名またはセキュリティ関連の質問
- **処理**: aws-customer-playbook-frameworkから関連情報を取得・解析
- **出力**: 具体的な予防策と実装手順
#### 対応シナリオ(最低限)
1. **S3セキュリティ強化**
- 「S3のセキュリティを強化したい」
- → S3公開アクセス防止プレイブックから予防策を抽出
2. **IAM設定見直し**
- 「IAMの設定を見直したい」
- → IAMクレデンシャル侵害プレイブックから予防策を抽出
## 4. 非機能要件
### 4.1 パフォーマンス
- GitHub API呼び出し:10秒以内に応答
- シンプルなエラーメッセージ提供
### 4.2 セキュリティ
- GitHub APIは公開リポジトリのみアクセス(認証不要)
- 入力値の基本的なバリデーション
## 5. 技術仕様
### 5.1 開発環境
- **言語**: TypeScript
- **ランタイム**: Node.js 18+
- **フレームワーク**: Model Context Protocol SDK
### 5.2 依存パッケージ(最小限)
{
"dependencies": {
"@modelcontextprotocol/sdk": "^latest",
"zod": "^latest"
},
"devDependencies": {
"@types/node": "^latest",
"typescript": "^latest"
}
}
### 5.3 外部API
- **GitHub API**: aws-customer-playbook-frameworkリポジトリ(認証不要)
## 6. アーキテクチャ
### 6.1 システム構成
[Claude Desktop] ←→ [MCP Client] ←→ [AWS Security Advisor MCP] ←→ [GitHub API]
### 6.2 ディレクトリ構成(最小限)
aws-customer-playbook-advisor-mcp/
├── README.md
├── package.json
├── tsconfig.json
└── src/
└── index.ts # MCPサーバー全機能
## 7. 開発計画(MVP)
### 7.1 開発ステップ
1. **基盤構築**
- [x] リポジトリ作成
- [ ] プロジェクト初期化
- [ ] 基本MCPサーバー実装
2. **機能実装**
- [ ] GitHub APIからプレイブック取得
- [ ] S3セキュリティガイダンス機能
- [ ] IAMセキュリティガイダンス機能
3. **動作確認**
- [ ] Claude Desktop連携テスト
- [ ] 基本的なエラーハンドリング
### 7.2 成果物
- 動作するMCPサーバー(1つのファイル)
- README.md(使用方法)
## 8. 成功指標(MVP)
### 8.1 機能的指標
- S3セキュリティガイダンス機能の正常動作
- IAMセキュリティガイダンス機能の正常動作
- Claude Desktopでの基本動作確認
### 8.2 品質指標
- 基本的なエラーハンドリング実装
- README.mdによる使用方法説明
---
**作成日**: 2025年6月29日
**最終更新**: 2025年6月29日
**バージョン**: 1.0 (MVP)
**作成者**: プロジェクトチーム
ClaudeCodeで実装する
最近VSCodeの拡張機能でClaudeCodeが追加されたみたいなのでこれもどうせなら使っていきます。
まずは簡易実装から
GithubのAPIを取得し情報をとってくる実装をする前に、そもそもMCPが機能するかどうかを試してみました。
なので、答えてくれる内容としてはハードコーディングした内容になります。
簡易版のアーキテクチャ
簡易版では実装スピードを最優先し、極限まで簡素化されたアプローチを採用:
// 静的データによるプロトタイプ
const SECURITY_GUIDANCE = {
general: "AWS基本セキュリティ原則:最小権限の原則、多層防御、継続的監視...",
s3: "S3セキュリティ:パブリックアクセスブロック、バージョニング、暗号化...",
iam: "IAMベストプラクティス:強力なパスワードポリシー、MFA、定期的な監査...",
ec2: "EC2セキュリティ:定期的なパッチ適用、セキュリティグループ設定、最小権限...",
vpc: "VPCセキュリティ:適切なサブネット設計、NACLs、フローログ..."
};
MCPサーバー実装
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = new Server({
name: 'aws-security-advisor',
version: '1.0.0'
}, {
capabilities: {
tools: {}
}
});
// ツール定義
const tools = [{
name: 'get_security_guidance',
description: 'AWSサービスのセキュリティガイダンスを取得',
inputSchema: {
type: 'object',
properties: {
service: {
type: 'string',
description: 'AWSサービス名(例: s3, iam, ec2)'
},
question: {
type: 'string',
description: '具体的な質問や懸念事項'
}
},
required: ['service']
}
}];
結果: 立案から1時間で簡易版完成!基本的な静的ガイダンス提供機能を実装
今回はClaude DesktopでMCPを使います。
Claude DesktopでMCPを使うためにやったこととしては↑の記事とほぼ同じです。
お、AWS Customer Playbook Advisor MCPでハードコーディングした中身をみてくれていますね!
実際にMCPを元にした回答が返ってくるのか、あえて嘘の情報をハードコーディングして試してみました。
しかし、自分が仕掛けた嘘のセキュリティプラクティスには引っかからず、
割とまともな回答が返ってきました。
参考にはしたけど明らかに間違っているものは返答として入れるわけではなさそうですね。
絶対違うだろ!ってやつは推論のログ?でも考察の経過を見ることができますね。おもしろいなぁ。
GitHub API統合によってCustomer Playbook Frameworkを元にした回答をしてもらおう!
いい感じにMCPが動いていそうなので、次はMCP内でGitHubAPIを使って実際にCustomer Playbook Frameworkを見てもらい、それを元にした回答をしてもらいましょう。
ClaudeCodeに
要件定義を元にして、実際にCustomer Playbook Frameworkを見にいく仕様にしたいので実装計画を立ててください。
と依頼し、実際に作ってもらった実装計画を見てよさそうだったのでそれを元に作ってもらいました。
以下は実装したコードです。
コード例
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// GitHub APIクライアント
class GitHubPlaybookClient {
private baseUrl = 'https://api.github.com';
private repo = 'aws-samples/aws-customer-playbook-framework';
private cache = new Map<string, { content: string; timestamp: number }>();
private cacheTimeout = 5 * 60 * 1000; // 5分キャッシュ
async getPlaybookList(): Promise<string[]> {
try {
const response = await fetch(`${this.baseUrl}/repos/${this.repo}/contents/docs`);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`);
}
const files = await response.json() as any[];
return files
.filter(file => file.name.endsWith('.md') && file.name !== 'README.md')
.map(file => file.name.replace('.md', ''));
} catch (error) {
console.error('Error fetching playbook list:', error);
return [];
}
}
async getPlaybookContent(filename: string): Promise<string> {
const cacheKey = `playbook_${filename}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.content;
}
try {
const response = await fetch(`${this.baseUrl}/repos/${this.repo}/contents/docs/${filename}.md`);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`);
}
const data = await response.json() as any;
const content = Buffer.from(data.content, 'base64').toString('utf-8');
// キャッシュに保存
this.cache.set(cacheKey, { content, timestamp: Date.now() });
return content;
} catch (error) {
console.error(`Error fetching playbook ${filename}:`, error);
return '';
}
}
async searchPlaybooks(query: string): Promise<{ filename: string; content: string }[]> {
const playbooks = await this.getPlaybookList();
const results: { filename: string; content: string }[] = [];
for (const playbook of playbooks) {
if (playbook.toLowerCase().includes(query.toLowerCase())) {
const content = await this.getPlaybookContent(playbook);
if (content) {
results.push({ filename: playbook, content });
}
}
}
return results;
}
async searchByServiceName(serviceName: string): Promise<{ filename: string; content: string }[]> {
const playbooks = await this.getPlaybookList();
const results: { filename: string; content: string }[] = [];
// サービス名に基づくファイル名フィルタリング
const serviceKeywords: Record<string, string[]> = {
's3': ['s3', 'public_access'],
'iam': ['iam', 'credentials', 'compromised'],
'ec2': ['ec2', 'ransom_response_ec2'],
'rds': ['rds', 'ransom_response_rds'],
'vpc': ['vpc', 'network'],
'ses': ['ses'],
'sagemaker': ['sagemaker'],
'bedrock': ['bedrock'],
'ransomware': ['ransom'],
'cryptojacking': ['cryptojacking']
};
const keywords = serviceKeywords[serviceName.toLowerCase()] || [serviceName.toLowerCase()];
for (const playbook of playbooks) {
const playbookLower = playbook.toLowerCase();
if (keywords.some((keyword: string) => playbookLower.includes(keyword))) {
const content = await this.getPlaybookContent(playbook);
if (content) {
results.push({ filename: playbook, content });
}
}
}
return results;
}
extractSummary(content: string): string {
const lines = content.split('\n');
let summary = '';
let inSummary = false;
for (const line of lines) {
if (line.includes('## Summary') || line.includes('## 概要') || line.includes('# Overview')) {
inSummary = true;
continue;
}
if (inSummary && line.startsWith('##') && !line.includes('Summary') && !line.includes('概要')) {
break;
}
if (inSummary && line.trim()) {
summary += line + '\n';
}
}
// サマリーが見つからない場合は最初の数行を使用
if (!summary.trim()) {
const firstLines = lines.slice(0, 10).filter(line => line.trim() && !line.startsWith('#')).slice(0, 3);
summary = firstLines.join('\n');
}
return summary.trim() || content.substring(0, 500) + '...';
}
extractPreventionGuidance(content: string): string {
const lines = content.split('\n');
let guidance = '';
let inPrevention = false;
// 予防策に関するセクションを探す
for (const line of lines) {
if (line.toLowerCase().includes('prevention') ||
line.toLowerCase().includes('mitigation') ||
line.toLowerCase().includes('best practices') ||
line.toLowerCase().includes('security controls') ||
line.includes('予防')) {
inPrevention = true;
guidance += line + '\n';
continue;
}
if (inPrevention && line.startsWith('##') &&
!line.toLowerCase().includes('prevention') &&
!line.toLowerCase().includes('mitigation') &&
!line.toLowerCase().includes('best practices')) {
break;
}
if (inPrevention && line.trim()) {
guidance += line + '\n';
}
}
return guidance.trim() || this.extractSummary(content);
}
}
const githubClient = new GitHubPlaybookClient();
// MCPサーバーの初期化
const server = new Server(
{
name: 'aws-security-advisor',
version: '2.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// ツールハンドラーの設定
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'get_aws_playbook',
description: 'AWS公式プレイブックフレームワークから最新のセキュリティプレイブックを取得します',
inputSchema: {
type: 'object',
properties: {
scenario: {
type: 'string',
description: 'セキュリティシナリオ(例: s3, iam, ransomware, compromised, public_access等)',
},
playbook_name: {
type: 'string',
description: '特定のプレイブック名(オプション)',
},
},
required: ['scenario'],
},
},
{
name: 'get_prevention_guidance',
description: 'AWSサービスの予防的セキュリティガイダンスを公式プレイブックから取得します',
inputSchema: {
type: 'object',
properties: {
service: {
type: 'string',
description: 'AWSサービス名(例: S3, IAM, EC2, VPC, RDS等)',
},
question: {
type: 'string',
description: '具体的な質問(オプション)',
},
},
required: ['service'],
},
},
{
name: 'list_available_playbooks',
description: '利用可能なAWSセキュリティプレイブックの一覧を取得します',
inputSchema: {
type: 'object',
properties: {},
},
},
],
};
});
// ツール実行ハンドラー
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'get_prevention_guidance': {
const { service, question } = args as { service?: string; question?: string };
if (!service) {
return {
content: [{ type: 'text', text: 'エラー: serviceパラメータが必要です。' }],
};
}
try {
const results = await githubClient.searchByServiceName(service);
if (results.length === 0) {
return {
content: [{ type: 'text', text: `サービス "${service}" に関連するプレイブックが見つかりませんでした。利用可能なサービス: S3, IAM, EC2, RDS, VPC, SES, SageMaker, Bedrock` }],
};
}
let response = question ? `### 質問: ${question}\n\n` : '';
response += `# ${service.toUpperCase()}セキュリティガイダンス\n\n`;
for (const result of results) {
const preventionGuidance = githubClient.extractPreventionGuidance(result.content);
response += `## ${result.filename}\n\n${preventionGuidance}\n\n---\n\n`;
}
return {
content: [{ type: 'text', text: response }],
};
} catch (error) {
return {
content: [{ type: 'text', text: `エラー: ガイダンスの取得に失敗しました - ${error}` }],
};
}
}
case 'list_available_playbooks': {
try {
const playbooks = await githubClient.getPlaybookList();
const playbookList = playbooks.length > 0
? `## 利用可能なAWSセキュリティプレイブック\n\n${playbooks.map(name => `- ${name}`).join('\n')}\n\n合計: ${playbooks.length}個のプレイブック`
: 'プレイブック一覧の取得に失敗しました。';
return {
content: [{ type: 'text', text: playbookList }],
};
} catch (error) {
return {
content: [{ type: 'text', text: `エラー: プレイブック一覧の取得に失敗しました - ${error}` }],
};
}
}
case 'get_aws_playbook': {
const { scenario, playbook_name } = args as { scenario?: string; playbook_name?: string };
if (!scenario) {
return {
content: [{ type: 'text', text: 'エラー: scenarioパラメータが必要です。' }],
};
}
try {
// 特定のプレイブックが指定されている場合
if (playbook_name) {
const content = await githubClient.getPlaybookContent(playbook_name);
if (content) {
return {
content: [{ type: 'text', text: `# ${playbook_name}\n\n${content}` }],
};
} else {
return {
content: [{ type: 'text', text: `エラー: プレイブック "${playbook_name}" が見つかりませんでした。` }],
};
}
}
// シナリオベースで検索
const results = await githubClient.searchPlaybooks(scenario);
if (results.length === 0) {
return {
content: [{ type: 'text', text: `シナリオ "${scenario}" に関連するプレイブックが見つかりませんでした。` }],
};
}
// 最初の結果を返す(複数ある場合は最初のもの)
const firstResult = results[0];
const summary = githubClient.extractSummary(firstResult.content);
let response = `# ${firstResult.filename}\n\n## 概要\n${summary}\n\n`;
if (results.length > 1) {
response += `## その他の関連プレイブック\n${results.slice(1).map(r => `- ${r.filename}`).join('\n')}\n\n`;
}
response += `## 詳細コンテンツ\n${firstResult.content}`;
return {
content: [{ type: 'text', text: response }],
};
} catch (error) {
return {
content: [{ type: 'text', text: `エラー: プレイブックの取得に失敗しました - ${error}` }],
};
}
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// サーバーの起動
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('AWS Security Advisor MCP Server v2.0 started (GitHub API only)');
}
main().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});
実際に触ってみよう
おーさっきとは違う挙動をしていそうですね。
実際にCustomer Playbook Frameworkを見てもRansom_Response_S3.mdが存在します。
Customer Playbook FrameworkにあるS3のセキュリティに関する情報をまとめて教えてくれているみたいです。
実際にS3に関するセキュリティガイドブックを作成してもらいました。中身を見てみると主にS3_Public_Access.mdとRansom_Response_S3.mdを参考にしているような記述が多く存在していそうです。
他にもCustomer Playbook Frameworkでは、インシデント対応についても書いてあるのでそれについて簡易的なセキュリティプレイブックを作成してくれることも可能です。
↓はIAMの認証情報侵害のプレイブックを作成してもらいました。
全体的に読みやすく概要を掴みやすくなりましたね!やった!
他にもできそうなこと
このMCPをつかっていろんなことができそうなので考えてみました。
- Customer Playbook Frameworkを元にしたインフラ設計&コード実装を全てAIで
- IaC化してあるインフラコードをAIに分析してもらい、Customer Playbook Frameworkを元にやった方がよさそうな設定の案出し
- 現在のセキュリティプレイブックたBCP対応の改善
- 問題が起こった際にすぐ相談できる相手先にする
みたいな感じでMCP化することで、読みやすくなるだけでなく普段使っているAIの使い方と組み合わせて効果的に使うことができるようになります。
まとめ
わかりにくいものがあったら、MCP化して勝手に読みに行かせるってことを行いました。
とはいったもののやったのは全てClaudeで自分はこうなったらいいなぁをひたすら言ってただけでした。笑
MCPの便利さを学ぶと同時にAI駆動開発的なことも行えたのでよかったです。(ClaudeCodeめっちゃすごい!)
AWS Security Advisor MCPとともにCustomer Playbook Frameworkを使いこなしてセキュリティを固めつつ、それでも侵害された時の対応力をつけていきます。
※MCPとして公開していいかわからなかったので、コードの公開に留めておきます。もし使い方がいらっしゃれば自己責任でお試しください!