feat: AWS 中国区 CloudFront 证书部署(IAM 证书)

pull/406/head
wifi9984 2025-05-31 21:46:21 +08:00
parent a12b824339
commit 8a55beda92
8 changed files with 1365 additions and 3685 deletions

View File

@ -39,6 +39,7 @@
"@alicloud/tea-typescript": "^1.8.0",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-acm": "^3.699.0",
"@aws-sdk/client-iam": "^3.699.0",
"@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0",
"@certd/acme-client": "^1.34.9",

View File

@ -14,6 +14,7 @@ export * from './plugin-cachefly/index.js';
export * from './plugin-gcore/index.js';
export * from './plugin-qnap/index.js';
export * from './plugin-aws/index.js';
export * from './plugin-aws-cn/index.js';
export * from './plugin-dnsla/index.js';
export * from './plugin-upyun/index.js';
export * from './plugin-volcengine/index.js'

View File

@ -0,0 +1,38 @@
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
export const AwsCNRegions = [
{ label: 'cn-north-1', value: 'cn-north-1' },
{ label: 'cn-northwest-1', value: 'cn-northwest-1' },
];
@IsAccess({
name: 'aws-cn',
title: '亚马逊云科技(国区)授权',
desc: '',
icon: 'svg:icon-aws',
})
export class AwsCNAccess extends BaseAccess {
@AccessInput({
title: 'accessKeyId',
component: {
placeholder: 'accessKeyId',
},
helper:
'右上角->安全凭证->访问密钥,[点击前往](https://cn-north-1.console.amazonaws.cn/iam/home?region=cn-north-1#/security_credentials/access-key-wizard#)',
required: true,
})
accessKeyId = '';
@AccessInput({
title: 'secretAccessKey',
component: {
placeholder: 'secretAccessKey',
},
required: true,
encrypt: true,
helper: '请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。',
})
secretAccessKey = '';
}
new AwsCNAccess();

View File

@ -0,0 +1,2 @@
export * from './plugins/index.js';
export * from './access.js';

View File

@ -0,0 +1,42 @@
// 导入所需的 SDK 模块
import { AwsCNAccess } from '../access.js';
import { CertInfo } from '@certd/plugin-cert';
type AwsIAMClientOptions = { access: AwsCNAccess; region: string };
export class AwsIAMClient {
options: AwsIAMClientOptions;
access: AwsCNAccess;
region: string;
constructor(options: AwsIAMClientOptions) {
this.options = options;
this.access = options.access;
this.region = options.region;
}
async importCertificate(certInfo: CertInfo, certName: string) {
// 创建 ACM 客户端
const { IAMClient, UploadServerCertificateCommand } = await import('@aws-sdk/client-iam');
const iamClient = new IAMClient({
region: this.region, // 替换为您的 AWS 区域
credentials: {
accessKeyId: this.access.accessKeyId, // 从环境变量中读取
secretAccessKey: this.access.secretAccessKey,
},
});
const cert = certInfo.crt.split('-----END CERTIFICATE-----')[0] + '-----END CERTIFICATE-----';
const chain = certInfo.crt.split('-----END CERTIFICATE-----\n')[1];
// 构建上传参数
const command = new UploadServerCertificateCommand({
Path: '/cloudfront/',
ServerCertificateName: certName,
CertificateBody: cert,
PrivateKey: certInfo.key,
CertificateChain: chain
})
const data = await iamClient.send(command);
console.log('Upload successful:', data);
// 返回证书 ID
return data.ServerCertificateMetadata.ServerCertificateId;
}
}

View File

@ -0,0 +1 @@
export * from './plugin-deploy-to-cloudfront.js';

View File

@ -0,0 +1,165 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { AwsCNAccess, AwsCNRegions } from "../access.js";
import { AwsIAMClient } from "../libs/aws-iam-client.js";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { optionsUtils } from "@certd/basic/dist/utils/util.options.js";
@IsTaskPlugin({
name: 'AwsCNDeployToCloudFront',
title: 'AWS(国区)-部署证书到CloudFront',
desc: '部署证书到 AWS CloudFront',
icon: 'svg:icon-aws',
group: pluginGroups.aws.key,
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class AwsCNDeployToCloudFront extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'AwsUploadToACM'],
},
required: true,
})
cert!: CertInfo | string;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: '区域',
helper: '证书上传区域',
component: {
name: 'a-auto-complete',
vModel: 'value',
options: AwsCNRegions,
},
required: true,
})
region!: string;
@TaskInput({
title: 'Access授权',
helper: 'aws的授权',
component: {
name: 'access-selector',
type: 'aws-cn',
},
required: true,
})
accessId!: string;
@TaskInput({
title: '证书名称',
helper: '上传后将以此名称作为前缀备注',
})
certName!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '分配ID',
helper: '请选择distributions id',
action: AwsCNDeployToCloudFront.prototype.onGetDistributions.name,
required: true,
})
)
distributionIds!: string[];
async onInstance() {}
async execute(): Promise<void> {
const access = await this.getAccess<AwsCNAccess>(this.accessId);
let certId = this.cert as string;
if (typeof this.cert !== 'string') {
//先上传
certId = await this.uploadToIAM(access, this.cert);
}
//部署到CloudFront
const { CloudFrontClient, UpdateDistributionCommand, GetDistributionConfigCommand } = await import('@aws-sdk/client-cloudfront');
const cloudFrontClient = new CloudFrontClient({
region: this.region,
credentials: {
accessKeyId: access.accessKeyId,
secretAccessKey: access.secretAccessKey,
},
});
// update-distribution
for (const distributionId of this.distributionIds) {
// get-distribution-config
const getDistributionConfigCommand = new GetDistributionConfigCommand({
Id: distributionId,
});
const configData = await cloudFrontClient.send(getDistributionConfigCommand);
const updateDistributionCommand = new UpdateDistributionCommand({
DistributionConfig: {
...configData.DistributionConfig,
ViewerCertificate: {
...configData.DistributionConfig.ViewerCertificate,
IAMCertificateId: certId,
},
},
Id: distributionId,
IfMatch: configData.ETag,
});
await cloudFrontClient.send(updateDistributionCommand);
this.logger.info(`部署${distributionId}完成:`);
}
this.logger.info('部署完成');
}
private async uploadToIAM(access: AwsCNAccess, cert: CertInfo) {
const acmClient = new AwsIAMClient({
access,
region: this.region,
});
const awsCertID = await acmClient.importCertificate(cert, this.appendTimeSuffix(this.certName));
this.logger.info('证书上传成功,id=', awsCertID);
return awsCertID;
}
//查找分配ID列表选项
async onGetDistributions() {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
const access = await this.getAccess<AwsCNAccess>(this.accessId);
const { CloudFrontClient, ListDistributionsCommand } = await import('@aws-sdk/client-cloudfront');
const cloudFrontClient = new CloudFrontClient({
region: this.region,
credentials: {
accessKeyId: access.accessKeyId,
secretAccessKey: access.secretAccessKey,
},
});
// list-distributions
const listDistributionsCommand = new ListDistributionsCommand({});
const data = await cloudFrontClient.send(listDistributionsCommand);
const distributions = data.DistributionList?.Items;
if (!distributions || distributions.length === 0) {
throw new Error('找不到CloudFront分配ID您可以手动输入');
}
const options = distributions.map((item: any) => {
return {
value: item.Id,
label: `${item.DomainName}<${item.Id}>`,
domain: item.DomainName,
};
});
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
}
new AwsCNDeployToCloudFront();

File diff suppressed because it is too large Load Diff