インターネットが使えない?問題ありません。JFrog Artifactoryをエアギャップで使用する – パート1

Use Artifactory with an Air Gap

事実上、すべての開発組織はビルドに必要な依存関係をダウンロードするために、Maven Central、NuGet Gallery、npmjs.org、Docker Hubなどの外部のパブリックリソースにアクセスする必要があります。Artifactoryを使用する大きな利点の1つは、これらのリモートリソースをプロキシし、ダウンロードされたアーティファクトをキャッシュするリモートリポジトリです。これにより、初めてアーティファクトを要求した開発者やCIサーバがあれば、そのアーティファクトはキャッシュされ、内部ネットワーク上のArtifactoryのリモートリポジトリから直接利用できるようになります。これがArtifactoryを使ってリモートリソースを操作する通常の方法です。

Accessing remote resources

しかし、金融機関や軍事施設など、より厳しいセキュリティ要件を持つ組織では業務をインターネットに公開するような設定は禁止されています。

エアギャップを利用する

このようなケースに対応するためには、少なくとも2つのArtifactoryインスタンスを用意することをお勧めします。1つはDMZ上、もう1つはエアギャップ上です。

通常、2つのシナリオがあります。

  1. ネットワークに未接続
  2. 一方通行の接続

ネットワークに未接続

このシナリオでは2つのArtifactoryインスタンス間にはネットワーク接続がありません。インターネットから依存関係を取得するためには外部インスタンスが依存関係をダウンロードし、それを外部デバイス(ハードドライブやUSBフラッシュドライブなど)にエクスポートし、内部インスタンスが開発者やCIサーバーで使用するためにそれをインポートする必要があります。

Exporting and Importing

 

依存関係の取得

ここではリモートリポジトリから依存関係を取得する2つの方法をご紹介します。

  1. 依存関係の宣言
    コードから依存関係の宣言だけを残します。宣言されたコードを実行に必要なツールを備えたDMZ上の仮想マシンにインストールします。
    例えば、npmパッケージを開発している場合はDMZマシンにnpmクライアントをインストールする必要があります。
    対応するクライアントは必要な依存関係をArtifactoryに要求して再帰的にnレベルの依存関係もダウンロードします。
  2. 専用スクリプト
    必要なすべてのパッケージを繰り返し検索し、Artifactoryに「ヘッドリクエスト」を送信して、リモートリソースからそれらのパッケージをダウンロードするスクリプトまたはメカニズムを実装します。
    例えば、次のbashの例ではキーがmaven centralのアーティファクトパスを表し、値がキャッシュされる必要のあるアーティファクトファイルのバージョンであるハッシュマップを維持します。
declare -A dependencies=(
  ['junit/junit']="4.13.2/junit-4.13.2.pom 4.13.2/junit-4.13.2.jar"
  ['org/webjars/jquery']="2.1.1/jquery-2.1.1.jar 3.6.0/jquery-3.6.0.jar 2.1.3/jquery-2.1.3.jar"
)

for d in "${!dependencies[@]}"
do
  echo -e "caching dependency  : $d, based on versions: ${dependencies[$d]}"
  for v in ${dependencies[$d]} 
  do 
  	curl -s -o /dev/null -LI -w "%{http_code}\n" -uadmin:password https:///artifactory/tal-maven-maven-remote/$d/$v 
  done
done

このハッシュマップは、より多くの要求されたアーティファクトをサポートするために拡張できます。

特定の技術では異なるメタデータ、プロパティ、APIリクエストを必要とする場合があります。たとえば、次のbashスクリプトはdockerクライアントでtomcatとalpineのバージョンのコレクションをキャッシュします。

declare -A dependencies=(
  ['alpine']="3.14 3.14.1"
  ['tomcat']="latest jdk8-openjdk jdk11"
)

docker login .jfrog.io -u admin -p password

for d in "${!dependencies[@]}"
do
  echo -e "caching dependency  : $d, based on versions: ${dependencies[$d]}"
  for v in ${dependencies[$d]} 
  do 
    docker pull .jfrog.io/docker-remote/$d:$v
  done
