Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev

This commit is contained in:
xiaojunnuo
2025-12-25 22:20:59 +08:00
29 changed files with 1430 additions and 216 deletions
+7
View File
@@ -17,6 +17,13 @@ Certd® 是一个免费的全自动证书管理系统,让你的网站证书永
> 流水线数量现已调整为无限制,欢迎大家使用 > 流水线数量现已调整为无限制,欢迎大家使用
|官方开源地址: | |
| ---- | ---- |
| [Github](https://github.com/certd/certd)| ![](https://img.shields.io/github/stars/certd/certd?logo=github) |
| [Gitee](https://gitee.com/certd/certd) | ![](https://gitee.com/certd/certd/badge/star.svg?theme=dark) |
| [AtomGit](https://atomgit.com/certd/certd) |![](https://atomgit.com/certd/certd/star/badge.svg) |
## 一、特性 ## 一、特性
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。 本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
+9
View File
@@ -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) ![](https://img.shields.io/github/stars/certd/certd?logo=github)
[Gitee](https://gitee.com/certd/certd) ![](https://gitee.com/certd/certd/badge/star.svg?theme=dark)
[AtomGit](https://atomgit.com/certd/certd) ![](https://atomgit.com/certd/certd/star/badge.svg)
## 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.
+1
View File
@@ -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"},
] ]
}, },
{ {
+7
View File
@@ -6,6 +6,13 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具 关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
| 官方开源地址: | |
| ---- | ---- |
| [Github](https://github.com/certd/certd)| ![](https://img.shields.io/github/stars/certd/certd?logo=github) |
| [Gitee](https://gitee.com/certd/certd) | ![](https://gitee.com/certd/certd/badge/star.svg?theme=dark) |
| [AtomGit](https://atomgit.com/certd/certd) |![](https://atomgit.com/certd/certd/star/badge.svg) |
![首页](../images/start/home.png) ![首页](../images/start/home.png)
## 1、关于证书续期 ## 1、关于证书续期
+8
View File
@@ -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记录,删除即可
+1 -1
View File
@@ -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代理方案
-10
View File
@@ -1,10 +0,0 @@
# 证书申请失败情况
## DNS记录问题
1. DNS 不要设置CAA记录,删除即可
2. DNSSEC相关报错,DNSSEC管理中删除即可
3. DNS 有其他平台申请过的_acme-challenge记录,删除即可
+11
View File
@@ -0,0 +1,11 @@
# IP证书申请
certd已支持IP证书申请
> 注意:IP证书有效期只有7天。
## 申请方式
相比普通的域名证书申请方式区别在于:
1. 域名栏填写IP
2. 校验方式选择HTTP(只能HTTP)
3. 证书颁发机构选择默认的Let's Encrypt
4. 过期更新天数改成2天
+6 -3
View File
@@ -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: "教程结束",
+2 -1
View File
@@ -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);
// 返回证书 ARNAmazon 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);
// 返回证书 ARNAmazon 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;
}
}
}
@@ -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)
@@ -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',
+1107 -145
View File
File diff suppressed because it is too large Load Diff