Codebuild と Gitlab 連携

CodeBuild と Gitlab

AWS Codebuild はネイティブな Gitlab 連携をサポートしていません。

f:id:nyuuuk:20200615041404p:plain

一方で、Github Enterprise を選択することで Gitlab と連携する方法が紹介されています。 qiita.com qiita.com

こちらの方法を試してみようと思い、CodeBuild -> ALB -> Gitlab 経由で接続を試みましたが、「CLIENT_ERROR: authentication required for primary source」 というエラーに悩まされました...

f:id:nyuuuk:20200615041653p:plain

これは CodeBuild が Gitlab リポジトリからソースをダウンロードする際に発生する認証エラーになります。

設定ミスかな~?などの自問自答を繰り返し試行錯誤していましたが、接続先を Public なリポジトリにしても認証エラーが発生するのでメダパニーマ状態。

今回はこのエラーで調査した内容をまとめます。

環境

nginx をプロキシとして利用して、CodeBuild-> nginx -> Gitlab の経路で接続します。 nginx を間に挟む理由は、エラーの内容をデバッグするためです。

Gitlab

認証 Token の動きをみるため、まず初めに Internal / Private なリポジトリで検証を進めていきます。

docs.gitlab.com

nginx の設定

以下のように設定します。

upstream backend {
    server [gitlab host]:9010 weight=1;
}

server {
    listen 8888;
    server_name [proxy host];

    location / {
        proxy_pass http://backend;
    }
}

nginx のログ

nginx のログ level を debug にしておきます。

hiroyukim.hatenablog.jp

Gitlab Deploy Token

公式の手順に従ってユーザーと Token を作成します。 ここでは、ユーザー名を "hoge"、作成された Token を "m21md2SLq3o3ueKwcVYM" として進めていきます。

f:id:nyuuuk:20200615042929p:plain

Github Enterprise 個人用アクセストーク

Github Enterprise では個人用アクセストークンを入力するフィールドがあります。 この入力内容は Authorization Header の Basic 認証 Token として利用されることになります(後述)。 ここでは以下のように、ユーザー名を "hoge"、パスワードを "m21md2SLq3o3ueKwcVYM" として設定します。

f:id:nyuuuk:20200615042851p:plain

CodeBuild の実行とエラーログ確認

この状態で CodeBuild を実行すると、nginx の access.log は以下のようになります。

[NATGW_IP] - hoge [13/Jun/2020:18:47:33 +0000] "GET /root/git-test.git/info/refs?service=git-upload-pack HTTP/1.1" 401 26 "-" "git/1.0" "-"
[NATGW_IP] - hoge [13/Jun/2020:18:47:34 +0000] "GET /root/git-test.git/info/refs?service=git-upload-pack HTTP/1.1" 401 26 "-" "git/1.0" "-"
[NATGW_IP] - hoge [13/Jun/2020:18:47:35 +0000] "GET /root/git-test.git/info/refs?service=git-upload-pack HTTP/1.1" 401 26 "-" "git/1.0" "-"
[NATGW_IP] - hoge [13/Jun/2020:18:47:36 +0000] "GET /root/git-test.git/info/refs?service=git-upload-pack HTTP/1.1" 401 26 "-" "git/1.0" "-"
[NATGW_IP] - hoge [13/Jun/2020:18:47:37 +0000] "GET /root/git-test.git/info/refs?service=git-upload-pack HTTP/1.1" 401 26 "-" "git/1.0" "-"
[NATGW_IP] - hoge [13/Jun/2020:18:47:38 +0000] "GET /root/git-test.git/info/refs?service=git-upload-pack HTTP/1.1" 401 26 "-" "git/1.0" "-"

検証ということもあり、root ユーザーでリポジトリ(git-test)を作成しています。[NATGW_IP] は Private Subnet で CodeBuild を実行したときのソース IP(NAT Gateway の IP) となります。

