JFrog Japan Blog

DevOpsを加速する、JFrog日本法人メンバーによるブログです。

JFrog ArtifactoryやXrayといった自社ツールはもちろん、CI/CDやコンテナ技術(DockerやKubernetes)などDevOpsの一般的な内容も扱います。その他、日本でのDevOps事例紹介やお楽しみコンテンツも掲載予定です。 JFrogならではの面白くて役に立つブログを目指しますので、お楽しみに!

【Google Cloud Build & JFrog Artifactory + Xray + CLI】Dockerイメージを自動でビルド・保存・セキュリティスキャンする

こんにちは、デベロッパーアドボケイトのよこなです。もう9月ですね。な〜つのお〜わ〜り〜

ソースコードだけじゃなくてアーティファクトを専用リポジトリで保存しましょう!
という話をよくするのですが、最近はもはやおまじないであっという間に「ソースコード→デプロイ」の過程を終えられる便利ツールが増えていますよね。「アーティファクトを取っておくと言っても、コマンド叩いたらサーバーまで行っちゃうんだけど…」と思われる方もいらっしゃるかもしれません。

この記事ではGCPとJFrog Platformを組み合わせた例を使って、アーティファクトの保管・セキュリティスキャンを行います。

☝やりたいこと

f:id:ihcomega:20210904124728p:plain

  1. Dockerで動くアプリケーションをビルドする
  2. アーティファクトをJFrog Artifactoryに保存する
  3. Artifactory上のアーティファクトにセキュリティスキャンをかけ、問題ない場合のみビルドを正常終了させる

この流れをCloud Build (CI/CDツール) 上で実現します。デプロイするところまで書こうかと思ったのですが、上記だけで超大作になったため別記事にします。
記事が長いので身構えるかもしれませんが、動かしたいアプリもない、クラウド環境もない、JFrogサーバーもないというゼロからの状態で始めて丁寧に説明しているからなのでご安心を。実際は既存で使っているものをカスタマイズすることになると思うのでもうちょっと気楽にお試し頂けるかと思います。
例えば既にCloud Buildでコンテナイメージのデプロイをしている場合はちょっとステップを追加するだけで脆弱性のスキャンができるようになります!必要に応じ読み飛ばしながらご活用ください🐸

☝Frogに関する前提知識

  • JFrog Artifactory: アーティファクト(今回はDockerイメージ)を保存するためのリポジトリ。
  • JFrog Xray: いわゆるSCAツール。Artifactoryと連携し、アーティファクトが依存するOSSに脆弱性やライセンス違反がないかをスキャンする。

☝コンテナ化されたアプリケーションを用意する

今回デプロイするアプリケーションはこちらです。JavaとSpring bootというフレームワークで作りました。アプリの作り方は詳細割愛しますが、Springの公式ドキュメントを参考にしました。
github.com

実行・アクセスすると Hello Docker World! と返せるだけのシンプル・サンプルアプリケーションです。
f:id:ihcomega:20210903165717p:plain

☝JFrog Artifactory上にDockerイメージ用のリポジトリを作成する

JFrog SaaS版の利用を開始する

jfrog.com

ここから登録してJFrog Platformが使える状態にしてください。
Name Your Environment*欄はお好きな値を指定していただけばOKです(既存の別のサーバーと重複はNG)。それがURLになり、後々Cloud Build側の設定にも使います。

リポジトリを作成する

アカウント作成して初回ログイン(これ以降、admin権限があるユーザーを使ってください)をしたら、そのままリポジトリも用意しちゃいましょう。

  • リモートリポジトリ
  • ローカルリポジトリ
  • バーチャルリポジトリ

の3つを作成します。以前は別々に作成する必要があったのですが、最近まとめて3つ作れるようになったので結構楽ちんです。

f:id:ihcomega:20210903130807p:plain
最初のログイン後表示される画面で「Docker」を選択する

f:id:ihcomega:20210903131037p:plainf:id:ihcomega:20210903131104p:plain
プロジェクト名を入れてリポジトリを作成する

