SonarQube
概要
SonarQubeは、コードの品質とセキュリティを継続的に検査するためのオープンソースプラットフォームです。静的コード解析を通じて、バグ、脆弱性、コードスメルを検出し、技術的負債を可視化します。
主な機能
1. コード品質分析
- バグ検出: 実行時エラーにつながる可能性のあるコードの問題を特定
- コードスメル: 保守性を低下させるコードパターンを検出
- 重複コード: コードベース内の重複を識別
- 複雑度測定: サイクロマティック複雑度などのメトリクスを計算
2. セキュリティ分析
- 脆弱性検出: OWASP Top 10などのセキュリティ脆弱性を特定
- セキュリティホットスポット: レビューが必要なセキュリティ上の懸念箇所を指摘
- CWE/SANS Top 25対応: 一般的な脆弱性パターンに基づく分析
3. 技術的負債の可視化
- 負債の定量化: 問題を修正するために必要な時間を推定
- 品質ゲート: プロジェクトが品質基準を満たしているかを判定
- トレンド分析: 時間経過に伴うコード品質の変化を追跡
サポート言語
SonarQubeは多数のプログラミング言語をサポートしています:
- Java、C#、JavaScript、TypeScript
- Python、PHP、Ruby、Go
- C、C++、Objective-C、Swift
- Kotlin、Scala、HTML、CSS
- SQL、XML、YAML など
検出される問題の例(.NET)
SonarQubeが検出する典型的なコード品質とセキュリティの問題を、C#のコード例で紹介します。
1. Null参照の問題
問題のあるコード:
public class UserService
{
public string GetUserName(User user)
{
// S2259: user がnullの可能性があるのにチェックしていない
return user.Name.ToUpper();
}
}
修正後:
public class UserService
{
public string GetUserName(User user)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
return user.Name?.ToUpper() ?? string.Empty;
}
}
2. 例外処理の問題
問題のあるコード:
public class DataProcessor
{
public void ProcessData()
{
try
{
// 処理
}
catch (Exception ex)
{
// S2737: 空のcatchブロック - 例外が無視されている
}
}
public void AnotherMethod()
{
try
{
// 処理
}
catch (Exception ex)
{
// S1181: 一般的なExceptionをキャッチすべきでない
Console.WriteLine(ex.Message);
}
}
}
修正後:
public class DataProcessor
{
private readonly ILogger<DataProcessor> _logger;
public void ProcessData()
{
try
{
// 処理
}
catch (InvalidOperationException ex)
{
// 具体的な例外をキャッチし、適切に処理
_logger.LogError(ex, "データ処理中にエラーが発生しました");
throw; // 必要に応じて再スロー
}
}
}
3. リソースのリーク
問題のあるコード:
public class FileHandler
{
public string ReadFile(string path)
{
// S2930: using ステートメントを使用していない
var reader = new StreamReader(path);
return reader.ReadToEnd();
// reader が破棄されない
}
}
修正後:
public class FileHandler
{
public string ReadFile(string path)
{
using (var reader = new StreamReader(path))
{
return reader.ReadToEnd();
}
// または using 宣言を使用
// using var reader = new StreamReader(path);
// return reader.ReadToEnd();
}
}
4. セキュリティ脆弱性 - SQLインジェクション
問題のあるコード:
public class UserRepository
{
private readonly SqlConnection _connection;
public User GetUser(string username)
{
// S3649: SQLインジェクションの脆弱性
var query = $"SELECT * FROM Users WHERE Username = '{username}'";
var command = new SqlCommand(query, _connection);
// 攻撃者が username に "admin' OR '1'='1" を入力可能
return ExecuteQuery(command);
}
}
修正後:
public class UserRepository
{
private readonly SqlConnection _connection;
public User GetUser(string username)
{
var query = "SELECT * FROM Users WHERE Username = @username";
var command = new SqlCommand(query, _connection);
command.Parameters.AddWithValue("@username", username);
return ExecuteQuery(command);
}
}
5. セキュリティ脆弱性 - ハードコードされた認証情報
問題のあるコード:
public class DatabaseConnection
{
// S2068: ハードコードされたパスワード
private const string ConnectionString =
"Server=myserver;Database=mydb;User=admin;Password=P@ssw0rd123;";
public SqlConnection GetConnection()
{
return new SqlConnection(ConnectionString);
}
}
修正後:
public class DatabaseConnection
{
private readonly IConfiguration _configuration;
public DatabaseConnection(IConfiguration configuration)
{
_configuration = configuration;
}
public SqlConnection GetConnection()
{
var connectionString = _configuration.GetConnectionString("DefaultConnection");
return new SqlConnection(connectionString);
}
}
6. コードの複雑度が高い
問題のあるコード:
public class OrderValidator
{
// S1541: 認知的複雑度が高すぎる(ネストが深い)
public bool ValidateOrder(Order order)
{
if (order != null)
{
if (order.Items != null)
{
if (order.Items.Count > 0)
{
foreach (var item in order.Items)
{
if (item != null)
{
if (item.Quantity > 0)
{
if (item.Price > 0)
{
// 処理
}
else
{
return false;
}
}
else
{
return false;
}
}
}
}
}
}
return true;
}
}
修正後:
public class OrderValidator
{
public bool ValidateOrder(Order order)
{
if (order == null || order.Items == null || order.Items.Count == 0)
return false;
return order.Items.All(item => IsValidItem(item));
}
private bool IsValidItem(OrderItem item)
{
return item != null && item.Quantity > 0 && item.Price > 0;
}
}
7. デッドコード
問題のあるコード:
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
// S1144: 使用されていないprivateメソッド
private int Subtract(int a, int b)
{
return a - b;
}
public int Multiply(int a, int b)
{
int unused = 10; // S1481: 使用されていないローカル変数
return a * b;
}
}
修正後:
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public int Multiply(int a, int b)
{
return a * b;
}
// 不要なコードを削除
}
8. 非同期メソッドの誤用
問題のあるコード:
public class ApiClient
{
private readonly HttpClient _httpClient;
public string GetData(string url)
{
// S4462: async メソッドを同期的に待機している(デッドロックの危険性)
var response = _httpClient.GetAsync(url).Result;
return response.Content.ReadAsStringAsync().Result;
}
}
修正後:
public class ApiClient
{
private readonly HttpClient _httpClient;
public async Task<string> GetDataAsync(string url)
{
var response = await _httpClient.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
}
9. 重複コード
問題のあるコード:
public class ReportGenerator
{
public string GeneratePdfReport(Report report)
{
var header = $"Report: {report.Title}\n";
header += $"Date: {DateTime.Now:yyyy-MM-dd}\n";
header += $"Author: {report.Author}\n";
header += "---\n";
// PDF生成処理
return GeneratePdf(header, report.Content);
}
public string GenerateHtmlReport(Report report)
{
var header = $"Report: {report.Title}\n";
header += $"Date: {DateTime.Now:yyyy-MM-dd}\n";
header += $"Author: {report.Author}\n";
header += "---\n";
// HTML生成処理
return GenerateHtml(header, report.Content);
}
}
修正後:
public class ReportGenerator
{
private string BuildHeader(Report report)
{
return $"Report: {report.Title}\n" +
$"Date: {DateTime.Now:yyyy-MM-dd}\n" +
$"Author: {report.Author}\n" +
"---\n";
}
public string GeneratePdfReport(Report report)
{
var header = BuildHeader(report);
return GeneratePdf(header, report.Content);
}
public string GenerateHtmlReport(Report report)
{
var header = BuildHeader(report);
return GenerateHtml(header, report.Content);
}
}
10. 暗号化の弱い実装
問題のあるコード:
public class PasswordHasher
{
// S4790: 弱いハッシュアルゴリズム(MD5)を使用
public string HashPassword(string password)
{
using (var md5 = MD5.Create())
{
var bytes = Encoding.UTF8.GetBytes(password);
var hash = md5.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
}
修正後:
public class PasswordHasher
{
public string HashPassword(string password)
{
// bcrypt、PBKDF2、またはArgon2を使用
return BCrypt.Net.BCrypt.HashPassword(password);
}
public bool VerifyPassword(string password, string hash)
{
return BCrypt.Net.BCrypt.Verify(password, hash);
}
}
これらの例は、SonarQubeが検出する問題の一部です。各問題には固有のルールID(例: S2259、S3649)が付与され、詳細な説明と修正方法がSonarQubeのダッシュボードで確認できます。
アーキテクチャ
┌─────────────────┐
│ 開発者のIDE │
│ (SonarLint) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ CI/CDパイプ │
│ (SonarScanner) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ SonarQubeサーバ │
│ ・分析エンジン │
│ ・データベース │
│ ・Webダッシュ │
└─────────────────┘
主要コンポーネント
-
SonarQube Server
- Webサーバー(ダッシュボード)
- 検索サーバー(Elasticsearch)
- コンピュートエンジン(バックグラウンド処理)
-
SonarScanner
- コードスキャンを実行するクライアントツール
- CI/CDパイプラインに統合
-
SonarLint
- IDE統合プラグイン
- リアルタイムでコード品質をチェック
-
Database
- 分析結果とプロジェクト設定を保存
- PostgreSQL、Microsoft SQL Server、Oracleをサポート
セットアップ
Docker を使用したクイックスタート
# SonarQubeコンテナを起動
docker run -d --name sonarqube \
-p 9000:9000 \
sonarqube:lts-community
# ブラウザで http://localhost:9000 にアクセス
# 初期認証情報: admin / admin
プロジェクトスキャンの実行
.NET プロジェクトの場合
# SonarScanner for .NET をインストール
dotnet tool install --global dotnet-sonarscanner
# スキャン開始
dotnet sonarscanner begin \
/k:"project-key" \
/d:sonar.host.url="http://localhost:9000" \
/d:sonar.login="your-token"
# ビルド実行
dotnet build
# スキャン完了
dotnet sonarscanner end /d:sonar.login="your-token"
JavaScript/TypeScript プロジェクトの場合
# sonar-scannerをインストール
npm install -g sonar-scanner
# プロジェクトルートにsonar-project.propertiesを作成
# sonar.projectKey=my-project
# sonar.sources=src
# sonar.host.url=http://localhost:9000
# sonar.login=your-token
# スキャン実行
sonar-scanner
Java/Maven プロジェクトの場合
# pom.xmlにSonarQubeプラグインを追加済みの場合
mvn clean verify sonar:sonar \
-Dsonar.projectKey=my-project \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.login=your-token
CI/CD統合
GitHub Actions
name: SonarQube Analysis
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # 履歴全体を取得
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
Azure DevOps
# azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: SonarQubePrepare@5
inputs:
SonarQube: 'SonarQubeServiceConnection'
scannerMode: 'MSBuild'
projectKey: 'my-project'
- task: DotNetCoreCLI@2
inputs:
command: 'build'
- task: SonarQubeAnalyze@5
- task: SonarQubePublish@5
inputs:
pollingTimeoutSec: '300'
品質ゲート
品質ゲートは、プロジェクトがリリース可能かどうかを判定する基準です。
デフォルトの品質ゲート条件
- 新しいコードのカバレッジ >= 80%
- 重複コード < 3%
- セキュリティホットスポットのレビュー率 = 100%
- 新しいコードの信頼性評価 = A
- 新しいコードのセキュリティ評価 = A
- 新しいコードの保守性評価 = A
カスタム品質ゲートの作成
品質ゲート > 作成 > 条件を追加
- メトリクスを選択(例: バグ、脆弱性、コードスメル)
- 演算子を選択(例: より大きい、より小さい)
- しきい値を設定
ベストプラクティス
1. 継続的な統合
- すべてのプルリクエストでSonarQube分析を実行
- 品質ゲートが失敗した場合はマージをブロック
- 定期的にmainブランチをスキャン
2. 新しいコードに焦点を当てる
- "Clean as You Code"アプローチを採用
- 既存の問題よりも新しいコードの品質を優先
- レガシーコードは段階的に改善
3. セキュリティを最優先
- セキュリティホットスポットを定期的にレビュー
- 脆弱性は即座に修正
- セキュリティレポートを活用
4. チーム全体での活用
- SonarLintをIDEに統合し、リアルタイムフィードバックを活用
- コードレビュー時にSonarQubeの結果を参照
- 品質指標をチームで共有
5. カスタムルールの設定
- プロジェクト固有のコーディング規約を反映
- 不要なルールは無効化
- 重要なルールの重要度を調整
SonarQube vs SonarCloud
| 特徴 | SonarQube | SonarCloud |
|---|---|---|
| ホスティング | セルフホスト | クラウド(SaaS) |
| 料金 | Community版は無料 | パブリックリポジトリは無料 |
| セットアップ | サーバーインストール必要 | すぐに利用可能 |
| カスタマイズ | 完全な制御 | 制限あり |
| メンテナンス | 自己管理 | SonarSource管理 |
| 適用対象 | プライベートコード向け | OSS・クラウドネイティブ向け |
トラブルシューティング
よくある問題
Q: スキャンが失敗する
# ログを確認
docker logs sonarqube
# メモリ不足の場合
docker run -d --name sonarqube \
-e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \
-p 9000:9000 \
sonarqube:lts-community
Q: カバレッジが表示されない
- カバレッジツールの実行を確認(例: dotnet test --collect:"XPlat Code Coverage")
- sonar.cs.opencover.reportsPaths パラメータでレポートパスを指定
Q: 分析時間が長い
- 増分分析を有効化
- 不要なファイルを除外(sonar.exclusions設定)
- コンピュートエンジンのスレッド数を増やす
参考リンク
まとめ
SonarQubeは、コード品質とセキュリティを継続的に改善するための強力なツールです。CI/CDパイプラインに統合することで、新しいコードの品質を維持し、技術的負債を可視化できます。"Clean as You Code"の原則に従い、チーム全体でコード品質向上に取り組むことが重要です。