Documentation

/

White Papers

開発速度と安定性の向上が証明されたQueryPieのDevSecOpsパイプライン

jake, ravi, noah

2024年11月22日

開発速度と安定性の向上が証明されたQueryPieのDevSecOpsパイプライン

はじめに

セキュリティを統合したQueryPieのパイプライン

QueryPieでは、開発スピードと安定性の両方を重視しています。私たちのチームは、変化に迅速に対応し、成長と機動性を追求しています。しかし、見過ごせない重要な要素がセキュリティです。初期段階では、開発速度を上げるとセキュリティレビューが後回しになったり、見落としを引き起こすことが多くありました。その結果、リリース後にセキュリティ脆弱性が発見され、緊急のパッチ適用や高額なコスト、ブランド信頼への潜在的なダメージが生じました。

DevSecOpsは、これらの問題に根本的に対処するために採用しているアプローチです。開発(Dev)と運用(Ops)のプロセスにセキュリティを統合することで、開発の初期段階から本番環境に至るまで、セキュリティが自動化され、標準化されていることを確保しています。QueryPieでは、セキュリティをプロセス全体にシームレスに統合したパイプラインを実装しています。

[QueryPieの開発ライフサイクル全段階におけるセキュリティレビュープロセス]

特権アクセス管理(PAM)ソリューションとして、QueryPieは、データベースアクセスコントローラー(DAC)、システムアクセスコントローラー(SAC)、およびKubernetesアクセスコントローラー(KAC)を通じて、重要な資産とデータを保護するために高いセキュリティレベルを提供します。徹底したセキュリティ管理を確保するために、QueryPieは開発ライフサイクル全体を通じて一貫したセキュリティレビューとチェックを実施しています。初期の要求定義フェーズから設計、実装、テスト、展開、運用に至るまで、各段階で多層的なセキュリティレビューを適用し、潜在的なセキュリティ脅威を事前に防止しています。これを実現するために、私たちはDevSecOpsアプローチを採用し、開発とセキュリティを密接に統合しています。自動化されたセキュリティテストと継続的な監視を通じて、脆弱性を迅速に特定し、対応することで、製品の安定性と信頼性を向上させています。私たちにとって、セキュリティは単なる機能ではなく、製品の核心的な要素です。

QueryPie CI/CDパイプライン
QueryPie CI/CDパイプライン

[DevSecOpsの必要性とクラウド環境でのセキュリティ課題]

従来のセキュリティアプローチでは、開発が完了した後にセキュリティレビューを行うことが一般的で、問題が発生した際に長期間の遅延や高額なコストがかかることが多くあります。例えば、コードが完成した後に発見されたセキュリティ脆弱性を修正するためには、すでに開発されたコードやシステムアーキテクチャを再分析して修正する必要があります。これは、初期段階で見つかった問題に対処するよりも遥かに複雑でコストがかかります。

しかし、DevSecOpsはセキュリティを開発プロセスの早い段階に統合することで、これらの問題を解決することができます。自動化されたセキュリティレビューや定期的で継続的なセキュリティチェックを通じて、QueryPie は開発効率を維持しながら、安全なクラウド環境を提供することができます。さらに、インフラストラクチャ管理が高度に自動化されているクラウド環境では、セキュリティ脆弱性が迅速に露呈するリスクがあります。したがって、QueryPieのDevSecOpsパイプラインは、クラウド環境特有のさまざまなセキュリティ課題に対応するために継続的に改善されており、安全な製品運用を確保しています。

[QueryPieのDevSecOpsパイプライン構築を通じた信頼性の確保]

QueryPieはDevSecOpsパイプラインを通じてセキュリティを自動化し、クラウド環境のセキュリティ課題を積極的に解決し、信頼できる PAM ソリューションとしての地位を確立しようとしています。本ホワイトペーパーでは、QueryPieの開発ライフサイクル全段階で行われるセキュリティ性の検討とDevSecOpsパイプラインの構築プロセスを通じて、セキュリティが製品の信頼性にどのように貢献しているかについて詳述します。

