mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 13:39:53 +00:00
feat: add volcengine cdn deployer
This commit is contained in:
parent
2bacf76664
commit
9eae8f5077
@ -79,7 +79,7 @@ make local.run
|
|||||||
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB |
|
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB |
|
||||||
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
||||||
| 多吉云 | | √ | 可部署到多吉云 CDN |
|
| 多吉云 | | √ | 可部署到多吉云 CDN |
|
||||||
| 火山引擎 | √ | √ | 可签发在火山引擎注册的域名;可部署到火山引擎 Live |
|
| 火山引擎 | √ | √ | 可签发在火山引擎注册的域名;可部署到火山引擎 Live、CDN |
|
||||||
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
||||||
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
||||||
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
|
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
|
||||||
|
@ -78,7 +78,7 @@ password:1234567890
|
|||||||
| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB |
|
| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB |
|
||||||
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
||||||
| Doge Cloud | | √ | Supports deployment to Doge Cloud CDN |
|
| Doge Cloud | | √ | Supports deployment to Doge Cloud CDN |
|
||||||
| Volcengine | √ | √ | Supports domains registered on Volcengine; supports deployment to Volcengine Live |
|
| Volcengine | √ | √ | Supports domains registered on Volcengine; supports deployment to Volcengine Live、CDN |
|
||||||
| AWS | √ | | Supports domains managed on AWS Route53 |
|
| AWS | √ | | Supports domains managed on AWS Route53 |
|
||||||
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
||||||
| GoDaddy | √ | | Supports domains registered on GoDaddy |
|
| GoDaddy | √ | | Supports domains registered on GoDaddy |
|
||||||
|
@ -41,6 +41,7 @@ const (
|
|||||||
targetWebhook = "webhook"
|
targetWebhook = "webhook"
|
||||||
targetK8sSecret = "k8s-secret"
|
targetK8sSecret = "k8s-secret"
|
||||||
targetVolcengineLive = "volcengine-live"
|
targetVolcengineLive = "volcengine-live"
|
||||||
|
targetVolcengineCDN = "volcengine-cdn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeployerOption struct {
|
type DeployerOption struct {
|
||||||
@ -153,6 +154,8 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
return NewK8sSecretDeployer(option)
|
return NewK8sSecretDeployer(option)
|
||||||
case targetVolcengineLive:
|
case targetVolcengineLive:
|
||||||
return NewVolcengineLiveDeployer(option)
|
return NewVolcengineLiveDeployer(option)
|
||||||
|
case targetVolcengineCDN:
|
||||||
|
return NewVolcengineCDNDeployer(option)
|
||||||
}
|
}
|
||||||
return nil, errors.New("unsupported deploy target")
|
return nil, errors.New("unsupported deploy target")
|
||||||
}
|
}
|
||||||
|
116
internal/deployer/volcengine_cdn.go
Normal file
116
internal/deployer/volcengine_cdn.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
volcenginecdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/volcengine/volc-sdk-golang/service/cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VolcengineCDNDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
sdkClient *cdn.CDN
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVolcengineCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.VolcengineAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
client := cdn.NewInstance()
|
||||||
|
client.Client.SetAccessKey(access.AccessKeyID)
|
||||||
|
client.Client.SetSecretKey(access.SecretAccessKey)
|
||||||
|
uploader, err := volcenginecdn.New(&volcenginecdn.VolcengineCDNUploaderConfig{
|
||||||
|
AccessKeyId: access.AccessKeyID,
|
||||||
|
AccessKeySecret: access.SecretAccessKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
return &VolcengineCDNDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VolcengineCDNDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VolcengineCDNDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VolcengineCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
apiCtx := context.Background()
|
||||||
|
// 上传证书
|
||||||
|
upres, err := d.sslUploader.Upload(apiCtx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
configDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if strings.HasPrefix(configDomain, "*.") {
|
||||||
|
// 获取证书可以部署的域名
|
||||||
|
// REF: https://www.volcengine.com/docs/6454/125711
|
||||||
|
describeCertConfigReq := &cdn.DescribeCertConfigRequest{
|
||||||
|
CertId: upres.CertId,
|
||||||
|
}
|
||||||
|
describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertConfig'")
|
||||||
|
}
|
||||||
|
for i := range describeCertConfigResp.Result.CertNotConfig {
|
||||||
|
// 当前未启用 HTTPS 的加速域名列表。
|
||||||
|
domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain)
|
||||||
|
}
|
||||||
|
for i := range describeCertConfigResp.Result.OtherCertConfig {
|
||||||
|
// 已启用了 HTTPS 的加速域名列表。这些加速域名关联的证书不是您指定的证书。
|
||||||
|
domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain)
|
||||||
|
}
|
||||||
|
for i := range describeCertConfigResp.Result.SpecifiedCertConfig {
|
||||||
|
// 已启用了 HTTPS 的加速域名列表。这些加速域名关联了您指定的证书。
|
||||||
|
d.infos = append(d.infos, fmt.Sprintf("%s域名已配置该证书", describeCertConfigResp.Result.SpecifiedCertConfig[i].Domain))
|
||||||
|
}
|
||||||
|
if len(domains) == 0 {
|
||||||
|
if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 {
|
||||||
|
// 所有匹配的域名都配置了该证书,跳过部署
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return xerrors.Errorf("未查询到匹配的域名: %s", configDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
domains = append(domains, configDomain)
|
||||||
|
}
|
||||||
|
// 部署证书
|
||||||
|
// REF: https://www.volcengine.com/docs/6454/125712
|
||||||
|
for i := range domains {
|
||||||
|
batchDeployCertReq := &cdn.BatchDeployCertRequest{
|
||||||
|
CertId: upres.CertId,
|
||||||
|
Domain: domains[i],
|
||||||
|
}
|
||||||
|
BindCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.BatchDeployCert'")
|
||||||
|
} else {
|
||||||
|
d.infos = append(d.infos, toStr(fmt.Sprintf("%s域名的证书已修改", domains[i]), BindCertResp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package volcenginecdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
"github.com/volcengine/volc-sdk-golang/service/cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VolcengineCDNUploaderConfig struct {
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VolcengineCDNUploader struct {
|
||||||
|
config *VolcengineCDNUploaderConfig
|
||||||
|
sdkClient *cdn.CDN
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*VolcengineCDNUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *VolcengineCDNUploaderConfig) (*VolcengineCDNUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
instance := cdn.NewInstance()
|
||||||
|
client := instance.Client
|
||||||
|
client.SetAccessKey(config.AccessKeyId)
|
||||||
|
client.SetSecretKey(config.AccessKeySecret)
|
||||||
|
|
||||||
|
return &VolcengineCDNUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: instance,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *VolcengineCDNUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 解析证书内容
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 查询证书列表,避免重复上传
|
||||||
|
// REF: https://www.volcengine.com/docs/6454/125709
|
||||||
|
pageNum := int64(1)
|
||||||
|
pageSize := int64(100)
|
||||||
|
certSource := "volc_cert_center"
|
||||||
|
listCertReq := &cdn.ListCertInfoRequest{
|
||||||
|
PageNum: &pageNum,
|
||||||
|
PageSize: &pageSize,
|
||||||
|
Source: certSource,
|
||||||
|
}
|
||||||
|
searchTotal := 0
|
||||||
|
for {
|
||||||
|
listCertResp, err := u.sdkClient.ListCertInfo(listCertReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ListCertInfo'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listCertResp.Result.CertInfo != nil {
|
||||||
|
for _, certDetail := range listCertResp.Result.CertInfo {
|
||||||
|
hash := sha256.Sum256(certX509.Raw)
|
||||||
|
isSameCert := hex.EncodeToString(hash[:]) == certDetail.CertFingerprint.Sha256
|
||||||
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
|
if isSameCert {
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certDetail.CertId,
|
||||||
|
CertName: certDetail.Desc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchTotal += len(listCertResp.Result.CertInfo)
|
||||||
|
if int(listCertResp.Result.Total) > searchTotal {
|
||||||
|
pageNum++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// 生成新证书名(需符合火山引擎命名规则)
|
||||||
|
var certId, certName string
|
||||||
|
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://www.volcengine.com/docs/6454/1245763
|
||||||
|
addCertificateReq := &cdn.AddCertificateRequest{
|
||||||
|
Certificate: certPem,
|
||||||
|
PrivateKey: privkeyPem,
|
||||||
|
Source: &certSource,
|
||||||
|
Desc: &certName,
|
||||||
|
}
|
||||||
|
createCertResp, err := u.sdkClient.AddCertificate(addCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.AddCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId = createCertResp.Result.CertId
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: certName,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -28,6 +28,7 @@ import DeployToSSH from "./DeployToSSH";
|
|||||||
import DeployToWebhook from "./DeployToWebhook";
|
import DeployToWebhook from "./DeployToWebhook";
|
||||||
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
|
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
|
||||||
import DeployToVolcengineLive from "./DeployToVolcengineLive"
|
import DeployToVolcengineLive from "./DeployToVolcengineLive"
|
||||||
|
import DeployToVolcengineCDN from "./DeployToVolcengineCDN"
|
||||||
import { deployTargetsMap, type DeployConfig } from "@/domain/domain";
|
import { deployTargetsMap, type DeployConfig } from "@/domain/domain";
|
||||||
import { accessProvidersMap } from "@/domain/access";
|
import { accessProvidersMap } from "@/domain/access";
|
||||||
import { useConfigContext } from "@/providers/config";
|
import { useConfigContext } from "@/providers/config";
|
||||||
@ -178,6 +179,9 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
|||||||
case "volcengine-live":
|
case "volcengine-live":
|
||||||
childComponent = <DeployToVolcengineLive />;
|
childComponent = <DeployToVolcengineLive />;
|
||||||
break;
|
break;
|
||||||
|
case "volcengine-cdn":
|
||||||
|
childComponent = <DeployToVolcengineCDN />;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
68
ui/src/components/certimate/DeployToVolcengineCDN.tsx
Normal file
68
ui/src/components/certimate/DeployToVolcengineCDN.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { produce } from "immer";
|
||||||
|
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToVolcengineCDNConfigParams = {
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployToVolcengineCDN = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToVolcengineCDNConfigParams>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!config.id) {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
config: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setErrors({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const res = formSchema.safeParse(config.config);
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col space-y-8">
|
||||||
|
<div>
|
||||||
|
<Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("domain.deployment.form.domain.placeholder")}
|
||||||
|
className="w-full mt-1"
|
||||||
|
value={config?.config?.domain}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
|
draft.config.domain = e.target.value?.trim();
|
||||||
|
});
|
||||||
|
setConfig(nv);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeployToVolcengineCDN;
|
@ -93,5 +93,6 @@ export const deployTargetsMap: Map<DeployTarget["type"], DeployTarget> = new Map
|
|||||||
["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"],
|
["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"],
|
||||||
["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"],
|
["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"],
|
||||||
["volcengine-live", "common.provider.volcengine.live", "/imgs/providers/volcengine.svg"],
|
["volcengine-live", "common.provider.volcengine.live", "/imgs/providers/volcengine.svg"],
|
||||||
|
["volcengine-cdn", "common.provider.volcengine.cdn", "/imgs/providers/volcengine.svg"],
|
||||||
].map(([type, name, icon]) => [type, { type, provider: type.split("-")[0], name, icon }])
|
].map(([type, name, icon]) => [type, { type, provider: type.split("-")[0], name, icon }])
|
||||||
);
|
);
|
||||||
|
@ -92,5 +92,6 @@
|
|||||||
"common.provider.serverchan": "ServerChan",
|
"common.provider.serverchan": "ServerChan",
|
||||||
"common.provider.bark": "Bark",
|
"common.provider.bark": "Bark",
|
||||||
"common.provider.volcengine": "Volcengine",
|
"common.provider.volcengine": "Volcengine",
|
||||||
"common.provider.volcengine.live": "Volcengine - Live"
|
"common.provider.volcengine.live": "Volcengine - Live",
|
||||||
|
"common.provider.volcengine.cdn": "Volcengine - CDN"
|
||||||
}
|
}
|
||||||
|
@ -92,5 +92,6 @@
|
|||||||
"common.provider.serverchan": "Server酱",
|
"common.provider.serverchan": "Server酱",
|
||||||
"common.provider.bark": "Bark",
|
"common.provider.bark": "Bark",
|
||||||
"common.provider.volcengine": "火山引擎",
|
"common.provider.volcengine": "火山引擎",
|
||||||
"common.provider.volcengine.live": "火山引擎 - 视频直播"
|
"common.provider.volcengine.live": "火山引擎 - 视频直播",
|
||||||
|
"common.provider.volcengine.cdn": "火山引擎 - CDN"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user