mirror of https://github.com/certd/certd
perf: 支持aws cloudfront
parent
b45977c29a
commit
0ae39f160a
|
@ -21,8 +21,9 @@ export const pluginGroups = {
|
||||||
huawei: new PluginGroup("huawei", "华为云", 3),
|
huawei: new PluginGroup("huawei", "华为云", 3),
|
||||||
tencent: new PluginGroup("tencent", "腾讯云", 4),
|
tencent: new PluginGroup("tencent", "腾讯云", 4),
|
||||||
qiniu: new PluginGroup("qiniu", "七牛云", 5),
|
qiniu: new PluginGroup("qiniu", "七牛云", 5),
|
||||||
host: new PluginGroup("host", "主机", 6),
|
aws: new PluginGroup("aws", "亚马逊云", 6),
|
||||||
cdn: new PluginGroup("cdn", "CDN", 7),
|
host: new PluginGroup("host", "主机", 7),
|
||||||
panel: new PluginGroup("panel", "面板", 8),
|
cdn: new PluginGroup("cdn", "CDN", 8),
|
||||||
other: new PluginGroup("other", "其他", 9),
|
panel: new PluginGroup("panel", "面板", 9),
|
||||||
|
other: new PluginGroup("other", "其他", 10),
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,6 +31,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alicloud/pop-core": "^1.7.10",
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
|
"@aws-sdk/client-acm": "^3.699.0",
|
||||||
|
"@aws-sdk/client-cloudfront": "^3.699.0",
|
||||||
|
"@aws-sdk/client-s3": "^3.705.0",
|
||||||
"@certd/acme-client": "^1.28.2",
|
"@certd/acme-client": "^1.28.2",
|
||||||
"@certd/basic": "^1.28.2",
|
"@certd/basic": "^1.28.2",
|
||||||
"@certd/commercial-core": "^1.28.2",
|
"@certd/commercial-core": "^1.28.2",
|
||||||
|
|
|
@ -13,3 +13,4 @@ export * from './plugin-woai/index.js';
|
||||||
export * from './plugin-cachefly/index.js';
|
export * from './plugin-cachefly/index.js';
|
||||||
export * from './plugin-gcore/index.js';
|
export * from './plugin-gcore/index.js';
|
||||||
export * from './plugin-qnap/index.js';
|
export * from './plugin-qnap/index.js';
|
||||||
|
export * from './plugin-aws/index.js';
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
||||||
|
|
||||||
|
export const AwsRegions = [
|
||||||
|
{ label: 'us-east-1', value: 'us-east-1' },
|
||||||
|
{ label: 'us-east-2', value: 'us-east-2' },
|
||||||
|
{ label: 'us-west-1', value: 'us-west-1' },
|
||||||
|
{ label: 'us-west-2', value: 'us-west-2' },
|
||||||
|
{ label: 'af-south-1', value: 'af-south-1' },
|
||||||
|
{ label: 'ap-east-1', value: 'ap-east-1' },
|
||||||
|
{ label: 'ap-northeast-1', value: 'ap-northeast-1' },
|
||||||
|
{ label: 'ap-northeast-2', value: 'ap-northeast-2' },
|
||||||
|
{ label: 'ap-northeast-3', value: 'ap-northeast-3' },
|
||||||
|
{ label: 'ap-south-1', value: 'ap-south-1' },
|
||||||
|
{ label: 'ap-south-2', value: 'ap-south-2' },
|
||||||
|
{ label: 'ap-southeast-1', value: 'ap-southeast-1' },
|
||||||
|
{ label: 'ap-southeast-2', value: 'ap-southeast-2' },
|
||||||
|
{ label: 'ap-southeast-3', value: 'ap-southeast-3' },
|
||||||
|
{ label: 'ap-southeast-4', value: 'ap-southeast-4' },
|
||||||
|
{ label: 'ap-southeast-5', value: 'ap-southeast-5' },
|
||||||
|
{ label: 'ca-central-1', value: 'ca-central-1' },
|
||||||
|
{ label: 'ca-west-1', value: 'ca-west-1' },
|
||||||
|
{ label: 'eu-central-1', value: 'eu-central-1' },
|
||||||
|
{ label: 'eu-central-2', value: 'eu-central-2' },
|
||||||
|
{ label: 'eu-north-1', value: 'eu-north-1' },
|
||||||
|
{ label: 'eu-south-1', value: 'eu-south-1' },
|
||||||
|
{ label: 'eu-south-2', value: 'eu-south-2' },
|
||||||
|
{ label: 'eu-west-1', value: 'eu-west-1' },
|
||||||
|
{ label: 'eu-west-2', value: 'eu-west-2' },
|
||||||
|
{ label: 'eu-west-3', value: 'eu-west-3' },
|
||||||
|
{ label: 'il-central-1', value: 'il-central-1' },
|
||||||
|
{ label: 'me-central-1', value: 'me-central-1' },
|
||||||
|
{ label: 'me-south-1', value: 'me-south-1' },
|
||||||
|
{ label: 'sa-east-1', value: 'sa-east-1' },
|
||||||
|
];
|
||||||
|
|
||||||
|
@IsAccess({
|
||||||
|
name: 'aws',
|
||||||
|
title: '亚马逊云aws授权',
|
||||||
|
desc: '',
|
||||||
|
icon: 'ant-design:aws-outlined',
|
||||||
|
})
|
||||||
|
export class AwsAccess extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: 'accessKeyId',
|
||||||
|
component: {
|
||||||
|
placeholder: 'accessKeyId',
|
||||||
|
},
|
||||||
|
helper:
|
||||||
|
'右上角->安全凭证->访问密钥,[点击前往](https://us-east-1.console.aws.amazon.com/iam/home?region=ap-east-1#/security_credentials/access-key-wizard)',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessKeyId = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: 'secretAccessKey',
|
||||||
|
component: {
|
||||||
|
placeholder: 'secretAccessKey',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
encrypt: true,
|
||||||
|
helper: '请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。',
|
||||||
|
})
|
||||||
|
secretAccessKey = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
new AwsAccess();
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './plugins/index.js';
|
||||||
|
export * from './access.js';
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 导入所需的 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 @@
|
||||||
|
export * from './plugin-deploy-to-cloudfront.js';
|
|
@ -0,0 +1,160 @@
|
||||||
|
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||||
|
import { CertInfo } from '@certd/plugin-cert';
|
||||||
|
import { AwsAccess, AwsRegions } from '../access.js';
|
||||||
|
import { AwsAcmClient } from '../libs/aws-acm-client.js';
|
||||||
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||||
|
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
|
||||||
|
|
||||||
|
@IsTaskPlugin({
|
||||||
|
name: 'AwsDeployToCloudFront',
|
||||||
|
title: '部署证书到 AWS CloudFront',
|
||||||
|
desc: '部署证书到 AWS CloudFront',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
group: pluginGroups.aws.key,
|
||||||
|
default: {
|
||||||
|
strategy: {
|
||||||
|
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class AwsDeployToCloudFront extends AbstractTaskPlugin {
|
||||||
|
@TaskInput({
|
||||||
|
title: '域名证书',
|
||||||
|
helper: '请选择前置任务输出的域名证书',
|
||||||
|
component: {
|
||||||
|
name: 'output-selector',
|
||||||
|
from: ['CertApply', 'CertApplyLego', 'AwsUploadToACM'],
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
cert!: CertInfo | string;
|
||||||
|
|
||||||
|
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||||
|
certDomains!: string[];
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: '区域',
|
||||||
|
helper: '证书上传区域',
|
||||||
|
component: {
|
||||||
|
name: 'a-auto-complete',
|
||||||
|
vModel: 'value',
|
||||||
|
options: AwsRegions,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
region!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: 'Access授权',
|
||||||
|
helper: 'aws的授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-selector',
|
||||||
|
type: 'aws',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessId!: string;
|
||||||
|
|
||||||
|
@TaskInput(
|
||||||
|
createRemoteSelectInputDefine({
|
||||||
|
title: '分配ID',
|
||||||
|
helper: '请选择distributions id',
|
||||||
|
action: AwsDeployToCloudFront.prototype.onGetDistributions.name,
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
distributionIds!: string[];
|
||||||
|
|
||||||
|
async onInstance() {}
|
||||||
|
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
const access = await this.getAccess<AwsAccess>(this.accessId);
|
||||||
|
|
||||||
|
let certId = this.cert as string;
|
||||||
|
if (typeof this.cert !== 'string') {
|
||||||
|
//先上传
|
||||||
|
certId = await this.uploadToACM(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,
|
||||||
|
CloudFrontDefaultCertificate: false,
|
||||||
|
ACMCertificateArn: certId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Id: distributionId,
|
||||||
|
IfMatch: configData.ETag,
|
||||||
|
});
|
||||||
|
await cloudFrontClient.send(updateDistributionCommand);
|
||||||
|
this.logger.info(`部署${distributionId}完成:`);
|
||||||
|
}
|
||||||
|
this.logger.info('部署完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadToACM(access: AwsAccess, cert: CertInfo) {
|
||||||
|
const acmClient = new AwsAcmClient({
|
||||||
|
access,
|
||||||
|
region: this.region,
|
||||||
|
});
|
||||||
|
const awsCertARN = await acmClient.importCertificate(cert);
|
||||||
|
this.logger.info('证书上传成功,id=', awsCertARN);
|
||||||
|
return awsCertARN;
|
||||||
|
}
|
||||||
|
|
||||||
|
//查找分配ID列表选项
|
||||||
|
async onGetDistributions() {
|
||||||
|
if (!this.accessId) {
|
||||||
|
throw new Error('请选择Access授权');
|
||||||
|
}
|
||||||
|
|
||||||
|
const access = await this.getAccess<AwsAccess>(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 AwsDeployToCloudFront();
|
|
@ -0,0 +1,71 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
@IsTaskPlugin({
|
||||||
|
name: 'AwsUploadToACM',
|
||||||
|
title: '上传证书 AWS ACM',
|
||||||
|
desc: '上传证书 AWS ACM',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
group: pluginGroups.aws.key,
|
||||||
|
default: {
|
||||||
|
strategy: {
|
||||||
|
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class AwsUploadToACM extends AbstractTaskPlugin {
|
||||||
|
@TaskInput({
|
||||||
|
title: '域名证书',
|
||||||
|
helper: '请选择前置任务输出的域名证书',
|
||||||
|
component: {
|
||||||
|
name: 'output-selector',
|
||||||
|
from: ['CertApply', 'CertApplyLego'],
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
cert!: CertInfo;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: 'Access授权',
|
||||||
|
helper: 'aws的授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-selector',
|
||||||
|
type: 'aws',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessId!: string;
|
||||||
|
@TaskInput({
|
||||||
|
title: '区域',
|
||||||
|
helper: '证书上传区域',
|
||||||
|
component: {
|
||||||
|
name: 'a-auto-complete',
|
||||||
|
vModel: 'value',
|
||||||
|
options: AwsRegions,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
region!: string;
|
||||||
|
|
||||||
|
@TaskOutput({
|
||||||
|
title: '证书ARN',
|
||||||
|
})
|
||||||
|
awsCertARN = '';
|
||||||
|
|
||||||
|
async onInstance() {}
|
||||||
|
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
const { cert, accessId, region } = this;
|
||||||
|
const access = await this.accessService.getById<AwsAccess>(accessId);
|
||||||
|
const acmClient = new AwsAcmClient({
|
||||||
|
access,
|
||||||
|
region,
|
||||||
|
});
|
||||||
|
this.awsCertARN = await acmClient.importCertificate(cert);
|
||||||
|
this.logger.info('证书上传成功,id=', this.awsCertARN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new AwsUploadToACM();
|
Loading…
Reference in New Issue