done
重要事項: Debian、Vagrant、PHP、R Artifactoryでは依存関係をエクスポートする際に具体的な必須プロパティをデプロイしないと、正常に動作しません。詳しくはDebianでのベストプラクティスとヒントをご覧ください。

エクスポートとインポート

ここでは新しい依存関係(前回エクスポートを実行した後にダウンロードされたもの)をエクスポートし、それを内部フォルダにインポートする2つの方法をご紹介します。

  1. UIとImport REST API
    簡単な方法としては必要なリポジトリをエクスポートするだけです。
    Export repository
    そして内部のArtifactoryにインポートします。
    Import repository
    また、自動化のためにリポジトリのコンテンツをインポートするためのREST APIエンドポイントも公開されています。
  2. JFrog CLIの利用
    エクスポートは基本的にファイルをある場所から別の場所にコピーすることなので、JFrog CLIはそれを実行するためのツールです。ここでは外部のArtifactoryインスタンスのリポジトリから新しいパッケージをすべてダウンロードし、内部のインスタンスにアップロードすることを考えています。新しいファイルをダウンロードする最も簡単な方法は以下のコマンドです。

    jfrog rt dl generic-local-archived NewFolder/
    

    すべてのファイルをダウンロードしなくて良いのかと思うかも知れませんが、JFrog CLIはチェックサムを認識しているため、前回のダウンロード以降に追加された新しいバイナリのみをダウンロードします。内部的にはJFrog CLIは実際にAQLクエリを実行して必要なファイルを見つけるため、レスポンスは次のようになります。

    [Info:] Pinging Artifactory...
    [Info:] Done pinging Artifactory.
    [Info:] Searching Artifactory using AQL query: items.find({"repo": "generic-local-archived","$or": [{"$and": [{"path": {"$match":"*"},"name":{"$match":"*"}}]}]}).include("name","repo","path","actual_md5","actual_sha1","size")
    [Info:] Artifactory response: 200 OK
    [Info:] Found 2 artifacts.
    [Info:] [Thread 0] Downloading generic-local-archived/jerseywar.tgz
    [Info:] [Thread 1] Downloading generic-local-archived/plugin.groovy
    [Info:] [Thread 1] Artifactory response: 200 OK
    [Info:] [Thread 0] Artifactory response: 200 OK
    [Info:] Downloaded 2 artifacts from Artifactory.
    

    これで、「NewFolder」を内部のインスタンスに持っていき、そのコンテンツをアップロードすることができます。これもJFrog CLIを使用します。

    jfrog rt u NewFolder/ generic-local-archive
    

    また、JFrog CLIはチェックサムデプロイを使用するため(ダウンロードの場合と同様)、内部インスタンスのターゲットにすでに存在するバイナリはデプロイされません。以下の出力では、apex-0.3.4.tarという1つの新しいファイルだけがチェックサムデプロイされています。

    [Info:] Pinging Artifactory...
    [Info:] Done pinging Artifactory.
    [Info:] [Thread 2] Uploading artifact: https://localhost:8081/artifactory/generic-local-archived/plugin.groovy
    [Info:] [Thread 1] Uploading artifact: https://localhost:8081/artifactory/generic-local-archived/jerseywar.tgz
    [Info:] [Thread 0] Uploading artifact: https://localhost:8081/artifactory/generic-local-archived/apex-0.3.4.tar
    [Info:] [Thread 1] Artifactory response: 201 Created
    [Info:] [Thread 2] Artifactory response: 201 Created
    [Info:] [Thread 0] Artifactory response: 201 Created (Checksum deploy)
    [Info:] Uploaded 3 artifacts to Artifactory.
    

複雑なクエリをファイルスペックで表現する簡単な方法

