此文章因原图片失效过多,以及cloudflare相关页面修改,于2023/05/11 重写。
CloudFlare在2020/10/1新推出了一个名为API Shield的功能,配置详情与使用介绍见这里。此处说的就是客户端证书校验的功能(MTLS)。

大致过程为【生成证书=>选择开启校验的host=>建立防火墙规则=>客户端安装证书】。在上面提到的两篇文章也有具体使用介绍。不过还是记录下今日测试的使用过程。

生成证书

  1. 打开 域名->SSL/TLS->客户端证书
    点击创建证书

  2. 创建客户端证书
    创建 csr 直接选择cloudflare生成,或者 https://myssl.com/csr_create.html 在线生成,或者手动使用 Openssl 进行生成。此处如果自己生成的话,存留密钥。

  3. 保存客户端pem证书与密钥
    保存pem格式的证书与给出的密钥(如果上一步选择cloudflare生成)

  4. 开启mtls验证
    此处也就是相当于nginx的ssl_verify_client optional; 当再次访问该域名时,服务端就会发送Certificate Request以让客户端选择证书(当客户端安装了对应的证书),但此时无论验证证书与否都可以正常访问。还需要定义一个防火墙规则来阻止没有通过证书验证的请求。

建立防火墙规则

在 /security/waf/custom-rules 进行添加规则

因cloudflare规则本身比较灵活,所以可以实现 为某域名下的某URL(比如后台管理)来开启证书校验等等。

主要生效的规则就是

not cf.tls_client_auth.cert_verified

客户端安装证书

此时我们打开 刚才的域名 就会发现 Access denied。因为此时防火墙规则已经生效,需要客户端安装对应证书才可以正常访问。例如Chrome支持的个人信息交换证书类型是 PKCS#12。所以此处需要拿到domain.key和domain.pem进行组合。

  1. 可以通过openssl进行转换:

    过程中会让输入export password。根据个人喜好设定。如果设定的话,则在导入客户端时也需要输入该password

openssl pkcs12 -export -out xx.p12 -in xx.pem -inkey xx.key
  1. 通过在线工具转换

    https://myssl.com/cert_convert.html

    原格式: PEM 目标格式: PKCS12
    完成之后就可以在客户端进行导入了,当安装客户端证书以后再访问相关的域名浏览器就会提示选择证书。选择刚导入的证书后即可正常访问。

此处有扫盲:

  • .DER .CER,文件是二进制格式,只保存证书,不保存私钥。
  • .PEM,一般是文本格式,可保存证书,可保存私钥。
  • .CRT,可以是二进制格式,可以是文本格式,与 .DER 格式相同,不保存私钥。
  • .PFX .P12,二进制格式,同时包含证书和私钥,一般有密码保护。
  • .JKS,二进制格式,同时包含证书和私钥,一般有密码保护。

其他

此功能名为API Shield。说明该功能主要为API设计,浏览器访问不属于考虑范畴,所以并未在创建时直接提供PKCS#12的类型。给出的默认案例是python:

import requests
import json
from datetime import datetime

def readSensor():

    # Takes a reading from a temperature sensor and store it to temp_measurement 

    dateTimeObj = datetime.now()
    timestampStr = dateTimeObj.strftime(%Y-%m-%dT%H:%M:%SZ)

    measurement = {'temperature':str(36.5),'time':timestampStr}
    return measurement

def main():

    print("Cloudflare API Shield [IoT device demonstration]")

    temperature = readSensor()
    payload = json.dumps(temperature)
    
    url = 'https://shield.upinatoms.com/temps'
    json_headers = {'Content-Type': 'application/json'}
    cert_file = ('/etc/ssl/certs/sensor.pem', '/etc/ssl/private/sensor-key.pem')
    
    r = requests.post(url, headers = json_headers, data = payload, cert = cert_file)
    
    print("Request body: ", r.request.body)
    print("Response status code: %d" % r.status_code)