【Linux】認証プロキシ+認証局のCA証明書の設定

## はじめに

とあるプロキシでは、HTTPS通信の中身を見たいという理由で、Basic認証proxyに加えて、proxyの認証局のCA証明書(SSLサーバ証明書)を加えないと,HTTP/HTTPS通信できないものが存在する。  
HTTPS通信が行われるアプリ、ライブラリを使用する際は追加設定を行う必要があり、なんともブラウザファーストな厄介な代物である。  
この仕組みでは、proxyがサイトのURLごとにCA証明書を作成し、pcとproxy間、proxyとサーバ間でSSL/TLS通信を確立する。  

## 用語

- プロキシユーザー名: プロキシサーバでBasic認証が必要な時のユーザー名  
- プロキシパスワード: プロキシサーバでBasic認証が必要な時のパスワード  
- CAfile: 使用するCA証明書ファイル  
- CApath: CA証明書が格納されたディレクトリ。CA証明書(もしくは証明書へのシンボリックリンク)は{hash}.0という名前になっている必要がある。 {hash}は証明書のsubjectをハッシュ化した値。`openssl x509 -in <証明書ファイル> -hash -noout`で確認できる。  
- デフォルト値: アプリやライブラリで指定するCAfileやCApathの初期値。コンパイル時の設定やディストリビューションにより異なっている。  

ハッシュの作成方法は以下のように行う。

```bash
$ openssl x509 -in IIJ1.pem -hash -noout
ec922ae0
$ sudo cp IIJ1.pem /etc/ssl/certs
$ cd /etc/ssl/certs
$ sudo ln -s IIJ1.pem $(openssl x509 -in IIJ1.pem -hash -noout).0
```


## CA証明書のエンコードの種類

ASN.1というデータ構造で格納されており

- DER形式: バイナリ形式でエンコード
- PEM形式: BASE64方式でエンコード

詳しくはこちら<https://qiita.com/TakahikoKawasaki/items/4c35ac38c52978805c69>

curlとかLinuxで使用されているコマンドはほとんどがPEM形式のCA証明書で認識されている。DER形式のものはPEM形式に変更する必要がある。  

## DER形式からPEM形式に変更

```bash
openssl x509 -inform der -in IIJ1.der -outform pem -out IIJ1.pem
```

## ディストリビューションのCA証明書の追加

`ca-certificates`という大変ありがたい仕組みが用意されている。

### Ubuntu22.04の場合

https://ubuntu.com/server/docs/install-a-root-ca-certificate-in-the-trust-store

元がIIJ1.derというDER形式のファイルの場合

```bash
$ sudo apt install ca-certificates
$ ls
IIJ1.der
$ openssl x509 -inform der -in IIJ1.der -outform pem -out IIJ1.crt
$ sudo cp IIJ1.crt /usr/local/share/ca-certificates
$ sudo update-ca-certificates
```

※拡張子を.crtにすること

CAfile: `/etc/ssl/certs/ca-certificates.crt`とCApath: `/etc/ssl/certs`の中身が更新される。

### CentOS Stream9の場合

https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/9/html/securing_networks/adding-new-certificates_using-shared-system-certificates

元がIIJ1.derというDER形式のファイルの場合
rootディレクトリにファイルおいて実行

```bash
dnf install ca-certificates
openssl x509 -inform der -in IIJ1.der -outform pem -out IIJ1.pem
cp IIJ1.pem /usr/share/pki/ca-trust-source/anchors/
update-ca-trust
```

CAfile: `/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem` が更新される。
(curlはCAfileとして`/etc/pki/tls/certs/ca-bundle.crt`を指定しているが、実態はそのシンボリックリンクになっている。)

ディストリビューション側で管理しているプログラムライブラリではシンボリックリンクを張ったりしてこのCA証明書を利用する設計になっているはずである。  

※このようにありがたい仕組みあるので、間違ってもCA証明書の検証を無視する設定にしないこと。

## 方針

設定の優先度は
`コマンドのオプション>プログラム毎の設定ファイル＞環境変数＞デフォルト値`
となっていることが多い。  

### プロキシ認証関係

プロキシサーバのBasic認証はセキュリティ上の問題からコマンドのオプションはできるだけ使用せずにおこなう。プログラム毎の設定ファイル又は環境変数により設定する。  

### CA証明書関係

基本デフォルト値で行う。ディストリビューションのCA証明書の追加でカバーが可能ならそれで行う。    


## curlによる設定

CA証明書はPEM形式でないと読み込めない。  

### コマンドオプションによる設定

```bash
curl \
--proxy <protocol://host:port> \
--noproxy <no-proxy-list> \
--proxy-user <user:password> \
--cacert <file> \
--capath <dir> \
```

### 設定ファイル