しかし、いつもそんなに単純ではありません。例えば全ての新規ファイルを外部のインスタンスから内部のインスタンスに移動させるのではなく、何らかの「承認スタンプ」が押されたファイルだけを移動させたいとします。このような場合、複雑なクエリを作成できるAQLの機能が選択肢の幅を広げてくれます。AQLを使えば、例えば「2020年10月15日以降に作成され、workflow.status=PASSEDというプロパティのファイルのみをgeneric-local-archivedからNewFolderライブラリにダウンロードする」というクエリを簡単に作成できます。JFrog CLIはパラメータをファイルスペックとして受け取ることができるので、以下のAQLクエリをNewAndPassed.jsonというファイルに作成します。

{
  "files": [
    {
      "aql": {
        "items.find": {
          "repo": "generic-local-archived",
          "created" : {"$gt" : "2020-10-15"},
          "@workflow.status" : "PASSED"
        }
      },
      "target": "NewFolder/"
    }
  ]
}

そしてJFrog CLIで指定します。

jfrog rt dl --spec NewAndPassed.json

あとは先ほどと同様、NewFolderのコンテンツを内部のインスタンスにアップロードするだけです。

一方通行の接続

セキュリティの高い機関の中にはインターネットと内部ネットワークとの分離を要求する一方で、やや緩やかなポリシーを持ち、一方通行の接続を許可しているところもあります。この場合、内部のArtifactoryインスタンスはプロキシまたは安全な一方向性のHTTPネットワーク接続を介して外部のインスタンスに接続することができます。このような設定を行う場合、内部のインスタンスが依存関係を得る手法が増えます。

  • スマートリモートリポジトリの使用
  • プルレプリケーションの使用

スマートリモートリポジトリ

Artifactoryのリモートリポジトリとはリモートリソースをプロキシするものです。スマートリモートリポジトリとはリモートリソースが実際に別のArtifactoryインスタンス内のリポジトリであるものです。

ここでは設定方法をご紹介します。

Smart remote repositories

DMZ上の外部インスタンスには

  • ダウンロード、スキャン、承認されたホワイトリストのアーティファクトをホストするローカルリポジトリ
  • 依存関係をダウンロードする必要があるリモートリソースをプロキシするリモートリポジトリ
  • 他のすべてを集約したバーチャルリポジトリ

内部のインスタンスには

  • ビルドやその他の承認されたローカルパッケージなど、ローカルのアーティストをホストするためのローカルリポジトリ
  • リモートリポジトリ – 実際には外部インスタンスのバーチャルリポジトリをプロキシするスマートリモートリポジトリ
  • 他のすべてを集約したバーチャルリポジトリ

その方法は以下の通りです。

  • ビルドツールがバーチャルリポジトリから内部のArtifactoryインスタンスへの依存関係を要求します。
  • 依存関係が内部で(ローカルリポジトリやリモートリポジトリのキャッシュで)見つからない場合、スマートリモートリポジトリは外部リソースに依存関係を要求します。その外部リソースとは実際には外部インスタンス上のバーチャルリポジトリです。
  • 外部インスタンスのバーチャルリポジトリは集約されたローカルリポジトリの1つ又はリモートリポジトリのキャッシュから要求された依存関係の提供を試みます。依存関係が見つからない場合、リモートリポジトリがリモートリソースからダウンロードし、そこから依存関係を要求した内部インスタンスに戻すことができます。

尚、内部インスタンスのバーチャルリポジトリには「Artifactory Requests Can Retrieve Remote Artifacts」が設定されている必要があります。

Virtual repository setting

プルレプリケーション

この方法では上記のいずれかの方法を用いて、依存関係を外部のArtifactoryインスタンス(DMZ内)にダウンロードします。後は内部インスタンスにリモートリポジトリを作成し、cronジョブに従って外部インスタンスの「クリーン」リポジトリからプルレプリケーションを起動し、ホワイトリストに登録された依存関係をすべて内部インスタンスに取り込むように設定するだけです。

パート2とパート3は近日公開。厳格なセキュリティポリシーを導入・維持し、エアギャップを利用して不変的なアーティファクトをリリースする方法を学びます。

関連資料