CI/CDパイプライン構築と自動化

QueryPieでは、CI/CDパイプラインはGithub Actionsを使用して構築されており、各ステージにセキュリティチェックポイントが配置され、セキュリティ検査が自動的に実施されます。各ステージで脆弱性評価が行われ、重大度が中以上の脆弱性が見つかった場合、その脆弱性が修正されるまで次のステージへの進行が制御されます。管理される脆弱性の種類は以下の画像に示されており、CI/CDパイプライン全体で様々な種類の脆弱性をチェック、識別、除去する管理プロセスが示されています。

CI / CD パイプラインで確認すべき脆弱性の種類
CI / CD パイプラインで確認すべき脆弱性の種類

[QueryPie DevSecOps段階]

  • STEP 0) デプロイのためのイメージを脆弱性のないクリーンなゴールデンイメージとして管理します。

  • STEP 1) SCA(Software Composition Analysis、ソフトウェア構成分析)、SAST(Static Application Security Testing、静的アプリケーションセキュリティテスト) チェックを通じてソースコードの脆弱性とオープンソース依存関係を点検します。

  • STEP 2) デプロイされるコンテナイメージの脆弱性をスキャンします。

  • STEP 3) DAST(Dynamic Application Security Testing、動的アプリケーションセキュリティテスト)と模擬ハッキングを並行してアプリケーションの脆弱性を点検します。

CI/CDパイプラインの脆弱性チェックツール
CI/CDパイプラインの脆弱性チェックツール
CI/CDパイプラインの脆弱性チェック状況
CI/CDパイプラインの脆弱性チェック状況

ゴールデンイメージ管理

QueryPieでは、お客様向けのデプロイ用イメージと内部テスト用イメージを分けて使用しています。

各イメージは、CIS ベンチマークレベル1およびCVE(Common Vulnerabilities and Exposures、共通脆弱性識別子)の脆弱性を除去したゴールデンイメージとして作成・管理され、他のイメージは使用を許可していません。

イメージの強化は、セキュリティチームが作成したスクリプトを使用して、CISベンチマークレベル1およびCVEの脆弱性を除去することで行われます。

次に、OSイメージハードニングのためのCISベンチマークのチェック項目は以下の通りです。

カテゴリ

詳細チェック項目

1. 初期設定

1.1 ファイルシステム

1.2 ソフトウェアとパッチ管理の設定

1.3 セキュアブート設定の設定

1.4 追加のプロセスハードニング設定

1.5 強制アクセス制御

1.6 コマンドライン警告バナーの設定

2. サービス

2.1 刻同期の設定

2.2 特別用途サービスの設定

2.3 サービスクライアントの設定

3. ネットワーク設定

3.1 ネットワークデバイスの設定

3.2 ネットワークカーネルモジュールの設定

3.3 ネットワークカーネルパラメータの設定

3.4 ホストベースファイアウォールの設定

4. アクセス、認証、認可

4.1 ジョブスケジューラの設定

4.2 SSHサーバーの設定

4.3 権限昇格の設定

4.4 プラグイン認証モジュールの設定

4.5 ユーザーアカウントと環境設定

5. ログと監査

5.1 ログの設定

5.2 システム会計 (auditd) の設定

5.3 整合性チェックの設定

6. システムメンテナンス

6.1 システムファイルの権限設定

6.2 ローカルユーザーおよびグループ設定

上記のチェック項目に基づき、以下のように修正スクリプトが作成され、自動的に設定が調整されます。

CISベンチマークレベル 1 - ハードニングプロセス
CISベンチマークレベル1 - ハードニングプロセス

コードセキュリティ検査および依存関係管理

