blog.ryota-ka.me

Nix でのビルド時に private なリソースにアクセスする

HERPでは多くの成果物がNixを用いてビルドされている.例として,アプリケーションのDocker image,npmライブラリのtarball,Helm chartを元にしたKubernetesのmanifestファイルなどが挙げられる.

"purely functional package manager"であるNixを利用すると高い再現性を持ったビルドを実現できるが,purely functionalであるがゆえに,privateなリソースに依存したビルドには一工夫が必要になる.privateなリソースにアクセスするためには概して何らかのcredentialが必要になるからだ.そのようなcredentialはビルド手順*1に残したくないし,仮に残したとしても,一定時間でexpireしたりするのでいずれにしても再現性が得られない.

もちろん,ビルド時にprivateなリソースを利用できないのでは営利企業内で利用するのは難しい.そこで,部分的に再現性を諦めるための抜け道が用意されている.本稿ではfetchurl関数のnetrcPhasenetrcImpureEnvVarsオプションを利用する方法を紹介する.

なお,本稿で単にfetchurlと書いた場合には,builtins.fetchurlではなくNixOS/nixpkgsリポジトリに定義されているfetchurl関数を指すものとする.

問題設定#

最近,アプリケーションが依存する社内用パッケージのレジストリとしてAWS CodeArtifactを利用し始めたので,これを具体例に挙げて解説する.

https://mydomain-123456789.d.codeartifact.ap-northeast-1.amazonaws.com:443/npm/myrepository/private-package/-/private-package-0.1.0.tgzというURLからprivate-packageというパッケージを取得することを考える.また,パケッジマネジャーにはYarnを利用する.

netrcPhasenetrcImpureEnvVars#

fetchurlはHTTPを通じてリソースを取得するための関数である.fetchurlを利用して,認可を要するprivateなリソースにアクセスしたい場合,netrcファイルが利用できる*2

fetchurlは内部的にcurlコマンドを利用してリソースを取得する*3.また,fetchurlには,curlが利用するnetrcファイルを生成する手順を伝えるためのnetrcPhaseというオプション*4が用意されている.このオプションを指定すると,curlコマンドに--netrc-file $PWD/netrcというオプションを追加で渡してくれる*5

また,重要な点として,ビルド時の環境に設定されている環境変数のうち,netrcImpureEnvVarsオプション*6に指定した環境変数はnetrcPhaseの中で利用することができる.

これらを勘案すると,最終的に以下のようなderivationを書けば目的が達成できることになる.

pkgs.fetchurl {
  name = "private-package";
  url = "https://mydomain-123456789.d.codeartifact.ap-northeast-1.amazonaws.com:443/npm/myrepository/private-package/-/private-package-0.1.0.tgz";

  # ここにホワイトリスト形式で指定した環境変数はnetrcPhase内で参照できる
  netrcImpureEnvVars = [
    "CREDENTIAL"
  ];

  # ./netrcファイルを作成する
  # ここではビルド時の環境で設定されている環境変数$CREDENTIALが参照できる
  netrcPhase = ''
    do_something_with ''$CREDENTIAL
  '';
}

netrcファイルの内容#

fetchurlnetrcPhaseオプションを指定し,netrcファイルを生成する手順を与えれば,生成されたnetrcファイルを内部で使われているcurlが利用するように取り計らってくれるということがわかった.次に問題になるのは,netrcPhaseでどのようなnetrcファイルを生成すればよいのかという点である.そこでNixのことは一旦忘れ,どのようなnetrcファイルを記述すれば,以下のようなcurlコマンドでCodeArtifact上のpackageを取得できるかを明らかにしたい.

$ curl -LO --netrc-file ./netrc https://mydomain-123456789.d.codeartifact.ap-northeast-1.amazonaws.com:443/npm/myrepository/private-package/-/private-package-0.1.0.tgz

これには,天下り的だが,以下のような内容を記述すればよい.

ここで<authorizationtoken>$ aws codeartifact get-authorization-tokenで取得できる認可トークンである*7

tarballを取得するderivation#

curl経由でtarballを取得できたので,次はfetchurlを利用して同じtarballを取得するderivationを作ってみよう.これまでの内容を組み合わせると,以下のようなderivationを書けばよいことになる.

必要な環境変数を設定した上でこのderivationをビルドすると,ビルド中にCodeArtifactからtarballをダウンロードすることができる.

$ export AWS_CODEARTIFACT_AUTHORIZATION_TOKEN=$(aws codeartifact get-authorization-token --domain mydomain --domain-owner 123456789 --query authorizationToken --output text)
$ nix-build ./private-package.nix

Yarnとの統合#

実際の開発においてはprivate-package以外にも多数のライブラリに依存するので,上記のようなderivationをいちいち手で書くわけにはいかない.yarn2nixを利用すれば,yarn.lockから自動的にderivationを生成してくれる.

$ nix-shell -p yarn2nix --run 'yarn2nix > ./nix/yarn.nix'

上記のコマンドを実行すると,以下のようなファイルが生成されているはずである.

ここで,引数のfetchurlにはnetrcImpureEnvVarsおよびnetrcPhaseが指定された状態のものが渡される必要があるので,fetchurlをoverrideしつつcallPackageする.

このderivationは,以下のような記述をすることで,Yarnのオフラインキャッシュとして利用できる.

謝辞#

この記事の内容は,同僚である@hiroqnおよび@ruiccによる調査の成果に負うところが大きい.感謝します.

広告#

株式会社HERPでは,プロダクト開発に集中できる環境を整備できるエンジニアを募集しています.

脚注#

*1: Nixであればderivation

*2: https://nixos.wiki/wiki/Enterprise

*3: 実装

*4: 実装

*5: 実装

*6: 実装

*7: codeartifact:GetAuthorizationTokenだけでなくsts:GetServiceBearerTokenの実行権限も必要なことに注意されたい.