Cloudflare API Shield とは
こちらのブログが参考になります。
公式ドキュメントはこちらです。全プランで使える機能になります。
API Shield (mTLS) 有効化前
TLS ハンドシェイクの詳細です。
HOSTNAME=mtls.example.com
CFIP=$(dig +short @1.1.1.1 $HOSTNAME | head -n 1)
% openssl s_client -verify 5 -connect $CFIP:443 -servername $HOSTNAME -quiet -state
verify depth is 5
SSL_connect:before SSL initialization
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS read server hello
SSL_connect:TLSv1.3 read encrypted extensions
depth=2 C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
verify return:1
depth=1 C = US, O = "Cloudflare, Inc.", CN = Cloudflare Inc ECC CA-3
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = "Cloudflare, Inc.", CN = example.com
verify return:1
SSL_connect:SSLv3/TLS read server certificate
SSL_connect:TLSv1.3 read server certificate verify
SSL_connect:SSLv3/TLS read finished
SSL_connect:SSLv3/TLS write change cipher spec
SSL_connect:SSLv3/TLS write finished
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSLv3/TLS read server session ticket
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSLv3/TLS read server session ticket
SSL3 alert read:warning:close notify
SSL3 alert write:warning:close notify
API Shield (mTLS) 有効化後
以下のダッシュボードでホスト名を追加すると、
以下のようにクライアント証明書を要求するように TLS ハンドシェイクが変わります。
% openssl s_client -verify 5 -connect $CFIP:443 -servername $HOSTNAME -quiet -state
verify depth is 5
SSL_connect:before SSL initialization
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS read server hello
SSL_connect:TLSv1.3 read encrypted extensions
SSL_connect:SSLv3/TLS read server certificate request <-- クライアント証明書を要求
depth=2 C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
verify return:1
depth=1 C = US, O = "Cloudflare, Inc.", CN = Cloudflare Inc ECC CA-3
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = "Cloudflare, Inc.", CN = example.com
verify return:1
SSL_connect:SSLv3/TLS read server certificate
SSL_connect:TLSv1.3 read server certificate verify
SSL_connect:SSLv3/TLS read finished
SSL_connect:SSLv3/TLS write change cipher spec
SSL_connect:SSLv3/TLS write client certificate
SSL_connect:SSLv3/TLS write finished
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSLv3/TLS read server session ticket
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSLv3/TLS read server session ticket
SSL3 alert read:warning:close notify
SSL3 alert write:warning:close notify
ファイアウォールルールの作成
こちらの手順に従って、作成できます。
クライアント証明書なしでcurlでアクセス
以下のようなエラーになります。
% curl -svo /dev/null https://mtls.example.com
* Trying 104.18.14.139...
* TCP_NODELAY set
* Connected to mtls.example.com (104.18.14.139) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [234 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [100 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [2327 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [115 bytes data]
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
{ [374 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
} [7 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [37 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=example.com
* start date: Aug 4 00:00:00 2021 GMT
* expire date: Aug 3 23:59:59 2022 GMT
* subjectAltName: host "mtls.example.com" matched cert's "*.example.com"
* issuer: C=US; O=Cloudflare, Inc.; CN=Cloudflare Inc ECC CA-3
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fa3ff80d600)
> GET / HTTP/2
> Host: mtls.example.com
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!
< HTTP/2 403
< date: Wed, 13 Oct 2021 17:15:12 GMT
< content-type: text/plain; charset=UTF-8
< content-length: 16
< x-frame-options: SAMEORIGIN
< referrer-policy: same-origin
< cache-control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
< expires: Thu, 01 Jan 1970 00:00:01 GMT
< expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< set-cookie: __cf_bm=OQ5zBTND0KtNG6simpoFUFbb52zxMhyll5Dnj2OHmBA-1634145312-0-AUtQQfUj4/luQhwAaUZEbEz/u4V0zMvsXqNYWqbIHf+DiLyRJVU5ya9kCpyWRzWkyRVmfJl+chiylIswSRk4PPQ=; path=/; expires=Wed, 13-Oct-21 17:45:12 GMT; domain=.example.com; HttpOnly; Secure; SameSite=None
< server: cloudflare
< cf-ray: 69da356c6ca00ad0-NRT
<
{ [16 bytes data]
* Connection #0 to host mtls.example.com left intact
* Closing connection 0
クライアント証明書なしでブラウザでアクセス
以下のようにブロックされます。
クライアント証明書の作成
ダッシュボードからクライアント証明書を作成します。
「Cloudflare Managed CA」が発行する証明書となります。
発行されたクライアント証明書と秘密鍵をローカルファイル cf_managed_client.pem
, cf_managed_client.key
に保存します。
クライアント証明書の内容確認
以下のコマンドで確認できます。
% cfssl certinfo -cert cf_managed_client.pem
{
"subject": {
"common_name": "Cloudflare",
"country": "US",
"names": [
"US",
"Cloudflare"
]
},
"issuer": {
"common_name": "Managed CA b5341236aea624bd7e2a0770ee3fb5e5",
"country": "US",
"organization": "Cloudflare, Inc.",
"organizational_unit": "www.cloudflare.com",
"locality": "San Francisco",
"province": "California",
"names": [
"US",
"California",
"San Francisco",
"Cloudflare, Inc.",
"www.cloudflare.com",
"Managed CA b5341236aea624bd7e2a0770ee3fb5e5"
]
},
"serial_number": "593413451556153623200318414325279173937034532728",
"not_before": "2021-10-13T06:05:00Z",
"not_after": "2031-10-11T06:05:00Z",
"sigalg": "SHA256WithRSA",
"authority_key_id": "B1:D9:A6:61:71:04:AB:A9:1C:1B:88:51:CC:20:1F:01:B9:88:98:50",
"subject_key_id": "D7:6F:64:16:92:F6:29:29:14:F3:FB:91:3F:BF:7A:C3:FB:20:90:78",
"pem": "-----BEGIN CERTIFICATE-----\nxxx\n-----END CERTIFICATE-----\n"
}
クライアント証明書ありでcurlでアクセス
先ほど保存したファイルを使って検証できます。
% curl -svo /dev/null --cert cf_managed_client.pem --key cf_managed_client.key https://mtls.example.com
* Trying 104.18.14.139...
* TCP_NODELAY set
* Connected to mtls.example.com (104.18.14.139) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [234 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [100 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [2327 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [116 bytes data]
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
{ [374 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
} [1059 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [37 bytes data]
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
} [264 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=example.com
* start date: Aug 4 00:00:00 2021 GMT
* expire date: Aug 3 23:59:59 2022 GMT
* subjectAltName: host "mtls.example.com" matched cert's "*.example.com"
* issuer: C=US; O=Cloudflare, Inc.; CN=Cloudflare Inc ECC CA-3
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7f9e3680d600)
> GET / HTTP/2
> Host: mtls.example.com
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!
< HTTP/2 200
< date: Wed, 13 Oct 2021 17:26:28 GMT
< content-type: text/html; charset=UTF-8
< x-powered-by: PHP/7.2.24
< x-apache-logname: -
< x-apache-username: -
< cf-cache-status: DYNAMIC
< expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< set-cookie: __cf_bm=sG5jNcimJrNvRrCI8QCJwN9hX1kvZ_VMEBbBhtM9REg-1634145988-0-AV1Z6zj22fs71zMX16Ej+lPfqo2JmBiWSlAmmTMun2yQXIfhUtfCYCRNybz2oZEKATtxDLTi4RZa8czZztjJaF4=; path=/; expires=Wed, 13-Oct-21 17:56:28 GMT; domain=.example.com; HttpOnly; Secure; SameSite=None
< server: cloudflare
< cf-ray: 69da45ec0cd234f3-NRT
<
{ [665 bytes data]
* Connection #0 to host mtls.example.com left intact
* Closing connection 0
クライアント証明書ありでブラウザでアクセス
macOS の場合、クライアント証明書をキーチェーンアクセスにインポートすることでブラウザから利用できるようになります。
まず、先ほど保存したクライアント証明書と秘密鍵を p12
形式にします。
openssl pkcs12 -export -nodes \
-in cf_managed_client.pem \
-inkey cf_managed_client.key \
-out cf_managed_client.p12 \
-passout pass:password \
-name "Cloudflare Client Certificate for API Shield"
その後、キーチェーンアクセスにインポートします。
% security -v import cf_managed_client.p12 -k ~/Library/Keychains/login.keychain-db -P "password"
import "cf_managed_client.p12" "-k" "/Users/kyouhei/Library/Keychains/login.keychain-db" "-P" "password"
1 identity imported.
% security -v find-identity ~/Library/Keychains/login.keychain-db
find-identity "/Users/kyouhei/Library/Keychains/login.keychain-db"
Policy: X.509 Basic
Matching identities
1) 90CABE912B699A9FC863B609366181022B647568 "Cloudflare" (CSSMERR_TP_NOT_TRUSTED)
1 identities found
Valid identities only
0 valid identities found
# 消したいとき
# security -v delete-identity -c Cloudflare ~/Library/Keychains/login.keychain-db
Chrome でアクセスしてみます。
open -a "Google Chrome" --args --incognito "https://mtls.example.com"
このポップアップ画面でクライアント証明書を選択すれば、アクセスできます。
参考:Safari 11.0 より古い場合
こちらにある対応が必要になります。
Why is Safari not detecting my Trusted Endpoints certificate?
security set-identity-preference -c Cloudflare -s mtls.example.com
security get-identity-preference -s mtls.example.com