Artifact Registryリモートリポジトリを活用し、インターネット接続が制限された環境でビルドを実行する

記事タイトルとURLをコピーする

当記事は みずほリサーチ&テクノロジーズ × G-gen エンジニアコラボレーション企画 で執筆されたものです。


みずほリサーチ&テクノロジーズ株式会社の舘山です。本日は Artifact Registry リモートリポジトリ機能について、検証した結果を共有します。

Artifact Registry

はじめに

セキュリティ上の理由で開発環境からインターネット接続が制限されている場合、Dockerのベースイメージやプログラム言語パッケージの取得が課題になります。

また、プロキシサーバでパブリックリポジトリへの通信を単純に許可した場合、アップロードによる情報持ち出しリスクが生じます。

Artifact Registryリモートリポジトリ機能を利用することで、インターネット接続が制限されたVPCから間接的に、パブリックリポジトリへの読み取り専用アクセスが可能になります。本記事では具体的に以下 2 つのテーマについてご紹介します。

  • インターネット接続が制限された環境で Docker コンテナをビルドできること
  • インターネット接続が制限された環境で Cloud Functions をビルドできること

注意点としてArtifact Registryリモートリポジトリ機能は2023/4/3現在、正式リリース前のプレビュー版です。正式リリースまで本番ワークロードでの利用は、推奨されません。 →2023/10/27に GA になりました。

前提知識

Artifact Registryリモートリポジトリ

Artifact Registry とプライベート接続

Artifact Registryはコンテナイメージと言語パッケージを管理するサービスです。

インターネット接続が制限されたVPCからArtifact Registryのプライベートリポジトリには、限定公開のGoogleアクセスPrivate Service Connectを利用したアクセスが可能です。

Artifact Registryのリモートリポジトリ機能により、プライベートリポジトリから間接的に

に対する読み取り専用アクセスが可能になります。

Docker Hubをソースとするリモートリポジトリ

注意点

現状、リモートリポジトリではApt、Yumは、未サポートです。

OSパッケージについては、別環境でダウンロードしたパッケージを持ち込んで、オフラインインストールする等の代替手段が必要です。

プライベートパッケージとの併用

Artifact Registryの標準リポジトリとリモートリポジトリに対する単一のアクセス ポイントを提供する、仮想リポジトリ機能を利用できますが、当記事では扱いません。

Cloud Buildプライベートプール

Cloud Buildはサーバレスのビルド実行サービスです。

Cloud Buildのデフォルト設定では、自由にインターネット接続が可能なデフォルトプールでビルドが実行されます。

ビルドスクリプトによる外部へのデータ持ち出しを予防するため、組織ポリシーでデフォルトプールの利用を制限し、パブリックIPアドレスを無効化したプライベートプールでビルドを実行させることも可能です。

検証1. プライベートプールでのDockerイメージビルド

検証内容

パブリックIPアドレスを無効化したプライベートプールで、PythonアプリケーションのDockerイメージのビルドを実行してみました。

インターネット接続が制限された環境でのビルド

改修前のビルド構成ファイルとDockerfile

ビルド構成ファイル

Cloud Buildのビルド構成ファイルのスキーマはCloud Buildのガイドを参照してください。

Cloud Buildの各ビルドステップは、Dockerコンテナで実行されます。

Dockerのビルド用途に利用できるDockerイメージはGoogleがContrainer Registry('gcr.io/cloud-builders/docker')で公開しています。

Cloud Buildでビルドした成果物のDockerイメージは、Artifact Registry標準リポジトリへ格納します。

steps:
- name: 'gcr.io/cloud-builders/docker'
  args: [ 'build', '-t', 'asia-northeast1-docker.pkg.dev/poc01-xxxx/gcf-artifacts/test', '.' ]
images:
- 'asia-northeast1-docker.pkg.dev/poc01-xxxx/gcf-artifacts/test'
options:
  logging: CLOUD_LOGGING_ONLY
  pool:
    name: 'projects/poc01-xxxx/locations/asia-northeast1/workerPools/no-external-ip-pool'

Dockerfile

Docker HubからPythonのベースイメージを取得し、プログラムをCOPYで配置。PyPIから依存ライブラリを取得します。

FROM python:3.9-slim

ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# Install production dependencies.
RUN pip install -r requirements.txt

パブリックIPアドレス無効化したプライベートプールでは、Docker HubとPyPIにアクセスできないため、ビルドエラーになります。

プライベートプールからDocker Hubへのアクセスがタイムアウト

なお、指定するベースイメージによっては、Docker Hubへのアクセスエラーが発生しませんでした。 cloud-builders/dockerがContainer RegistryのDocker Hubイメージキャッシュを先にチェックしているためと思われます。

リモートリポジトリの作成

ガイドの記載に従いDocker HubとPyPIをソースとするリモートリポジトリを作成します。
- リモート リポジトリを作成する

Terraformを利用する場合、以下のような記載になります。

