CIS（IBM Cloud Internet Service）でAuthenticated Origin Pull（認証済みOrigin Pull）を構成してみました

#1.はじめに
CISではクライアント証明書・サーバー証明書を利用したmTLSがサポートされています。これによりクライアントとCIS間のmTLSでセキュアな通信が可能となります（プランによって利用可能／不可能がありますので、詳細はご確認ください）。
そして、CISとオリジン間についてはHTTP通信・自己署名証明書を用いたTLS通信（いわゆるオレオレ証明書通信）・正式な証明書を用いたTLS通信などを利用することができます。このCISとオリジン（起点／バックエンドサーバー）間の通信でもmTLSをを利用したセキュアな通信を利用することが可能で、これがAuthenticated Origin Pull（認証済みオリジンプル）と呼ばれる機能となります。

この記事では、CISとオリジン間でのmTLS設定・利用方法（Authenticated Origin Pull）について記載します。なお、クライアントとCIS間のmTLSの詳しい設定についてはここでは割愛させていただきますが、デフォルトでは利用できないためSupportにCaseを上げてmTLSを有効化する作業が必要となりますのでご注意ください。

- CISのmTLSについては[こちら](https://cloud.ibm.com/docs/cis?topic=cis-mtls-features&locale=ja "相互TLSの利用")を参照してください。
- Authenticated Origin Pullについては[こちら](https://cloud.ibm.com/docs/cis?topic=cis-authenticated-origin-pull "Authenticated Origin Pull")を参照してください。
- Authenticated Origin Pullは、TLSのモードが「オフ」もしくは「クライアントからエッジ」の場合利用できません。

#2.準備
今回の検証用にバックエンド（オリジン）サーバーを準備します。今回はクライアント証明書は自己署名で作成し、サーバー証明書についてはCISのオリジン証明書発行機能を利用して作成しました。
またバックエンド（オリジン）サーバーですが、検証用ということでIBM Cloud のHyper Protect Virtual Serverの無料プランを利用しました。

##2.1 証明書作成
###2.1.1 認証局作成
mTLSに利用するクライアント証明書を作成するために、任意のディレクトリで以下のコマンドを実行することにより自己署名証明書用認証局を作成します。(windows wsl2を利用したubuntu/bashで作業しています）

```bash
#ubuntu/bashで作業
$ openssl req -new -x509 -nodes -days 365 -subj '/cn=jtoyo-ca' -keyout ca.key -out ca.crt
#作成後、確認
$openssl x509 -in ca.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            0a:4f:47:81:b5:ac:86:fc:a1:68:15:82:78:ce:8f:c1:13:61:ac:d9
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = jtoyo-ca
        Validity
            Not Before: May 12 05:04:31 2021 GMT
            Not After : May 12 05:04:31 2022 GMT
        Subject: CN = jtoyo-ca
....
```
###2.1.2 クライアント証明書作成
作成した認証局を利用してクライアント証明書を作成します。

```bash
#ubuntu/bashで作業
#Client証明書用key作成
$openssl genrsa -out client.key
#CSR作成
$openssl req -new -subj '/cn=localhost' -key client.key  -out client.csr 
#署名してClient証明書作成
$openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -in client.csr -out clinet.crt
#作成後、確認
$openssl x509 -in client.crt -text -noout
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            43:59:c6:67:6e:36:5e:10:a1:d8:61:5e:f4:9b:8e:82:01:56:59:aa
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = jtoyo-ca
        Validity
            Not Before: May 12 05:13:24 2021 GMT
            Not After : May 12 05:13:24 2022 GMT
        Subject: CN = localhost
        Subject Public Key Info:
```
###2.1.3 サーバ証明書作成
サーバー証明書については、CISのオリジン証明書発行機能を利用して作成します。

1. CISのダッシュボード画面で、ドメインを選択し、「セキュリティ」->「起点」を開きます。
1. 「注文」ボタンを押して任意の名前のRSA証明書を作成します。
![image-20210521144523504.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648609/7ace77bc-3165-ba6c-cb62-9f908348e2e5.png)
1. 作成された証明書と鍵を保存します（注：証明書／鍵ファイルを必ずここで保存してください。この記事ではファイルはbkend.crt/bkend.keyとしておきます）。
![q3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648609/45e55bba-5b66-d073-05c0-e41c9b88c02c.png)

##2.2 バックエンドサーバーの準備
1. バックエンドサーバーとしてIBM Cloud Hyper Protect Virtual Serverを利用します。準備として作成された仮想サーバーのIPアドレスをCISのDNSに登録しておきます。
![q4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648609/1ab36f92-ce9d-f32d-7e39-60d34c614549.png)
2. 作成した仮想サーバーに接続しログイン後、nodejs/npm/expressをインストールし任意のディレクトリにコード／保存しておいたサーバー証明書／鍵を配置し実行します。

- nodejs/npm/expressインストール

```bash
$ apt-get install nodejs
$ apt-get install npm
$ npm install express
```
- 任意のディレクトリに以下のコードを配置します。

```bash
// index.js
const express = require('express');
const https = require('https');
const fs = require('fs');
const options = {
  ca: fs.readFileSync('ca.crt'), // 認証局の証明書
  cert: fs.readFileSync('bkend.crt'), // サーバの証明書
  key: fs.readFileSync('bkend.key'), // サーバの秘密鍵
  rejectUnauthorized: true, // クライアント認証に失敗するとリジェクト
  requestCert: true, // クライアント認証を実施
};

const app = express();
app.get('/test1',(req, res) => {
	res.status(200).json({
		message: 'Hello mtls\n'
	});
});
https.createServer(options,app).listen(8443, () => {
	console.log('HTTPS listening on 8443....');
});
```

- Port 8443で通信できるようにiptablesを変更し、index.jsを実行します。

```bash
$ iptables -D INPUT -p tcp --dport 8443 -j ACCEPT
$ iptables -L  # 8443がtcpに対してOpenされていることを確認
Chain INPUT (policy DROP)
target     prot opt source               destination
.....
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:8443
.....
$ node index.js
HTTP listening on 8443....
```



#3. mTLS設定
##3.1 クライアントとCIS間のmTLS設定
今回の検証には関係ないのですが、CISとバックエンド間でのみmTLSの設定をすることはあまりないと思われるので設定しました。詳細な設定方法は省きますが以下ポイントのみ記述します。設定方法などは[こちら](https://cloud.ibm.com/docs/cis?topic=cis-mtls-features&locale=ja "相互TLSの利用")を参照ください。

1. CISではデフォルトではmTLSが有効になっていないため、Caseを上げてサポートに有効化してもらう必要があります。
1. クライアントとCIS間のmTLS用（証明書検証用）のルート証明書をCISにアップロードします。この際公的機関発行の証明書をアップロードすると実質だれでもアクセスできてしまうので注意が必要です（mTLSを用いたセキュアな通信という意味では問題無いです）。
1. 証明書のアップロードが完了したら、MTLSアクセス・ポリシーを作成します。ここでmTLS対象となるパス（アプリ）を追加することによりクライアントとCIS間でのmTLSによるセキュアな通信が可能となります。


##3.2 CISとバックエンド（オリジン）サーバ間のmTLS設定
本題のAuthenticated Origin Pullの設定ですが、今のところ（2021年6月現在）GUIからの設定はできずすべてコマンドラインからの設定となります。このため、まずibmcloudコマンドの最新化／CIS用pluginの導入をしておきます。

```bash
# ibmcloudコマンドの最新化
$ ibmcloud update
# CIS plutinの導入
$ ibmcloud plugin install cloud-internet-services
```

なおAuthenticated Origin Pullの設定には、「ゾーンレベル」「ホストレベル」に２種類のレベルが利用可能ですが、今回は「ゾーンレベル」（ゾーン全体に設定が効く）の設定を実施しています。ホストレベルの設定や、より詳しいAuthenticated Origin Pullの設定については[こちら](https://cloud.ibm.com/docs/cis?topic=cis-authenticated-origin-pull "Authenticated Origin Pull")を参照してください。またCLIの詳しいコマンドは[こちら](https://cloud.ibm.com/docs/cis?topic=cis-cli-plugin-cis-cli#authenticated-origin-pull "CIS CLIリファレンス 認証済みOrigin Pull")を参照してください。

###3.2.1 CISとバックエンド（オリジン）サーバー間のmTLSに利用するクライアント証明書をjson形式で保存します
ここでは、最初に作ったクライアント証明書を利用します。以下のコマンドで証明書文字列の改行コードをすべて"\n"に置き換えます。

```bash
# 証明書の改行コードを書き換え
$ MYCERT="$(cat client.crt|perl -pe 's/\r?\n/\\n/'|sed -e 's/..$//')"
# 鍵の改行コードを書き換え
$ MYKEY="$(cat client.key|perl -pe 's/\r?\n/\\n/'|sed -e's/..$//')"
```
続いて、ibmcloudコマンドに渡すjsonファイルを作成します。

```bash
$ cat <<EOF > reqbody.json
> { "certificate": "$MYCERT\n","private_key": "$MYKEY\n"}
> EOF
```
###3.2.2 CISに証明書（jsonファイル）をアップロードします。

IBM Cloudにログインし、設定するドメインのID確認します。

```bash
$ ibmcloud login 
....ログイン（省略）....
$ ibmcloud cis domains -i "cisのインスタンス名"
Listing domains for service instance 'cisのインスタンス名' ...
OK
ID                                 Name                        Status    Paused   Type
xxxxxxxxx(ドメインID)xxxxxxxxxxxx  ドメイン名(xxx.yyy.net)      active    false    full
```

作成したjsonファイル化した証明書をCISにアップロードします。

```bash
#　証明書アップロード（ゾーン）
$ ibmcloud cis authenticated-origin-pull-certificate-upload xxxxxxxxx(ドメインID)xxxxxxxxxxxx --json @reqbody.json
Uploading authenticated origin pull certificates of 'xxxxxxxxx(ドメインID)xxxxxxxxxxxx' for service instance 'cisのインスタンス名' ...
OK

    ID              a0123456-f123-4567-b123-012345678900
    Issuer          CN=jtoyo-ca
    Signature       SHA256-RSA
    Status          pending_deployment
    Serial Number   475848283729243497940160217941858139058153609640
    Uploaded On     2021-05-21T08:15:23.37397Z
    Updated At      2021-05-21T08:15:23.37397Z
    Expires On      2022-05-21T07:06:38Z

#　アップロードした証明書の確認
$ ibmcloud cis authenticated-origin-pull-certificates xxxxxxxxx(ドメインID)xxxxxxxxxxxx
Listing authenticated origin pull certificates of 'xxxxxxxxx(ドメインID)xxxxxxxxxxxx' for service instance 'cisのインスタンス名' ...
OK
ID                                     Status   Uploaded On                  Expires On
a0123456-f123-4567-b123-012345678900   active   2021-05-21T08:15:23.37397Z   2022-05-21T07:06:38Z
```

###3.2.3 アップロードした証明書を使ってAuthenticated Origin Pullを設定します。
以下のコマンドで設定します。

```bash
$ ibmcloud cis authenticated-origin-pull-settings-update xxxxxxxxx(ドメインID)xxxxxxxxxxxx --enabled on
Updating authenticated origin pull settings of 'xxxxxxxxx(ドメインID)xxxxxxxxxxxx' for service instance 'cisのインスタンス名' ...
OK

Enabled   true
```

###3.2.4 確認
最後にcurlを使って確認します。

- バックエンド（オリジン）サーバーに証明書なしで直接アクセス

```bash
$ curl -vk https://backend.yyyy.net:8443/test1 # バックエンド（オリジン）サーバーのホスト名を指定
*   Trying xxx.xx.xxx.xx...
* TCP_NODELAY set
* Connected to backend.yyyy.net (xxx.xx.xxx.xx) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS alert, Server hello (2):
* error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
* stopped the pause stream!
* Closing connection 0
curl: (35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
```
証明書を指定していないので、ハンドシェイクエラーとなり接続できません。

- CIS経由でアクセス

```bash
$ curl -vk https://xxx.yyyy.net:8443/test1 # CIS（GLB)のホスト名を指定

*   Trying xxx.xxx.x.x. ...
*   TCP_NODELAY set
*   Connected to xxxx.yyyy.net (xxx.xxx.x.x) port 8443 (#0)
*   ALPN, offering h2
*   ALPN, offering http/1.1
・・・（省略）・・・

> GET /test1 HTTP/2
> Host: xxx.yyyy.net:8443
> User-Agent: curl/7.58.0
> Accept: */*

* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS Unknown, Unknown (23):
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* TLSv1.3 (OUT), TLS Unknown, Unknown (23):
* TLSv1.3 (IN), TLS Unknown, Unknown (23):
  < HTTP/2 200
  < date: Fri, 21 May 2021 08:18:57 GMT
  < content-type: application/json; charset=utf-8
  < content-length: 24
  < set-cookie: CF_Authorization=eyJraWQiOiJlMTIyNTFkZjg0OTIzNTQ0Y2NiMDRjZGRiYzFjMzBjNzg0ZThmODViODViNDRjNjdjYWVkOTgxYzU1Yzk2ZDZlIiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJ0eXBlIjoiYXBwIiwiYXVkIjoiZjQ1ZDQ1NDc0Y2U3ZTIwYjIyMjIyMzgyY2MxODYzY2ZhMWY3YzJkYjdlODMzZmE3MjBjNTc4YzcwOTI5YzE5YSIsImV4cCI6MTYyMTY3MTUzNiwiaXNzIjoiaHR0cHM6XC9cL2ZiNDc0Mzk2Yjk5NDM0NGE2NWNiYTgzMTg5ZjYwZDA2LmNsb3VkZmxhcmVhY2Nlc3MuY29tIiwiY29tbW9uX25hbWUiOiJsb2NhbGhvc3QiLCJpYXQiOjE2MjE1ODUxMzYsInN1YiI6IkNOPWxvY2FsaG9zdCJ9.TIKbVqXFhsF3xyyzsxC7qFH_WIZcTagyM_etuKcKfnMd3KHZ0jkjY5usEJoEPm4QHyuBf1cIMrgELtaoWOpzyOPXng_-UaljMkMwNwR_A2NldSgQqolZrra_PUzWzmzsvdwBxdE1iOv2IOEPmDTGrsyd-tEGf7CSic3Sm6_eoSk9q7psIlx_rVoUKzYqo9g2W4QK7qszn-70vO9RJ7ROi6RmTrCJsw36JWwGI6meISRbKy4U4yzF1wES00yaBhTwBvyJuHBYvMx6boffg5jaGhnWxZgUIxsQdru-qh1lQyXCpjezZTMgTu1VnBn9xxcFcGWZmeNhYZMq6hOzpiq55Q; Expires=Sat, 22 May 2021 08:18:56 GMT; Path=/; Secure; HttpOnly
  < x-powered-by: Express
  < etag: W/"18-g/xxyjKO9ZXN0/7BZTmgDKWVQBE"
  < cf-cache-status: DYNAMIC
  < cf-request-id: 0a2f9aa2f30000f8eb69884000000001
  < expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
  < server: cloudflare
  < cf-ray: 652c607e59fdf8eb-NRT
  <
* Connection #0 to host xxx.yyyy.net left intact
  {"message":"Hello mtls"}
```

証明書を指定していませんが、CISが証明書を付けてバックエンド（オリジン）サーバーへとリクエストを送っているため正常にレスポンスが戻ってきています。

#4.おわりに
この記事ではCISのAuthenticated Origin Pullを利用してバックエンドまでmTLSでアクセスできることを確認しました。なおバックエンド（オリジン）サーバーでのmTLSですが、別途Range機能を利用すればクライアントとバックエンド（オリジン）サーバー間でmTLSによる通信が可能となります。ただしこの場合はWAF/CDNなどのCISの有用な機能が一部利用できなくなりますのでご注意ください。