nginx のデフォルトのログフォーマットでは、"hoge" が $remote_user になります。 この $remote_user は、Basic 認証時に利用されるユーザー名になり、リターンコードが 401 で返却されており、認証に失敗していることがわかります。 nginx の error.log を確認すると、以下のようなログが出力されています。

2020/06/13 19:31:06 [debug] 1089#0: *23 http proxy header: "User-Agent: git/1.0"
2020/06/13 19:31:06 [debug] 1089#0: *23 http proxy header: "Accept: */*"
2020/06/13 19:31:06 [debug] 1089#0: *23 http proxy header: "Authorization: Basic aG9nZTptMjFtZDJTTHEzbzN1ZUt3Y1ZZTTp4LW9hdXRoLWJhc2lj"
2020/06/13 19:31:06 [debug] 1089#0: *23 http proxy header: "Accept-Encoding: gzip"
2020/06/13 19:31:06 [debug] 1089#0: *23 http proxy header:
"GET /root/git-test.git/info/refs?service=git-upload-pack HTTP/1.0
Host: backend
Connection: close
User-Agent: git/1.0
Accept: */*
Authorization: Basic aG9nZTptMjFtZDJTTHEzbzN1ZUt3Y1ZZTTp4LW9hdXRoLWJhc2lj
Accept-Encoding: gzip

"

この Autorization の Token をデコードしてみます。

 $ echo -n "aG9nZTptMjFtZDJTTHEzbzN1ZUt3Y1ZZTTp4LW9hdXRoLWJhc2lj" | base64 -d
hoge:m21md2SLq3o3ueKwcVYM:x-oauth-basic

...あれ?末尾に ":x-oauth-basic" が付与されていますね。 上記 Deploy Token で作成したユーザーと Token は "hoge:m21md2SLq3o3ueKwcVYM" ですが、CodeBuild のリクエストでは "hoge:m21md2SLq3o3ueKwcVYM:x-oauth-basic" で Gitlab の認証を行うため、401 のエラーを返していました。

対策

"hoge:m21md2SLq3o3ueKwcVYM" を base64 エンコードすると以下になります。

 $ echo -n "hoge:m21md2SLq3o3ueKwcVYM" | base64
aG9nZTptMjFtZDJTTHEzbzN1ZUt3Y1ZZTQ==

これより nginx の設定を以下のように変更します。

upstream backend {
    server  [gitlab-host]:9010 weight=1;
}

server {
    listen 8888;
    server_name [proxy-host];

    location / {
        set $authorization_value $http_authorization;
        if ($http_authorization = "Basic aG9nZTptMjFtZDJTTHEzbzN1ZUt3Y1ZZTTp4LW9hdXRoLWJhc2lj") {
            set $authorization_value "Basic aG9nZTptMjFtZDJTTHEzbzN1ZUt3Y1ZZTQ==";
        }
        proxy_set_header Authorization $authorization_value;
        proxy_pass http://backend;
    }
}

nginx を再起動して、CodeBuild を実行すると... 失敗を繰り返していた DOWNLOAD_SOURCE に成功しました!

f:id:nyuuuk:20200615043117p:plain

Public リポジトリに接続するには?

上記は Private / Internal のリポジトリを対象としていましたが、Public なリポジトリに接続するにはどうしたらよいでしょうか? 想定では、nginx の設定で空の Authorization Header に置換すればできそうな気がします。 これを実際に試してみます。

まずは CodeBuild のソース編集画面から、Github Enterprise 個人用アクセストークンで hoge のみを指定。

ここでは hoge ユーザーのみを指定します。 ユーザーもパスワードも指定しない、ということもできます。

CodeBuild から Gitlab への Authorization Header リクエスト内容は以下のようになります。

$ echo -n "hoge:x-oauth-basic" |base64
aG9nZTp4LW9hdXRoLWJhc2lj

今回は Public なリポジトリから Pull したいので認証は不要です。nginx の設定で空の Authorization Header に置換するようにします。

upstream backend {
    server  [gitlab-host]:9010 weight=1;
}

server {
    listen 8888;
    server_name [proxy-host];

    location / {
        set $authorization_value $http_authorization;
        if ($http_authorization = "Basic aG9nZTp4LW9hdXRoLWJhc2lj") {
            set $authorization_value "";
        }
        proxy_set_header Authorization $authorization_value;
        proxy_pass http://backend;
    }
}

nginx を再起動して、CodeBuild を実行すると...無事に Public なリポジトリに接続することができました!

ちなみに nginx には以下のように Authorization Header が書き換えられたログが出力されています。

(略)
2020/06/13 21:57:25 [debug] 1879#0: *5 http script complex value
2020/06/13 21:57:25 [debug] 1879#0: *5 http script var: "Basic aG9nZTp4LW9hdXRoLWJhc2lj"
2020/06/13 21:57:25 [debug] 1879#0: *5 http script set $authorization_value
2020/06/13 21:57:25 [debug] 1879#0: *5 http script var
2020/06/13 21:57:25 [debug] 1879#0: *5 http script var: "Basic aG9nZTp4LW9hdXRoLWJhc2lj"
2020/06/13 21:57:25 [debug] 1879#0: *5 http script value: "Basic aG9nZTp4LW9hdXRoLWJhc2lj"
2020/06/13 21:57:25 [debug] 1879#0: *5 http script equal
2020/06/13 21:57:25 [debug] 1879#0: *5 http script if
2020/06/13 21:57:25 [debug] 1879#0: *5 http script value: ""
2020/06/13 21:57:25 [debug] 1879#0: *5 http script set $authorization_value
(略)

SSL 通信の場合でも CodeBuild から接続できる?

上記は全て HTTP プロトコルで検証を進めてきました。 本番環境では Gitlab や nginx へのアクセスも、SSL 通信を意識することが多いでしょう。 ここでは以下のような構成でもう少し動作確認を進めてみます。

  • CodeBuild
    • Github Enterprise を選択(ただし nginx で Authorization Header を削除)
    • 自己証明書の場合、CodeBuild -> nginx への SSL 通信で pem ファイルが必要(S3 格納)
  • nginx
    • 今回は自己証明書を作成(server.crt / server.key)
    • SSL のリバースプロキシ
upstream backend {
    server [gitlab host]:6443 weight=1;
}

# HTTP -> HTTPS リダイレクトなども考える(今回は入れませんでした)

server {
    listen 6443 ssl;
    server_name [nginx host];

    ssl_protocols TLSv1.2;
    ssl_ciphers EECDH+AESGCM:EECDH+AES;
    ssl_ecdh_curve prime256v1;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    location / {
        if ($http_authorization = "Basic aG9nZTp4LW9hdXRoLWJhc2lj") {
            set $authorization_value "";
        }
        proxy_set_header Authorization $authorization_value;
        proxy_pass https://backend;
    }
}

都合上 6443 ポートを使用するにようしています(gitlab 起動時に 6443 を SSL の Port に指定しています)

  • gitlab
    • 今回は自己証明書を作成(gitlab.crt / gitlab.key)

Gtilab リポジトリHTTPS プロトコルで指定(https://[nginx host]:6443/root/git-test.git)して CodeBuild を実行します。 nginx で SSL 終端が行われ、これまでと同様に Authorization Header が削除されます。 その後、gitlab から無事にソースをダウンロードすることができました。

実際に proxy_pass を指定する場合は、DNS cache に注意する必要があります。 www.subthread.co.jp

まとめ

  • CodeBuild から Gitlab を直接呼び出そうとすると、401 の 認証エラーが発生します
  • CodeBuild から ALB などを経由して Gitlab に接続しても同様のエラーが発生します
  • これは、CodeBuild の Github Enterprise で Gitlab リポジトリに接続しようとすると、Authorization Header に想定外の文字列(":x-oauth-basic")が付与されるからです
  • CodeBuild から Gitlab に接続するためには、Authorization Header を適切な形式に変換する必要があります(":x-oauth-basic" を削除)
  • nginx などのプロキシを用いることで、Authorizaiton Header を変換して CodeBuild から Gitlab の接続ができるようになります