Hello World|Labs
Published on

n8nを使ったアプリーケーションリスク評価フローの作成

本記事ではアプリケーションで使用されているOSSのライブラリやパッケージ、モジュールなどのリスク評価を行い、評価結果を通知するワークフローを作成します。

フロー概要

作成するワークフローは以下のようになります。 まず、アプリケーションで使用されているOSSのライブラリやパッケージ、モジュールの使用状況を監視し、脆弱性などのリスク評価を行います。それらの評価結果をチャットに送信して通知します。

システム構成

今回のシステム構成は下図のようになります。アプリケーションのコンポーネント構成をDependency-Trackに登録しリスク評価を行います。n8nが日次で評価結果を取得しQuickChartでグラフ化し画像としてローカルディスクに保存します。そして、Mattermostにグラフ画像を添付して評価結果を送信します。評価結果に添付するグラフ画像はCaddyから配信します。

ResourceUsageHosting TypeLicensing Model
Caddyグラフ画像を配信するSelf Hosting(Docker container on Hetzner Cloud)Free
Open Source
Mattermost評価結果の通知先Self Hosting(Docker container on Hetzner Cloud)Freemium
Open Source
n8nワークフローを実行するSelf Hosting(Docker container on Hetzner Cloud)Freemium
Open Source
Dependency-Trackアプリケーションのリスク評価を行うSelf Hosting(Docker container on Hetzner Cloud)Free
Open Source
QuickChartグラフ画像を生成するSelf Hosting(Docker container on Hetzner Cloud)Freemium
Open Source

フローの作成

コンポーネントの登録&リスク評価

今回はサンプルとてLaravel+Vue.jsで構築されたアプリケーションを使用します。 最初にDependency-Trackにアプリケーション用のプロジェクトを作成します。 プロジェクトは下図のように親プロジェクト(オペレーティングシステム)の下にPHPとJavaScriptの子プロジェクト(アプリケーション)を設ける階層構造にします。

プロジェクトを作成したら各プロジェクトに使用しているコンポーネントを登録します。 Dependency-Trackへのコンポーネントの登録はCycloneDXフォーマットのSBOMをインポートして行います。

SBOMの作成にはCycloneDX Generator (cdxgen)を使用します。 ツールをインストールしたら下記のコマンドを実行してDependency-TrackにSBOMを送信します。

# Retrieve DEB packages, APT sources, RPM packages, ...
cdxgen -t os --server-url "Dependency-Track APIサーバのURL" --api-key "Dependency-Trackで発行したAPIキー" --project-id "Dependency-Trackで作成した親プロジェクトのID"
 
# Parse composer.lock
cdxgen -t php "プロジェクトのルートディレクトリ" --server-url "Dependency-Track APIサーバのURL" --api-key "Dependency-Trackで発行したAPIキー" --project-id "Dependency-Trackで作成した子プロジェクトのID"
 
# Parse package-lock.json
cdxgen -t javascript "プロジェクトのルートディレクトリ" --server-url "Dependency-Track APIサーバのURL" --api-key "Dependency-Trackで発行したAPIキー" --project-id "Dependency-Trackで作成した子プロジェクトのID"

コンポーネントの登録が完了するとリスク評価が行われ下図のように一覧が表示されます。

リスク評価結果の通知

プロジェクトの設定が完了しましたのでリスク評価結果を通知するワークフローを作成していきます。

ワークフローは1日1回(毎日午前0時)にスケジュール起動するようにします。 まずはワークフローをスケジュール起動するためのScheduleノード(ノードパネルからOn a scheduleを選択)とWorkflowノード(ノードパネルからWhen called by another workflowを選択)を追加します。

Scheduleノードは設定パネルで以下のように設定します。

NameValue
Trigger IntervalDays
Days Between Triggers1
Trigger at HourMidnight
Trigger at Minute0

次にDependency-Trackに登録したプロジェクトのリスク評価結果のサマリーをREST API経由で取得するためにHTTP Requestノードを追加します。

ノードを追加して設定パネルで以下のように設定します。

NameValue
MethodGET
URLhttp://(Dependency-Track APIサーバーのサービス名):8080/api/v1/project
AuthenticationGeneric Credential Type
Generic Auth TypeHeader Auth
Credential for Basic AuthNameにX-Api-Key、ValueにDependency-Trackで発行したAPIキーを設定
Send Query ParametersOFF
Send HeadersOFF
Send BodyOFF

APIを実行すると以下のような形でリスク評価結果のサマリーを取得することができます。

[
  {
    "name": "demo-app-php",
    [...]
    "metrics": {
      "critical": 1,
      "high": 6,
      "medium": 0,
      "low": 0,
      "unassigned": 0,
      "vulnerabilities": 7,
      "vulnerableComponents": 3,
      "components": 97,
      "suppressed": 0,
      "findingsTotal": 7,
      "findingsAudited": 0,
      "findingsUnaudited": 7,
      "inheritedRiskScore": 40,
      "policyViolationsFail": 0,
      "policyViolationsWarn": 0,
      "policyViolationsInfo": 130,
      "policyViolationsTotal": 130,
      [...]
    }
  }
]

QuickChartでグラフ作成するためにCodeノードで取得したデータを加工します(ノードパネルのData transformationから選択)。

ノードを追加して設定パネルのコード入力欄に以下のJavaScriptを入力します。

const base = {
  version: "2",
  backgroundColor: "transparent",
  width: 500,
  height: 300,
  devicePixelRatio: 1.0,
  format: "png",
  chart: {
    type: "bar",
    data: {
      labels: [],
      datasets: []
    },
    options: {
      title: {
        display: true,
        position: "top",
        text: ""
      }
    }
  }
}
const vulnerabilities = JSON.parse(JSON.stringify(base))
const violations = JSON.parse(JSON.stringify(base))
 