```:~/.curlrc
proxy = "<protocol://host:port>"
noproxy = "<no-proxy-list>"
proxy-user = "<user:password>"
cacert = "<file>"
capath = "<dir>"
```

### 環境変数

- http_proxy=protocol://[user:passwd@]host:port
- HTTPS_PROXY=protocol://[user:passwd@]host:port
- https_proxy=protocol://[user:passwd@]host:port
- ALL_PROXY
- NO_PROXY
- no_proxy
- CURL_CA_BUNDLE: CA証明書のパス

※http_prxoyは小文字のみ、それ以外は両方OK。両方ある場合は小文字優先。

### CA証明書のデフォルト値

`curl --verbose`で調べることが可能。

#### Ubuntu22.04でのCA証明書のデフォルト値

ca-certificatesパッケージのデータを利用している。

- CAfile: `/etc/ssl/certs/ca-certificates.crt`
- CApath: `/etc/ssl/certs`

基本は`ca-certificates`の仕組みで組み込めばよい。

#### CentOS Stream 9でのCA証明書のデフォルト値

ca-certificatesパッケージのデータを利用している。

- CAfile: `/etc/pki/tls/certs/ca-bundle.crt`
- CApath: None

基本は`ca-certificates`の仕組みで組み込めばよい。

## dnfの設定

http/https通信はcurlを利用。

### 設定ファイル

dnfは内部でcurlを用いているが、`~/.curlrc`読み込まない。

```:/etc/dnf/dnf.conf
[main]
proxy=<protocol://host:port>
proxy_username=<user>
proxy_password=<password>
sslcacert=<pemfile>
```

### 環境変数

curlの使っているのでcurlと同じ

### dnfのデフォルト値

curlのデフォルト値となるが

- CAfile: `/etc/pki/tls/certs/ca-bundle.crt`
- CApath: None

基本は`ca-certificates`の仕組みで組みこんで`sslcacert`は使わない。

## aptの設定

### コマンドオプションによる設定

```bash
sudo apt \
-o Acquire::http::Proxy=<http_proxy> \
-o Acquire::https::Proxy=<https_proxy>
```

### 設定ファイル

`/etc/apt/apt.conf`または`/etc/apt/apt.conf.d`以下で作成した`XXXX.conf`ファイルに記載

```:/etc/apt/apt.conf or /etc/apt/apt.conf.d/proxy.conf
Acquire {
    http {
        Proxy "http://[[user][:pass]@]host[:port]/";
    };
    https {
        proxy "http://[[user][:pass]@]host[:port]/";
    };
};
```

### 環境変数による設定

- http_proxy=protocol://[user:passwd@]host:port
- https_proxy=protocol://[user:passwd@]host:port

※sudo実行する場合はデォルトではこの環境変数は削除されて実行してしまうので、sudoの設定ファイルを変更してください。

```:/etc/sudoers.d/proxy
Defaults env_keep = "http_proxy https_proxy"
```

### デフォルトのCA証明書について

CApath:`/etc/ssl/certs`
CAfile:`/etc/ssl/certs/ca-certificates.crt`  

基本は`ca-certificates`の仕組みで組み込めばよい。

## Python pipの設定

https://pip.pypa.io/en/stable/user_guide/#using-a-proxy-server

https://pip.pypa.io/en/stable/topics/https-certificates/

### コマンドオプションによる設定

```bash
python -m pip --proxy <protocol://[user:passwd@]host:port> \
--cert <file.pem>
```

### 設定ファイル

コマンドでpip.confに書き込む

```bash
python -m pip config set global.proxy <protocol://[user:passwd@]host:port>
python -m pip config set global.cert <file.pem>
```

直接書き込む

```:~/.config/pip/pip.conf
[global]
proxy = <protocol://[user:passwd@]host:port>
cert = <file.pem>
```

ちなみにLinuxでの場所は
`$HOME/.config/pip/pip.conf($XDG_CONFIG_HOME=$HOME/.config)`
Windowsでの場所は`%APPDATA%\pip\pip.ini`


### 環境変数による設定

- http_proxy=protocol://[user:passwd@]host:port
- https_proxy=protocol://[user:passwd@]host:port
- no_proxy
- PIP_CERT="CAfile"
- REQUESTS_CA_BUNDLE="CAfile"
- CURL_CA_BUNDLE="CAfile"

### デフォルトのCA証明書

デフォルトのCA証明書はpipの中にある`certifi`モジュールの中にある。下記の図のようにsite-packagesの直下ではなくpipディレクトリの_vendorディレクトリに組み込んでいる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/998887/572795d6-1295-6719-a480-2135024843ad.png)

環境変数REQUESTS_CA_BUNDLEに`ca-certificates`で作成されたCAfileパスを設定するのが一番簡単である。

