mirror of
https://github.com/certd/certd.git
synced 2026-04-24 20:57:26 +08:00
Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev
This commit is contained in:
@@ -17,6 +17,13 @@ Certd® 是一个免费的全自动证书管理系统,让你的网站证书永
|
|||||||
> 流水线数量现已调整为无限制,欢迎大家使用
|
> 流水线数量现已调整为无限制,欢迎大家使用
|
||||||
|
|
||||||
|
|
||||||
|
|官方开源地址: | |
|
||||||
|
| ---- | ---- |
|
||||||
|
| [Github](https://github.com/certd/certd)|  |
|
||||||
|
| [Gitee](https://gitee.com/certd/certd) |  |
|
||||||
|
| [AtomGit](https://atomgit.com/certd/certd) | |
|
||||||
|
|
||||||
|
|
||||||
## 一、特性
|
## 一、特性
|
||||||
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
|
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ Certd® is a free, fully automated certificate management system that ensures yo
|
|||||||
|
|
||||||
> The number of pipelines is now unlimited. Welcome to use it.
|
> The number of pipelines is now unlimited. Welcome to use it.
|
||||||
|
|
||||||
|
|
||||||
|
Official Open Source Address:
|
||||||
|
|
||||||
|
[Github](https://github.com/certd/certd) 
|
||||||
|
[Gitee](https://gitee.com/certd/certd) 
|
||||||
|
[AtomGit](https://atomgit.com/certd/certd) 
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 1. Features
|
## 1. Features
|
||||||
This project not only supports automated certificate application but also automated certificate deployment and updates, ensuring your certificates never expire.
|
This project not only supports automated certificate application but also automated certificate deployment and updates, ensuring your certificates never expire.
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ export default defineConfig({
|
|||||||
{text: "宝塔动态IP白名单", link: "/guide/use/baota/white_list.md"},
|
{text: "宝塔动态IP白名单", link: "/guide/use/baota/white_list.md"},
|
||||||
{text: "子域名托管", link: "/guide/use/cert/subdomain.md"},
|
{text: "子域名托管", link: "/guide/use/cert/subdomain.md"},
|
||||||
{text: "流水线有效期", link: "/guide/use/pipeline/valid.md"},
|
{text: "流水线有效期", link: "/guide/use/pipeline/valid.md"},
|
||||||
|
{text: "IP证书申请", link: "/guide/use/cert/ip.md"},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
|
|||||||
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
|
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
|
||||||
|
|
||||||
|
|
||||||
|
| 官方开源地址: | |
|
||||||
|
| ---- | ---- |
|
||||||
|
| [Github](https://github.com/certd/certd)|  |
|
||||||
|
| [Gitee](https://gitee.com/certd/certd) |  |
|
||||||
|
| [AtomGit](https://atomgit.com/certd/certd) | |
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 1、关于证书续期
|
## 1、关于证书续期
|
||||||
|
|||||||
@@ -43,4 +43,12 @@ service:
|
|||||||
certd_koa_hostname: 0.0.0.0
|
certd_koa_hostname: 0.0.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 6. DNS记录问题
|
||||||
|
|
||||||
|
1. DNS 不要设置CAA记录,删除即可
|
||||||
|
|
||||||
|
2. DNSSEC相关报错,DNSSEC管理中删除即可
|
||||||
|
|
||||||
|
3. DNS 有其他平台申请过的_acme-challenge记录,删除即可
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
> 如果出现过: 100.25.1.5 , 100.25.4.8
|
> 如果出现过: 100.25.1.5 , 100.25.4.8
|
||||||
>
|
>
|
||||||
> 可以尝试配置 100.25.*.5
|
> 可以尝试配置 100.25.*.*
|
||||||
|
|
||||||
## 二、nginx代理方案
|
## 二、nginx代理方案
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
# 证书申请失败情况
|
|
||||||
|
|
||||||
|
|
||||||
## DNS记录问题
|
|
||||||
|
|
||||||
1. DNS 不要设置CAA记录,删除即可
|
|
||||||
|
|
||||||
2. DNSSEC相关报错,DNSSEC管理中删除即可
|
|
||||||
|
|
||||||
3. DNS 有其他平台申请过的_acme-challenge记录,删除即可
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# IP证书申请
|
||||||
|
certd已支持IP证书申请
|
||||||
|
|
||||||
|
> 注意:IP证书有效期只有7天。
|
||||||
|
|
||||||
|
## 申请方式
|
||||||
|
相比普通的域名证书申请方式区别在于:
|
||||||
|
1. 域名栏填写IP
|
||||||
|
2. 校验方式选择HTTP(只能HTTP)
|
||||||
|
3. 证书颁发机构选择默认的Let's Encrypt
|
||||||
|
4. 过期更新天数改成2天
|
||||||
@@ -7,13 +7,14 @@
|
|||||||
services:
|
services:
|
||||||
certd:
|
certd:
|
||||||
environment: # 环境变量
|
environment: # 环境变量
|
||||||
- certd_system_resetAdminPasswd=false
|
- certd_system_resetAdminPasswd=true
|
||||||
```
|
```
|
||||||
## 2. 重启容器
|
## 2. 重启容器
|
||||||
```shell
|
```shell
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
docker logs -f --tail 500 certd
|
docker logs -f --tail 500 certd
|
||||||
# 观察日志,当日志中输出“重置1号管理员用户的密码完成”,即可操作下一步
|
# 观察日志,当日志中输出“重置1号管理员用户密码完成”,即可操作下一步
|
||||||
|
# 这里会打印1号管理员记录的用户名,如果你修改过管理员用户名,请注意查看此条日志
|
||||||
```
|
```
|
||||||
## 3. 恢复环境变量
|
## 3. 恢复环境变量
|
||||||
修改docker-compose.yaml,将`certd_system_resetAdminPasswd`改回`false`
|
修改docker-compose.yaml,将`certd_system_resetAdminPasswd`改回`false`
|
||||||
@@ -23,4 +24,6 @@ docker logs -f --tail 500 certd
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
## 5. 默认密码登录
|
## 5. 默认密码登录
|
||||||
使用`admin/123456`登录系统,请及时修改管理员密码
|
使用`原管理员账号/123456`登录系统,请及时修改管理员密码
|
||||||
|
> 默认管理员账号: admin
|
||||||
|
> 如果忘记管理员账号,请查看修改密码时的启动日志,会打印管理员账号名
|
||||||
|
|||||||
@@ -233,13 +233,22 @@ export class AcmeService {
|
|||||||
|
|
||||||
// const origDomain = punycode.toUnicode(domain);
|
// const origDomain = punycode.toUnicode(domain);
|
||||||
const origFullDomain = punycode.toUnicode(fullDomain);
|
const origFullDomain = punycode.toUnicode(fullDomain);
|
||||||
|
|
||||||
|
const isIp = utils.domain.isIp(origFullDomain);
|
||||||
|
function checkIpChallenge(type: string) {
|
||||||
|
if (isIp) {
|
||||||
|
throw new Error(`IP证书不支持${type}校验方式,请选择HTTP方式校验`);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (providers.domainsVerifyPlan) {
|
if (providers.domainsVerifyPlan) {
|
||||||
//按照计划执行
|
//按照计划执行
|
||||||
const domainVerifyPlan = providers.domainsVerifyPlan[origFullDomain];
|
const domainVerifyPlan = providers.domainsVerifyPlan[origFullDomain];
|
||||||
if (domainVerifyPlan) {
|
if (domainVerifyPlan) {
|
||||||
if (domainVerifyPlan.type === "dns") {
|
if (domainVerifyPlan.type === "dns") {
|
||||||
|
checkIpChallenge("dns");
|
||||||
dnsProvider = domainVerifyPlan.dnsProvider;
|
dnsProvider = domainVerifyPlan.dnsProvider;
|
||||||
} else if (domainVerifyPlan.type === "cname") {
|
} else if (domainVerifyPlan.type === "cname") {
|
||||||
|
checkIpChallenge("cname");
|
||||||
const cname: CnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
|
const cname: CnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
|
||||||
if (cname) {
|
if (cname) {
|
||||||
dnsProvider = cname.dnsProvider;
|
dnsProvider = cname.dnsProvider;
|
||||||
@@ -274,6 +283,7 @@ export class AcmeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dnsChallenge = getChallenge("dns-01");
|
const dnsChallenge = getChallenge("dns-01");
|
||||||
|
checkIpChallenge("dns");
|
||||||
return await doDnsVerify(dnsChallenge, fullRecord, dnsProvider);
|
return await doDnsVerify(dnsChallenge, fullRecord, dnsProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
|||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: "更新天数",
|
title: "更新天数",
|
||||||
value: 35,
|
value: 18,
|
||||||
component: {
|
component: {
|
||||||
name: "a-input-number",
|
name: "a-input-number",
|
||||||
vModel: "value",
|
vModel: "value",
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const preferredChainMergeScript = (() => {
|
|||||||
desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上",
|
desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上",
|
||||||
default: {
|
default: {
|
||||||
input: {
|
input: {
|
||||||
renewDays: 35,
|
renewDays: 18,
|
||||||
forceUpdate: false,
|
forceUpdate: false,
|
||||||
},
|
},
|
||||||
strategy: {
|
strategy: {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
description: "Automatic running",
|
description: "Automatic running",
|
||||||
setSchedule: "Set Scheduled Execution",
|
setSchedule: "Set Scheduled Execution",
|
||||||
pipelineSuccessThenSchedule: "Pipeline tests succeed, then configure scheduled triggers so it runs automatically daily",
|
pipelineSuccessThenSchedule: "Pipeline tests succeed, then configure scheduled triggers so it runs automatically daily",
|
||||||
recommendDailyRun: "Recommend configuring to run once daily; new certs requested 35 days before expiry and auto-skipped otherwise",
|
recommendDailyRun: "Recommend configuring to run once daily; new certs requested 18 days before expiry and auto-skipped otherwise",
|
||||||
setEmailNotification: "Set Email Notifications",
|
setEmailNotification: "Set Email Notifications",
|
||||||
suggestErrorAndRecoveryEmails: "Suggest listening for 'On Error' and 'Error to Success' to quickly troubleshoot failures (basic version requires mail server setup)",
|
suggestErrorAndRecoveryEmails: "Suggest listening for 'On Error' and 'Error to Success' to quickly troubleshoot failures (basic version requires mail server setup)",
|
||||||
tutorialEndTitle: "Tutorial End",
|
tutorialEndTitle: "Tutorial End",
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
description: "自动运行",
|
description: "自动运行",
|
||||||
setSchedule: "设置定时执行",
|
setSchedule: "设置定时执行",
|
||||||
pipelineSuccessThenSchedule: "流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了",
|
pipelineSuccessThenSchedule: "流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了",
|
||||||
recommendDailyRun: "推荐配置每天运行一次,默认到期前35天会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。",
|
recommendDailyRun: "推荐配置每天运行一次,默认到期前18天会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。",
|
||||||
setEmailNotification: "设置邮件通知",
|
setEmailNotification: "设置邮件通知",
|
||||||
suggestErrorAndRecoveryEmails: "建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题",
|
suggestErrorAndRecoveryEmails: "建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题",
|
||||||
tutorialEndTitle: "教程结束",
|
tutorialEndTitle: "教程结束",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env NODE_ENV=production node --optimize-for-size ./bootstrap.js",
|
"start": "cross-env NODE_ENV=production node --optimize-for-size ./bootstrap.js",
|
||||||
"dev-start": "cross-env NODE_ENV=dev & mwtsc --watch --run @midwayjs/mock/app",
|
"dev-start": "cross-env NODE_ENV=dev & mwtsc --watch --run @midwayjs/mock/app",
|
||||||
"dc": "cd ../../../ && pnpm run dev",
|
"dc": "cd ../../../ && pnpm run dev",
|
||||||
"dev": "cross-env NODE_ENV=local & pnpm run dev-start",
|
"dev": "cross-env NODE_ENV=local & pnpm run dev-start",
|
||||||
"dev-commlocal": "cross-env NODE_ENV=dev-commlocal mwtsc --watch --run @midwayjs/mock/app",
|
"dev-commlocal": "cross-env NODE_ENV=dev-commlocal mwtsc --watch --run @midwayjs/mock/app",
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
"@aws-sdk/client-acm": "^3.699.0",
|
"@aws-sdk/client-acm": "^3.699.0",
|
||||||
"@aws-sdk/client-cloudfront": "^3.699.0",
|
"@aws-sdk/client-cloudfront": "^3.699.0",
|
||||||
"@aws-sdk/client-iam": "^3.699.0",
|
"@aws-sdk/client-iam": "^3.699.0",
|
||||||
|
"@aws-sdk/client-route-53": "^3.957.0",
|
||||||
"@aws-sdk/client-s3": "^3.705.0",
|
"@aws-sdk/client-s3": "^3.705.0",
|
||||||
"@certd/acme-client": "^1.37.16",
|
"@certd/acme-client": "^1.37.16",
|
||||||
"@certd/basic": "^1.37.16",
|
"@certd/basic": "^1.37.16",
|
||||||
|
|||||||
@@ -31,6 +31,20 @@ process.on('uncaughtException', error => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// function startHeapLog() {
|
||||||
|
// function format(bytes: any) {
|
||||||
|
// return (bytes / 1024 / 1024).toFixed(2) + ' MB';
|
||||||
|
// }
|
||||||
|
// function log() {
|
||||||
|
// const mu = process.memoryUsage();
|
||||||
|
// logger.info(`rss:${format(mu.rss)},heapUsed: ${format(mu.heapUsed)},heapTotal: ${format(mu.heapTotal)},external: ${format(mu.external)}`);
|
||||||
|
// }
|
||||||
|
// setInterval(log, 200);
|
||||||
|
// log()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// startHeapLog();
|
||||||
|
|
||||||
@Configuration({
|
@Configuration({
|
||||||
detectorOptions: {
|
detectorOptions: {
|
||||||
ignore: [
|
ignore: [
|
||||||
@@ -64,6 +78,9 @@ export class MainConfiguration {
|
|||||||
app: koa.Application;
|
app: koa.Application;
|
||||||
|
|
||||||
async onReady() {
|
async onReady() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// add middleware
|
// add middleware
|
||||||
// this.app.useMiddleware([ReportMiddleware]);
|
// this.app.useMiddleware([ReportMiddleware]);
|
||||||
// add filter
|
// add filter
|
||||||
|
|||||||
@@ -242,7 +242,9 @@ export class LoginService {
|
|||||||
}
|
}
|
||||||
const info = await this.userService.findOne({id: oauthBound.userId});
|
const info = await this.userService.findOne({id: oauthBound.userId});
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
throw new CommonException('用户不存在');
|
// 用户已被删除,删除此oauth绑定
|
||||||
|
await this.oauthBoundService.delete([oauthBound.id]);
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
return this.generateToken(info);
|
return this.generateToken(info);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1012,7 +1012,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
title: "申请证书",
|
title: "申请证书",
|
||||||
runnableType: "step",
|
runnableType: "step",
|
||||||
input: {
|
input: {
|
||||||
renewDays: 35,
|
renewDays: 18,
|
||||||
domains: req.domains,
|
domains: req.domains,
|
||||||
email: req.email,
|
email: req.email,
|
||||||
"challengeType": "auto",
|
"challengeType": "auto",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { RandomUtil } from '../../../../utils/random.js';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { DbAdapter } from '../../../db/index.js';
|
import { DbAdapter } from '../../../db/index.js';
|
||||||
import { simpleNanoId, utils } from '@certd/basic';
|
import { simpleNanoId, utils } from '@certd/basic';
|
||||||
|
import { OauthBoundService } from '../../../login/service/oauth-bound-service.js';
|
||||||
|
|
||||||
export type RegisterType = 'username' | 'mobile' | 'email';
|
export type RegisterType = 'username' | 'mobile' | 'email';
|
||||||
export type ForgotPasswordType = 'mobile' | 'email';
|
export type ForgotPasswordType = 'mobile' | 'email';
|
||||||
@@ -42,6 +43,10 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
@Inject()
|
@Inject()
|
||||||
dbAdapter: DbAdapter;
|
dbAdapter: DbAdapter;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
oauthBoundService: OauthBoundService;
|
||||||
|
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
getRepository() {
|
getRepository() {
|
||||||
return this.repository;
|
return this.repository;
|
||||||
@@ -311,6 +316,9 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
throw new CommonException('不能删除管理员');
|
throw new CommonException('不能删除管理员');
|
||||||
}
|
}
|
||||||
await super.delete(ids);
|
await super.delete(ids);
|
||||||
|
await this.oauthBoundService.deleteWhere({
|
||||||
|
userId: In(ids),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async isAdmin(userId: any) {
|
async isAdmin(userId: any) {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
||||||
|
|
||||||
export const AwsRegions = [
|
export const AwsRegions = [
|
||||||
|
{ label: 'cn-north-1', value: 'cn-north-1' },
|
||||||
|
{ label: 'cn-northwest-1', value: 'cn-northwest-1' },
|
||||||
|
{ label: '---------------', value: '--',disabled: true },
|
||||||
{ label: 'us-east-1', value: 'us-east-1' },
|
{ label: 'us-east-1', value: 'us-east-1' },
|
||||||
{ label: 'us-east-2', value: 'us-east-2' },
|
{ label: 'us-east-2', value: 'us-east-2' },
|
||||||
{ label: 'us-west-1', value: 'us-west-1' },
|
{ label: 'us-west-1', value: 'us-west-1' },
|
||||||
@@ -61,6 +64,18 @@ export class AwsAccess extends BaseAccess {
|
|||||||
helper: '请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。',
|
helper: '请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。',
|
||||||
})
|
})
|
||||||
secretAccessKey = '';
|
secretAccessKey = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: 'region',
|
||||||
|
component: {
|
||||||
|
name:"a-select",
|
||||||
|
options: AwsRegions,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
helper: '请选择您的默认AWS区域,默认us-east-1',
|
||||||
|
options: AwsRegions,
|
||||||
|
})
|
||||||
|
region = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
new AwsAccess();
|
new AwsAccess();
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||||
|
import { AwsClient } from './libs/aws-client.js';
|
||||||
|
import { AwsAccess } from './access.js';
|
||||||
|
|
||||||
|
|
||||||
|
@IsDnsProvider({
|
||||||
|
name: 'aws-route53',
|
||||||
|
title: 'AWS Route53',
|
||||||
|
desc: 'AWS Route53 DNS解析提供商',
|
||||||
|
accessType: 'aws',
|
||||||
|
icon: 'svg:icon-aws',
|
||||||
|
order:0,
|
||||||
|
})
|
||||||
|
export class AwsRoute53Provider extends AbstractDnsProvider {
|
||||||
|
|
||||||
|
client: AwsClient;
|
||||||
|
async onInstance() {
|
||||||
|
const access: AwsAccess = this.ctx.access as AwsAccess
|
||||||
|
this.client = new AwsClient({ access: access, logger: this.logger, region:access.region || 'us-east-1' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRecord(options: CreateRecordOptions): Promise<any> {
|
||||||
|
const { fullRecord, value, type, domain } = options;
|
||||||
|
this.logger.info('添加域名解析:', fullRecord, value, domain);
|
||||||
|
// const domain = await this.matchDomain(fullRecord);
|
||||||
|
|
||||||
|
const {ZoneId,ZoneName} = await this.client.route53GetHostedZoneId(domain);
|
||||||
|
this.logger.info(`获取到hostedZoneId:${ZoneId},name:${ZoneName},domain:${domain}`);
|
||||||
|
|
||||||
|
await this.client.route53ChangeRecord({
|
||||||
|
hostedZoneId: ZoneId,
|
||||||
|
fullRecord: fullRecord,
|
||||||
|
type: type,
|
||||||
|
value: value,
|
||||||
|
action: 'CREATE',
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
hostedZoneId: ZoneId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async removeRecord(options: RemoveRecordOptions<any>): Promise<any> {
|
||||||
|
const { fullRecord, value,type } = options.recordReq;
|
||||||
|
const record = options.recordRes;
|
||||||
|
const hostedZoneId = record.hostedZoneId;
|
||||||
|
|
||||||
|
try{
|
||||||
|
await this.client.route53ChangeRecord({
|
||||||
|
hostedZoneId: hostedZoneId,
|
||||||
|
fullRecord: fullRecord,
|
||||||
|
type: type,
|
||||||
|
value: value,
|
||||||
|
action: 'DELETE',
|
||||||
|
});
|
||||||
|
}catch(e){
|
||||||
|
this.logger.warn(`删除域名解析失败:${e.message} : ${hostedZoneId} ${fullRecord} ${value} ${type} `, );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new AwsRoute53Provider();
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './plugins/index.js';
|
export * from './plugins/index.js';
|
||||||
export * from './access.js';
|
export * from './access.js';
|
||||||
|
export * from './aws-route53-provider.js';
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// 导入所需的 SDK 模块
|
|
||||||
import { AwsAccess } from '../access.js';
|
|
||||||
import { CertInfo } from '@certd/plugin-cert';
|
|
||||||
|
|
||||||
type AwsAcmClientOptions = { access: AwsAccess; region: string };
|
|
||||||
|
|
||||||
export class AwsAcmClient {
|
|
||||||
options: AwsAcmClientOptions;
|
|
||||||
access: AwsAccess;
|
|
||||||
region: string;
|
|
||||||
constructor(options: AwsAcmClientOptions) {
|
|
||||||
this.options = options;
|
|
||||||
this.access = options.access;
|
|
||||||
this.region = options.region;
|
|
||||||
}
|
|
||||||
async importCertificate(certInfo: CertInfo) {
|
|
||||||
// 创建 ACM 客户端
|
|
||||||
const { ACMClient, ImportCertificateCommand } = await import('@aws-sdk/client-acm');
|
|
||||||
const acmClient = new ACMClient({
|
|
||||||
region: this.region, // 替换为您的 AWS 区域
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: this.access.accessKeyId, // 从环境变量中读取
|
|
||||||
secretAccessKey: this.access.secretAccessKey,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const cert = certInfo.crt.split('-----END CERTIFICATE-----')[0] + '-----END CERTIFICATE-----';
|
|
||||||
// 构建上传参数
|
|
||||||
const data = await acmClient.send(
|
|
||||||
new ImportCertificateCommand({
|
|
||||||
Certificate: Buffer.from(cert),
|
|
||||||
PrivateKey: Buffer.from(certInfo.key),
|
|
||||||
// CertificateChain: certificateChain, // 可选
|
|
||||||
})
|
|
||||||
);
|
|
||||||
console.log('Upload successful:', data);
|
|
||||||
// 返回证书 ARN(Amazon Resource Name)
|
|
||||||
return data.CertificateArn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
// 导入所需的 SDK 模块
|
||||||
|
import { AwsAccess } from '../access.js';
|
||||||
|
import { CertInfo } from '@certd/plugin-cert';
|
||||||
|
import {ILogger, utils} from '@certd/basic';
|
||||||
|
type AwsClientOptions = { access: AwsAccess; region: string, logger:ILogger };
|
||||||
|
|
||||||
|
export class AwsClient {
|
||||||
|
options: AwsClientOptions;
|
||||||
|
access: AwsAccess;
|
||||||
|
region: string;
|
||||||
|
logger: ILogger;
|
||||||
|
constructor(options: AwsClientOptions) {
|
||||||
|
this.options = options;
|
||||||
|
this.access = options.access;
|
||||||
|
this.region = options.region;
|
||||||
|
this.logger = options.logger;
|
||||||
|
}
|
||||||
|
async importCertificate(certInfo: CertInfo) {
|
||||||
|
// 创建 ACM 客户端
|
||||||
|
const { ACMClient, ImportCertificateCommand } = await import('@aws-sdk/client-acm');
|
||||||
|
const acmClient = new ACMClient({
|
||||||
|
region: this.region, // 替换为您的 AWS 区域
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: this.access.accessKeyId, // 从环境变量中读取
|
||||||
|
secretAccessKey: this.access.secretAccessKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const cert = certInfo.crt.split('-----END CERTIFICATE-----')[0] + '-----END CERTIFICATE-----';
|
||||||
|
// 构建上传参数
|
||||||
|
const data = await acmClient.send(
|
||||||
|
new ImportCertificateCommand({
|
||||||
|
Certificate: Buffer.from(cert),
|
||||||
|
PrivateKey: Buffer.from(certInfo.key),
|
||||||
|
// CertificateChain: certificateChain, // 可选
|
||||||
|
})
|
||||||
|
);
|
||||||
|
console.log('Upload successful:', data);
|
||||||
|
// 返回证书 ARN(Amazon Resource Name)
|
||||||
|
return data.CertificateArn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async route53ClientGet() {
|
||||||
|
const { Route53Client } = await import('@aws-sdk/client-route-53');
|
||||||
|
return new Route53Client({
|
||||||
|
region: this.region,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: this.access.accessKeyId, // 从环境变量中读取
|
||||||
|
secretAccessKey: this.access.secretAccessKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async route53GetHostedZoneId(name:string) :Promise<{ZoneId:string,ZoneName:string}> {
|
||||||
|
const hostedZones = await this.route53ListHostedZones(name);
|
||||||
|
const zoneId = hostedZones[0].Id.replace('/hostedzone/','');
|
||||||
|
this.logger.info(`获取到hostedZoneId:${zoneId},name:${hostedZones[0].Name}`);
|
||||||
|
return {
|
||||||
|
ZoneId: zoneId,
|
||||||
|
ZoneName: hostedZones[0].Name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async route53ListHostedZones(name:string) :Promise<{Id:string,Name:string}[]> {
|
||||||
|
const { ListHostedZonesByNameCommand } =await import("@aws-sdk/client-route-53"); // ES Modules import
|
||||||
|
|
||||||
|
const client = await this.route53ClientGet();
|
||||||
|
const input = { // ListHostedZonesByNameRequest
|
||||||
|
DNSName: name,
|
||||||
|
};
|
||||||
|
const command = new ListHostedZonesByNameCommand(input);
|
||||||
|
const response = await this.doRequest(()=>client.send(command));
|
||||||
|
if (response.HostedZones.length === 0) {
|
||||||
|
throw new Error(`找不到 HostedZone ${name}`);
|
||||||
|
}
|
||||||
|
this.logger.info(`获取到hostedZoneId:${JSON.stringify(response.HostedZones)}`);
|
||||||
|
return response.HostedZones;
|
||||||
|
}
|
||||||
|
|
||||||
|
async route53ChangeRecord(req:{
|
||||||
|
hostedZoneId:string,fullRecord:string,type:string, value:string, action:"CREATE"|"DELETE"}){
|
||||||
|
const { ChangeResourceRecordSetsCommand} =await import("@aws-sdk/client-route-53"); // ES Modules import
|
||||||
|
// const { Route53Client, ChangeResourceRecordSetsCommand } = require("@aws-sdk/client-route-53"); // CommonJS import
|
||||||
|
// import type { Route53ClientConfig } from "@aws-sdk/client-route-53";
|
||||||
|
const client = await this.route53ClientGet();
|
||||||
|
|
||||||
|
const appendBody:any = {}
|
||||||
|
if(req.action === 'CREATE'){
|
||||||
|
appendBody.TTL = 60;
|
||||||
|
}
|
||||||
|
const input = { // ChangeResourceRecordSetsRequest
|
||||||
|
HostedZoneId: req.hostedZoneId, // required
|
||||||
|
ChangeBatch: { // ChangeBatch
|
||||||
|
Changes: [ // Changes // required
|
||||||
|
{ // Change
|
||||||
|
Action: req.action as any , // required
|
||||||
|
ResourceRecordSet: { // ResourceRecordSet
|
||||||
|
Name: req.fullRecord+".", // required
|
||||||
|
Type: req.type.toUpperCase() as any,
|
||||||
|
ResourceRecords: [ // ResourceRecords
|
||||||
|
{ // ResourceRecord
|
||||||
|
Value: `"${req.value}"`, // required
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...appendBody
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.logger.info(`添加域名解析参数:${JSON.stringify(input)}`);
|
||||||
|
const command = new ChangeResourceRecordSetsCommand(input);
|
||||||
|
const response = await this.doRequest(()=>client.send(command));
|
||||||
|
console.log('Add record successful:', JSON.stringify(response));
|
||||||
|
await utils.sleep(3000);
|
||||||
|
return response;
|
||||||
|
/*
|
||||||
|
// { // ChangeResourceRecordSetsResponse
|
||||||
|
// ChangeInfo: { // ChangeInfo
|
||||||
|
// Id: "STRING_VALUE", // required
|
||||||
|
// Status: "PENDING" || "INSYNC", // required
|
||||||
|
// SubmittedAt: new Date("TIMESTAMP"), // required
|
||||||
|
// Comment: "STRING_VALUE",
|
||||||
|
// },
|
||||||
|
// };*/
|
||||||
|
}
|
||||||
|
|
||||||
|
async doRequest<T>(call:()=>Promise<T>):Promise<T>{
|
||||||
|
try{
|
||||||
|
return await call();
|
||||||
|
}catch(err){
|
||||||
|
this.logger.error(`调用接口失败:${err.Error?.Message || err.message},requestId:${err.requestId}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-2
@@ -1,7 +1,7 @@
|
|||||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||||
import { AwsAccess, AwsRegions } from "../access.js";
|
import { AwsAccess, AwsRegions } from "../access.js";
|
||||||
import { AwsAcmClient } from "../libs/aws-acm-client.js";
|
import { AwsClient } from "../libs/aws-client.js";
|
||||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||||
import { optionsUtils } from "@certd/basic";
|
import { optionsUtils } from "@certd/basic";
|
||||||
|
|
||||||
@@ -115,9 +115,10 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async uploadToACM(access: AwsAccess, cert: CertInfo) {
|
private async uploadToACM(access: AwsAccess, cert: CertInfo) {
|
||||||
const acmClient = new AwsAcmClient({
|
const acmClient = new AwsClient({
|
||||||
access,
|
access,
|
||||||
region: this.region,
|
region: this.region,
|
||||||
|
logger: this.logger,
|
||||||
});
|
});
|
||||||
const awsCertARN = await acmClient.importCertificate(cert);
|
const awsCertARN = await acmClient.importCertificate(cert);
|
||||||
this.logger.info('证书上传成功,id=', awsCertARN);
|
this.logger.info('证书上传成功,id=', awsCertARN);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
|
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
|
||||||
import { CertInfo } from '@certd/plugin-cert';
|
import { CertInfo } from '@certd/plugin-cert';
|
||||||
import { AwsAccess, AwsRegions } from '../access.js';
|
import { AwsAccess, AwsRegions } from '../access.js';
|
||||||
import { AwsAcmClient } from '../libs/aws-acm-client.js';
|
import { AwsClient } from '../libs/aws-client.js';
|
||||||
import { CertApplyPluginNames} from '@certd/plugin-cert';
|
import { CertApplyPluginNames} from '@certd/plugin-cert';
|
||||||
@IsTaskPlugin({
|
@IsTaskPlugin({
|
||||||
name: 'AwsUploadToACM',
|
name: 'AwsUploadToACM',
|
||||||
@@ -59,9 +59,10 @@ export class AwsUploadToACM extends AbstractTaskPlugin {
|
|||||||
async execute(): Promise<void> {
|
async execute(): Promise<void> {
|
||||||
const { cert, accessId, region } = this;
|
const { cert, accessId, region } = this;
|
||||||
const access = await this.getAccess<AwsAccess>(accessId);
|
const access = await this.getAccess<AwsAccess>(accessId);
|
||||||
const acmClient = new AwsAcmClient({
|
const acmClient = new AwsClient({
|
||||||
access,
|
access,
|
||||||
region,
|
region,
|
||||||
|
logger: this.logger,
|
||||||
});
|
});
|
||||||
this.awsCertARN = await acmClient.importCertificate(cert);
|
this.awsCertARN = await acmClient.importCertificate(cert);
|
||||||
this.logger.info('证书上传成功,id=', this.awsCertARN);
|
this.logger.info('证书上传成功,id=', this.awsCertARN);
|
||||||
|
|||||||
@@ -170,13 +170,15 @@ export class HauweiDeployCertToOBS extends AbstractTaskPlugin {
|
|||||||
const params:any = {
|
const params:any = {
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
DomainName: domain,
|
DomainName: domain,
|
||||||
Name: this.buildCertName( domain)
|
DomainBody:{
|
||||||
|
Name: this.buildCertName( domain),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (typeof cert === 'string'){
|
if (typeof cert === 'string'){
|
||||||
params.CertificateId= cert
|
params.DomainBody.CertificateId= cert
|
||||||
}else{
|
}else{
|
||||||
params.Certificate= cert.crt
|
params.DomainBody.Certificate= cert.crt
|
||||||
params.PrivateKey = cert.key
|
params.DomainBody.PrivateKey = cert.key
|
||||||
}
|
}
|
||||||
const res = await obsClient.setBucketCustomDomain(params)
|
const res = await obsClient.setBucketCustomDomain(params)
|
||||||
this.checkRet(res)
|
this.checkRet(res)
|
||||||
|
|||||||
+2
-2
@@ -54,8 +54,8 @@ export class TencentDeleteExpiringCert extends AbstractPlusTaskPlugin {
|
|||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: '即将过期天数',
|
title: '即将过期天数',
|
||||||
helper:
|
helper:
|
||||||
'仅删除有效期小于此天数的证书,\n<span class="color-red">注意:`1.26.14`版本之前Certd创建的证书流水线默认是到期前20天才更新证书,需要将之前创建的证书申请任务的更新天数改为35天,保证删除之前就已经替换掉即将过期证书</span>',
|
'仅删除有效期小于此天数的证书',
|
||||||
value: 30,
|
value: 18,
|
||||||
component: {
|
component: {
|
||||||
name: 'a-input-number',
|
name: 'a-input-number',
|
||||||
vModel: 'value',
|
vModel: 'value',
|
||||||
|
|||||||
Generated
+1107
-145
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user