vulnerabilities.chart.data.labels = ["critical", "high", "medium", "low"]
vulnerabilities.chart.options.title.text = "vulnerabilities"
violations.chart.data.labels = ["fail", "warn", "info"]
violations.chart.options.title.text = "violations"
 
for (const project of $input.all()) {
  vulnerabilities.chart.data.datasets.push({
    label: project.json.name,
    data: [
      project.json.metrics.critical,
      project.json.metrics.high,
      project.json.metrics.medium,
      project.json.metrics.low
    ],
  })
  violations.chart.data.datasets.push({
    label: project.json.name,
    data: [
      project.json.metrics.policyViolationsFail,
      project.json.metrics.policyViolationsWarn,
      project.json.metrics.policyViolationsInfo,
    ]
  })
}
 
return [vulnerabilities, violations]

上記の処理で脆弱性およびポリシー違反の2つのグラフを作成するためのデータセットが準備できましたので、次にグラフを作成していきます。 まずLoop Over Itemsノード(ノードパネルのFlowから選択)を使って繰り返しフローを作成し、loopブランチにHTTP Requestノードを追加してREST API経由でQuickChartにグラフ画像の生成を要求します。

ノードを追加して設定パネルで以下のように設定します。

NameValue
MethodPOST
URLhttp://(QuickChartのサービス名):3400/chart
AuthenticationNone
Send Query ParametersOFF
Send HeadersON
Header Parameters (Name1)Content-Type
Header Parameters (Value1)application/json
Header Parameters (Name2)Accept
Header Parameters (Value2)image/png
Send BodyON
Body Content TypeJSON
Specify BodyUsing JSON
JSON{{ $json.toJsonString() }}

Note

QuickChartはGETメソッドでもグラフ画像を作成できるため送信メッセージにクエリパラメータ付きのURLを埋め込んでグラフを表示させることも可能です。ただし、その場合外部から誰でも画像生成リクエストを送信できるようになってしまうため悪用される危険性があります。 そのため、ここではQuickChartへのアクセスは同じDockerネットワークにあるサービスからのみに制限し、生成した画像はローカルディスクに保存してCaddyを介して静的ファイルとして配信するようにしています。

グラフ画像を生成したらそのバイナリデータをもとにCryptoノード(ノードパネルのData transformationから選択)を使用してハッシュ値を計算します。ハッシュ値はグラフ画像を保存する際のファイル名に使用します。

ノードを追加して設定パネルで以下のように設定します。

NameValue
ActionHash
TypeSHA256
Binary FileON
Binary Property NameData
Property Namehash
EncodingHEX

次にグラフ画像のバイナリデータをファイル書き込みノード(Read/Write Files from Disk)を使ってローカルディスクにファイルを出力します(ファイル書き込みノードに渡すinputデータにバイナリデータを追加する必要があるため先にCodeノードで加工を行います)。

Codeノードの設定パネルでコード入力欄に以下のJavaScriptを入力します。

items[0].binary = $('グラフ画像生成ノードの名前').first().binary
return items

ファイル書き込みノードの設定パネルで以下のように設定します。

NameValue
OperationWrite File to Disk
File Path and Name/(出力先ディレクトリ)/{{ $('ハッシュ値生成ノードの名前').item.json.hash }}.png
Input Binary Fielddata

Note

ホストマシンのディレクトリをバインドマウントでn8nのコンテナ内に出力先ディレクトリとしてマウントし、Caddyのコンテナでもそのホストマシンのディレクトリをマウントして静的ファイルとして配信します。 Caddyの静的ファイル配信はfile_serverディレクティブを使用します。

ファイルの出力が完了したら通知するリスク評価結果のメッセージを作成します。

ファイル書き込みノードの後にCodeノードを追加して設定パネルのコード入力欄に以下のJavaScriptを入力し、画像ファイルを参照するためのURLを組み立てます。

return {
  filepath: 'http://(Caddyの静的ファイル配信用アドレス)/' + $('ハッシュ値作成ノードの名前').first().json.hash + '.png'
}

LoopノードのdoneブランチにCodeノードを追加して設定パネルのコード入力欄に以下のJavaScriptを入力し、Markdown形式のメッセージを作成します。

let message = ''
 
for (const f of $input.all()) {
  message += `![chart](${f.json.filepath})\n`
}
 
for (const p of $('プロジェクト取得ノードの名前').all()) {
  message += `
**${p.json.name}**
 
|vulnerability|count|
|:---|---:|
|critical|${p.json.metrics.critical}|
|high|${p.json.metrics.high}|
|medium|${p.json.metrics.medium}|
|low|${p.json.metrics.low}|
 
|policy violation|count|
|:---|---:|
|fail|${p.json.metrics.policyViolationsFail}|
|warn|${p.json.metrics.policyViolationsWarn}|
|info|${p.json.metrics.policyViolationsInfo}|
 
`
}
 
return {
  message
}

最後にMattermostノード(ノードパネルのAction in an appから選択)を追加してメッセージを送信します。

ノードを追加して設定パネルで以下のように設定します。

NameValue
Credential to connect withMattermostで発行したアクセストークンとMattermostのURLを設定
ResourceMessage
OperationPost
Channel Name or ID投稿したいチャネルのIDを設定
Message下記のメッセージを入力
### Security Report

{{ $json.message }}

動作確認

ワークフローの全体像は以下のようになります。

ワークフローが実行されると以下のようなメッセージが通知されます。