CIS(IBM Cloud Internet Service)でmTLSを構成する(1) - 基本設定編

#1. はじめに

CISではmTLSがサポートされており、クライアント証明書・サーバー証明書の両方を利用することで非常にセキュアな通信が可能である。CISのmTLS機能は内部的には、Cloudflareの「**Cloudflare Access**」を利用しており、**[Enterprise Plan(Usage & Packageのみ。Enterprise GLBやEnterprise Securityでは不可）](https://cloud.ibm.com/docs/cis?topic=cis-cis-plan-comparison)**が必要である。
本記事では、CISにおけるmTLSの設定、動作確認、およびmTLSに必要なクライアント証明書やCA(認証局)の作成方法について記載する。

- mTLSの挙動については、[ここ](https://developers.cloudflare.com/access/service-auth/mtls/)を参照。
- クライアント証明書の発行には公的なCAを利用しない。なぜならば、その公的なCAから正当性を保証された証明書を持っていれば誰でもアクセスできてしまうためである。むしろ特定のユーザーからしかアクセスさせたくないため、自前でroot CAを作成する。
- mTLSの有効化はドメインレベルで行うが、実際にはFQDNごとにmTLSを設定することになる。よって、mTLSを有効化したドメインにおいて、あるFQDNはmTLSを利用するが別のFQDNはmTLSを利用しないという構成が可能である。

2020/09/01追記
本記事を初めて投稿した時にはAPI/CLIしかサポートされていなかったが、現時点ではUIでの構成も可能になっている。以下はCLIで以前構成した内容をUIで確認した画面である。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/017dd6f5-9041-2f24-75c6-d51b76bd21ea.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/ce9a343f-f041-7639-6abb-f85320a84eae.png)




#2. mTLS機能の有効化

##2-1. ibmcloudコマンドおよびCIS pluginを最新化する。

```txt:ibmcloudコマンドおよびプラグインの更新
ibmcloudコマンド本体の更新
# ibmcloud update

まだCIS pluginを導入していない場合は新規導入
# ibmcloud plugin install cloud-internet-services 

CIS pluginを導入済みの場合は、最新化
# ibmcloud plugin update
```

##2-2. mTLSを有効化する対象domain IDの取得

```txt:ドメインIDの取得
インスタンスを探す
# ibmcloud cis instances
Retrieving service instances for service 'internet-svcs' ...
OK
Name                    ID                                                                                                                     Location   State    Service Name
CIS-Enterprise-Usage1   crn:v1:bluemix:public:internet-svcs:global:a/039dbe6794084c7cb514a276dd2345da:cf4d8b04-b49e-4bf0-a99d-9303bba2cd90::   global     active   internet-svcs

コマンドごとにIDを指定しなくて済むように、default instanceを設定しておく
# ibmcloud cis instance-set CIS-Enterprise-Usage1
Setting context service instance to 'CIS-Enterprise-Usage1' ...
OK
Context service instance set succeeded.

# ibmcloud cis domains
Listing domains for service instance 'CIS-Enterprise-Usage1' ...
OK
ID                                 Name                 Status   Paused
3476b3464d25f1******************   certest.tk           active   false
```

##2-3. Caseを起票してmTLSを利用するための資格を割り当ててもらう

```txt:Case起票の文例
Please assign the entitlement for mTLS to my following CIS domain.

InstanceID: crn:v1:bluemix:public:internet-svcs:global:a/039dbe6794084c7cb514a276dd2345da:cf4d8b04-b49e-4bf0-a99d-9303bba2cd90::
DomainID: 3476b3464d25f1******************
```

##2-4. mTLS機能を有効化する

[ibmcloud cis access-enable](https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-cli-plugin-cis-cli&locale=en#enable-mtls)を利用。

```txt:mTLS機能の有効化
有効化コマンド
# ibmcloud cis access-enable
Mutual TLS is not enabled. Do you want to enable Mutual TLS? [y/N]> y
OK

Status       MTLS enabled
Created At   2020-04-10 06:49:01 +0000 UTC

確認（有効化コマンドと同じ）
# ibmcloud cis access-enable
OK

Status       MTLS enabled
Created At   2020-04-10 06:49:01 +0000 UTC
```

#3. CA証明書、クライアント証明書の作成

[ここ](https://developers.cloudflare.com/access/service-auth/mtls-testing/)が参考になるので、この通りにやってみる。

##3-1. cfsslおよびcfssljsonの導入
[ここ](https://pkg.cfssl.org/)にパッケージがあるので、導入。

```txt:cfsslおよびcfssljsonの導入
# curl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -o /usr/local/bin/cfssl
# curl https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 -o /usr/local/bin/cfssljson
# chmod +x /usr/local/bin/cfssl
# chmod +x /usr/local/bin/cfssljson
```

##3-2. クライアント証明書用のRoot CAの作成

```json:ca-csr.json
{
  "CN": "Access Testing CA",
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C": "US",
      "L": "Austin",
      "O": "Access Testing",
      "OU": "TX",
      "ST": "Texas"
    }
  ]
}
```
```json:ca-config.json
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "server": {
        "usages": ["signing", "key encipherment", "server auth"],
        "expiry": "8760h"
      },
      "client": {
        "usages": ["signing","key encipherment","client auth"],
        "expiry": "8760h"
      }
    }
  }
}
```

```txt:CA証明書およびCA秘密鍵の作成
# cfssl gencert -initca ca-csr.json | cfssljson -bare ca
2020/04/14 14:17:44 [INFO] generating a new CA key and certificate from CSR
2020/04/14 14:17:44 [INFO] generate received request
2020/04/14 14:17:44 [INFO] received CSR
2020/04/14 14:17:44 [INFO] generating key: rsa-4096
2020/04/14 14:17:50 [INFO] encoded CSR
2020/04/14 14:17:50 [INFO] signed certificate with serial number 627775792215544866389038048136817977289645998627

ca.pemがCA証明書。ca-key.pemがCA秘密鍵。
# ls -l
合計 20
-rw-r--r--. 1 root root  330  4月 14 14:04 ca-config.json
-rw-r--r--. 1 root root  219  4月 14 14:04 ca-csr.json
-rw-------. 1 root root 3243  4月 14 14:17 ca-key.pem
-rw-r--r--. 1 root root 1708  4月 14 14:17 ca.csr
-rw-r--r--. 1 root root 2082  4月 14 14:17 ca.pem
```

##3-3. クライアント証明書の作成

```json:client-csr.json
{
  "CN": "James Royal",
  "hosts": [""],
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C": "US",
      "L": "Austin",
      "O": "Access",
      "OU": "Access Admins",
      "ST": "Texas"
    }
  ]
}
```

```txt:クライアント証明書の作成
# cfssl gencert -ca=./ca.pem -ca-key=./ca-key.pem  -config=./ca-config.json -profile=client client-csr.json | cfssljson -bare client
2020/04/14 14:30:35 [INFO] generate received request
2020/04/14 14:30:35 [INFO] received CSR
2020/04/14 14:30:35 [INFO] generating key: rsa-4096
2020/04/14 14:30:37 [INFO] encoded CSR
2020/04/14 14:30:37 [INFO] signed certificate with serial number 634572527291617866038895563282136013011215666520
2020/04/14 14:30:37 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").

# ls -l
合計 36
-rw-r--r--. 1 root root  330  4月 14 14:04 ca-config.json
-rw-r--r--. 1 root root  219  4月 14 14:04 ca-csr.json
-rw-------. 1 root root 3243  4月 14 14:17 ca-key.pem
-rw-r--r--. 1 root root 1708  4月 14 14:17 ca.csr
-rw-r--r--. 1 root root 2082  4月 14 14:17 ca.pem
-rw-r--r--. 1 root root  233  4月 14 14:29 client-csr.json
-rw-------. 1 root root 3243  4月 14 14:30 client-key.pem
-rw-r--r--. 1 root root 1744  4月 14 14:30 client.csr
-rw-r--r--. 1 root root 2118  4月 14 14:30 client.pem
```



#4. mTLSを構成してクライアント証明書なしでのアクセスを禁止する

##4-1. Cloudflare AccessにCA証明書をアップロード
[ibmcloud cis access-certificate-create](https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-cli-plugin-cis-cli&locale=en#create-access-certificate)

なお、コマンド中で`--associated-hostnames`の項は設定しないと動かないので注意（一度はまりました）。

```txt:CA証明書のアップロード
# ibmcloud cis access-certificate-create 3476b3464d25f1****************** --name CA_YASUDA --ca-cert-file ./ca.pem --associated-hostnames www.certest.tk
Creating access certificate for domain '3476b3464d25f1******************' ...
OK

ID                     837de934-5edc-438d-b7bc-b77980b55979
Name                   CA_YASUDA
Fingerprint            MD5 Fingerprint=37:C5:53:A6:81:B5:7D:21:76:94:87:3E:63:A7:54:5F
Associated Hostnames   www.certest.tk
Created At             2020-04-14 05:34:13 +0000 UTC
Updated At             2020-04-14 05:34:13 +0000 UTC
Expires On             2025-04-13 05:13:00 +0000 UTC
```

ここまでは、`https://www.certest.tk`に普通にアクセスできる。

##4-2. Cloudflare Accessでアクセス制御されるアプリ名を定義
[ibmcloud cis access-app-create](https://cloud.ibm.com/docs/cis?topic=cis-cli-plugin-cis-cli&locale=en#access-app-create)

- `--domain`指定時にドメイン名だけでなくPATHも一緒に指定が可能。PATHを指定しない場合は、ドメイン全体が対象となる。[参考](https://developers.cloudflare.com/access/setting-up-access/configuring-access-policies/)

```
# ibmcloud cis access-app-create 3476b3464d25f1****************** --name sampleapp1 --domain www.certest.tk
Creating access application for domain '3476b3464d25f1******************' ...
OK

ID                 9958befb-a0a7-4923-8c18-fa0783210b77
Name               sampleapp1
Domain/Path        www.certest.tk
Session Duration   24h
Created At         2020-04-14 19:29:37 +0000 UTC
Updated At         2020-04-14 19:29:37 +0000 UTC
Aud                79d720b7ec9fd37fc45926b59444fe62baa1033c77b4da2fdf99ffc9d356f9a8
```

この状態で該当ドメインにブラウザでアクセスすると、以下のように表示されるようになる。
<img width="1280" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/77d2a8bc-ab8b-c7b9-1e3d-333eeab1d571.png">


##4-3. Cloudflare Accessでアクセス制御されるアプリでクライアント証明書が必要であるように定義
[ibmcloud cis access-policy-create](https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-cli-plugin-cis-cli&locale=en#create-access-policy)

```
# ibmcloud cis access-policy-create 3476b3464d25f1****************** 9958befb-a0a7-4923-8c18-fa0783210b77 --name sampleapp1policy --decision non_identity --include certificate
Creating access policy for access application '9958befb-a0a7-4923-8c18-fa0783210b77' of domain '3476b3464d25f1******************' ...
OK

ID           be34f795-71ec-4f65-becc-bec980414f96
Name         sampleapp1policy
Decision     non_identity
Include      {"certificate": {}}
Created At   2020-04-14 19:31:23 +0000 UTC
Updated At   2020-04-14 19:31:23 +0000 UTC
```

#5. テスト

##5-1. クライアント証明書なしでcurlでアクセス

```shell-session:Client証明書が必須になったため、403エラーが返ってくる
# curl -svo /dev/null  https://www.certest.tk
* About to connect() to www.certest.tk port 443 (#0)
*   Trying 104.18.18.234...
* Connected to www.certest.tk (104.18.18.234) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* NSS: client certificate not found (nickname not specified)
* SSL connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* 	subject: CN=certest.tk,O="Cloudflare, Inc.",L=San Francisco,ST=CA,C=US
* 	start date: Mar 17 00:00:00 2020 GMT
* 	expire date: Oct 09 12:00:00 2020 GMT
* 	common name: certest.tk
* 	issuer: CN=CloudFlare Inc ECC CA-2,O="CloudFlare, Inc.",L=San Francisco,ST=CA,C=US
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.certest.tk
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Date: Tue, 14 Apr 2020 19:32:44 GMT
< Content-Type: text/html
< Transfer-Encoding: chunked
< Connection: keep-alive
< Set-Cookie: __cfduid=xxxxxxxx; expires=Thu, 14-May-20 19:32:44 GMT; path=/; domain=.certest.tk; HttpOnly; SameSite=Lax
< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< Server: cloudflare
< CF-RAY: 583fdac11b7def96-NRT
<
{ [data not shown]
* Connection #0 to host www.certest.tk left intact
```

##5-2. クライアント証明書なしでブラウザでアクセス

こちらも当然アクセスに失敗する（Status codeは403）
<img width="1279" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/acc888b7-939a-9859-3605-e5be997f4626.png">

##5-3. クライアント証明書ありでcurlでアクセス

こちらは期待通り成功

```shell-session:クライアント証明書およびクライアント鍵を指定
# curl -svo /dev/null --cert ./client.pem --key ./client-key.pem https://www.certest.tk
* About to connect() to www.certest.tk port 443 (#0)
*   Trying 104.18.19.234...
* Connected to www.certest.tk (104.18.19.234) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* NSS: client certificate from file
* 	subject: CN=James Royal,OU=Access Admins,O=Access,L=Austin,ST=Texas,C=US
* 	start date: Apr 14 05:26:00 2020 GMT
* 	expire date: Apr 14 05:26:00 2021 GMT
* 	common name: James Royal
* 	issuer: CN=Access Testing CA,OU=TX,O=Access Testing,L=Austin,ST=Texas,C=US
* SSL connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* 	subject: CN=certest.tk,O="Cloudflare, Inc.",L=San Francisco,ST=CA,C=US
* 	start date: Mar 17 00:00:00 2020 GMT
* 	expire date: Oct 09 12:00:00 2020 GMT
* 	common name: certest.tk
* 	issuer: CN=CloudFlare Inc ECC CA-2,O="CloudFlare, Inc.",L=San Francisco,ST=CA,C=US
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.certest.tk
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 14 Apr 2020 19:33:22 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Set-Cookie: __cfduid=xxxxxxxx; expires=Thu, 14-May-20 19:33:21 GMT; path=/; domain=.certest.tk; HttpOnly; SameSite=Lax
< Set-Cookie: CF_Authorization=xxxxxxxx; Expires=Wed, 15 Apr 2020 19:33:21 GMT; Path=/; Secure; HttpOnly
< Last-Modified: Fri, 11 Oct 2019 11:35:18 GMT
< Accept-Ranges: bytes
< CF-Cache-Status: DYNAMIC
< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< Server: cloudflare
< CF-RAY: 583fdbabd8edf8f3-NRT
<
{ [data not shown]
* Connection #0 to host www.certest.tk left intact
```