feat: 给update.sh里的 dns 配上环境变量

This commit is contained in:
yexinhao 2023-02-03 20:41:28 +08:00
commit 105333dd6f
25 changed files with 1539 additions and 0 deletions

71
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,71 @@
name: Docker
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
schedule:
- cron: '40 23 * * *'
push:
branches: [ main ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
# pull_request:
# branches: [ main ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry
if: github.event_name != 'pull_request'
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

45
.github/workflows/update-cert.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Update Cert
on:
schedule:
- cron: '0 10 1 * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Prepare Environment
run: |
curl https://get.acme.sh | sh -s email=my@example.com
pip install -r requirements.txt
- name: Update Cert
env:
DP_Id: ${{ secrets.DP_Id }}
DP_Key: ${{ secrets.DP_Key }}
ACME_DNS_TYPE: ${{ secrets.ACME_DNS_TYPE }}
ACME_DOMAIN: ${{ secrets.ACME_DOMAIN }}
SECRETID: ${{ secrets.SECRETID }}
SECRETKEY: ${{ secrets.SECRETKEY }}
CDN_DOMAIN: ${{ secrets.CDN_DOMAIN }}
CERT_HOME: /home/runner/.acme.sh
ACME_HOME: /home/runner/.acme.sh
WORK_DIR: .
run: sh ./docker/update.sh
- name: Notification
uses: monlor/bark-action@v3
if: always()
with:
host: ${{ secrets.BARK_HOST}} # not required
key: ${{ secrets.BARK_KEY }} # Your secret key
title: Github Actions
body: 'Your tencent cdn certs update ${{ job.status }}'
isArchive: 1
url: 'github://github.com/${{ github.repository }}'
group: Github
icon: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
copy: ${{ steps.meta.outputs.tags }}

129
.gitignore vendored Normal file
View File

@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

5
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -0,0 +1,5 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
</profile>
</component>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/qcloud-ssl-cdn.iml" filepath="$PROJECT_DIR$/.idea/qcloud-ssl-cdn.iml" />
</modules>
</component>
</project>

12
.idea/qcloud-ssl-cdn.iml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

27
Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM python:alpine
LABEL AUTHOR="monlor"
ENV LANG="C.UTF-8"
ENV TZ="Asia/Shanghai"
ENV CERT_HOME="/data/certs"
ENV WORK_DIR="/data"
ENV ACME_HOME="/root/.acme.sh"
WORKDIR /data
COPY api /data/api
COPY main.py /data/
COPY requirements.txt /data/
COPY docker/entrypoint.sh /
COPY docker/update.sh /data
RUN apk update && apk add --no-cache curl openssl socat && \
ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && \
echo "${TZ}" > /etc/timezone && \
pip install -r requirements.txt && \
chmod +x /entrypoint.sh /data/update.sh && \
curl https://get.acme.sh | sh -s email=my@example.com
VOLUME ["/data/certs"]
ENTRYPOINT [ "/entrypoint.sh" ]

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

94
README.md Normal file
View File

@ -0,0 +1,94 @@
## 腾讯云自动SSL证书上传及替换
功能:
* 把本地的SSL证书上传到腾讯云[SSL证书](https://console.cloud.tencent.com/ssl)并记录id
* 为CDN服务更换指定id的SSL证书
* 根据网址批量预热URL
目的:
* 把利用[acme.sh](https://github.com/acmesh-official/acme.sh)申请的`Let's Encrypt`证书上传到腾讯云
* 由于多次申请`TrustAsia`的一年期免费单域名证书失败,所以准备使用`Let's Encrypt`证书
* 该程序已将每一个步骤都实现自动上传SSL并替换CDN的证书
* 为了使网站访问更快每天预热URL可以单独抽出该函数运行在[腾讯云函数](https://github.com/zfb132/auto_push_url)
## 部署方式
### 使用 Docker 快速部署
每月 1 号凌晨 2 点定时执行证书更新
* `ACME_DNS_TYPE`: Acme 的 dns 类型,你可以选择你的 dns 类型并配置[环境变量密钥](https://github.com/acmesh-official/acme.sh/wiki/dnsapi)
* `ACME_DOMAIN`: 你的顶级域名例如monlor.com自动申请证书 monlor.com/*.monlor.com
* `SECRETID`: 腾讯云 Secret Id
* `SECRETKEY`: 腾讯云 Secret Key
* `CDN_DOMAIN`: CDN 域名,多个域名用逗号分隔
* `RUN_NOW`: 是否在 Docker 启动时执行程序
```bash
docker run -d \
--name qcloud-ssl-cdn \
--restart=unless-stopped \
-e DP_Id=xxx \
-e DP_Key=xxx \
-e ACME_DNS_TYPE=dns_dp \
-e ACME_DOMAIN=monlor.com \
-e SECRETID=xxx \
-e SECRETKEY=xxx \
-e CDN_DOMAIN=www.monlor.com \
-e RUN_NOW=true \
ghcr.io/monlor/qcloud-ssl-cdn:main
```
#### 其他变量
* `ACME_ENABLED`: 是否启用 acme不启用将证书映射到容器`/data/certs`目录
* `PUSH_URLS`: CDN 刷新/预热地址,逗号分隔
* `PUSH_URLS_PATH`: CDN 刷新/预热地址文件路径,文件映射到 Docker 容器,**路径不能是`/data/urls.txt`**
### 使用 GitHub Action 部署
Fork 此项目,配置以下 Github Action Secrets
* `ACME_DNS_TYPE`: Acme 的 dns 类型,你可以选择你的 dns 类型并配置[环境变量密钥](https://github.com/acmesh-official/acme.sh/wiki/dnsapi)
* `ACME_DOMAIN`: 你的顶级域名例如monlor.com自动申请证书 monlor.com/*.monlor.com
* `SECRETID`: 腾讯云 Secret Id
* `SECRETKEY`: 腾讯云 Secret Key
* `CDN_DOMAIN`: CDN 域名,多个域名用逗号分隔
* `BARK_HOST`: [Bark](https://github.com/Finb/Bark) 消息通知 Host
* `BARK_KEY`: [Bark](https://github.com/Finb/Bark) 消息通知 Key
### 手动部署
#### 使用acme.sh申请证书
[安装及简单使用](https://blog.whuzfb.cn/blog/2020/07/07/web_https/#3-%E5%AE%89%E8%A3%85acme%E8%87%AA%E5%8A%A8%E7%AD%BE%E5%8F%91%E8%AF%81%E4%B9%A6)
对于本程序
```bash
# 腾讯云支持使用单域名和泛域名的证书,例如申请泛域名
acme.sh --issue -d "whuzfb.cn" -d "*.whuzfb.cn" --dns dns_dp
# 申请单域名
# acme.sh --issue -d "blog.whuzfb.cn" --dns dns_dp
```
#### 修改config.example.py参数
根据注释修改每一项内容
然后重命名为`config.py`
## 主要函数
`ssl.get_cert_list(client)`获取所有的SSL证书列表
`ssl.get_cert_info(client, cert_id)`根据id获取SSL证书的信息
`ssl.get_cert_detail(client, cert_id)`根据id获取SSL证书的详情
`ssl.delete_cert(client, cert_id)`删除指定id的SSL证书
`ssl.upload_cert(client, local_cert_info)`把本地的SSL证书上传到腾讯云返回新证书的id
`cdn.get_cdn_detail_info(client)`获取所有CDN的详细信息返回列表
`cdn.get_cdn_url_push_info(client)`查询CDN预热配额和每日可用量
`cdn.update_cdn_url_push(client, urls)`:指定 URL 资源列表加载至 CDN 节点,支持指定加速区域预热;默认情况下境内、境外每日预热 URL 限额为各 1000 条,每次最多可提交 20 条
`cdn.get_cdn_purge_url_info(client)`查询CDN刷新配额和每日可用量
`cdn.update_cdn_purge_url(client, urls)`:指定 URL 资源的刷新,支持指定加速区域刷新;默认情况下境内、境外每日刷新 URL 限额为各 10000 条,每次最多可提交 1000 条
`cdn.update_cdn_ssl(client, domain, cert_id)`为指定域名的CDN更换SSL证书
`ecdn.get_ecdn_basic_info(client)`获取所有ECDN全球加速服务的基本信息返回列表
`ecdn.get_ecdn_detail_info(client)`获取所有ECDN的详细信息返回列表
`ecdn.update_ecdn_ssl(client, domain, cert_id)`为指定域名的CDN的更换SSL证书

0
api/__init__.py Normal file
View File

213
api/cdn.py Normal file
View File

@ -0,0 +1,213 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2020-12-02 15:42
import json
from datetime import datetime
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
# 导入 cdn 产品模块的 models
from tencentcloud.cdn.v20180606 import models
from api.get_client_profile import get_client_instance
def get_cdn_client_instance(id, key):
'''获取cdn的实例用于后面对cdn的各种操作
'''
client = get_client_instance(id, key, "cdn")
return client
def get_cdn_detail_info(client):
'''获取所有CDN的详细信息返回列表
'''
try:
req = models.DescribeDomainsConfigRequest()
# 参数列表为空:表示获取所有信息
# 部分可选参数
# Filters: Array Of DomainFilter, 查询条件过滤器,复杂类型
# filter = DomainFilter()
# filter.Name = "domain"
# filter.Value = [domain]
# filter.Fuzzy = False
params = {}
req.from_json_string(json.dumps(params))
resp = client.DescribeDomainsConfig(req)
# print(resp.to_json_string())
print("获取所有cdn详细信息成功")
return resp.Domains
except TencentCloudSDKException as err:
print(err)
return []
def get_cdn_basic_info(client, domain_name):
'''获取指定CDN的基本信息
'''
try:
req = models.DescribeDomainsRequest()
params = {
"Limit": 1,
"Filters": [
{
"Name": "domain",
"Value": [ domain_name ],
"Fuzzy": False
}
]
}
req.from_json_string(json.dumps(params))
resp = client.DescribeDomains(req)
# print(resp.to_json_string())
print("获取指定cdn基本信息成功")
return resp.Domains
except TencentCloudSDKException as err:
print(err)
return []
def get_cdn_url_push_info(client):
'''查询CDN预热配额和每日可用量
'''
try:
req = models.DescribePushQuotaRequest()
params = {}
req.from_json_string(json.dumps(params))
resp = client.DescribePushQuota(req)
# print(resp.to_json_string())
print("获取CDN预热配额和每日可用量信息成功")
return resp.UrlPush
except TencentCloudSDKException as err:
print(err)
return []
def update_cdn_url_push(client, urls, region):
'''指定 URL 资源列表加载至 CDN 节点,支持指定加速区域预热
默认情况下境内境外每日预热 URL 限额为各 1000 每次最多可提交 20
'''
try:
req = models.PushUrlsCacheRequest()
params = {
"Urls": urls,
"Area": region,
}
req.from_json_string(json.dumps(params))
resp = client.PushUrlsCache(req)
print(resp.to_json_string())
print("URL:{}预热成功".format(', '.join(urls)))
return True
except TencentCloudSDKException as err:
print(err)
return False
def get_cdn_purge_url_info(client):
'''查询CDN刷新URL配额和每日可用量
'''
try:
req = models.DescribePurgeQuotaRequest()
params = {}
req.from_json_string(json.dumps(params))
resp = client.DescribePurgeQuota(req)
# print(resp.to_json_string())
print("获取CDN刷新URL配额和每日可用量信息成功")
return resp.UrlPurge
except TencentCloudSDKException as err:
print(err)
return []
def update_cdn_purge_url(client, urls, region):
'''指定 URL 资源的刷新,支持指定加速区域刷新
默认情况下境内境外每日刷新 URL 限额为各 10000 每次最多可提交 1000
'''
try:
req = models.PurgeUrlsCacheRequest()
params = {
"Urls": urls,
"Area": region,
"UrlEncode": True
}
req.from_json_string(json.dumps(params))
resp = client.PurgeUrlsCache(req)
print(resp.to_json_string())
print("URL:{}刷新成功".format(', '.join(urls)))
return True
except TencentCloudSDKException as err:
print(err)
return False
def update_cdn_ssl(client, domain, cert_id):
'''为指定域名的CDN更换SSL证书
'''
try:
req = models.UpdateDomainConfigRequest()
# 必选参数
# Domain: String, 域名
# 部分可选参数
# Https: Https, Https 加速配置
# 该类型详见 https://cloud.tencent.com/document/api/228/30987#Https
timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
params = {
"Domain": domain,
"Https": {
"Switch": "on",
"CertInfo": {
"CertId": cert_id,
"Message": "Auto update by api at {}".format(timestr)
}
}
}
req.from_json_string(json.dumps(params))
resp = client.UpdateDomainConfig(req)
print(resp.to_json_string())
print("成功更新域名为{0}的CDN的ssl证书为{1}".format(domain, cert_id))
except TencentCloudSDKException as err:
print(err)
exit("为CDN设置SSL证书{}出错".format(cert_id))
def update_cdn_https_options(client, domain, http2, hsts, age, hsts_subdomain, ocsp):
'''为指定域名的CDN的HTTPS开启HTTP 2.0、HSTS、OCSP等多个可选项
'''
try:
req = models.UpdateDomainConfigRequest()
params = {
"Domain": domain,
"Https": {
"Switch": "on"
}
}
if http2:
params["Https"]["Http2"] = "on"
if hsts:
params["Https"]["Hsts"] = {
"Switch": "off",
"MaxAge": 0,
"IncludeSubDomains": "off"
}
params["Https"]["Hsts"]["Switch"] = "on"
params["Https"]["Hsts"]["MaxAge"] = age
if hsts_subdomain:
params["Https"]["Hsts"]["IncludeSubDomains"] = "on"
if ocsp:
params["Https"]["OcspStapling"] = "on"
req.from_json_string(json.dumps(params))
resp = client.UpdateDomainConfig(req)
print(resp.to_json_string())
print("成功开启域名为{0}的CDN的HTTPS选项".format(domain))
except TencentCloudSDKException as err:
print(err)
exit("{}的CDN开启HTTPS选项功能出错".format(domain))

79
api/ecdn.py Normal file
View File

@ -0,0 +1,79 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2020-12-02 15:50
import json
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
# 导入 ecdn 产品模块的 models
from tencentcloud.ecdn.v20191012 import models
from api.get_client_profile import get_client_instance
def get_ecdn_client_instance(id, key):
'''获取ecdn的实例用于后面对ecdn的各种操作
'''
client = get_client_instance(id, key, "ecdn")
return client
def get_ecdn_basic_info(client):
'''获取所有ECDN的基本信息返回列表
'''
try:
req = models.DescribeDomainsRequest()
params = {}
req.from_json_string(json.dumps(params))
resp = client.DescribeDomains(req)
# print(resp.to_json_string())
print("获取所有ecdn基本信息成功")
return resp.Domains
except TencentCloudSDKException as err:
print(err)
return []
def get_ecdn_detail_info(client):
'''获取所有ECDN的详细信息返回列表
'''
try:
req = models.DescribeDomainsConfigRequest()
params = {}
req.from_json_string(json.dumps(params))
resp = client.DescribeDomainsConfig(req)
# print(resp.to_json_string())
print("获取所有ecdn详细信息成功")
return resp.Domains
except TencentCloudSDKException as err:
print(err)
return []
def update_ecdn_ssl(client, domain, cert_id):
'''为指定域名的CDN的更换SSL证书
'''
# 为ecdn更新证书使用ecdn相关接口
# https://console.cloud.tencent.com/api/explorer?Product=ecdn&Version=2019-10-12
try:
req = models.UpdateDomainConfigRequest()
# 必选参数
# Domain: String, 域名
# 部分可选参数
# Https: Https, Https 加速配置
# 该类型详见 https://cloud.tencent.com/document/api/228/30987#Https
params = {
"Domain": domain,
"Https": {
"CertInfo": {
"CertId": cert_id
}
}
}
req.from_json_string(json.dumps(params))
resp = client.UpdateDomainConfig(req)
print(resp.to_json_string())
print("成功更新域名为{0}的CDN的ssl证书为{1}".format(domain, cert_id))
except TencentCloudSDKException as err:
print(err)
exit("为CDN设置SSL证书{}出错".format(cert_id))

52
api/get_client_profile.py Normal file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2020-12-02 15:17
from tencentcloud.common import credential
# 导入可选配置类
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
# 导入ssl产品模块的 client
from tencentcloud.ssl.v20191205 import ssl_client
# 导入cdn产品模块的 client
from tencentcloud.cdn.v20180606 import cdn_client
# 导入ecdn产品模块的 client
from tencentcloud.ecdn.v20191012 import ecdn_client
def get_client_instance(id, key, product):
'''获取指定endpoint的实例用于后面对其的各种操作
'''
try:
# 实例化一个认证对象,入参需要传入腾讯云账户 secretIdsecretKey, 此处还需注意密钥对的保密
cred = credential.Credential(id, key)
# 实例化一个 http 选项,可选
httpProfile = HttpProfile()
# post 请求 (默认为 post 请求)
httpProfile.reqMethod = "POST"
# 请求超时时间,单位为秒 (默认60秒)
httpProfile.reqTimeout = 30
# 不指定接入地域域名 (默认就近接入)
httpProfile.endpoint = "{}.tencentcloudapi.com".format(product)
# 实例化一个 client 选项,可选
clientProfile = ClientProfile()
clientProfile.httpProfile = httpProfile
# 实例化要请求产品的 client 对象clientProfile 是可选的
if product == "ssl":
client = ssl_client.SslClient(cred, "", clientProfile)
print("实例化一个ssl_client成功")
elif product == "cdn":
client = cdn_client.CdnClient(cred, "", clientProfile)
print("实例化cdn client成功")
elif product == "ecdn":
client = ecdn_client.EcdnClient(cred, "", clientProfile)
print("实例化ecdn client成功")
else:
exit("本程序仅支持ssl、cdn、ecdn")
return client
except TencentCloudSDKException as err:
print(err)
exit(-1)

145
api/ssl.py Normal file
View File

@ -0,0 +1,145 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2020-12-02 15:02
import json
from datetime import datetime
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
# 导入 ssl 产品模块的 models
from tencentcloud.ssl.v20191205 import models
from api.get_client_profile import get_client_instance
def get_ssl_client_instance(id, key):
'''获取ssl的实例用于后面对ssl的各种操作
'''
client = get_client_instance(id, key, "ssl")
return client
def get_cert_list(client):
'''获取所有的SSL证书列表
'''
try:
# 实例化一个 ssl 实例信息查询请求对象,每个接口都会对应一个 request 对象
req = models.DescribeCertificatesRequest()
# 可选参数列表
# Offset: Integer, 分页偏移量从0开始
# Limit: Integer, 每页数量默认20
# SearchKey: String, 搜索关键词,可搜索证书 ID、备注名称、域名
# CertificateType: String, 证书类型CA = 客户端证书SVR = 服务器证书
# ProjectId: Integer, 项目 ID
# ExpirationSort: String, 按到期时间排序DESC = 降序, ASC = 升序
# CertificateStatus: Array Of Integer, 证书状态
# Deployable: Integer, 是否可部署可选值1 = 可部署0 = 不可部署
params = {}
req.from_json_string(json.dumps(params))
# 通过 client 对象调用 DescribeCertificatesRequest 方法发起请求,请求方法名与请求对象对应
# 返回的 resp 是一个 DescribeCertificatesResponse 类的实例,与请求对象对应
resp = client.DescribeCertificates(req)
# 输出 json 格式的字符串回包
# print(resp.to_json_string())
# 也可以取出单个值,通过官网接口文档或跳转到 response 对象的定义处查看返回字段的定义
# print(resp.TotalCount)
print("获取ssl证书列表成功")
return resp.Certificates
except TencentCloudSDKException as err:
print(err)
return []
def get_cert_info(client, cert_id):
'''根据id获取SSL证书的信息
'''
try:
req = models.DescribeCertificateRequest()
# 必选参数
# CertificateId: String, 证书 ID
params = {
"CertificateId": cert_id
}
req.from_json_string(json.dumps(params))
resp = client.DescribeCertificate(req)
# print(resp.to_json_string())
print("获取ssl证书{}的信息成功".format(cert_id))
return resp
except TencentCloudSDKException as err:
print(err)
exit("获取证书{}信息出错".format(cert_id))
def get_cert_detail(client, cert_id):
'''根据id获取SSL证书的详情
'''
try:
req = models.DescribeCertificateDetailRequest()
# 必选参数
# CertificateId: String, 证书 ID
params = {
"CertificateId": cert_id
}
req.from_json_string(json.dumps(params))
resp = client.DescribeCertificateDetail(req)
# print(resp.to_json_string())
print("获取ssl证书{}的详细信息成功".format(cert_id))
except TencentCloudSDKException as err:
print(err)
exit("获取证书{}详细信息出错".format(cert_id))
def delete_cert(client, cert_id):
'''删除指定id的SSL证书(删除不存在的id会出现警告)
'''
try:
req = models.DeleteCertificateRequest()
# 必选参数
# CertificateId: String, 证书 ID
params = {
"CertificateId": cert_id
}
req.from_json_string(json.dumps(params))
resp = client.DeleteCertificate(req)
# print(resp.to_json_string())
print("删除ssl证书{}成功".format(cert_id))
except TencentCloudSDKException as err:
print(err)
exit("删除证书{}出错".format(cert_id))
def upload_cert(client, local_cert_info):
'''把本地的SSL证书上传到腾讯云返回新证书的id
'''
try:
req = models.UploadCertificateRequest()
# 必选参数
# CertificatePublicKey: String, 证书公钥内容
# CertificatePrivateKey: String, 私钥内容,证书类型为 SVR 时必填,为 CA 时可不填
# 可选参数列表
# CertificateType: String, 证书类型,默认 SVR。CA = 客户端证书SVR = 服务器证书
# Alias: String, 备注名称
# ProjectId: Integer, 项目 ID
timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
params = {
"CertificatePublicKey": local_cert_info["cer"],
"CertificatePrivateKey": local_cert_info["key"],
"CertificateType": local_cert_info["type"],
"Alias": "Auto upload by api at {}".format(timestr)
}
req.from_json_string(json.dumps(params))
resp = client.UploadCertificate(req)
# print(resp.to_json_string())
print("上传ssl证书成功")
return resp.CertificateId
except TencentCloudSDKException as err:
print(err)
return ""

85
api/tools.py Normal file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2020-12-02 15:45
def read_file(name):
'''读取文件内容
'''
with open(name, 'r') as file:
text = file.read()
return text
def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i + n]
def resize_url_list(url_list, group_size):
'''将一维列表按照指定长度分割
'''
url_chunks = list(chunks(url_list, group_size))
results = []
for i in range(len(url_chunks)):
results.append(url_chunks[i])
print("重置的URL列表个数{},每个列表包含文件数{}".format(len(results), group_size))
return results
def get_sitemap_urls(url):
'''从给定的sitemap.xml文件获取链接
'''
import requests
import re
text = requests.get(url).text
pattern = re.compile(r'<loc>(.*?)</loc>')
results = re.findall(pattern, text)
url_list = []
for res in results:
if not res.endswith("/"):
res = res + "/"
url_list.append(res)
return url_list
def get_urls_from_file(file_name):
'''从给定的文件获取链接
'''
with open(file_name, 'r') as file:
return [x.strip() for x in file.readlines()]
def generate_https(https):
'''由于Https无法序列化自己将其改为字典已弃用
'''
server_cert = {}
server_cert["CertId"] = https.CertInfo.CertId
server_cert["CertName"] = https.CertInfo.CertName
server_cert["Certificate"] = https.CertInfo.Certificate
server_cert["PrivateKey"] = https.CertInfo.PrivateKey
server_cert["ExpireTime"] = https.CertInfo.ExpireTime
server_cert["DeployTime"] = https.CertInfo.DeployTime
server_cert["Message"] = https.CertInfo.Message
client_cert = {}
client_cert["Certificate"] = https.ClientCertInfo.Certificate
client_cert["CertName"] = https.ClientCertInfo.CertName
client_cert["ExpireTime"] = https.ClientCertInfo.ExpireTime
client_cert["DeployTime"] = https.ClientCertInfo.DeployTime
hsts = {}
hsts["Switch"] = https.Hsts.Switch
hsts["MaxAge"] = https.Hsts.MaxAge
hsts["IncludeSubDomains"] = https.Hsts.IncludeSubDomains
res = {}
res["Switch"] = https.Switch
res["Http2"] = https.Http2
res["OcspStapling"] = https.OcspStapling
res["VerifyClient"] = https.VerifyClient
res["Spdy"] = https.Spdy
res["SslStatus"] = https.SslStatus
res["CertInfo"] = server_cert
res["ClientCertInfo"] = client_cert
res["Hsts"] = https.Hsts
return res

55
config.example.py Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2020-12-02 16:15
# 腾讯云支持使用单域名和泛域名的证书,例如
# acme.sh --issue -d "whuzfb.cn" -d "*.whuzfb.cn" --dns dns_dp
# acme.sh --issue -d "blog.whuzfb.cn" --dns dns_dp
# 使用ACME申请的SSL完整证书的本地存放路径
CER_FILE = "/home/zfb/.acme.sh/whuzfb.cn/fullchain.cer"
# 使用ACME申请的SSL证书私钥的本地存放路径
KEY_FILE = "/home/zfb/.acme.sh/whuzfb.cn/whuzfb.cn.key"
# CDN服务配置的域名需要提前在腾讯云网页前端创建
# 如果ACME申请的证书为泛域名证书且要配置多个CDN加速
# CDN_DOMAIN = ["blog.whuzfb.cn", "blog2.whuzfb.cn", "web.whuzfb.cn"]
CDN_DOMAIN = ["blog.whuzfb.cn"]
# 腾讯云https://console.cloud.tencent.com/cam/capi
SECRETID = "AKeee5555512345677777123456788889876"
SECRETKEY = "A71234567890abcdefedcba098765432"
# 控制功能开关
# 是否进行上传证书文件的操作根据CER_FILE和KEY_FILE
UPLOAD_SSL = True
# 以下为HTTPS额外功能
# 是否开启HTTP2
ENABLE_HTTP2 = True
# 是否开启HSTS
ENABLE_HSTS = True
# 为HSTS设定最长过期时间以秒为单位
HSTS_TIMEOUT_AGE = 1
# HSTS包含子域名仅对泛域名有效
HSTS_INCLUDE_SUBDOMAIN = True
# 是否开启OCSP
ENABLE_OCSP = True
# 是否删除适用于CDN_DOMAIN域名下的其他所有证书
# 满足以下条件证书适用于CDN_DOMAIN、证书id不是本次使用的id
DELETE_OLD_CERTS = True
# 是否进行为CDN_DOMAIN更换SSL证书的操作
# 若UPDATE_SSL = True且UPLOAD_SSL = True则CERT_ID可不设置直接利用UPLOAD_SSL的证书
UPDATE_SSL = True
CERT_ID = ""
# 是否进行预热URL的操作
PUSH_URL = True
# 是否进行刷新URL的操作
PURGE_URL = True
# 自定义的预热URL默认会预热sitemap.xml的所有链接文件路径
# 该文件内每行一个URL例如
# https://blog.whuzfb.cn/img/me2.jpg
# https://blog.whuzfb.cn/img/home-bg.jpg
URLS_FILE = "urls.txt"

17
docker/entrypoint.sh Normal file
View File

@ -0,0 +1,17 @@
#!/bin/sh
# 添加crontab配置
cat > /var/spool/cron/crontabs/root <<-EOF
0 2 1 * * /data/update.sh &> /data/run.log
EOF
# 启动crontab服务
crond
# 马上执行证书更新
if [ ! -f /data/run.log ]; then
touch /data/run.log
fi
if [ "${RUN_NOW:-"false"}" = "true" ]; then
nohup /data/update.sh &> /data/run.log &
fi
tail -f /data/run.log

75
docker/update.sh Normal file
View File

@ -0,0 +1,75 @@
#!/bin/sh
set -u
if [ "${ACME_ENABLED:=true}" = "true" ]; then
# 使用acme获取/更新证书
${ACME_HOME}/acme.sh ${ACME_PARAMS:-} --force --issue --cert-home ${CERT_HOME} -d ${ACME_DOMAIN} -d *.${ACME_DOMAIN} --dns ${ACME_DNS_TYPE}
fi
# 添加刷新url
echo "${PUSH_URLS:-}" | tr ',' '\n' > ${WORK_DIR}/urls.txt
if [ -n "${PUSH_URLS_PATH:-}" ]; then
cat "${PUSH_URLS_PATH}" >> ${WORK_DIR}/urls.txt
fi
# 写入腾讯云cdn更新配置
cat>${WORK_DIR}/config.py<<-EOF
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2020-12-02 16:15
# 腾讯云支持使用单域名和泛域名的证书,例如
# acme.sh --issue -d "whuzfb.cn" -d "*.whuzfb.cn" --dns dns_dp
# acme.sh --issue -d "blog.whuzfb.cn" --dns dns_dp
# 使用ACME申请的SSL完整证书的本地存放路径
CER_FILE = "${CERT_HOME}/${ACME_DOMAIN}/fullchain.cer"
# 使用ACME申请的SSL证书私钥的本地存放路径
KEY_FILE = "${CERT_HOME}/${ACME_DOMAIN}/${ACME_DOMAIN}.key"
# CDN服务配置的域名需要提前在腾讯云网页前端创建
# 如果ACME申请的证书为泛域名证书且要配置多个CDN加速
# CDN_DOMAIN = ["blog.whuzfb.cn", "blog2.whuzfb.cn", "web.whuzfb.cn"]
CDN_DOMAIN = ["`echo ${CDN_DOMAIN} | sed -e 's/,/","/g'`"]
# 腾讯云https://console.cloud.tencent.com/cam/capi
SECRETID = "${SECRETID}"
SECRETKEY = "${SECRETKEY}"
# 控制功能开关
# 是否进行上传证书文件的操作根据CER_FILE和KEY_FILE
UPLOAD_SSL = True
# 以下为HTTPS额外功能
# 是否开启HTTP2
ENABLE_HTTP2 = True
# 是否开启HSTS
ENABLE_HSTS = True
# 为HSTS设定最长过期时间以秒为单位
HSTS_TIMEOUT_AGE = 31536000
# HSTS包含子域名仅对泛域名有效
HSTS_INCLUDE_SUBDOMAIN = True
# 是否开启OCSP
ENABLE_OCSP = True
# 是否删除适用于CDN_DOMAIN域名下的其他所有证书
# 满足以下条件证书适用于CDN_DOMAIN、证书id不是本次使用的id
DELETE_OLD_CERTS = True
# 是否进行为CDN_DOMAIN更换SSL证书的操作
# 若UPDATE_SSL = True且UPLOAD_SSL = True则CERT_ID可不设置直接利用UPLOAD_SSL的证书
UPDATE_SSL = True
CERT_ID = ""
# 是否进行预热URL的操作
PUSH_URL = True
# 是否进行刷新URL的操作
PURGE_URL = True
# 自定义的预热URL默认会预热sitemap.xml的所有链接文件路径
# 该文件内每行一个URL例如
# https://blog.whuzfb.cn/img/me2.jpg
# https://blog.whuzfb.cn/img/home-bg.jpg
URLS_FILE = "urls.txt"
EOF
# 更新CDN证书
cd ${WORK_DIR} && python main.py

199
main.py Normal file
View File

@ -0,0 +1,199 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2020-12-02 15:56
from api import cdn, ecdn, ssl, tools
import config
def run_config_ssl(id, key, cer_file, key_file):
'''上传SSl证书到腾讯云SSL证书管理返回新证书的id
'''
cert_info = {
# Let's Encrypt 是通过中级 CA 机构颁发的证书,拿到的证书文件包含
# whuzfb.cn.cer ca.cer
# 需要人为地将服务器证书与中间证书拼接在一起acme.sh已经进行拼接
# fullchain.cer
"cer": tools.read_file(cer_file),
"key": tools.read_file(key_file),
"type": "CA"
}
ssl_client = ssl.get_ssl_client_instance(id, key)
cert_list = ssl.get_cert_list(ssl_client)
for cert in cert_list:
# 获取每个证书的id
cert_id = cert.CertificateId
# 获取每个证书的信息
# if domain == ssl.get_cert_info(ssl_client, cert_id).Domain:
# # 删除证书
# # delete_cert(client, cert_id)
# # break
# print(cert_id)
# 上传证书获取新证书的id
id = ssl.upload_cert(ssl_client, cert_info)
if len(id)>0:
return id
else:
exit("获取新证书id失败")
def run_config_cdn(id, key, domain, cert_id):
'''该函数实现为CDN更新ssl证书的功能
'''
cdn_client = cdn.get_cdn_client_instance(id, key)
cdns = cdn.get_cdn_detail_info(cdn_client)
https = None
for _cdn in cdns:
if _cdn.Domain == domain:
https = _cdn.Https
break
print(https)
# generate_https(https)
cdn.update_cdn_ssl(cdn_client, domain, cert_id)
def https_options_enabler(id, key, domain, http2, hsts, age, hsts_subdomain, ocsp):
'''开启HTTPS配置中的部分选项
'''
cdn_client = cdn.get_cdn_client_instance(id, key)
cdn.update_cdn_https_options(cdn_client, domain, http2, hsts, age, hsts_subdomain, ocsp)
def delete_old_ssls(id, key, cdn_domain, ignore_id):
'''删除某个CDN的除ignore_id以外的所有ssl证书
'''
ssl_client = ssl.get_ssl_client_instance(id, key)
cert_list = ssl.get_cert_list(ssl_client)
for cert in cert_list:
cert_id = cert.CertificateId
# 刚上传的这个证书不删除
if cert_id == ignore_id:
continue
cert_info = ssl.get_cert_info(ssl_client, cert_id)
cert_domain_and_alt_name = [cert_info.Domain] + cert_info.SubjectAltName
matched = False
# 判断域名匹配
for cert_name in cert_domain_and_alt_name:
if cert_name:
# 判断主域名或多域名
if cert_name == cdn_domain:
matched = True
break
# 判断泛域名 m=['*','example.cn']
m = cert_name.split('.', 1)
n = cdn_domain.split('.', 1)
if m[0] == "*" and m[1] == n[1]:
matched = True
break
# 根据结果删除证书
if matched:
ssl.delete_cert(ssl_client, cert_id)
def run_config_ecdn(id, key, domain, cert_id):
'''全站加速网络为指定域名的CDN更新SSL证书
'''
ecdn_client = ecdn.get_ecdn_client_instance(id, key)
ecdn.get_ecdn_basic_info(ecdn_client)
cdns = ecdn.get_ecdn_detail_info(ecdn_client)
ecdn.update_ecdn_ssl(ecdn_client, domain, cert_id)
def run_url_push(id, key, domain, urls_file):
'''为CDN推送预热URL
'''
from time import sleep
from os.path import isfile
urls = []
try:
urls = tools.get_sitemap_urls("https://{}/sitemap.xml".format(domain))
except Exception as e:
print(repr(e))
if isfile(urls_file):
urls = urls + tools.get_urls_from_file(urls_file)
cdn_client = cdn.get_cdn_client_instance(id, key)
cdn_region = cdn.get_cdn_basic_info(cdn_client, domain)[0].Area
# 预热URL支持area为global的参数但是为了方便统计用量配额手动分开刷新
if cdn_region == 'global':
cdn_region = ['mainland', 'overseas']
else:
cdn_region = [cdn_region]
info = cdn.get_cdn_url_push_info(cdn_client)
# 统计预热url数量
cnt = 0
# 根据加速域名配置的区域进行预热
for i in info:
if i.Area in cdn_region:
grp_size = i.Batch
available = i.Available
print("正在对区域{0}进行url预热剩余配额{1}".format(i.Area, available))
new_urls = tools.resize_url_list(urls, grp_size)
for url_grp in new_urls:
res = cdn.update_cdn_url_push(cdn_client, url_grp, i.Area)
if res:
cnt = cnt + len(url_grp)
sleep(0.1)
else:
break
print("成功预热{}个URL".format(cnt))
def run_purge_url(id, key, domain, urls_file):
'''为CDN推送刷新URL
'''
from time import sleep
from os.path import isfile
urls = []
try:
urls = tools.get_sitemap_urls("https://{}/sitemap.xml".format(domain))
except Exception as e:
print(repr(e))
if isfile(urls_file):
urls = urls + tools.get_urls_from_file(urls_file)
cdn_client = cdn.get_cdn_client_instance(id, key)
cdn_region = cdn.get_cdn_basic_info(cdn_client, domain)[0].Area
# 刷新URL不支持area为global的参数
if cdn_region == 'global':
cdn_region = ['mainland', 'overseas']
else:
cdn_region = [cdn_region]
info = cdn.get_cdn_purge_url_info(cdn_client)
# 统计刷新url数量
cnt = 0
# 根据加速域名配置的区域进行刷新
for i in info:
if i.Area in cdn_region:
grp_size = i.Batch
available = i.Available
print("正在对区域{0}进行url刷新剩余配额{1}".format(i.Area, available))
new_urls = tools.resize_url_list(urls, grp_size)
for url_grp in new_urls:
res = cdn.update_cdn_purge_url(cdn_client, url_grp, i.Area)
if res:
cnt = cnt + len(url_grp)
sleep(0.1)
else:
break
print("成功刷新{}个URL".format(cnt))
if __name__ == "__main__":
SECRETID = config.SECRETID
SECRETKEY = config.SECRETKEY
# 泛域名证书
if config.UPLOAD_SSL:
cert_id = run_config_ssl(SECRETID, SECRETKEY, config.CER_FILE, config.KEY_FILE)
else:
cert_id = config.CERT_ID
for my_domain in config.CDN_DOMAIN:
if config.UPDATE_SSL:
run_config_cdn(SECRETID, SECRETKEY, my_domain, cert_id)
if config.ENABLE_HSTS or config.ENABLE_OCSP or config.ENABLE_HTTP2:
https_options_enabler(SECRETID, SECRETKEY, my_domain, config.ENABLE_HTTP2, config.ENABLE_HSTS,
config.HSTS_TIMEOUT_AGE, config.HSTS_INCLUDE_SUBDOMAIN, config.ENABLE_OCSP)
if config.DELETE_OLD_CERTS:
delete_old_ssls(SECRETID, SECRETKEY, my_domain, cert_id)
if config.PUSH_URL:
run_url_push(SECRETID, SECRETKEY, my_domain, config.URLS_FILE)
if config.PURGE_URL:
run_purge_url(SECRETID, SECRETKEY, my_domain, config.URLS_FILE)
# ecdn是全球加速服务与CDN不同本账号没有开通该功能
# run_config_ecdn(SECRETID, SECRETKEY, my_domain, cert_id)

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
tencentcloud-sdk-python==3.0.302
requests==2.22.0

2
urls.txt Normal file
View File

@ -0,0 +1,2 @@
https://blog.whuzfb.cn/img/me2.jpg
https://blog.whuzfb.cn/img/home-bg.jpg