JenkinsでSBOM生成と脆弱性検知を自動化する : 1.Jenkins環境準備


本記事では、Jenkinsを使って、`ビルド > SBOM生成 > 脆弱性検知`を実行するパイプラインを構築するための環境を準備します。今回は自分のマシンにJenkinsサーバーを立てるのではなく、公式が用意している[コンテナイメージ](https://hub.docker.com/r/jenkins/jenkins)を活用して、Jenkins環境を構築していきます。

# 検証環境
- Ubuntu 22.04.3 LTS (WSL2)

| コンポーネント | バージョン | 使用用途 |
| --- | --- | --- |
| Docker | 24.0.5 | コンテナイメージのビルド, pull |
| Jenkins | 2.414.3 | CIツール |
| syft | v1.0.1 | SBOM生成ツール |
| grype | v0.74.7| 脆弱性検知ツール|
| sbomdiff | v0.5.3 | sbom差分検知ツール|
| jq | 1.6 | jsonファイル成形ツール |


# 環境構成
環境構成図について現在作成中です。


# Jenkinsサーバ用コンテナイメージをビルドする
Jenkinsを構築用のコンテナイメージをビルドするための、Dockerfileを作成します。公式がすでにJenkinsサーバーを構築している[コンテナイメージ](https://hub.docker.com/r/jenkins/jenkins)を配布しています。そちらを活用していきます。

以下のDockerfileを使用して、Jenkinsサーバー用コンテナイメージをビルドします。

```Dockerfile:Dockerfile
FROM jenkins/jenkins:2.414.3-jdk17

USER root
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
RUN apt-get update && apt-get install -y --no-install-recommends\
    wget \
    tree \
    jq \
    && rm -rf /var/lib/apt/list/* \
    && curl -kL https://bootstrap.pypa.io/get-pip.py | python \
    && pip install sbomdiff=0.53

ARG syft_version=1.0.1
ARG grype_version=0.74.7

RUN mkdir /tmp/syfttmp && \
    mkdir /tmp/grypetmp && \
    wget -P /tmp/syfttmp/  https://github.com/anchore/syft/releases/download/v${syft_version}/syft_${syft_version}_linux_amd64.tar.gz && \
    wget -P /tmp/grypetmp/  https://github.com/anchore/grype/releases/download/v${grype_version}/grype_${grype_version}_linux_amd64.tar.gz && \
    tar xvf /tmp/syfttmp/syft_${syft_version}_linux_amd64.tar.gz -C /tmp/syfttmp && \
    tar xvf /tmp/grypetmp/grype_${grype_version}_linux_amd64.tar.gz -C /tmp/grypetmp && \
    mv /tmp/syfttmp/syft /usr/local/bin/ && \
    mv /tmp/grypetmp/grype /usr/local/bin/

USER jenkins
```


Dockerfileのあるディレクトリへ移動し、そこでdocker buildを行います。
```
docker build -t jenkins-test .
```

ここで、上記のDockerfile作成は、Jenkinsサーバーコンテナにインストールされていないソフトウェアをインストールすることが目的です。



## コンテナを起動する
作成したコンテナイメージを使って、コンテナを起動させます。本記事ではコンテナの中でHostのDockerを利用するために、DooD(Docker outside of Docker)を実装しています。こちらの記事を参考にしました。
- [DooD(Docker outside of Docker)環境Jenkinsにおけるjenkins_homeと/var/run/docker.sockのパーミッション](https://qiita.com/hiroyuki_onodera/items/c6c6e6163d61ddf4dd5c)

```bash
docker run -d \
  --name jenkins-test \
  -u $(id -u):$(id -g) \
  --group-add $(awk -F: '$1 == "docker" {print $3}' /etc/group) \
  --restart=on-failure \
  -v $HOME/jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -p 8081:8080 \
  -p 50001:50000 \
  jenkins-test
```

ここで、docker run`コマンドの各引数は以下を表します：
- `-d`: デタッチドモードで実行し、バックグラウンドでコンテナを起動させる
- `--name jenkins-test`: コンテナにjenkins-testという名前を割り当てる
- `-u $(id -u):$(id -g)`: コンテナを現在のユーザーIDとグループIDで実行する
- `--group-add $(awk -F: '$1 == "docker" {print $3}' /etc/group)`: コンテナのプロセスにdockerグループを追加し、ホストのDockerデーモンと通信できるようにする
- `--restart=on-failure`: コンテナが失敗した場合にのみ再起動する
- `-v $HOME/jenkins_home:/var/jenkins_home`: ホストの`$HOME/jenkins_home`ディレクトリをコンテナ内の`/var/jenkins_home`ディレクトリにマウントする
- `-v /var/run/docker.sock:/var/run/docker.sock`: ホストのDockerソケットをコンテナ内にマウントし、コンテナ内からホストのDockerデーモンを制御できるようにする
- `-p 8081:8080`: ホストの8081ポートをコンテナの8080ポートにマッピングする
- `-p 50001:50000`: ホストの50001ポートをコンテナの50000ポートにマッピングする
- `jenkins-test`: 先ほどビルドしたコンテナイメージを指定する


```bash
docker logs -f 8ae5248661c61f49204ef3938f3761d3de9c04a02ea7dbcd4f3441f3f6551b55
```



### 初期パスワードを入手する
次にJenkinsの初期パスワードを入手します。
Jenkinsの初期パスワードは`/var/jenkins_home/secrets/initialAdminPassword`というファイルに記載されています。

この情報を取得するために、以下のコマンドを実行します。
```bash
docker exec ${CONTAINER_ID or CONTAINER_NAME} cat /var/jenkins_home/secrets/initialAdminPassword
```
出力結果がJenkinsにログインするための初期パスワードです。パスワードをコピーしておきます。

## Jenkinsにログインする
コンテナの起動とパスワードのチェックが終わったら、
http://localhost:8081/
へアクセスします。

すると、以下のような画面がでるので、先ほどコピーした初期パスワードを`[Administaratorpassword]`へ貼り付け、`[Continue]`をクリックします。
![Jenkins1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/1bd04ec1-eb19-8449-548b-293c0895aa98.png)

次に、`[ユーザー名]`と`[パスワード]`を変更することができます。ここで、このプロセスは`[]`をクリックすることでスキップすることが可能です。
![Jenkins2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/7dd340e4-cbf2-cd7d-7b3b-fef55a8418ef.png)

そして、JenkinsのURLを自由に決めることができます。今回は`http://localhost:8081/`のままにしておきます。`[Save and Finish]`をクリックすると、Jenkinsがスタートします。

![Jenkins3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/19ebb6c0-1b23-c012-ba84-dec9dc61c603.png)



## Testしてみる
ここでは、`Hello Jenkins`と出力するだけのとても簡単なジョブを作成してみます。
まず、Jenkinsのホームから、`[新規ジョブ作成]`をクリックします。

![Jenkins4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/cb2336e1-b4aa-d641-8b96-b25f628f2b76.png)

すると、新規ジョブの設定画面が現れます。`[ジョブ名]`を決め、`[フリースタイルプロジェクトのビルド]`を選択し、`[保存]`をクリックします。

![Jenkins5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/0f3e59a4-fec1-50a8-c7b9-8ad68ac1a988.png)

ジョブの設定が開始されます。今回は、シェルコマンドを実行するだけなので、`[Build Steps] > [ビルド手順の追加] > [シェルの実行]`を選択します。

![Jenkins6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/0ed9176c-53c8-c8ea-29a4-09c95f1755cc.png)

![Jenkins7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/5dd65bce-8285-c28f-0f4d-5a7f656ec874.png)

シェルを記述するためのブロックが作成されるので、ブロック内にシェルコマンドを記述します。今回書いたスクリプトは、
```bash
echo "Hello Jenkins"
```
です。そして、`[保存]`をクリックすれば、ジョブ`Hello_Test`が作成されます。ジョブ作成後、`[ビルド実行]`をクリックすると、ジョブが実行されます。その結果は`ビルド履歴`に記載されます。



![Jenkins8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/1ad1ecc0-e779-e209-cf5c-c20207d4c522.png)
![Jenkins9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/d422a917-24b1-7f69-12b2-a8f46741168d.png)

`#1`はジョブ`Hello_Test`におけるジョブの実行回数を表す番号です。`#1 > [コンソール出力]`をクリックするとジョブの実行結果を確認することができます。



![Jenkins10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/ed07d349-87ec-25d1-d37b-139d1d2aad82.png)
![Jenkins11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/8763b851-4aaa-858e-75d7-6f42a64f39f6.png)




## GitHubとJenkinsを連携する
Jenkinsのセットアップ後、GitHubとJenkinsを連携します。Githubのリポジトリ内のディレクトリに対し、fetchを行うためです。fetch後、Jenkinsを使い、ビルド、SBOM生成、脆弱性検知を連続的に行うことが目的です。

### Jenkins側の設定
`[ダッシュボード] > [開発者]`を選択し、自分が使っているユーザー名をクリックします。
![Jenkins-Github1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/60c11270-b20c-417b-236a-2f7bac7fa1df.png)


`[ダッシュボード] > [ユーザー名] > [設定]`へ進んでください。そして、`[APIトークン] > [現在のトークン]`に行きます。`トークン新規追加`をクリックし、トークン名を入力、`生成`をクリックします。すると以下のようにトークンが生成されるので、トークンのパスワードをコピーしておきます。最後に、保存を押せば設定完了です。
![Jenkins-Github2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/c8b82737-5307-5721-690a-2346e4b7640d.png)


### Github側の設定
まず、privateリポジトリを作成します。作成したリポジトリの`[Settings] > [Webhooks]`を選択します。
![Jenkins-Github3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/f6a5e245-1493-a234-aa3a-e6620e2b1891.png)

すると、Webhookの設定ができます。Webhookの設定では、`Payload URL`へ以下のURLを設定します。
```
http://[Jenkinsのユーザー名]:[JenkinsのAPIトークン]@[サーバのIPアドレス:ポート]/job/[Jenkinsのジョブ名]/buildWithParameters?token=[JenkinsのAPIトークンの名前]
```

ここで、サーバーのIPアドレスは次のコマンド
```bash
hostname -I
```
で検索してください。一番左にあるIPアドレスを設定に使えば大丈夫だと思います。ポート番号はコンテナを起動させるときに使用した。8081ポートを設定します。

それぞれ
- Jenkinsのユーザー名
- JenkinsのAPIトークン
- サーバのIPアドレス:ポート
を表しています。

サーバーのIPアドレスはhostname -Iで検索してください
一番左にあるIPアドレスを設定に使えば大丈夫だと思います。

例として、以下のような設定の場合は、
- Jenkinsのユーザー名 : admin
- JenkinsのAPIトークン : 1234567
- サーバのIPアドレス:ポート : 172.00.00.00:8081
- Jenkinsのジョブ名 : test_job
- JenkinsのAPIトークンの名前 : github
Payload URLは
```bash:example
http://admin:1234567@172.00.00.00:8081/job/test_job/buildWithParameters?token=github
```
となります。
![Jenkins-Github4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/54fb33ec-1c4f-4e34-ee21-e65ccb7b0c85.png)



## Jenkinsジョブの設定
`[ソースコード管理] > [Git]` へ行き、先ほど作成したリポジトリのURLとGithubのユーザー名とパスワードを設定します。Githubのユーザー名とトークンを認証情報として渡すために、`追加`をクリックし、`Jenkins`を選択します。

![Jenkins-Github5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/f204c736-b98b-ac6f-9550-e643cbeb9cbc.png)

その後、以下のような画面が開くので、Githubで使用している`ユーザー名`とGithubの認証のために作成した`トークン`を設定します。
![Jenkins-Github6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/16c58b2b-dce0-0ca4-e58e-25a764c675cf.png)

認証情報を追加後、選択できるようになるので、選択します。
![Jenkins-Github7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/e059a01b-97ab-9039-9033-6531016f6601.png)

最後にジョブの設定を保存しビルドを実行してみて、成功していれば、連携は成功です。

お疲れ様でした。初期設定は大変だったと思いますが、次回から、SBOM生成、脆弱性検知を行うパイプラインを実装していきます。

## 参考文献
### Jenkinsについて
- [Jenkins公式ドキュメント](https://www.jenkins.io/)
- [公式が用意しているコンテナイメージ](https://hub.docker.com/r/jenkins/jenkins)
- [DooD(Docker outside of Docker)環境Jenkinsにおけるjenkins_homeと/var/run/docker.sockのパーミッション](https://qiita.com/hiroyuki_onodera/items/c6c6e6163d61ddf4dd5c)


### GitHubとJenkinsの連携について
- [GitHub×JenkinsなCI環境の構築](https://zenn.dev/t45k/articles/3e196646b25d0e)
- [【Jenkins】GitHub連携①](https://qiita.com/mojapico/items/9c114f0890adaf3a0862)
- [【Jenkins】GitHub連携②](https://qiita.com/mojapico/items/ce475d827551b739ab3f)