Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d46dab4fdd | ||
|
|
d44849c53c | ||
|
|
dbc5a3c6b3 | ||
|
|
4a5fa767ed | ||
|
|
1b7debc6a4 | ||
|
|
19a6b94680 | ||
|
|
65a72b8d60 | ||
|
|
7f61cab101 | ||
|
|
37caef38ad | ||
|
|
9cc01db1d5 | ||
|
|
9172440f79 | ||
|
|
e0eb3a4413 | ||
|
|
ae0f16bf35 | ||
|
|
6c9ed162e3 | ||
|
|
3849b52cdf | ||
|
|
9ecfcb5814 | ||
|
|
54ad09f755 | ||
|
|
6ee4dc165b | ||
|
|
8e2eb89696 | ||
|
|
9d397cc8be | ||
|
|
cbfb0755b3 | ||
|
|
d8d127ee9d | ||
|
|
0ed5430e80 | ||
|
|
878c1f52fa | ||
|
|
6900452b49 | ||
|
|
b97e0e512d | ||
|
|
f740ff517f | ||
|
|
e5989fe023 | ||
|
|
4323156fbe | ||
|
|
3c721901c5 | ||
|
|
5c2c50839a | ||
|
|
fd54c2ffac | ||
|
|
7e483e6091 | ||
|
|
80c48e9acd | ||
|
|
b98f1c0dd0 | ||
|
|
b53874a0b8 | ||
|
|
c4c9adb8bf | ||
|
|
162e10909b | ||
|
|
0f1ae6ccd9 |
36
CHANGELOG.md
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.16](https://github.com/certd/certd/compare/v1.20.15...v1.20.16) (2024-07-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复配置了cdn cname后申请失败的bug ([4a5fa76](https://github.com/certd/certd/commit/4a5fa767edc347d03d29a467e86c9a4d70b0220c))
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复无法强制取消任务的bug ([9cc01db](https://github.com/certd/certd/commit/9cc01db1d569a5c45bb3e731f35d85df324a8e62))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/certd/certd/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
|
||||
* 支持windows文件上传 ([7f61cab](https://github.com/certd/certd/commit/7f61cab101fa13b4e88234e9ad47434e6130fed2))
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复修改密码功能异常问题 ([f740ff5](https://github.com/certd/certd/commit/f740ff517f521dce361284c2c54bccc68aee0ea2))
|
||||
|
||||
## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 日志高度越界 ([c4c9adb](https://github.com/certd/certd/commit/c4c9adb8bfd513f57252e523794e3799a9b220f8))
|
||||
* 修复邮箱设置页面SMTP拼写错误的问题 ([b98f1c0](https://github.com/certd/certd/commit/b98f1c0dd0bc6c6b4f814c578692afdf6d90b88d))
|
||||
* 修复logo问题 ([7e483e6](https://github.com/certd/certd/commit/7e483e60913d509b113148c735fe13ba1d72dddf))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 增加警告,修复一些样式错乱问题 ([fd54c2f](https://github.com/certd/certd/commit/fd54c2ffac492222e85ff2f5f49a9ee5cfc73588))
|
||||
* ssh登录支持openssh格式私钥、支持私钥密码 ([5c2c508](https://github.com/certd/certd/commit/5c2c50839a9076004f9034d754ac6deb531acdfb))
|
||||
|
||||
## [1.20.12](https://github.com/certd/certd/compare/v1.20.10...v1.20.12) (2024-06-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
35
README.md
@@ -49,9 +49,8 @@ https://certd.handsfree.work/
|
||||
|
||||
1.2 安装docker
|
||||
https://docs.docker.com/engine/install/
|
||||
选择对应的操作系统,按照官方文档执行命令即可
|
||||
|
||||
1.3 安装docker-compose
|
||||
https://docs.docker.com/compose/install/linux/
|
||||
|
||||
### 2. 下载docker-compose.yaml文件
|
||||
|
||||
@@ -82,12 +81,12 @@ https://github.com/certd/certd/releases
|
||||
# 如果docker compose是插件化安装
|
||||
export CERTD_VERSION=latest
|
||||
docker compose up -d
|
||||
|
||||
#如果docker compose是独立安装
|
||||
export CERTD_VERSION=latest
|
||||
docker-compose up -d
|
||||
|
||||
```
|
||||
如果提示 没有compose命令,请安装docker-compose
|
||||
https://docs.docker.com/compose/install/linux/
|
||||
|
||||
|
||||
|
||||
### 4. 访问
|
||||
|
||||
http://your_server_ip:7001
|
||||
@@ -102,8 +101,7 @@ http://your_server_ip:7001
|
||||
|
||||
|
||||
|
||||
## 五、一些说明及问题处理
|
||||
### 1. 一些说明
|
||||
## 五、一些说明
|
||||
* 本项目ssl证书提供商为letencrypt
|
||||
* 申请过程遵循acme协议
|
||||
* 需要验证域名所有权,一般有两种方式(目前本项目仅支持dns-01)
|
||||
@@ -115,8 +113,15 @@ http://your_server_ip:7001
|
||||
* 免费证书过期时间90天,以后可能还会缩短,所以自动化部署必不可少
|
||||
* 设置每天自动运行,当证书过期前20天,会自动重新申请证书并部署
|
||||
|
||||
### 2. 问题处理
|
||||
#### 2.1 忘记管理员密码
|
||||
## 六、不同平台的设置说明
|
||||
|
||||
* [Cloudflare](./doc/cf/cf.md)
|
||||
* [腾讯云](./doc/tencent/tencent.md)
|
||||
* [windows主机](./doc/host/host.md)
|
||||
|
||||
|
||||
## 七、问题处理
|
||||
### 7.1 忘记管理员密码
|
||||
解决方法如下:
|
||||
1. 修改docker-compose.yaml文件,将环境变量`certd_system_resetAdminPassword`改为`true`
|
||||
```yaml
|
||||
@@ -138,7 +143,7 @@ docker compose up -d
|
||||
```
|
||||
5. 使用admin/123456登录系统,请及时修改管理员密码
|
||||
|
||||
## 六、联系作者
|
||||
## 八、联系作者
|
||||
如有疑问,欢迎加入群聊(请备注certd)
|
||||
* QQ群:141236433
|
||||
* 微信群:
|
||||
@@ -150,7 +155,7 @@ docker compose up -d
|
||||
<img height="230" src="./doc/images/me.png">
|
||||
</p>
|
||||
|
||||
## 捐赠
|
||||
## 九、捐赠
|
||||
媳妇儿说:“一天到晚搞开源,也不管管老婆孩子!😡😡😡”
|
||||
拜托各位捐赠支持一下,让媳妇儿开心开心,我也能有更多时间进行开源项目,感谢🙏🙏🙏
|
||||
<p align="center">
|
||||
@@ -158,12 +163,12 @@ docker compose up -d
|
||||
</p>
|
||||
|
||||
|
||||
## 七、贡献代码
|
||||
## 十、贡献代码
|
||||
|
||||
[贡献插件教程](./plugin.md)
|
||||
|
||||
|
||||
## 八、我的其他项目
|
||||
## 十一、我的其他项目
|
||||
* [袖手GPT](https://ai.handsfree.work/) ChatGPT,国内可用,无需FQ,每日免费额度
|
||||
* [fast-crud](https://gitee.com/fast-crud/fast-crud/) 基于vue3的crud快速开发框架
|
||||
* [dev-sidecar](https://github.com/docmirror/dev-sidecar/) 直连访问github工具,无需FQ,解决github无法访问的问题
|
||||
|
||||
15
doc/cf/cf.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Cloudflare
|
||||
|
||||
|
||||
## CF Token申请
|
||||
|
||||
### 申请地址:
|
||||
https://dash.cloudflare.com/profile/api-tokens
|
||||
|
||||
### 权限设置:
|
||||
需要设置权限和资源范围
|
||||
权限包括:Zone.Zone.edit, Zone.DNS.edit
|
||||
资源范围:要包含对应域名,推荐直接设置为All Zones
|
||||
最终效果如下,可以切换语言为英文对比如下图检查
|
||||
|
||||

|
||||
BIN
doc/cf/cf_token.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
24
doc/host/host.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# 远程主机
|
||||
|
||||
远程主机基于ssh协议,通过ssh连接远程主机,执行命令。
|
||||
|
||||
## windows开启OpenSSH Server
|
||||
1. 安装OpenSSH Server
|
||||
请前往Microsoft官方文档查看如何开启openSSH
|
||||
https://learn.microsoft.com/zh-cn/windows-server/administration/openssh/openssh_install_firstuse?tabs=gui#install-openssh-for-windows
|
||||
|
||||
2. 启动OpenSSH Server服务
|
||||
```
|
||||
win+R 弹出运行对话框,输入 services.msc 打开服务管理器
|
||||
找到 OpenSSH SSH Server
|
||||
启动ssh server服务,并且设置为自动启动
|
||||
```
|
||||
|
||||
3. 测试ssh登录
|
||||
使用你常用的ssh客户端,连接你的windows主机,进行测试
|
||||
|
||||
```cmd
|
||||
# 如何确定你用户名
|
||||
C:\Users\xiaoj>
|
||||
↑↑↑↑---------这个就是windows ssh的登录用户名
|
||||
```
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 145 KiB |
BIN
doc/tencent/dnspod-token.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
doc/tencent/tencent-access.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
16
doc/tencent/tencent.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 腾讯云
|
||||
|
||||
|
||||
## DNSPOD 授权设置
|
||||
目前腾讯云管理的域名的dns暂时只支持从DNSPOD进行设置
|
||||
打开 https://console.dnspod.cn/account/token/apikey
|
||||
然后按如下方式获取DNSPOD的授权
|
||||

|
||||
|
||||
|
||||
## 腾讯云API密钥设置
|
||||
|
||||
腾讯云其他部署需要API密钥,需要在腾讯云控制台进行设置
|
||||
打开 https://console.cloud.tencent.com/cam/capi
|
||||
然后按如下方式获取腾讯云的API密钥
|
||||

|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.20.12"
|
||||
"version": "1.20.16"
|
||||
}
|
||||
|
||||
@@ -9,15 +9,8 @@ env:
|
||||
rules:
|
||||
indent: [2, 4, { SwitchCase: 1, VariableDeclarator: 1 }]
|
||||
brace-style: [2, 'stroustrup', { allowSingleLine: true }]
|
||||
space-before-function-paren: [2, { anonymous: 'never', named: 'never' }]
|
||||
func-names: 0
|
||||
prefer-destructuring: 0
|
||||
object-curly-newline: 0
|
||||
class-methods-use-this: 0
|
||||
wrap-iife: [2, 'inside']
|
||||
no-param-reassign: 0
|
||||
comma-dangle: [2, 'never']
|
||||
max-len: [1, 200, 2, { ignoreUrls: true, ignoreComments: false }]
|
||||
no-multiple-empty-lines: [2, { max: 2, maxBOF: 0, maxEOF: 0 }]
|
||||
prefer-object-spread: 0
|
||||
import/no-useless-path-segments: 0
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
---
|
||||
name: test
|
||||
on: [push, pull_request]
|
||||
|
||||
@@ -12,7 +11,6 @@ jobs:
|
||||
node: [16, 18, 20, 22]
|
||||
eab: [0, 1]
|
||||
|
||||
|
||||
#
|
||||
# Environment
|
||||
#
|
||||
@@ -41,7 +39,6 @@ jobs:
|
||||
ACME_HTTP_PORT: 5002
|
||||
ACME_HTTPS_PORT: 5003
|
||||
|
||||
|
||||
#
|
||||
# Pipeline
|
||||
#
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.16](https://github.com/publishlab/node-acme-client/compare/v1.20.15...v1.20.16) (2024-07-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复配置了cdn cname后申请失败的bug ([4a5fa76](https://github.com/publishlab/node-acme-client/commit/4a5fa767edc347d03d29a467e86c9a4d70b0220c))
|
||||
|
||||
## [1.20.15](https://github.com/publishlab/node-acme-client/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/publishlab/node-acme-client/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
|
||||
|
||||
## [1.20.14](https://github.com/publishlab/node-acme-client/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.20.13](https://github.com/publishlab/node-acme-client/compare/v1.20.12...v1.20.13) (2024-06-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.20.12](https://github.com/publishlab/node-acme-client/compare/v1.20.10...v1.20.12) (2024-06-17)
|
||||
|
||||
### Bug Fixes
|
||||
@@ -41,7 +61,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
# Changelog
|
||||
|
||||
## v5.3.1
|
||||
## v5.3.1 (2024-05-22)
|
||||
|
||||
* `fixed` Allow `client.auto()` being called with an empty CSR common name
|
||||
* `fixed` Bug when calling `updateAccountKey()` with external account binding
|
||||
@@ -78,7 +98,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
* `fixed` Upgrade `axios@0.26.1`
|
||||
* `fixed` Upgrade `node-forge@1.3.0` - [CVE-2022-24771](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24771), [CVE-2022-24772](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24772), [CVE-2022-24773](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24773)
|
||||
|
||||
## 4.2.4 (2022-03-19)
|
||||
## v4.2.4 (2022-03-19)
|
||||
|
||||
* `fixed` Use SHA-256 when signing CSRs
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ This module is written to handle communication with a Boulder/Let's Encrypt-styl
|
||||
|
||||
## Compatibility
|
||||
|
||||
| acme-client | Node.js | |
|
||||
| ------------- | --------- | ----------------------------------------- |
|
||||
| v5.x | >= v16 | [Upgrade guide](docs/upgrade-v5.md) |
|
||||
| v4.x | >= v10 | [Changelog](CHANGELOG.md#v400-2020-05-29) |
|
||||
| v3.x | >= v8 | [Changelog](CHANGELOG.md#v300-2019-07-13) |
|
||||
| v2.x | >= v4 | [Changelog](CHANGELOG.md#v200-2018-04-02) |
|
||||
| v1.x | >= v4 | [Changelog](CHANGELOG.md#v100-2017-10-20) |
|
||||
| acme-client | Node.js | |
|
||||
| ----------- | ------- | ----------------------------------------- |
|
||||
| v5.x | >= v16 | [Upgrade guide](docs/upgrade-v5.md) |
|
||||
| v4.x | >= v10 | [Changelog](CHANGELOG.md#v400-2020-05-29) |
|
||||
| v3.x | >= v8 | [Changelog](CHANGELOG.md#v300-2019-07-13) |
|
||||
| v2.x | >= v4 | [Changelog](CHANGELOG.md#v200-2018-04-02) |
|
||||
| v1.x | >= v4 | [Changelog](CHANGELOG.md#v100-2017-10-20) |
|
||||
|
||||
## Table of contents
|
||||
|
||||
@@ -49,7 +49,7 @@ const accountPrivateKey = '<PEM encoded private key>';
|
||||
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: accountPrivateKey
|
||||
accountKey: accountPrivateKey,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -75,8 +75,8 @@ const client = new acme.Client({
|
||||
accountKey: accountPrivateKey,
|
||||
externalAccountBinding: {
|
||||
kid: 'YOUR-EAB-KID',
|
||||
hmacKey: 'YOUR-EAB-HMAC-KEY'
|
||||
}
|
||||
hmacKey: 'YOUR-EAB-HMAC-KEY',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -90,7 +90,7 @@ In some cases, for example with some EAB providers, this account creation step m
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: accountPrivateKey,
|
||||
accountUrl: 'https://acme-v02.api.letsencrypt.org/acme/acct/12345678'
|
||||
accountUrl: 'https://acme-v02.api.letsencrypt.org/acme/acct/12345678',
|
||||
});
|
||||
```
|
||||
|
||||
@@ -113,8 +113,7 @@ const privateRsaKey = await acme.crypto.createPrivateRsaKey();
|
||||
const privateEcdsaKey = await acme.crypto.createPrivateEcdsaKey();
|
||||
|
||||
const [certificateKey, certificateCsr] = await acme.crypto.createCsr({
|
||||
commonName: '*.example.com',
|
||||
altNames: ['example.com']
|
||||
altNames: ['example.com', '*.example.com'],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -139,7 +138,7 @@ const autoOpts = {
|
||||
email: 'test@example.com',
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {},
|
||||
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {}
|
||||
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {},
|
||||
};
|
||||
|
||||
const certificate = await client.auto(autoOpts);
|
||||
@@ -156,7 +155,7 @@ To modify challenge priority, provide a list of challenge types in `challengePri
|
||||
```js
|
||||
await client.auto({
|
||||
...,
|
||||
challengePriority: ['http-01', 'dns-01']
|
||||
challengePriority: ['http-01', 'dns-01'],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -171,7 +170,7 @@ To completely disable `acme-client`s internal challenge verification, enable `sk
|
||||
```js
|
||||
await client.auto({
|
||||
...,
|
||||
skipChallengeVerification: true
|
||||
skipChallengeVerification: true,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -185,14 +184,14 @@ For more fine-grained control you can interact with the ACME API using the metho
|
||||
```js
|
||||
const account = await client.createAccount({
|
||||
termsOfServiceAgreed: true,
|
||||
contact: ['mailto:test@example.com']
|
||||
contact: ['mailto:test@example.com'],
|
||||
});
|
||||
|
||||
const order = await client.createOrder({
|
||||
identifiers: [
|
||||
{ type: 'dns', value: 'example.com' },
|
||||
{ type: 'dns', value: '*.example.com' }
|
||||
]
|
||||
{ type: 'dns', value: '*.example.com' },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -207,7 +206,7 @@ const acme = require('acme-client');
|
||||
|
||||
acme.axios.defaults.proxy = {
|
||||
host: '127.0.0.1',
|
||||
port: 9000
|
||||
port: 9000,
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
23:08
|
||||
00:35
|
||||
|
||||
@@ -63,7 +63,7 @@ Create ACME client instance
|
||||
```js
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: 'Private key goes here'
|
||||
accountKey: 'Private key goes here',
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
@@ -75,7 +75,7 @@ const client = new acme.Client({
|
||||
accountUrl: 'Optional account URL goes here',
|
||||
backoffAttempts: 10,
|
||||
backoffMin: 5000,
|
||||
backoffMax: 30000
|
||||
backoffMax: 30000,
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
@@ -86,8 +86,8 @@ const client = new acme.Client({
|
||||
accountKey: 'Private key goes here',
|
||||
externalAccountBinding: {
|
||||
kid: 'YOUR-EAB-KID',
|
||||
hmacKey: 'YOUR-EAB-HMAC-KEY'
|
||||
}
|
||||
hmacKey: 'YOUR-EAB-HMAC-KEY',
|
||||
},
|
||||
});
|
||||
```
|
||||
<a name="AcmeClient+getTermsOfServiceUrl"></a>
|
||||
@@ -145,7 +145,7 @@ https://datatracker.ietf.org/doc/html/rfc8555#section-7.3
|
||||
Create a new account
|
||||
```js
|
||||
const account = await client.createAccount({
|
||||
termsOfServiceAgreed: true
|
||||
termsOfServiceAgreed: true,
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
@@ -153,7 +153,7 @@ Create a new account with contact info
|
||||
```js
|
||||
const account = await client.createAccount({
|
||||
termsOfServiceAgreed: true,
|
||||
contact: ['mailto:test@example.com']
|
||||
contact: ['mailto:test@example.com'],
|
||||
});
|
||||
```
|
||||
<a name="AcmeClient+updateAccount"></a>
|
||||
@@ -174,7 +174,7 @@ https://datatracker.ietf.org/doc/html/rfc8555#section-7.3.2
|
||||
Update existing account
|
||||
```js
|
||||
const account = await client.updateAccount({
|
||||
contact: ['mailto:foo@example.com']
|
||||
contact: ['mailto:foo@example.com'],
|
||||
});
|
||||
```
|
||||
<a name="AcmeClient+updateAccountKey"></a>
|
||||
@@ -218,8 +218,8 @@ Create a new order
|
||||
const order = await client.createOrder({
|
||||
identifiers: [
|
||||
{ type: 'dns', value: 'example.com' },
|
||||
{ type: 'dns', value: 'test.example.com' }
|
||||
]
|
||||
{ type: 'dns', value: 'test.example.com' },
|
||||
],
|
||||
});
|
||||
```
|
||||
<a name="AcmeClient+getOrder"></a>
|
||||
@@ -452,7 +452,7 @@ Revoke certificate with reason
|
||||
```js
|
||||
const certificate = { ... }; // Previously created certificate
|
||||
const result = await client.revokeCertificate(certificate, {
|
||||
reason: 4
|
||||
reason: 4,
|
||||
});
|
||||
```
|
||||
<a name="AcmeClient+auto"></a>
|
||||
@@ -479,7 +479,7 @@ Auto mode
|
||||
Order a certificate using auto mode
|
||||
```js
|
||||
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
commonName: 'test.example.com'
|
||||
altNames: ['test.example.com'],
|
||||
});
|
||||
|
||||
const certificate = await client.auto({
|
||||
@@ -491,14 +491,14 @@ const certificate = await client.auto({
|
||||
},
|
||||
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {
|
||||
// Clean up challenge here
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
Order a certificate using auto mode with preferred chain
|
||||
```js
|
||||
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
commonName: 'test.example.com'
|
||||
altNames: ['test.example.com'],
|
||||
});
|
||||
|
||||
const certificate = await client.auto({
|
||||
@@ -507,7 +507,7 @@ const certificate = await client.auto({
|
||||
termsOfServiceAgreed: true,
|
||||
preferredChain: 'DST Root CA X3',
|
||||
challengeCreateFn: async () => {},
|
||||
challengeRemoveFn: async () => {}
|
||||
challengeRemoveFn: async () => {},
|
||||
});
|
||||
```
|
||||
<a name="Client"></a>
|
||||
|
||||
@@ -239,29 +239,30 @@ Create a Certificate Signing Request
|
||||
Create a Certificate Signing Request
|
||||
```js
|
||||
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
commonName: 'test.example.com'
|
||||
altNames: ['test.example.com'],
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
Certificate Signing Request with both common and alternative names
|
||||
> *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
|
||||
```js
|
||||
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
keySize: 4096,
|
||||
commonName: 'test.example.com',
|
||||
altNames: ['foo.example.com', 'bar.example.com']
|
||||
altNames: ['foo.example.com', 'bar.example.com'],
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
Certificate Signing Request with additional information
|
||||
```js
|
||||
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
commonName: 'test.example.com',
|
||||
altNames: ['test.example.com'],
|
||||
country: 'US',
|
||||
state: 'California',
|
||||
locality: 'Los Angeles',
|
||||
organization: 'The Company Inc.',
|
||||
organizationUnit: 'IT Department',
|
||||
emailAddress: 'contact@example.com'
|
||||
emailAddress: 'contact@example.com',
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
@@ -270,8 +271,9 @@ Certificate Signing Request with ECDSA private key
|
||||
const certificateKey = await acme.crypto.createPrivateEcdsaKey();
|
||||
|
||||
const [, certificateRequest] = await acme.crypto.createCsr({
|
||||
commonName: 'test.example.com'
|
||||
altNames: ['test.example.com'],
|
||||
}, certificateKey);
|
||||
```
|
||||
<a name="createAlpnCertificate"></a>
|
||||
|
||||
## createAlpnCertificate(authz, keyAuthorization, [keyPem]) ⇒ <code>Promise.<Array.<buffer>></code>
|
||||
@@ -298,6 +300,7 @@ Create a ALPN certificate with ECDSA private key
|
||||
```js
|
||||
const alpnKey = await acme.crypto.createPrivateEcdsaKey();
|
||||
const [, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization, alpnKey);
|
||||
```
|
||||
<a name="isAlpnCertificateAuthorizationValid"></a>
|
||||
|
||||
## isAlpnCertificateAuthorizationValid(certPem, keyAuthorization) ⇒ <code>boolean</code>
|
||||
|
||||
@@ -222,29 +222,30 @@ Create a Certificate Signing Request
|
||||
Create a Certificate Signing Request
|
||||
```js
|
||||
const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
commonName: 'test.example.com'
|
||||
altNames: ['test.example.com'],
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
Certificate Signing Request with both common and alternative names
|
||||
> *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
|
||||
```js
|
||||
const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
keySize: 4096,
|
||||
commonName: 'test.example.com',
|
||||
altNames: ['foo.example.com', 'bar.example.com']
|
||||
altNames: ['foo.example.com', 'bar.example.com'],
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
Certificate Signing Request with additional information
|
||||
```js
|
||||
const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
commonName: 'test.example.com',
|
||||
altNames: ['test.example.com'],
|
||||
country: 'US',
|
||||
state: 'California',
|
||||
locality: 'Los Angeles',
|
||||
organization: 'The Company Inc.',
|
||||
organizationUnit: 'IT Department',
|
||||
emailAddress: 'contact@example.com'
|
||||
emailAddress: 'contact@example.com',
|
||||
});
|
||||
```
|
||||
**Example**
|
||||
@@ -253,5 +254,5 @@ Certificate Signing Request with predefined private key
|
||||
const certificateKey = await acme.forge.createPrivateKey();
|
||||
|
||||
const [, certificateRequest] = await acme.forge.createCsr({
|
||||
commonName: 'test.example.com'
|
||||
altNames: ['test.example.com'],
|
||||
}, certificateKey);
|
||||
|
||||
@@ -8,7 +8,6 @@ function log(m) {
|
||||
process.stdout.write(`${m}\n`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function used to satisfy an ACME challenge
|
||||
*
|
||||
@@ -25,7 +24,6 @@ async function challengeCreateFn(authz, challenge, keyAuthorization) {
|
||||
log(keyAuthorization);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function used to remove an ACME challenge response
|
||||
*
|
||||
@@ -41,30 +39,29 @@ async function challengeRemoveFn(authz, challenge, keyAuthorization) {
|
||||
log(keyAuthorization);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
|
||||
module.exports = async function() {
|
||||
module.exports = async () => {
|
||||
/* Init client */
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: await acme.crypto.createPrivateKey()
|
||||
accountKey: await acme.crypto.createPrivateKey(),
|
||||
});
|
||||
|
||||
/* Register account */
|
||||
await client.createAccount({
|
||||
termsOfServiceAgreed: true,
|
||||
contact: ['mailto:test@example.com']
|
||||
contact: ['mailto:test@example.com'],
|
||||
});
|
||||
|
||||
/* Place new order */
|
||||
const order = await client.createOrder({
|
||||
identifiers: [
|
||||
{ type: 'dns', value: 'example.com' },
|
||||
{ type: 'dns', value: '*.example.com' }
|
||||
]
|
||||
{ type: 'dns', value: '*.example.com' },
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -138,8 +135,7 @@ module.exports = async function() {
|
||||
|
||||
/* Finalize order */
|
||||
const [key, csr] = await acme.crypto.createCsr({
|
||||
commonName: '*.example.com',
|
||||
altNames: ['example.com']
|
||||
altNames: ['example.com', '*.example.com'],
|
||||
});
|
||||
|
||||
const finalized = await client.finalizeOrder(order, csr);
|
||||
|
||||
@@ -9,7 +9,6 @@ function log(m) {
|
||||
process.stdout.write(`${m}\n`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function used to satisfy an ACME challenge
|
||||
*
|
||||
@@ -47,7 +46,6 @@ async function challengeCreateFn(authz, challenge, keyAuthorization) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function used to remove an ACME challenge response
|
||||
*
|
||||
@@ -80,25 +78,24 @@ async function challengeRemoveFn(authz, challenge, keyAuthorization) {
|
||||
|
||||
/* Replace this */
|
||||
log(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`);
|
||||
// await dnsProvider.removeRecord(dnsRecord, 'TXT');
|
||||
// await dnsProvider.removeRecord(dnsRecord, 'TXT', recordValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
|
||||
module.exports = async function() {
|
||||
module.exports = async () => {
|
||||
/* Init client */
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: await acme.crypto.createPrivateKey()
|
||||
accountKey: await acme.crypto.createPrivateKey(),
|
||||
});
|
||||
|
||||
/* Create CSR */
|
||||
const [key, csr] = await acme.crypto.createCsr({
|
||||
commonName: 'example.com'
|
||||
altNames: ['example.com'],
|
||||
});
|
||||
|
||||
/* Certificate */
|
||||
@@ -107,7 +104,7 @@ module.exports = async function() {
|
||||
email: 'test@example.com',
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn,
|
||||
challengeRemoveFn
|
||||
challengeRemoveFn,
|
||||
});
|
||||
|
||||
/* Done */
|
||||
|
||||
@@ -19,7 +19,6 @@ function log(m) {
|
||||
process.stdout.write(`${(new Date()).toISOString()} ${m}\n`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
@@ -33,18 +32,16 @@ function log(m) {
|
||||
log('Initializing ACME client');
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: await acme.crypto.createPrivateKey()
|
||||
accountKey: await acme.crypto.createPrivateKey(),
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Order wildcard certificate
|
||||
*/
|
||||
|
||||
log(`Creating CSR for ${WILDCARD_DOMAIN}`);
|
||||
const [key, csr] = await acme.crypto.createCsr({
|
||||
commonName: WILDCARD_DOMAIN,
|
||||
altNames: [`*.${WILDCARD_DOMAIN}`]
|
||||
altNames: [WILDCARD_DOMAIN, `*.${WILDCARD_DOMAIN}`],
|
||||
});
|
||||
|
||||
log(`Ordering certificate for ${WILDCARD_DOMAIN}`);
|
||||
@@ -60,12 +57,11 @@ function log(m) {
|
||||
challengeRemoveFn: (authz, challenge, keyAuthorization) => {
|
||||
/* TODO: Implement this */
|
||||
log(`[TODO] Remove TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
log(`Certificate for ${WILDCARD_DOMAIN} created successfully`);
|
||||
|
||||
|
||||
/**
|
||||
* HTTPS server
|
||||
*/
|
||||
@@ -78,7 +74,7 @@ function log(m) {
|
||||
|
||||
const httpsServer = https.createServer({
|
||||
key,
|
||||
cert
|
||||
cert,
|
||||
}, requestListener);
|
||||
|
||||
httpsServer.listen(HTTPS_SERVER_PORT, () => {
|
||||
|
||||
@@ -23,7 +23,6 @@ function log(m) {
|
||||
process.stdout.write(`${(new Date()).toISOString()} ${m}\n`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* On-demand certificate generation using http-01
|
||||
*/
|
||||
@@ -52,7 +51,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
/* Create CSR */
|
||||
log(`Creating CSR for ${servername}`);
|
||||
const [key, csr] = await acme.crypto.createCsr({
|
||||
commonName: servername
|
||||
altNames: [servername],
|
||||
});
|
||||
|
||||
/* Order certificate */
|
||||
@@ -67,7 +66,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
},
|
||||
challengeRemoveFn: (authz, challenge) => {
|
||||
delete challengeResponses[challenge.token];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/* Done, store certificate */
|
||||
@@ -77,7 +76,6 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
return certificateStore[servername];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
@@ -91,10 +89,9 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
log('Initializing ACME client');
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: await acme.crypto.createPrivateKey()
|
||||
accountKey: await acme.crypto.createPrivateKey(),
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* HTTP server
|
||||
*/
|
||||
@@ -129,7 +126,6 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
log(`HTTP server listening on port ${HTTP_SERVER_PORT}`);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* HTTPS server
|
||||
*/
|
||||
@@ -158,7 +154,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
log(`[ERROR] ${e.message}`);
|
||||
cb(e.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
}, requestListener);
|
||||
|
||||
httpsServer.listen(HTTPS_SERVER_PORT, () => {
|
||||
|
||||
@@ -22,7 +22,6 @@ function log(m) {
|
||||
process.stdout.write(`${(new Date()).toISOString()} ${m}\n`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* On-demand certificate generation using tls-alpn-01
|
||||
*/
|
||||
@@ -51,7 +50,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
/* Create CSR */
|
||||
log(`Creating CSR for ${servername}`);
|
||||
const [key, csr] = await acme.crypto.createCsr({
|
||||
commonName: servername
|
||||
altNames: [servername],
|
||||
});
|
||||
|
||||
/* Order certificate */
|
||||
@@ -66,7 +65,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
},
|
||||
challengeRemoveFn: (authz) => {
|
||||
delete alpnResponses[authz.identifier.value];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/* Done, store certificate */
|
||||
@@ -76,7 +75,6 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
return certificateStore[servername];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
@@ -90,10 +88,9 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
log('Initializing ACME client');
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: await acme.crypto.createPrivateKey()
|
||||
accountKey: await acme.crypto.createPrivateKey(),
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* ALPN responder
|
||||
*/
|
||||
@@ -118,14 +115,14 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
log(`Found ALPN certificate for ${servername}, serving secure context`);
|
||||
cb(null, tls.createSecureContext({
|
||||
key: alpnResponses[servername][0],
|
||||
cert: alpnResponses[servername][1]
|
||||
cert: alpnResponses[servername][1],
|
||||
}));
|
||||
}
|
||||
catch (e) {
|
||||
log(`[ERROR] ${e.message}`);
|
||||
cb(e.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/* Terminate once TLS handshake has been established */
|
||||
@@ -137,7 +134,6 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
log(`ALPN responder listening on port ${ALPN_RESPONDER_PORT}`);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* HTTPS server
|
||||
*/
|
||||
@@ -166,7 +162,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
|
||||
log(`[ERROR] ${e.message}`);
|
||||
cb(e.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
}, requestListener);
|
||||
|
||||
httpsServer.listen(HTTPS_SERVER_PORT, () => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.20.12",
|
||||
"version": "1.20.16",
|
||||
"main": "src/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"license": "MIT",
|
||||
@@ -16,24 +16,24 @@
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@peculiar/x509": "^1.9.7",
|
||||
"@peculiar/x509": "^1.10.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^1.7.2",
|
||||
"debug": "^4.1.1",
|
||||
"https-proxy-agent": "^7.0.4",
|
||||
"node-forge": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.5",
|
||||
"@types/node": "^20.12.12",
|
||||
"chai": "^4.4.1",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"eslint": "^8.56.0",
|
||||
"chai-as-promised": "^7.1.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"jsdoc-to-markdown": "^8.0.0",
|
||||
"mocha": "^10.2.0",
|
||||
"nock": "^13.5.0",
|
||||
"tsd": "^0.30.4",
|
||||
"jsdoc-to-markdown": "^8.0.1",
|
||||
"mocha": "^10.4.0",
|
||||
"nock": "^13.5.4",
|
||||
"tsd": "^0.31.0",
|
||||
"typescript": "^4.8.4",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
const util = require('./util');
|
||||
|
||||
|
||||
/**
|
||||
* AcmeApi
|
||||
*
|
||||
@@ -18,7 +17,6 @@ class AcmeApi {
|
||||
this.accountUrl = accountUrl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get account URL
|
||||
*
|
||||
@@ -34,7 +32,6 @@ class AcmeApi {
|
||||
return this.accountUrl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ACME API request
|
||||
*
|
||||
@@ -59,7 +56,6 @@ class AcmeApi {
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ACME API request by resource name helper
|
||||
*
|
||||
@@ -78,7 +74,6 @@ class AcmeApi {
|
||||
return this.apiRequest(resourceUrl, payload, validStatusCodes, { includeJwsKid, includeExternalAccountBinding });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Terms of Service URL if available
|
||||
*
|
||||
@@ -91,7 +86,6 @@ class AcmeApi {
|
||||
return this.http.getMetaField('termsOfService');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new account
|
||||
*
|
||||
@@ -104,7 +98,7 @@ class AcmeApi {
|
||||
async createAccount(data) {
|
||||
const resp = await this.apiResourceRequest('newAccount', data, [200, 201], {
|
||||
includeJwsKid: false,
|
||||
includeExternalAccountBinding: (data.onlyReturnExisting !== true)
|
||||
includeExternalAccountBinding: (data.onlyReturnExisting !== true),
|
||||
});
|
||||
|
||||
/* Set account URL */
|
||||
@@ -115,7 +109,6 @@ class AcmeApi {
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update account
|
||||
*
|
||||
@@ -129,7 +122,6 @@ class AcmeApi {
|
||||
return this.apiRequest(this.getAccountUrl(), data, [200, 202]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update account key
|
||||
*
|
||||
@@ -143,7 +135,6 @@ class AcmeApi {
|
||||
return this.apiResourceRequest('keyChange', data, [200]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new order
|
||||
*
|
||||
@@ -157,7 +148,6 @@ class AcmeApi {
|
||||
return this.apiResourceRequest('newOrder', data, [201]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get order
|
||||
*
|
||||
@@ -171,7 +161,6 @@ class AcmeApi {
|
||||
return this.apiRequest(url, null, [200]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finalize order
|
||||
*
|
||||
@@ -186,7 +175,6 @@ class AcmeApi {
|
||||
return this.apiRequest(url, data, [200]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get identifier authorization
|
||||
*
|
||||
@@ -200,7 +188,6 @@ class AcmeApi {
|
||||
return this.apiRequest(url, null, [200]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update identifier authorization
|
||||
*
|
||||
@@ -215,7 +202,6 @@ class AcmeApi {
|
||||
return this.apiRequest(url, data, [200]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Complete challenge
|
||||
*
|
||||
@@ -230,7 +216,6 @@ class AcmeApi {
|
||||
return this.apiRequest(url, data, [200]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Revoke certificate
|
||||
*
|
||||
@@ -245,6 +230,5 @@ class AcmeApi {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Export API */
|
||||
module.exports = AcmeApi;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
const { readCsrDomains } = require('./crypto');
|
||||
const { log } = require('./logger');
|
||||
const { wait } = require('./wait');
|
||||
|
||||
const defaultOpts = {
|
||||
csr: null,
|
||||
@@ -13,10 +14,9 @@ const defaultOpts = {
|
||||
skipChallengeVerification: false,
|
||||
challengePriority: ['http-01', 'dns-01'],
|
||||
challengeCreateFn: async () => { throw new Error('Missing challengeCreateFn()'); },
|
||||
challengeRemoveFn: async () => { throw new Error('Missing challengeRemoveFn()'); }
|
||||
challengeRemoveFn: async () => { throw new Error('Missing challengeRemoveFn()'); },
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* ACME client auto mode
|
||||
*
|
||||
@@ -25,8 +25,8 @@ const defaultOpts = {
|
||||
* @returns {Promise<buffer>} Certificate
|
||||
*/
|
||||
|
||||
module.exports = async function(client, userOpts) {
|
||||
const opts = Object.assign({}, defaultOpts, userOpts);
|
||||
module.exports = async (client, userOpts) => {
|
||||
const opts = { ...defaultOpts, ...userOpts };
|
||||
const accountPayload = { termsOfServiceAgreed: opts.termsOfServiceAgreed };
|
||||
|
||||
if (!Buffer.isBuffer(opts.csr)) {
|
||||
@@ -37,7 +37,6 @@ module.exports = async function(client, userOpts) {
|
||||
accountPayload.contact = [`mailto:${opts.email}`];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register account
|
||||
*/
|
||||
@@ -53,7 +52,6 @@ module.exports = async function(client, userOpts) {
|
||||
await client.createAccount(accountPayload);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse domains from CSR
|
||||
*/
|
||||
@@ -64,7 +62,6 @@ module.exports = async function(client, userOpts) {
|
||||
|
||||
log(`[auto] Resolved ${uniqueDomains.length} unique domains from parsing the Certificate Signing Request`);
|
||||
|
||||
|
||||
/**
|
||||
* Place order
|
||||
*/
|
||||
@@ -76,7 +73,6 @@ module.exports = async function(client, userOpts) {
|
||||
|
||||
log(`[auto] Placed certificate order successfully, received ${authorizations.length} identity authorizations`);
|
||||
|
||||
|
||||
/**
|
||||
* Resolve and satisfy challenges
|
||||
*/
|
||||
@@ -118,7 +114,18 @@ module.exports = async function(client, userOpts) {
|
||||
let recordItem = null;
|
||||
try {
|
||||
recordItem = await opts.challengeCreateFn(authz, challenge, keyAuthorization);
|
||||
|
||||
log(`[auto] [${d}] challengeCreateFn success`);
|
||||
log(`[auto] [${d}] add challengeRemoveFn()`);
|
||||
clearTasks.push(async () => {
|
||||
/* Trigger challengeRemoveFn(), suppress errors */
|
||||
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
|
||||
try {
|
||||
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem);
|
||||
}
|
||||
catch (e) {
|
||||
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
|
||||
}
|
||||
});
|
||||
// throw new Error('测试异常');
|
||||
/* Challenge verification */
|
||||
if (opts.skipChallengeVerification === true) {
|
||||
@@ -140,19 +147,6 @@ module.exports = async function(client, userOpts) {
|
||||
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
log(`[auto] [${d}] add challengeRemoveFn()`);
|
||||
clearTasks.push(async () => {
|
||||
/* Trigger challengeRemoveFn(), suppress errors */
|
||||
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
|
||||
try {
|
||||
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem);
|
||||
}
|
||||
catch (e) {
|
||||
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
/* Deactivate pending authz when unable to complete challenge */
|
||||
@@ -177,7 +171,6 @@ module.exports = async function(client, userOpts) {
|
||||
await challengeFunc(authz);
|
||||
});
|
||||
|
||||
|
||||
function runAllPromise(tasks) {
|
||||
let promise = Promise.resolve();
|
||||
tasks.forEach((task) => {
|
||||
@@ -186,14 +179,20 @@ module.exports = async function(client, userOpts) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
// function runPromisePa(tasks) {
|
||||
// return Promise.all(tasks.map((task) => task()));
|
||||
// }
|
||||
|
||||
async function runPromisePa(tasks) {
|
||||
const results = [];
|
||||
// eslint-disable-next-line no-await-in-loop,no-restricted-syntax
|
||||
for (const task of tasks) {
|
||||
results.push(task());
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await wait(10000);
|
||||
}
|
||||
return Promise.all(results);
|
||||
}
|
||||
|
||||
try {
|
||||
log('开始challenge');
|
||||
await runAllPromise(challengePromises);
|
||||
await runPromisePa(challengePromises);
|
||||
|
||||
log('challenge结束');
|
||||
|
||||
@@ -210,11 +209,18 @@ module.exports = async function(client, userOpts) {
|
||||
}
|
||||
catch (e) {
|
||||
log('证书申请失败');
|
||||
throw e;
|
||||
log(e);
|
||||
throw new Error(`证书申请失败:${e.message}`);
|
||||
}
|
||||
finally {
|
||||
log(`清理challenge痕迹,length:${clearTasks.length}`);
|
||||
await runAllPromise(clearTasks);
|
||||
try {
|
||||
await runAllPromise(clearTasks);
|
||||
}
|
||||
catch (e) {
|
||||
log('清理challenge失败');
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
||||
// try {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
const axios = require('axios');
|
||||
const pkg = require('./../package.json');
|
||||
|
||||
|
||||
/**
|
||||
* Instance
|
||||
*/
|
||||
@@ -19,9 +18,8 @@ instance.defaults.headers.common['User-Agent'] = `node-${pkg.name}/${pkg.version
|
||||
instance.defaults.acmeSettings = {
|
||||
httpChallengePort: 80,
|
||||
httpsChallengePort: 443,
|
||||
tlsAlpnChallengePort: 443
|
||||
tlsAlpnChallengePort: 443,
|
||||
};
|
||||
|
||||
// instance.defaults.proxy = {
|
||||
// host: '192.168.34.139',
|
||||
// port: 10811
|
||||
@@ -35,7 +33,6 @@ instance.defaults.acmeSettings = {
|
||||
|
||||
instance.defaults.adapter = 'http';
|
||||
|
||||
|
||||
/**
|
||||
* Export instance
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,6 @@ const verify = require('./verify');
|
||||
const util = require('./util');
|
||||
const auto = require('./auto');
|
||||
|
||||
|
||||
/**
|
||||
* ACME states
|
||||
*
|
||||
@@ -24,7 +23,6 @@ const validStates = ['ready', 'valid'];
|
||||
const pendingStates = ['pending', 'processing'];
|
||||
const invalidStates = ['invalid'];
|
||||
|
||||
|
||||
/**
|
||||
* Default options
|
||||
*
|
||||
@@ -38,10 +36,9 @@ const defaultOpts = {
|
||||
externalAccountBinding: {},
|
||||
backoffAttempts: 10,
|
||||
backoffMin: 5000,
|
||||
backoffMax: 30000
|
||||
backoffMax: 30000,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* AcmeClient
|
||||
*
|
||||
@@ -61,7 +58,7 @@ const defaultOpts = {
|
||||
* ```js
|
||||
* const client = new acme.Client({
|
||||
* directoryUrl: acme.directory.letsencrypt.staging,
|
||||
* accountKey: 'Private key goes here'
|
||||
* accountKey: 'Private key goes here',
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
@@ -73,7 +70,7 @@ const defaultOpts = {
|
||||
* accountUrl: 'Optional account URL goes here',
|
||||
* backoffAttempts: 10,
|
||||
* backoffMin: 5000,
|
||||
* backoffMax: 30000
|
||||
* backoffMax: 30000,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
@@ -84,8 +81,8 @@ const defaultOpts = {
|
||||
* accountKey: 'Private key goes here',
|
||||
* externalAccountBinding: {
|
||||
* kid: 'YOUR-EAB-KID',
|
||||
* hmacKey: 'YOUR-EAB-HMAC-KEY'
|
||||
* }
|
||||
* hmacKey: 'YOUR-EAB-HMAC-KEY',
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@@ -96,19 +93,17 @@ class AcmeClient {
|
||||
opts.accountKey = Buffer.from(opts.accountKey);
|
||||
}
|
||||
|
||||
this.opts = Object.assign({}, defaultOpts, opts);
|
||||
|
||||
this.opts = { ...defaultOpts, ...opts };
|
||||
this.backoffOpts = {
|
||||
attempts: this.opts.backoffAttempts,
|
||||
min: this.opts.backoffMin,
|
||||
max: this.opts.backoffMax
|
||||
max: this.opts.backoffMax,
|
||||
};
|
||||
|
||||
this.http = new HttpClient(this.opts.directoryUrl, this.opts.accountKey, this.opts.externalAccountBinding);
|
||||
this.api = new AcmeApi(this.http, this.opts.accountUrl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Terms of Service URL if available
|
||||
*
|
||||
@@ -128,7 +123,6 @@ class AcmeClient {
|
||||
return this.api.getTermsOfServiceUrl();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get current account URL
|
||||
*
|
||||
@@ -150,7 +144,6 @@ class AcmeClient {
|
||||
return this.api.getAccountUrl();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new account
|
||||
*
|
||||
@@ -162,7 +155,7 @@ class AcmeClient {
|
||||
* @example Create a new account
|
||||
* ```js
|
||||
* const account = await client.createAccount({
|
||||
* termsOfServiceAgreed: true
|
||||
* termsOfServiceAgreed: true,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
@@ -170,7 +163,7 @@ class AcmeClient {
|
||||
* ```js
|
||||
* const account = await client.createAccount({
|
||||
* termsOfServiceAgreed: true,
|
||||
* contact: ['mailto:test@example.com']
|
||||
* contact: ['mailto:test@example.com'],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@@ -196,7 +189,6 @@ class AcmeClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update existing account
|
||||
*
|
||||
@@ -208,7 +200,7 @@ class AcmeClient {
|
||||
* @example Update existing account
|
||||
* ```js
|
||||
* const account = await client.updateAccount({
|
||||
* contact: ['mailto:foo@example.com']
|
||||
* contact: ['mailto:foo@example.com'],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@@ -236,7 +228,6 @@ class AcmeClient {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update account private key
|
||||
*
|
||||
@@ -282,7 +273,6 @@ class AcmeClient {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new order
|
||||
*
|
||||
@@ -296,8 +286,8 @@ class AcmeClient {
|
||||
* const order = await client.createOrder({
|
||||
* identifiers: [
|
||||
* { type: 'dns', value: 'example.com' },
|
||||
* { type: 'dns', value: 'test.example.com' }
|
||||
* ]
|
||||
* { type: 'dns', value: 'test.example.com' },
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@@ -314,7 +304,6 @@ class AcmeClient {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refresh order object from CA
|
||||
*
|
||||
@@ -376,7 +365,6 @@ class AcmeClient {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get identifier authorizations from order
|
||||
*
|
||||
@@ -406,7 +394,6 @@ class AcmeClient {
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deactivate identifier authorization
|
||||
*
|
||||
@@ -427,10 +414,7 @@ class AcmeClient {
|
||||
throw new Error('Unable to deactivate identifier authorization, URL not found');
|
||||
}
|
||||
|
||||
const data = {
|
||||
status: 'deactivated'
|
||||
};
|
||||
|
||||
const data = { status: 'deactivated' };
|
||||
const resp = await this.api.updateAuthorization(authz.url, data);
|
||||
|
||||
/* Add URL to response */
|
||||
@@ -438,7 +422,6 @@ class AcmeClient {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get key authorization for ACME challenge
|
||||
*
|
||||
@@ -480,7 +463,6 @@ class AcmeClient {
|
||||
throw new Error(`Unable to produce key authorization, unknown challenge type: ${challenge.type}`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify that ACME challenge is satisfied
|
||||
*
|
||||
@@ -515,7 +497,6 @@ class AcmeClient {
|
||||
return util.retry(verifyFn, this.backoffOpts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify CA that challenge has been completed
|
||||
*
|
||||
@@ -536,7 +517,6 @@ class AcmeClient {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wait for ACME provider to verify status on a order, authorization or challenge
|
||||
*
|
||||
@@ -593,7 +573,6 @@ class AcmeClient {
|
||||
return util.retry(verifyFn, this.backoffOpts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get certificate from ACME order
|
||||
*
|
||||
@@ -640,7 +619,6 @@ class AcmeClient {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Revoke certificate
|
||||
*
|
||||
@@ -660,7 +638,7 @@ class AcmeClient {
|
||||
* ```js
|
||||
* const certificate = { ... }; // Previously created certificate
|
||||
* const result = await client.revokeCertificate(certificate, {
|
||||
* reason: 4
|
||||
* reason: 4,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@@ -671,7 +649,6 @@ class AcmeClient {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Auto mode
|
||||
*
|
||||
@@ -689,7 +666,7 @@ class AcmeClient {
|
||||
* @example Order a certificate using auto mode
|
||||
* ```js
|
||||
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
* commonName: 'test.example.com'
|
||||
* altNames: ['test.example.com'],
|
||||
* });
|
||||
*
|
||||
* const certificate = await client.auto({
|
||||
@@ -701,14 +678,14 @@ class AcmeClient {
|
||||
* },
|
||||
* challengeRemoveFn: async (authz, challenge, keyAuthorization) => {
|
||||
* // Clean up challenge here
|
||||
* }
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example Order a certificate using auto mode with preferred chain
|
||||
* ```js
|
||||
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
* commonName: 'test.example.com'
|
||||
* altNames: ['test.example.com'],
|
||||
* });
|
||||
*
|
||||
* const certificate = await client.auto({
|
||||
@@ -717,7 +694,7 @@ class AcmeClient {
|
||||
* termsOfServiceAgreed: true,
|
||||
* preferredChain: 'DST Root CA X3',
|
||||
* challengeCreateFn: async () => {},
|
||||
* challengeRemoveFn: async () => {}
|
||||
* challengeRemoveFn: async () => {},
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@@ -727,6 +704,5 @@ class AcmeClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Export client */
|
||||
module.exports = AcmeClient;
|
||||
|
||||
@@ -13,7 +13,6 @@ const forge = require('node-forge');
|
||||
|
||||
const generateKeyPair = promisify(forge.pki.rsa.generateKeyPair);
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to parse forge object from PEM encoded string
|
||||
*
|
||||
@@ -54,7 +53,6 @@ function forgeObjectFromPem(input) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse domain names from a certificate or CSR
|
||||
*
|
||||
@@ -93,11 +91,10 @@ function parseDomains(obj) {
|
||||
|
||||
return {
|
||||
commonName,
|
||||
altNames
|
||||
altNames,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a private RSA key
|
||||
*
|
||||
@@ -123,7 +120,6 @@ async function createPrivateKey(size = 2048) {
|
||||
|
||||
exports.createPrivateKey = createPrivateKey;
|
||||
|
||||
|
||||
/**
|
||||
* Create public key from a private RSA key
|
||||
*
|
||||
@@ -136,14 +132,13 @@ exports.createPrivateKey = createPrivateKey;
|
||||
* ```
|
||||
*/
|
||||
|
||||
exports.createPublicKey = async function(key) {
|
||||
exports.createPublicKey = async (key) => {
|
||||
const privateKey = forge.pki.privateKeyFromPem(key);
|
||||
const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
|
||||
const pemKey = forge.pki.publicKeyToPem(publicKey);
|
||||
return Buffer.from(pemKey);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse body of PEM encoded object from buffer or string
|
||||
* If multiple objects are chained, the first body will be returned
|
||||
@@ -157,7 +152,6 @@ exports.getPemBody = (str) => {
|
||||
return forge.util.encode64(msg.body);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Split chain of PEM encoded objects from buffer or string into array
|
||||
*
|
||||
@@ -167,7 +161,6 @@ exports.getPemBody = (str) => {
|
||||
|
||||
exports.splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode);
|
||||
|
||||
|
||||
/**
|
||||
* Get modulus
|
||||
*
|
||||
@@ -182,7 +175,7 @@ exports.splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode);
|
||||
* ```
|
||||
*/
|
||||
|
||||
exports.getModulus = async function(input) {
|
||||
exports.getModulus = async (input) => {
|
||||
if (!Buffer.isBuffer(input)) {
|
||||
input = Buffer.from(input);
|
||||
}
|
||||
@@ -191,7 +184,6 @@ exports.getModulus = async function(input) {
|
||||
return Buffer.from(forge.util.hexToBytes(obj.n.toString(16)), 'binary');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get public exponent
|
||||
*
|
||||
@@ -206,7 +198,7 @@ exports.getModulus = async function(input) {
|
||||
* ```
|
||||
*/
|
||||
|
||||
exports.getPublicExponent = async function(input) {
|
||||
exports.getPublicExponent = async (input) => {
|
||||
if (!Buffer.isBuffer(input)) {
|
||||
input = Buffer.from(input);
|
||||
}
|
||||
@@ -215,7 +207,6 @@ exports.getPublicExponent = async function(input) {
|
||||
return Buffer.from(forge.util.hexToBytes(obj.e.toString(16)), 'binary');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Read domains from a Certificate Signing Request
|
||||
*
|
||||
@@ -231,7 +222,7 @@ exports.getPublicExponent = async function(input) {
|
||||
* ```
|
||||
*/
|
||||
|
||||
exports.readCsrDomains = async function(csr) {
|
||||
exports.readCsrDomains = async (csr) => {
|
||||
if (!Buffer.isBuffer(csr)) {
|
||||
csr = Buffer.from(csr);
|
||||
}
|
||||
@@ -240,7 +231,6 @@ exports.readCsrDomains = async function(csr) {
|
||||
return parseDomains(obj);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Read information from a certificate
|
||||
*
|
||||
@@ -260,7 +250,7 @@ exports.readCsrDomains = async function(csr) {
|
||||
* ```
|
||||
*/
|
||||
|
||||
exports.readCertificateInfo = async function(cert) {
|
||||
exports.readCertificateInfo = async (cert) => {
|
||||
if (!Buffer.isBuffer(cert)) {
|
||||
cert = Buffer.from(cert);
|
||||
}
|
||||
@@ -270,15 +260,14 @@ exports.readCertificateInfo = async function(cert) {
|
||||
|
||||
return {
|
||||
issuer: {
|
||||
commonName: issuerCn ? issuerCn.value : null
|
||||
commonName: issuerCn ? issuerCn.value : null,
|
||||
},
|
||||
domains: parseDomains(obj),
|
||||
notAfter: obj.validity.notAfter,
|
||||
notBefore: obj.validity.notBefore
|
||||
notBefore: obj.validity.notBefore,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine ASN.1 type for CSR subject short name
|
||||
* Note: https://datatracker.ietf.org/doc/html/rfc5280
|
||||
@@ -299,7 +288,6 @@ function getCsrValueTagClass(shortName) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create array of short names and values for Certificate Signing Request subjects
|
||||
*
|
||||
@@ -319,7 +307,6 @@ function createCsrSubject(subjectObj) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create array of alt names for Certificate Signing Requests
|
||||
* Note: https://github.com/digitalbazaar/forge/blob/dfdde475677a8a25c851e33e8f81dca60d90cfb9/lib/x509.js#L1444-L1454
|
||||
@@ -336,7 +323,6 @@ function formatCsrAltNames(altNames) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a Certificate Signing Request
|
||||
*
|
||||
@@ -356,29 +342,30 @@ function formatCsrAltNames(altNames) {
|
||||
* @example Create a Certificate Signing Request
|
||||
* ```js
|
||||
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
* commonName: 'test.example.com'
|
||||
* altNames: ['test.example.com'],
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example Certificate Signing Request with both common and alternative names
|
||||
* > *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
|
||||
* ```js
|
||||
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
* keySize: 4096,
|
||||
* commonName: 'test.example.com',
|
||||
* altNames: ['foo.example.com', 'bar.example.com']
|
||||
* altNames: ['foo.example.com', 'bar.example.com'],
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example Certificate Signing Request with additional information
|
||||
* ```js
|
||||
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
* commonName: 'test.example.com',
|
||||
* altNames: ['test.example.com'],
|
||||
* country: 'US',
|
||||
* state: 'California',
|
||||
* locality: 'Los Angeles',
|
||||
* organization: 'The Company Inc.',
|
||||
* organizationUnit: 'IT Department',
|
||||
* emailAddress: 'contact@example.com'
|
||||
* emailAddress: 'contact@example.com',
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
@@ -387,11 +374,11 @@ function formatCsrAltNames(altNames) {
|
||||
* const certificateKey = await acme.forge.createPrivateKey();
|
||||
*
|
||||
* const [, certificateRequest] = await acme.forge.createCsr({
|
||||
* commonName: 'test.example.com'
|
||||
* altNames: ['test.example.com'],
|
||||
* }, certificateKey);
|
||||
*/
|
||||
|
||||
exports.createCsr = async function(data, key = null) {
|
||||
exports.createCsr = async (data, key = null) => {
|
||||
if (!key) {
|
||||
key = await createPrivateKey(data.keySize);
|
||||
}
|
||||
@@ -423,7 +410,7 @@ exports.createCsr = async function(data, key = null) {
|
||||
L: data.locality,
|
||||
O: data.organization,
|
||||
OU: data.organizationUnit,
|
||||
E: data.emailAddress
|
||||
E: data.emailAddress,
|
||||
});
|
||||
|
||||
csr.setSubject(subject);
|
||||
@@ -434,8 +421,8 @@ exports.createCsr = async function(data, key = null) {
|
||||
name: 'extensionRequest',
|
||||
extensions: [{
|
||||
name: 'subjectAltName',
|
||||
altNames: formatCsrAltNames(data.altNames)
|
||||
}]
|
||||
altNames: formatCsrAltNames(data.altNames),
|
||||
}],
|
||||
}]);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ const subjectAltNameOID = '2.5.29.17';
|
||||
/* id-pe-acmeIdentifier - https://datatracker.ietf.org/doc/html/rfc8737#section-6.1 */
|
||||
const alpnAcmeIdentifierOID = '1.3.6.1.5.5.7.1.31';
|
||||
|
||||
|
||||
/**
|
||||
* Determine key type and info by attempting to derive public key
|
||||
*
|
||||
@@ -35,7 +34,7 @@ function getKeyInfo(keyPem) {
|
||||
const result = {
|
||||
isRSA: false,
|
||||
isECDSA: false,
|
||||
publicKey: crypto.createPublicKey(keyPem)
|
||||
publicKey: crypto.createPublicKey(keyPem),
|
||||
};
|
||||
|
||||
if (result.publicKey.asymmetricKeyType === 'rsa') {
|
||||
@@ -51,7 +50,6 @@ function getKeyInfo(keyPem) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a private RSA key
|
||||
*
|
||||
@@ -74,8 +72,8 @@ async function createPrivateRsaKey(modulusLength = 2048) {
|
||||
modulusLength,
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem'
|
||||
}
|
||||
format: 'pem',
|
||||
},
|
||||
});
|
||||
|
||||
return Buffer.from(pair.privateKey);
|
||||
@@ -83,7 +81,6 @@ async function createPrivateRsaKey(modulusLength = 2048) {
|
||||
|
||||
exports.createPrivateRsaKey = createPrivateRsaKey;
|
||||
|
||||
|
||||
/**
|
||||
* Alias of `createPrivateRsaKey()`
|
||||
*
|
||||
@@ -92,7 +89,6 @@ exports.createPrivateRsaKey = createPrivateRsaKey;
|
||||
|
||||
exports.createPrivateKey = createPrivateRsaKey;
|
||||
|
||||
|
||||
/**
|
||||
* Generate a private ECDSA key
|
||||
*
|
||||
@@ -115,14 +111,13 @@ exports.createPrivateEcdsaKey = async (namedCurve = 'P-256') => {
|
||||
namedCurve,
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem'
|
||||
}
|
||||
format: 'pem',
|
||||
},
|
||||
});
|
||||
|
||||
return Buffer.from(pair.privateKey);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a public key derived from a RSA or ECDSA key
|
||||
*
|
||||
@@ -140,13 +135,12 @@ exports.getPublicKey = (keyPem) => {
|
||||
|
||||
const publicKey = info.publicKey.export({
|
||||
type: info.isECDSA ? 'spki' : 'pkcs1',
|
||||
format: 'pem'
|
||||
format: 'pem',
|
||||
});
|
||||
|
||||
return Buffer.from(publicKey);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a JSON Web Key derived from a RSA or ECDSA key
|
||||
*
|
||||
@@ -163,7 +157,7 @@ exports.getPublicKey = (keyPem) => {
|
||||
|
||||
function getJwk(keyPem) {
|
||||
const jwk = crypto.createPublicKey(keyPem).export({
|
||||
format: 'jwk'
|
||||
format: 'jwk',
|
||||
});
|
||||
|
||||
/* Sort keys */
|
||||
@@ -175,7 +169,6 @@ function getJwk(keyPem) {
|
||||
|
||||
exports.getJwk = getJwk;
|
||||
|
||||
|
||||
/**
|
||||
* Produce CryptoKeyPair and signing algorithm from a PEM encoded private key
|
||||
*
|
||||
@@ -191,7 +184,7 @@ async function getWebCryptoKeyPair(keyPem) {
|
||||
/* Signing algorithm */
|
||||
const sigalg = {
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: { name: 'SHA-256' }
|
||||
hash: { name: 'SHA-256' },
|
||||
};
|
||||
|
||||
if (info.isECDSA) {
|
||||
@@ -215,7 +208,6 @@ async function getWebCryptoKeyPair(keyPem) {
|
||||
return [{ privateKey, publicKey }, sigalg];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Split chain of PEM encoded objects from string into array
|
||||
*
|
||||
@@ -235,7 +227,6 @@ function splitPemChain(chainPem) {
|
||||
|
||||
exports.splitPemChain = splitPemChain;
|
||||
|
||||
|
||||
/**
|
||||
* Parse body of PEM encoded object and return a Base64URL string
|
||||
* If multiple objects are chained, the first body will be returned
|
||||
@@ -256,7 +247,6 @@ exports.getPemBodyAsB64u = (pem) => {
|
||||
return Buffer.from(dec).toString('base64url');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse domains from a certificate or CSR
|
||||
*
|
||||
@@ -277,11 +267,10 @@ function parseDomains(input) {
|
||||
|
||||
return {
|
||||
commonName,
|
||||
altNames
|
||||
altNames,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read domains from a Certificate Signing Request
|
||||
*
|
||||
@@ -307,7 +296,6 @@ exports.readCsrDomains = (csrPem) => {
|
||||
return parseDomains(csr);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Read information from a certificate
|
||||
* If multiple certificates are chained, the first will be read
|
||||
@@ -338,15 +326,14 @@ exports.readCertificateInfo = (certPem) => {
|
||||
|
||||
return {
|
||||
issuer: {
|
||||
commonName: cert.issuerName.getField('CN').pop() || null
|
||||
commonName: cert.issuerName.getField('CN').pop() || null,
|
||||
},
|
||||
domains: parseDomains(cert),
|
||||
notBefore: cert.notBefore,
|
||||
notAfter: cert.notAfter
|
||||
notAfter: cert.notAfter,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine ASN.1 character string type for CSR subject field name
|
||||
*
|
||||
@@ -369,7 +356,6 @@ function getCsrAsn1CharStringType(field) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create array of subject fields for a Certificate Signing Request
|
||||
*
|
||||
@@ -391,7 +377,6 @@ function createCsrSubject(input) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create x509 subject alternate name extension
|
||||
*
|
||||
@@ -409,7 +394,6 @@ function createSubjectAltNameExtension(altNames) {
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a Certificate Signing Request
|
||||
*
|
||||
@@ -429,29 +413,30 @@ function createSubjectAltNameExtension(altNames) {
|
||||
* @example Create a Certificate Signing Request
|
||||
* ```js
|
||||
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
* commonName: 'test.example.com'
|
||||
* altNames: ['test.example.com'],
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example Certificate Signing Request with both common and alternative names
|
||||
* > *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
|
||||
* ```js
|
||||
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
* keySize: 4096,
|
||||
* commonName: 'test.example.com',
|
||||
* altNames: ['foo.example.com', 'bar.example.com']
|
||||
* altNames: ['foo.example.com', 'bar.example.com'],
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example Certificate Signing Request with additional information
|
||||
* ```js
|
||||
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
|
||||
* commonName: 'test.example.com',
|
||||
* altNames: ['test.example.com'],
|
||||
* country: 'US',
|
||||
* state: 'California',
|
||||
* locality: 'Los Angeles',
|
||||
* organization: 'The Company Inc.',
|
||||
* organizationUnit: 'IT Department',
|
||||
* emailAddress: 'contact@example.com'
|
||||
* emailAddress: 'contact@example.com',
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
@@ -460,8 +445,9 @@ function createSubjectAltNameExtension(altNames) {
|
||||
* const certificateKey = await acme.crypto.createPrivateEcdsaKey();
|
||||
*
|
||||
* const [, certificateRequest] = await acme.crypto.createCsr({
|
||||
* commonName: 'test.example.com'
|
||||
* altNames: ['test.example.com'],
|
||||
* }, certificateKey);
|
||||
* ```
|
||||
*/
|
||||
|
||||
exports.createCsr = async (data, keyPem = null) => {
|
||||
@@ -489,7 +475,7 @@ exports.createCsr = async (data, keyPem = null) => {
|
||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment), // eslint-disable-line no-bitwise
|
||||
|
||||
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
|
||||
createSubjectAltNameExtension(data.altNames)
|
||||
createSubjectAltNameExtension(data.altNames),
|
||||
];
|
||||
|
||||
/* Create CSR */
|
||||
@@ -504,8 +490,8 @@ exports.createCsr = async (data, keyPem = null) => {
|
||||
L: data.locality,
|
||||
O: data.organization,
|
||||
OU: data.organizationUnit,
|
||||
E: data.emailAddress
|
||||
})
|
||||
E: data.emailAddress,
|
||||
}),
|
||||
});
|
||||
|
||||
/* Done */
|
||||
@@ -513,7 +499,6 @@ exports.createCsr = async (data, keyPem = null) => {
|
||||
return [keyPem, Buffer.from(pem)];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a self-signed ALPN certificate for TLS-ALPN-01 challenges
|
||||
*
|
||||
@@ -533,6 +518,7 @@ exports.createCsr = async (data, keyPem = null) => {
|
||||
* ```js
|
||||
* const alpnKey = await acme.crypto.createPrivateEcdsaKey();
|
||||
* const [, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization, alpnKey);
|
||||
* ```
|
||||
*/
|
||||
|
||||
exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) => {
|
||||
@@ -564,7 +550,7 @@ exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) =
|
||||
await x509.SubjectKeyIdentifierExtension.create(keys.publicKey),
|
||||
|
||||
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
|
||||
createSubjectAltNameExtension([commonName])
|
||||
createSubjectAltNameExtension([commonName]),
|
||||
];
|
||||
|
||||
/* ALPN extension */
|
||||
@@ -581,8 +567,8 @@ exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) =
|
||||
notBefore: now,
|
||||
notAfter: now,
|
||||
name: createCsrSubject({
|
||||
CN: commonName
|
||||
})
|
||||
CN: commonName,
|
||||
}),
|
||||
});
|
||||
|
||||
/* Done */
|
||||
@@ -590,7 +576,6 @@ exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) =
|
||||
return [keyPem, Buffer.from(pem)];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Validate that a ALPN certificate contains the expected key authorization
|
||||
*
|
||||
|
||||
@@ -40,7 +40,6 @@ class HttpClient {
|
||||
this.jwk = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* HTTP request
|
||||
*
|
||||
@@ -70,7 +69,6 @@ class HttpClient {
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensure provider directory exists
|
||||
*
|
||||
@@ -95,7 +93,6 @@ class HttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get JSON Web Key
|
||||
*
|
||||
@@ -110,7 +107,6 @@ class HttpClient {
|
||||
return this.jwk;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get nonce from directory API endpoint
|
||||
*
|
||||
@@ -130,7 +126,6 @@ class HttpClient {
|
||||
return resp.headers['replay-nonce'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get URL for a directory resource
|
||||
*
|
||||
@@ -148,7 +143,6 @@ class HttpClient {
|
||||
return this.directory[resource];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get directory meta field
|
||||
*
|
||||
@@ -166,7 +160,6 @@ class HttpClient {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare HTTP request body for signature
|
||||
*
|
||||
@@ -199,11 +192,10 @@ class HttpClient {
|
||||
/* Body */
|
||||
return {
|
||||
payload: payload ? Buffer.from(JSON.stringify(payload)).toString('base64url') : '',
|
||||
protected: Buffer.from(JSON.stringify(header)).toString('base64url')
|
||||
protected: Buffer.from(JSON.stringify(header)).toString('base64url'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create JWS HTTP request body using HMAC
|
||||
*
|
||||
@@ -226,7 +218,6 @@ class HttpClient {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create JWS HTTP request body using RSA or ECC
|
||||
*
|
||||
@@ -267,13 +258,12 @@ class HttpClient {
|
||||
result.signature = signer.sign({
|
||||
key: this.accountKey,
|
||||
padding: RSA_PKCS1_PADDING,
|
||||
dsaEncoding: 'ieee-p1363'
|
||||
dsaEncoding: 'ieee-p1363',
|
||||
}, 'base64url');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Signed HTTP request
|
||||
*
|
||||
@@ -309,7 +299,7 @@ class HttpClient {
|
||||
const data = this.createSignedBody(url, payload, { nonce, kid });
|
||||
const resp = await this.request(url, 'post', { data });
|
||||
|
||||
/* Retry on bad nonce - https://datatracker.ietf.org/doc/html/draft-ietf-acme-acme-10#section-6.4 */
|
||||
/* Retry on bad nonce - https://datatracker.ietf.org/doc/html/rfc8555#section-6.5 */
|
||||
if (resp.data && resp.data.type && (resp.status === 400) && (resp.data.type === 'urn:ietf:params:acme:error:badNonce') && (attempts < this.maxBadNonceRetries)) {
|
||||
nonce = resp.headers['replay-nonce'] || null;
|
||||
attempts += 1;
|
||||
@@ -323,6 +313,5 @@ class HttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Export client */
|
||||
module.exports = HttpClient;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
exports.Client = require('./client');
|
||||
|
||||
|
||||
/**
|
||||
* Directory URLs
|
||||
*/
|
||||
@@ -12,18 +11,17 @@ exports.Client = require('./client');
|
||||
exports.directory = {
|
||||
buypass: {
|
||||
staging: 'https://api.test4.buypass.no/acme/directory',
|
||||
production: 'https://api.buypass.com/acme/directory'
|
||||
production: 'https://api.buypass.com/acme/directory',
|
||||
},
|
||||
letsencrypt: {
|
||||
staging: 'https://acme-staging-v02.api.letsencrypt.org/directory',
|
||||
production: 'https://acme-v02.api.letsencrypt.org/directory'
|
||||
production: 'https://acme-v02.api.letsencrypt.org/directory',
|
||||
},
|
||||
zerossl: {
|
||||
production: 'https://acme.zerossl.com/v2/DV90'
|
||||
}
|
||||
production: 'https://acme.zerossl.com/v2/DV90',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Crypto
|
||||
*/
|
||||
@@ -31,14 +29,12 @@ exports.directory = {
|
||||
exports.crypto = require('./crypto');
|
||||
exports.forge = require('./crypto/forge');
|
||||
|
||||
|
||||
/**
|
||||
* Axios
|
||||
*/
|
||||
|
||||
exports.axios = require('./axios');
|
||||
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,6 @@ const debug = require('debug')('acme-client');
|
||||
|
||||
let logger = () => {};
|
||||
|
||||
|
||||
/**
|
||||
* Set logger function
|
||||
*
|
||||
@@ -17,11 +16,10 @@ exports.setLogger = (fn) => {
|
||||
logger = fn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log message
|
||||
*
|
||||
* @param {string} Message
|
||||
* @param {string} msg Message
|
||||
*/
|
||||
|
||||
exports.log = (msg) => {
|
||||
|
||||
@@ -7,7 +7,6 @@ const dns = require('dns').promises;
|
||||
const { readCertificateInfo, splitPemChain } = require('./crypto');
|
||||
const { log } = require('./logger');
|
||||
|
||||
|
||||
/**
|
||||
* Exponential backoff
|
||||
*
|
||||
@@ -26,7 +25,6 @@ class Backoff {
|
||||
this.attempts = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get backoff duration
|
||||
*
|
||||
@@ -40,7 +38,6 @@ class Backoff {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retry promise
|
||||
*
|
||||
@@ -70,7 +67,6 @@ async function retryPromise(fn, attempts, backoff) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retry promise
|
||||
*
|
||||
@@ -87,7 +83,6 @@ function retry(fn, { attempts = 5, min = 5000, max = 30000 } = {}) {
|
||||
return retryPromise(fn, attempts, backoff);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse URLs from link header
|
||||
*
|
||||
@@ -107,7 +102,6 @@ function parseLinkHeader(header, rel = 'alternate') {
|
||||
return results.filter((r) => r);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find certificate chain with preferred issuer common name
|
||||
* - If issuer is found in multiple chains, the closest to root wins
|
||||
@@ -157,7 +151,6 @@ function findCertificateChainForIssuer(chains, issuer) {
|
||||
return chains[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find and format error in response object
|
||||
*
|
||||
@@ -178,7 +171,6 @@ function formatResponseError(resp) {
|
||||
return result.replace(/\n/g, '');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve root domain name by looking for SOA record
|
||||
*
|
||||
@@ -204,7 +196,6 @@ async function resolveDomainBySoaRecord(recordName) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get DNS resolver using domains authoritative NS records
|
||||
*
|
||||
@@ -245,7 +236,6 @@ async function getAuthoritativeDnsResolver(recordName) {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to retrieve TLS ALPN certificate from peer
|
||||
*
|
||||
@@ -267,7 +257,7 @@ async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
|
||||
port,
|
||||
servername: host,
|
||||
rejectUnauthorized: false,
|
||||
ALPNProtocols: ['acme-tls/1']
|
||||
ALPNProtocols: ['acme-tls/1'],
|
||||
});
|
||||
|
||||
socket.setTimeout(timeout);
|
||||
@@ -299,7 +289,6 @@ async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export utils
|
||||
*/
|
||||
@@ -310,5 +299,5 @@ module.exports = {
|
||||
findCertificateChainForIssuer,
|
||||
formatResponseError,
|
||||
getAuthoritativeDnsResolver,
|
||||
retrieveTlsAlpnCertificate
|
||||
retrieveTlsAlpnCertificate,
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ const axios = require('./axios');
|
||||
const util = require('./util');
|
||||
const { isAlpnCertificateAuthorizationValid } = require('./crypto');
|
||||
|
||||
|
||||
/**
|
||||
* Verify ACME HTTP challenge
|
||||
*
|
||||
@@ -43,7 +42,6 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Walk DNS until TXT records are found
|
||||
*/
|
||||
@@ -81,7 +79,6 @@ async function walkDnsChallengeRecord(recordName, resolver = dns) {
|
||||
throw new Error(`No TXT records found for name: ${recordName}`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify ACME DNS challenge
|
||||
*
|
||||
@@ -121,7 +118,6 @@ async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify ACME TLS ALPN challenge
|
||||
*
|
||||
@@ -149,7 +145,6 @@ async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export API
|
||||
*/
|
||||
@@ -157,5 +152,5 @@ async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
|
||||
module.exports = {
|
||||
'http-01': verifyHttpChallenge,
|
||||
'dns-01': verifyDnsChallenge,
|
||||
'tls-alpn-01': verifyTlsAlpnChallenge
|
||||
'tls-alpn-01': verifyTlsAlpnChallenge,
|
||||
};
|
||||
|
||||
9
packages/core/acme-client/src/wait.js
Normal file
@@ -0,0 +1,9 @@
|
||||
async function wait(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
wait
|
||||
};
|
||||
@@ -16,7 +16,6 @@ const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
||||
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
||||
const tlsAlpnPort = axios.defaults.acmeSettings.tlsAlpnChallengePort || 443;
|
||||
|
||||
|
||||
describe('pebble', () => {
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||
|
||||
@@ -39,18 +38,16 @@ describe('pebble', () => {
|
||||
const testTlsAlpn01ChallengeHost = `${uuid()}.${domainName}`;
|
||||
const testTlsAlpn01ChallengeValue = uuid();
|
||||
|
||||
|
||||
/**
|
||||
* Pebble CTS required
|
||||
*/
|
||||
|
||||
before(function() {
|
||||
before(function () {
|
||||
if (!cts.isEnabled()) {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* DNS mocking
|
||||
*/
|
||||
@@ -92,7 +89,6 @@ describe('pebble', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* HTTP-01 challenge response
|
||||
*/
|
||||
@@ -118,7 +114,6 @@ describe('pebble', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* HTTPS-01 challenge response
|
||||
*/
|
||||
@@ -143,7 +138,7 @@ describe('pebble', () => {
|
||||
/* Assert HTTP 302 */
|
||||
const resp = await axios.get(`http://${testHttps01ChallengeHost}:${httpPort}/.well-known/acme-challenge/${testHttps01ChallengeToken}`, {
|
||||
maxRedirects: 0,
|
||||
validateStatus: null
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(resp.status, 302);
|
||||
@@ -165,7 +160,6 @@ describe('pebble', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* DNS-01 challenge response
|
||||
*/
|
||||
@@ -188,7 +182,6 @@ describe('pebble', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* TLS-ALPN-01 challenge response
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,6 @@ const axios = require('./../src/axios');
|
||||
const HttpClient = require('./../src/http');
|
||||
const pkg = require('./../package.json');
|
||||
|
||||
|
||||
describe('http', () => {
|
||||
let testClient;
|
||||
|
||||
@@ -20,7 +19,6 @@ describe('http', () => {
|
||||
const defaultUaEndpoint = `http://${uuid()}.example.com`;
|
||||
const customUaEndpoint = `http://${uuid()}.example.com`;
|
||||
|
||||
|
||||
/**
|
||||
* HTTP mocking
|
||||
*/
|
||||
@@ -43,7 +41,6 @@ describe('http', () => {
|
||||
axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
@@ -52,7 +49,6 @@ describe('http', () => {
|
||||
testClient = new HttpClient();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* HTTP verbs
|
||||
*/
|
||||
@@ -65,7 +61,6 @@ describe('http', () => {
|
||||
assert.strictEqual(resp.data, 'ok');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* User-Agent
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
const { assert } = require('chai');
|
||||
const logger = require('./../src/logger');
|
||||
|
||||
|
||||
describe('logger', () => {
|
||||
let lastLogMessage = null;
|
||||
|
||||
@@ -13,7 +12,6 @@ describe('logger', () => {
|
||||
lastLogMessage = msg;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
@@ -23,7 +21,6 @@ describe('logger', () => {
|
||||
assert.isNull(lastLogMessage);
|
||||
});
|
||||
|
||||
|
||||
it('should log with custom logger', () => {
|
||||
logger.setLogger(customLoggerFn);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ const verify = require('./../src/verify');
|
||||
|
||||
const domainName = process.env.ACME_DOMAIN_NAME || 'example.com';
|
||||
|
||||
|
||||
describe('verify', () => {
|
||||
const challengeTypes = ['http-01', 'dns-01'];
|
||||
|
||||
@@ -30,18 +29,16 @@ describe('verify', () => {
|
||||
const testTlsAlpn01Challenge = { type: 'dns-01', status: 'pending', token: uuid() };
|
||||
const testTlsAlpn01Key = uuid();
|
||||
|
||||
|
||||
/**
|
||||
* Pebble CTS required
|
||||
*/
|
||||
|
||||
before(function() {
|
||||
before(function () {
|
||||
if (!cts.isEnabled()) {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* API
|
||||
*/
|
||||
@@ -50,7 +47,6 @@ describe('verify', () => {
|
||||
assert.containsAllKeys(verify, challengeTypes);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* http-01
|
||||
*/
|
||||
@@ -81,7 +77,6 @@ describe('verify', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* https-01
|
||||
*/
|
||||
@@ -102,7 +97,6 @@ describe('verify', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* dns-01
|
||||
*/
|
||||
@@ -133,7 +127,6 @@ describe('verify', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* tls-alpn-01
|
||||
*/
|
||||
|
||||
@@ -9,10 +9,9 @@ const spec = require('./spec');
|
||||
const forge = require('./../src/crypto/forge');
|
||||
|
||||
const cryptoEngines = {
|
||||
forge
|
||||
forge,
|
||||
};
|
||||
|
||||
|
||||
describe('crypto-legacy', () => {
|
||||
let testPemKey;
|
||||
let testCert;
|
||||
@@ -28,7 +27,6 @@ describe('crypto-legacy', () => {
|
||||
const testCertPath = path.join(__dirname, 'fixtures', 'certificate.crt');
|
||||
const testSanCertPath = path.join(__dirname, 'fixtures', 'san-certificate.crt');
|
||||
|
||||
|
||||
/**
|
||||
* Fixtures
|
||||
*/
|
||||
@@ -50,7 +48,6 @@ describe('crypto-legacy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Engines
|
||||
*/
|
||||
@@ -62,7 +59,6 @@ describe('crypto-legacy', () => {
|
||||
let testNonCnCsr;
|
||||
let testNonAsciiCsr;
|
||||
|
||||
|
||||
/**
|
||||
* Key generation
|
||||
*/
|
||||
@@ -83,14 +79,13 @@ describe('crypto-legacy', () => {
|
||||
publicKeyStore.push(key.toString().replace(/[\r\n]/gm, ''));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Certificate Signing Request
|
||||
*/
|
||||
|
||||
it('should generate a csr', async () => {
|
||||
const [key, csr] = await engine.createCsr({
|
||||
commonName: testCsrDomain
|
||||
commonName: testCsrDomain,
|
||||
});
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -102,7 +97,7 @@ describe('crypto-legacy', () => {
|
||||
it('should generate a san csr', async () => {
|
||||
const [key, csr] = await engine.createCsr({
|
||||
commonName: testSanCsrDomains[0],
|
||||
altNames: testSanCsrDomains.slice(1, testSanCsrDomains.length)
|
||||
altNames: testSanCsrDomains.slice(1, testSanCsrDomains.length),
|
||||
});
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -113,7 +108,7 @@ describe('crypto-legacy', () => {
|
||||
|
||||
it('should generate a csr without common name', async () => {
|
||||
const [key, csr] = await engine.createCsr({
|
||||
altNames: testSanCsrDomains
|
||||
altNames: testSanCsrDomains,
|
||||
});
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -126,7 +121,7 @@ describe('crypto-legacy', () => {
|
||||
const [key, csr] = await engine.createCsr({
|
||||
commonName: testCsrDomain,
|
||||
organization: '大安區',
|
||||
organizationUnit: '中文部門'
|
||||
organizationUnit: '中文部門',
|
||||
});
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -167,7 +162,6 @@ describe('crypto-legacy', () => {
|
||||
assert.deepStrictEqual(result.altNames, [testCsrDomain]);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Certificate
|
||||
*/
|
||||
@@ -188,7 +182,6 @@ describe('crypto-legacy', () => {
|
||||
assert.deepEqual(info.domains.altNames, testSanCsrDomains.slice(1, testSanCsrDomains.length));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* PEM utils
|
||||
*/
|
||||
@@ -214,7 +207,6 @@ describe('crypto-legacy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Modulus and exponent
|
||||
*/
|
||||
@@ -246,7 +238,6 @@ describe('crypto-legacy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Verify identical results
|
||||
*/
|
||||
|
||||
@@ -50,7 +50,6 @@ dGVzdGluZ3Rlc3Rpbmd0ZXN0aW5ndGVzdGluZ3Rlc3Rpbmd0ZXN0aW5ndGVzdGluZ3Rlc3Rpbmd0ZXN0
|
||||
-----END TEST-----
|
||||
`;
|
||||
|
||||
|
||||
describe('crypto', () => {
|
||||
const testCsrDomain = 'example.com';
|
||||
const testSanCsrDomains = ['example.com', 'test.example.com', 'abc.example.com'];
|
||||
@@ -58,7 +57,6 @@ describe('crypto', () => {
|
||||
const testCertPath = path.join(__dirname, 'fixtures', 'certificate.crt');
|
||||
const testSanCertPath = path.join(__dirname, 'fixtures', 'san-certificate.crt');
|
||||
|
||||
|
||||
/**
|
||||
* Key types
|
||||
*/
|
||||
@@ -68,24 +66,23 @@ describe('crypto', () => {
|
||||
createKeyFns: {
|
||||
s1024: () => crypto.createPrivateRsaKey(1024),
|
||||
s2048: () => crypto.createPrivateRsaKey(),
|
||||
s4096: () => crypto.createPrivateRsaKey(4096)
|
||||
s4096: () => crypto.createPrivateRsaKey(4096),
|
||||
},
|
||||
jwkSpecFn: spec.jwk.rsa
|
||||
jwkSpecFn: spec.jwk.rsa,
|
||||
},
|
||||
ecdsa: {
|
||||
createKeyFns: {
|
||||
p256: () => crypto.createPrivateEcdsaKey(),
|
||||
p384: () => crypto.createPrivateEcdsaKey('P-384'),
|
||||
p521: () => crypto.createPrivateEcdsaKey('P-521')
|
||||
p521: () => crypto.createPrivateEcdsaKey('P-521'),
|
||||
},
|
||||
jwkSpecFn: spec.jwk.ecdsa
|
||||
}
|
||||
jwkSpecFn: spec.jwk.ecdsa,
|
||||
},
|
||||
}).forEach(([name, { createKeyFns, jwkSpecFn }]) => {
|
||||
describe(name, () => {
|
||||
const testPrivateKeys = {};
|
||||
const testPublicKeys = {};
|
||||
|
||||
|
||||
/**
|
||||
* Iterate through all generator variations
|
||||
*/
|
||||
@@ -97,7 +94,6 @@ describe('crypto', () => {
|
||||
let testNonAsciiCsr;
|
||||
let testAlpnCertificate;
|
||||
|
||||
|
||||
/**
|
||||
* Keys and JWK
|
||||
*/
|
||||
@@ -132,14 +128,13 @@ describe('crypto', () => {
|
||||
jwkSpecFn(jwk);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Certificate Signing Request
|
||||
*/
|
||||
|
||||
it(`${n}/should generate a csr`, async () => {
|
||||
const [key, csr] = await crypto.createCsr({
|
||||
commonName: testCsrDomain
|
||||
commonName: testCsrDomain,
|
||||
}, testPrivateKeys[n]);
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -151,7 +146,7 @@ describe('crypto', () => {
|
||||
it(`${n}/should generate a san csr`, async () => {
|
||||
const [key, csr] = await crypto.createCsr({
|
||||
commonName: testSanCsrDomains[0],
|
||||
altNames: testSanCsrDomains.slice(1, testSanCsrDomains.length)
|
||||
altNames: testSanCsrDomains.slice(1, testSanCsrDomains.length),
|
||||
}, testPrivateKeys[n]);
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -162,7 +157,7 @@ describe('crypto', () => {
|
||||
|
||||
it(`${n}/should generate a csr without common name`, async () => {
|
||||
const [key, csr] = await crypto.createCsr({
|
||||
altNames: testSanCsrDomains
|
||||
altNames: testSanCsrDomains,
|
||||
}, testPrivateKeys[n]);
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -175,7 +170,7 @@ describe('crypto', () => {
|
||||
const [key, csr] = await crypto.createCsr({
|
||||
commonName: testCsrDomain,
|
||||
organization: '大安區',
|
||||
organizationUnit: '中文部門'
|
||||
organizationUnit: '中文部門',
|
||||
}, testPrivateKeys[n]);
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -186,7 +181,7 @@ describe('crypto', () => {
|
||||
|
||||
it(`${n}/should generate a csr with key as string`, async () => {
|
||||
const [key, csr] = await crypto.createCsr({
|
||||
commonName: testCsrDomain
|
||||
commonName: testCsrDomain,
|
||||
}, testPrivateKeys[n].toString());
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
@@ -195,11 +190,10 @@ describe('crypto', () => {
|
||||
|
||||
it(`${n}/should throw with invalid key`, async () => {
|
||||
await assert.isRejected(crypto.createCsr({
|
||||
commonName: testCsrDomain
|
||||
commonName: testCsrDomain,
|
||||
}, testPublicKeys[n]));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Domain and info resolver
|
||||
*/
|
||||
@@ -243,7 +237,6 @@ describe('crypto', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* ALPN
|
||||
*/
|
||||
@@ -284,7 +277,6 @@ describe('crypto', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Common functionality
|
||||
*/
|
||||
@@ -294,7 +286,6 @@ describe('crypto', () => {
|
||||
let testCert;
|
||||
let testSanCert;
|
||||
|
||||
|
||||
it('should read private key fixture', async () => {
|
||||
testPemKey = await fs.readFile(testKeyPath);
|
||||
assert.isTrue(Buffer.isBuffer(testPemKey));
|
||||
@@ -310,21 +301,19 @@ describe('crypto', () => {
|
||||
assert.isTrue(Buffer.isBuffer(testSanCert));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* CSR with auto-generated key
|
||||
*/
|
||||
|
||||
it('should generate a csr with default key', async () => {
|
||||
const [key, csr] = await crypto.createCsr({
|
||||
commonName: testCsrDomain
|
||||
commonName: testCsrDomain,
|
||||
});
|
||||
|
||||
assert.isTrue(Buffer.isBuffer(key));
|
||||
assert.isTrue(Buffer.isBuffer(csr));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Certificate
|
||||
*/
|
||||
@@ -352,7 +341,6 @@ describe('crypto', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* ALPN
|
||||
*/
|
||||
@@ -365,7 +353,6 @@ describe('crypto', () => {
|
||||
assert.isTrue(Buffer.isBuffer(cert));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* PEM utils
|
||||
*/
|
||||
|
||||
@@ -20,35 +20,32 @@ const clientOpts = {
|
||||
directoryUrl,
|
||||
backoffAttempts: 5,
|
||||
backoffMin: 1000,
|
||||
backoffMax: 5000
|
||||
backoffMax: 5000,
|
||||
};
|
||||
|
||||
if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY) {
|
||||
clientOpts.externalAccountBinding = {
|
||||
kid: process.env.ACME_EAB_KID,
|
||||
hmacKey: process.env.ACME_EAB_HMAC_KEY
|
||||
hmacKey: process.env.ACME_EAB_HMAC_KEY,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
describe('client', () => {
|
||||
const testDomain = `${uuid()}.${domainName}`;
|
||||
const testDomainAlpn = `${uuid()}.${domainName}`;
|
||||
const testDomainWildcard = `*.${testDomain}`;
|
||||
const testContact = `mailto:test-${uuid()}@nope.com`;
|
||||
|
||||
|
||||
/**
|
||||
* Pebble CTS required
|
||||
*/
|
||||
|
||||
before(function() {
|
||||
before(function () {
|
||||
if (!cts.isEnabled()) {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Key types
|
||||
*/
|
||||
@@ -58,18 +55,18 @@ describe('client', () => {
|
||||
createKeyFn: () => acme.crypto.createPrivateRsaKey(),
|
||||
createKeyAltFns: {
|
||||
s1024: () => acme.crypto.createPrivateRsaKey(1024),
|
||||
s4096: () => acme.crypto.createPrivateRsaKey(4096)
|
||||
s4096: () => acme.crypto.createPrivateRsaKey(4096),
|
||||
},
|
||||
jwkSpecFn: spec.jwk.rsa
|
||||
jwkSpecFn: spec.jwk.rsa,
|
||||
},
|
||||
ecdsa: {
|
||||
createKeyFn: () => acme.crypto.createPrivateEcdsaKey(),
|
||||
createKeyAltFns: {
|
||||
p384: () => acme.crypto.createPrivateEcdsaKey('P-384'),
|
||||
p521: () => acme.crypto.createPrivateEcdsaKey('P-521')
|
||||
p521: () => acme.crypto.createPrivateEcdsaKey('P-521'),
|
||||
},
|
||||
jwkSpecFn: spec.jwk.ecdsa
|
||||
}
|
||||
jwkSpecFn: spec.jwk.ecdsa,
|
||||
},
|
||||
}).forEach(([name, { createKeyFn, createKeyAltFns, jwkSpecFn }]) => {
|
||||
describe(name, () => {
|
||||
let testIssuers;
|
||||
@@ -97,7 +94,6 @@ describe('client', () => {
|
||||
let testCertificateAlpn;
|
||||
let testCertificateWildcard;
|
||||
|
||||
|
||||
/**
|
||||
* Fixtures
|
||||
*/
|
||||
@@ -114,11 +110,11 @@ describe('client', () => {
|
||||
|
||||
it('should generate certificate signing request', async () => {
|
||||
[, testCsr] = await acme.crypto.createCsr({ commonName: testDomain }, await createKeyFn());
|
||||
[, testCsrAlpn] = await acme.crypto.createCsr({ commonName: testDomainAlpn }, await createKeyFn());
|
||||
[, testCsrWildcard] = await acme.crypto.createCsr({ commonName: testDomainWildcard }, await createKeyFn());
|
||||
[, testCsrAlpn] = await acme.crypto.createCsr({ altNames: [testDomainAlpn] }, await createKeyFn());
|
||||
[, testCsrWildcard] = await acme.crypto.createCsr({ altNames: [testDomainWildcard] }, await createKeyFn());
|
||||
});
|
||||
|
||||
it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
||||
it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
|
||||
if (!capAlternateCertRoots) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -134,7 +130,6 @@ describe('client', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Initialize clients
|
||||
*/
|
||||
@@ -142,7 +137,7 @@ describe('client', () => {
|
||||
it('should initialize client', () => {
|
||||
testClient = new acme.Client({
|
||||
...clientOpts,
|
||||
accountKey: testAccountKey
|
||||
accountKey: testAccountKey,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -151,12 +146,11 @@ describe('client', () => {
|
||||
jwkSpecFn(jwk);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Terms of Service
|
||||
*/
|
||||
|
||||
it('should produce tos url [ACME_CAP_META_TOS_FIELD]', async function() {
|
||||
it('should produce tos url [ACME_CAP_META_TOS_FIELD]', async function () {
|
||||
if (!capMetaTosField) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -165,7 +159,7 @@ describe('client', () => {
|
||||
assert.isString(tos);
|
||||
});
|
||||
|
||||
it('should not produce tos url [!ACME_CAP_META_TOS_FIELD]', async function() {
|
||||
it('should not produce tos url [!ACME_CAP_META_TOS_FIELD]', async function () {
|
||||
if (capMetaTosField) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -174,12 +168,11 @@ describe('client', () => {
|
||||
assert.isNull(tos);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Create account
|
||||
*/
|
||||
|
||||
it('should refuse account creation without tos [ACME_CAP_META_TOS_FIELD]', async function() {
|
||||
it('should refuse account creation without tos [ACME_CAP_META_TOS_FIELD]', async function () {
|
||||
if (!capMetaTosField) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -187,7 +180,7 @@ describe('client', () => {
|
||||
await assert.isRejected(testClient.createAccount());
|
||||
});
|
||||
|
||||
it('should refuse account creation without eab [ACME_CAP_EAB_ENABLED]', async function() {
|
||||
it('should refuse account creation without eab [ACME_CAP_EAB_ENABLED]', async function () {
|
||||
if (!capEabEnabled) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -195,17 +188,17 @@ describe('client', () => {
|
||||
const client = new acme.Client({
|
||||
...clientOpts,
|
||||
accountKey: testAccountKey,
|
||||
externalAccountBinding: null
|
||||
externalAccountBinding: null,
|
||||
});
|
||||
|
||||
await assert.isRejected(client.createAccount({
|
||||
termsOfServiceAgreed: true
|
||||
termsOfServiceAgreed: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should create an account', async () => {
|
||||
testAccount = await testClient.createAccount({
|
||||
termsOfServiceAgreed: true
|
||||
termsOfServiceAgreed: true,
|
||||
});
|
||||
|
||||
spec.rfc8555.account(testAccount);
|
||||
@@ -217,7 +210,6 @@ describe('client', () => {
|
||||
assert.isString(testAccountUrl);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Create account with alternate key sizes
|
||||
*/
|
||||
@@ -226,11 +218,11 @@ describe('client', () => {
|
||||
it(`should create account with key=${k}`, async () => {
|
||||
const client = new acme.Client({
|
||||
...clientOpts,
|
||||
accountKey: await altKeyFn()
|
||||
accountKey: await altKeyFn(),
|
||||
});
|
||||
|
||||
const account = await client.createAccount({
|
||||
termsOfServiceAgreed: true
|
||||
termsOfServiceAgreed: true,
|
||||
});
|
||||
|
||||
spec.rfc8555.account(account);
|
||||
@@ -238,7 +230,6 @@ describe('client', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Find existing account using secondary client
|
||||
*/
|
||||
@@ -246,22 +237,22 @@ describe('client', () => {
|
||||
it('should throw when trying to find account using invalid account key', async () => {
|
||||
const client = new acme.Client({
|
||||
...clientOpts,
|
||||
accountKey: testAccountSecondaryKey
|
||||
accountKey: testAccountSecondaryKey,
|
||||
});
|
||||
|
||||
await assert.isRejected(client.createAccount({
|
||||
onlyReturnExisting: true
|
||||
onlyReturnExisting: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should find existing account using account key', async () => {
|
||||
const client = new acme.Client({
|
||||
...clientOpts,
|
||||
accountKey: testAccountKey
|
||||
accountKey: testAccountKey,
|
||||
});
|
||||
|
||||
const account = await client.createAccount({
|
||||
onlyReturnExisting: true
|
||||
onlyReturnExisting: true,
|
||||
});
|
||||
|
||||
spec.rfc8555.account(account);
|
||||
@@ -269,7 +260,6 @@ describe('client', () => {
|
||||
assert.deepStrictEqual(account.key, testAccount.key);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Account URL
|
||||
*/
|
||||
@@ -278,7 +268,7 @@ describe('client', () => {
|
||||
const client = new acme.Client({
|
||||
...clientOpts,
|
||||
accountKey: testAccountKey,
|
||||
accountUrl: 'https://acme-staging-v02.api.letsencrypt.org/acme/acct/1'
|
||||
accountUrl: 'https://acme-staging-v02.api.letsencrypt.org/acme/acct/1',
|
||||
});
|
||||
|
||||
await assert.isRejected(client.updateAccount());
|
||||
@@ -288,11 +278,11 @@ describe('client', () => {
|
||||
const client = new acme.Client({
|
||||
...clientOpts,
|
||||
accountKey: testAccountKey,
|
||||
accountUrl: testAccountUrl
|
||||
accountUrl: testAccountUrl,
|
||||
});
|
||||
|
||||
const account = await client.createAccount({
|
||||
onlyReturnExisting: true
|
||||
onlyReturnExisting: true,
|
||||
});
|
||||
|
||||
spec.rfc8555.account(account);
|
||||
@@ -300,7 +290,6 @@ describe('client', () => {
|
||||
assert.deepStrictEqual(account.key, testAccount.key);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Update account contact info
|
||||
*/
|
||||
@@ -316,12 +305,11 @@ describe('client', () => {
|
||||
assert.include(account.contact, testContact);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Change account private key
|
||||
*/
|
||||
|
||||
it('should change account private key [ACME_CAP_UPDATE_ACCOUNT_KEY]', async function() {
|
||||
it('should change account private key [ACME_CAP_UPDATE_ACCOUNT_KEY]', async function () {
|
||||
if (!capUpdateAccountKey) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -329,7 +317,7 @@ describe('client', () => {
|
||||
await testClient.updateAccountKey(testAccountSecondaryKey);
|
||||
|
||||
const account = await testClient.createAccount({
|
||||
onlyReturnExisting: true
|
||||
onlyReturnExisting: true,
|
||||
});
|
||||
|
||||
spec.rfc8555.account(account);
|
||||
@@ -337,7 +325,6 @@ describe('client', () => {
|
||||
assert.notDeepEqual(account.key, testAccount.key);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Create new certificate order
|
||||
*/
|
||||
@@ -357,7 +344,6 @@ describe('client', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Get status of existing certificate order
|
||||
*/
|
||||
@@ -371,7 +357,6 @@ describe('client', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Get identifier authorization
|
||||
*/
|
||||
@@ -401,7 +386,6 @@ describe('client', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Generate challenge key authorization
|
||||
*/
|
||||
@@ -418,7 +402,6 @@ describe('client', () => {
|
||||
[testKeyAuthorization, testKeyAuthorizationAlpn, testKeyAuthorizationWildcard].forEach((k) => assert.isString(k));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Deactivate identifier authorization
|
||||
*/
|
||||
@@ -427,8 +410,8 @@ describe('client', () => {
|
||||
const order = await testClient.createOrder({
|
||||
identifiers: [
|
||||
{ type: 'dns', value: `${uuid()}.${domainName}` },
|
||||
{ type: 'dns', value: `${uuid()}.${domainName}` }
|
||||
]
|
||||
{ type: 'dns', value: `${uuid()}.${domainName}` },
|
||||
],
|
||||
});
|
||||
|
||||
const authzCollection = await testClient.getAuthorizations(order);
|
||||
@@ -445,7 +428,6 @@ describe('client', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Verify satisfied challenge
|
||||
*/
|
||||
@@ -460,7 +442,6 @@ describe('client', () => {
|
||||
await testClient.verifyChallenge(testAuthzWildcard, testChallengeWildcard);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Complete challenge
|
||||
*/
|
||||
@@ -474,7 +455,6 @@ describe('client', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Wait for valid challenge
|
||||
*/
|
||||
@@ -483,7 +463,6 @@ describe('client', () => {
|
||||
await Promise.all([testChallenge, testChallengeAlpn, testChallengeWildcard].map(async (c) => testClient.waitForValidStatus(c)));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Finalize order
|
||||
*/
|
||||
@@ -500,7 +479,6 @@ describe('client', () => {
|
||||
assert.strictEqual(testOrderWildcard.url, finalizeWildcard.url);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Wait for valid order
|
||||
*/
|
||||
@@ -509,7 +487,6 @@ describe('client', () => {
|
||||
await Promise.all([testOrder, testOrderAlpn, testOrderWildcard].map(async (o) => testClient.waitForValidStatus(o)));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Get certificate
|
||||
*/
|
||||
@@ -525,7 +502,7 @@ describe('client', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should get alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
||||
it('should get alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
|
||||
if (!capAlternateCertRoots) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -539,7 +516,7 @@ describe('client', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
||||
it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
|
||||
if (!capAlternateCertRoots) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -551,7 +528,6 @@ describe('client', () => {
|
||||
assert.strictEqual(testIssuers[0], info.issuer.commonName);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Revoke certificate
|
||||
*/
|
||||
@@ -568,7 +544,6 @@ describe('client', () => {
|
||||
await assert.isRejected(testClient.getCertificate(testOrderWildcard));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Deactivate account
|
||||
*/
|
||||
@@ -581,7 +556,6 @@ describe('client', () => {
|
||||
assert.strictEqual(account.status, 'deactivated');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Verify that no new orders can be made
|
||||
*/
|
||||
|
||||
@@ -18,17 +18,16 @@ const clientOpts = {
|
||||
directoryUrl,
|
||||
backoffAttempts: 5,
|
||||
backoffMin: 1000,
|
||||
backoffMax: 5000
|
||||
backoffMax: 5000,
|
||||
};
|
||||
|
||||
if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY) {
|
||||
clientOpts.externalAccountBinding = {
|
||||
kid: process.env.ACME_EAB_KID,
|
||||
hmacKey: process.env.ACME_EAB_HMAC_KEY
|
||||
hmacKey: process.env.ACME_EAB_HMAC_KEY,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
describe('client.auto', () => {
|
||||
const testDomain = `${uuid()}.${domainName}`;
|
||||
const testHttpDomain = `${uuid()}.${domainName}`;
|
||||
@@ -40,21 +39,19 @@ describe('client.auto', () => {
|
||||
const testSanDomains = [
|
||||
`${uuid()}.${domainName}`,
|
||||
`${uuid()}.${domainName}`,
|
||||
`${uuid()}.${domainName}`
|
||||
`${uuid()}.${domainName}`,
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Pebble CTS required
|
||||
*/
|
||||
|
||||
before(function() {
|
||||
before(function () {
|
||||
if (!cts.isEnabled()) {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Key types
|
||||
*/
|
||||
@@ -64,16 +61,16 @@ describe('client.auto', () => {
|
||||
createKeyFn: () => acme.crypto.createPrivateRsaKey(),
|
||||
createKeyAltFns: {
|
||||
s1024: () => acme.crypto.createPrivateRsaKey(1024),
|
||||
s4096: () => acme.crypto.createPrivateRsaKey(4096)
|
||||
}
|
||||
s4096: () => acme.crypto.createPrivateRsaKey(4096),
|
||||
},
|
||||
},
|
||||
ecdsa: {
|
||||
createKeyFn: () => acme.crypto.createPrivateEcdsaKey(),
|
||||
createKeyAltFns: {
|
||||
p384: () => acme.crypto.createPrivateEcdsaKey('P-384'),
|
||||
p521: () => acme.crypto.createPrivateEcdsaKey('P-521')
|
||||
}
|
||||
}
|
||||
p521: () => acme.crypto.createPrivateEcdsaKey('P-521'),
|
||||
},
|
||||
},
|
||||
}).forEach(([name, { createKeyFn, createKeyAltFns }]) => {
|
||||
describe(name, () => {
|
||||
let testIssuers;
|
||||
@@ -82,12 +79,11 @@ describe('client.auto', () => {
|
||||
let testSanCertificate;
|
||||
let testWildcardCertificate;
|
||||
|
||||
|
||||
/**
|
||||
* Fixtures
|
||||
*/
|
||||
|
||||
it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
||||
it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
|
||||
if (!capAlternateCertRoots) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -103,7 +99,6 @@ describe('client.auto', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Initialize client
|
||||
*/
|
||||
@@ -111,31 +106,30 @@ describe('client.auto', () => {
|
||||
it('should initialize client', async () => {
|
||||
testClient = new acme.Client({
|
||||
...clientOpts,
|
||||
accountKey: await createKeyFn()
|
||||
accountKey: await createKeyFn(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Invalid challenge response
|
||||
*/
|
||||
|
||||
it('should throw on invalid challenge response', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: `${uuid()}.${domainName}`
|
||||
commonName: `${uuid()}.${domainName}`,
|
||||
}, await createKeyFn());
|
||||
|
||||
await assert.isRejected(testClient.auto({
|
||||
csr,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.challengeNoopFn,
|
||||
challengeRemoveFn: cts.challengeNoopFn
|
||||
challengeRemoveFn: cts.challengeNoopFn,
|
||||
}), /^authorization not found/i);
|
||||
});
|
||||
|
||||
it('should throw on invalid challenge response with opts.skipChallengeVerification=true', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: `${uuid()}.${domainName}`
|
||||
commonName: `${uuid()}.${domainName}`,
|
||||
}, await createKeyFn());
|
||||
|
||||
await assert.isRejected(testClient.auto({
|
||||
@@ -143,38 +137,37 @@ describe('client.auto', () => {
|
||||
termsOfServiceAgreed: true,
|
||||
skipChallengeVerification: true,
|
||||
challengeCreateFn: cts.challengeNoopFn,
|
||||
challengeRemoveFn: cts.challengeNoopFn
|
||||
challengeRemoveFn: cts.challengeNoopFn,
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Challenge function exceptions
|
||||
*/
|
||||
|
||||
it('should throw on challengeCreate exception', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: `${uuid()}.${domainName}`
|
||||
commonName: `${uuid()}.${domainName}`,
|
||||
}, await createKeyFn());
|
||||
|
||||
await assert.isRejected(testClient.auto({
|
||||
csr,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.challengeThrowFn,
|
||||
challengeRemoveFn: cts.challengeNoopFn
|
||||
challengeRemoveFn: cts.challengeNoopFn,
|
||||
}), /^oops$/);
|
||||
});
|
||||
|
||||
it('should not throw on challengeRemove exception', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: `${uuid()}.${domainName}`
|
||||
commonName: `${uuid()}.${domainName}`,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
csr,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.challengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeThrowFn
|
||||
challengeRemoveFn: cts.challengeThrowFn,
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
@@ -188,8 +181,8 @@ describe('client.auto', () => {
|
||||
`${uuid()}.${domainName}`,
|
||||
`${uuid()}.${domainName}`,
|
||||
`${uuid()}.${domainName}`,
|
||||
`${uuid()}.${domainName}`
|
||||
]
|
||||
`${uuid()}.${domainName}`,
|
||||
],
|
||||
}, await createKeyFn());
|
||||
|
||||
await assert.isRejected(testClient.auto({
|
||||
@@ -205,28 +198,27 @@ describe('client.auto', () => {
|
||||
results.push(true);
|
||||
return cts.challengeCreateFn(...args);
|
||||
},
|
||||
challengeRemoveFn: cts.challengeRemoveFn
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
}));
|
||||
|
||||
assert.strictEqual(results.length, 5);
|
||||
assert.deepStrictEqual(results, [false, false, false, true, true]);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Order certificates
|
||||
*/
|
||||
|
||||
it('should order certificate', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: testDomain
|
||||
commonName: testDomain,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
csr,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.challengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
@@ -235,7 +227,7 @@ describe('client.auto', () => {
|
||||
|
||||
it('should order certificate using http-01', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: testHttpDomain
|
||||
commonName: testHttpDomain,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
@@ -243,7 +235,7 @@ describe('client.auto', () => {
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.assertHttpChallengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
challengePriority: ['http-01']
|
||||
challengePriority: ['http-01'],
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
@@ -251,7 +243,7 @@ describe('client.auto', () => {
|
||||
|
||||
it('should order certificate using https-01', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: testHttpsDomain
|
||||
commonName: testHttpsDomain,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
@@ -259,7 +251,7 @@ describe('client.auto', () => {
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.assertHttpsChallengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
challengePriority: ['http-01']
|
||||
challengePriority: ['http-01'],
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
@@ -267,7 +259,7 @@ describe('client.auto', () => {
|
||||
|
||||
it('should order certificate using dns-01', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: testDnsDomain
|
||||
commonName: testDnsDomain,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
@@ -275,7 +267,7 @@ describe('client.auto', () => {
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.assertDnsChallengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
challengePriority: ['dns-01']
|
||||
challengePriority: ['dns-01'],
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
@@ -283,7 +275,7 @@ describe('client.auto', () => {
|
||||
|
||||
it('should order certificate using tls-alpn-01', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: testAlpnDomain
|
||||
commonName: testAlpnDomain,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
@@ -291,7 +283,7 @@ describe('client.auto', () => {
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.assertTlsAlpnChallengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
challengePriority: ['tls-alpn-01']
|
||||
challengePriority: ['tls-alpn-01'],
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
@@ -299,14 +291,14 @@ describe('client.auto', () => {
|
||||
|
||||
it('should order san certificate', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
altNames: testSanDomains
|
||||
altNames: testSanDomains,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
csr,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.challengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
@@ -315,15 +307,14 @@ describe('client.auto', () => {
|
||||
|
||||
it('should order wildcard certificate', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: testWildcardDomain,
|
||||
altNames: [`*.${testWildcardDomain}`]
|
||||
altNames: [testWildcardDomain, `*.${testWildcardDomain}`],
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
csr,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.challengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
@@ -332,7 +323,7 @@ describe('client.auto', () => {
|
||||
|
||||
it('should order certificate with opts.skipChallengeVerification=true', async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: `${uuid()}.${domainName}`
|
||||
commonName: `${uuid()}.${domainName}`,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
@@ -340,20 +331,20 @@ describe('client.auto', () => {
|
||||
termsOfServiceAgreed: true,
|
||||
skipChallengeVerification: true,
|
||||
challengeCreateFn: cts.challengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
});
|
||||
|
||||
it('should order alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
||||
it('should order alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
|
||||
if (!capAlternateCertRoots) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await Promise.all(testIssuers.map(async (issuer) => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: `${uuid()}.${domainName}`
|
||||
commonName: `${uuid()}.${domainName}`,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
@@ -361,7 +352,7 @@ describe('client.auto', () => {
|
||||
termsOfServiceAgreed: true,
|
||||
preferredChain: issuer,
|
||||
challengeCreateFn: cts.challengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
});
|
||||
|
||||
const rootCert = acme.crypto.splitPemChain(cert).pop();
|
||||
@@ -371,13 +362,13 @@ describe('client.auto', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
||||
it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
|
||||
if (!capAlternateCertRoots) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: `${uuid()}.${domainName}`
|
||||
commonName: `${uuid()}.${domainName}`,
|
||||
}, await createKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
@@ -385,7 +376,7 @@ describe('client.auto', () => {
|
||||
termsOfServiceAgreed: true,
|
||||
preferredChain: uuid(),
|
||||
challengeCreateFn: cts.challengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
});
|
||||
|
||||
const rootCert = acme.crypto.splitPemChain(cert).pop();
|
||||
@@ -394,7 +385,6 @@ describe('client.auto', () => {
|
||||
assert.strictEqual(testIssuers[0], info.issuer.commonName);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Order certificate with alternate key sizes
|
||||
*/
|
||||
@@ -402,21 +392,20 @@ describe('client.auto', () => {
|
||||
Object.entries(createKeyAltFns).forEach(([k, altKeyFn]) => {
|
||||
it(`should order certificate with key=${k}`, async () => {
|
||||
const [, csr] = await acme.crypto.createCsr({
|
||||
commonName: testDomain
|
||||
commonName: testDomain,
|
||||
}, await altKeyFn());
|
||||
|
||||
const cert = await testClient.auto({
|
||||
csr,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: cts.challengeCreateFn,
|
||||
challengeRemoveFn: cts.challengeRemoveFn
|
||||
challengeRemoveFn: cts.challengeRemoveFn,
|
||||
});
|
||||
|
||||
assert.isString(cert);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Read certificates
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,6 @@ const axios = require('./../src/axios');
|
||||
const apiBaseUrl = process.env.ACME_CHALLTESTSRV_URL || null;
|
||||
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
||||
|
||||
|
||||
/**
|
||||
* Send request
|
||||
*/
|
||||
@@ -21,20 +20,18 @@ async function request(apiPath, data = {}) {
|
||||
await axios.request({
|
||||
url: `${apiBaseUrl}/${apiPath}`,
|
||||
method: 'post',
|
||||
data
|
||||
data,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* State
|
||||
*/
|
||||
|
||||
exports.isEnabled = () => !!apiBaseUrl;
|
||||
|
||||
|
||||
/**
|
||||
* DNS
|
||||
*/
|
||||
@@ -42,7 +39,6 @@ exports.isEnabled = () => !!apiBaseUrl;
|
||||
exports.addDnsARecord = async (host, addresses) => request('add-a', { host, addresses });
|
||||
exports.setDnsCnameRecord = async (host, target) => request('set-cname', { host, target });
|
||||
|
||||
|
||||
/**
|
||||
* Challenge response
|
||||
*/
|
||||
@@ -55,7 +51,7 @@ async function addHttps01ChallengeResponse(token, content, targetHostname) {
|
||||
await addHttp01ChallengeResponse(token, content);
|
||||
return request('add-redirect', {
|
||||
path: `/.well-known/acme-challenge/${token}`,
|
||||
targetURL: `https://${targetHostname}:${httpsPort}/.well-known/acme-challenge/${token}`
|
||||
targetURL: `https://${targetHostname}:${httpsPort}/.well-known/acme-challenge/${token}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,7 +68,6 @@ exports.addHttps01ChallengeResponse = addHttps01ChallengeResponse;
|
||||
exports.addDns01ChallengeResponse = addDns01ChallengeResponse;
|
||||
exports.addTlsAlpn01ChallengeResponse = addTlsAlpn01ChallengeResponse;
|
||||
|
||||
|
||||
/**
|
||||
* Challenge response mock functions
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,6 @@ const util = require('./../src/util');
|
||||
|
||||
const pebbleManagementUrl = process.env.ACME_PEBBLE_MANAGEMENT_URL || null;
|
||||
|
||||
|
||||
/**
|
||||
* Pebble
|
||||
*/
|
||||
@@ -26,7 +25,6 @@ async function getPebbleCertIssuers() {
|
||||
return info.map((i) => i.issuer.commonName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get certificate issuers
|
||||
*/
|
||||
|
||||
@@ -7,14 +7,12 @@ const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
const axios = require('./../src/axios');
|
||||
|
||||
|
||||
/**
|
||||
* Add promise support to Chai
|
||||
*/
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
|
||||
/**
|
||||
* Challenge test server ports
|
||||
*/
|
||||
@@ -31,7 +29,6 @@ if (process.env.ACME_TLSALPN_PORT) {
|
||||
axios.defaults.acmeSettings.tlsAlpnChallengePort = process.env.ACME_TLSALPN_PORT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* External account binding
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,6 @@ const { assert } = require('chai');
|
||||
const spec = {};
|
||||
module.exports = spec;
|
||||
|
||||
|
||||
/**
|
||||
* ACME
|
||||
*/
|
||||
@@ -120,7 +119,6 @@ spec.rfc8555.challenge = (obj) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Crypto
|
||||
*/
|
||||
@@ -150,7 +148,6 @@ spec.crypto.certificateInfo = (obj) => {
|
||||
assert.strictEqual(Object.prototype.toString.call(obj.notAfter), '[object Date]');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* JWK
|
||||
*/
|
||||
|
||||
6
packages/core/acme-client/types/index.d.ts
vendored
@@ -15,7 +15,6 @@ export type PublicKeyString = string;
|
||||
export type CertificateString = string;
|
||||
export type CsrString = string;
|
||||
|
||||
|
||||
/**
|
||||
* Augmented ACME interfaces
|
||||
*/
|
||||
@@ -28,7 +27,6 @@ export interface Authorization extends rfc8555.Authorization {
|
||||
url: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Client
|
||||
*/
|
||||
@@ -80,7 +78,6 @@ export class Client {
|
||||
auto(opts: ClientAutoOptions): Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Directory URLs
|
||||
*/
|
||||
@@ -99,7 +96,6 @@ export const directory: {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Crypto
|
||||
*/
|
||||
@@ -177,14 +173,12 @@ export interface CryptoLegacyInterface {
|
||||
|
||||
export const forge: CryptoLegacyInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Axios
|
||||
*/
|
||||
|
||||
export const axios: AxiosInstance;
|
||||
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import * as acme from 'acme-client';
|
||||
|
||||
|
||||
(async () => {
|
||||
/* Client */
|
||||
const accountKey = await acme.crypto.createPrivateKey();
|
||||
|
||||
4
packages/core/acme-client/types/rfc8555.d.ts
vendored
@@ -27,7 +27,6 @@ export interface AccountUpdateRequest {
|
||||
termsOfServiceAgreed?: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Order
|
||||
*
|
||||
@@ -53,7 +52,6 @@ export interface OrderCreateRequest {
|
||||
notAfter?: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Authorization
|
||||
*
|
||||
@@ -73,7 +71,6 @@ export interface Identifier {
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Challenge
|
||||
*
|
||||
@@ -102,7 +99,6 @@ export interface DnsChallenge extends ChallengeAbstract {
|
||||
|
||||
export type Challenge = HttpChallenge | DnsChallenge;
|
||||
|
||||
|
||||
/**
|
||||
* Certificate
|
||||
*
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.16](https://github.com/certd/certd/compare/v1.20.15...v1.20.16) (2024-07-01)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.20.12](https://github.com/certd/certd/compare/v1.20.10...v1.20.12) (2024-06-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.20.12",
|
||||
"version": "1.20.16",
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
@@ -23,7 +23,7 @@
|
||||
"qs": "^6.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/acme-client": "workspace:^1.20.12",
|
||||
"@certd/acme-client": "workspace:^1.20.16",
|
||||
"@rollup/plugin-commonjs": "^23.0.4",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
|
||||
@@ -52,6 +52,8 @@ export function createAxiosService({ logger }: { logger: Logger }) {
|
||||
// }
|
||||
logger.error(`请求出错:url:${error?.response?.config.url},method:${error.response.config.method},status:${error?.response?.status}`);
|
||||
logger.info("返回数据:", JSON.stringify(error?.response?.data));
|
||||
delete error.config;
|
||||
delete error.response;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.16](https://github.com/certd/certd/compare/v1.20.15...v1.20.16) (2024-07-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复配置了cdn cname后申请失败的bug ([4a5fa76](https://github.com/certd/certd/commit/4a5fa767edc347d03d29a467e86c9a4d70b0220c))
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/certd/certd/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.20.12](https://github.com/certd/certd/compare/v1.20.10...v1.20.12) (2024-06-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.20.12",
|
||||
"version": "1.20.16",
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
@@ -17,8 +17,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "workspace:^1.20.12",
|
||||
"@certd/pipeline": "workspace:^1.20.12",
|
||||
"@certd/acme-client": "workspace:^1.20.16",
|
||||
"@certd/pipeline": "workspace:^1.20.16",
|
||||
"jszip": "^3.10.1",
|
||||
"node-forge": "^0.10.0",
|
||||
"psl": "^1.9.0"
|
||||
|
||||
@@ -45,7 +45,7 @@ export class AcmeService {
|
||||
directoryUrl: isTest ? acme.directory.letsencrypt.staging : acme.directory.letsencrypt.production,
|
||||
accountKey: conf.key,
|
||||
accountUrl: conf.accountUrl,
|
||||
backoffAttempts: 20,
|
||||
backoffAttempts: 70,
|
||||
backoffMin: 5000,
|
||||
backoffMax: 10000,
|
||||
});
|
||||
@@ -178,6 +178,7 @@ export class AcmeService {
|
||||
csr,
|
||||
email: email,
|
||||
termsOfServiceAgreed: true,
|
||||
skipChallengeVerification: true,
|
||||
challengePriority: ["dns-01"],
|
||||
challengeCreateFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string): Promise<any> => {
|
||||
return await this.challengeCreateFn(authz, challenge, keyAuthorization, dnsProvider);
|
||||
|
||||
@@ -90,7 +90,7 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
|
||||
vModel: "value",
|
||||
},
|
||||
required: true,
|
||||
helper: "到期前多少天后更新证书",
|
||||
helper: "到期前多少天后更新证书,注意:流水线默认不会自动运行,请设置定时器,每天定时运行本流水线",
|
||||
})
|
||||
renewDays!: number;
|
||||
|
||||
@@ -106,9 +106,16 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
|
||||
|
||||
@TaskInput({
|
||||
title: "CsrInfo",
|
||||
helper: "暂时没有用",
|
||||
})
|
||||
csrInfo!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: "配置说明",
|
||||
helper: "运行策略请选择总是运行,其他证书部署任务请选择成功后跳过;当证书快到期前将会自动重新申请证书,然后会清空后续任务的成功状态,部署任务将会重新运行",
|
||||
})
|
||||
intro!: string;
|
||||
|
||||
acme!: AcmeService;
|
||||
logger!: Logger;
|
||||
userContext!: IContext;
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.16](https://github.com/certd/certd/compare/v1.20.15...v1.20.16) (2024-07-01)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-util
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-util
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-util
|
||||
|
||||
## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-util
|
||||
|
||||
## [1.20.12](https://github.com/certd/certd/compare/v1.20.10...v1.20.12) (2024-06-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-util
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-util",
|
||||
"private": false,
|
||||
"version": "1.20.12",
|
||||
"version": "1.20.16",
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
@@ -21,7 +21,7 @@
|
||||
"shelljs": "^0.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/pipeline": "workspace:^1.20.12",
|
||||
"@certd/pipeline": "workspace:^1.20.16",
|
||||
"@rollup/plugin-commonjs": "^23.0.4",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
|
||||
@@ -3,6 +3,8 @@ VITE_APP_API=/api
|
||||
VITE_APP_PM_ENABLED=true
|
||||
VITE_APP_TITLE=Certd
|
||||
VITE_APP_SLOGAN=让你的证书永不过期
|
||||
VITE_APP_COPYRIGHT=Copyright © 2021-2024 handsfree.work
|
||||
VITE_APP_COPYRIGHT_YEAR=2021-2024
|
||||
VITE_APP_COPYRIGHT_NAME=handsfree.work
|
||||
VITE_APP_COPYRIGHT_URL=https://certd.handsfree.work
|
||||
VITE_APP_LOGO_PATH=./images/logo/logo.svg
|
||||
VITE_APP_PROJECT_PATH=https://github.com/certd/certd
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.16](https://github.com/certd/certd/compare/v1.20.15...v1.20.16) (2024-07-01)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-client
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复无法强制取消任务的bug ([9cc01db](https://github.com/certd/certd/commit/9cc01db1d569a5c45bb3e731f35d85df324a8e62))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 支持windows文件上传 ([7f61cab](https://github.com/certd/certd/commit/7f61cab101fa13b4e88234e9ad47434e6130fed2))
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复修改密码功能异常问题 ([f740ff5](https://github.com/certd/certd/commit/f740ff517f521dce361284c2c54bccc68aee0ea2))
|
||||
|
||||
## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 日志高度越界 ([c4c9adb](https://github.com/certd/certd/commit/c4c9adb8bfd513f57252e523794e3799a9b220f8))
|
||||
* 修复邮箱设置页面SMTP拼写错误的问题 ([b98f1c0](https://github.com/certd/certd/commit/b98f1c0dd0bc6c6b4f814c578692afdf6d90b88d))
|
||||
* 修复logo问题 ([7e483e6](https://github.com/certd/certd/commit/7e483e60913d509b113148c735fe13ba1d72dddf))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 增加警告,修复一些样式错乱问题 ([fd54c2f](https://github.com/certd/certd/commit/fd54c2ffac492222e85ff2f5f49a9ee5cfc73588))
|
||||
|
||||
## [1.20.12](https://github.com/certd/certd/compare/v1.20.10...v1.20.12) (2024-06-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.20.12",
|
||||
"version": "1.20.16",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -26,10 +26,10 @@
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@aws-sdk/client-s3": "^3.535.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.535.0",
|
||||
"@fast-crud/fast-crud": "^1.21.0",
|
||||
"@fast-crud/fast-extends": "^1.21.0",
|
||||
"@fast-crud/ui-antdv4": "^1.21.0",
|
||||
"@fast-crud/ui-interface": "^1.21.0",
|
||||
"@fast-crud/fast-crud": "^1.21.1",
|
||||
"@fast-crud/fast-extends": "^1.21.1",
|
||||
"@fast-crud/ui-antdv4": "^1.21.1",
|
||||
"@fast-crud/ui-interface": "^1.21.1",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@soerenmartius/vue3-clipboard": "^0.1.2",
|
||||
"ant-design-vue": "^4.1.2",
|
||||
@@ -59,7 +59,7 @@
|
||||
"vuedraggable": "^2.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/pipeline": "^1.20.12",
|
||||
"@certd/pipeline": "^1.20.16",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -1,106 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="210mm"
|
||||
height="210mm"
|
||||
viewBox="0 0 210 210"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="500" height="500" viewBox="0 0 500.000000 500.000000"
|
||||
>
|
||||
<g id="layer1" style="display:inline">
|
||||
<path
|
||||
style="fill:#002255;stroke:none;stroke-width:0.625348"
|
||||
d="M 35.587501,69.766667 V 59.766664 h 70.000109 69.99991 v 10.000003 9.999997 H 105.58761 35.587501 Z"
|
||||
id="path12" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-4"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8-3"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8-2"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-7"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-4-0"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-9"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8-37"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8-4"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-7-1"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="82.941666" />
|
||||
</g>
|
||||
<polygon
|
||||
points="75.3,174.4 103.1,103.6 79.8,103.6 112.6,41.3 156.4,41.3 129.9,90.5 148.1,90.5 "
|
||||
fill="#f6cc00"
|
||||
id="polygon276"
|
||||
transform="matrix(1.0930933,0,0,0.99853202,-17.517362,-0.52287941)" />
|
||||
|
||||
</svg>
|
||||
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
|
||||
transform="translate(70, 76) scale(6,6)"
|
||||
></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 397 B |
@@ -19,7 +19,9 @@
|
||||
<a href="_self">隐私</a>
|
||||
<a href="_self">条款</a>
|
||||
</div>
|
||||
<div class="copyright">{{ envRef.COPYRIGHT }}</div>
|
||||
<div class="copyright">
|
||||
Copyright © {{ envRef.COPYRIGHT_YEAR }} <a :href="envRef.COPYRIGHT_URL" target="_blank">{{ envRef.COPYRIGHT_NAME }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,6 @@ function install(app: App, options: any = {}) {
|
||||
view: { type: "link", text: null, icon: "ion:eye-outline" },
|
||||
copy: { show: true, type: "link", text: null, icon: "ion:copy-outline" },
|
||||
edit: { type: "link", text: null, icon: "ion:create-outline" },
|
||||
copy: {show:true,type: "link", text: null, icon: "ion:copy-outline"},
|
||||
remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" }
|
||||
},
|
||||
dropdown: {
|
||||
@@ -126,16 +125,17 @@ function install(app: App, options: any = {}) {
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
createdAt: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
order: 1000
|
||||
}
|
||||
}
|
||||
// createdAt: {
|
||||
// title: "创建时间",
|
||||
// type: "datetime",
|
||||
// form: {
|
||||
// show: false
|
||||
// },
|
||||
// column: {
|
||||
// show: false,
|
||||
// order: 1000
|
||||
// }
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -65,7 +65,9 @@ h1, h2, h3, h4, h5, h6 {
|
||||
.mt-10{
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
.mb-10{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.m-10{
|
||||
margin:10px;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ export class EnvConfig {
|
||||
STORAGE: string;
|
||||
TITLE: string;
|
||||
SLOGAN: string;
|
||||
COPYRIGHT: string;
|
||||
COPYRIGHT_YEAR: string;
|
||||
COPYRIGHT_NAME: string;
|
||||
COPYRIGHT_URL: string;
|
||||
LOGO_PATH: string;
|
||||
PM_ENABLED: string;
|
||||
constructor() {
|
||||
|
||||
@@ -97,5 +97,7 @@ async function changePassword() {
|
||||
const formOptions = buildFormOptions(passwordFormOptions);
|
||||
formOptions.newInstance = true; //新实例打开
|
||||
passwordFormRef.value = await openDialog(formOptions);
|
||||
debugger;
|
||||
console.log(passwordFormRef.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #actionbar-right>
|
||||
<span style="margin-left: 10px">出现<a-tag>Promise rejected attempt #18,retrying in 10000ms:No TXT recordsfound for name</a-tag>属于正常现象,多重试几次</span>
|
||||
<!-- <span style="margin-left: 10px">出现<a-tag>Promise rejected attempt #18,retrying in 10000ms:No TXT recordsfound for name</a-tag>属于正常现象,多重试几次</span>-->
|
||||
</template>
|
||||
<pi-certd-form ref="certdFormRef"></pi-certd-form>
|
||||
</fs-crud>
|
||||
|
||||
@@ -28,6 +28,9 @@ export default defineComponent({
|
||||
return {};
|
||||
}
|
||||
},
|
||||
historyId: {
|
||||
type: Number
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "icon"
|
||||
@@ -59,7 +62,7 @@ export default defineComponent({
|
||||
okText: "确认",
|
||||
cancelText: "取消",
|
||||
onOk: async () => {
|
||||
await api.Cancel(props.runnable.id);
|
||||
await api.Cancel(props.historyId);
|
||||
notification.success({
|
||||
message: "任务取消成功"
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a-drawer v-model:open="stepDrawerVisible" placement="right" :closable="true" width="600px" @after-open-change="stepDrawerOnAfterVisibleChange">
|
||||
<template #title>
|
||||
编辑任务
|
||||
编辑步骤
|
||||
<a-button v-if="editMode" @click="stepDelete()">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
|
||||
@@ -71,11 +71,11 @@ import _ from "lodash-es";
|
||||
import { nanoid } from "nanoid";
|
||||
import PiStepForm from "../step-form/index.vue";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import {CopyOutlined} from "@ant-design/icons-vue";
|
||||
import { CopyOutlined } from "@ant-design/icons-vue";
|
||||
|
||||
export default {
|
||||
name: "PiTaskForm",
|
||||
components: {CopyOutlined, PiStepForm },
|
||||
components: { CopyOutlined, PiStepForm },
|
||||
props: {
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
@@ -88,7 +88,7 @@ export default {
|
||||
const stepFormRef: Ref<any> = ref(null);
|
||||
const currentStepIndex = ref(0);
|
||||
provide("currentStepIndex", currentStepIndex);
|
||||
const stepAdd = (task: any,stepDef:any) => {
|
||||
const stepAdd = (task: any, stepDef: any) => {
|
||||
currentStepIndex.value = task.steps.length;
|
||||
stepFormRef.value.stepAdd((type: any, value: any) => {
|
||||
if (type === "save") {
|
||||
@@ -97,14 +97,14 @@ export default {
|
||||
task.title = value.title;
|
||||
}
|
||||
}
|
||||
},stepDef);
|
||||
}, stepDef);
|
||||
};
|
||||
|
||||
const stepCopy = (task: any, step: any, stepIndex: any) => {
|
||||
step = _.cloneDeep(step)
|
||||
step.id = nanoid()
|
||||
step.title = step.title +"_copy"
|
||||
stepAdd(task,step)
|
||||
step = _.cloneDeep(step);
|
||||
step.id = nanoid();
|
||||
step.title = step.title + "_copy";
|
||||
stepAdd(task, step);
|
||||
};
|
||||
const stepEdit = (task: any, step: any, stepIndex: any) => {
|
||||
currentStepIndex.value = stepIndex;
|
||||
@@ -135,7 +135,7 @@ export default {
|
||||
});
|
||||
};
|
||||
|
||||
return { stepAdd, stepEdit,stepCopy, stepDelete, stepFormRef };
|
||||
return { stepAdd, stepEdit, stepCopy, stepDelete, stepFormRef };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,10 +177,10 @@ export default {
|
||||
taskDrawerShow();
|
||||
};
|
||||
|
||||
const taskAdd = (emit: any,taskMerge:any) => {
|
||||
const taskAdd = (emit: any, taskMerge: any) => {
|
||||
mode.value = "add";
|
||||
const blankTask = { id: nanoid(), title: "新任务", steps: [], status: null }
|
||||
const task: any = _.merge(blankTask,taskMerge) ;
|
||||
const blankTask = { id: nanoid(), title: "新任务", steps: [], status: null };
|
||||
const task: any = _.merge(blankTask, taskMerge);
|
||||
taskOpen(task, emit);
|
||||
};
|
||||
|
||||
@@ -234,7 +234,7 @@ export default {
|
||||
taskSave,
|
||||
taskDelete,
|
||||
rules,
|
||||
blankFn,
|
||||
blankFn
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -252,5 +252,8 @@ export default {
|
||||
.steps {
|
||||
margin: 0 50px 0 50px;
|
||||
}
|
||||
.ant-list .ant-list-item .ant-list-item-meta .ant-list-item-meta-title {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<pi-status-show :status="item.node.status?.result" type="icon"></pi-status-show>
|
||||
</div>
|
||||
</template>
|
||||
<pre class="pi-task-view-logs"><template v-for="(text, index) of item.logs" :key="index">{{ text }}</template></pre>
|
||||
<pre class="pi-task-view-logs" style="overflow: auto;"><template v-for="(text, index) of item.logs" :key="index">{{ text }}</template></pre>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-modal>
|
||||
|
||||
@@ -81,11 +81,11 @@
|
||||
<fs-icon v-if="editMode" class="add-stage-btn" title="添加新阶段" icon="ion:add-circle" @click="stageAdd(index)"></fs-icon>
|
||||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" @click="taskEdit(stage, index, task, taskIndex)">
|
||||
{{ task.title }}
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
</a-button>
|
||||
<fs-icon class="copy" v-if="editMode" title="复制" icon="ion:copy-outline" @click="taskCopy(stage, index, task)"></fs-icon>
|
||||
<a-button shape="round" @click="taskEdit(stage, index, task, taskIndex)">
|
||||
{{ task.title }}
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
</a-button>
|
||||
<fs-icon v-if="editMode" class="copy" title="复制" icon="ion:copy-outline" @click="taskCopy(stage, index, task)"></fs-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editMode" class="task-container is-add">
|
||||
@@ -99,7 +99,6 @@
|
||||
并行任务
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,7 +187,14 @@
|
||||
<a-page-header title="运行历史" sub-title="点任务可查看日志" class="logs-block">
|
||||
<a-timeline class="mt-10">
|
||||
<template v-for="item of histories" :key="item.id">
|
||||
<pi-history-timeline-item :runnable="item.pipeline" :is-current="currentHistory?.id === item.id" :edit-mode="editMode" @view="historyView(item)" @cancel="historyCancel()"></pi-history-timeline-item>
|
||||
<pi-history-timeline-item
|
||||
:runnable="item.pipeline"
|
||||
:history-id="item.id"
|
||||
:is-current="currentHistory?.id === item.id"
|
||||
:edit-mode="editMode"
|
||||
@view="historyView(item)"
|
||||
@cancel="historyCancel()"
|
||||
></pi-history-timeline-item>
|
||||
</template>
|
||||
<a-empty v-if="histories.length === 0"> </a-empty>
|
||||
</a-timeline>
|
||||
@@ -373,7 +379,7 @@ export default defineComponent({
|
||||
|
||||
const taskView = useTaskView();
|
||||
|
||||
const taskAdd = (stage: any, stageIndex: number, onSuccess?: any,taskDef?:any) => {
|
||||
const taskAdd = (stage: any, stageIndex: number, onSuccess?: any, taskDef?: any) => {
|
||||
currentStageIndex.value = stageIndex;
|
||||
currentTaskIndex.value = stage.tasks.length;
|
||||
taskFormRef.value.taskAdd((type: any, value: any) => {
|
||||
@@ -383,17 +389,17 @@ export default defineComponent({
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
},taskDef);
|
||||
}, taskDef);
|
||||
};
|
||||
|
||||
const taskCopy = (stage: any, stageIndex: number, task: any ) => {
|
||||
task = _.cloneDeep(task)
|
||||
task.id = nanoid()
|
||||
task.title= task.title+"_copy"
|
||||
const taskCopy = (stage: any, stageIndex: number, task: any) => {
|
||||
task = _.cloneDeep(task);
|
||||
task.id = nanoid();
|
||||
task.title = task.title + "_copy";
|
||||
for (const step of task.steps) {
|
||||
step.id = nanoid()
|
||||
step.id = nanoid();
|
||||
}
|
||||
taskAdd(stage,stageIndex,null,task)
|
||||
taskAdd(stage, stageIndex, null, task);
|
||||
};
|
||||
|
||||
const taskEdit = (stage: any, stageIndex: number, task: any, taskIndex: number, onSuccess?: any) => {
|
||||
@@ -423,7 +429,7 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
return { taskAdd, taskEdit, taskCopy,taskFormRef, ...taskView };
|
||||
return { taskAdd, taskEdit, taskCopy, taskFormRef, ...taskView };
|
||||
}
|
||||
|
||||
function useStage(useTaskRet: any) {
|
||||
@@ -760,10 +766,10 @@ export default defineComponent({
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
|
||||
.copy{
|
||||
.copy {
|
||||
position: absolute;
|
||||
right:60px;
|
||||
top:18px;
|
||||
right: 60px;
|
||||
top: 18px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
|
||||
@@ -36,6 +36,13 @@ const StatusEnum: StatusEnumType = {
|
||||
spin: true,
|
||||
icon: "ant-design:sync-outlined"
|
||||
},
|
||||
canceled: {
|
||||
value: "canceled",
|
||||
label: "已取消",
|
||||
color: "yellow",
|
||||
spin: true,
|
||||
icon: "ant-design:minus-circle-twotone"
|
||||
},
|
||||
none: {
|
||||
value: "none",
|
||||
label: "未运行",
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
</template>
|
||||
<div class="email-form">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish" @finish-failed="onFinishFailed">
|
||||
<a-form-item label="STMP域名" name="host" :rules="[{ required: true, message: '请输入smtp域名或ip' }]">
|
||||
<a-form-item label="SMTP域名" name="host" :rules="[{ required: true, message: '请输入smtp域名或ip' }]">
|
||||
<a-input v-model:value="formState.host" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="STMP端口" name="port" :rules="[{ required: true, message: '请输入smtp端口号' }]">
|
||||
<a-form-item label="SMTP端口" name="port" :rules="[{ required: true, message: '请输入smtp端口号' }]">
|
||||
<a-input v-model:value="formState.port" />
|
||||
</a-form-item>
|
||||
|
||||
@@ -18,12 +18,14 @@
|
||||
</a-form-item>
|
||||
<a-form-item label="密码" :name="['auth', 'pass']" :rules="[{ required: true, message: '请输入密码' }]">
|
||||
<a-input-password v-model:value="formState.auth.pass" />
|
||||
<div class="helper">如果是qq邮箱,需要到qq邮箱的设置里面申请授权码作为密码</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="发件邮箱" name="sender" :rules="[{ required: true, message: '请输入发件邮箱' }]">
|
||||
<a-input v-model:value="formState.sender" />
|
||||
</a-form-item>
|
||||
<a-form-item label="是否ssl" name="secure">
|
||||
<a-switch v-model:checked="formState.secure" />
|
||||
<div class="helper">ssl和非ssl的smtp端口是不一样的,注意修改端口</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="忽略证书校验" name="tls.rejectUnauthorized">
|
||||
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
||||
@@ -124,5 +126,12 @@ async function onTestSend() {
|
||||
width: 500px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.helper{
|
||||
padding:1px;
|
||||
margin:0px;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
<template>
|
||||
<div class="d2-page-cover">
|
||||
<div class="d2-page-cover__title" @click="$open('https://github.com/certd/certd')">
|
||||
<div class="title-line">Certd v{{ version }}</div>
|
||||
<div class="title-line">
|
||||
<span>Certd v{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="d2-page-cover__sub-title">让你的证书永不过期</p>
|
||||
<div class="warnning">
|
||||
<a-alert type="warning" show-icon>
|
||||
<template #description>
|
||||
<div class="flex">
|
||||
证书和授权为敏感信息,不要使用来历不明的在线CertD服务,以免泄露;请务必私有化部署使用,认准官方版本发布渠道:
|
||||
<a class="ml-5 flex" href="https://gitee.com/certd/certd" target="_blank">gitee</a>、
|
||||
<a class="ml-5 flex" href="https://github.com/certd/certd" target="_blank">github</a>
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
<div class="content">
|
||||
<img src="/images/preview.png" class="preview_img" />
|
||||
</div>
|
||||
<div class="footer_box">
|
||||
<div class="left"></div>
|
||||
<div class="right">
|
||||
<div>如果觉得好用,请不要吝啬你的star哟!</div>
|
||||
<a href="https://gitee.com/certd/certd" target="_blank"><img src="https://gitee.com/certd/certd/badge/star.svg?theme=dark" alt="star" /></a>
|
||||
<a href="https://github.com/certd/certd" target="_blank"><img alt="GitHub stars" src="https://img.shields.io/github/stars/certd/certd?logo=github" /></a>
|
||||
</div>
|
||||
<div>如果觉得好用,请不要吝啬你的star哟!</div>
|
||||
<a href="https://gitee.com/certd/certd" target="_blank"><img src="https://gitee.com/certd/certd/badge/star.svg?theme=dark" alt="star" /></a>
|
||||
<a href="https://github.com/certd/certd" target="_blank"><img alt="GitHub stars" src="https://img.shields.io/github/stars/certd/certd?logo=github" /></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -73,7 +83,7 @@ export default defineComponent({
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
width: 80%;
|
||||
width: 70%;
|
||||
.preview_img {
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
|
||||
@@ -65,7 +65,7 @@ export default defineComponent({
|
||||
//处理过,无需再次处理
|
||||
return;
|
||||
}
|
||||
value.class="is-twig"
|
||||
value.class = "is-twig";
|
||||
if (value.children != null && value.children.length > 0) {
|
||||
return;
|
||||
}
|
||||
@@ -83,13 +83,13 @@ export default defineComponent({
|
||||
}
|
||||
// 所有的子节点都没有children
|
||||
parent.class = "is-twig"; // 连接叶子节点的末梢枝杈节点
|
||||
let i = 0
|
||||
let i = 0;
|
||||
for (const child of parent.children) {
|
||||
child.class = "is-leaf";
|
||||
if(i !== 0){
|
||||
if (i !== 0) {
|
||||
child.class += " leaf-after";
|
||||
}
|
||||
i++
|
||||
i++;
|
||||
}
|
||||
});
|
||||
return [
|
||||
@@ -135,11 +135,10 @@ export default defineComponent({
|
||||
|
||||
<style lang="less">
|
||||
.fs-permission-tree {
|
||||
|
||||
.ant-tree-list-holder-inner{
|
||||
.ant-tree-list-holder-inner {
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap;
|
||||
.is-twig{
|
||||
.is-twig {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -149,8 +148,8 @@ export default defineComponent({
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.leaf-after{
|
||||
.ant-tree-indent-unit{
|
||||
&.leaf-after {
|
||||
.ant-tree-indent-unit {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -159,7 +158,6 @@ export default defineComponent({
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//.is-twig ul {
|
||||
// display: flex;
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.16](https://github.com/fast-crud/fast-server-js/compare/v1.20.15...v1.20.16) (2024-07-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复配置了cdn cname后申请失败的bug ([4a5fa76](https://github.com/fast-crud/fast-server-js/commit/4a5fa767edc347d03d29a467e86c9a4d70b0220c))
|
||||
|
||||
## [1.20.15](https://github.com/fast-crud/fast-server-js/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复无法强制取消任务的bug ([9cc01db](https://github.com/fast-crud/fast-server-js/commit/9cc01db1d569a5c45bb3e731f35d85df324a8e62))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/fast-crud/fast-server-js/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
|
||||
* 支持windows文件上传 ([7f61cab](https://github.com/fast-crud/fast-server-js/commit/7f61cab101fa13b4e88234e9ad47434e6130fed2))
|
||||
|
||||
## [1.20.14](https://github.com/fast-crud/fast-server-js/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-server
|
||||
|
||||
## [1.20.13](https://github.com/fast-crud/fast-server-js/compare/v1.20.12...v1.20.13) (2024-06-18)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* ssh登录支持openssh格式私钥、支持私钥密码 ([5c2c508](https://github.com/fast-crud/fast-server-js/commit/5c2c50839a9076004f9034d754ac6deb531acdfb))
|
||||
|
||||
## [1.20.12](https://github.com/fast-crud/fast-server-js/compare/v1.20.10...v1.20.12) (2024-06-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.20.12",
|
||||
"version": "1.20.16",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -24,10 +24,10 @@
|
||||
"@alicloud/cs20151215": "^3.0.3",
|
||||
"@alicloud/openapi-client": "^0.4.0",
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@certd/acme-client": "^1.20.12",
|
||||
"@certd/pipeline": "^1.20.12",
|
||||
"@certd/plugin-cert": "^1.20.12",
|
||||
"@certd/plugin-util": "^1.20.12",
|
||||
"@certd/acme-client": "^1.20.16",
|
||||
"@certd/pipeline": "^1.20.16",
|
||||
"@certd/plugin-cert": "^1.20.16",
|
||||
"@certd/plugin-util": "^1.20.16",
|
||||
"@koa/cors": "^3.4.3",
|
||||
"@midwayjs/bootstrap": "^3.15.0",
|
||||
"@midwayjs/cache": "^3.14.0",
|
||||
@@ -46,6 +46,7 @@
|
||||
"dayjs": "^1.11.7",
|
||||
"glob": "^7.2.3",
|
||||
"https-proxy-agent": "^7.0.4",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"koa-send": "^5.0.1",
|
||||
@@ -62,6 +63,7 @@
|
||||
"ssh2": "^0.8.9",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.44",
|
||||
"tencentcloud-sdk-nodejs-dnspod": "^4.0.866",
|
||||
"typeorm": "^0.3.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -236,14 +236,18 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async cancel(historyId: number) {
|
||||
async cancel(historyId) {
|
||||
const executor = runningTasks.get(historyId);
|
||||
if (executor) {
|
||||
await executor.cancel();
|
||||
} else {
|
||||
const entity = await this.historyService.info(historyId);
|
||||
if(entity == null){
|
||||
return
|
||||
}
|
||||
const pipeline: Pipeline = JSON.parse(entity.pipeline);
|
||||
pipeline.status.status = ResultType.canceled;
|
||||
pipeline.status.result = ResultType.canceled;
|
||||
const runtime = new RunHistory(historyId, null, pipeline);
|
||||
await this.saveHistory(runtime);
|
||||
}
|
||||
@@ -291,6 +295,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
const entity: HistoryEntity = new HistoryEntity();
|
||||
entity.id = parseInt(history.id);
|
||||
entity.userId = history.pipeline.userId;
|
||||
entity.status = pipelineEntity.status
|
||||
entity.pipeline = JSON.stringify(history.pipeline);
|
||||
entity.pipelineId = parseInt(history.pipeline.id);
|
||||
await this.historyService.save(entity);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { CertInfo, CertReader } from '@certd/plugin-cert';
|
||||
@IsTaskPlugin({
|
||||
name: 'CloudflareDeployToCDN',
|
||||
title: '部署证书到CF CDN',
|
||||
desc: '暂未实现,不可用',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AccessInput, IAccess, IsAccess } from '@certd/pipeline';
|
||||
import { ConnectConfig } from 'ssh2';
|
||||
|
||||
@IsAccess({
|
||||
name: 'ssh',
|
||||
@@ -6,7 +7,7 @@ import { AccessInput, IAccess, IsAccess } from '@certd/pipeline';
|
||||
desc: '',
|
||||
input: {},
|
||||
})
|
||||
export class SshAccess implements IAccess {
|
||||
export class SshAccess implements IAccess, ConnectConfig {
|
||||
@AccessInput({
|
||||
title: '主机地址',
|
||||
component: {
|
||||
@@ -19,11 +20,12 @@ export class SshAccess implements IAccess {
|
||||
title: '端口',
|
||||
value: '22',
|
||||
component: {
|
||||
name: 'a-input-number',
|
||||
placeholder: '22',
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
})
|
||||
port!: string;
|
||||
port!: number;
|
||||
@AccessInput({
|
||||
title: '用户名',
|
||||
value: 'root',
|
||||
@@ -40,14 +42,50 @@ export class SshAccess implements IAccess {
|
||||
})
|
||||
password!: string;
|
||||
@AccessInput({
|
||||
title: '密钥',
|
||||
helper: '密钥或密码必填一项',
|
||||
title: '私钥登录',
|
||||
helper: '私钥或密码必填一项',
|
||||
component: {
|
||||
name: 'a-textarea',
|
||||
vModel: 'value',
|
||||
},
|
||||
})
|
||||
privateKey!: string;
|
||||
|
||||
@AccessInput({
|
||||
title: '私钥密码',
|
||||
helper: '如果你的私钥有密码的话',
|
||||
component: {
|
||||
name: 'a-input-password',
|
||||
vModel: 'value',
|
||||
},
|
||||
})
|
||||
passphrase!: string;
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: '是否Windows',
|
||||
helper: '如果是Windows主机,请勾选此项',
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
vModel: 'checked',
|
||||
},
|
||||
})
|
||||
windows: boolean = false;
|
||||
|
||||
@AccessInput({
|
||||
title: '命令编码',
|
||||
helper: '如果是Windows主机,且出现乱码了,请尝试设置为GBK',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
options:[
|
||||
{value:"","label":"默认"},
|
||||
{value:"GBK","label":"GBK"},
|
||||
{value:"UTF8","label":"UTF-8"},
|
||||
]
|
||||
},
|
||||
})
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
new SshAccess();
|
||||
|
||||
@@ -1,8 +1,140 @@
|
||||
// @ts-ignore
|
||||
import ssh2 from 'ssh2';
|
||||
import ssh2, { ConnectConfig } from 'ssh2';
|
||||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
import { ILogger } from '@certd/pipeline';
|
||||
import iconv from 'iconv-lite';
|
||||
import {SshAccess} from "../access";
|
||||
export class AsyncSsh2Client {
|
||||
conn: ssh2.Client;
|
||||
logger: ILogger;
|
||||
connConf: ssh2.ConnectConfig;
|
||||
windows:boolean = false;
|
||||
encoding:string;
|
||||
constructor(connConf: SshAccess, logger: ILogger) {
|
||||
this.connConf = connConf;
|
||||
this.logger = logger;
|
||||
this.windows = connConf.windows || false;
|
||||
this.encoding = connConf.encoding;
|
||||
}
|
||||
|
||||
convert(buffer: Buffer) {
|
||||
if(this.encoding){
|
||||
return iconv.decode(buffer, this.encoding);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.logger.info(`开始连接,${this.connConf.host}:${this.connConf.port}`);
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new ssh2.Client();
|
||||
conn
|
||||
.on('error', (err: any) => {
|
||||
reject(err);
|
||||
})
|
||||
.on('ready', () => {
|
||||
this.logger.info('连接成功');
|
||||
this.conn = conn;
|
||||
resolve(this.conn);
|
||||
})
|
||||
.connect(this.connConf);
|
||||
});
|
||||
}
|
||||
async getSftp() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info('获取sftp');
|
||||
this.conn.sftp((err: any, sftp: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(sftp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async fastPut(options: { sftp: any; localPath: string; remotePath: string }) {
|
||||
const { sftp, localPath, remotePath } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info(`开始上传:${localPath} => ${remotePath}`);
|
||||
sftp.fastPut(localPath, remotePath, (err: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.logger.info(`上传文件成功:${localPath} => ${remotePath}`);
|
||||
resolve({});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async exec(script: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info(`执行命令:[${this.connConf.host}][exec]: ` + script);
|
||||
this.conn.exec(script, (err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
let data: string = null;
|
||||
stream
|
||||
.on('close', (code: any, signal: any) => {
|
||||
this.logger.info(`[${this.connConf.host}][close]:code:${code}`);
|
||||
if (code === 0) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(new Error(data));
|
||||
}
|
||||
})
|
||||
.on('data', (ret: Buffer) => {
|
||||
data = this.convert(ret)
|
||||
this.logger.info(`[${this.connConf.host}][info]: ` + data);
|
||||
})
|
||||
.stderr.on('data', (ret:Buffer) => {
|
||||
data = this.convert(ret)
|
||||
this.logger.info(`[${this.connConf.host}][error]: ` + data);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async shell(script: string | string[]): Promise<string[]> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.logger.info(
|
||||
`执行shell脚本:[${this.connConf.host}][shell]: ` + script
|
||||
);
|
||||
this.conn.shell((err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const output: string[] = [];
|
||||
stream
|
||||
.on('close', () => {
|
||||
this.logger.info('Stream :: close');
|
||||
resolve(output);
|
||||
})
|
||||
.on('data', (ret: Buffer) => {
|
||||
const data = this.convert(ret)
|
||||
this.logger.info('' + data);
|
||||
output.push(data);
|
||||
})
|
||||
.stderr.on('data', (ret:Buffer) => {
|
||||
const data = this.convert(ret)
|
||||
output.push(data);
|
||||
this.logger.info(`[${this.connConf.host}][error]: ` + data);
|
||||
});
|
||||
stream.end(script + '\nexit\n');
|
||||
});
|
||||
});
|
||||
}
|
||||
end() {
|
||||
if (this.conn) {
|
||||
this.conn.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
export class SshClient {
|
||||
logger: ILogger;
|
||||
constructor(logger: ILogger) {
|
||||
@@ -19,41 +151,36 @@ export class SshClient {
|
||||
}
|
||||
* @param options
|
||||
*/
|
||||
uploadFiles(options: { connectConf: any; transports: any }) {
|
||||
async uploadFiles(options: { connectConf: SshAccess; transports: any }) {
|
||||
const { connectConf, transports } = options;
|
||||
const conn = new ssh2.Client();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
conn
|
||||
.on('ready', () => {
|
||||
this.logger.info('连接服务器成功');
|
||||
conn.sftp(async (err: any, sftp: any) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
const sftp = await conn.getSftp();
|
||||
this.logger.info('开始上传');
|
||||
for (const transport of transports) {
|
||||
let filePath = path.dirname(transport.remotePath);
|
||||
let mkdirCmd = `mkdir -p ${filePath} `;
|
||||
if(conn.windows){
|
||||
if(filePath.indexOf("/") > -1){
|
||||
this.logger.info("--------------------------")
|
||||
this.logger.info("请注意:windows下,文件目录分隔应该写成\\而不是/")
|
||||
this.logger.info("--------------------------")
|
||||
}
|
||||
|
||||
try {
|
||||
for (const transport of transports) {
|
||||
this.logger.info('上传文件:', JSON.stringify(transport));
|
||||
await this.exec({
|
||||
connectConf,
|
||||
script: `mkdir -p ${path.dirname(transport.remotePath)} `,
|
||||
});
|
||||
await this.fastPut({ sftp, ...transport });
|
||||
}
|
||||
resolve({});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
});
|
||||
})
|
||||
.connect(connectConf);
|
||||
mkdirCmd = `if not exist "${filePath}" mkdir ${filePath} `
|
||||
}
|
||||
await conn.exec(mkdirCmd);
|
||||
await conn.fastPut({ sftp, ...transport });
|
||||
}
|
||||
this.logger.info('文件全部上传成功');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
exec(options: { connectConf: any; script: string | Array<string> }) {
|
||||
async exec(options: {
|
||||
connectConf: SshAccess;
|
||||
script: string | Array<string>;
|
||||
}) {
|
||||
let { script } = options;
|
||||
const { connectConf } = options;
|
||||
if (_.isArray(script)) {
|
||||
@@ -61,102 +188,38 @@ export class SshClient {
|
||||
script = script.join('\n');
|
||||
}
|
||||
this.logger.info('执行命令:', script);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connect({
|
||||
connectConf,
|
||||
onError(err: any) {
|
||||
reject(err);
|
||||
},
|
||||
onReady: (conn: any) => {
|
||||
conn.exec(script, (err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
let data: any = null;
|
||||
stream
|
||||
.on('close', (code: any, signal: any) => {
|
||||
this.logger.info(`[${connectConf.host}][close]:code:${code}`);
|
||||
data = data ? data.toString() : null;
|
||||
if (code === 0) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(new Error(data));
|
||||
}
|
||||
conn.end();
|
||||
})
|
||||
.on('data', (ret: any) => {
|
||||
this.logger.info(`[${connectConf.host}][info]: ` + ret);
|
||||
data = ret;
|
||||
})
|
||||
.stderr.on('data', (err: Error) => {
|
||||
this.logger.info(`[${connectConf.host}][error]: ` + err);
|
||||
data = err;
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
return await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
return await conn.exec(script as string);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
shell(options: { connectConf: any; script: string }) {
|
||||
async shell(options: {
|
||||
connectConf: SshAccess;
|
||||
script: string;
|
||||
}): Promise<string[]> {
|
||||
const { connectConf, script } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connect({
|
||||
connectConf,
|
||||
onError: (err: any) => {
|
||||
this.logger.error(err);
|
||||
reject(err);
|
||||
},
|
||||
onReady: (conn: any) => {
|
||||
conn.shell((err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const output: any = [];
|
||||
stream
|
||||
.on('close', () => {
|
||||
this.logger.info('Stream :: close');
|
||||
conn.end();
|
||||
resolve(output);
|
||||
})
|
||||
.on('data', (data: any) => {
|
||||
this.logger.info('' + data);
|
||||
output.push('' + data);
|
||||
});
|
||||
stream.end(script + '\nexit\n');
|
||||
});
|
||||
},
|
||||
});
|
||||
return await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
return await conn.shell(script as string);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
connect(options: { connectConf: any; onReady: any; onError: any }) {
|
||||
const { connectConf, onReady, onError } = options;
|
||||
const conn = new ssh2.Client();
|
||||
conn
|
||||
.on('error', (err: any) => {
|
||||
onError(err);
|
||||
})
|
||||
.on('ready', () => {
|
||||
this.logger.info('Client :: ready');
|
||||
onReady(conn);
|
||||
})
|
||||
.connect(connectConf);
|
||||
return conn;
|
||||
}
|
||||
|
||||
fastPut(options: { sftp: any; localPath: string; remotePath: string }) {
|
||||
const { sftp, localPath, remotePath } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
sftp.fastPut(localPath, remotePath, (err: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve({});
|
||||
});
|
||||
});
|
||||
async _call(options: {
|
||||
connectConf: SshAccess;
|
||||
callable: any;
|
||||
}): Promise<string[]> {
|
||||
const { connectConf, callable } = options;
|
||||
const conn = new AsyncSsh2Client(connectConf, this.logger);
|
||||
await conn.connect();
|
||||
try {
|
||||
return await callable(conn);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { SshClient } from "../../lib/ssh";
|
||||
import {
|
||||
AbstractTaskPlugin,
|
||||
IAccessService,
|
||||
ILogger,
|
||||
IsTaskPlugin,
|
||||
RunStrategy,
|
||||
TaskInput,
|
||||
} from '@certd/pipeline';
|
||||
import { SshClient } from '../../lib/ssh';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "hostShellExecute",
|
||||
title: "执行远程主机脚本命令",
|
||||
name: 'hostShellExecute',
|
||||
title: '执行远程主机脚本命令',
|
||||
input: {},
|
||||
default: {
|
||||
strategy: {
|
||||
@@ -14,20 +21,20 @@ import { SshClient } from "../../lib/ssh";
|
||||
})
|
||||
export class HostShellExecutePlugin extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: "主机登录配置",
|
||||
helper: "登录",
|
||||
title: '主机登录配置',
|
||||
helper: '登录',
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "ssh",
|
||||
name: 'pi-access-selector',
|
||||
type: 'ssh',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
@TaskInput({
|
||||
title: "shell脚本命令",
|
||||
title: 'shell脚本命令',
|
||||
component: {
|
||||
name: "a-textarea",
|
||||
vModel: "value",
|
||||
name: 'a-textarea',
|
||||
vModel: 'value',
|
||||
},
|
||||
})
|
||||
script!: string;
|
||||
@@ -46,7 +53,7 @@ export class HostShellExecutePlugin extends AbstractTaskPlugin {
|
||||
connectConf,
|
||||
script,
|
||||
});
|
||||
this.logger.info("exec res:", ret);
|
||||
this.logger.info('exec res:', ret);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { SshClient } from '../../lib/ssh';
|
||||
import { CertInfo, CertReader } from '@certd/plugin-cert';
|
||||
import * as fs from 'fs';
|
||||
import {SshAccess} from "../../access";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'uploadCertToHost',
|
||||
@@ -24,7 +25,7 @@ import * as fs from 'fs';
|
||||
export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: '证书保存路径',
|
||||
helper: '需要有写入权限,路径要包含证书文件名',
|
||||
helper: '需要有写入权限,路径要包含证书文件名,文件名不能用*?!等特殊符号',
|
||||
component: {
|
||||
placeholder: '/root/deploy/nginx/cert.crt',
|
||||
},
|
||||
@@ -32,9 +33,9 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
crtPath!: string;
|
||||
@TaskInput({
|
||||
title: '私钥保存路径',
|
||||
helper: '需要有写入权限,路径要包含证书文件名',
|
||||
helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号',
|
||||
component: {
|
||||
placeholder: '/root/deploy/nginx/cert.crt',
|
||||
placeholder: '/root/deploy/nginx/cert.key',
|
||||
},
|
||||
})
|
||||
keyPath!: string;
|
||||
@@ -59,9 +60,9 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
accessId!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: '复制到当前主机',
|
||||
title: '仅复制到当前主机',
|
||||
helper:
|
||||
'开启后,将直接复制到当前主机某个目录,由于是docker启动,实际上复制到的是docker容器内的目录,你需要事先在docker-compose.yaml中配置主机目录映射: volumes: /your_target_path:/your_target_path',
|
||||
'开启后,将直接复制到当前主机某个目录,不上传到主机,由于是docker启动,实际上是复制到docker容器内的“证书保存路径”,你需要事先在docker-compose.yaml中配置主机目录映射: volumes: /your_target_path:/your_target_path',
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
value: false,
|
||||
@@ -98,11 +99,12 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
async execute(): Promise<void> {
|
||||
const { crtPath, keyPath, cert, accessId } = this;
|
||||
const certReader = new CertReader(cert);
|
||||
|
||||
this.logger.info('将证书写入本地缓存文件');
|
||||
const saveCrtPath = certReader.saveToFile('crt');
|
||||
const saveKeyPath = certReader.saveToFile('key');
|
||||
|
||||
if (this.copyToThisHost) {
|
||||
this.logger.info('复制到目标路径');
|
||||
this.copyFile(saveCrtPath, crtPath);
|
||||
this.copyFile(saveKeyPath, keyPath);
|
||||
this.logger.info('证书复制成功:crtPath=', crtPath, ',keyPath=', keyPath);
|
||||
@@ -110,7 +112,8 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
if (!accessId) {
|
||||
throw new Error('主机登录授权配置不能为空');
|
||||
}
|
||||
const connectConf = await this.accessService.getById(accessId);
|
||||
this.logger.info('准备上传到服务器');
|
||||
const connectConf:SshAccess = await this.accessService.getById(accessId);
|
||||
const sshClient = new SshClient(this.logger);
|
||||
await sshClient.uploadFiles({
|
||||
connectConf,
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { Autowire, HttpClient, ILogger } from "@certd/pipeline";
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
|
||||
import _ from "lodash";
|
||||
import { DnspodAccess } from "../access";
|
||||
import { Autowire, HttpClient, ILogger } from '@certd/pipeline';
|
||||
import {
|
||||
AbstractDnsProvider,
|
||||
CreateRecordOptions,
|
||||
IsDnsProvider,
|
||||
RemoveRecordOptions,
|
||||
} from '@certd/plugin-cert';
|
||||
import _ from 'lodash';
|
||||
import { DnspodAccess } from '../access';
|
||||
|
||||
@IsDnsProvider({
|
||||
name: 'dnspod',
|
||||
title: 'dnspod(腾讯云)',
|
||||
desc: '腾讯云的域名解析接口已迁移到dnspod',
|
||||
title: 'dnspod(已过时)',
|
||||
desc: '请尽快换成腾讯云类型',
|
||||
accessType: 'dnspod',
|
||||
})
|
||||
export class DnspodDnsProvider extends AbstractDnsProvider {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Autowire, HttpClient, ILogger } from '@certd/pipeline';
|
||||
import {
|
||||
AbstractDnsProvider,
|
||||
CreateRecordOptions,
|
||||
IsDnsProvider,
|
||||
RemoveRecordOptions,
|
||||
} from '@certd/plugin-cert';
|
||||
import { TencentAccess } from '../access';
|
||||
import tencentcloud from 'tencentcloud-sdk-nodejs/index';
|
||||
import TencentCloudSDKHttpException from 'tencentcloud-sdk-nodejs/tencentcloud/common/exception/tencent_cloud_sdk_exception';
|
||||
|
||||
const DnspodClient = tencentcloud.dnspod.v20210323.Client;
|
||||
@IsDnsProvider({
|
||||
name: 'tencent',
|
||||
title: '腾讯云',
|
||||
desc: '腾讯云域名DNS解析提供者',
|
||||
accessType: 'tencent',
|
||||
})
|
||||
export class TencentDnsProvider extends AbstractDnsProvider {
|
||||
@Autowire()
|
||||
http!: HttpClient;
|
||||
|
||||
@Autowire()
|
||||
access!: TencentAccess;
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
|
||||
client!: any;
|
||||
|
||||
endpoint = 'dnspod.tencentcloudapi.com';
|
||||
|
||||
async onInstance() {
|
||||
const clientConfig = {
|
||||
credential: this.access,
|
||||
region: '',
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: this.endpoint,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
||||
this.client = new DnspodClient(clientConfig);
|
||||
}
|
||||
|
||||
async createRecord(options: CreateRecordOptions): Promise<any> {
|
||||
const { fullRecord, value, type, domain } = options;
|
||||
this.logger.info('添加域名解析:', fullRecord, value);
|
||||
const rr = fullRecord.replace('.' + domain, '');
|
||||
|
||||
const params = {
|
||||
Domain: domain,
|
||||
RecordType: type,
|
||||
RecordLine: '默认',
|
||||
Value: value,
|
||||
SubDomain: rr,
|
||||
};
|
||||
|
||||
try {
|
||||
const ret = await this.client.CreateRecord(params);
|
||||
this.logger.info(
|
||||
'添加域名解析成功:',
|
||||
fullRecord,
|
||||
value,
|
||||
JSON.stringify(ret)
|
||||
);
|
||||
/*
|
||||
{
|
||||
"RecordId": 162,
|
||||
"RequestId": "ab4f1426-ea15-42ea-8183-dc1b44151166"
|
||||
}
|
||||
*/
|
||||
return ret;
|
||||
} catch (e: any) {
|
||||
if (e instanceof TencentCloudSDKHttpException) {
|
||||
if (e.code === 'InvalidParameter.DomainRecordExist') {
|
||||
this.logger.info('域名解析已存在,无需重复添加:', fullRecord, value);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async removeRecord(options: RemoveRecordOptions<any>) {
|
||||
const { fullRecord, value, domain, record } = options;
|
||||
|
||||
const params = {
|
||||
Domain: domain,
|
||||
RecordId: record.RecordId,
|
||||
};
|
||||
const ret = await this.client.DeleteRecord(params);
|
||||
this.logger.info('删除域名解析成功:', fullRecord, value);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
new TencentDnsProvider();
|
||||