Skip to content

Commit

Permalink
v2.0 Release
Browse files Browse the repository at this point in the history
  • Loading branch information
0xJacky committed Jul 8, 2019
1 parent 7eeeb8e commit 0490c3d
Show file tree
Hide file tree
Showing 18 changed files with 416 additions and 278 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
settings.py
cert.db
test.py
cert/
.idea
126 changes: 51 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
# CDN Cert
自动将 Let's encrypt 续签后的证书推送到阿里云 CDN

## v2 更新日志
更新于 2019 年 7 月 8 日

1. 支持多 RAM 账号。

即当您有多个网站存在于同一个服务器上,且多个网站部署CDN时使用的不是同一阿里云账号时,
CDN Cert 可以向多个阿里云账号推送续签后的证书。

2. **完全迁移至 Python 3.7**

### 工作原理
定期对比存储在本机的证书与上一次推送成功的证书的 MD5
定期[1]对比存储在本机的证书与上一次推送成功的证书的 MD5

如有差异则将新证书推送到 CDN

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

### 使用方法
## 配置环境
1. 准备
```
git clone https://github.com/0xJacky/cdn_cert.git
pip install aliyun-python-sdk-cdn sqlalchemy
git clone https://github.com/0xJacky/cdn_cert.git "CDN Cert"
pip3 install -r requirements.txt
```
2. 配置

`settings-template.py` 复制一份并命名为 `settings.py`

打开 `settings.py` 进行配置

运行 `python update.py -a` 添加需要自动续期的域名到数据库
打开 `settings.py` 配置 Let's encrypt 证书目录,邮件发送账户等,在 `settings-simple.py`
中,我提供了基于 certbot 和 acme.sh 管理证书的配置模板,请根据需求进行注释或解除注释

##### 2018.6.7 更新日志(important!)

Expand All @@ -32,81 +41,48 @@ pip install aliyun-python-sdk-cdn sqlalchemy

例如,使用 acme.sh 管理证书的用户,生成的私钥名称与域名相同,则应该设置为 `PrivkeyName = '{{ domain_name }}.key'`


3. 参数

```
$python update.py -h
usage: update.py [-h] [-f] [-o ONLY] [-a] [-d] [-ls]
CDN_Cert - Automatically push the new certificates to CDN
optional arguments:
-h, --help show this help message and exit
-f, --force force update
-o ONLY, --only ONLY update only, use it after -f/-force
-a, --add add domain name to database
-d, --delete remove domain name from database
-ls, --list print all the domain names from database
e.g.
$python update.py
Domain: jackyu.cn
Result: Push success
RequestId: D40F6BC4-6418-43B1-8E31-8BBB548AB3E2
Domain: beta.uozi.org
Result: Push success
RequestId: 9BF6A271-38CC-45F4-8DA0-48022DB742A3
邮件发送成功!
$python update.py -f
Domain: jackyu.cn
Result: Push success
RequestId: D40F6BC4-6418-43B1-8E31-8BBB548AB3E2
Domain: beta.uozi.org
Result: Push success
RequestId: 9BF6A271-38CC-45F4-8DA0-48022DB742A3
邮件发送成功!
$python update.py -f -o ipsw.pw
Domain: ipsw.pw
Result: Push success
RequestId: AE15AC6F-5D71-4732-A6A6-02863057B202
$python update.py -ls
CDN Cert -- Domain List
-----------------------
apt.uozi.org
jackyu.cn
ipsw.pw
-----------------------
$python update.py -a
Plase input the domain name, use ',' to split.
ojbk.me
Execute successfully.
$python update.py -d
Plase input a domain name to delete.
beta.uozi.org
Execute successfully.
```
4. 定时配置
## 使用方法

1. 用法 `-h/ --help`
```
python3 cdncert.py -h
usage: cdncert.py [-h] [-f] [-o ONLY] [-a {domain,user}] [-d {domain,user}]
[-ls {domains,users}] [-v]
CDN Cert - Automatically push the new certificates to CDN
optional arguments:
-h, --help show this help message and exit
-f, --force force update
-o ONLY, --only ONLY update only, use it after -f/--force
-a {domain,user}, --add {domain,user}
add [domain/user] to database
-d {domain,user}, --delete {domain,user}
delete [domain/user] from database
-ls {domains,users}, --list {domains,users}
print all [domains/users] from database
-v, --verbosity increase output verbosity
```
2. 添加用户信息 `-a user`
3. 添加域名信息 `-a domain`
4. 删除用户 `-d user`
5. 删除域名 `-d domain`
6. 列出所有域名/用户 `-ls users/domains`
7. 开发模式 `-v`
8. 强制更新 `-f`
9. 推送成功的邮件模板
9. 定时配置
```
crontab -e
# 每天 3:30 执行
30 3 * * * python /home/cdn_cert/update.py
```
### LICENSE 版权声明
Copyright © 2017 0xJacky
Copyright © 2017 - 2019 0xJacky
The program is distributed under the terms of the GNU Affero General Public License.
Expand Down
42 changes: 42 additions & 0 deletions cdncert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/python
# -*- coding:utf-8 -*-
#
# Copyright (c) 2017 0xJacky <[email protected]>
#
# !请先使用 pip install aliyun-python-sdk-cdn 安装 sdk!

import argparse
from core import Core
from database import Database