## Python urllib.requestの設定

### 環境変数による設定

- http_proxy=protocol://[user:passwd@]host:port
- https_proxy=protocol://[user:passwd@]host:port

### デフォルトのCA証明書

https通信ではsslライブラリ(Openssl)を利用している

https://docs.python.org/3/library/ssl.html

```python
import ssl
ssl.get_default_verify_paths()
# Ubuntu22.04 
# DefaultVerifyPaths(cafile=None, capath='/usr/lib/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/usr/lib/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/usr/lib/ssl/certs')
# CentOS Stream9
# DefaultVerifyPaths(cafile='/etc/pki/tls/cert.pem', capath='/etc/pki/tls/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/etc/pki/tls/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/etc/pki/tls/certs')
```

#### Ubuntu22.04の場合
/usr/lib/ssl/certs -> /etc/ssl/certsのシンボリックリンク
/usr/lib/ssl/cert.pem -> 存在しない

#### CentOS Steam9の場合
/etc/pki/tls/cert.pem -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pemのシンボリックリンク
/etc/pki/tls/certs/　-> 存在する。(ca-bundle.crt,ca-bundle.trust.crt)

いずれもディストリビューションが設定したopensslを利用しているので`ca-certificates`の仕組みで組み込めばよい。

※Anacondaの場合はAnacondaのopensslの設定を利用しているので異なる。

## Python requestsの設定

https://docs.python-requests.org/en/latest/index.html

### スクリプトで直接記入

```python
import requests

proxies = {
  'http': 'protocol://[user:passwd@]host:port',
  'https': 'protocol://[user:passwd@]host:port',
}
verify = '/path/to/certfile'

requests.get('https://github.com', proxies=proxies, verify=verify)
```


### 環境変数

- http_proxy=protocol://[user:passwd@]host:port
- HTTP_PROXY=protocol://[user:passwd@]host:port
- https_proxy=protocol://[user:passwd@]host:portt
- HTTPS_PROXY=protocol://[user:passwd@]host:portt
- no_proxy
- NO_RROXY
- REQUESTS_CA_BUNDLE="CAfile"
- CURL_CA_BUNDLE="CAfile"

### デフォルトのCA証明書

CAバンドルは`certifi`ライブラリを使用

```python
import certifi
certifi.where()
# .venv/lib/python3.11/site-packages/certifi/cacert.pem
```

このcacert.pemにPEM形式のファイルを追記等を行うか、環境変数REQUESTS_CA_BUNDLEに`ca-certificates`で作成されたCAfileパスを設定するのが一番簡単である。

## wgetの設定

### コマンドオプションによる設定

```bash
wget --proxy-user=<user> \
--proxy-password=<password> \
--ca-certificate=<file> \
--ca-directory=<directory>
```

※ --http-proxyのようなオプションはないので、環境変数とかでカバーする。

### 設定ファイル

```:~/.wgetrc
http_proxy = <http_proxy>
https_proxy = <https_proxy>
no_proxy = <no_proxy>
proxy_user = <user>
proxy_password = <password>
ca_certificate = <file>
ca_directory = <directory>
```

### 環境変数

- http_proxy=protocol://[user:passwd@]host:port
- https_proxy=protocol://[user:passwd@]host:port
- no_proxy

### デフォルトのCA証明書

ディストリビューションのOpenSSLからで、  
CAfile: "OpenSSL directory"配下のcert.pem  
CApath: "OpenSSL directory"配下のcertsディレクトリ  
となる。
"OpenSSL directory"は`openssl version -d`で確認可能。

```bash
$ openssl version -d
# Ubuntu 22.04
# OPENSSLDIR: "/usr/lib/ssl"
# CentOS Stream 9
# OPENSSLDIR: "/etc/pki/tls"
```

#### Ubuntu22.04の場合

/usr/lib/ssl/certs -> /etc/ssl/certsのシンボリックリンク
/usr/lib/ssl/cert.pem -> 存在しない

#### CentOS Steam9の場合

/etc/pki/tls/cert.pem -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pemのシンボリックリンク
/etc/pki/tls/certs/　-> 存在する(ca-bundle.crt,ca-bundle.trust.crt)

基本的に`ca-certificates`の仕組みで組み込めばよい。

## gitの設定

https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpproxy

### 設定ファイル

`git config`コマンドで`~/.gitconfig`に書き込む

```bash
git config --global user.name "<user>"
git config --global user.email "<e-mail>"
git config --global http.proxy "<[protocol://][user[:password]@]proxyhost[:port]>"
git config --global http.sslCAInfo "<cafile>"
git config --global http.sslCAPath "<capath>"
```

※https.proxyという設定はない！

確認

```bash
git config --global --list
```

`~/.gitconfig`(ini形式に近いが同じではない。)