今回は雑(サンプルとしては分かりやすいとも言う)ですが、springという名前を指定しました。spring-docker-local, spring-docker-remote, spring-dockerという3つのリポジトリが爆誕します。

リポジトリをXrayでスキャンできるよう設定する

スキャンには時間がかかるので、予めインデックスを追加しておくことでいざスキャンの命令が来たときに素早く結果を返せるようにしておきます。

f:id:ihcomega:20210903154145p:plain

リポジトリの設定画面からローカル・リモートリポジトリに対してEnable Indexing in Xrayを有効にする設定を入れてあげればOKです。ローカルとリモートを束ねる存在であるバーチャルリポジトリには設定不要です(そもそも設定項目がありません)。

f:id:ihcomega:20210903154554p:plainf:id:ihcomega:20210903154557p:plain
ローカルもリモートも似た画面で設定する

☝Cloud Buildでビルドできるようにする

Google Cloud上にプロジェクトを作成(手順)済みの状態で始めましょう。今回の例では「My First Project」を作りました。

ビルドを定義する設定ファイルcloudbuild.yamlを作成する

さて、ビルドの準備を進めていきますが、基本的には公式ドキュメントが詳しいし分かりやすいです。
cloud.google.com

サンプルアプリケーションのプロジェクトルートにcloudbuild.yaml (Cloud Build上で自動で実行したいことを書き連ねていく設定ファイル)を追加します。完成形はGitHub上のファイルをご確認ください。
では、まずはDockerイメージをビルドしてタグをうつところまで。