resource "google_artifact_registry_repository" "docker-proxy" {
  provider      = google-beta
  location      = "asia-northeast1"
  repository_id = "docker-proxy"
  description   = "Docker Hubをソースとするリモートリポジトリ"
  format        = "DOCKER"
  mode          = "REMOTE_REPOSITORY"
  remote_repository_config {
    description = "docker hub"
    docker_repository {
      public_repository = "DOCKER_HUB"
    }
  }
}

resource "google_artifact_registry_repository" "pypi-proxy" {
  provider      = google-beta
  location      = "asia-northeast1"
  repository_id = "pypi-proxy"
  description   = "PyPIをソースとするリモートリポジトリ"
  format        = "PYTHON"
  mode          = "REMOTE_REPOSITORY"
  remote_repository_config {
    description = "pypi"
    python_repository {
      public_repository = "PYPI"
    }
  }
}

google-betaプロバイダーのバージョン4.57.0 (March 13, 2023)以降の利用が必要です。

リポジトリをVPC Service Controlsで保護する場合、アップストリームソースへのアクセス許可設定が必要ですが、2023/4/3現在、Terraformで設定する方法は見つかりませんでした。

Dockerfileとビルド構成ファイルの改修方針

Artifact RegistryのPythonリポジトリ認証方式は2つあります。 認証方式によってDockerfileとビルド構成ファイルの改修内容が異なるため、それぞれの手順を解説します。

(1) キーリングによる認証を用いた改修

検証手順

Artifact RegistryのガイドではPythonキーリングライブラリはPyPIからインストールしていますが、今回はPyPIに直接アクセスできない前提のため、キーリングライブラリは別環境で事前にダウンロードし、ソースと一緒にビルド環境へ持ち込みます。 ビルド対象のDockerイメージにはライブラリをCOPY配置してオフラインインストールします。

参考サイト:
オフライン環境: pipでファイルからpythonパッケージインストール

Dockerfile、ビルド構成ファイルを以下の様に修正します。

Dockerfile

ベースイメージの取得先をArtifact Registryリモートリポジトリに変更します。
COPYで持ち込んだキーリングライブラリをオフラインインストールします。
Pythonライブラリの取得先をArtifact Registryリモートリポジトリに変更します。
--index-urlはrequirements.txt内で指定することも可能です。

# Docker HubをソースとするArtifact Registryリモートリポジトリを参照
FROM asia-northeast1-docker.pkg.dev/poc01-xxxx/docker-hub-proxy/python:3.9-slim

ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# Artifact Registry認証用キーリングライブラリはCOPYで持ち込み、オフラインインストールする
RUN pip install --no-index --find-links=./keyring keyring
RUN pip install --no-index --find-links=./keyring keyrings.google-artifactregistry-auth

# PyPIをソースするArtifact Registryリモートリポジトリを参照。
# RUNコマンド実行時にデフォルト認証情報(Cloud Buildサービスアカウント)を取得可能にしておくため、
# docker buildコマンドのオプションで--network=cloudbuild指定する。
# https://cloud.google.com/build/docs/build-config-file-schema?hl=ja#network
RUN pip install --index-url https://asia-northeast1-python.pkg.dev/poc01-xxxx/pypi-proxy/simple/ -r requirements.txt 

ビルド構成ファイル(抜粋)

RUN命令でビルド対象のコンテナ内でのpipコマンド実行時に、キーリングライブラリがCloud Buildサービスアカウント認証情報を取得できるように、docker buildコマンドのオプションに--network=cloudbuildを追加します。
- Cloud Build ネットワーク

steps:
- name: 'gcr.io/cloud-builders/docker'
  args: [ 'build','--network=cloudbuild', '-t', 'asia-northeast1-docker.pkg.dev/poc01-xxxx/gcf-artifacts/test', '.' ]

今回検証していませんが、キーリングライブラリは実行環境には不要なので、マルチステージビルドにして最終的なイメージから除外してもよいでしょう。

(2) サービスアカウントキーによるパスワード認証を用いた改修

検証手順

リモートリポジトリ参照のみ可能な最小権限のサービスアカウントを作成し、ガイドの記載に従いサービスアカウントキーからベーシック認証でリポジトリへアクセスするURLを生成します。

以下のような、ベーシック認証でリポジトリへアクセスするURLが取得できます。{key}部分は実際には長大な文字列になります。

https://_json_key_base64:{KEY}@{LOCATION}-python.pkg.dev/{PROJECT}/{REPOSITORY}/simple/

リモートリポジトリ参照のみ可能な認証情報漏洩の影響は限定的(リモートリポジトリをVPC Service Controlsで保護する場合、外部からのアクセス不可)ですが、今回はベーシック認証のURLをSecret Managerに保持することにします。

シークレットの作成方法は、Secret Managerのガイドを参照してください。
- シークレットを作成する

Dockerfile、ビルド構成ファイルを以下の様に修正します。

Dockerfile

ベースイメージの取得先をArtifact Registryリモートリポジトリに変更します。
Pythonライブラリの取得先はbuild-arg:PIP_INDEX_URLとして実行時に指定します。

# DockerHubをソースとするArtifact Registryリモートリポジトリを参照
FROM asia-northeast1-docker.pkg.dev/poc01-hn-audit-115243084873/docker-hub-proxy/python:3.9-slim