parser = argparse.ArgumentParser(description='CDN Cert - Automatically push the new certificates to CDN')
parser.add_argument('-f', '--force', action="store_true", help='force update')
parser.add_argument('-o', '--only', action="store", default=None, help='update only, use it after -f/--force')
parser.add_argument('-a', '--add', action="store", choices=['domain', 'user'], help='add [domain/user] to database')
parser.add_argument('-d', '--delete', action="store", choices=['domain', 'user'], help='delete [domain/user] from database')
parser.add_argument('-ls', '--list', action="store", choices=['domains', 'users'], help='print all [domains/users] from database')
parser.add_argument("-v", "--verbosity", action="store_true", help="increase output verbosity")
args = parser.parse_args()

Core = Core()
db = Database(args.verbosity)

if args.force:
Core.do(force=True, only=args.only)
elif args.add:
if args.add == 'user':
Core.add_user()
elif args.add == 'domain':
Core.add_domain()
elif args.delete:
if args.delete == 'user':
Core.delete_user()
elif args.delete == 'domain':
Core.delete_domain()
elif args.list:
if args.list == 'users':
Core.get_all_user()
elif args.list == 'domains':
Core.get_all_domain()
else:
Core.do()
171 changes: 171 additions & 0 deletions core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/usr/bin/python
# -*- coding:utf-8 -*-
import datetime
import os
import hashlib
import json
import settings
from aliyunsdkcore.client import AcsClient
from aliyunsdkcdn.request.v20180510.SetDomainServerCertificateRequest import SetDomainServerCertificateRequest
from prettytable import PrettyTable
from database import Database
from mail import Mail

db = Database()
mail = Mail()


class Core:
def __init__(self):
pass

# 执行操作
def do(self, force=False, only=''):
queue = ()
if only:
if db.has_domain(only):
queue = (only,)
else:
exit('\033[1;31mNo record of this domain.\nPlease add this domain before the operation.\033[0m')

else:
domains = db.get_all_domain()
for domain in domains:
queue = (domain.domain, )
self.push(force=force, queue=queue)

# 获取文件 md5
# 返回: 字符串
@staticmethod
def md5sum(path):
file = open(path, 'rb')
return hashlib.md5(file.read()).hexdigest()

def add_user(self):
name = input('Please input the user name\n')

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

access_key_id = input('Please input the Access Key ID\n')
access_key_secret = input('Please input the Access Key Secret\n')
db.add_user(name, access_key_id, access_key_secret)

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

if db.has_domain(domain):
exit('\033[1;31mDomain %s already exists\033[0m' % domain)

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

if db.has_user(user) is not True:
exit('\033[1;31mNo record of %s.\033[0m' % user)

db.add_domain(domain, user)

@staticmethod
def get_all_domain():
print('--------------------------------')
print('Here are your domains list')
domains = db.get_all_domain()
table = PrettyTable(['Domain', 'User'])
for domain in domains:
table.add_row([domain.domain, domain.user])
print(table)
print('--------------------------------')

@staticmethod
def get_all_user():
print('--------------------------------')
print('Here are your users list')
users = db.get_all_user()
table = PrettyTable(['User', 'Access Key ID'])
for user in users:
table.add_row([user.name, user.access_key_id])
print(table)
print('--------------------------------')

def update_domain(self):
self.get_all_domain()
domain = input('Please input a domain from the table above.\n')

if db.has_domain(domain) is not True:
exit('\033[1;31mNo record of this domain.\033[0m')

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

if db.has_user(user) is not True:
exit('\033[1;31mNo record of this user.\033[0m')

db.update_domain(domain, user=user)

def delete_domain(self):
self.get_all_domain()
domain = input('Please input a domain to delete from the table above.\n')

if db.has_domain(domain) is not True:
exit('\033[1;31mNo record of this domain.\033[0m')

sure = input('Are you sure to delete this domain: [y/n]')

if sure == 'y':
db.delete_domain(domain)

def delete_user(self):
self.get_all_user()
user = input('Please input a user to delete from the table above.\n')

if db.has_user(user) is not True:
exit('\033[1;31mNo record of this user.\033[0m')

sure = input('Are you sure to delete this user: [y/n]')
if sure == 'y':
db.delete_user(user)

# 处理推送
def push(self, force, queue=()):
msg = {}
for domain in queue:
PrivateKeyPath = os.path.join(settings.LiveCert, domain,
settings.PrivkeyName.replace('{{ domain_name }}', domain)) # 私钥路径
info = db.get_domain(domain)
user = db.get_user(info.user)
store_md5 = info.md5
currect_md5 = self.md5sum(PrivateKeyPath)
if not currect_md5 == store_md5 or force is True:
try:
client = AcsClient(user.access_key_id, user.access_key_secret, 'cn-hangzhou')
ServerCertificatePath = os.path.join(settings.LiveCert, domain,
settings.ServerCertificateName) # 安全证书路径
ServerCertificate = open(ServerCertificatePath, 'r').read()
PrivateKey = open(PrivateKeyPath, 'r').read()
CertName = domain + '_' + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") # 证书名称

request = SetDomainServerCertificateRequest()
request.set_accept_format('json')
request.set_DomainName(domain)
request.set_ServerCertificateStatus("on")
request.set_CertName(CertName)
request.set_ServerCertificate(ServerCertificate)
request.set_PrivateKey(PrivateKey)
request.set_CertType("upload") # 上传证书
request.set_ForceSet("1") # 忽略证书同名检测
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, currect_md5)
except Exception as e:
result = e.get_error_code() if hasattr(e, 'get_error_code') else e
msg[domain] = result
if msg:
content = ""
for k, v in msg.items():
content += 'Domain: ' + k + '\nResult: ' + str(v) + "\n\n"
print(content)
mail.send('[CDN Cert] 证书推送结果', content)
else:
print("Already up-to-date.")
Loading

0 comments on commit 0490c3d

Please sign in to comment.