steps:
  - name: 'gcr.io/cloud-builders/docker'
    entrypoint: 'bash'
    args:
    - '-c'
    - |-
      docker build --build-arg JAR_FILE==build/libs/*.jar -t gcr.io/$PROJECT_ID/spring-boot-docker .
      docker tag gcr.io/$PROJECT_ID/spring-boot-docker:latest ${_JFROG_PLATFORM_URL}/spring-docker/spring-boot-docker:latest

公式ドキュメントと異なるのはentrypointを指定してbash -cでコマンドをまるごと渡せるようにしているところです。これで、ひとつのステップで2行コマンドを実行できます(参考: bash スクリプトの実行)。
コマンド内の$PROJECT_IDはプロジェクトIDに置換される変数で、Cloud Buildが標準で提供しているものです。一方、${_JFROG_PLATFORM_URL}は独自で設定する環境変数です。値の設定方法は後ほど見せますが、ここではArtifactoryのURLをCI実行時に埋め込むようにしています。

ここまで出来たらgcloudコマンドが使えるようCloud SDKをインストール(手順)して、プロジェクトルートで次のコマンドを実行すればCloud Buildを動かすことができます。

gcloud builds submit

結果はそのままターミナルで確認できますが、Cloud Buildのコンソールにて「履歴」を見ることも可能です。

f:id:ihcomega:20210903054042p:plain
コンソール上で確認できるビルド履歴

ビルドのトリガーを設定し、自動でビルドが実行されるようにする

先に設定ファイルを書ききってもいいですが、いったん何らかのイベントをきっかけにCloud Build上で自動ビルドが走るように設定しちゃいましょう。ローカルでコマンドを打たなくて良いので動作確認も楽になります。

今回はspring-boot-dockerリポジトリのmainブランチに変更が入ったらビルドするという設定にします。Cloud Buildのコンソールで「トリガー」ページを開きます。

f:id:ihcomega:20210903054345p:plain

先に最終形をお見せするとこんな感じです。

f:id:ihcomega:20210903123638p:plain

GitHubと初めて連携する場合、リポジトリを指定するところでGitHubとの行き来が発生します。認証を行い、使うリポジトリにGoogle Cloud Buildのツールを入れましょう。

f:id:ihcomega:20210903130158p:plain

また、変数をセットするのもポイントですね。今回は次の2つを設定しておいてください(参考: 変換値の置換)。

変数名
_JFROG_PLATFORM_URL JFrog Platform登録時に指定したサーバー名(xxx.jfrog.ioxxx部分)
_JFROG_PLATFORM_USER JFrog Platformのログインユーザー(メアド)

f:id:ihcomega:20210903134041p:plain

☝Cloud BuildからArtifactoryへDockerイメージを保存する

Cloud Build上でさっきビルドしたイメージをArtifactoryへ保存するための設定をしていきます。

JFrog PlatformのAPI KEYをCloud Key Management Serviceで暗号化する

まずはJFrog Platformで認証するためのAPI KEYを発行し、クリップボードにコピーしてローカルで使えるようにします。

f:id:ihcomega:20210903133112p:plainf:id:ihcomega:20210903133122p:plain
右上のメニューからプロフィールページへ行くと発行できる

続いて、今取得したAPI KEYを暗号化します。そうすることで、cloudbuild.ymlに(暗号化した)値を直接書いて安全に使うことができるようになります。

公式ドキュメントを参考にしつつ、暗号化するための鍵を作る2ステップを実施します。

gcloud kms keyrings create [KEYRING-NAME] --location=global
gcloud kms keys create [KEY-NAME] --location=global --keyring=[KEYRING-NAME] --purpose=encryption

[KEYRING-NAME](Keyring = 簡単に言うと鍵のグループ)と[KEY-NAME](Key = 鍵)は自分で決めます。サンプルではそれぞれspring-docker-keyring, key-exampleとしました。これらの値はこの後cloudbuild.yamlでも使います。

これで鍵が作成されたので、それを使って暗号化します。さっきクリップボードにコピーしたAPI_KEYをローカルの環境変数$RT_API_KEYに設定した上で、次のコマンドを実行します。3つ目のコマンドを実行すると返ってくる値こそが暗号化したAPI KEYなので、控えておいてください(まぁ紛失してもまたコマンドを実行すればOKです)。

echo $RT_API_KEY | gcloud kms encrypt --plaintext-file=- --ciphertext-file=- --location=global --keyring=[KEYRING-NAME] --key=[KEY-NAME] | base64

暗号化した値を復号できるよう設定する

暗号化した鍵はビルド実行時に復号したいので、Cloud Buildに復号の許可を与えます。Cloud Buildのコンソールより「設定」画面で変更してください。
f:id:ihcomega:20210903140608p:plain

おそらくこのときに「APIを有効にしてね」という別画面が立ち上がると思うので、画面に従って設定しましょう。

f:id:ihcomega:20210903145443p:plainf:id:ihcomega:20210903145451p:plain
KMS APIを有効にする

設定ファイルで暗号化したAPI KEYを使えるよう追記する

ようやく準備が整ったので、cloudbuild.yamlに追記します。まずはファイルの末尾に暗号化したAPI KEYを追記しましょう。[]で覆われた値は上の手順にある鍵発行・暗号化時に得たものに差し替えてください。

secrets:
  - kmsKeyName: projects/dark-hall-324609/locations/global/keyRings/[KEYRING-NAME]/cryptoKeys/[KEY-NAME]
    secretEnv:
      APIKEY: '[暗号化されたAPI KEY]'

つまり私のサンプルバージョンだとこんな感じになります。

secrets:
  - kmsKeyName: projects/dark-hall-324609/locations/global/keyRings/spring-docker-keyring/cryptoKeys/key-example
    secretEnv:
      APIKEY: 'CiQAy/L6lVzsfbJqvTbOInV71GDrfUnHFPMbclD7a45xkGgsXlkScwAbTXpQ7gUCXwLhVYLC9BaGcGx7KmaSjuIaErsNQ7CacG/Jetll1xpDc/ITCAr+WrCZqYSktGWyvmE4zSBct9cMWPdVgPnoc1mmOst9iiD1S8G9QeCu8xY9XFYJq/T5K+Cu5lchGtAfWRNB8c1QA58/Qk4='

JFrog CLIを使ってDockerイメージをArtifactoryへ保存する

続いて、ArtifactoryへにDockerイメージを保存するためのステップを追加します。やっとこの記事の肝にたどり着きました…。

ググるとIntegrating Google Cloud Build with JFrog Artifactory | Google Cloud Blogっていう公式の記事もあるんですが、

  • 最新のJFrog CLIのバージョンがv2になっていること
  • かつてCLIのイメージを取得する先であったbintrayはサービス終了していること
    をふまえると、次のような記述が良いでしょう。
  - name: 'releases-docker.jfrog.io/jfrog/jfrog-cli-v2'
    entrypoint: 'bash'
    args:
    - '-c'
    - |-
      apk add docker-cli
      jfrog c add my-jfrog-server --url=https://${_JFROG_PLATFORM_URL}/ --user=${_JFROG_PLATFORM_USER} --password=$$APIKEY
      jfrog rt dp ${_JFROG_PLATFORM_URL}/spring-docker/spring-boot-docker:latest spring-docker --build-name=spring-boot-docker-build --build-number=${BUILD_ID} --server-id=my-jfrog-server
      jfrog rt bp spring-boot-docker-build ${BUILD_ID}

jfrog-cli-v2イメージを使用することでCLIを有効にし、jfrogコマンドが使えるようにします。

CLIの使い方はやりたいことに応じてご確認いただくのが1番ですが、今回使っているコマンドについては説明します。
www.jfrog.com

apk add docker-cli

(当然これはJFrog CLIじゃないですが、準備として必要なので補足します)

  • Cloud Build上ではCLIもコンテナで動いているわけですが、そのコンテナからDockerイメージをプッシュするためにDockerクライアントが必要です。
  • Alpine(CLIのDockerコンテナで動いている軽量のLinux)にapkというパッケージマネージャーでDockerクライアントをインストールしておきます。

jfrog c add

  • cconfigのことです。この記事の上の方で作ったJFrogのサーバーを指定し、認証しておきます。
  • CLIにおいてJFrogのサーバーを一意に識別するため、my-jfrog-serverという名前をここでつけています。

jfrog rt dp

  • rtはArtifactoryを操作するコマンド、dpdocker-pushの略です。直前に設定したmy-jfrog-serverという名前でサーバーを指定し、そこに対してdocker pushしています。
  • この作業でspring-boot-docker-buildという名の「ビルド」という塊を作っているのですが、この後セキュリティスキャンをする際もこのビルドに対して操作を行います。
  • spring-dockerは前半で作成したバーチャルリポジトリの名前で、プッシュ先として指定しています。バーチャルリポジトリに対してプッシュするとアーティファクトはローカルリポジトリに保存されます*1
  • ビルドを識別するためにIDを振る必要があるので、今回はCloud Buildが標準で提供してくれている変数$BUILD_IDを使いました。シーケンシャルな番号を振るとかでも良いです。

jfrog rt bp

  • bpbuild-publishを意味しており、これを実行しておくとビルド実行時のbuild info (アーティファクトの詳細や依存関係、サーバーの環境変数などビルドの付加情報) がArtifactory上に一緒に保存されます。

上記の設定を反映してGitHubにプッシュすると、トリガーの設定がうまく行っていればCloud Buildが実行されるはずです。

f:id:ihcomega:20210903143759p:plain
ArtifactoryのBuildsに行が増えていればイメージの保存成功

f:id:ihcomega:20210903144119p:plain
build info

ビルドをXrayでスキャンできるよう設定する

作成したビルドにもXrayのインデックスを追加しておきましょう。AdminメニューのXray SettingsでIndexed Resourcesを開き、Buildタブで操作します。

f:id:ihcomega:20210903161241p:plainf:id:ihcomega:20210903161238p:plain
Xray設定画面からスキャンしたいビルドを追加する

Manage Indexをクリック後に開く画面でspring-boot-docker-buildを追加すれば完了です。
f:id:ihcomega:20210903161632p:plain

☝JFrog Xrayでスキャンする

無事ビルドを作成できたのでXrayを使ってスキャンします。無料版では脆弱性のスキャンのみが使えるので、やっていきましょう。

その前にXray用語をごく簡単に紹介します。

  • ルール: チェックのレベルを定義します。「どの緊急度まで検知するか」「検知したら誰にどうやって通知するか」「検知した場合、そのアーティファクトの利用を許可するか」などを決めます。CVSSのスコアにも対応しています。
  • ポリシー: ルールを束ねてスキャンの方針を示すようなものです。後述のウォッチと紐付けて使います。
  • ウォッチ: ポリシーをどのビルド, リポジトリ*2に適用するかを定義します。

ということでルール, ポリシー, ウォッチを用意します。

f:id:ihcomega:20210903152038p:plain
adminメニューのXray設定画面を開く

ポリシー・ルールを追加する

f:id:ihcomega:20210903152127p:plain
ポリシーの作成画面からRuleを作成していく

ルールの作成画面はこのように細かい決まりを定められます。今回は例として

  • 緊急度がHigh以上なら検知する
  • 検知をしたらCIでのビルドを失敗させる(Fail Buildにチェックを入れるとそうなります)
    という設定を入れてみます。

f:id:ihcomega:20210903152424p:plainf:id:ihcomega:20210903152431p:plain
実現したいことをルールで表現する

ルールが追加できたらポリシーを保存しましょう。

f:id:ihcomega:20210903152924p:plain

ちなみに、なぜ1つのポリシーに複数のルールが設定できるのかについてはユースケースの例を挙げると分かりやすいかもしれません。
例えば「緊急度がHigh以上ならビルドを失敗させたいけど、Medium以下ならとりあえずメールで通知してほしい」場合は、今行った設定に加えて、Mediumならばメール通知するというルールを足してあげればOKです。

ウォッチを作成する

ポリシーを作った画面で隣のWatchesタブから操作できます。

f:id:ihcomega:20210903161717p:plain

ウォッチはポリシーとビルドを紐付けるものです。どのリポジトリのどのビルドに対しどのポリシーでXrayスキャンを行うかここで指定してください。

f:id:ihcomega:20210903161925p:plain

それぞれプラスボタンを押して、追加画面でポチポチ選択していきます。

f:id:ihcomega:20210903162108p:plainf:id:ihcomega:20210903162101p:plainf:id:ihcomega:20210903162104p:plain
リポジトリ、ビルド、ポリシーを追加する

f:id:ihcomega:20210903162706p:plain
無事追加できたら設定完了

JFrog CLIを使って保存したDockerイメージにセキュリティスキャンを行う

Xrayが準備万端になったので、cloudbuild.yamlにスキャンするためのコマンドをbpコマンドの後に追記しましょう。

jfrog rt bs spring-boot-docker-build ${BUILD_ID}

bsbinary-scanのことで、これがIDで特定したビルドに対しスキャンを行うための記述です。

結果

ついに全ての設定が完了しました!!!GitHubへ変更を反映させるとまたCloud Buildが走ります。

f:id:ihcomega:20210903164407p:plain

Xrayをオンにしたらビルドがコケるようになりました(1番上の行)。サンプルで使ったアプリケーションはベースイメージで大量の脆弱性が見つかってしまったんです…。

Cloud Buildの実行結果を見たらJSON形式で詳細が確認できます。
f:id:ihcomega:20210903164843p:plain

JFrog Platformにアクセスすればよりリッチに詳細を見ることもできます。

f:id:ihcomega:20210903165103p:plainf:id:ihcomega:20210903165059p:plain

☝終わりに

今回はGCP系のツールでやってみましたが、別のプラットフォーム、CI/CDツールでも同様のことが出来ます。オフィシャルにインテグレーションがサポートされている場合は公式ドキュメントが見つかるはずですが、英語しかなかったり最新バージョンを追従できていなかったりすることもあるので、JFrog関連で気になることや不明点があったらぜひご連絡ください〜!ブログのコメント欄でもOKです◎

文中に出てこなかった参考文献

*1:つまり、ここでローカルリポジトリを指定しても同じことが実現できるのですが、基本的にバーチャルリポジトリを使うようにすると便利です。詳細は別の機会に…

*2:「リリースバンドル」にも適用できるのですが、今日は登場していない概念なので割愛しました