# PyPIをソースとするArtifact Registryリモートリポジトリをサービスアカウントキーで認証するベーシック認証URL
ARG PIP_INDEX_URL

ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

RUN pip install --index-url $PIP_INDEX_URL -r requirements.txt 

ビルド構成ファイル

ベーシック認証でリモートリポジトリへアクセスするURLをSecret Managerから取得し、docker buildコマンドのbuild-arg:PIP_INDEX_URLに引き渡します。

Cloud BuildでのSecret Manager利用方法はCloud Buildのガイドを参照してください。
- Secret Manager のシークレットを使用する

steps:
- name: 'gcr.io/cloud-builders/docker'
  entrypoint: 'bash'
  args: ['-c', 'docker build --build-arg PIP_INDEX_URL=$$PIP_INDEX_URL -t asia-northeast1-docker.pkg.dev/poc01-xxxx/gcf-artifacts/test .']
  secretEnv: ['PIP_INDEX_URL']
images:
- 'asia-northeast1-docker.pkg.dev/poc01-xxxx/gcf-artifacts/test'
availableSecrets:
  secretManager:
  - versionName: projects/xxxxxxxxx/secrets/pip_index_url/versions/1
    env: PIP_INDEX_URL
options:
  logging: CLOUD_LOGGING_ONLY
  pool:
    name: 'projects/poc01-xxxx/locations/asia-northeast1/workerPools/no-external-ip-pool'

注意点として、この方法で最終イメージを作成した場合、ARGに渡したベーシック認証のURLはdocker historyで露出します。

参考サイト:
Dockerイメージビルド時の秘密情報の扱い方に関するまとめ

前述のように漏洩の影響は限定的ですが、許容できない場合には別ステップでライブラリを取得し、結果だけを最終イメージに取り込むような手当が必要になります。

ビルド実行後のリモートリポジトリ

コンソールでソースリポジトリから取得したパッケージを確認できます。

ビルド実行後のリモートリポジトリ(Docker Hub)

ビルド実行後のリモートリポジトリ(PyPI)

検証2. Cloud Functions 関数のビルド

検証内容

AWS Lambda と異なり、Cloud Functions 関数のビルドはユーザープロジェクトの Cloud Build 環境で実行されます。

インターネット接続制限のため、組織ポリシーでデフォルトプールの利用を制限した場合、関数のビルドにもデフォルトプールを利用できなくなります。

今回、Python、Node.js、Javaについて、ライブラリの取得先をArtifact Registryリモートリポジトリへ変更することで、パブリックIP無効化したプライベートプールでビルドする方法を調査しました。

(1) Python関数

Cloud Functionsのガイド「Python での依存関係の指定:プライベート依存関係を使用する」を参考に、requirements.txt

--index-url https://asia-northeast1-python.pkg.dev/poc01-xxxx/pypi-proxy/simple/
functions-framework==3.*
boto3
beautifulsoup4

のように変更することで、パブリックIP無効化したプライベートプールでのビルドが成功しました。

Artifact Registryの認証には、Cloud Buildサービスアカウントが自動で利用されます。

なお、かつてBuildpackのPython関数ビルドプロセスには最初にpip、setuptools、wheelをPyPIからアップデートする処理が入っていましたが、2023/3末の更新でロジックが修正されています。

参考として、以前 (2023/3/30時点) の挙動では、pip、setuptools、wheelをPyPIからアップデートする処理が走り、パブリックIP無効化したプライベートプールからPyPIへ接続できないためビルドエラーとなりました。

ビルドログ

(2) Node.js関数

Cloud Functionsのガイド「Node.js での依存関係の指定:Artifact Registry の非公開モジュール」を参考にpackage.jsonと同じ階層に.npmrcファイルを配置して、参照先リポジトリを切り替えることで、パブリックIP無効化したプライベートプールでのビルドが成功しました。

registry=https://asia-northeast1-npm.pkg.dev/poc01-xxxx/npm-proxy
//asia-northeast1-npm.pkg.dev/poc01-xxxx/npm-proxy:always-auth=true

(3) Java関数

Cloud Functionsのガイド「Java での依存関係の指定」にはArtifact Registryの利用方法が言及されていませんが、

Artifact Registryのガイド「Maven と Gradle 用の認証を設定する:Maven を構成する」を参考に、pom.xmlにrepositoriesセクション、buildセクションの記載を追加することで、パブリックIP無効化したプライベートプールでのビルドが成功しました。

  <repositories>
    <repository>
      <id>central</id>
      <name>Maven Central remote repository</name>
      <url>artifactregistry://asia-northeast1-maven.pkg.dev/poc01-xxxx/maven-proxy</url>
      <layout>default</layout>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>

  <build>
    <extensions>
      <extension>
        <groupId>com.google.cloud.artifactregistry</groupId>
        <artifactId>artifactregistry-maven-wagon</artifactId>
        <version>2.2.0</version>
      </extension>
    </extensions>
  </build>

舘山 浩之

みずほリサーチ&テクノロジーズ

先端技術研究部に所属。個人のキャリアではAWSの利用経験が長く、Google Cloudは2022年より利用開始。