第11章:セキュリティ実践
11.1 AI協働時代のSecrets管理
AI生成コードを考慮したSecrets管理
AIが誤って機密情報を生成するリスクを踏まえ、第2章の品質ゲートと連携した管理が必要です。
AI対応を含むSecretsの階層
secrets_hierarchy:
# Organization secrets
organization:
scope: "All repositories in organization"
examples:
- DOCKERHUB_TOKEN
- NPM_AUTH_TOKEN
- SONAR_TOKEN
- GITHUB_COPILOT_API_TOKEN # Copilot API利用時のトークン(Enterprise設定の自動化用)
ai_safety:
- scan_for_hardcoded: true
- ai_generation_block: true
# Repository secrets
repository:
scope: "Specific repository only"
examples:
- DATABASE_URL
- API_KEY
- DEPLOYMENT_KEY
ai_protection:
- pattern_detection: enabled
- placeholder_check: true # AIが生成しやすいプレースホルダー検出
# Environment secrets
environment:
scope: "Specific environment in repository"
examples:
- PROD_DATABASE_URL
- STAGING_API_KEY
- DEV_CREDENTIALS
ai_restrictions:
- production:
ai_access: "read-only" # 本番環境はAIアクセス制限
- development:
ai_access: "full" # 開発環境はAI支援許可
注意:GitHub Copilot APIトークンについて
GITHUB_COPILOT_API_TOKEN
は、GitHub Copilot Enterprise環境でAPI経由での設定自動化や使用状況監視を行う場合に必要となるトークンです。一般的なCopilotの利用では不要ですが、大規模組織でのCopilot設定の一括管理や、使用統計の自動収集を行う際に使用されます。
Secretsの作成と管理
GitHub CLIを使用した一括設定
#!/bin/bash
# set_secrets.sh
# Organization secrets
gh secret set DOCKERHUB_TOKEN --org ai-research-lab < ~/.docker/token
gh secret set NPM_AUTH_TOKEN --org ai-research-lab < ~/.npm/token
# Repository secrets
gh secret set DATABASE_URL --repo ai-research-lab/ml-platform < .env.production
gh secret set AWS_ACCESS_KEY_ID --repo ai-research-lab/ml-platform
# Environment secrets
gh secret set PROD_DATABASE_URL \
--repo ai-research-lab/ml-platform \
--env production < .env.production
プログラムによるSecrets管理
# secrets_manager.py
import base64
from nacl import encoding, public
class SecretsManager:
def __init__(self, github_client):
self.github = github_client
def encrypt_secret(self, public_key, secret_value):
"""GitHub用にシークレットを暗号化"""
public_key_obj = public.PublicKey(
public_key.encode("utf-8"),
encoding.Base64Encoder()
)
sealed_box = public.SealedBox(public_key_obj)
encrypted = sealed_box.encrypt(secret_value.encode("utf-8"))
return base64.b64encode(encrypted).decode("utf-8")
def set_repository_secret(self, repo_name, secret_name, secret_value):
"""リポジトリシークレットを設定"""
repo = self.github.get_repo(repo_name)
# 公開鍵を取得
public_key = repo.get_public_key()
# 暗号化
encrypted_value = self.encrypt_secret(
public_key.key,
secret_value
)
# シークレットを作成/更新
repo.create_secret(
secret_name,
encrypted_value,
public_key.key_id
)
def rotate_secrets(self, repo_name, secret_mapping):
"""シークレットのローテーション"""
repo = self.github.get_repo(repo_name)
rotation_log = []
for secret_name, new_value in secret_mapping.items():
try:
# 新しい値を設定
self.set_repository_secret(repo_name, secret_name, new_value)
rotation_log.append({
'secret': secret_name,
'status': 'rotated',
'timestamp': datetime.now().isoformat()
})
# 通知
self.notify_secret_rotation(repo_name, secret_name)
except Exception as e:
rotation_log.append({
'secret': secret_name,
'status': 'failed',
'error': str(e),
'timestamp': datetime.now().isoformat()
})
return rotation_log
Secretsの使用パターン
AI生成コードセキュリティチェックを含むワークフロー
name: Deploy to Production with AI Security
on:
push:
branches: [main]
jobs:
# 第2章の品質ゲートにAIセキュリティチェックを追加
ai-security-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: AI Placeholder Detection
run: |
python scripts/ai_security_checker.py --check-placeholders
- name: AI Hardcoded Secrets Scan
run: |
python scripts/ai_security_checker.py --check-secrets
- name: AI Code Pattern Analysis
run: |
# AI生成コードの脆弱性パターンをチェック
python scripts/ai_vulnerability_scanner.py
deploy:
needs: ai-security-check
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: us-east-1
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: $
password: $
- name: Deploy application
env:
DATABASE_URL: $
API_KEY: $
run: |
# Secretsは環境変数として利用可能
./deploy.sh
10.2 Environment設定と環境変数
Environment(環境)の設計
環境の定義
environments:
development:
protection_rules: false
secrets:
- DEV_DATABASE_URL
- DEV_API_KEY
variables:
- LOG_LEVEL: "debug"
- FEATURE_FLAGS: "all"
staging:
protection_rules:
required_reviewers: ["qa-team"]
wait_timer: 0
secrets:
- STAGING_DATABASE_URL
- STAGING_API_KEY
variables:
- LOG_LEVEL: "info"
- FEATURE_FLAGS: "stable"
production:
protection_rules:
required_reviewers: ["ops-team", "security-team"]
wait_timer: 30 # 30分の待機時間
secrets:
- PROD_DATABASE_URL
- PROD_API_KEY
variables:
- LOG_LEVEL: "warning"
- FEATURE_FLAGS: "production"
環境保護ルール
Protection Rules設定
# environment_manager.py
class EnvironmentManager:
def __init__(self, repo):
self.repo = repo
def create_environment(self, env_name, protection_rules=None):
"""環境を作成して保護ルールを設定"""
# GitHub APIを使用して環境を作成
url = f"{self.repo.url}/environments/{env_name}"
data = {
"wait_timer": protection_rules.get("wait_timer", 0),
"reviewers": [
{"type": "User", "id": user_id}
for user_id in protection_rules.get("required_reviewers", [])
],
"deployment_branch_policy": {
"protected_branches": protection_rules.get("protected_branches", True),
"custom_branch_policies": protection_rules.get("branch_patterns", False)
}
}
response = requests.put(url, json=data, headers=self.headers)
return response.json()
def set_environment_secrets(self, env_name, secrets):
"""環境固有のシークレットを設定"""
for secret_name, secret_value in secrets.items():
self.set_environment_secret(
env_name,
secret_name,
secret_value
)
def deploy_with_approval(self, env_name, deployment_data):
"""承認付きデプロイメント"""
# デプロイメントを作成
deployment = self.repo.create_deployment(
ref=deployment_data['ref'],
environment=env_name,
required_contexts=[],
payload=deployment_data.get('payload', {})
)
# 承認待ち
if self.requires_approval(env_name):
print(f"Waiting for approval for {env_name} deployment...")
# 承認プロセス
return deployment
環境変数のベストプラクティス
設定ファイルテンプレート
# config_template.py
import os
from typing import Dict, Any
class ConfigTemplate:
"""環境別設定テンプレート"""
@staticmethod
def get_config(environment: str) -> Dict[str, Any]:
base_config = {
'app_name': 'ml-platform',
'version': '1.0.0',
'features': {
'ml_monitoring': True,
'auto_scaling': False,
'debug_mode': False
}
}
env_configs = {
'development': {
'database_url': os.getenv('DEV_DATABASE_URL'),
'api_endpoint': 'https://dev-api.example.com',
'log_level': 'DEBUG',
'features': {
'ml_monitoring': True,
'auto_scaling': False,
'debug_mode': True
}
},
'staging': {
'database_url': os.getenv('STAGING_DATABASE_URL'),
'api_endpoint': 'https://staging-api.example.com',
'log_level': 'INFO',
'features': {
'ml_monitoring': True,
'auto_scaling': True,
'debug_mode': False
}
},
'production': {
'database_url': os.getenv('PROD_DATABASE_URL'),
'api_endpoint': 'https://api.example.com',
'log_level': 'WARNING',
'features': {
'ml_monitoring': True,
'auto_scaling': True,
'debug_mode': False
}
}
}
# ベース設定と環境固有設定をマージ
config = base_config.copy()
config.update(env_configs.get(environment, {}))
return config
@staticmethod
def validate_config(config: Dict[str, Any]) -> bool:
"""設定の妥当性を検証"""
required_keys = ['database_url', 'api_endpoint', 'log_level']
for key in required_keys:
if key not in config or not config[key]:
raise ValueError(f"Missing required config: {key}")
return True
10.3 Dependabotによる脆弱性対策
Dependabot設定の最適化
包括的な設定例
.github/dependabot.yml
:
version: 2
updates:
# Python dependencies
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
time: "09:00"
timezone: "Asia/Tokyo"
open-pull-requests-limit: 10
# バージョニング戦略
versioning-strategy: "increase-if-necessary"
# レビュアーとラベル
reviewers:
- "security-team"
- "ml-team"
labels:
- "dependencies"
- "security"
# コミットメッセージ
commit-message:
prefix: "chore"
prefix-development: "chore"
include: "scope"
# 特定の依存関係を無視
ignore:
- dependency-name: "tensorflow"
versions: ["2.x"] # TF 1.xを使い続ける
# セキュリティアップデートのみ
allow:
- dependency-type: "direct"
- dependency-type: "indirect"
# グループ化
groups:
ml-dependencies:
patterns:
- "torch*"
- "tensorflow*"
- "scikit-learn"
# Docker dependencies
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
reviewers:
- "devops-team"
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
commit-message:
prefix: "ci"
脆弱性対応の自動化
自動マージ設定
name: Auto-merge Dependabot PRs
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: write
pull-requests: write
jobs:
auto-merge:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Metadata
id: metadata
uses: dependabot/fetch-metadata@v1
with:
github-token: "$"
- name: Auto-merge for patch and minor updates
if: |
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
steps.metadata.outputs.update-type == 'version-update:semver-minor'
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: $
GITHUB_TOKEN: $
- name: Approve PR
if: |
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
steps.metadata.outputs.update-type == 'version-update:semver-minor'
run: gh pr review --approve "$PR_URL"
env:
PR_URL: $
GITHUB_TOKEN: $
脆弱性レポートの生成
カスタムレポートツール
# vulnerability_reporter.py
import json
from datetime import datetime
from typing import List, Dict
class VulnerabilityReporter:
def __init__(self, github_client):
self.github = github_client
def get_vulnerability_alerts(self, repo_name):
"""脆弱性アラートを取得"""
repo = self.github.get_repo(repo_name)
query = """
query($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
vulnerabilityAlerts(first: 100) {
nodes {
createdAt
dismissedAt
securityVulnerability {
package {
name
ecosystem
}
severity
advisory {
summary
description
cvss {
score
}
}
}
}
}
}
}
"""
# GraphQL APIを使用
import requests
headers = {
'Authorization': f'token {self.github._Github__requester._Requester__authorizationHeader.split()[1]}',
'Content-Type': 'application/json'
}
data = {
'query': query,
'variables': {"owner": repo.owner.login, "name": repo.name}
}
response = requests.post(
'https://api.github.com/graphql',
headers=headers,
json=data
)
result = response.json()
return result['data']['repository']['vulnerabilityAlerts']['nodes']
def generate_report(self, repos: List[str]) -> Dict:
"""複数リポジトリの脆弱性レポート生成"""
report = {
'generated_at': datetime.now().isoformat(),
'summary': {
'total_vulnerabilities': 0,
'critical': 0,
'high': 0,
'medium': 0,
'low': 0
},
'by_repository': {},
'by_package': {}
}
for repo_name in repos:
alerts = self.get_vulnerability_alerts(repo_name)
active_alerts = [a for a in alerts if not a['dismissedAt']]
repo_summary = {
'total': len(active_alerts),
'by_severity': {},
'vulnerabilities': []
}
for alert in active_alerts:
vuln = alert['securityVulnerability']
severity = vuln['severity']
package_name = vuln['package']['name']
# サマリー更新
report['summary']['total_vulnerabilities'] += 1
report['summary'][severity.lower()] += 1
# リポジトリ別
repo_summary['by_severity'][severity] = \
repo_summary['by_severity'].get(severity, 0) + 1
repo_summary['vulnerabilities'].append({
'package': package_name,
'severity': severity,
'summary': vuln['advisory']['summary'],
'cvss_score': vuln['advisory']['cvss']['score']
})
# パッケージ別
if package_name not in report['by_package']:
report['by_package'][package_name] = {
'affected_repos': [],
'severity': severity
}
report['by_package'][package_name]['affected_repos'].append(repo_name)
report['by_repository'][repo_name] = repo_summary
return report
def prioritize_fixes(self, report: Dict) -> List[Dict]:
"""修正の優先順位付け"""
priorities = []
for package, info in report['by_package'].items():
score = 0
# 深刻度によるスコア
severity_scores = {
'CRITICAL': 40,
'HIGH': 30,
'MEDIUM': 20,
'LOW': 10
}
score += severity_scores.get(info['severity'], 0)
# 影響範囲によるスコア
score += len(info['affected_repos']) * 5
priorities.append({
'package': package,
'priority_score': score,
'severity': info['severity'],
'affected_repos': info['affected_repos']
})
# スコアの高い順にソート
priorities.sort(key=lambda x: x['priority_score'], reverse=True)
return priorities
10.4 Security Policyの設定
セキュリティポリシーファイル
SECURITY.md の作成
# Security Policy
## Supported Versions
現在サポートされているバージョン:
| Version | Supported |
| ------- | ------------------ |
| 2.x.x | :white_check_mark: |
| 1.x.x | :white_check_mark: |
| < 1.0 | :x: |
## Reporting a Vulnerability
### 報告方法
セキュリティ脆弱性を発見した場合は、以下の手順で報告してください:
1. **公開しない**: GitHub Issuesやその他の公開チャンネルで報告しないでください
2. **プライベート報告**: security@example.com にメールを送信
3. **暗号化**: 可能であればPGP暗号化を使用(公開鍵: [link])
### 報告に含めるべき情報
- 脆弱性の詳細な説明
- 再現手順
- 影響範囲
- 可能であれば修正案
### 対応プロセス
1. **受領確認**: 48時間以内に受領確認をお送りします
2. **初期評価**: 1週間以内に深刻度を評価し、対応計画をお知らせします
3. **修正**: 深刻度に応じて以下のタイムラインで対応します:
- Critical: 48時間以内
- High: 1週間以内
- Medium: 2週間以内
- Low: 次回リリース時
### 報奨金プログラム
有効な脆弱性報告には、深刻度に応じて報奨金を提供しています:
- Critical: $1,000 - $5,000
- High: $500 - $1,000
- Medium: $100 - $500
- Low: クレジット表記
### 除外事項
以下は脆弱性報告の対象外です:
- DoS攻撃
- ソーシャルエンジニアリング
- 物理的な攻撃
- サービスの乱用
## Security Advisories
過去のセキュリティアドバイザリは[こちら](/security/advisories)で確認できます。
## Contact
- セキュリティチーム: security@example.com
- PGP公開鍵: [0xABCDEF12](https://keys.example.com)
セキュリティスキャンの統合
複数スキャナーの統合
name: Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * 0' # 毎週日曜日
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# 1. CodeQL
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: 'python, javascript'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
# 2. Trivy (コンテナスキャン)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:$'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
# 3. Snyk
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/python@master
env:
SNYK_TOKEN: $
with:
args: --severity-threshold=high
# 4. OWASP Dependency Check
- name: Run OWASP Dependency Check
uses: jeremylong/dependency-check-action@main
with:
project: 'ml-platform'
path: '.'
format: 'ALL'
# 5. カスタムセキュリティチェック
- name: Run custom security checks
run: |
python scripts/security_check.py
bash scripts/secret_scan.sh
10.5 セキュリティインシデント対応
インシデント対応プレイブック
対応フロー
# incident_response.py
from enum import Enum
from datetime import datetime
import asyncio
class IncidentSeverity(Enum):
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
class IncidentResponse:
def __init__(self):
self.response_times = {
IncidentSeverity.CRITICAL: 30, # 30分
IncidentSeverity.HIGH: 120, # 2時間
IncidentSeverity.MEDIUM: 480, # 8時間
IncidentSeverity.LOW: 1440 # 24時間
}
async def handle_incident(self, incident):
"""インシデント対応のオーケストレーション"""
# 1. インシデントの記録
incident_id = self.log_incident(incident)
# 2. 深刻度の評価
severity = self.assess_severity(incident)
# 3. 通知
await self.notify_stakeholders(incident_id, severity)
# 4. 初期対応
if severity in [IncidentSeverity.CRITICAL, IncidentSeverity.HIGH]:
await self.immediate_containment(incident)
# 5. 調査
investigation = await self.investigate(incident)
# 6. 修復
await self.remediate(incident, investigation)
# 7. 事後分析
await self.post_mortem(incident_id)
return incident_id
def assess_severity(self, incident):
"""インシデントの深刻度を評価"""
severity_factors = {
'data_breach': IncidentSeverity.CRITICAL,
'unauthorized_access': IncidentSeverity.HIGH,
'malware_detection': IncidentSeverity.HIGH,
'policy_violation': IncidentSeverity.MEDIUM,
'suspicious_activity': IncidentSeverity.LOW
}
return severity_factors.get(
incident['type'],
IncidentSeverity.MEDIUM
)
async def immediate_containment(self, incident):
"""即座の封じ込め措置"""
actions = []
if incident['type'] == 'unauthorized_access':
# アクセストークンの無効化
actions.append(self.revoke_tokens(incident['user']))
# リポジトリへのアクセス制限
actions.append(self.restrict_access(incident['repositories']))
elif incident['type'] == 'data_breach':
# 影響を受けたシークレットのローテーション
actions.append(self.rotate_secrets(incident['compromised_secrets']))
# 監査ログの保全
actions.append(self.preserve_audit_logs())
await asyncio.gather(*actions)
def generate_incident_report(self, incident_id):
"""インシデントレポートの生成"""
incident = self.get_incident(incident_id)
report = f"""
# Security Incident Report
## Incident ID: {incident_id}
### Summary
- **Date/Time**: {incident['timestamp']}
- **Type**: {incident['type']}
- **Severity**: {incident['severity']}
- **Status**: {incident['status']}
### Timeline
{self.format_timeline(incident['timeline'])}
### Impact Assessment
- **Affected Systems**: {incident['affected_systems']}
- **Data Exposure**: {incident['data_exposure']}
- **User Impact**: {incident['user_impact']}
### Response Actions
{self.format_actions(incident['response_actions'])}
### Root Cause Analysis
{incident['root_cause']}
### Lessons Learned
{incident['lessons_learned']}
### Recommendations
{self.format_recommendations(incident['recommendations'])}
"""
return report
自動化されたインシデント検知
リアルタイム監視
# security_monitor.py
class SecurityMonitor:
def __init__(self):
self.alert_threshold = {
'failed_logins': 5,
'api_rate_limit': 1000,
'suspicious_downloads': 10
}
async def monitor_events(self):
"""セキュリティイベントの監視"""
while True:
events = await self.fetch_recent_events()
for event in events:
if self.is_suspicious(event):
await self.trigger_alert(event)
await asyncio.sleep(60) # 1分ごとにチェック
def is_suspicious(self, event):
"""疑わしいイベントの検出"""
suspicious_patterns = [
# 大量のリポジトリダウンロード
lambda e: e['action'] == 'repo.download' and
e['count'] > self.alert_threshold['suspicious_downloads'],
# 短時間での複数の認証失敗
lambda e: e['action'] == 'auth.failed' and
e['count'] > self.alert_threshold['failed_logins'],
# 異常なAPI使用
lambda e: e['action'] == 'api.call' and
e['rate'] > self.alert_threshold['api_rate_limit'],
# 不審な時間帯のアクセス
lambda e: e['hour'] < 6 or e['hour'] > 22,
# 不審な地域からのアクセス
lambda e: e['country'] not in ['JP', 'US', 'GB']
]
return any(pattern(event) for pattern in suspicious_patterns)
まとめ
本章では、GitHubでのセキュリティ実践について学習しました:
- Secrets管理で機密情報を安全に保管
- Environment設定で環境別のセキュリティ制御
- Dependabotで脆弱性を自動検出・修正
- Security Policyで脆弱性報告プロセスを確立
- インシデント対応プレイブックで迅速な対応
次章では、実践的なワークフロー設計について学習します。
確認事項
- Secretsを安全に管理している
- 環境別のアクセス制御を設定している
- Dependabotが有効で適切に設定されている
- Security Policyを作成・公開している
- インシデント対応プロセスが確立されている