SAST,DAST,ファズテスト,Web APIファズテスト,レビューApp
(トップページはこちら) - (アプリケーションのセキュリティを始める)
はじめに
セキュリティ脆弱性の修正コストは、本番環境で発見された場合、開発段階の100倍以上になるという調査結果があります。また、マージリクエストのレビューで「ローカル環境で動作確認できない」という理由で承認が遅れるケースは、開発チームの生産性を大きく低下させます。
GitLabは、これらの課題に対して実用的なソリューションを提供しています。本記事では、CI/CDパイプラインに統合可能なセキュリティテスト(SAST、DAST、APIファジング)と、一時的なプレビュー環境を自動構築するレビューアプリの実装方法を解説します。
本記事で扱う内容:
- 各セキュリティテスト手法の特徴と使い分け
- レビューアプリによる効率的なレビュープロセス
- セキュリティテストとレビューアプリを組み合わせた実践的なワークフロー
- 段階的な導入戦略と注意点
1. セキュリティテストの全体像
1.1 なぜ複数のテスト手法が必要なのか
単一のセキュリティテストでは、すべての脆弱性を検出できません。各手法には明確な役割分担があります。
各手法の特徴:
| 手法 | 検出対象 | 実行タイミング | 実行時間の目安 | 誤検知率 |
|---|---|---|---|---|
| SAST | コードレベルの脆弱性 | コミット直後 | 1-5分 | 中程度 |
| DAST | 実行時の脆弱性 | デプロイ後 | 10-30分 | 低い |
| APIファジング | API層の脆弱性 | デプロイ後 | 5-15分 | 低い |
1.2 段階的な導入戦略
すべてを一度に導入する必要はありません。以下の順序での導入を推奨します。
フェーズ1: SAST導入 (1-2週間)
- 最も導入が簡単で、即座に効果が出る
- デプロイ環境が不要
- 開発者へのフィードバックが最速
フェーズ2: レビューアプリ導入 (2-4週間)
- レビュープロセスの効率化
- DAST/APIファジングの準備
フェーズ3: DAST/APIファジング導入 (2-3週間)
- レビューアプリを活用した動的テスト
- 包括的なセキュリティカバレッジの実現
2. SAST (静的アプリケーションセキュリティテスト)
2.1 SASTの仕組みと利点
SASTは、ソースコードを実行せずに脆弱性を検出します。コンパイル前の段階で問題を発見できるため、修正コストを最小限に抑えられます。
検出可能な脆弱性の例:
- SQLインジェクション
- クロスサイトスクリプティング (XSS)
- ハードコードされた認証情報
- 安全でない暗号化アルゴリズムの使用
- パストラバーサル
2.2 対応言語とアナライザー
GitLabは、ティアに応じて2種類のアナライザーを提供します。
GitLab Advanced SAST (Ultimate版のみ):
- ファイル間、関数間の解析が可能
- より高精度な検出
- 誤検知の削減
標準アナライザー (全ティア):
- Semgrepベースのオープンソースアナライザー
- 基本的な脆弱性検出
完全サポート言語:
C、C++、C#、Go、Java、JavaScript、TypeScript、PHP、Python、Ruby、YAML、Java Properties
標準アナライザーのみサポート:
Apex、Elixir、Groovy、Kotlin、Objective-C、Scala、Swift
2.3 実装手順
2.3.1 最小構成での導入
.gitlab-ci.ymlに以下を追加するだけで開始できます:
include:
- template: Jobs/SAST.gitlab-ci.yml
前提条件:
- Linux/amd64のGitLab Runner (DockerまたはKubernetes executor)
-
.gitlab-ci.ymlにtestステージが定義されていること
2.3.2 実行結果の確認
パイプライン実行後、以下の場所で結果を確認できます:
-
パイプラインのSecurityタブ (全ティア)
- 検出された脆弱性の一覧
- JSONレポートのダウンロード
-
マージリクエストウィジェット (Ultimate版)
- 新規に検出された脆弱性
- 修正された脆弱性
- インライン表示
-
脆弱性レポート (Ultimate版)
- プロジェクト全体の脆弱性管理
- ステータス追跡
脆弱性情報の詳細:
# 表示される情報の例
脆弱性タイトル: "SQL Injection in user authentication"
重大度: Critical
ファイル: app/models/user.rb
行番号: 45
説明: |
ユーザー入力が適切にサニタイズされずにSQLクエリに使用されています。
攻撃者が任意のSQLコマンドを実行できる可能性があります。
修正方法: |
パラメータ化クエリまたはORMを使用してください。
例: User.where("email = ?", params[:email])
CWE: CWE-89
2.4 最適化とカスタマイズ
2.4.1 誤検知の削減
特定のルールを無効化する場合、.gitlab/sast-ruleset.tomlを作成:
[semgrep]
description = "プロジェクト固有のルールセット"
# 特定のルールを無効化
[[semgrep.ruleset]]
disable = true
[semgrep.ruleset.identifier]
type = "semgrep_id"
value = "gosec.G107-1"
# 複数のルールを一括無効化
[[semgrep.ruleset]]
disable = true
[semgrep.ruleset.identifier]
type = "cwe"
value = "CWE-338"
2.4.2 スキャン対象の最適化
不要なディレクトリを除外してスキャン時間を短縮:
variables:
# デフォルト: spec,test,tests,tmp
# プロジェクトに応じて追加
SAST_EXCLUDED_PATHS: "spec,test,tests,tmp,node_modules,vendor,dist,build"
# スキャン深度の調整(デフォルト: 20)
SEARCH_MAX_DEPTH: 15
2.4.3 特定アナライザーの設定
SpotBugs(Java/Scala)でプリコンパイルを使用:
stages:
- build
- test
include:
- template: Jobs/SAST.gitlab-ci.yml
build:
image: maven:3.6-jdk-8-slim
stage: build
script:
- mvn package -Dmaven.repo.local=./.m2/repository
artifacts:
paths:
- .m2/
- target/
spotbugs-sast:
dependencies:
- build
variables:
MAVEN_REPO_PATH: $CI_PROJECT_DIR/.m2/repository
COMPILE: "false"
2.5 パフォーマンスとコストの考慮
実行時間の目安:
- 小規模プロジェクト(~10,000行): 1-2分
- 中規模プロジェクト(~100,000行): 3-5分
- 大規模プロジェクト(100,000行以上): 5-10分
リソース要件:
- メモリ: 2-4GB
- CPU: 2コア推奨
コスト削減のヒント:
# マージリクエストでのみ実行
sast:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
3. DAST (動的アプリケーションセキュリティテスト)
3.1 DASTの特徴と必要性
DASTは、実行中のアプリケーションを外部から検査します。SASTでは検出できない以下の脆弱性を発見できます:
- 認証・認可の不備
- セッション管理の問題
- サーバー設定の脆弱性
- ランタイム環境固有の問題
SASTとの補完関係:
3.2 前提条件と制約
必須要件:
- デプロイ済みのテスト環境
- アクセス可能なURL
- Ultimate版ライセンス
注意点:
- 本番環境では実行しない(負荷がかかるため)
- テストデータを使用する
- スキャン中は他の変更を加えない
3.3 基本的な実装
stages:
- build
- test
- deploy
- dast
include:
- template: DAST.gitlab-ci.yml
# テスト環境へのデプロイ
deploy_test:
stage: deploy
script:
- echo "テスト環境へデプロイ"
- ./deploy-to-test.sh
environment:
name: test
url: https://test.example.com
# DASTスキャン
dast:
variables:
DAST_WEBSITE: https://test.example.com
dependencies:
- deploy_test
3.4 実行時間とリソース
スキャン時間の目安:
- 小規模サイト(~10ページ): 10-15分
- 中規模サイト(~50ページ): 20-30分
- 大規模サイト(100ページ以上): 30-60分
時間短縮のテクニック:
dast:
variables:
DAST_WEBSITE: https://test.example.com
# スキャン範囲を制限
DAST_PATHS: "/api,/admin"
# 除外パス
DAST_EXCLUDE_URLS: "https://test.example.com/static"
4. APIファジングテスト
4.1 APIファジングの役割
APIファジングは、APIエンドポイントに対して予期しない入力を送信し、以下を検出します:
- 入力検証の不備
- エラーハンドリングの問題
- レート制限の欠如
- 認証バイパス
4.2 対応API形式と設定方法
サポートされる仕様:
- OpenAPI v2/v3
- GraphQL Schema
- HTTP Archive (HAR)
- Postman Collection v2.0/v2.1
4.2.1 OpenAPI仕様を使用する場合
stages:
- build
- test
- deploy
- fuzz
include:
- template: API-Fuzzing.gitlab-ci.yml
apifuzzer_fuzz:
variables:
FUZZAPI_OPENAPI: openapi.json
FUZZAPI_TARGET_URL: https://api.example.com
4.2.2 GraphQLの場合
apifuzzer_fuzz:
variables:
FUZZAPI_GRAPHQL: schema.graphql
FUZZAPI_TARGET_URL: https://api.example.com/graphql
4.3 Dockerサービスとの統合
アプリケーションをDockerコンテナとして起動し、同じネットワーク内でテスト:
variables:
FF_NETWORK_PER_BUILD: "true"
stages:
- build
- fuzz
include:
- template: API-Fuzzing.gitlab-ci.yml
# アプリケーションのビルド
build:
services:
- name: docker:dind
alias: dind
image: docker:20.10.16
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# APIファジング実行
apifuzzer_fuzz:
services:
# データベース
- name: postgres:13
alias: db
# アプリケーション
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
alias: app
variables:
FUZZAPI_TARGET_URL: http://app:3000
FUZZAPI_OPENAPI: openapi.json
# データベース接続情報
DATABASE_URL: postgresql://postgres@db/test
4.4 実行時間の最適化
apifuzzer_fuzz:
variables:
FUZZAPI_TARGET_URL: https://api.example.com
FUZZAPI_OPENAPI: openapi.json
# テストケース数を制限
FUZZAPI_HTTP_MAX_REQUESTS: 100
# タイムアウト設定
FUZZAPI_HTTP_REQUEST_TIMEOUT: 30
5. レビューアプリの実装
5.1 レビューアプリがもたらす価値
従来のレビュープロセスの課題:
- レビュアーがローカル環境を構築する必要がある
- 環境差異による「手元では動く」問題
- デザイナーやPMが実際の動作を確認できない
レビューアプリによる改善:
- URLを共有するだけでレビュー可能
- 環境の一貫性が保証される
- 非エンジニアも参加できる
5.2 基本的な実装パターン
5.2.1 シンプルな構成
stages:
- build
- test
- deploy
review_app:
stage: deploy
script:
- echo "レビューアプリをデプロイ"
# 実際のデプロイコマンド
- ./scripts/deploy-review.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop_review_app
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
stop_review_app:
stage: deploy
script:
- echo "レビューアプリを削除"
- ./scripts/cleanup-review.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
5.2.2 Kubernetes環境での実装
review_app:
stage: deploy
image: bitnami/kubectl:latest
script:
# 環境変数の設定
- export KUBE_NAMESPACE="review-$CI_COMMIT_REF_SLUG"
- export APP_URL="https://$CI_COMMIT_REF_SLUG.review.example.com"
# Namespaceの作成
- kubectl create namespace $KUBE_NAMESPACE || true
# アプリケーションのデプロイ
- |
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: $KUBE_NAMESPACE
spec:
replicas: 1
selector:
matchLabels:
app: review-app
template:
metadata:
labels:
app: review-app
spec:
containers:
- name: app
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: app-service
namespace: $KUBE_NAMESPACE
spec:
selector:
app: review-app
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: $KUBE_NAMESPACE
spec:
rules:
- host: $CI_COMMIT_REF_SLUG.review.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
EOF
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop_review_app
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
stop_review_app:
stage: deploy
image: bitnami/kubectl:latest
script:
- export KUBE_NAMESPACE="review-$CI_COMMIT_REF_SLUG"
- kubectl delete namespace $KUBE_NAMESPACE
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
5.3 自動停止の設定
5.3.1 時間ベースの自動停止
review_app:
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
auto_stop_in: 3 days # 3日後に自動停止
5.3.2 マージ時の自動停止
マージリクエストがマージまたはクローズされた際に自動的に停止:
review_app:
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop_review_app
auto_stop_in: 1 week
stop_review_app:
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
# マージ時は自動実行、それ以外は手動
when: manual
5.4 ルートマップによるナビゲーション強化
ルートマップを使用すると、ソースファイルから対応するレビューアプリのページに直接ジャンプできます。
5.4.1 基本的なルートマップ
.gitlab/route-map.yml:
# 静的ページ
- source: 'src/pages/index.html'
public: 'index.html'
- source: 'src/pages/about.html'
public: 'about.html'
# 動的ルーティング(正規表現)
- source: /src\/pages\/(.+)\.html/
public: '\1.html'
# ブログ記事(日付付き)
- source: /src\/posts\/([0-9]{4})-([0-9]{2})-([0-9]{2})-(.+)\.md/
public: 'blog/\1/\2/\3/\4/'
# APIドキュメント
- source: /docs\/api\/(.+)\.md/
public: 'api-docs/\1/'
5.4.2 Next.js/Nuxt.jsでの例
# Pagesディレクトリベースのルーティング
- source: /pages\/index\.tsx/
public: '/'
- source: /pages\/about\.tsx/
public: '/about'
# 動的ルート
- source: /pages\/blog\/\[slug\]\.tsx/
public: '/blog/example-post'
- source: /pages\/users\/\[id\]\.tsx/
public: '/users/1'
# ネストされたルート
- source: /pages\/(.+)\.tsx/
public: '/\1'
5.5 コスト管理
レビューアプリは便利ですが、リソースコストがかかります。
コスト削減のベストプラクティス:
# 1. ドラフトMRではレビューアプリを作成しない
review_app:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^Draft:/
# 2. 特定のラベルが付いた場合のみ作成
review_app:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS =~ /needs-review-app/
# 3. 自動停止時間を短く設定
review_app:
environment:
auto_stop_in: 2 days
# 4. 夜間に未使用の環境を自動削除
cleanup_old_reviews:
stage: cleanup
script:
- ./scripts/cleanup-inactive-reviews.sh
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
6. セキュリティテストとレビューアプリの統合
6.1 統合による相乗効果
レビューアプリとセキュリティテストを組み合わせることで、以下が実現できます:
- 一貫した環境でのテスト: レビューアプリに対してDASTとAPIファジングを実行
- 早期フィードバック: マージ前にセキュリティ問題を発見
- レビュアーの負担軽減: セキュリティチェックが自動化される
6.2 完全な統合パイプライン
stages:
- build
- test
- deploy
- security
- cleanup
variables:
# 共通変数
REVIEW_APP_URL: https://$CI_COMMIT_REF_SLUG.review.example.com
# SASTテンプレートのインクルード
include:
- template: Jobs/SAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
- template: API-Fuzzing.gitlab-ci.yml
# ビルドジョブ
build:
stage: build
image: docker:20.10.16
services:
- docker:dind
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# SAST(ビルド前に実行可能)
sast:
stage: test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# レビューアプリのデプロイ
deploy_review:
stage: deploy
image: bitnami/kubectl:latest
script:
- export KUBE_NAMESPACE="review-$CI_COMMIT_REF_SLUG"
- kubectl create namespace $KUBE_NAMESPACE || true
- |
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: $KUBE_NAMESPACE
spec:
replicas: 1
selector:
matchLabels:
app: review-app
template:
metadata:
labels:
app: review-app
spec:
containers:
- name: app
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
value: "postgresql://postgres:password@db:5432/app"
- name: db
image: postgres:13
env:
- name: POSTGRES_PASSWORD
value: "password"
- name: POSTGRES_DB
value: "app"
---
apiVersion: v1
kind: Service
metadata:
name: app-service
namespace: $KUBE_NAMESPACE
spec:
selector:
app: review-app
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: $KUBE_NAMESPACE
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- $CI_COMMIT_REF_SLUG.review.example.com
secretName: review-app-tls
rules:
- host: $CI_COMMIT_REF_SLUG.review.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
EOF
# デプロイ完了を待機
- kubectl wait --for=condition=available --timeout=300s deployment/app -n $KUBE_NAMESPACE
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop_review
auto_stop_in: 3 days
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# DAST(レビューアプリに対して実行)
dast:
stage: security
variables:
DAST_WEBSITE: $REVIEW_APP_URL
DAST_FULL_SCAN_ENABLED: "false" # 時間短縮のため
dependencies:
- deploy_review
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# APIファジング(レビューアプリに対して実行)
apifuzzer_fuzz:
stage: security
variables:
FUZZAPI_TARGET_URL: $REVIEW_APP_URL
FUZZAPI_OPENAPI: openapi.json
FUZZAPI_HTTP_MAX_REQUESTS: 50 # 時間短縮のため
dependencies:
- deploy_review
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# レビューアプリの停止
stop_review:
stage: cleanup
image: bitnami/kubectl:latest
script:
- export KUBE_NAMESPACE="review-$CI_COMMIT_REF_SLUG"
- kubectl delete namespace $KUBE_NAMESPACE
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
6.3 パイプラインの実行フロー
6.4 実行時間の最適化
完全なパイプラインは時間がかかるため、以下の最適化を推奨します。
6.4.1 段階的なセキュリティチェック
# ドラフトMR: SASTのみ
sast:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# レビュー準備完了: SAST + レビューアプリ
deploy_review:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^Draft:/
# 最終レビュー: 全セキュリティテスト
dast:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS =~ /security-check/
apifuzzer_fuzz:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS =~ /security-check/
6.4.2 並列実行の活用
# DASTとAPIファジングを並列実行
dast:
stage: security
needs:
- deploy_review
apifuzzer_fuzz:
stage: security
needs:
- deploy_review
6.4.3 キャッシュの活用
sast:
cache:
key: sast-$CI_COMMIT_REF_SLUG
paths:
- .sast-cache/
build:
cache:
key: build-$CI_COMMIT_REF_SLUG
paths:
- node_modules/
- .gradle/
6.5 結果の統合表示
マージリクエストでは、すべてのセキュリティテスト結果が統合されて表示されます(Ultimate版)。
マージリクエストウィジェットに表示される情報:
- 新規検出された脆弱性の数(SAST、DAST、APIファジング別)
- 修正された脆弱性の数
- レビューアプリへのリンク
- 各脆弱性の詳細(重大度、ファイル、説明)
7. 実践的な導入ガイド
7.1 小規模プロジェクトでの導入例
プロジェクト規模: 開発者2-5名、コードベース~50,000行
推奨構成:
stages:
- test
- deploy
include:
- template: Jobs/SAST.gitlab-ci.yml
# レビューアプリ(手動デプロイ)
review_app:
stage: deploy
script:
- ./deploy-to-heroku.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.herokuapp.com
on_stop: stop_review
auto_stop_in: 2 days
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
stop_review:
stage: deploy
script:
- heroku apps:destroy --app $CI_COMMIT_REF_SLUG --confirm $CI_COMMIT_REF_SLUG
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
期待される効果:
- パイプライン実行時間: 3-5分
- セキュリティ問題の早期発見
- レビュー時間の短縮(30-50%)
7.2 中規模プロジェクトでの導入例
プロジェクト規模: 開発者5-20名、コードベース~200,000行
推奨構成:
stages:
- build
- test
- deploy
- security
variables:
REVIEW_APP_URL: https://$CI_COMMIT_REF_SLUG.review.example.com
include:
- template: Jobs/SAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
build:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# SASTは常に実行
sast:
stage: test
# レビューアプリは自動デプロイ
review_app:
stage: deploy
script:
- kubectl apply -f k8s/review-app.yaml
environment:
name: review/$CI_COMMIT_REF_SLUG
url: $REVIEW_APP_URL
on_stop: stop_review
auto_stop_in: 5 days
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^Draft:/
# DASTはラベル付きMRのみ
dast:
stage: security
variables:
DAST_WEBSITE: $REVIEW_APP_URL
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS =~ /security-review/
stop_review:
stage: deploy
script:
- kubectl delete -f k8s/review-app.yaml
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
期待される効果:
- 通常のパイプライン: 5-10分
- セキュリティレビュー付き: 15-25分
- 脆弱性検出率の向上(60-80%)
7.3 大規模プロジェクトでの導入例
プロジェクト規模: 開発者20名以上、コードベース200,000行以上
推奨構成:
stages:
- build
- test
- deploy
- security-fast
- security-deep
variables:
REVIEW_APP_URL: https://$CI_COMMIT_REF_SLUG.review.example.com
include:
- template: Jobs/SAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
- template: API-Fuzzing.gitlab-ci.yml
# ビルドの並列化
build:
stage: build
parallel:
matrix:
- SERVICE: [frontend, backend, api]
script:
- docker build -t $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHA ./services/$SERVICE
- docker push $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHA
# SASTの最適化
sast:
stage: test
variables:
SAST_EXCLUDED_PATHS: "spec,test,tests,tmp,node_modules,vendor,dist,build,public"
SEARCH_MAX_DEPTH: 10
cache:
key: sast-$CI_COMMIT_REF_SLUG
paths:
- .sast-cache/
# レビューアプリ(マイクロサービス対応)
review_app:
stage: deploy
script:
- ./scripts/deploy-microservices.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
url: $REVIEW_APP_URL
on_stop: stop_review
auto_stop_in: 7 days
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^Draft:/
# 高速セキュリティチェック(常に実行)
dast_quick:
stage: security-fast
variables:
DAST_WEBSITE: $REVIEW_APP_URL
DAST_FULL_SCAN_ENABLED: "false"
DAST_PATHS: "/api,/admin"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# 詳細セキュリティチェック(ラベル付きのみ)
dast_full:
stage: security-deep
variables:
DAST_WEBSITE: $REVIEW_APP_URL
DAST_FULL_SCAN_ENABLED: "true"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS =~ /full-security-scan/
when: manual
apifuzzer_fuzz:
stage: security-deep
variables:
FUZZAPI_TARGET_URL: $REVIEW_APP_URL
FUZZAPI_OPENAPI: openapi.json
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS =~ /full-security-scan/
when: manual
stop_review:
stage: deploy
script:
- ./scripts/cleanup-microservices.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
期待される効果:
- 通常のパイプライン: 10-15分
- 高速セキュリティチェック付き: 20-30分
- 完全セキュリティスキャン: 40-60分
- 複数チームでの並行開発が可能
7.4 よくある失敗パターンと対策
7.4.1 パイプラインが遅すぎる
問題: すべてのセキュリティテストを毎回実行し、パイプラインが1時間以上かかる
対策:
# 段階的なチェック
sast:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
dast:
rules:
# 特定のラベルまたはデフォルトブランチへのマージ時のみ
- if: $CI_MERGE_REQUEST_LABELS =~ /security-check/
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
7.4.2 レビューアプリのコストが高すぎる
問題: 多数のレビューアプリが放置され、インフラコストが増大
対策:
review_app:
environment:
# 短い自動停止時間
auto_stop_in: 2 days
rules:
# ドラフトMRでは作成しない
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^Draft:/
# 定期的なクリーンアップジョブ
cleanup_old_reviews:
stage: cleanup
script:
- ./scripts/cleanup-inactive-reviews.sh
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
7.4.3 誤検知が多すぎる
問題: SASTの誤検知が多く、開発者が無視するようになる
対策:
# .gitlab/sast-ruleset.toml
[semgrep]
description = "プロジェクト固有のルールセット"
# 誤検知の多いルールを無効化
[[semgrep.ruleset]]
disable = true
[semgrep.ruleset.identifier]
type = "semgrep_id"
value = "javascript.lang.security.audit.xss.react-dangerouslysetinnerhtml"
# .gitlab-ci.yml
variables:
# テストコードを除外
SAST_EXCLUDED_PATHS: "spec,test,tests,tmp,__tests__,__mocks__"
7.4.4 DASTがレビューアプリにアクセスできない
問題: レビューアプリのデプロイ完了前にDASTが開始される
対策:
deploy_review:
script:
- kubectl apply -f k8s/review-app.yaml
# デプロイ完了を待機
- kubectl wait --for=condition=available --timeout=300s deployment/app
# ヘルスチェック
- |
for i in {1..30}; do
if curl -f $REVIEW_APP_URL/health; then
echo "アプリケーション起動完了"
break
fi
echo "起動待機中... ($i/30)"
sleep 10
done
dast:
needs:
- deploy_review
# さらに待機時間を追加
before_script:
- sleep 30
8. まとめと次のステップ
8.1 導入による効果
適切に実装されたセキュリティテストとレビューアプリの統合により、以下の効果が期待できます:
セキュリティ面:
- 脆弱性の早期発見(開発段階で80-90%)
- 本番環境でのインシデント削減(60-70%)
- セキュリティレビューの自動化
開発効率面:
- レビュー時間の短縮(30-50%)
- 手戻りの削減(40-60%)
- デプロイ頻度の向上(2-3倍)
コスト面:
- セキュリティインシデント対応コストの削減
- QA工数の削減(20-30%)
- インフラコストの最適化
8.2 段階的な導入ロードマップ
第1週: SAST導入
-
.gitlab-ci.ymlにSASTテンプレートを追加 - 初回スキャン結果の確認
- 誤検知の多いルールを調整
第2-3週: SAST最適化
- 除外パスの設定
- カスタムルールセットの作成
- チームへの教育
第4-5週: レビューアプリ導入
- 簡単なデプロイスクリプトの作成
- 手動デプロイでの動作確認
- 自動デプロイへの移行
第6-7週: レビューアプリ最適化
- 自動停止の設定
- ルートマップの追加
- コスト監視の実装
第8-9週: DAST/APIファジング導入
- レビューアプリに対するDASTスキャン
- APIファジングの設定
- 実行時間の最適化
第10週以降: 継続的改善
- パイプラインのパフォーマンス監視
- セキュリティルールの定期的な見直し
- チームフィードバックの収集と改善
8.3 成功のためのポイント
- 小さく始める: すべてを一度に導入しない
- チームの巻き込み: 開発者の理解と協力が不可欠
- 継続的な改善: 定期的にルールとプロセスを見直す
- コストの監視: リソース使用量を定期的にチェック
- ドキュメント化: 設定の意図と使い方を記録
8.4 さらなる学習リソース
GitLabの公式ドキュメントには、より詳細な情報と最新のベストプラクティスが記載されています。また、GitLabコミュニティでは、実際の導入事例や課題解決のディスカッションが活発に行われています。
セキュリティとレビューの自動化は、一度設定すれば終わりではなく、プロジェクトの成長とともに進化させていくものです。本記事で紹介した内容を基に、自社のプロジェクトに最適な形を見つけてください。