ちなみにWindowsでは`C:\Users\(ユーザー)\.gitconfig`

```:~/.gitconfig
[user]
        name = <user>
        email = <e-mail>
[http]
        proxy = <[protocol://][user[:password]@]proxyhost[:port]>
        sslCAInfo = <cafile>
        sslCAPath = <capath>
```

### 環境変数

curlの環境変数に加えて

- http_proxy=protocol://[user:passwd@]host:port
- HTTPS_PROXY=protocol://[user:passwd@]host:port
- https_proxy=protocol://[user:passwd@]host:port
- ALL_PROXY
- NO_PROXY
- no_proxy
- CURL_CA_BUNDLE: CA証明書のパス
- GIT_SSL_CAINFO
- GIT_SSL_CAPATH

### デフォルトのCA証明書

curlと同じなので基本は`ca-certificates`の仕組みで組み込めばよい。


## Condaの設定

https://docs.conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html

https://docs.conda.io/projects/conda/en/latest/user-guide/configuration/settings.html

https://docs.conda.io/projects/conda/en/latest/user-guide/configuration/non-standard-certs.html

### 設定ファイル

YAML形式で書かれる
Linux: `~/.condarc`
Windows: `C:\Users\(ユーザー)\Anaconda3\.condarc`

#### `conda config`コマンドによる入力

```bash
conda config --set proxy_servers.http <protocol://[user:passwd@]host:port>
conda config --set proxy_servers.https <protocol://[user:passwd@]host:port>
conda config --set ssl_verify <file.pem>
```

設定を見る

```bash
conda config --show
```

#### 直接入力

```:~/.condarc
proxy_servers:
  http: <protocol://[user:passwd@]host:port>
  https: <protocol://[user:passwd@]host:port>
ssl_verify: <file.pem>
```

### 環境変数

- HTTP_PROXY=protocol://[user:passwd@]host:port
- HTTPS_PROXY=protocol://[user:passwd@]host:port
- REQUESTS_CA_BUNDLE
- CURL_CA_BUNDLE

### デフォルトのCA証明書

condaの中にあるopensslを利用していると思われる。  

```bash
$ ~/miniconda3/bin/openssl version -d
# OPENSSLDIR: "~/miniconda3/ssl"
```

これを見ると`~/miniconda3/ssl/cert.pem` -> `~/miniconda3/ssl/cacert.pem`がCA証明書となる。
環境変数REQUESTS_CA_BUNDLEに`ca-certificates`で作成されたCAfileパスを設定するのが一番簡単である。

## まとめ

どのプログラムでも可能な一番簡単な設定は

- `ca-certificates`でCA証明書を取り込む
- 環境変数ですべて設定する

設定例
```bash:~/.bash_profile or ~/.profile
export proxy_host=proxy.server
export proxy_port=8080
# どちらかを利用、ただしCIDR表記は認識しないプログラムが多いと思われる。ドメインはワイルドカードなしで記入する。
#no_use_proxy=192.168.0.0/16
no_use_proxy="$(echo 192.168.1.{1..255})"
no_use_proxy="${no_use_proxy// /,}"
no_use_proxy="$no_use_proxy",.example.com
export no_use_proxy

# Usage: set_proxy
function set_proxy() {
	read -p "Please enter your name: " __user
	read -sp "Please enter your password: " __pass ; echo
	
	# パーセントエンコーディング
	user=$(echo "$__user" | jq -Rr @uri)
	pass=$(echo "$__pass" | jq -Rr @uri)

	proxy="http://${user}:${pass}@${proxy_host}:${proxy_port}"
	http_proxy="$proxy"
	HTTP_PROXY="$proxy"
	https_proxy="$proxy"
	HTTPS_PROXY="$proxy"
	no_proxy=127.0.0.1,localhost,"$no_use_proxy"
	NO_PROXY="$no_proxy"
	
	osName=$(grep ^ID= /etc/os-release | sed s/ID=// | sed s/\"//g)
	if [[ "$osName" == "ubuntu" ]]; then
		CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
	fi
	if [[ "$osName" == "centos" ]]; then
		CA_BUNDLE=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
	fi
	REQUESTS_CA_BUNDLE="$CA_BUNDLE"
	CURL_CA_BUNDLE="$CA_BUNDLE"
	
	export http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
	export REQUESTS_CA_BUNDLE CURL_CA_BUNDLE
}

# Usage: unset_proxy
function unset_proxy() {
	unset http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
	unset REQUESTS_CA_BUNDLE CURL_CA_BUNDLE
}

```
※jqを使用しています。

セキュリティの観点からユーザー名とパスワードを入力するようにした。  
使用するときにset_proxyと入力してユーザー名とパスワードを入力する。  
削除する際はunset_proxyと入力する。