Skip to content

Commit

Permalink
v2.1 Release
Browse files Browse the repository at this point in the history
可修改域名证书配置和用户信息
支持 Docker 部署
Docker 容器自动配置定时任务,每天 01:00 执行更新,日志自动记录在 cdncert.log
  • Loading branch information
0xJacky committed Aug 28, 2021
1 parent 3f6eaa6 commit c414b2d
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ cert/
venv
.DS_Store
config.ini
cdncert.log
9 changes: 5 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
FROM python:latest
VOLUME ['/app', '/cert']
WORKDIR /app

RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
COPY ./sources.list /etc/apt/sources.list
RUN apt update -y && apt install cron -y
WORKDIR /app
COPY ./requirements.txt ./requirements.txt
RUN cd /app && pip install -r requirements.txt
ENTRYPOINT ["python3", "cron.py"]
83 changes: 62 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# CDN Cert
自动将 Let's encrypt,或其他网站证书推送到阿里云 CDN

## v2.1 Beta 更新日志
更新于 2021 年 8 月 27
## v2.1 更新日志
更新于 2021 年 8 月 28
1. 修复 Python3.9 下邮件发送问题
2. 新增自定义证书路径配置,从上个版本更新的用户请在 `cert.db` 中,
`domain` 表添加 `cert_path(VARCHAR(255),is_nullable:YES)`
`private_key_path(VARCHAR(255),is_nullable:YES)`
3. 使用 configparse 管理配置文件,更新时请将 `config-template.ini` 复制一份改名为 `config.ini` 并重新配置
4. 支持 Docker 部署 (TODO)
4. 可修改域名证书配置和用户信息
5. 支持 Docker 部署
6. Docker 容器自动配置定时任务,每天 01:00 执行更新,日志自动记录在 cdncert.log

## v2 更新日志
更新于 2019 年 7 月 8 日
Expand All @@ -27,25 +29,54 @@

使用 SQLite3 做为数据库,并支持阿里云邮件推送服务,如有更新可以将推送结果发送到您的邮箱。

## Docker 部署

1. 准备

```
git clone https://github.com/0xJacky/cdn_cert.git
docker build -t cdn_cert .
```

2. 配置将 `config-template.ini` 复制一份并命名为 `config.ini`

3. 运行 Docker

```
docker run -dit -v ${cdn_cert项目的绝对路径}:/app \
-v ${证书文件夹的绝对路径}:/cert \
--name=cdn_cert -e "TZ=Asia/Shanghai" \
cdn_cert /bin/bash
```

4. 修改配置后使用 `docker restart cdn_cert` 重启容器。

5. 进入容器配置域名及用户信息

```
docker exec -it <image_id> /bin/bash
```

6. 注意映射目录后进入 docker 内配置自定义证书的路径,此路径是容器内的绝对路径。

## 手动配置环境

1. 准备
```
git clone https://github.com/0xJacky/cdn_cert.git
pip3 install -r requirements.txt
```
2. 配置

`config-template.ini` 复制一份并命名为 `config-template.ini`
2.`config-template.ini` 复制一份并命名为 `config.ini`

#### config.ini 配置说明
## config.ini 配置说明

