perf: 支持aws route53 dns

This commit is contained in:
xiaojunnuo
2025-12-25 18:56:27 +08:00
parent 0e467a6024
commit cbb8319cfa
13 changed files with 1352 additions and 197 deletions
@@ -64,6 +64,18 @@ export class AwsAccess extends BaseAccess {
helper: '请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。',
})
secretAccessKey = '';
@AccessInput({
title: 'region',
component: {
name:"a-select",
options: AwsRegions,
},
required: true,
helper: '请选择您的默认AWS区域,默认us-east-1',
options: AwsRegions,
})
region = '';
}
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 './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 { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
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 { optionsUtils } from "@certd/basic";
@@ -115,9 +115,10 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
}
private async uploadToACM(access: AwsAccess, cert: CertInfo) {
const acmClient = new AwsAcmClient({
const acmClient = new AwsClient({
access,
region: this.region,
logger: this.logger,
});
const awsCertARN = await acmClient.importCertificate(cert);
this.logger.info('证书上传成功,id=', awsCertARN);
@@ -1,7 +1,7 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import { CertInfo } from '@certd/plugin-cert';
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';
@IsTaskPlugin({
name: 'AwsUploadToACM',
@@ -59,9 +59,10 @@ export class AwsUploadToACM extends AbstractTaskPlugin {
async execute(): Promise<void> {
const { cert, accessId, region } = this;
const access = await this.getAccess<AwsAccess>(accessId);
const acmClient = new AwsAcmClient({
const acmClient = new AwsClient({
access,
region,
logger: this.logger,
});
this.awsCertARN = await acmClient.importCertificate(cert);
this.logger.info('证书上传成功,id=', this.awsCertARN);