はじめに

社内ネットワークから sbt でビルドしていると、「リポジトリにアクセスできない」「unresolved dependency」といったエラーに遭遇することがあります。プロキシ認証を必要とする環境では ~/.sbt/repositories~/.sbt/1.0/global.sbt にユーザー名・パスワードを記載しますが、パスワードに #+\ などの記号が含まれると正しく認証されずハマりがちです。
この記事では、記号入りパスワードを含むプロキシ設定を、sbt 1.x 系で動作確認した手順を交えて解説します。読み終えると、以下のことができるようになります。

  • プロキシ認証用の URL を正しくエンコードする
  • パスワードを環境変数で安全に管理する
  • エラー時のログレベル調整と切り分けのコツ

前提知識

  • sbt の基本操作(ビルド定義ファイルの書き方)
  • 社内プロキシ(Basic 認証)の存在を把握していること
  • URI エンコードの基礎(% 付きの文字列を見たことがある)

プロキシ認証で記号入りパスワードが失敗する理由

sbt は内部で Ivy を使って依存ライブラリを解決します。Ivy は http(s)://user:pass@host:port 形式の URI をパースしてプロキシへ接続しますが、この際「:@ より前の文字列がユーザー名、以降がパスワード」という単純な分割を行うため、パスワードに記号が含まれると意図した区切り位置がずれて認証エラーになります。
例えばパスワードが P@ss#123 のとき、URI 文字列全体が
http://user:P@ss#123@proxy:8080
となり、@ が2つ出現してしまうため Ivy が正しく解釈できません。これを防ぐため、パスワード部分を RFC 3986 準拠のパーセントエンコード する必要があります。

記号入りパスワードを安全に扱う設定手順

以降、macOS / Linux を想定して説明します(Windows の場合は PowerShell の -replacesetx を読み替えてください)。

ステップ1:パスワードをエンコードする

ターミナルで下記コマンドを実行し、パスワードをエンコードします(Ruby が入っている場合)。

Bash
ruby -ruri -e 'puts URI.encode_www_form_component("P@ss#123")' # => P%40ss%23123

Python でも可。

Bash
python3 -c 'import urllib.parse; print(urllib.parse.quote("P@ss#123"))'

どちらも同じ結果が得られます。出力された文字列(P%40ss%23123)をメモしておきます。

ステップ2:環境変数にユーザー名・エンコード済みパスワードを格納する

平文でビルドファイルに書きたくないため、環境変数で管理します。
~/.zshrc~/.bashrc に以下を追記。

Bash
export PROXY_USER=myuser export PROXY_PASS=P%40ss%23123 # エンコード済み

設定を読み込み直す。

Bash
source ~/.zshrc # zsh の場合

ステップ3:sbt の global.sbt でプロキシを設定する

~/.sbt/1.0/global.sbt を作成(または追記)します。

Scala
val user = sys.env.getOrElse("PROXY_USER", "") val pass = sys.env.getOrElse("PROXY_PASS", "") val host = "proxy.example.com" val port = 8080 sys.props.put("http.proxyHost", host) sys.props.put("http.proxyPort", port.toString) sys.props.put("http.proxyUser", user) sys.props.put("http.proxyPassword", pass) sys.props.put("https.proxyHost", host) sys.props.put("https.proxyPort", port.toString) sys.props.put("https.proxyUser", user) sys.props.put("https.proxyPassword", pass) // Ivy 用の repositories ファイルでも同じ形式を使う

この方法であれば、パスワードに #+ が含まれていてもエンコード済みなので Ivy が正しく認証できます。

ステップ4:動作確認とトラブルシュート

適当なプロジェクトで sbt update を実行し、依存が取得できれば成功です。
エラーが出る場合は以下を確認します。

  • 環境変数が正しく読み込まれているか
    sbt 'show sys.env' | grep PROXY
  • エンコードが正しいか
    ruby -ruri -e 'puts URI.encode_www_form_component("元のパスワード")' で再チェック
  • プロキシが Basic 認証ではなく NTLM か
    その場合は cntlmpx などの中継プロキシを挟む必要があります

ハマった点とエラー解決

エラー例1:401 Proxy Authentication Required

[error] (update) sbt.librarymanagement.ResolveException: unresolved dependency: ...

このメッセージが出ても、パスワードが間違っているとは限りません。URI パースが失敗してプロキシにユーザ名が届いていないケースも多いです。
解決策global.sbtlogLevel := Level.Debug を一時的に追加し、proxyPassword に想定通りの値が渡っているか確認します。

エラー例2:sbt 起動時に MalformedURLException

記号をエンコードせずに http://user:P@ss#123@proxy:8080 のように書くと起きます。
解決策:ステップ1 の通り、パスワード部分のみを必ず URL エンコードすること。

エラー例3:Windows で環境変数が読めない

Windows の環境変数に &# を含む文字列を直接セットすると、コマンドプロンプトが解釈してしまいます。
解決策:PowerShell で設定する場合はシングルクォートで囲むか、

Powershell
setx PROXY_PASS "P%40ss%23123"

を利用します。set ではなく setx を使わないと再起動後に反映されない点に注意です。

まとめ

本記事では、sbt のプロキシ認証で記号を含むパスワードを使う際の落とし穴と対策を紹介しました。

  • URI パースの関係上、パスワードは必ずパーセントエンコードする
  • ビルドファイルに平文を書かず、環境変数で管理する
  • エラー時は Debug ログでプロキシパラメータを確認する

この方法を使えば、特殊文字が含まれていても安全かつ確実にプロキシ認証が通るようになります。
次回は、「sbt 1.10 以降で導入された Coursier のプロキシ設定」について、同じような観点でまとめる予定です。

参考資料