| 配置项 | 默认值 | 说明 |
| -------------------------------- | --------------------- | ------------------------------------------------------------ |
| database.Path | cert.db | 指定数据库存储的地址,相对路径 |
| letencrypt.Path | /cert/ssl | Let's encrypt 证书目录 |
| letencrypt.Path | /cert | Let's encrypt 证书目录,使用 Docker 模式时将证书文件夹的绝对路径映射到 /cert 即可,可以不修改此项 |
| letencrypt.ServerCertificateName | fullchain.cer | 如果您使用的是 Let's encrypt 官方的 certbot 则无需修改此项 |
| letencrypt.PrivateKeyName | {{ domain_name }}.key | PrivkeyName 提供变量 {{ domain_name }}<br />例如,使用 acme.sh 管理证书的用户,生成的私钥名称与域名相同,则应该设置为 PrivkeyName = {{ domain_name }}.key |
| mail.Host | smtpdm.aliyun.com | # 邮件反馈设置 - 阿里云邮件推送服务 |
| mail.Host | smtpdm.aliyun.com | 邮件反馈设置 - 阿里云邮件推送服务 |
| mail.Port | 465 | 发信端口,除 80 端口外默认使用 SSL |
| mail.UserName | - | 发件人地址,通过控制台创建的发件人地址 |
| mail.PassWord | - | 发件人密码,通过控制台创建的发件人密码 |
Expand Down Expand Up @@ -77,6 +108,7 @@ pip3 install -r requirements.txt
-v, --verbosity increase output verbosity
```

2. 添加用户信息 `-a user`

![image][image-1]
Expand All @@ -87,38 +119,47 @@ pip3 install -r requirements.txt

![image][image-3]

4. 删除用户 `-d user`
4. 编辑用户 `-e user`

5. 编辑域名 `-e domain`

6. 删除用户 `-d user`

![image][image-4]

![image][image-5]

5. 删除域名 `-d domain`
7. 删除域名 `-d domain`

![image][image-6]

6. 列出所有域名/用户 `-ls users/domains`
8. 列出所有域名/用户 `-ls users/domains`

![image][image-7]

![image][image-8]

7. 开发模式 `-v`
8. 强制更新 `-f`
9. 仅更新单个域名 `-f -o {doamain}`
10. 推送成功的邮件模板

![image][image-9]

11. 定时配置(Docker 无需配置)

9. 开发模式 `-v`

10. 强制更新 `-f`

11. 仅更新单个域名 `-f -o {doamain}`

12. 推送成功的邮件模板

![image][image-9]

13. 定时配置(Docker 无需配置)
```
crontab -e
# 每天 3:30 执行
30 3 * * * python3 /home/cdn_cert/cdncert.py
30 3 * * * python3 /path/to/cdncert.py
```

### LICENSE 版权声明
Copyright © 2017 - 2019 0xJacky
Copyright © 2017 - 2021 0xJacky

The program is distributed under the terms of the GNU Affero General Public License.

Expand Down
2 changes: 1 addition & 1 deletion cdncert.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
elif args.edit:
if args.edit == 'user':
pass
# Core.update_user()
Core.update_user()
elif args.edit == 'domain':
Core.update_domain()
elif args.delete:
Expand Down
2 changes: 1 addition & 1 deletion config-template.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Path=cert.db

[letencrypt]
Path=/cert/ssl
Path=/cert
ServerCertificateName=fullchain.cer
PrivateKeyName = {{ domain_name }}.key

Expand Down
33 changes: 27 additions & 6 deletions core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import os
import hashlib
import json

import settings
from logger import log
from pathlib import Path
from aliyunsdkcore.client import AcsClient
from aliyunsdkcdn.request.v20180510.SetDomainServerCertificateRequest import SetDomainServerCertificateRequest
Expand Down Expand Up @@ -33,7 +35,10 @@ def do(self, force=False, only=''):
domains = db.get_all_domain()
for domain in domains:
queue += (domain.domain,)
self.push(force=force, queue=queue)
try:
self.push(force=force, queue=queue)
except Exception as e:
log.error(e)

# 获取文件 md5
# 返回: 字符串
Expand All @@ -53,6 +58,22 @@ def add_user():
access_key_secret = input('Please input the Access Key Secret\n')
db.add_user(name, access_key_id, access_key_secret)

def update_user(self):
self.get_all_user()

name = input('Please input a user name from the table above.\n')

if db.has_user(name) is False:
exit('\033[1;31mUser %s not exists.\033[0m' % name)

access_key_id = input('Please input the Access Key ID, '
'leave black if you do not want to change\n')

access_key_secret = input('Please input the Access Key Secret, '
'leave black if you do not want to change\n')

db.update_user(name, access_key_id, access_key_secret)

def add_domain(self):
domain = input('Please input your domain here\n')

Expand Down Expand Up @@ -169,7 +190,7 @@ def push(self, force, queue=()):
settings.ServerCertificateName) # 安全证书路径

PrivateKeyPath = os.path.join(settings.LiveCert, domain,
settings.PrivkeyName.replace('{{ domain_name }}', domain)) # 私钥路径
settings.PrivateKeyName.replace('{{ domain_name }}', domain)) # 私钥路径
info = db.get_domain(domain)
user = db.get_user(info.user)
store_md5 = info.md5
Expand Down Expand Up @@ -199,16 +220,16 @@ def push(self, force, queue=()):
response = client.do_action_with_exception(request)
RequestId = json.loads(response.decode('utf-8'))['RequestId']
result = "Push successfully\nRequestId: " + str(RequestId)

db.update_domain(domain, current_md5)
except Exception as e:
result = e.get_error_code() if hasattr(e, 'get_error_code') else e
finally:
db.update_domain(domain, current_md5)
msg[domain] = result
if msg:
content = ""
for k, v in msg.items():
content += 'Domain: ' + k + '\nResult: ' + str(v) + "\n\n"
print(content)
log.info(content)
mail.send('[CDN Cert] 证书推送结果', content)
else:
print("Already up-to-date.")
log.info("Already up-to-date.")
17 changes: 17 additions & 0 deletions cron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import time

import schedule
from core import Core
from logger import log

Time = '01:00'

log.info('cdn_cert cron job started, will check at %s every day', Time)

core = Core()

schedule.every().day.at(Time).do(core.do)

while True:
schedule.run_pending()
time.sleep(100)
6 changes: 0 additions & 6 deletions cron.sh

This file was deleted.

17 changes: 15 additions & 2 deletions database.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,29 @@ def get_user(self, name):
def get_all_user(self):
return self.session.query(User).all()

def update_user(self, name, access_key_id=None, access_key_secret=None):
try:
user = self.session.query(User).filter(User.name == name).first()
if access_key_id:
user.access_key_id = access_key_id
if access_key_secret:
user.access_key_secret = access_key_secret

self.session.commit()
print("\033[1;32mUser %s updated successfully\033[0m" % name)
except Exception as e:
print(e)

def update_domain(self, domain, md5, user=None, cert_path=None, private_key_path=None):
try:
domain = self.session.query(Domain).filter(Domain.domain == domain).first()
domain.md5 = md5
if user is not None:
domain.user = user
if cert_path is not None:
domain.cert_path=cert_path
domain.cert_path = cert_path
if private_key_path is not None:
domain.private_key_path=private_key_path
domain.private_key_path = private_key_path
self.session.commit()
print("\033[1;32mDomain %s updated successfully\033[0m" % domain.domain)
except Exception as e:
Expand Down
19 changes: 19 additions & 0 deletions logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sys
import logging

log_format = '[%(asctime)s] %(levelname)s %(message)s'
datetime_format = '%Y-%m-%d %H:%M:%S'

logging.basicConfig(filename="cdncert.log",
filemode="w",
format=log_format,
datefmt=datetime_format,
level=logging.INFO)

console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter(fmt=log_format, datefmt=datetime_format))

logging.getLogger().addHandler(console)

log = logging.getLogger(__name__)
9 changes: 5 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
aliyun_python_sdk_cdn==3.6.1
aliyun_python_sdk_core==2.13.30
prettytable==2.0.0
SQLAlchemy==1.3.23
aliyun_python_sdk_cdn==3.6.4
aliyun_python_sdk_core==2.13.35
prettytable==2.2.0
schedule==1.1.0
SQLAlchemy==1.4.23
18 changes: 7 additions & 11 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,23 @@
# -*- coding:utf-8 -*-
import os
import configparser

config = configparser.ConfigParser()
config.read('config.ini')
# 数据存储地址
ABS_PATH = os.path.split(os.path.realpath(__file__))[0]
DB_PATH = os.path.join(ABS_PATH, config['database']['Path'])
## 配置开始

# 配置开始
# Let's encrypt 证书目录,一般情况下无需修改
# LetsencryptPath = os.path.join('/usr/local/nginx/conf')
# LiveCert = os.path.join(LetsencryptPath, 'ssl')
# LetsencryptPath = os.path.join('/etc/letsencrypt')
# LiveCert = os.path.join(LetsencryptPath, 'live')
LiveCert = os.path.join(config['letencrypt']['Path'])

# 安全证书与私钥名称设置
# 警告:如果您使用的是 Let's encrypt 官方的 certbot 则无需修改此项目
# ServerCertificateName = 'fullchain.pem'
# PrivkeyName = 'privkey.pem'
# PrivkeyName 提供变量 {{ domain_name }}
# 例如,使用 acme.sh 管理证书的用户,生成的私钥名称与域名相同,则应该设置为 PrivkeyName = '{{ domain_name }}.key'
# 警告:如果您使用的是 Let's encrypt 官方的 certbot 则无需修改此项
ServerCertificateName = config['letencrypt']['ServerCertificateName']
PrivkeyName = config['letencrypt']['PrivateKeyName']
# PrivateKeyName 提供变量 {{ domain_name }}
# 例如,使用 acme.sh 管理证书的用户,生成的私钥名称与域名相同,则应该设置为 PrivateKeyName = '{{ domain_name }}.key'
PrivateKeyName = config['letencrypt']['PrivateKeyName']

mail = config['mail']
# 邮件反馈设置 - 阿里云邮件推送服务
Expand Down
4 changes: 0 additions & 4 deletions sources.list

This file was deleted.

4 changes: 2 additions & 2 deletions start-dev-docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ docker build -t cdn_cert .

docker run -dit -v /Users/Jacky/Documents/Projects/cdn_cert:/app \
-v /Users/Jacky/Documents/Projects/cdn_cert/cert:/cert \
--name=cdn_cert \
cdn_cert /bin/bash
--name=cdn_cert -e "TZ=Asia/Shanghai" \
cdn_cert /bin/bash

0 comments on commit c414b2d

Please sign in to comment.