開発ソースコードのセキュリティ脆弱性を特定するために使用するSASTツールは、ベンダーが提供する基本的な検出ルールだけでは、十分なカバレッジを提供しない場合がよくあります。以下は、脆弱性が含まれた公開サンプルコードを対象に、基本的な検出ルールを使用した脆弱性検出結果を示しています。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
package ai.qwiet.springbootkotlinwebgoat import org.springframework.http.HttpHeadersimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RequestParamimport org.springframework.web.bind.annotation.RestControllerimport org.springframework.http.HttpStatusimport org.springframework.http.ResponseEntityimport java.io.InputStreamReaderimport java.io.BufferedReaderimport java.io.Fileimport mu.KotlinLoggingimport org.apache.logging.log4j.LogManager @RestControllerclass HelloController {    val logger = KotlinLogging.logger {}    val secondaryLogger = LogManager.getLogger()     @GetMapping("/")    fun index(): String {        return "Greetings from Spring Boot!"    }     @GetMapping("/greet")    fun greet(@RequestParam("username") username: String): String {        logger.info { "Got request for `/greet`" }        // vulnerability: Sensitive Data Leak        secondaryLogger.debug("Params for `/greet `: $username")        // vulnerability: XSS        return "Greetings ${username}!"    }     fun parseParams(name: String, msg: String): Map {        val checkedName = name.takeUnless { it contains '\\' }?.ifBlank { "default_name" }        val checkedMsg = msg.ifBlank { "default_msg" }        return mapOf("parsed_name" to checkedName, "parsed_msg" to checkedMsg)    }     @GetMapping("/exec")    fun exec(@RequestParam("cmd") cmd: String): ResponseEntity {        logger.info { "Got request for `/exec`!" }        secondaryLogger.debug("Params for `/exec`: $cmd")         var out = "NOP"        if (cmd != "nop") {            // vulnerability: Remote Code Execution            val proc = Runtime.getRuntime().exec(cmd)            val lineReader = BufferedReader(InputStreamReader(proc.getInputStream()))            val output = StringBuilder()            lineReader.lines().forEach { line ->                output.append(line + "\n")            }            out = "Did execute command `$cmd`, got stdout: $output"        }        return ResponseEntity(out, HttpStatus.OK)    }     @GetMapping("/touch_file")    fun touchFile(@RequestParam("name") name: String, @RequestParam("msg") msg: String): ResponseEntity {        logger.info { "Got request for `/touch_file`!" }        secondaryLogger.debug("Params for `/touch_file`: $name | $msg")        if (name.length < 3) {            logger.warn { "The provided name is very short!" }        }         if (name == null || msg == null) {            return ResponseEntity("The `name` & `msg` parameters have to be set.", HttpStatus.OK)        } else {            val parsedParams = parseParams(name, msg)            val fullPath = "/tmp/http4kexample/" + parsedParams["parsed_name"]            val finalMsg = "MESSAGE: " + parsedParams["parsed_msg"]            // vulnerability: Directory Traversal            File(fullPath).writeText(finalMsg)            return ResponseEntity("Did write message `$finalMsg` to file at `$fullPath`", HttpStatus.OK)        }    }     @GetMapping("/debug")    fun debug(@RequestParam("url") url: String): ResponseEntity {        logger.info { "Got request for `/debug`!" }        secondaryLogger.debug("Params for `/debug`: $url")         val headers = HttpHeaders()        headers.add("Location", url)        // vulnerability: Open Redirect        return ResponseEntity(headers, HttpStatus.FOUND)    }     @GetMapping("/render_html")    fun renderHtml(@RequestParam("name") name: String): ResponseEntity {        logger.info { "Got request for `/render_html`!" }        secondaryLogger.debug("Params for `/render_html`: $name")         // vulnerability: XSS        val out = StringBuilder().append("<h1>Hello there, ").append("$name").append("!</h1>").toString()        return ResponseEntity(out, HttpStatus.OK)    }     @GetMapping("/add")    fun add(@RequestParam("x") x: String, @RequestParam("y") y: String): ResponseEntity {        logger.info { "Got request for `/add`!" }        secondaryLogger.debug("Params for `/add`: $x | $y")         val xi = x.toInt()        val xy = y.toInt()        val out = (xi + xy).toString()        return ResponseEntity(out, HttpStatus.OK)    }}