あらしおブログ

技術ネタ中心の粗削りブログ

Jenkinsfile を書く前に知っておくべきこと (セキュリティ制約編)

Jenkins のパイプラインスクリプト(Jenkinsfile)は groovy ベースですが、 セキュリティや機能上の理由から様々な制約があります。 特別複雑な処理に制約があるというわけでもなく、groovy なら誰もが使いたくなるリストやマップのクロージャ処理が使えなかったり、 Serializable でないオブジェクトを扱えなかったりと、思った以上に使いにくいところがあるようです。

今回はセキュリティ上の制約についてわかったことを書きたいと思います。

対象読者は、Jenkins 公式の Overview を一通り読んでこれから自分でスクリプトを書こうと思っている方、 書いてみたけど RejectedAccessException 例外が発生して先に進めない方などです。

セキュリティ上の制約

誰でも任意のコードをパイプラインスクリプトで実行できるのはさすがにセキュリティ上の問題がありそうです。 そこでパイプラインにはセキュリティを担保する、以下の仕組みがあります。

スクリプト承認

スクリプト承認とは、管理者権限があるユーザが承認しない限り、パイプラインスクリプトそのものが実行できないというものです。 その代わり承認されれば中の処理に関わらず何でも実行することができます。シンプルですね。 なお、管理者自身がスクリプトを設定した場合は自動的に承認されます。

非管理者ユーザが未承認のスクリプトを実行すると、 以下のように UnapprovedUsageException エラーになり、スクリプトは承認待ちの状態になります。

Started by user user1
org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedUsageException: script not yet approved for use
    at org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval.using(ScriptApproval.java:459)
    at org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition.create(CpsFlowDefinition.java:106)
    at org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition.create(CpsFlowDefinition.java:59)
    at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:214)
    at hudson.model.ResourceController.execute(ResourceController.java:98)
    at hudson.model.Executor.run(Executor.java:410)
Finished: FAILURE

承認待ちのスクリプトは管理者がブラウザから [Jenkins の管理] > [In-process Script Approval] で確認することができます。 ここで「Approve」をクリックするとスクリプトが実行できるようになります。(画像の赤枠部分)

f:id:arasio:20161007004746p:plain

Groovy サンドボックス

Groovy サンドボックスとは、使えるメソッドが制限されたサンドボックスの中でパイプラインを実行するというものです。 許可されていないメソッド呼び出しがあると、RejectedAccessException が発生し、ビルドが失敗します。

例えば、Thread.sleep(1000) という一行だけのパイプラインスクリプトでも、Thread.sleep() が未承認のためエラーになります。 エラーメッセージを見る限り、すべての静的メソッドは承認が必要なようです。

Started by user user1
[Pipeline] End of Pipeline
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use staticMethod java.lang.Thread sleep long
    at org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist.rejectStaticMethod(StaticWhitelist.java:190)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onStaticCall(SandboxInterceptor.java:142)
    at org.kohsuke.groovy.sandbox.impl.Checker$2.call(Checker.java:180)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedStaticCall(Checker.java:177)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:91)
    at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:16)
    at WorkflowScript.run(WorkflowScript:1)
    at ___cps.transform___(Native Method)

実行しようとした未承認のメソッドも、スクリプト同様に UI から確認できるようになっています。 上の画像で青枠の部分になります。この例では Thread.sleep() が未承認となっています。 メソッドを確認し問題なければ「Approve」をクリックし、承認リストに追加することで、次回以降パイプラインスクリプトの中で実行できるようになります。

しかし、これは非常に面倒な作業です。なにしろ、実行してみないと何が使えないのかわかりません。 いくつかのメソッドはデフォルトでホワイトリストに入っているようですが、 私の感覚では、スクリプト内でちょっとデータ処理をしようと思うとほぼ確実に RejectedAccessException に出くわします。 一つ進んだと思ったら次のメソッドでまたエラーというのもよくあることです。 その度にジョブが失敗するので、はっきり言って結構萎える作業です。

設定方法

実際にパイプラインを作成するとき、どちらを使うか設定する方法を紹介します *1。 ここで、パイプラインスクリプトを Jenkins の UI 上に直接記述するか、バージョン管理システムからチェックアウトするかが重要になってきます。

1. パイプラインスクリプトを UI 上で直接記述する (Pipeline Script)

簡単な動作確認などをしたい場合、下のように Jenkins の UI 上で直接編集する場合があります。

f:id:arasio:20161007003144p:plain

ここで "Use Groovy Sandbox" にチェックを入れるとサンドボックスで実行されるようになります。 チェックをはずすと、スクリプト承認になります(ジョブを設定したのが管理者であれば自動承認)。 特に難しいところはありません。

2. パイプラインスクリプトを SCM で管理する (Pipeline Script from SCM)

パイプラインスクリプトは普通 git などでバージョン管理していると思います。 注意してほしいのは「Pipeline script from SCM」を選択すると、Groovy サンドボックスが強制的にオンになります。 チェックボックスがなく、選択の余地はありません。

f:id:arasio:20161007002959p:plain

管理者がビルドを実行するときも例外ではなく、未承認のメソッドが含まれる場合はビルドが失敗します。そして通すには管理者としてメソッド承認する必要があります。 おそらく SCM 管理だと誰が編集しているのかわからないので、常に Groovy サンドボックスで実行するようになっているのでしょう。

さきほど愚痴ったように、サンドボックスの仕組みは正直なところ実運用上とても使いにくいと感じています(特に小規模のチームでは)。 そこで、以下のようなハックも考えられています。

  1. パイプラインスクリプト自体は SCM で管理する
  2. パイプラインジョブではスクリプトを直接書く形式を選択し、Groovy サンドボックスをオフにする
  3. ジョブのスクリプト内で、パイプラインスクリプトを SCM からチェックアウトし実行するスクリプトを記述する

こうすればパイプラインスクリプト本体を SCM 管理にしたまま、Groovy サンドボックスをオフにした状態でジョブを実行することができます。 詳しくはこちらを参考にしてください。 セキュリティ上のリスクはもちろん負うことになります。

まとめ

長々と書きましたが、まとめると以下のようになります。

  • Jenkinsfile で記述できる groovy スクリプトにはいろいろな制約がある
  • セキュリティ上の制約として、スクリプト認証と Groovy サンドボックスの2つの仕組みがある
  • スクリプト認証
    • シンプルで運用が簡単
    • パイプラインスクリプトを UI 上で直接書く場合に使える
    • パイプラインスクリプトを SCM で管理する場合は基本的に使えない (回避策は一応あり)
  • Groovy サンドボックス
    • 運用が複雑というか面倒な代わりに、セキュリティ管理がしっかりできる
    • メソッド単位でサンドボックスで実行できるものを承認
    • 規模の小さいチームでは非効率でオーバースペック


機能上の制約についても書きましたのでご参考まで (2016/10/08 追記)

arasio.hatenablog.com

参考

*1:どちらも使わないという選択肢は残念ながらないようです