# SSL证书申请

申请步骤与阿里云申请证书一致

考虑到大部分用户采用的是nginx，其他用户可自行按照如下思路研究

首次使用申请ssl证书，推荐使用`手动验证`，流程跑通熟练之后，再使用`一键部署`功能

申请步骤：

## 1、填写域名

可以是一个域名，也可以是多个域名，每行一个

例如：

```bash
baidu.com
www.baidu.com
```

## 2、验证域名

2.1、手动文件验证

- 下载`验证文件`，到服务器
- 浏览器打开`验证URL`，如果没有报错，有任何响应结果，就是部署正确
- 点击`验证` 按钮，进行域名验证；如果验证失败，请自行检查部署是否正确
 
> 说明：如果是多域名，需要将所有的待验证文件都放到服务器

2.2、自动部署验证文件

- 填写`服务器账号和密码`
- 填写`服务器目录`，如果目录不存在，需要自行创建
- 点击`一键部署`，验证文件就会自动拷贝到指定的服务器目录
- 点击`验证` 按钮，进行域名验证；如果验证失败，请自行检查部署是否正确


## 3、下载证书

3.1、手动部署

- 点击zip 下载SSL证书，自行解压
- 上传到服务器对应目录下

3.2、自动部署证书文件

- 同样配置好这几个参数：`服务器地址`、`私钥部署路径`、`公钥部署路径`、`重启命令`
- 点击`一键部署`，证书文件就会自动拷贝到指定的服务器目录，并重启nginx服务

## 4、远程部署

通过调用远程api接口，将证书数据发送到远程接口，可自定义实现的部署逻辑

申请证书api推送格式

```json
{
  "domains": [
      "www.baidu.com",
      "zhidao.baidu.com"
  ],
  "ssl_certificate":"-----BEGIN CERTIFICATE-----\nMIIGdTCCBN2gAwI\n-----END CERTIFICATE-----",
  "ssl_certificate_key":"-----BEGIN PRIVATE KEY-----\nMIIEvH+bpTwI=\n-----END PRIVATE KEY-----",
  "start_time": "2023-01-04 14:33:39",
  "expire_time": "2023-04-04 14:33:39"
}
```


托管证书api推送格式

```json
{
  "domain": "www.baidu.com",
  "ssl_certificate":"-----BEGIN CERTIFICATE-----\nMIIGdTCCBN2gAwI\n-----END CERTIFICATE-----",
  "ssl_certificate_key":"-----BEGIN PRIVATE KEY-----\nMIIEvH+bpTwI=\n-----END PRIVATE KEY-----",
  "start_time": "2023-01-04 14:33:39",
  "expire_time": "2023-04-04 14:33:39"
}
```

参数说明

| 参数  | 类型                       | 说明 |
| -|--------------------------| - |
| domains | `array<string>` / string | 域名列表
| ssl_certificate | string                   | 证书公钥
| ssl_certificate_key | string                   | 证书私钥
| start_time | string                   | 证书生效时间
| expire_time | string                   | 证书过期时间


远程服务器实现示例

```python
# -*- coding: utf-8 -*-
"""
@File    : fake_server.py
@Date    : 2024-03-22
"""
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route(rule='/issueCertificate', methods=['POST'])
def issue_certificate():
    print(request.json)
    print(request.headers)
    # 此处可自行实现部署逻辑
    return jsonify({'result': 'ok'})


if __name__ == '__main__':
    app.run(port=8082)
```

完整示例，由群友 `@星河 ๑.`提供

```python
import os
import shutil
import subprocess
import threading
import time
from flask import Flask, request, jsonify

app = Flask(__name__)

def delayed_restart_nginx():
    """在延迟5秒后重启nginx"""
    time.sleep(5)
    try:
        subprocess.run(['docker', 'restart', 'nginx'])
        print('重启nginx完成。')
    except subprocess.CalledProcessError as e:
        print('重启nginx失败: ', e)

@app.route('/deploy-cert', methods=['POST'])
def deploy_cert():
    token = request.headers.get('token')
    if token != 'token':
        return jsonify({'error': 'Invalid token'}), 401

    # 获取请求参数
    data = request.get_json()
    # 获取域名列表
    domains = data.get('domains', [])
    # 获取证书文件
    ssl_certificate = data.get('ssl_certificate', '')
    # 获取证书密钥
    ssl_certificate_key = data.get('ssl_certificate_key', '')

    # 检测是否有值，避免无效请求
    if not domains or not ssl_certificate or not ssl_certificate_key:
        return jsonify({'error': 'Missing required fields'}), 400

    # 定义基础路径，因为本人的 nginx 是 docker 运行的，做了磁盘映射
    # nginx 的配置文件都在此目录，加上域名作为不同域名的证书目录
    # 如：/server/dockerApps/nginx/nginx/certs/simple.com/fullchain.pem
    # 如：/server/dockerApps/nginx/nginx/certs/simple.com/cert.key
    base_path = '/server/dockerApps/nginx/nginx/certs'

    # 遍历域名列表
    for domain in domains:
        # 将泛域名中的 *. 去掉
        if domain.startswith('*.'):
            domain = domain.replace('*.', '', 1)
        # 创建目录
        domain_dir = os.path.join(base_path, domain)
        if os.path.exists(domain_dir):
            shutil.rmtree(domain_dir)
        os.makedirs(domain_dir)

        # 生成证书文件
        cert_path = os.path.join(domain_dir, 'fullchain.pem')
        key_path = os.path.join(domain_dir, 'cert.key')

        with open(cert_path, 'w') as cert_file:
            cert_file.write(ssl_certificate)

        with open(key_path, 'w') as key_file:
            key_file.write(ssl_certificate_key)

        print(domain, " ==> 证书创建成功")

    # 执行重启nginx的线程
    threading.Thread(target=delayed_restart_nginx).start()

    # 返回前端结果
    return jsonify({'result': 'ok'}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8001)
```

基于golang实现的部署方案，由网友提供

- [https://github.com/Gourds/domain-admin-ssl-deploy](https://github.com/Gourds/domain-admin-ssl-deploy) [issues#121](https://github.com/mouday/domain-admin/issues/121)

## 说明

- 如果是全程采用`一键部署`方式操作，域名到期前30天会自动续期
- 申请到的证书，不会到期提醒，如需到期提醒，请将部署该证书的域名添加到`证书监控`列表
- 如果总是验证不通过，可能是验证服务器有问题，可以使用其他证书申请手段，临时处理证书申请问题
    - 阿里云：每一个自然年内，提供20个90天个人测试证书（免费版） [点击跳转](https://help.aliyun.com/zh/ssl-certificate/user-guide/purchase-an-individual-test-certificate?from=domain-admin.cn)
    - 腾讯云：申请20张免费证书 [点击跳转](https://cloud.tencent.com/document/product/400/6814?from=domain-admin.cn)
    - acme.sh 自动申请证书也可以很好的配合domain-admin的到期提醒
