はじめに
Dependabot(正確にはDependabot security updates)はリポジトリで使用している外部のライブラリやパッケージのアップデートを検出して、外部パッケージの管理ファイル(*1)に対する修正リクエスト(*2)を自動で作成してくれるツールです。
DependabotはGithubに買収されています ので、開発元はGithubとなります。
そのため、GithubではDependabotがサッと使えるようになっているのですが、同じことをself-managedなGitlabでもやってみたいと思います。
Dependabotのことをより知りたい人は Github Docs - Code security - Dependabot を使用してサプライ チェーンを安全に保つ を読んでみましょう。
(*1) Pythonで言うrequirements.txtや、Goで言うgo.modなどです
(*2) 言わずもがな、Gitlabではマージリクエストのことです
環境の前提
Gitlabの環境についてはお手元のものをお使いください。
一から構築する場合は GitlabとKanikoによるコンテナの自動ビルド&配布環境の構築 でも紹介しています。
UIや操作面で若干違いがあるかもしれませんが、参考にしていただければ幸いです。
以降は以下の設定が終わっている前提で進行します。
- GitlabおよびGitlab runner(Docker)が設定済み
- 空っぽのプロジェクトが作成済み
- 今回は
qiita/dependabot
を作りました
- 今回は
- ユーザーはGitlabに対してgit pull/pushするためのgit設定が完了している
- Githubのアカウントを持っている(dependabotで使用するトークン取得のため)
サンプルプロジェクトの作成
まずは、Dependabotでパッケージ更新をチェックするためのサンプルコードを作成します。
空のプロジェクトを取得してスタートです。
git clone http://qiita-gitlab.example.com/qiita/dependabot.git
cd dependabot
今回はPythonでやってみましょう。
Dependabotに関する設定などは後で行うので、ここでは以下の2ファイルだけを作成します。
http://localhost:8080
にGETリクエストを送るだけの簡単なプログラムです。
2023/10/13 時点ではrequestsの最新版は2.31.0です。この後の手順では、dependabotの設定をすることで、requestsパッケージのアップデートをするマージリクエストが作成されることを確認していきます。
requirements.txt
requests==2.29.0
sample.py
import requests
def get(url):
resp = requests.get(url)
print(f"GET {url} returned {resp.status_code}")
if __name__ == '__main__':
get("http://localhost:8000")
もちろん通信相手がいなければ例外がドバーっと出力されるわけですが、本題では無いので気にしないでください。
あとは、これらのコードをリポジトリに格納します。
git switch --create main
git add requirements.txt
git add sample.py
git commit -m "Add sample code"
git push --set-upstream origin main
Dependabotの導入にあたって
DependabotはGithubが開発していますが dependabot-coreはOSSとして公開 してくれています。
これを使って Gitlabでdependabotを動かすプロジェクト がありますので、これを参考に導入していきます。
特に参考になるのは同プロジェクトの standalone の部分の説明ですね。
Application can be used in a stateless cli like mode. This mode is most useful to run dependency updates from Gitlab CI. dependabot-gitlab/dependabot-standalone describes the best way to run dependency updates from gitlab ci. Like docker compose, all configuration is done via environment variables.
というわけで https://gitlab.com/dependabot-gitlab/dependabot-standalone をに見に行くと、サンプルとなる .gitlab-ci.yml と README.md が置かれています。
必要なトークンは以下の2つです。
-
SETTINGS__GITLAB_ACCESS_TOKEN
- gitlab personal access token withapi
access scope and at leastDeveloper
role -
SETTINGS__GITHUB_ACCESS_TOKEN
- github personal access token with repository read scope
更に、スケジューラ登録時に必要な変数は以下の3つです。
-
PROJECT_PATH
- project path, likedependabot-gitlab/dependabot
-
PACKAGE_MANAGER_SET
- comma separated package eco-systems, likebundler,docker
(dependency file must be in the same directory for multiple package managers to work within same schedule job) -
DIRECTORY
- update directory, usually/
if not a larger monorepo
そこで、サンプルプロジェクトに手を入れる前に、まずは必要なトークンを取得します。
Githubトークンの取得
GithubアカウントのSettings左下の "Developer settings" を選択します。
Personao access tokensからclassicのトークンを選択します。
アクセスが必要なのは public_repo
だけです。Expirationは各自の用途に合わせて設定してください。
トークンが生成出来たらすぐ後で使うので保存しておきます。
Gitlabトークンの取得
Gitlabに対しては、Dependabotがプロジェクトにマージリクエストを作成するための権限が必要です。
今回はプロジェクトアクセストークンを発行して、プロジェクト固有で使えるようにします。
必要な権限は api
スコープに対して Developer
ロールが必要です。
Gitlabではトークンの有効期限は最長1年となっていますが、指定しないとデフォルトで1年になります。
GitlabプロジェクトのCI/CD変数設定
取得したトークンをプロジェクトのCI/CD変数に設定しておきます。
変数名は SETTINGS__GITLAB_ACCESS_TOKEN
と SETTINGS__GITHUB_ACCESS_TOKEN
が予約名なので、その通りにします。
-
Protect variable
については、以降の手順で動作確認用のブランチを作成するため、一時的にOFFにしておきます -
Mask variable
はトークンがログに出力されないようにONにしておきましょう -
Expand variable reference
はデフォルトのONで良いです
Dependabotのコード追加
Pythonだけなので https://gitlab.com/dependabot-gitlab/dependabot-standalone/-/blob/main/.gitlab-ci.yml の中からPythonに関係する部分だけを抜き出して、以下のような .gitlab-ci.yml を作成します。
今回は例示のために必要部分だけを抜き出していますが、通常は上記のリポジトリからファイルをそのまま取得して、includeするのが簡単で良いでしょう。
この記事では 3.5.0-alpha.1
を使用していますが、最新のリリース状況については https://gitlab.com/dependabot-gitlab/dependabot/-/releases を参照してください。
default:
image: python:3.11.6-slim
tags:
- docker
variables:
DEPENDABOT_GITLAB_IMAGE: docker.io/andrcuns/dependabot-gitlab
DEPENDABOT_GITLAB_VERSION: 3.5.0-alpha.1
.dependabot-gitlab:
image:
name: ${DEPENDABOT_GITLAB_IMAGE}-${CI_JOB_NAME}:${DEPENDABOT_GITLAB_VERSION}
entrypoint: [""]
variables:
GIT_STRATEGY: none
RAILS_ENV: production
SECRET_KEY_BASE: key
PACKAGE_MANAGER: $CI_JOB_NAME
SETTINGS__GITLAB_URL: $CI_SERVER_URL
SETTINGS__STANDALONE: "true"
SETTINGS__LOG_COLOR: "true"
script:
- cd /home/dependabot/app
- bundle exec rake "dependabot:update[${PROJECT_PATH?},${PACKAGE_MANAGER?},${DIRECTORY?}]"
pip:
extends: .dependabot-gitlab
rules:
- if: $DEPENDENCY_UPDATES_DISABLED
when: never
- if: '$CI_PIPELINE_SOURCE == "schedule" && $PACKAGE_MANAGER_SET =~ /\bpip\b/'
更に、dependabotには https://gitlab.com/dependabot-gitlab/dependabot-standalone のREADMEに書かれているように .gitlab/dependabot.yml
を作成する必要があります。
dependabot.ymlの形式はGithub公式の dependabot.yml ファイルの構成オプション に従って書くことになります。
上記の文書には package-ecosystem
/ directory
/ schedule.interval
が必須となっていますが、スケジューラはGitlab側で設定することになるため schedule.interval
は参照されません。
従って、Gitlabにおける最小限構成は以下のようになります。
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
それでは、新しくブランチを作成して、今作ったファイルを追加したらpushします。
git switch --create dependabot
git add .gitlab-ci.yml
git add .gitlab/dependabot.yml
git commit -m "Add dependabot"
git push --set-upstream origin dependabot
Dependabotの試験動作
それでは、Gitlabプロジェクトでスケジューラを設定してDependabotを動かしてみましょう。
設定値は以下の通り。
- Description: なんでも良いです。とりあえずdependabotと書きました。
- Interval Pattern: 状況に合わせて設定してください。今回はデフォルトのEvery day。
- Cron timezone: 状況に合わせて設定してください。とりあえず日本にいるのでTokyo。
- Select target branch or tag: 試験動作なので、先ほどpushした
dependabot
ブランチを選択。 - Variables
-
PROJECT_PATH
: 今回はqiita/dependabot
になります -
PACKAGE_MANAGER_SET
: 今回はpip
になります -
DIRECTORY
: 今回は/
になります- モノレポなどで、プロジェクトファイルが下の階層にある場合は、構成ファイルが置かれているディレクトリを指定します。
-
SETTINGS__CONFIG_BRANCH
: 今だけdependabot
を設定します。これは.gitlab/dependabot.yml
を取得するブランチを指しており、mainブランチにマージするまでは設定しておきます。
-
- Activated: 試験動作が終わるまではOFFにしておきます。
それでは、作成したスケジューラを実行してみましょう。
Pipelineが成功したら、Merge requestsが1件増えたようです。
古いパッケージが検出されて、マージリクエストが作成されました!
マージリクエストにはRelease NoteやVulnerabilities fixedが書かれており、いちいち調べに行かなくてもサマリーがまとまっています。
このVulnerabilities fixedについては、情報源がGitHub Advisory Databaseなので CVE-2023-32681 のページに書かれている内容と同じ文章が使われているはずです。
Dependabotが作成するマージリクエストの文章粒度はパッケージの作成者によるため、必ずこれだけの情報量があるわけではありません。
とはいえ、大抵リポジトリにタグが打ってあるので、コミット一覧くらいは最低限取得できると思ってよいでしょう。
そして、requestsパッケージを2.31.0にアップデートする変更がちゃんと加えられています。
変更に問題がなければ、このマージリクエストはそのままマージしてしまいましょう。
CIコードのマージと後始末
最後に、動作確認が出来てmainブランチ(Protectedなブランチ)にマージしたら、試験動作用の設定を削除して、定期実行を有効にします。
- CI/CD -> Schedules -> dependabot
- Select target branch or tag を
main
に変更(※各自のブランチ戦略に合わせてください) - Variables
SETTINGS__CONFIG_BRANCH
を削除 - Activatedにチェックを入れて、定期実行を有効化
- Select target branch or tag を
- Settings -> CI/CD -> Variables
-
SETTINGS__GITHUB_ACCESS_TOKEN
のFlagsでProtect variable
を有効化 -
SETTINGS__GITLAB_ACCESS_TOKEN
のFlagsでProtect variable
を有効化 - これで、Protectedなmainブランチ以外ではdependabot用のトークンが参照できなくなります。
-
設定変更後に再度手動でdependabotのパイプラインを実行し、念のため完走することを確認して完了です。
パッケージの更新が無ければ重複したマージリクエストは作られませんので、何度実行しても大丈夫です。
おしまい
最初は勢いでソフトウェアを作ってみたものの、ふと目を離すと知らない間に依存ライブラリがアップデートされている...というのは良くある話ですよね。
自分の書いたコードには更新が無いけど、依存ライブラリの更新が無いか定期的にチェックできると、とても便利です。
脆弱性が見つかったパッケージにすぐ対応できるので、セキュリティリスクを減らすことが出来るでしょう。
それだけでなく、Dependabotの作るプルリクエスト(マージリクエスト)には、Release NoteやChangelogが書かれているのがとても親切です。
あると便利なのになと思っていた機能がある日追加されているのに気付いたり、知らない機能に対応したRelease Noteを見て新しい知識を取り入れたり、自身のアップデートにも活用できます。
是非色々なプロジェクトに導入してみてください。