From a3fb24993d7ac8fbb0bb354fa02ef067f609021e Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Wed, 31 Dec 2025 17:01:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=80=90=E7=A0=B4=E5=9D=8F=E6=80=A7?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=80=91=E6=8F=92=E4=BB=B6=E6=94=B9=E4=B8=BA?= =?UTF-8?q?metadata=E5=8A=A0=E8=BD=BD=E6=A8=A1=E5=BC=8F=EF=BC=8Cplugin-cer?= =?UTF-8?q?t=E3=80=81plugin-lib=E5=8C=85=E9=83=A8=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=BD=AC=E7=A7=BB=E5=88=B0certd-server=E4=B8=AD?= =?UTF-8?q?=EF=BC=8C=E5=BD=B1=E5=93=8D=E8=87=AA=E5=AE=9A=E4=B9=89=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E9=9C=80=E8=A6=81=E4=BF=AE=E6=94=B9=E7=9B=B8?= =?UTF-8?q?=E5=85=B3import=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ssh、aliyun、tencent、qiniu、oss等 access和client需要转移import --- docs/guide/plugins/access.md | 118 ++--- docs/guide/plugins/deploy.md | 131 +++--- .../core/pipeline/src/access/decorator.ts | 6 + .../pipeline/src/notification/decorator.ts | 6 + .../core/pipeline/src/plugin/decorator.ts | 12 + packages/core/pipeline/src/service/cname.ts | 2 +- .../src/user/addon/api/decorator.ts | 6 + packages/plugins/plugin-cert/package.json | 7 +- packages/plugins/plugin-cert/src/index.ts | 4 +- packages/plugins/plugin-lib/package.json | 6 +- .../src/cert}/cert-reader.ts | 14 +- .../plugins/plugin-lib/src/cert/consts.ts | 2 + .../src/cert}/convert.ts | 7 +- .../src/cert}/dns-provider/api.ts | 0 .../src/cert}/dns-provider/base.ts | 0 .../src/cert}/dns-provider/decorator.ts | 3 + .../src/cert}/dns-provider/domain-parser.ts | 0 .../src/cert}/dns-provider/index.ts | 0 .../src/cert}/dns-provider/registry.ts | 0 packages/plugins/plugin-lib/src/cert/index.ts | 4 + .../plugins/plugin-lib/src/ctyun/index.ts | 1 - packages/plugins/plugin-lib/src/index.ts | 9 +- packages/plugins/plugin-lib/src/lib/check.ts | 17 + packages/plugins/plugin-lib/src/lib/index.ts | 1 + .../plugin-lib/src/tencent/lib/cos-client.js | 183 -------- packages/ui/certd-client/package.json | 2 +- packages/ui/certd-server/.env | 1 + packages/ui/certd-server/.env.production | 1 + packages/ui/certd-server/.env.production.yaml | 3 - .../certd-server/metadata/access_1panel.yaml | 102 ++++ .../certd-server/metadata/access_aliesa.yaml | 29 ++ .../certd-server/metadata/access_alioss.yaml | 85 ++++ .../certd-server/metadata/access_alipay.yaml | 29 ++ .../certd-server/metadata/access_aliyun.yaml | 22 + .../certd-server/metadata/access_baidu.yaml | 29 ++ .../certd-server/metadata/access_baishan.yaml | 15 + .../certd-server/metadata/access_baota.yaml | 44 ++ .../metadata/access_baotawaf.yaml | 36 ++ .../certd-server/metadata/access_cdnfly.yaml | 93 ++++ .../certd-server/metadata/access_ctyun.yaml | 22 + .../ui/certd-server/metadata/access_eab.yaml | 31 ++ .../ui/certd-server/metadata/access_ftp.yaml | 49 ++ .../certd-server/metadata/access_google.yaml | 84 ++++ .../ui/certd-server/metadata/access_k8s.yaml | 23 + .../metadata/access_kuocaicdn.yaml | 20 + .../certd-server/metadata/access_lecdn.yaml | 69 +++ .../certd-server/metadata/access_lucky.yaml | 29 ++ .../certd-server/metadata/access_maoyun.yaml | 35 ++ .../certd-server/metadata/access_plesk.yaml | 33 ++ .../certd-server/metadata/access_qiniu.yaml | 19 + .../metadata/access_qiniuoss.yaml | 20 + .../ui/certd-server/metadata/access_s3.yaml | 53 +++ .../metadata/access_safeline.yaml | 26 + .../ui/certd-server/metadata/access_sftp.yaml | 23 + .../ui/certd-server/metadata/access_ssh.yaml | 128 +++++ .../metadata/access_synology.yaml | 88 ++++ .../certd-server/metadata/access_tencent.yaml | 46 ++ .../metadata/access_tencentcos.yaml | 75 +++ .../metadata/access_unicloud.yaml | 21 + .../certd-server/metadata/access_wxpay.yaml | 40 ++ .../certd-server/metadata/access_yfysms.yaml | 21 + .../certd-server/metadata/access_yidun.yaml | 22 + .../metadata/access_yidunrcdn.yaml | 20 + .../certd-server/metadata/access_yizhifu.yaml | 45 ++ .../deploy_1PanelDeployToWebsitePlugin.yaml | 107 +++++ .../deploy_AliyunDeployCertToAll.yaml | 177 +++++++ .../metadata/deploy_BaiduDeployToBLB.yaml | 112 +++++ .../metadata/deploy_BaiduDeployToCDN.yaml | 62 +++ .../metadata/deploy_BaiduUploadCert.yaml | 33 ++ .../metadata/deploy_BaishanUpdateCert.yaml | 45 ++ .../deploy_BaotaDeleteExpiringCert.yaml | 23 + .../metadata/deploy_BaotaDeployPanelCert.yaml | 32 ++ .../metadata/deploy_BaotaDeployWAF.yaml | 63 +++ .../deploy_BaotaDeployWebSiteCert.yaml | 84 ++++ .../deploy_BaotaDeployWebSiteWin.yaml | 72 +++ .../metadata/deploy_CdnflyDeployToCDN.yaml | 116 +++++ .../metadata/deploy_CertApply.yaml | 443 ++++++++++++++++++ .../deploy_CertApplyGetFormAliyun.yaml | 136 ++++++ .../metadata/deploy_CertApplyLego.yaml | 173 +++++++ .../metadata/deploy_CertApplyUpload.yaml | 150 ++++++ .../metadata/deploy_CtyunDeployToCDN.yaml | 98 ++++ .../deploy_DeployCertToAliyunAck.yaml | 137 ++++++ .../metadata/deploy_HostDeployToIIS.yaml | 77 +++ .../metadata/deploy_K8sApply.yaml | 52 ++ .../metadata/deploy_K8sDeployToIngress.yaml | 52 ++ .../metadata/deploy_K8sDeployToSecret.yaml | 67 +++ .../metadata/deploy_KuocaiDeployToRCDN.yaml | 59 +++ .../metadata/deploy_LeCDNUpdateCert.yaml | 57 +++ .../metadata/deploy_LeCDNUpdateCertV2.yaml | 58 +++ .../metadata/deploy_LuckyUpdateCert.yaml | 74 +++ .../metadata/deploy_MaoyunDeployToCdn.yaml | 73 +++ .../metadata/deploy_PleskDeploySiteCert.yaml | 80 ++++ .../metadata/deploy_PleskRefreshCert.yaml | 74 +++ .../metadata/deploy_QiniuDeployCertToOSS.yaml | 37 ++ .../deploy_SafelineDeployToWebsitePlugin.yaml | 76 +++ .../deploy_SynologyDeployToPanel.yaml | 43 ++ .../deploy_UniCloudDeployToSpace.yaml | 62 +++ .../metadata/deploy_UploadCertToFTP.yaml | 169 +++++++ .../metadata/deploy_YidunDeployToCDN.yaml | 46 ++ .../metadata/deploy_YidunDeployToRCDN.yaml | 59 +++ packages/ui/certd-server/package.json | 9 +- .../src/modules/auto/auto-b-load-plugins.ts | 10 +- .../src/modules/basic/sms/aliyun-sms.ts | 2 +- .../src/modules/basic/sms/tencent-sms.ts | 2 +- .../src/modules/basic/sms/yfy-sms.ts | 2 +- packages/ui/certd-server/src/plugins/index.ts | 5 +- .../plugins/plugin-admin/plugin-db-backup.ts | 5 +- .../dns-provider/aliesa-dns-provider.ts | 2 +- .../dns-provider/aliyun-dns-provider.ts | 3 +- .../src/plugins/plugin-aliyun/index.ts | 2 +- .../plugin/deploy-to-ack/index.ts | 277 +++++++++++ .../plugin/deploy-to-alb/index.ts | 6 +- .../plugin/deploy-to-all/index.ts | 307 ++++++++++++ .../plugin/deploy-to-apig/index.ts | 4 +- .../plugin/deploy-to-apigateway/index.ts | 3 +- .../plugin/deploy-to-cdn/index.ts | 4 +- .../plugin/deploy-to-dcdn/index.ts | 5 +- .../plugin/deploy-to-esa/index.ts | 4 +- .../plugin/deploy-to-fc/index.ts | 3 +- .../plugin/deploy-to-nlb/index.ts | 6 +- .../plugin/deploy-to-oss/index.ts | 4 +- .../plugin/deploy-to-slb/index.ts | 6 +- .../plugin/deploy-to-vod/index.ts | 3 +- .../plugin/deploy-to-waf/index.ts | 5 +- .../src/plugins/plugin-aliyun/plugin/index.ts | 1 + .../plugin/upload-to-aliyun/index.ts | 4 +- .../plugins/plugin-refresh-cert.ts | 3 +- .../plugins/plugin-captcha/tencent/index.ts | 2 +- .../plugins/plugin-cert}/access/eab-access.ts | 0 .../plugin-cert}/access/google-access.ts | 0 .../src/plugins/plugin-cert}/access/index.ts | 0 .../src/plugins/plugin-cert/index.ts | 2 + .../src/plugins/plugin-cert}/libs/google.ts | 0 .../plugin-cert}/plugin/cert-plugin/acme.ts | 6 +- .../plugin/cert-plugin/base-convert.ts | 5 +- .../plugin-cert}/plugin/cert-plugin/base.ts | 2 +- .../plugin/cert-plugin/custom/index.ts | 2 +- .../plugin/cert-plugin/getter/aliyun.ts | 4 +- .../plugin-cert}/plugin/cert-plugin/index.ts | 14 +- .../plugin/cert-plugin/lego/dns.ts | 0 .../plugin/cert-plugin/lego/index.ts | 2 +- .../src/plugins/plugin-cert}/plugin/index.ts | 3 - .../plugin-cmcc/plugin-deploy-to-cdn.ts | 2 +- .../src/plugins/plugin-fnos/index.ts | 4 +- .../plugins/plugin-check-release.ts | 2 +- .../plugin/host-shell-execute/index.ts | 2 +- .../src/plugins/plugin-host/plugin/index.ts | 1 + .../plugin/plugin-deploy-to-iis.ts | 283 +++++++++++ .../plugin/plugin-upload-to-oss.ts | 2 +- .../plugin/upload-to-host/index.ts | 2 +- .../aliyun/access/aliesa-access.ts | 0 .../aliyun/access/alioss-access.ts | 0 .../aliyun/access/aliyun-access.ts | 0 .../plugin-lib}/aliyun/access/index.ts | 0 .../src/plugins/plugin-lib}/aliyun/index.ts | 3 +- .../plugin-lib}/aliyun/lib/base-client.ts | 0 .../plugins/plugin-lib}/aliyun/lib/index.ts | 0 .../plugin-lib}/aliyun/lib/oss-client.ts | 0 .../plugin-lib}/aliyun/lib/ssl-client.ts | 0 .../aliyun}/utils/index.ts | 2 +- .../src/plugins/plugin-lib}/ftp/access.ts | 0 .../src/plugins/plugin-lib}/ftp/client.ts | 2 +- .../src/plugins/plugin-lib}/ftp/index.ts | 0 .../src/plugins/plugin-lib/index.ts | 7 + .../src/plugins/plugin-lib}/oss/api.ts | 0 .../src/plugins/plugin-lib}/oss/factory.ts | 2 +- .../plugins/plugin-lib}/oss/impls/alioss.ts | 0 .../src/plugins/plugin-lib}/oss/impls/ftp.ts | 0 .../plugins/plugin-lib}/oss/impls/qiniuoss.ts | 0 .../src/plugins/plugin-lib}/oss/impls/s3.ts | 0 .../src/plugins/plugin-lib}/oss/impls/sftp.ts | 0 .../src/plugins/plugin-lib}/oss/impls/ssh.ts | 0 .../plugin-lib}/oss/impls/tencentcos.ts | 0 .../src/plugins/plugin-lib}/oss/index.ts | 0 .../plugins/plugin-lib}/qiniu/access-oss.ts | 0 .../src/plugins/plugin-lib}/qiniu/access.ts | 0 .../src/plugins/plugin-lib}/qiniu/index.ts | 0 .../src/plugins/plugin-lib}/qiniu/lib/sdk.ts | 0 .../src/plugins/plugin-lib}/s3/access.ts | 0 .../src/plugins/plugin-lib}/s3/index.ts | 0 .../src/plugins/plugin-lib}/ssh/index.ts | 0 .../plugins/plugin-lib}/ssh/sftp-access.ts | 0 .../src/plugins/plugin-lib}/ssh/ssh-access.ts | 0 .../src/plugins/plugin-lib}/ssh/ssh.ts | 2 +- .../plugins/plugin-lib}/tencent/access-cos.ts | 0 .../src/plugins/plugin-lib}/tencent/access.ts | 0 .../src/plugins/plugin-lib}/tencent/index.ts | 0 .../plugin-lib}/tencent/lib/cos-client.ts | 0 .../plugins/plugin-lib}/tencent/lib/index.ts | 0 .../plugin-lib}/tencent/lib/ssl-client.ts | 0 .../src/plugins/plugin-plus/1panel/access.ts | 160 +++++++ .../src/plugins/plugin-plus/1panel/client.ts | 228 +++++++++ .../src/plugins/plugin-plus/1panel/index.ts | 3 + .../1panel/plugins/deploy-to-website.ts | 212 +++++++++ .../plugin-plus/1panel/plugins/index.ts | 1 + .../src/plugins/plugin-plus/1panel/util.ts | 65 +++ .../src/plugins/plugin-plus/alipay/access.ts | 48 ++ .../src/plugins/plugin-plus/alipay/index.ts | 1 + .../src/plugins/plugin-plus/baidu/access.ts | 61 +++ .../src/plugins/plugin-plus/baidu/client.ts | 191 ++++++++ .../src/plugins/plugin-plus/baidu/index.ts | 3 + .../plugin-plus/baidu/plugins/index.ts | 3 + .../baidu/plugins/plugin-deploy-to-blb.ts | 323 +++++++++++++ .../baidu/plugins/plugin-deploy-to-cdn.ts | 145 ++++++ .../baidu/plugins/plugin-upload-to-baidu.ts | 68 +++ .../src/plugins/plugin-plus/baishan/access.ts | 26 + .../src/plugins/plugin-plus/baishan/index.ts | 2 + .../plugin-plus/baishan/plugins/index.ts | 1 + .../baishan/plugins/plugin-update-cert.ts | 103 ++++ .../src/plugins/plugin-plus/baota/access.ts | 86 ++++ .../src/plugins/plugin-plus/baota/index.ts | 4 + .../plugins/plugin-plus/baota/lib/client.ts | 88 ++++ .../plugin-plus/baota/plugins/index.ts | 5 + .../plugins/plugin-delete-expiring-cert.ts | 69 +++ .../baota/plugins/plugin-deploy-to-aawaf.ts | 143 ++++++ .../baota/plugins/plugin-deploy-to-panel.ts | 68 +++ .../plugins/plugin-deploy-to-website-win.ts | 143 ++++++ .../baota/plugins/plugin-deploy-to-website.ts | 232 +++++++++ .../plugins/plugin-plus/baota/waf-access.ts | 128 +++++ .../src/plugins/plugin-plus/cdnfly/access.ts | 198 ++++++++ .../src/plugins/plugin-plus/cdnfly/index.ts | 2 + .../plugin-plus/cdnfly/plugins/index.ts | 1 + .../cdnfly/plugins/plugin-deploy-to-cdn.ts | 261 +++++++++++ .../plugin-plus}/ctyun/access/ctyun-access.ts | 0 .../src/plugins/plugin-plus/ctyun/index.ts | 3 + .../src/plugins/plugin-plus/ctyun/lib.ts | 249 ++++++++++ .../plugin-plus/ctyun/plugins/index.ts | 1 + .../ctyun/plugins/plugin-deploy-to-cdn.ts | 161 +++++++ .../src/plugins/plugin-plus/ftp/index.ts | 1 + .../plugins/plugin-plus/ftp/plugins/index.ts | 1 + .../ftp/plugins/plugin-upload-to-ftp.ts | 215 +++++++++ .../src/plugins/plugin-plus/index.ts | 21 + .../src/plugins/plugin-plus/k8s/access.ts | 34 ++ .../src/plugins/plugin-plus/k8s/index.ts | 2 + .../plugins/plugin-plus/k8s/plugins/index.ts | 3 + .../plugin-plus/k8s/plugins/plugin-apply.ts | 135 ++++++ .../plugin-plus/k8s/plugins/plugin-ingress.ts | 130 +++++ .../plugin-plus/k8s/plugins/plugin-secret.ts | 148 ++++++ .../src/plugins/plugin-plus/kuocai/access.ts | 35 ++ .../src/plugins/plugin-plus/kuocai/index.ts | 2 + .../plugin-plus/kuocai/plugins/index.ts | 1 + .../kuocai/plugins/plugin-deploy-to-cdn.ts | 168 +++++++ .../src/plugins/plugin-plus/lecdn/access.ts | 90 ++++ .../src/plugins/plugin-plus/lecdn/index.ts | 2 + .../plugin-plus/lecdn/plugins/index.ts | 2 + .../lecdn/plugins/plugin-update-cert-v2.ts | 175 +++++++ .../lecdn/plugins/plugin-update-cert.ts | 165 +++++++ .../src/plugins/plugin-plus/lucky/access.ts | 54 +++ .../src/plugins/plugin-plus/lucky/index.ts | 2 + .../plugin-plus/lucky/plugins/index.ts | 1 + .../lucky/plugins/plugin-upload.ts | 170 +++++++ .../src/plugins/plugin-plus/maoyun/access.ts | 54 +++ .../src/plugins/plugin-plus/maoyun/client.ts | 98 ++++ .../src/plugins/plugin-plus/maoyun/index.ts | 4 + .../plugin-plus/maoyun/plugins/index.ts | 1 + .../maoyun/plugins/plugin-deploy-to-cdn.ts | 150 ++++++ .../src/plugins/plugin-plus/plesk/access.ts | 204 ++++++++ .../src/plugins/plugin-plus/plesk/index.ts | 2 + .../plugin-plus/plesk/plugins/index.ts | 3 + .../plesk/plugins/plugin-deploy-cert.ts | 141 ++++++ .../plesk/plugins/plugin-refresh-cert.ts | 114 +++++ .../plugins/plugin-plus/safeline/access.ts | 45 ++ .../src/plugins/plugin-plus/safeline/index.ts | 2 + .../safeline/plugins/deploy-to-website.ts | 124 +++++ .../plugin-plus/safeline/plugins/index.ts | 1 + .../plugins/plugin-plus/synology/access.ts | 135 ++++++ .../plugins/plugin-plus/synology/client.ts | 190 ++++++++ .../src/plugins/plugin-plus/synology/index.ts | 3 + .../plugin-plus/synology/plugins/index.ts | 1 + .../plugins/plugin-deploy-to-panel.ts | 139 ++++++ .../plugins/plugin-plus/unicloud/access.ts | 34 ++ .../plugins/plugin-plus/unicloud/client.ts | 169 +++++++ .../src/plugins/plugin-plus/unicloud/index.ts | 3 + .../plugin-plus/unicloud/plugins/index.ts | 1 + .../plugins/plugin-deploy-to-space.ts | 111 +++++ .../src/plugins/plugin-plus/wxpay/access.ts | 65 +++ .../src/plugins/plugin-plus/wxpay/index.ts | 1 + .../src/plugins/plugin-plus/xinnet/client.ts | 314 +++++++++++++ .../src/plugins/plugin-plus/xinnet/index.ts | 1 + .../plugin-plus/xinnet/test-checkCode.mjs | 161 +++++++ .../plugins/plugin-plus/xinnet/test-login.mjs | 341 ++++++++++++++ .../plugins/plugin-plus/yidun/access-rcdn.ts | 35 ++ .../plugins/plugin-plus/yidun/access-sms.ts | 36 ++ .../src/plugins/plugin-plus/yidun/access.ts | 37 ++ .../src/plugins/plugin-plus/yidun/index.ts | 4 + .../plugin-plus/yidun/plugins/index.ts | 2 + .../yidun/plugins/plugin-deploy-to-cdn.ts | 147 ++++++ .../yidun/plugins/plugin-deploy-to-rcdn.ts | 168 +++++++ .../src/plugins/plugin-plus/yizhifu/access.ts | 70 +++ .../src/plugins/plugin-plus/yizhifu/index.ts | 1 + .../plugin-proxmox/plugins/plugin-upload.ts | 2 +- .../plugin/deploy-to-cdn/index.ts | 5 +- .../src/plugins/plugin-qiniu/plugin/index.ts | 1 + .../plugin/plugin-deploy-to-oss.ts | 84 ++++ .../plugin-qiniu/plugin/upload-cert/index.ts | 3 +- .../plugin-qnap/plugins/plugin-qnap.ts | 4 +- .../dns-provider/tencent-dns-provider.ts | 2 +- .../dns-provider/teo-dns-provider.ts | 2 +- .../plugin/delete-expiring-cert/index.ts | 4 +- .../plugin/deploy-to-all/index.ts | 2 +- .../plugin/deploy-to-cdn-v2/index.ts | 2 +- .../plugin/deploy-to-cdn/index.ts | 2 +- .../plugin/deploy-to-clb/index.ts | 2 +- .../plugin/deploy-to-cos/index.ts | 3 +- .../plugin/deploy-to-eo/index.ts | 5 +- .../plugin/deploy-to-live/index.ts | 4 +- .../plugin/refresh-cert/index.ts | 3 +- .../plugin/start-instances/index.ts | 3 +- .../plugin/upload-to-tencent/index.ts | 3 +- .../src/plugins/plugin-xinnet/access.ts | 2 +- .../src/plugins/plugin-xinnet/dns-provider.ts | 2 +- pnpm-lock.yaml | 228 +++------ 312 files changed, 14321 insertions(+), 597 deletions(-) rename packages/plugins/{plugin-cert/src/plugin/cert-plugin => plugin-lib/src/cert}/cert-reader.ts (96%) create mode 100644 packages/plugins/plugin-lib/src/cert/consts.ts rename packages/plugins/{plugin-cert/src/plugin/cert-plugin => plugin-lib/src/cert}/convert.ts (96%) rename packages/plugins/{plugin-cert/src => plugin-lib/src/cert}/dns-provider/api.ts (100%) rename packages/plugins/{plugin-cert/src => plugin-lib/src/cert}/dns-provider/base.ts (100%) rename packages/plugins/{plugin-cert/src => plugin-lib/src/cert}/dns-provider/decorator.ts (88%) rename packages/plugins/{plugin-cert/src => plugin-lib/src/cert}/dns-provider/domain-parser.ts (100%) rename packages/plugins/{plugin-cert/src => plugin-lib/src/cert}/dns-provider/index.ts (100%) rename packages/plugins/{plugin-cert/src => plugin-lib/src/cert}/dns-provider/registry.ts (100%) create mode 100644 packages/plugins/plugin-lib/src/cert/index.ts delete mode 100644 packages/plugins/plugin-lib/src/ctyun/index.ts create mode 100644 packages/plugins/plugin-lib/src/lib/check.ts delete mode 100644 packages/plugins/plugin-lib/src/tencent/lib/cos-client.js create mode 100644 packages/ui/certd-server/.env.production create mode 100644 packages/ui/certd-server/metadata/access_1panel.yaml create mode 100644 packages/ui/certd-server/metadata/access_aliesa.yaml create mode 100644 packages/ui/certd-server/metadata/access_alioss.yaml create mode 100644 packages/ui/certd-server/metadata/access_alipay.yaml create mode 100644 packages/ui/certd-server/metadata/access_aliyun.yaml create mode 100644 packages/ui/certd-server/metadata/access_baidu.yaml create mode 100644 packages/ui/certd-server/metadata/access_baishan.yaml create mode 100644 packages/ui/certd-server/metadata/access_baota.yaml create mode 100644 packages/ui/certd-server/metadata/access_baotawaf.yaml create mode 100644 packages/ui/certd-server/metadata/access_cdnfly.yaml create mode 100644 packages/ui/certd-server/metadata/access_ctyun.yaml create mode 100644 packages/ui/certd-server/metadata/access_eab.yaml create mode 100644 packages/ui/certd-server/metadata/access_ftp.yaml create mode 100644 packages/ui/certd-server/metadata/access_google.yaml create mode 100644 packages/ui/certd-server/metadata/access_k8s.yaml create mode 100644 packages/ui/certd-server/metadata/access_kuocaicdn.yaml create mode 100644 packages/ui/certd-server/metadata/access_lecdn.yaml create mode 100644 packages/ui/certd-server/metadata/access_lucky.yaml create mode 100644 packages/ui/certd-server/metadata/access_maoyun.yaml create mode 100644 packages/ui/certd-server/metadata/access_plesk.yaml create mode 100644 packages/ui/certd-server/metadata/access_qiniu.yaml create mode 100644 packages/ui/certd-server/metadata/access_qiniuoss.yaml create mode 100644 packages/ui/certd-server/metadata/access_s3.yaml create mode 100644 packages/ui/certd-server/metadata/access_safeline.yaml create mode 100644 packages/ui/certd-server/metadata/access_sftp.yaml create mode 100644 packages/ui/certd-server/metadata/access_ssh.yaml create mode 100644 packages/ui/certd-server/metadata/access_synology.yaml create mode 100644 packages/ui/certd-server/metadata/access_tencent.yaml create mode 100644 packages/ui/certd-server/metadata/access_tencentcos.yaml create mode 100644 packages/ui/certd-server/metadata/access_unicloud.yaml create mode 100644 packages/ui/certd-server/metadata/access_wxpay.yaml create mode 100644 packages/ui/certd-server/metadata/access_yfysms.yaml create mode 100644 packages/ui/certd-server/metadata/access_yidun.yaml create mode 100644 packages/ui/certd-server/metadata/access_yidunrcdn.yaml create mode 100644 packages/ui/certd-server/metadata/access_yizhifu.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_1PanelDeployToWebsitePlugin.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_AliyunDeployCertToAll.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaiduDeployToBLB.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaiduDeployToCDN.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaiduUploadCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaishanUpdateCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaotaDeleteExpiringCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaotaDeployPanelCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaotaDeployWAF.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaotaDeployWebSiteCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_BaotaDeployWebSiteWin.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_CdnflyDeployToCDN.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_CertApply.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_CertApplyGetFormAliyun.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_CertApplyLego.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_CertApplyUpload.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_CtyunDeployToCDN.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_DeployCertToAliyunAck.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_HostDeployToIIS.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_K8sApply.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_K8sDeployToIngress.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_K8sDeployToSecret.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_KuocaiDeployToRCDN.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_LeCDNUpdateCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_LeCDNUpdateCertV2.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_LuckyUpdateCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_MaoyunDeployToCdn.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_PleskDeploySiteCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_PleskRefreshCert.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_QiniuDeployCertToOSS.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_SafelineDeployToWebsitePlugin.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_SynologyDeployToPanel.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_UniCloudDeployToSpace.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_UploadCertToFTP.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_YidunDeployToCDN.yaml create mode 100644 packages/ui/certd-server/metadata/deploy_YidunDeployToRCDN.yaml create mode 100644 packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-all/index.ts rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/access/eab-access.ts (100%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/access/google-access.ts (100%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/access/index.ts (100%) create mode 100644 packages/ui/certd-server/src/plugins/plugin-cert/index.ts rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/libs/google.ts (100%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/cert-plugin/acme.ts (98%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/cert-plugin/base-convert.ts (97%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/cert-plugin/base.ts (99%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/cert-plugin/custom/index.ts (99%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/cert-plugin/getter/aliyun.ts (96%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/cert-plugin/index.ts (98%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/cert-plugin/lego/dns.ts (100%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/cert-plugin/lego/index.ts (99%) rename packages/{plugins/plugin-cert/src => ui/certd-server/src/plugins/plugin-cert}/plugin/index.ts (59%) create mode 100644 packages/ui/certd-server/src/plugins/plugin-host/plugin/plugin-deploy-to-iis.ts rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/access/aliesa-access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/access/alioss-access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/access/aliyun-access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/access/index.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/index.ts (67%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/lib/base-client.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/lib/index.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/lib/oss-client.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/aliyun/lib/ssl-client.ts (100%) rename packages/ui/certd-server/src/plugins/{plugin-aliyun => plugin-lib/aliyun}/utils/index.ts (94%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/ftp/access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/ftp/client.ts (97%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/ftp/index.ts (100%) create mode 100644 packages/ui/certd-server/src/plugins/plugin-lib/index.ts rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/api.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/factory.ts (96%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/impls/alioss.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/impls/ftp.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/impls/qiniuoss.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/impls/s3.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/impls/sftp.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/impls/ssh.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/impls/tencentcos.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/oss/index.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/qiniu/access-oss.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/qiniu/access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/qiniu/index.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/qiniu/lib/sdk.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/s3/access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/s3/index.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/ssh/index.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/ssh/sftp-access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/ssh/ssh-access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/ssh/ssh.ts (99%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/tencent/access-cos.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/tencent/access.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/tencent/index.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/tencent/lib/cos-client.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/tencent/lib/index.ts (100%) rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-lib}/tencent/lib/ssl-client.ts (100%) create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/1panel/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/1panel/client.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/1panel/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/1panel/plugins/deploy-to-website.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/1panel/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/1panel/util.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/alipay/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/alipay/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baidu/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baidu/client.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baidu/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-deploy-to-blb.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-deploy-to-cdn.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-upload-to-baidu.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baishan/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baishan/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baishan/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baishan/plugins/plugin-update-cert.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/lib/client.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-delete-expiring-cert.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-aawaf.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-panel.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-website-win.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-website.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/baota/waf-access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/plugins/plugin-deploy-to-cdn.ts rename packages/{plugins/plugin-lib/src => ui/certd-server/src/plugins/plugin-plus}/ctyun/access/ctyun-access.ts (100%) create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/ctyun/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/ctyun/lib.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/ctyun/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/ctyun/plugins/plugin-deploy-to-cdn.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/ftp/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/ftp/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/ftp/plugins/plugin-upload-to-ftp.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/k8s/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/k8s/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-apply.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-ingress.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-secret.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/kuocai/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/kuocai/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/kuocai/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/kuocai/plugins/plugin-deploy-to-cdn.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lecdn/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lecdn/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/plugin-update-cert-v2.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/plugin-update-cert.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lucky/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lucky/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lucky/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/lucky/plugins/plugin-upload.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/maoyun/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/maoyun/client.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/maoyun/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/maoyun/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/maoyun/plugins/plugin-deploy-to-cdn.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/plesk/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/plesk/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/plugin-deploy-cert.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/plugin-refresh-cert.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/safeline/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/safeline/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/safeline/plugins/deploy-to-website.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/safeline/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/synology/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/synology/client.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/synology/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/synology/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/synology/plugins/plugin-deploy-to-panel.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/unicloud/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/unicloud/client.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/unicloud/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/unicloud/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/unicloud/plugins/plugin-deploy-to-space.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/wxpay/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/wxpay/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/xinnet/client.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/xinnet/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/xinnet/test-checkCode.mjs create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/xinnet/test-login.mjs create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yidun/access-rcdn.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yidun/access-sms.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yidun/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yidun/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/plugin-deploy-to-cdn.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/plugin-deploy-to-rcdn.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yizhifu/access.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-plus/yizhifu/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/plugin-deploy-to-oss.ts diff --git a/docs/guide/plugins/access.md b/docs/guide/plugins/access.md index 42cb93da5..be378aa5f 100644 --- a/docs/guide/plugins/access.md +++ b/docs/guide/plugins/access.md @@ -2,71 +2,71 @@ | 序号 | 名称 | 说明 | |-----|-----|-----| -| 1.| **主机登录授权** | | -| 2.| **阿里云授权** | | -| 3.| **阿里云ESA授权** | | -| 4.| **腾讯云** | | +| 1.| **阿里云授权** | | +| 2.| **阿里云ESA授权** | | +| 3.| **腾讯云** | | +| 4.| **主机登录授权** | | | 5.| **华为云授权** | | | 6.| **火山引擎** | | | 7.| **京东云** | | | 8.| **七牛云授权** | | -| 9.| **天翼云授权** | | -| 10.| **baota授权** | | -| 11.| **百度云授权** | | -| 12.| **EAB授权** | ZeroSSL证书申请需要EAB授权 | -| 13.| **google cloud** | 谷歌云授权 | +| 9.| **baota授权** | | +| 10.| **百度云授权** | | +| 11.| **天翼云授权** | | +| 12.| **阿里云OSS授权** | 包含地域和Bucket | +| 13.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 | | 14.| **SFTP授权** | | -| 15.| **阿里云OSS授权** | 包含地域和Bucket | -| 16.| **FTP授权** | | -| 17.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 | +| 15.| **授权插件示例** | | +| 16.| **西部数码授权** | | +| 17.| **多吉云** | | | 18.| **七牛OSS授权** | | -| 19.| **s3/minio授权** | S3/minio oss授权 | -| 20.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 | -| 21.| **易盾DCDN授权** | https://user.yiduncdn.com | -| 22.| **易盾rcdn授权** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) | -| 23.| **易发云短信** | sms.yfyidc.cn/ | -| 24.| **cdnfly授权** | | -| 25.| **群晖登录授权** | | -| 26.| **k8s授权** | | -| 27.| **1panel授权** | 账号和密码 | -| 28.| **LeCDN授权** | | -| 29.| **白山云授权** | | -| 30.| **plesk授权** | | -| 31.| **易支付** | | -| 32.| **支付宝** | | -| 33.| **微信支付** | | -| 34.| **长亭雷池授权** | | -| 35.| **lucky** | | -| 36.| **括彩云cdn授权** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) | -| 37.| **uniCloud** | unicloud授权 | -| 38.| **猫云授权** | | -| 39.| **授权插件示例** | | -| 40.| **西部数码授权** | | -| 41.| **多吉云** | | -| 42.| **我爱云授权** | 我爱云CDN | -| 43.| **CacheFly** | CacheFly | -| 44.| **Gcore** | Gcore | -| 45.| **亚马逊云aws授权** | | -| 46.| **亚马逊云科技(国区)授权** | | -| 47.| **dns.la授权** | | -| 48.| **又拍云** | | -| 49.| **51dns授权** | | -| 50.| **FlexCDN授权** | | -| 51.| **farcdn授权** | | -| 52.| **cloudflare授权** | | -| 53.| **Github授权** | | -| 54.| **namesilo授权** | | -| 55.| **proxmox** | | -| 56.| **网宿授权** | | -| 57.| **金山云授权** | | -| 58.| **APISIX授权** | | -| 59.| **Dokploy授权** | | -| 60.| **godaddy授权** | | -| 61.| **新网授权** | | -| 62.| **新网授权(代理方式)** | | -| 63.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 | -| 64.| **中国移动CND授权** | | -| 65.| **UCloud授权** | 优刻得授权 | +| 19.| **我爱云授权** | 我爱云CDN | +| 20.| **CacheFly** | CacheFly | +| 21.| **Gcore** | Gcore | +| 22.| **亚马逊云aws授权** | | +| 23.| **亚马逊云科技(国区)授权** | | +| 24.| **dns.la授权** | | +| 25.| **又拍云** | | +| 26.| **51dns授权** | | +| 27.| **FlexCDN授权** | | +| 28.| **farcdn授权** | | +| 29.| **cloudflare授权** | | +| 30.| **Github授权** | | +| 31.| **namesilo授权** | | +| 32.| **proxmox** | | +| 33.| **网宿授权** | | +| 34.| **金山云授权** | | +| 35.| **APISIX授权** | | +| 36.| **Dokploy授权** | | +| 37.| **godaddy授权** | | +| 38.| **新网授权** | | +| 39.| **新网授权(代理方式)** | | +| 40.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 | +| 41.| **中国移动CND授权** | | +| 42.| **UCloud授权** | 优刻得授权 | +| 43.| **FTP授权** | | +| 44.| **s3/minio授权** | S3/minio oss授权 | +| 45.| **EAB授权** | ZeroSSL证书申请需要EAB授权 | +| 46.| **google cloud** | 谷歌云授权 | +| 47.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 | +| 48.| **易盾DCDN授权** | https://user.yiduncdn.com | +| 49.| **易盾rcdn授权** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) | +| 50.| **易发云短信** | sms.yfyidc.cn/ | +| 51.| **cdnfly授权** | | +| 52.| **群晖登录授权** | | +| 53.| **k8s授权** | | +| 54.| **1panel授权** | 账号和密码 | +| 55.| **LeCDN授权** | | +| 56.| **白山云授权** | | +| 57.| **plesk授权** | | +| 58.| **易支付** | | +| 59.| **支付宝** | | +| 60.| **微信支付** | | +| 61.| **长亭雷池授权** | | +| 62.| **lucky** | | +| 63.| **括彩云cdn授权** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) | +| 64.| **uniCloud** | unicloud授权 | +| 65.| **猫云授权** | | | 66.| **雨云授权** | https://app.rainyun.com/ | | 67.| **GoEdge授权** | | diff --git a/docs/guide/plugins/deploy.md b/docs/guide/plugins/deploy.md index 50640e6a5..bf0477fe0 100644 --- a/docs/guide/plugins/deploy.md +++ b/docs/guide/plugins/deploy.md @@ -1,5 +1,5 @@ # 任务插件 -共 `110` 款任务插件 +共 `109` 款任务插件 ## 1. 证书申请 | 序号 | 名称 | 说明 | @@ -12,80 +12,79 @@ | 序号 | 名称 | 说明 | |-----|-----|-----| -| 1.| **FTP-上传证书到FTP** | 将证书上传到FTP服务器 | -| 2.| **IIS-部署到IIS站点** | | -| 3.| **主机-执行远程主机脚本命令** | 可以执行重启nginx等操作让证书生效 | -| 4.| **主机-部署证书到SSH主机** | SFTP上传证书到主机,然后SSH执行部署脚本命令 | -| 5.| **主机-复制到本机** | 【仅管理员使用】实际上是复制证书到docker容器内的某个路径,需要做目录映射到宿主机 | -| 6.| **上传证书到对象存储OSS** | 支持阿里云OSS、腾讯云COS、七牛云KODO、S3、MinIO、FTP、SFTP | +| 1.| **主机-执行远程主机脚本命令** | 可以执行重启nginx等操作让证书生效 | +| 2.| **主机-部署证书到SSH主机** | SFTP上传证书到主机,然后SSH执行部署脚本命令 | +| 3.| **主机-复制到本机** | 【仅管理员使用】实际上是复制证书到docker容器内的某个路径,需要做目录映射到宿主机 | +| 4.| **上传证书到对象存储OSS** | 支持阿里云OSS、腾讯云COS、七牛云KODO、S3、MinIO、FTP、SFTP | +| 5.| **IIS-部署到IIS站点** | | +| 6.| **FTP-上传证书到FTP** | 将证书上传到FTP服务器 | ## 3. CDN | 序号 | 名称 | 说明 | |-----|-----|-----| -| 1.| **易盾-部署到易盾DCDN** | 主要是防御,http://user.yiduncdn.com/ | -| 2.| **易盾-部署到易盾RCDN** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) | -| 3.| **cdnfly-部署证书到cdnfly** | cdnfly | -| 4.| **LeCDN-更新证书** | | -| 5.| **LeCDN-更新证书V2** | 支持新版本LeCDN | -| 6.| **白山云-更新证书** | | -| 7.| **天翼云-部署证书到CDN** | 部署证书到天翼云CDN和全站加速 | -| 8.| **括彩云-部署到括彩云CDN** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) | -| 9.| **西数-部署到虚拟主机** | 西部数码部署证书到虚拟主机 | -| 10.| **多吉云-部署到多吉云CDN** | | -| 11.| **我爱云-部署证书到我爱云CDN** | 部署证书到我爱云CDN | -| 12.| **CacheFly-部署证书到CacheFly** | 部署证书到 CacheFly | -| 13.| **Gcore-部署证书到Gcore** | 仅上传 并不会部署到cdn | -| 14.| **Gcore-刷新Gcore证书** | 刷新现有的证书 | -| 15.| **又拍云-部署证书到CDN/USS** | 支持又拍云CDN,又拍云云存储USS | -| 16.| **FlexCDN-更新证书** | | -| 17.| **farcdn-更新证书** | www.farcdn.net | -| 18.| **雨云-更新证书** | app.rainyun.com | -| 19.| **网宿-更新证书** | 网宿证书自动更新 | -| 20.| **金山云-更新CDN证书** | 金山云自动更新CDN证书 | -| 21.| **APISIX-更新证书** | 自动更新APISIX证书 | -| 22.| **中国移动-部署证书到CDN** | 中国移动自动部署证书到CDN | -| 23.| **GoEdge-更新证书** | GoEdge | +| 1.| **西数-部署到虚拟主机** | 西部数码部署证书到虚拟主机 | +| 2.| **多吉云-部署到多吉云CDN** | | +| 3.| **我爱云-部署证书到我爱云CDN** | 部署证书到我爱云CDN | +| 4.| **CacheFly-部署证书到CacheFly** | 部署证书到 CacheFly | +| 5.| **Gcore-部署证书到Gcore** | 仅上传 并不会部署到cdn | +| 6.| **Gcore-刷新Gcore证书** | 刷新现有的证书 | +| 7.| **又拍云-部署证书到CDN/USS** | 支持又拍云CDN,又拍云云存储USS | +| 8.| **FlexCDN-更新证书** | | +| 9.| **farcdn-更新证书** | www.farcdn.net | +| 10.| **雨云-更新证书** | app.rainyun.com | +| 11.| **网宿-更新证书** | 网宿证书自动更新 | +| 12.| **金山云-更新CDN证书** | 金山云自动更新CDN证书 | +| 13.| **APISIX-更新证书** | 自动更新APISIX证书 | +| 14.| **中国移动-部署证书到CDN** | 中国移动自动部署证书到CDN | +| 15.| **GoEdge-更新证书** | GoEdge | +| 16.| **易盾-部署到易盾DCDN** | 主要是防御,http://user.yiduncdn.com/ | +| 17.| **易盾-部署到易盾RCDN** | 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) | +| 18.| **cdnfly-部署证书到cdnfly** | cdnfly | +| 19.| **LeCDN-更新证书** | | +| 20.| **LeCDN-更新证书V2** | 支持新版本LeCDN | +| 21.| **白山云-更新证书** | | +| 22.| **天翼云-部署证书到CDN** | 部署证书到天翼云CDN和全站加速 | +| 23.| **括彩云-部署到括彩云CDN** | 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) | ## 4. 面板 | 序号 | 名称 | 说明 | |-----|-----|-----| -| 1.| **宝塔-面板证书部署** | 部署宝塔面板本身的ssl证书 | -| 2.| **宝塔-网站证书部署** | 部署宝塔管理的站点的ssl证书,目前支持宝塔网站站点、docker站点等。本插件也支持aaPanel。 | -| 3.| **宝塔-WAF证书部署** | 部署宝塔云WAF/aaWAF | -| 4.| **宝塔win-网站证书部署** | 部署到Windows版宝塔管理的站点的ssl证书 | -| 5.| **宝塔-删除过期证书** | 删除证书夹中过期证书 | -| 6.| **群晖-部署证书到群晖面板** | Synology,支持6.x以上版本 | -| 7.| **K8S-部署证书到Secret** | 部署证书到k8s的secret | -| 8.| **K8S-Ingress 证书部署** | 部署证书到k8s的Ingress | -| 9.| **K8S-Apply自定义yaml** | apply自定义yaml到k8s | -| 10.| **1Panel-部署证书到1Panel** | 更新1Panel的证书 | -| 11.| **Plesk-部署Plesk网站证书** | | -| 12.| **雷池-更新证书** | 更新长亭雷池WAF的证书 | -| 13.| **lucky-更新Lucky证书** | | -| 14.| **uniCloud-部署到服务空间** | 部署到服务空间 | -| 15.| **威联通-部署证书到威联通** | 部署证书到qnap | -| 16.| **飞牛NAS-部署证书** | | -| 17.| **Proxmox-上传证书到Proxmox** | | -| 18.| **Dokploy-部署server证书** | 自动更新Dokploy server证书 | +| 1.| **威联通-部署证书到威联通** | 部署证书到qnap | +| 2.| **飞牛NAS-部署证书** | | +| 3.| **Proxmox-上传证书到Proxmox** | | +| 4.| **Dokploy-部署server证书** | 自动更新Dokploy server证书 | +| 5.| **宝塔-面板证书部署** | 部署宝塔面板本身的ssl证书 | +| 6.| **宝塔-网站证书部署** | 部署宝塔管理的站点的ssl证书,目前支持宝塔网站站点、docker站点等。本插件也支持aaPanel。 | +| 7.| **宝塔-WAF证书部署** | 部署宝塔云WAF/aaWAF | +| 8.| **宝塔win-网站证书部署** | 部署到Windows版宝塔管理的站点的ssl证书 | +| 9.| **宝塔-删除过期证书** | 删除证书夹中过期证书 | +| 10.| **群晖-部署证书到群晖面板** | Synology,支持6.x以上版本 | +| 11.| **K8S-部署证书到Secret** | 部署证书到k8s的secret | +| 12.| **K8S-Ingress 证书部署** | 部署证书到k8s的Ingress | +| 13.| **K8S-Apply自定义yaml** | apply自定义yaml到k8s | +| 14.| **1Panel-部署证书到1Panel** | 更新1Panel的证书 | +| 15.| **Plesk-部署Plesk网站证书** | | +| 16.| **雷池-更新证书** | 更新长亭雷池WAF的证书 | +| 17.| **lucky-更新Lucky证书** | | +| 18.| **uniCloud-部署到服务空间** | 部署到服务空间 | ## 5. 阿里云 | 序号 | 名称 | 说明 | |-----|-----|-----| -| 1.| **阿里云-部署到Ack** | 部署到阿里云Ack集群Ingress等通过Secret管理证书的应用 | -| 2.| **阿里云-部署至任意云资源** | 【不建议使用】需要消耗阿里云自动部署次数,支持SLB、LIVE、webHosting、VOD、CR、DCDN、DDoS、CDN、ALB、APIGateway、FC、GA、MSE、NLB、OSS、SAE、WAF等云产品 | -| 3.| **阿里云-部署证书至CDN** | 自动部署域名证书至阿里云CDN | -| 4.| **阿里云-部署证书至DCDN** | 依赖证书申请前置任务,自动部署域名证书至阿里云DCDN | -| 5.| **阿里云-部署证书至OSS** | 部署域名证书至阿里云OSS自定义域名,不是上传到阿里云oss | -| 6.| **阿里云-上传证书到CAS** | 上传证书到阿里云证书管理服务(CAS),如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出 | -| 7.| **阿里云-部署至阿里云WAF** | 部署证书到阿里云WAF | -| 8.| **阿里云-部署至ALB(应用负载均衡)** | ALB,更新监听器的默认证书 | -| 9.| **阿里云-部署至NLB(网络负载均衡)** | NLB,网络负载均衡,更新监听器的默认证书 | -| 10.| **阿里云-部署至CLB(传统负载均衡)** | 部署证书到阿里云CLB(传统负载均衡) | -| 11.| **阿里云-部署至阿里云FC(3.0)** | 部署证书到阿里云函数计算(FC3.0) | -| 12.| **阿里云-部署至ESA** | 部署证书到阿里云ESA(边缘安全加速),自动删除过期证书 | -| 13.| **阿里云-部署至VOD** | 部署证书到阿里云视频点播(vod) | -| 14.| **阿里云-部署证书至API网关** | 自动部署域名证书至阿里云API网关(APIGateway) | -| 15.| **阿里云-部署至云原生API网关/AI网关** | 自动部署域名证书至云原生API网关、AI网关 | +| 1.| **阿里云-部署证书至CDN** | 自动部署域名证书至阿里云CDN | +| 2.| **阿里云-部署证书至DCDN** | 依赖证书申请前置任务,自动部署域名证书至阿里云DCDN | +| 3.| **阿里云-部署证书至OSS** | 部署域名证书至阿里云OSS自定义域名,不是上传到阿里云oss | +| 4.| **阿里云-上传证书到CAS** | 上传证书到阿里云证书管理服务(CAS),如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出 | +| 5.| **阿里云-部署至阿里云WAF** | 部署证书到阿里云WAF | +| 6.| **阿里云-部署至ALB(应用负载均衡)** | ALB,更新监听器的默认证书 | +| 7.| **阿里云-部署至NLB(网络负载均衡)** | NLB,网络负载均衡,更新监听器的默认证书 | +| 8.| **阿里云-部署至CLB(传统负载均衡)** | 部署证书到阿里云CLB(传统负载均衡) | +| 9.| **阿里云-部署至阿里云FC(3.0)** | 部署证书到阿里云函数计算(FC3.0) | +| 10.| **阿里云-部署至ESA** | 部署证书到阿里云ESA(边缘安全加速),自动删除过期证书 | +| 11.| **阿里云-部署至VOD** | 部署证书到阿里云视频点播(vod) | +| 12.| **阿里云-部署证书至API网关** | 自动部署域名证书至阿里云API网关(APIGateway) | +| 13.| **阿里云-部署至云原生API网关/AI网关** | 自动部署域名证书至云原生API网关、AI网关 | +| 14.| **阿里云-部署到Ack** | 部署到阿里云Ack集群Ingress等通过Secret管理证书的应用 | ## 6. 华为云 | 序号 | 名称 | 说明 | @@ -148,9 +147,9 @@ | 序号 | 名称 | 说明 | |-----|-----|-----| -| 1.| **七牛云-部署证书至OSS** | 自动部署域名证书至七牛云KODO,注意是自定义源站域名,不是CDN域名 | -| 2.| **七牛云-部署证书至CDN/DCDN** | 自动部署域名证书至七牛云CDN、DCDN | -| 3.| **七牛云-上传证书到七牛云** | 上传到七牛云 | +| 1.| **七牛云-部署证书至CDN/DCDN** | 自动部署域名证书至七牛云CDN、DCDN | +| 2.| **七牛云-上传证书到七牛云** | 上传到七牛云 | +| 3.| **七牛云-部署证书至OSS** | 自动部署域名证书至七牛云KODO,注意是自定义源站域名,不是CDN域名 | ## 13. 亚马逊云 | 序号 | 名称 | 说明 | diff --git a/packages/core/pipeline/src/access/decorator.ts b/packages/core/pipeline/src/access/decorator.ts index bb2f0d6bb..0fb73da72 100644 --- a/packages/core/pipeline/src/access/decorator.ts +++ b/packages/core/pipeline/src/access/decorator.ts @@ -11,6 +11,9 @@ export const ACCESS_INPUT_KEY = "pipeline:access:input"; export function IsAccess(define: AccessDefine): ClassDecorator { return (target: any) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target); const inputs: any = {}; @@ -35,6 +38,9 @@ export function IsAccess(define: AccessDefine): ClassDecorator { export function AccessInput(input?: AccessInputDefine): PropertyDecorator { return (target, propertyKey) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target, propertyKey); // const _type = Reflect.getMetadata("design:type", target, propertyKey); Reflect.defineMetadata(ACCESS_INPUT_KEY, input, target, propertyKey); diff --git a/packages/core/pipeline/src/notification/decorator.ts b/packages/core/pipeline/src/notification/decorator.ts index 53ff8cbc5..7c8e08a45 100644 --- a/packages/core/pipeline/src/notification/decorator.ts +++ b/packages/core/pipeline/src/notification/decorator.ts @@ -11,6 +11,9 @@ export const NOTIFICATION_INPUT_KEY = "pipeline:notification:input"; export function IsNotification(define: NotificationDefine): ClassDecorator { return (target: any) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target); const inputs: any = {}; @@ -35,6 +38,9 @@ export function IsNotification(define: NotificationDefine): ClassDecorator { export function NotificationInput(input?: NotificationInputDefine): PropertyDecorator { return (target, propertyKey) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target, propertyKey); // const _type = Reflect.getMetadata("design:type", target, propertyKey); Reflect.defineMetadata(NOTIFICATION_INPUT_KEY, input, target, propertyKey); diff --git a/packages/core/pipeline/src/plugin/decorator.ts b/packages/core/pipeline/src/plugin/decorator.ts index ad77ade83..b0db6a233 100644 --- a/packages/core/pipeline/src/plugin/decorator.ts +++ b/packages/core/pipeline/src/plugin/decorator.ts @@ -8,6 +8,9 @@ export const PLUGIN_CLASS_KEY = "pipeline:plugin"; export function IsTaskPlugin(define: PluginDefine): ClassDecorator { return (target: any) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target); const inputs: any = {}; @@ -69,6 +72,9 @@ export const PLUGIN_INPUT_KEY = "pipeline:plugin:input"; export function TaskInput(input?: TaskInputDefine): PropertyDecorator { return (target, propertyKey) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target, propertyKey); Reflect.defineMetadata(PLUGIN_INPUT_KEY, input, target, propertyKey); }; @@ -78,6 +84,9 @@ export function TaskInput(input?: TaskInputDefine): PropertyDecorator { export const PLUGIN_OUTPUT_KEY = "pipeline:plugin:output"; export function TaskOutput(output?: TaskOutputDefine): PropertyDecorator { return (target, propertyKey) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target, propertyKey); Reflect.defineMetadata(PLUGIN_OUTPUT_KEY, output, target, propertyKey); }; @@ -86,6 +95,9 @@ export function TaskOutput(output?: TaskOutputDefine): PropertyDecorator { export const PLUGIN_DOWNLOAD_KEY = "pipeline:plugin:download"; export function TaskDownload(output?: TaskOutputDefine): PropertyDecorator { return (target, propertyKey) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target, propertyKey); Reflect.defineMetadata(PLUGIN_DOWNLOAD_KEY, output, target, propertyKey); }; diff --git a/packages/core/pipeline/src/service/cname.ts b/packages/core/pipeline/src/service/cname.ts index ad0fbd32a..735222fca 100644 --- a/packages/core/pipeline/src/service/cname.ts +++ b/packages/core/pipeline/src/service/cname.ts @@ -1,4 +1,4 @@ -import { IAccess } from "../access"; +import { IAccess } from "../access/index.js"; export type CnameProvider = { id: any; diff --git a/packages/libs/lib-server/src/user/addon/api/decorator.ts b/packages/libs/lib-server/src/user/addon/api/decorator.ts index 6a96b49ac..e82becd77 100644 --- a/packages/libs/lib-server/src/user/addon/api/decorator.ts +++ b/packages/libs/lib-server/src/user/addon/api/decorator.ts @@ -11,6 +11,9 @@ export const ADDON_INPUT_KEY = "pipeline:addon:input"; export function IsAddon(define: AddonDefine): ClassDecorator { return (target: any) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target); const inputs: any = {}; @@ -36,6 +39,9 @@ export function IsAddon(define: AddonDefine): ClassDecorator { export function AddonInput(input?: AddonInputDefine): PropertyDecorator { return (target, propertyKey) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target, propertyKey); // const _type = Reflect.getMetadata("design:type", target, propertyKey); Reflect.defineMetadata(ADDON_INPUT_KEY, input, target, propertyKey); diff --git a/packages/plugins/plugin-cert/package.json b/packages/plugins/plugin-cert/package.json index 847d391da..587e0cb88 100644 --- a/packages/plugins/plugin-cert/package.json +++ b/packages/plugins/plugin-cert/package.json @@ -21,13 +21,8 @@ "@certd/basic": "^1.37.17", "@certd/pipeline": "^1.37.17", "@certd/plugin-lib": "^1.37.17", - "@google-cloud/publicca": "^1.3.0", - "dayjs": "^1.11.7", - "jszip": "^3.10.1", - "lodash-es": "^4.17.21", - "psl": "^1.9.0", "punycode.js": "^2.3.1", - "rimraf": "^5.0.5" + "psl": "^1.9.0" }, "devDependencies": { "@types/chai": "^4.3.3", diff --git a/packages/plugins/plugin-cert/src/index.ts b/packages/plugins/plugin-cert/src/index.ts index ef88c7587..4f136a13d 100644 --- a/packages/plugins/plugin-cert/src/index.ts +++ b/packages/plugins/plugin-cert/src/index.ts @@ -1,3 +1 @@ -export * from "./access/index.js"; -export * from "./plugin/index.js"; -export * from "./dns-provider/index.js"; +export * from "@certd/plugin-lib"; \ No newline at end of file diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json index 9aca8c609..49d8b434b 100644 --- a/packages/plugins/plugin-lib/package.json +++ b/packages/plugins/plugin-lib/package.json @@ -21,6 +21,7 @@ "@alicloud/openapi-util": "^0.3.2", "@alicloud/pop-core": "^1.7.10", "@alicloud/tea-util": "^1.4.10", + "psl": "^1.15.0", "@aws-sdk/client-s3": "^3.787.0", "@certd/basic": "^1.37.17", "@certd/pipeline": "^1.37.17", @@ -37,7 +38,10 @@ "socks-proxy-agent": "^8.0.4", "ssh2": "1.17.0", "strip-ansi": "^7.1.0", - "tencentcloud-sdk-nodejs": "^4.0.1005" + "tencentcloud-sdk-nodejs": "^4.0.1005", + "@certd/acme-client": "^1.37.17", + "@certd/plus-core": "^1.37.17", + "punycode.js": "^2.3.1" }, "devDependencies": { "@types/chai": "^4.3.3", diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts b/packages/plugins/plugin-lib/src/cert/cert-reader.ts similarity index 96% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts rename to packages/plugins/plugin-lib/src/cert/cert-reader.ts index 66b8f24e7..ba8c10262 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts +++ b/packages/plugins/plugin-lib/src/cert/cert-reader.ts @@ -1,4 +1,3 @@ -import { CertInfo } from "./acme.js"; import fs from "fs"; import os from "os"; import path from "path"; @@ -7,6 +6,19 @@ import { ILogger } from "@certd/basic"; import dayjs from "dayjs"; import { uniq } from "lodash-es"; +export type CertInfo = { + crt: string; //fullchain证书 + key: string; //私钥 + csr: string; //csr + oc?: string; //仅证书,非fullchain证书 + ic?: string; //中间证书 + pfx?: string; + der?: string; + jks?: string; + one?: string; + p7b?: string; +}; + export type CertReaderHandleContext = { reader: CertReader; tmpCrtPath: string; diff --git a/packages/plugins/plugin-lib/src/cert/consts.ts b/packages/plugins/plugin-lib/src/cert/consts.ts new file mode 100644 index 000000000..a9ded8ebb --- /dev/null +++ b/packages/plugins/plugin-lib/src/cert/consts.ts @@ -0,0 +1,2 @@ +export const CertApplyPluginNames = [":cert:"]; +export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success"; \ No newline at end of file diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts b/packages/plugins/plugin-lib/src/cert/convert.ts similarity index 96% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts rename to packages/plugins/plugin-lib/src/cert/convert.ts index dfb4cafae..65e5eff57 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts +++ b/packages/plugins/plugin-lib/src/cert/convert.ts @@ -1,13 +1,10 @@ import { ILogger, sp } from "@certd/basic"; -import type { CertInfo } from "../cert-plugin/acme.js"; -import { CertReader, CertReaderHandleContext } from "../cert-plugin/cert-reader.js"; +import type { CertInfo } from "./cert-reader.js"; +import { CertReader, CertReaderHandleContext } from "./cert-reader.js"; import path from "path"; import os from "os"; import fs from "fs"; -export { CertReader }; -export type { CertInfo }; - export class CertConverter { logger: ILogger; diff --git a/packages/plugins/plugin-cert/src/dns-provider/api.ts b/packages/plugins/plugin-lib/src/cert/dns-provider/api.ts similarity index 100% rename from packages/plugins/plugin-cert/src/dns-provider/api.ts rename to packages/plugins/plugin-lib/src/cert/dns-provider/api.ts diff --git a/packages/plugins/plugin-cert/src/dns-provider/base.ts b/packages/plugins/plugin-lib/src/cert/dns-provider/base.ts similarity index 100% rename from packages/plugins/plugin-cert/src/dns-provider/base.ts rename to packages/plugins/plugin-lib/src/cert/dns-provider/base.ts diff --git a/packages/plugins/plugin-cert/src/dns-provider/decorator.ts b/packages/plugins/plugin-lib/src/cert/dns-provider/decorator.ts similarity index 88% rename from packages/plugins/plugin-cert/src/dns-provider/decorator.ts rename to packages/plugins/plugin-lib/src/cert/dns-provider/decorator.ts index 418a1d4e3..4a9d99138 100644 --- a/packages/plugins/plugin-cert/src/dns-provider/decorator.ts +++ b/packages/plugins/plugin-lib/src/cert/dns-provider/decorator.ts @@ -8,6 +8,9 @@ export const DNS_PROVIDER_CLASS_KEY = "pipeline:dns-provider"; export function IsDnsProvider(define: DnsProviderDefine): ClassDecorator { return (target: any) => { + if (process.env.certd_plugin_loadmode === "metadata") { + return; + } target = Decorator.target(target); Reflect.defineMetadata(DNS_PROVIDER_CLASS_KEY, define, target); diff --git a/packages/plugins/plugin-cert/src/dns-provider/domain-parser.ts b/packages/plugins/plugin-lib/src/cert/dns-provider/domain-parser.ts similarity index 100% rename from packages/plugins/plugin-cert/src/dns-provider/domain-parser.ts rename to packages/plugins/plugin-lib/src/cert/dns-provider/domain-parser.ts diff --git a/packages/plugins/plugin-cert/src/dns-provider/index.ts b/packages/plugins/plugin-lib/src/cert/dns-provider/index.ts similarity index 100% rename from packages/plugins/plugin-cert/src/dns-provider/index.ts rename to packages/plugins/plugin-lib/src/cert/dns-provider/index.ts diff --git a/packages/plugins/plugin-cert/src/dns-provider/registry.ts b/packages/plugins/plugin-lib/src/cert/dns-provider/registry.ts similarity index 100% rename from packages/plugins/plugin-cert/src/dns-provider/registry.ts rename to packages/plugins/plugin-lib/src/cert/dns-provider/registry.ts diff --git a/packages/plugins/plugin-lib/src/cert/index.ts b/packages/plugins/plugin-lib/src/cert/index.ts new file mode 100644 index 000000000..0eed8630f --- /dev/null +++ b/packages/plugins/plugin-lib/src/cert/index.ts @@ -0,0 +1,4 @@ +export * from "./convert.js"; +export * from "./cert-reader.js"; +export * from "./consts.js"; +export * from "./dns-provider/index.js"; \ No newline at end of file diff --git a/packages/plugins/plugin-lib/src/ctyun/index.ts b/packages/plugins/plugin-lib/src/ctyun/index.ts deleted file mode 100644 index 750c81aef..000000000 --- a/packages/plugins/plugin-lib/src/ctyun/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./access/ctyun-access.js"; diff --git a/packages/plugins/plugin-lib/src/index.ts b/packages/plugins/plugin-lib/src/index.ts index f97e78bcd..ac0f1be1d 100644 --- a/packages/plugins/plugin-lib/src/index.ts +++ b/packages/plugins/plugin-lib/src/index.ts @@ -1,11 +1,4 @@ -export * from "./ssh/index.js"; -export * from "./aliyun/index.js"; export * from "./common/index.js"; -export * from "./ftp/index.js"; -export * from "./tencent/index.js"; -export * from "./qiniu/index.js"; -export * from "./ctyun/index.js"; -export * from "./oss/index.js"; -export * from "./s3/index.js"; export * from "./lib/index.js"; export * from "./service/index.js"; +export * from "./cert/index.js"; \ No newline at end of file diff --git a/packages/plugins/plugin-lib/src/lib/check.ts b/packages/plugins/plugin-lib/src/lib/check.ts new file mode 100644 index 000000000..2425976c1 --- /dev/null +++ b/packages/plugins/plugin-lib/src/lib/check.ts @@ -0,0 +1,17 @@ +import { AbstractTaskPlugin, TaskInstanceContext } from "@certd/pipeline"; +import { isPlus } from "@certd/plus-core"; + +export function mustPlus() { + if (!isPlus()) { + throw new Error("此插件仅供专业版中使用"); + } +} + +export abstract class AbstractPlusTaskPlugin extends AbstractTaskPlugin { + setCtx(ctx: TaskInstanceContext) { + super.setCtx(ctx); + mustPlus(); + } + + abstract execute(): Promise; +} diff --git a/packages/plugins/plugin-lib/src/lib/index.ts b/packages/plugins/plugin-lib/src/lib/index.ts index d66827933..56f24306a 100644 --- a/packages/plugins/plugin-lib/src/lib/index.ts +++ b/packages/plugins/plugin-lib/src/lib/index.ts @@ -1 +1,2 @@ export * from "./ocr-api.js"; +export * from "./check.js"; \ No newline at end of file diff --git a/packages/plugins/plugin-lib/src/tencent/lib/cos-client.js b/packages/plugins/plugin-lib/src/tencent/lib/cos-client.js deleted file mode 100644 index a6d6af0a0..000000000 --- a/packages/plugins/plugin-lib/src/tencent/lib/cos-client.js +++ /dev/null @@ -1,183 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TencentCosClient = void 0; -var basic_1 = require("@certd/basic"); -var fs_1 = require("fs"); -var TencentCosClient = /** @class */ (function () { - function TencentCosClient(opts) { - this.access = opts.access; - this.logger = opts.logger; - this.bucket = opts.bucket; - this.region = opts.region; - } - TencentCosClient.prototype.getCosClient = function () { - return __awaiter(this, void 0, void 0, function () { - var sdk, clientConfig; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, Promise.resolve().then(function () { return require("cos-nodejs-sdk-v5"); })]; - case 1: - sdk = _a.sent(); - clientConfig = { - SecretId: this.access.secretId, - SecretKey: this.access.secretKey, - }; - return [2 /*return*/, new sdk.default(clientConfig)]; - } - }); - }); - }; - TencentCosClient.prototype.uploadFile = function (key, file) { - return __awaiter(this, void 0, void 0, function () { - var cos; - var _this = this; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.getCosClient()]; - case 1: - cos = _a.sent(); - return [2 /*return*/, (0, basic_1.safePromise)(function (resolve, reject) { - var readableStream = file; - if (typeof file === "string") { - readableStream = fs_1.default.createReadStream(file); - } - cos.putObject({ - Bucket: _this.bucket /* 必须 */, - Region: _this.region /* 必须 */, - Key: key /* 必须 */, - Body: readableStream, // 上传文件对象 - onProgress: function (progressData) { - console.log(JSON.stringify(progressData)); - }, - }, function (err, data) { - if (err) { - reject(err); - return; - } - resolve(data); - }); - })]; - } - }); - }); - }; - TencentCosClient.prototype.removeFile = function (key) { - return __awaiter(this, void 0, void 0, function () { - var cos; - var _this = this; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.getCosClient()]; - case 1: - cos = _a.sent(); - return [2 /*return*/, (0, basic_1.safePromise)(function (resolve, reject) { - cos.deleteObject({ - Bucket: _this.bucket, - Region: _this.region, - Key: key, - }, function (err, data) { - if (err) { - reject(err); - return; - } - resolve(data); - }); - })]; - } - }); - }); - }; - TencentCosClient.prototype.downloadFile = function (key, savePath) { - return __awaiter(this, void 0, void 0, function () { - var cos, writeStream; - var _this = this; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.getCosClient()]; - case 1: - cos = _a.sent(); - writeStream = fs_1.default.createWriteStream(savePath); - return [2 /*return*/, (0, basic_1.safePromise)(function (resolve, reject) { - cos.getObject({ - Bucket: _this.bucket, - Region: _this.region, - Key: key, - Output: writeStream, - }, function (err, data) { - if (err) { - reject(err); - return; - } - resolve(data); - }); - })]; - } - }); - }); - }; - TencentCosClient.prototype.listDir = function (dirKey) { - return __awaiter(this, void 0, void 0, function () { - var cos; - var _this = this; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.getCosClient()]; - case 1: - cos = _a.sent(); - return [2 /*return*/, (0, basic_1.safePromise)(function (resolve, reject) { - cos.getBucket({ - Bucket: _this.bucket, - Region: _this.region, - Prefix: dirKey, - MaxKeys: 1000, - }, function (err, data) { - if (err) { - reject(err); - return; - } - resolve(data.Contents); - }); - })]; - } - }); - }); - }; - return TencentCosClient; -}()); -exports.TencentCosClient = TencentCosClient; diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json index 917f9ab27..0cb930a53 100644 --- a/packages/ui/certd-client/package.json +++ b/packages/ui/certd-client/package.json @@ -83,7 +83,7 @@ "postcss-antd-fixes": "^0.2.0", "postcss-import": "^16.1.0", "postcss-preset-env": "^10.1.5", - "psl": "^1.9.0", + "psl": "^1.15.0", "qiniu-js": "^3.4.2", "qrcode": "^1.5.4", "radix-vue": "^1.9.16", diff --git a/packages/ui/certd-server/.env b/packages/ui/certd-server/.env index 536d4b688..345247b4a 100644 --- a/packages/ui/certd-server/.env +++ b/packages/ui/certd-server/.env @@ -1 +1,2 @@ LEGO_VERSION=4.30.1 +certd_plugin_loadmode=metadata \ No newline at end of file diff --git a/packages/ui/certd-server/.env.production b/packages/ui/certd-server/.env.production new file mode 100644 index 000000000..3e755de5e --- /dev/null +++ b/packages/ui/certd-server/.env.production @@ -0,0 +1 @@ +certd_plugin_loadmode=metadata diff --git a/packages/ui/certd-server/.env.production.yaml b/packages/ui/certd-server/.env.production.yaml index 25be762ba..05522de6b 100644 --- a/packages/ui/certd-server/.env.production.yaml +++ b/packages/ui/certd-server/.env.production.yaml @@ -15,6 +15,3 @@ typeorm: account: server: baseUrl: 'https://app.handfree.work/subject' - -plugin: - loadMode: metadata \ No newline at end of file diff --git a/packages/ui/certd-server/metadata/access_1panel.yaml b/packages/ui/certd-server/metadata/access_1panel.yaml new file mode 100644 index 000000000..7b3278ba8 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_1panel.yaml @@ -0,0 +1,102 @@ +name: 1panel +title: 1panel授权 +desc: 账号和密码 +icon: svg:icon-onepanel +input: + baseUrl: + title: 1Panel面板的url + component: + placeholder: http://xxxx.com:1231 + helper: 不要带安全入口 + required: true + safeEnter: + title: 安全入口 + component: + placeholder: 登录的安全入口 + encrypt: true + required: false + type: + title: 授权方式 + component: + name: a-select + vModel: value + options: + - label: 模拟登录【不推荐】 + value: password + - label: 接口密钥【推荐】 + value: apikey + required: true + apiVersion: + title: 接口版本 + value: v1 + component: + placeholder: v1 / v2 + name: a-select + vModel: value + options: + - label: v1 + value: v1 + - label: v2 + value: v2 + required: true + username: + title: 用户名 + component: + placeholder: username + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + + required: true + password: + title: 密码 + component: + placeholder: password + helper: '' + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + + required: true + encrypt: true + apiKey: + title: 接口密钥 + component: + placeholder: 接口密钥 + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'apikey'; + }) + } + + helper: 面板设置->API接口中获取 + required: true + encrypt: true + skipSslVerify: + title: 忽略证书校验 + value: true + component: + name: a-switch + vModel: checked + helper: 如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭 + testRequest: + title: 测试 + component: + name: api-test + action: onTestRequest + helper: |- + 点击测试接口看是否正常 + IP需要加白名单,如果是同一台机器部署的,可以试试面板的url使用网卡docker0的ip,白名单使用172.16.0.0/12 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/1panel/access.js diff --git a/packages/ui/certd-server/metadata/access_aliesa.yaml b/packages/ui/certd-server/metadata/access_aliesa.yaml new file mode 100644 index 000000000..3e1adaa4f --- /dev/null +++ b/packages/ui/certd-server/metadata/access_aliesa.yaml @@ -0,0 +1,29 @@ +name: aliesa +title: 阿里云ESA授权 +desc: '' +icon: ant-design:aliyun-outlined +order: 0 +input: + accessId: + title: 阿里云授权 + component: + name: access-selector + vModel: modelValue + type: aliyun + helper: 请选择阿里云授权 + required: true + region: + title: 地区 + component: + name: a-select + vModel: value + options: + - label: 杭州 + value: cn-hangzhou + - label: 新加坡 + value: ap-southeast-1 + helper: 请选择ESA地区 + required: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/aliyun/access/aliesa-access.js diff --git a/packages/ui/certd-server/metadata/access_alioss.yaml b/packages/ui/certd-server/metadata/access_alioss.yaml new file mode 100644 index 000000000..311391967 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_alioss.yaml @@ -0,0 +1,85 @@ +name: alioss +title: 阿里云OSS授权 +desc: 包含地域和Bucket +icon: ant-design:aliyun-outlined +input: + accessId: + title: 阿里云授权 + component: + name: access-selector + vModel: modelValue + type: aliyun + helper: 请选择阿里云授权 + required: true + region: + title: 大区 + component: + name: a-auto-complete + vModel: value + options: + - value: oss-cn-hangzhou + label: 华东1(杭州) + - value: oss-cn-shanghai + label: 华东2(上海) + - value: oss-cn-nanjing + label: 华东5(南京-本地地域) + - value: oss-cn-fuzhou + label: 华东6(福州-本地地域) + - value: oss-cn-wuhan-lr + label: 华中1(武汉-本地地域) + - value: oss-cn-qingdao + label: 华北1(青岛) + - value: oss-cn-beijing + label: 华北2(北京) + - value: oss-cn-zhangjiakou + label: 华北 3(张家口) + - value: oss-cn-huhehaote + label: 华北5(呼和浩特) + - value: oss-cn-wulanchabu + label: 华北6(乌兰察布) + - value: oss-cn-shenzhen + label: 华南1(深圳) + - value: oss-cn-heyuan + label: 华南2(河源) + - value: oss-cn-guangzhou + label: 华南3(广州) + - value: oss-cn-chengdu + label: 西南1(成都) + - value: oss-cn-hongkong + label: 中国香港 + - value: oss-us-west-1 + label: 美国(硅谷)① + - value: oss-us-east-1 + label: 美国(弗吉尼亚)① + - value: oss-ap-northeast-1 + label: 日本(东京)① + - value: oss-ap-northeast-2 + label: 韩国(首尔) + - value: oss-ap-southeast-1 + label: 新加坡① + - value: oss-ap-southeast-2 + label: 澳大利亚(悉尼)① + - value: oss-ap-southeast-3 + label: 马来西亚(吉隆坡)① + - value: oss-ap-southeast-5 + label: 印度尼西亚(雅加达)① + - value: oss-ap-southeast-6 + label: 菲律宾(马尼拉) + - value: oss-ap-southeast-7 + label: 泰国(曼谷) + - value: oss-eu-central-1 + label: 德国(法兰克福)① + - value: oss-eu-west-1 + label: 英国(伦敦) + - value: oss-me-east-1 + label: 阿联酋(迪拜)① + - value: oss-rg-china-mainland + label: 无地域属性(中国内地) + required: true + bucket: + title: Bucket + helper: 存储桶名称 + required: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/aliyun/access/alioss-access.js diff --git a/packages/ui/certd-server/metadata/access_alipay.yaml b/packages/ui/certd-server/metadata/access_alipay.yaml new file mode 100644 index 000000000..17a73971c --- /dev/null +++ b/packages/ui/certd-server/metadata/access_alipay.yaml @@ -0,0 +1,29 @@ +name: alipay +title: 支付宝 +icon: ion:logo-alipay +input: + appId: + title: AppId + component: + placeholder: 201909176714xxxx + required: true + encrypt: false + privateKey: + title: 应用私钥 + component: + placeholder: MIIEvQIBADANB... + name: a-textarea + rows: 3 + required: true + encrypt: true + alipayPublicKey: + title: 支付宝公钥 + component: + name: a-textarea + rows: 3 + placeholder: MIIBIjANBg... + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/alipay/access.js diff --git a/packages/ui/certd-server/metadata/access_aliyun.yaml b/packages/ui/certd-server/metadata/access_aliyun.yaml new file mode 100644 index 000000000..7d482e979 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_aliyun.yaml @@ -0,0 +1,22 @@ +name: aliyun +title: 阿里云授权 +desc: '' +icon: ant-design:aliyun-outlined +order: 0 +input: + accessKeyId: + title: accessKeyId + component: + placeholder: accessKeyId + helper: 登录阿里云控制台->AccessKey管理页面获取。 + required: true + accessKeySecret: + title: accessKeySecret + component: + placeholder: accessKeySecret + required: true + encrypt: true + helper: 注意:证书申请需要dns解析权限;其他阿里云插件,需要对应的权限,比如证书上传需要证书管理权限;嫌麻烦就用主账号的全量权限的accessKey +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/aliyun/access/aliyun-access.js diff --git a/packages/ui/certd-server/metadata/access_baidu.yaml b/packages/ui/certd-server/metadata/access_baidu.yaml new file mode 100644 index 000000000..ef5f60e2f --- /dev/null +++ b/packages/ui/certd-server/metadata/access_baidu.yaml @@ -0,0 +1,29 @@ +name: baidu +title: 百度云授权 +desc: '' +icon: ant-design:baidu-outlined +order: 2 +input: + accessKey: + title: AccessKey + component: + placeholder: AccessKey + helper: '[百度智能云->安全认证获取](https://console.bce.baidu.com/iam/#/iam/accesslist)' + required: true + encrypt: false + secretKey: + title: SecretKey + component: + placeholder: SecretKey + helper: '' + required: true + encrypt: true + testRequest: + title: 测试 + component: + name: api-test + action: onTestRequest + helper: 点击测试接口看是否正常 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baidu/access.js diff --git a/packages/ui/certd-server/metadata/access_baishan.yaml b/packages/ui/certd-server/metadata/access_baishan.yaml new file mode 100644 index 000000000..b57705046 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_baishan.yaml @@ -0,0 +1,15 @@ +name: baishan +title: 白山云授权 +desc: '' +icon: material-symbols:shield-outline +input: + token: + title: token + component: + placeholder: token + helper: 自行联系提供商申请 + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baishan/access.js diff --git a/packages/ui/certd-server/metadata/access_baota.yaml b/packages/ui/certd-server/metadata/access_baota.yaml new file mode 100644 index 000000000..721a3f13b --- /dev/null +++ b/packages/ui/certd-server/metadata/access_baota.yaml @@ -0,0 +1,44 @@ +name: baota +title: baota授权 +desc: '' +icon: svg:icon-bt +order: 2 +input: + panelUrl: + title: 宝塔URL地址 + component: + placeholder: http://192.168.42.237:41896 + helper: 宝塔面板的url地址,不要带安全入口,例如:http://192.168.42.237:41896 + required: true + apiSecret: + title: 接口密钥 + component: + placeholder: 接口密钥 + helper: |- + 宝塔面板设置->面板设置->API接口->接口配置->接口密钥。 + 必须要加IP白名单,您可以点击下方测试按钮,报错之后会打印IP,将IP加入白名单之后再次测试即可 + required: true + encrypt: true + skipSslVerify: + title: 忽略证书校验 + value: true + component: + name: a-switch + vModel: checked + helper: 如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭 + isWindows: + title: windows版 + value: false + component: + name: a-switch + vModel: checked + helper: 是否是windows版 + testRequest: + title: 测试 + component: + name: api-test + action: TestRequest + helper: 点击测试接口看是否正常 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baota/access.js diff --git a/packages/ui/certd-server/metadata/access_baotawaf.yaml b/packages/ui/certd-server/metadata/access_baotawaf.yaml new file mode 100644 index 000000000..613455272 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_baotawaf.yaml @@ -0,0 +1,36 @@ +name: baotawaf +title: 宝塔云WAF授权 +desc: 用于连接和管理宝塔云WAF服务的授权配置 +icon: svg:icon-bt +input: + panelUrl: + title: 在宝塔WAF URL + component: + placeholder: http://192.168.42.237:41896 + helper: 在宝塔WAF的URL地址,不要带安全入口,例如:http://192.168.42.237:41896 + required: true + apiSecret: + title: WAF API 密钥 + component: + placeholder: 请输入WAF API接口密钥 + helper: |- + 在宝塔WAF设置页面 - API接口中获取的API密钥。 + 必须添加IP白名单,请确保已将CertD服务器IP加入白名单 + required: true + encrypt: true + skipSslVerify: + title: 忽略SSL证书校验 + value: false + component: + name: a-switch + vModel: checked + helper: 如果面板使用的是自签名SSL证书,则需要开启此选项 + testRequest: + title: 测试 + component: + name: api-test + action: onTestRequest + helper: 点击测试WAF请求 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baota/waf-access.js diff --git a/packages/ui/certd-server/metadata/access_cdnfly.yaml b/packages/ui/certd-server/metadata/access_cdnfly.yaml new file mode 100644 index 000000000..300241c4e --- /dev/null +++ b/packages/ui/certd-server/metadata/access_cdnfly.yaml @@ -0,0 +1,93 @@ +name: cdnfly +title: cdnfly授权 +desc: '' +icon: majesticons:cloud-line +input: + url: + title: cdnfly系统网址 + component: + name: a-input + vModel: value + required: true + helper: 例如:http://demo.cdnfly.cn + type: + title: 授权方式 + value: apikey + component: + name: a-select + vModel: value + options: + - label: 接口密钥 + value: apikey + - label: 模拟登录 + value: password + required: true + username: + title: 用户名 + component: + placeholder: username + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + + required: true + password: + title: 密码 + component: + placeholder: password + helper: '' + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + + required: true + encrypt: true + apiKey: + title: api_key + component: + placeholder: api_key + helper: 登录cdnfly控制台->账户中心->Api密钥,点击开启后获取 + required: true + encrypt: true + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'apikey'; + }) + } + + apiSecret: + title: api_secret + component: + placeholder: api_secret + helper: 登录cdnfly控制台->账户中心->Api密钥,点击开启后获取 + required: true + encrypt: true + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'apikey'; + }) + } + + testRequest: + title: 测试 + component: + name: api-test + action: onTestRequest + helper: |- + 点击测试接口看是否正常 + IP需要加白名单,如果是同一台机器部署的,可以试试面板的url使用网卡docker0的ip,白名单使用172.16.0.0/12 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/cdnfly/access.js diff --git a/packages/ui/certd-server/metadata/access_ctyun.yaml b/packages/ui/certd-server/metadata/access_ctyun.yaml new file mode 100644 index 000000000..7eb0cd51a --- /dev/null +++ b/packages/ui/certd-server/metadata/access_ctyun.yaml @@ -0,0 +1,22 @@ +name: ctyun +title: 天翼云授权 +desc: '' +icon: ant-design:aliyun-outlined +order: 2 +input: + accessKeyId: + title: accessKeyId + component: + placeholder: accessKeyId + helper: '[前往创建天翼云AccessKey](https://iam.ctyun.cn/myAccessKey)' + required: true + securityKey: + title: securityKey + component: + placeholder: securityKey + required: true + encrypt: true + helper: '' +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/ctyun/access/ctyun-access.js diff --git a/packages/ui/certd-server/metadata/access_eab.yaml b/packages/ui/certd-server/metadata/access_eab.yaml new file mode 100644 index 000000000..036759067 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_eab.yaml @@ -0,0 +1,31 @@ +name: eab +title: EAB授权 +desc: ZeroSSL证书申请需要EAB授权 +icon: ic:outline-lock +input: + kid: + title: KID + component: + placeholder: kid / keyId + helper: EAB KID, google的叫 keyId,ssl.com的叫Account/ACME Key + required: true + encrypt: true + hmacKey: + title: HMACKey + component: + placeholder: HMAC Key / b64MacKey + helper: EAB HMAC Key ,google的叫b64MacKey + required: true + encrypt: true + email: + title: email + component: + placeholder: 绑定一个邮箱 + rules: + - type: email + message: 请输入正确的邮箱 + helper: Google的EAB申请证书,更换邮箱会导致EAB失效,可以在此处绑定一个邮箱避免此问题 + required: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-cert/access/eab-access.js diff --git a/packages/ui/certd-server/metadata/access_ftp.yaml b/packages/ui/certd-server/metadata/access_ftp.yaml new file mode 100644 index 000000000..7e6c0ea3c --- /dev/null +++ b/packages/ui/certd-server/metadata/access_ftp.yaml @@ -0,0 +1,49 @@ +name: ftp +title: FTP授权 +desc: '' +icon: mdi:folder-upload-outline +input: + host: + title: host + component: + placeholder: ip / 域名 + name: a-input + vModel: value + helper: FTP地址 + required: true + port: + title: 端口 + value: 21 + component: + placeholder: '21' + name: a-input-number + vModel: value + helper: FTP端口 + required: true + user: + title: user + component: + placeholder: 用户名 + helper: FTP用户名 + required: true + password: + title: password + component: + placeholder: 密码 + component: + name: a-input-password + vModel: value + encrypt: true + helper: FTP密码 + required: true + secure: + title: secure + value: false + component: + name: a-switch + vModel: checked + helper: 是否使用SSL + required: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/ftp/access.js diff --git a/packages/ui/certd-server/metadata/access_google.yaml b/packages/ui/certd-server/metadata/access_google.yaml new file mode 100644 index 000000000..601cd2f56 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_google.yaml @@ -0,0 +1,84 @@ +name: google +title: google cloud +desc: 谷歌云授权 +icon: flat-color-icons:google +input: + type: + title: 密钥类型 + value: serviceAccount + component: + placeholder: 密钥类型 + name: a-select + vModel: value + options: + - value: serviceAccount + label: 服务账号密钥 + - value: apiKey + label: ApiKey,暂不可用 + disabled: true + helper: 密钥类型 + required: true + encrypt: false + projectId: + title: 项目ID + component: + placeholder: ProjectId + helper: ProjectId + required: true + encrypt: false + mergeScript: |2- + + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'apiKey' + }) + } + + apiKey: + title: ApiKey + component: + placeholder: ApiKey + helper: 不要选,目前没有用 + required: true + encrypt: true + mergeScript: |2- + + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'apiKey' + }) + } + + serviceAccountSecret: + title: 服务账号密钥 + component: + placeholder: serviceAccountSecret + name: a-textarea + vModel: value + rows: 4 + helper: >- + [如何创建服务账号](https://cloud.google.com/iam/docs/service-accounts-create?hl=zh-CN) + + [获取密钥](https://console.cloud.google.com/iam-admin/serviceaccounts?hl=zh-cn),点击详情,点击创建密钥,将下载json文件,把内容填在此处 + required: true + encrypt: true + mergeScript: |2- + + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'serviceAccount' + }) + } + + httpsProxy: + title: https代理 + component: + placeholder: http://127.0.0.1:10811 + helper: |- + Google的请求需要走代理,如果不配置,则会使用环境变量中的全局HTTPS_PROXY配置 + 或者服务器本身在海外,则不需要配置 + required: false + encrypt: false +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-cert/access/google-access.js diff --git a/packages/ui/certd-server/metadata/access_k8s.yaml b/packages/ui/certd-server/metadata/access_k8s.yaml new file mode 100644 index 000000000..a19c486fd --- /dev/null +++ b/packages/ui/certd-server/metadata/access_k8s.yaml @@ -0,0 +1,23 @@ +name: k8s +title: k8s授权 +desc: '' +icon: mdi:kubernetes +input: + kubeconfig: + title: kubeconfig + component: + name: a-textarea + vModel: value + placeholder: kubeconfig + required: true + encrypt: true + skipTLSVerify: + title: 忽略证书校验 + component: + name: a-switch + vModel: checked + required: false + encrypt: false +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/k8s/access.js diff --git a/packages/ui/certd-server/metadata/access_kuocaicdn.yaml b/packages/ui/certd-server/metadata/access_kuocaicdn.yaml new file mode 100644 index 000000000..280c630e5 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_kuocaicdn.yaml @@ -0,0 +1,20 @@ +name: kuocaicdn +title: 括彩云cdn授权 +icon: material-symbols:shield-outline +desc: 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) +input: + username: + title: 账户 + component: + placeholder: 手机号 + required: true + encrypt: true + password: + title: 密码 + component: + placeholder: password + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/kuocai/access.js diff --git a/packages/ui/certd-server/metadata/access_lecdn.yaml b/packages/ui/certd-server/metadata/access_lecdn.yaml new file mode 100644 index 000000000..2edd0914b --- /dev/null +++ b/packages/ui/certd-server/metadata/access_lecdn.yaml @@ -0,0 +1,69 @@ +name: lecdn +title: LeCDN授权 +desc: '' +icon: material-symbols:shield-outline +input: + url: + title: LeCDN系统网址 + component: + name: a-input + vModel: value + required: true + helper: 例如:http://demo.xxxx.cn + type: + title: 认证类型 + component: + placeholder: 请选择 + name: a-select + vModel: value + options: + - value: token + label: API访问令牌 + - value: password + label: 账号密码(旧版本) + required: true + username: + title: 用户名 + component: + placeholder: username + mergeScript: |2- + + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + + required: true + encrypt: false + password: + title: 登录密码 + component: + placeholder: password + required: true + encrypt: true + mergeScript: |2- + + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + + apiToken: + title: Api访问令牌 + component: + placeholder: apiToken + required: true + encrypt: true + mergeScript: |2- + + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'token'; + }) + } + +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/lecdn/access.js diff --git a/packages/ui/certd-server/metadata/access_lucky.yaml b/packages/ui/certd-server/metadata/access_lucky.yaml new file mode 100644 index 000000000..2012b9647 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_lucky.yaml @@ -0,0 +1,29 @@ +name: lucky +title: lucky +desc: '' +icon: svg:icon-lucky +input: + url: + title: 访问url + component: + placeholder: http://xxx.xx.xx:16301 + helper: 不要带安全入口 + required: true + encrypt: false + safePath: + title: 安全入口 + component: + placeholder: /your_safe_path + helper: 请参考lucky设置中关于安全入口的配置, + required: false + encrypt: true + openToken: + title: OpenToken + component: + placeholder: OpenToken + helper: 设置->最下面开发者设置->启用OpenToken + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/lucky/access.js diff --git a/packages/ui/certd-server/metadata/access_maoyun.yaml b/packages/ui/certd-server/metadata/access_maoyun.yaml new file mode 100644 index 000000000..7106240dd --- /dev/null +++ b/packages/ui/certd-server/metadata/access_maoyun.yaml @@ -0,0 +1,35 @@ +name: maoyun +title: 猫云授权 +desc: '' +icon: svg:icon-lucky +input: + username: + title: 用户名 + component: + placeholder: username/手机号/email + name: a-input + vModel: value + helper: 用户名 + required: true + password: + title: password + component: + placeholder: 密码 + component: + name: a-input-password + vModel: value + encrypt: true + helper: 密码 + required: true + httpProxy: + title: HttpProxy + component: + placeholder: http://192.168.x.x:10811 + component: + name: a-input + vModel: value + encrypt: false + required: false +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/maoyun/access.js diff --git a/packages/ui/certd-server/metadata/access_plesk.yaml b/packages/ui/certd-server/metadata/access_plesk.yaml new file mode 100644 index 000000000..0c5cc8c8c --- /dev/null +++ b/packages/ui/certd-server/metadata/access_plesk.yaml @@ -0,0 +1,33 @@ +name: plesk +title: plesk授权 +desc: '' +icon: svg:icon-plesk +input: + url: + title: Plesk网址 + component: + name: a-input + vModel: value + required: true + helper: 例如:https://xxxx.xxxxx:8443/ + username: + title: 用户名 + component: + placeholder: username + required: true + encrypt: false + password: + title: 登录密码 + component: + placeholder: password + required: true + encrypt: true + testRequest: + title: 测试 + component: + name: api-test + action: onTestRequest + helper: 点击测试接口看是否正常 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/plesk/access.js diff --git a/packages/ui/certd-server/metadata/access_qiniu.yaml b/packages/ui/certd-server/metadata/access_qiniu.yaml new file mode 100644 index 000000000..fb8b2be72 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_qiniu.yaml @@ -0,0 +1,19 @@ +name: qiniu +title: 七牛云授权 +desc: '' +icon: svg:icon-qiniuyun +input: + accessKey: + title: AccessKey + rules: + - required: true + message: 此项必填 + helper: AK,前往[密钥管理](https://portal.qiniu.com/developer/user/key)获取 + secretKey: + title: SecretKey + encrypt: true + helper: SK +order: 2 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/qiniu/access.js diff --git a/packages/ui/certd-server/metadata/access_qiniuoss.yaml b/packages/ui/certd-server/metadata/access_qiniuoss.yaml new file mode 100644 index 000000000..4eb4b71cb --- /dev/null +++ b/packages/ui/certd-server/metadata/access_qiniuoss.yaml @@ -0,0 +1,20 @@ +name: qiniuoss +title: 七牛OSS授权 +desc: '' +icon: svg:icon-qiniuyun +input: + accessId: + title: 七牛云授权 + component: + name: access-selector + vModel: modelValue + type: qiniu + helper: 请选择七牛云授权 + required: true + bucket: + title: Bucket + helper: 存储桶名称 + required: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/qiniu/access-oss.js diff --git a/packages/ui/certd-server/metadata/access_s3.yaml b/packages/ui/certd-server/metadata/access_s3.yaml new file mode 100644 index 000000000..e34b37350 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_s3.yaml @@ -0,0 +1,53 @@ +name: s3 +title: s3/minio授权 +desc: S3/minio oss授权 +icon: mdi:folder-upload-outline +input: + endpoint: + title: endpoint + component: + placeholder: http://xxxxxx:9000 + name: a-input + vModel: value + helper: Minio的地址,如果是aws s3 则无需填写 + required: false + accessKeyId: + title: accessKeyId + component: + placeholder: accessKeyId + helper: accessKeyId + required: true + secretAccessKey: + title: secretAccessKey + component: + placeholder: secretAccessKey + component: + name: a-input + vModel: value + helper: secretAccessKey + encrypt: true + required: true + region: + title: 地区 + value: us-east-1 + component: + name: a-input + vModel: value + helper: region + required: true + bucket: + title: 存储桶 + component: + name: a-input + vModel: value + helper: bucket 名称 + required: true + testRequest: + title: 测试 + component: + name: api-test + action: TestRequest + helper: 点击测试接口是否正常 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/s3/access.js diff --git a/packages/ui/certd-server/metadata/access_safeline.yaml b/packages/ui/certd-server/metadata/access_safeline.yaml new file mode 100644 index 000000000..f60ede183 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_safeline.yaml @@ -0,0 +1,26 @@ +name: safeline +title: 长亭雷池授权 +icon: svg:icon-safeline +input: + baseUrl: + title: 雷池的访问url + component: + placeholder: https://xxxx.com:9443 + required: true + apiToken: + title: ApiToken + component: + placeholder: apiToken + helper: '' + required: true + encrypt: true + skipSslVerify: + title: 忽略证书校验 + value: true + component: + name: a-switch + vModel: checked + helper: 如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/safeline/access.js diff --git a/packages/ui/certd-server/metadata/access_sftp.yaml b/packages/ui/certd-server/metadata/access_sftp.yaml new file mode 100644 index 000000000..576b07cdb --- /dev/null +++ b/packages/ui/certd-server/metadata/access_sftp.yaml @@ -0,0 +1,23 @@ +name: sftp +title: SFTP授权 +desc: '' +icon: clarity:host-line +input: + sshAccess: + title: SSH授权 + component: + name: access-selector + type: ssh + vModel: modelValue + helper: 请选择一个SSH授权 + required: true + fileMode: + title: 文件权限 + component: + name: a-input + vModel: value + placeholder: '777' + helper: 文件上传后是否修改文件权限 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/ssh/sftp-access.js diff --git a/packages/ui/certd-server/metadata/access_ssh.yaml b/packages/ui/certd-server/metadata/access_ssh.yaml new file mode 100644 index 000000000..40030a5f7 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_ssh.yaml @@ -0,0 +1,128 @@ +name: ssh +title: 主机登录授权 +desc: '' +icon: clarity:host-line +input: + host: + title: 主机地址 + component: + placeholder: 主机域名或IP地址 + required: true + port: + title: 端口 + value: 22 + component: + name: a-input-number + placeholder: '22' + rules: + - required: true + message: 此项必填 + username: + title: 用户名 + value: root + rules: + - required: true + message: 此项必填 + password: + title: 密码 + component: + name: a-input-password + vModel: value + encrypt: true + helper: 登录密码或密钥必填一项 + privateKey: + title: 私钥登录 + helper: 私钥或密码必填一项 + component: + name: pem-input + vModel: modelValue + encrypt: true + passphrase: + title: 私钥密码 + helper: 如果你的私钥有密码的话 + component: + name: a-input-password + vModel: value + encrypt: true + scriptType: + title: 脚本类型 + helper: bash 、sh 、fish + component: + name: a-select + vModel: value + options: + - value: default + label: 默认 + - value: sh + label: sh + - value: bash + label: bash + - value: fish + label: fish(不支持set -e) + pty: + title: 伪终端 + helper: >- + 如果登录报错:all authentication methods + failed,可以尝试开启伪终端模式进行keyboard-interactive方式登录 + + 开启后对日志输出有一定的影响 + component: + name: a-switch + vModel: checked + socksProxy: + title: socks代理 + helper: socks代理配置,格式:socks5://user:password@host:port + component: + name: a-input + vModel: value + placeholder: socks5://user:password@host:port + encrypt: false + timeout: + title: 超时时间 + helper: 执行命令的超时时间,单位秒,默认30分钟 + component: + name: a-input-number + windows: + title: 是否Windows + helper: >- + 如果是Windows主机,请勾选此项 + + 并且需要windows[安装OpenSSH](https://certd.docmirror.cn/guide/use/host/windows.html) + component: + name: a-switch + vModel: checked + encoding: + title: 命令编码 + helper: 如果是Windows主机,且出现乱码了,请尝试设置为GBK + component: + name: a-select + vModel: value + options: + - value: '' + label: 默认 + - value: GBK + label: GBK + - value: UTF8 + label: UTF-8 + testRequest: + title: 测试 + component: + name: api-test + type: access + typeName: ssh + action: TestRequest + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 点击测试 +order: 0 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/ssh/ssh-access.js diff --git a/packages/ui/certd-server/metadata/access_synology.yaml b/packages/ui/certd-server/metadata/access_synology.yaml new file mode 100644 index 000000000..8ed90b444 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_synology.yaml @@ -0,0 +1,88 @@ +name: synology +title: 群晖登录授权 +desc: '' +icon: simple-icons:synology +input: + version: + title: 群晖版本 + component: + name: a-select + vModel: value + options: + - label: 7.x + value: '7' + - label: 6.x + value: '6' + required: true + baseUrl: + title: 群晖面板的url + component: + placeholder: https://yourdomain:5006 + helper: 群晖面板的访问地址,例如:https://yourdomain:5006 + required: true + username: + title: 账号 + component: + placeholder: 账号 + helper: 群晖面板登录账号,必须是处于管理员用户组 + required: true + password: + title: 密码 + component: + placeholder: 密码 + helper: 群晖面板登录密码 + required: true + encrypt: true + otp: + title: 双重认证 + value: false + component: + name: a-switch + vModel: checked + helper: 是否启用了双重认证 + required: true + deviceId: + title: 设备ID + component: + placeholder: 设备ID + name: synology-device-id-getter + type: access + typeName: synology + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + show: ctx.compute(({form})=>{ + return form.access.otp + }) + } + + helper: |- + 1.如果开启了双重认证,需要获取设备ID + 2.填好上面的必填项,然后点击获取设备ID,输入双重认证APP上的码,确认即可获得设备ID,此操作只需要做一次 + 3.注意:必须勾选‘安全性->允许网页浏览器的用户通过信任设备来跳过双重验证 + 4.注意:在群晖信任设备页面里面会生成一条记录,不要删除 + 5.注意:需要将流水线证书申请过期前多少天设置为30天以下,避免设备ID过期 + required: false + encrypt: true + skipSslVerify: + title: 忽略证书校验 + value: true + component: + name: a-switch + vModel: checked + helper: 如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭 + timeout: + title: 请求超时 + value: 120 + component: + name: a-input-number + vModel: value + helper: 请求超时时间,单位:秒 +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/synology/access.js diff --git a/packages/ui/certd-server/metadata/access_tencent.yaml b/packages/ui/certd-server/metadata/access_tencent.yaml new file mode 100644 index 000000000..f8b64db3a --- /dev/null +++ b/packages/ui/certd-server/metadata/access_tencent.yaml @@ -0,0 +1,46 @@ +name: tencent +title: 腾讯云 +icon: svg:icon-tencentcloud +order: 0 +input: + secretId: + title: secretId + helper: |- + 使用对应的插件需要有对应的权限,比如上传证书,需要证书管理权限;部署到clb需要clb相关权限 + 前往[密钥管理](https://console.cloud.tencent.com/cam/capi)进行创建 + component: + placeholder: secretId + rules: + - required: true + message: 该项必填 + secretKey: + title: secretKey + component: + placeholder: secretKey + encrypt: true + rules: + - required: true + message: 该项必填 + accountType: + title: 站点类型 + value: cn + component: + name: a-select + options: + - label: 国内站 + value: cn + - label: 国际站 + value: intl + encrypt: false + rules: + - required: true + message: 该项必填 + closeExpiresNotify: + title: 关闭证书过期通知 + value: true + component: + name: a-switch + vModel: checked +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/tencent/access.js diff --git a/packages/ui/certd-server/metadata/access_tencentcos.yaml b/packages/ui/certd-server/metadata/access_tencentcos.yaml new file mode 100644 index 000000000..19dd9be1a --- /dev/null +++ b/packages/ui/certd-server/metadata/access_tencentcos.yaml @@ -0,0 +1,75 @@ +name: tencentcos +title: 腾讯云COS授权 +icon: svg:icon-tencentcloud +desc: 腾讯云对象存储授权,包含地域和存储桶 +input: + accessId: + title: 腾讯云授权 + component: + name: access-selector + vModel: modelValue + type: tencent + helper: 请选择腾讯云授权 + required: true + region: + title: 所在地域 + helper: 存储桶所在地域 + component: + name: a-auto-complete + vModel: value + options: + - value: '' + label: '--------中国大陆地区-------' + disabled: true + - value: ap-beijing-1 + label: 北京1区 + - value: ap-beijing + label: 北京 + - value: ap-nanjing + label: 南京 + - value: ap-shanghai + label: 上海 + - value: ap-guangzhou + label: 广州 + - value: ap-chengdu + label: 成都 + - value: ap-chongqing + label: 重庆 + - value: ap-shenzhen-fsi + label: 深圳金融 + - value: ap-shanghai-fsi + label: 上海金融 + - value: ap-beijing-fsi + label: 北京金融 + - value: '' + label: '--------中国香港及境外-------' + disabled: true + - value: ap-hongkong + label: 中国香港 + - value: ap-singapore + label: 新加坡 + - value: ap-mumbai + label: 孟买 + - value: ap-jakarta + label: 雅加达 + - value: ap-seoul + label: 首尔 + - value: ap-bangkok + label: 曼谷 + - value: ap-tokyo + label: 东京 + - value: na-siliconvalley + label: 硅谷 + - value: na-ashburn + label: 弗吉尼亚 + - value: sa-saopaulo + label: 圣保罗 + - value: eu-frankfurt + label: 法兰克福 + bucket: + title: Bucket + helper: 存储桶名称 + required: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-lib/tencent/access-cos.js diff --git a/packages/ui/certd-server/metadata/access_unicloud.yaml b/packages/ui/certd-server/metadata/access_unicloud.yaml new file mode 100644 index 000000000..70d198191 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_unicloud.yaml @@ -0,0 +1,21 @@ +name: unicloud +title: uniCloud +icon: material-symbols:shield-outline +desc: unicloud授权 +input: + email: + title: 账号 + component: + placeholder: email + helper: 登录邮箱 + required: true + encrypt: false + password: + title: 密码 + component: + placeholder: 密码 + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/unicloud/access.js diff --git a/packages/ui/certd-server/metadata/access_wxpay.yaml b/packages/ui/certd-server/metadata/access_wxpay.yaml new file mode 100644 index 000000000..6563e85b1 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_wxpay.yaml @@ -0,0 +1,40 @@ +name: wxpay +title: 微信支付 +icon: tdesign:logo-wechatpay-filled +input: + appId: + title: AppId + component: + placeholder: 201909176714xxxx + required: true + encrypt: false + mchid: + title: 商户ID + component: + placeholder: 201909176714xxxx + required: true + encrypt: false + publicKey: + title: 公钥 + component: + name: a-textarea + rows: 3 + placeholder: MIIBIjANBg... + required: true + encrypt: true + privateKey: + title: 私钥 + component: + placeholder: MIIEvQIBADANB... + name: a-textarea + rows: 3 + required: true + encrypt: true + key: + title: APIv3密钥 + helper: 微信商户平台—>账户设置—>API安全—>设置APIv3密钥 + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/wxpay/access.js diff --git a/packages/ui/certd-server/metadata/access_yfysms.yaml b/packages/ui/certd-server/metadata/access_yfysms.yaml new file mode 100644 index 000000000..4684f7b99 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_yfysms.yaml @@ -0,0 +1,21 @@ +name: yfysms +title: 易发云短信 +icon: material-symbols:shield-outline +desc: sms.yfyidc.cn/ +input: + keyId: + title: KeyID + component: + placeholder: api_key + helper: '[获取密钥](http://sms.yfyidc.cn/user/index#)' + required: true + encrypt: true + keySecret: + title: KeySecret + component: + placeholder: '' + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/yidun/access-sms.js diff --git a/packages/ui/certd-server/metadata/access_yidun.yaml b/packages/ui/certd-server/metadata/access_yidun.yaml new file mode 100644 index 000000000..56fcc26c5 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_yidun.yaml @@ -0,0 +1,22 @@ +name: yidun +title: 易盾DCDN授权 +icon: material-symbols:shield-outline +desc: https://user.yiduncdn.com +input: + apiKey: + title: api_key + component: + placeholder: api_key + helper: http://user.yiduncdn.com/console/index.html#/account/config/api,点击开启后获取 + required: true + encrypt: true + apiSecret: + title: api_secret + component: + placeholder: api_secret + helper: http://user.yiduncdn.com/console/index.html#/account/config/api,点击开启后获取 + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/yidun/access.js diff --git a/packages/ui/certd-server/metadata/access_yidunrcdn.yaml b/packages/ui/certd-server/metadata/access_yidunrcdn.yaml new file mode 100644 index 000000000..74b0fb563 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_yidunrcdn.yaml @@ -0,0 +1,20 @@ +name: yidunrcdn +title: 易盾rcdn授权 +icon: material-symbols:shield-outline +desc: 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) +input: + username: + title: 账户 + component: + placeholder: 手机号 + required: true + encrypt: true + password: + title: 密码 + component: + placeholder: password + required: true + encrypt: true +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/yidun/access-rcdn.js diff --git a/packages/ui/certd-server/metadata/access_yizhifu.yaml b/packages/ui/certd-server/metadata/access_yizhifu.yaml new file mode 100644 index 000000000..5f4ee0f30 --- /dev/null +++ b/packages/ui/certd-server/metadata/access_yizhifu.yaml @@ -0,0 +1,45 @@ +name: yizhifu +title: 易支付 +icon: svg:icon-yizhifu +input: + url: + title: url + component: + placeholder: https://pay.xxxx.com + helper: 易支付系统地址 + required: true + encrypt: false + pid: + title: 商户id + component: + placeholder: pid + required: true + encrypt: false + key: + title: key + component: + placeholder: key + required: true + encrypt: true + payType: + title: 固定支付方式 + component: + placeholder: 固定一种支付方式,也就是submit.php中的type参数 + helper: 不填则跳转到收银台由用户自己选择,如果您的易支付系统不支持收银台,则必须填写 + required: false + encrypt: false + signType: + title: 签名方式 + component: + name: a-select + vModel: value + options: + - label: MD5 + value: MD5 + - label: SHA256 + value: SHA256 + required: true + encrypt: false +pluginType: access +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/yizhifu/access.js diff --git a/packages/ui/certd-server/metadata/deploy_1PanelDeployToWebsitePlugin.yaml b/packages/ui/certd-server/metadata/deploy_1PanelDeployToWebsitePlugin.yaml new file mode 100644 index 000000000..2816b63db --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_1PanelDeployToWebsitePlugin.yaml @@ -0,0 +1,107 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: 1PanelDeployToWebsitePlugin +title: 1Panel-部署证书到1Panel +icon: svg:icon-onepanel +desc: 更新1Panel的证书 +group: panel +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: true + order: 0 + accessId: + title: 1Panel授权 + helper: 1Panel授权 + component: + name: access-selector + type: 1panel + required: true + order: 0 + currentNode: + title: 1Panel节点 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: OnePanelDeployToWebsitePlugin + action: onGetNodes + search: false + pager: false + watches: + - certDomains + - accessId + value: local + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 要更新的1Panel证书的节点信息,目前只有v2存在此概念 + order: 0 + sslIds: + title: 1Panel证书ID + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: 1PanelDeployToWebsitePlugin + action: onGetSSLIds + search: false + pager: false + watches: + - certDomains + - accessId + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: |- + 要更新的1Panel证书id,选择授权之后,从下拉框中选择 + IP需要加白名单,如果是同一台机器部署的,可以试试172.16.0.0/12 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/1panel/plugins/deploy-to-website.js diff --git a/packages/ui/certd-server/metadata/deploy_AliyunDeployCertToAll.yaml b/packages/ui/certd-server/metadata/deploy_AliyunDeployCertToAll.yaml new file mode 100644 index 000000000..ed5c898d5 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_AliyunDeployCertToAll.yaml @@ -0,0 +1,177 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: AliyunDeployCertToAll +title: 阿里云-部署至任意云资源 +icon: svg:icon-aliyun +group: aliyun +desc: >- + 【不建议使用】需要消耗阿里云自动部署次数,支持SLB、LIVE、webHosting、VOD、CR、DCDN、DDoS、CDN、ALB、APIGateway、FC、GA、MSE、NLB、OSS、SAE、WAF等云产品 +needPlus: false +input: + cert: + title: 域名证书 + helper: |- + 请选择证书申请任务输出的域名证书 + 或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量 + component: + name: output-selector + from: + - ':cert:' + - uploadCertToAliyun + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: false + order: 0 + endpoint: + title: 接入点 + helper: 不会选就按默认 + value: cas.aliyuncs.com + component: + name: a-select + options: + - value: cas.aliyuncs.com + label: 中国大陆 + - value: cas.ap-southeast-1.aliyuncs.com + label: 新加坡 + - value: cas.eu-central-1.aliyuncs.com + label: 德国(法兰克福) + required: true + order: 0 + accessId: + title: Access授权 + helper: 阿里云授权AccessKeyId、AccessKeySecret + component: + name: access-selector + type: aliyun + required: true + order: 0 + cloudProduct: + title: 云产品类型 + helper: 请选择云产品类型 + component: + name: a-select + vModel: value + options: + - value: SLB + label: SLB-传统型负载均衡 CLB(仅中国站) + - value: LIVE + label: LIVE-视频直播(仅中国站) + - value: webHosting + label: webHosting-云虚拟主机(仅中国站) + - value: VOD + label: VOD-视频点播(仅中国站) + - value: CR + label: CR-容器镜像服务(仅中国站) + - value: DCDN + label: DCDN-全站加速 + - value: DDoS + label: DDos 防护 + - value: CDN + label: CDN-内容分发网络 + - value: ALB + label: ALB-应用负载均衡 + - value: APIGateway + label: APIGateway-API 网关 + - value: FC + label: FC-函数计算 + - value: GA + label: GA-全球加速 + - value: MSE + label: MSE-微服务引擎 + - value: NLB + label: NLB-网络型负载均衡 + - value: OSS + label: OSS-对象存储 + - value: SAE + label: SAE-Serverless应用引擎 + - value: WAF + label: WAF-Web应用防火墙 + required: true + order: 0 + productIds: + title: 要部署证书的云产品 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: AliyunDeployCertToAll + action: onGetProductList + search: false + pager: false + watches: + - certDomains + - accessId + - cloudProduct + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 请选择要部署证书的云产品,注意:新创建的云产品资源可能需要过1-2小时才会在此处显示 + order: 0 + contactIds: + title: 联系人 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: AliyunDeployCertToAll + action: onGetContactList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: >- + 请选择联系人,如果没有,需要先到[阿里云控制台创建联系人](https://yundun.console.aliyun.com/?p=cas#/informationManagement/person/) + order: 0 + checkTimeout: + title: 检查超时时间 + helper: 检查部署任务超时时间,单位分钟 + value: 10 + component: + name: a-input-number + vModel: value + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-aliyun/plugin/deploy-to-all/index.js diff --git a/packages/ui/certd-server/metadata/deploy_BaiduDeployToBLB.yaml b/packages/ui/certd-server/metadata/deploy_BaiduDeployToBLB.yaml new file mode 100644 index 000000000..f32b050f2 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaiduDeployToBLB.yaml @@ -0,0 +1,112 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: BaiduDeployToBLB +title: 百度云-部署证书到负载均衡 +icon: ant-design:baidu-outlined +group: baidu +desc: 部署到百度云负载均衡,包括BLB、APPBLB +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + - BaiduUploadCert + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: true + order: 0 + region: + title: 区域 + component: + name: a-select + vModel: value + options: + - value: bj + label: 北京 + - value: fsh + label: 上海 + - value: gz + label: 广州 + - value: fwh + label: 武汉 + - value: su + label: 苏州 + - value: bd + label: 保定 + - value: hkg + label: 香港 + - value: sin + label: 新加坡 + required: true + order: 0 + blbType: + title: 负载均衡类型 + component: + name: a-select + vModel: value + options: + - value: blb + label: 普通负载均衡 + - value: appblb + label: 应用负载均衡 + required: true + order: 0 + accessId: + title: 百度云授权 + helper: 百度云授权 + component: + name: access-selector + type: baidu + required: true + order: 0 + blbIds: + title: 负载均衡ID + component: + name: remote-select + vModel: value + mode: tags + action: GetBLBList + watches: + - certDomains + - blbType + - accessId + required: true + order: 0 + listenerIds: + title: 监听器ID + component: + name: remote-select + vModel: value + mode: tags + action: GetListenerList + watches: + - certDomains + - accessId + - blbIds + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baidu/plugins/plugin-deploy-to-blb.js diff --git a/packages/ui/certd-server/metadata/deploy_BaiduDeployToCDN.yaml b/packages/ui/certd-server/metadata/deploy_BaiduDeployToCDN.yaml new file mode 100644 index 000000000..bbff31552 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaiduDeployToCDN.yaml @@ -0,0 +1,62 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: BaiduDeployToCDN +title: 百度云-部署证书到CDN +icon: ant-design:baidu-outlined +group: baidu +desc: 部署到百度云CDN +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + - BaiduUploadCert + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: true + order: 0 + accessId: + title: 百度云授权 + helper: 百度云授权 + component: + name: access-selector + type: baidu + required: true + order: 0 + domains: + title: CDN域名 + component: + name: remote-select + vModel: value + mode: tags + action: GetDomainList + watches: + - certDomains + - accessId + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baidu/plugins/plugin-deploy-to-cdn.js diff --git a/packages/ui/certd-server/metadata/deploy_BaiduUploadCert.yaml b/packages/ui/certd-server/metadata/deploy_BaiduUploadCert.yaml new file mode 100644 index 000000000..b458f721a --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaiduUploadCert.yaml @@ -0,0 +1,33 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: BaiduUploadCert +title: 百度云-上传到证书托管 +icon: ant-design:baidu-outlined +desc: 上传证书到百度云证书托管中心 +group: baidu +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: Access授权 + helper: access授权 + component: + name: access-selector + type: baidu + required: true + order: 0 +output: + baiduCertId: + title: 百度云CertId +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baidu/plugins/plugin-upload-to-baidu.js diff --git a/packages/ui/certd-server/metadata/deploy_BaishanUpdateCert.yaml b/packages/ui/certd-server/metadata/deploy_BaishanUpdateCert.yaml new file mode 100644 index 000000000..719389a49 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaishanUpdateCert.yaml @@ -0,0 +1,45 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: BaishanUpdateCert +title: 白山云-更新证书 +icon: material-symbols:shield-outline +group: cdn +needPlus: false +input: + certId: + title: 证书ID + component: + name: a-input-number + vModel: value + helper: 证书ID,在证书管理页面查看,每条记录都有证书id + order: 0 + certName: + title: 证书名称 + component: + name: a-input + vModel: value + helper: 给证书设置一个名字,便于区分 + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: 白山云授权 + helper: 白山云授权 + component: + name: access-selector + type: baishan + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baishan/plugins/plugin-update-cert.js diff --git a/packages/ui/certd-server/metadata/deploy_BaotaDeleteExpiringCert.yaml b/packages/ui/certd-server/metadata/deploy_BaotaDeleteExpiringCert.yaml new file mode 100644 index 000000000..444a987af --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaotaDeleteExpiringCert.yaml @@ -0,0 +1,23 @@ +showRunStrategy: true +default: + strategy: + runStrategy: 0 +name: BaotaDeleteExpiringCert +title: 宝塔-删除过期证书 +icon: svg:icon-bt +group: panel +desc: 删除证书夹中过期证书 +needPlus: true +input: + accessId: + title: 宝塔授权 + helper: baota的接口密钥 + component: + name: access-selector + type: baota + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baota/plugins/plugin-delete-expiring-cert.js diff --git a/packages/ui/certd-server/metadata/deploy_BaotaDeployPanelCert.yaml b/packages/ui/certd-server/metadata/deploy_BaotaDeployPanelCert.yaml new file mode 100644 index 000000000..81f921f99 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaotaDeployPanelCert.yaml @@ -0,0 +1,32 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: BaotaDeployPanelCert +title: 宝塔-面板证书部署 +icon: svg:icon-bt +group: panel +desc: 部署宝塔面板本身的ssl证书 +needPlus: true +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: 宝塔授权 + helper: baota的接口密钥 + component: + name: access-selector + type: baota + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baota/plugins/plugin-deploy-to-panel.js diff --git a/packages/ui/certd-server/metadata/deploy_BaotaDeployWAF.yaml b/packages/ui/certd-server/metadata/deploy_BaotaDeployWAF.yaml new file mode 100644 index 000000000..63f8d5224 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaotaDeployWAF.yaml @@ -0,0 +1,63 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: BaotaDeployWAF +title: 宝塔-WAF证书部署 +icon: svg:icon-bt +group: panel +desc: 部署宝塔云WAF/aaWAF +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: true + order: 0 + accessId: + title: 宝塔WAF授权 + helper: aaWAF的接口密钥 + component: + name: access-selector + type: baotawaf + required: true + order: 0 + siteIds: + title: 站点ID + component: + name: remote-select + vModel: value + mode: tags + action: onGetSiteList + search: true + watches: + - certDomains + - accessId + required: true + helper: 将会自动获取证书匹配的站点,请选择要部署证书的站点 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baota/plugins/plugin-deploy-to-aawaf.js diff --git a/packages/ui/certd-server/metadata/deploy_BaotaDeployWebSiteCert.yaml b/packages/ui/certd-server/metadata/deploy_BaotaDeployWebSiteCert.yaml new file mode 100644 index 000000000..951393c4e --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaotaDeployWebSiteCert.yaml @@ -0,0 +1,84 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: BaotaDeployWebSiteCert +title: 宝塔-网站证书部署 +icon: svg:icon-bt +group: panel +desc: 部署宝塔管理的站点的ssl证书,目前支持宝塔网站站点、docker站点等。本插件也支持aaPanel。 +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: true + order: 0 + accessId: + title: 宝塔授权 + helper: baota的接口密钥 + component: + name: access-selector + type: baota + required: true + order: 0 + isDockerSite: + title: 是否Docker站点 + value: false + component: + name: a-switch + vModel: checked + helper: 是否为docker站点 + required: true + order: 0 + siteName: + title: 站点名称 + component: + name: remote-select + vModel: value + mode: tags + action: GetSiteList + watches: + - certDomains + - accessId + - isDockerSite + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: |- + 将会自动获取证书匹配的站点名称 + 宝塔版本低于9.0.0时,此处会获取失败,忽略错误,手动输入站点域名即可 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baota/plugins/plugin-deploy-to-website.js diff --git a/packages/ui/certd-server/metadata/deploy_BaotaDeployWebSiteWin.yaml b/packages/ui/certd-server/metadata/deploy_BaotaDeployWebSiteWin.yaml new file mode 100644 index 000000000..ab706819f --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_BaotaDeployWebSiteWin.yaml @@ -0,0 +1,72 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: BaotaDeployWebSiteWin +title: 宝塔win-网站证书部署 +icon: svg:icon-bt +group: panel +desc: 部署到Windows版宝塔管理的站点的ssl证书 +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: true + order: 0 + accessId: + title: 宝塔授权 + helper: baota的接口密钥 + component: + name: access-selector + type: baota + required: true + order: 0 + siteIds: + title: 站点Id + component: + name: remote-select + vModel: value + mode: tags + action: GetSiteList + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 将会自动获取证书匹配的站点名称 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/baota/plugins/plugin-deploy-to-website-win.js diff --git a/packages/ui/certd-server/metadata/deploy_CdnflyDeployToCDN.yaml b/packages/ui/certd-server/metadata/deploy_CdnflyDeployToCDN.yaml new file mode 100644 index 000000000..83ee034bc --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_CdnflyDeployToCDN.yaml @@ -0,0 +1,116 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: CdnflyDeployToCDN +title: cdnfly-部署证书到cdnfly +icon: majesticons:cloud-line +group: cdn +desc: cdnfly +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: false + order: 0 + accessId: + title: cdnfly授权 + helper: cdnfly授权 + component: + name: access-selector + type: cdnfly + required: true + order: 0 + autoMatch: + title: 自动匹配站点 + component: + name: a-switch + vModel: checked + helper: |- + 是否自动匹配站点进行部署 + 如果选择自动匹配,则下方参数无需填写 + order: 0 + certId: + title: 证书ID + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: CdnflyDeployToCDNPlugin + action: onGetCertList + search: true + pager: false + watches: + - certDomains + - accessId + - cert + - accessId + required: false + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 请选择证书Id,需要先手动上传一次证书,后续可以自动更新证书【推荐】 + order: 0 + siteId: + title: 网站Id + component: + name: remote-select + vModel: value + mode: tags + type: plugin + action: onGetSiteList + search: true + pager: false + watches: + - certDomains + - accessId + - url + - cert + - accessId + required: false + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 请选择要部署证书的网站Id + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/cdnfly/plugins/plugin-deploy-to-cdn.js diff --git a/packages/ui/certd-server/metadata/deploy_CertApply.yaml b/packages/ui/certd-server/metadata/deploy_CertApply.yaml new file mode 100644 index 000000000..c21cd4477 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_CertApply.yaml @@ -0,0 +1,443 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 0 + input: + renewDays: 18 + forceUpdate: false +name: CertApply +title: 证书申请(JS版) +icon: ph:certificate +group: cert +desc: 免费通配符域名证书申请,支持多个域名打到同一个证书上 +input: + domains: + title: 证书域名 + component: + name: a-select + vModel: value + mode: tags + open: false + placeholder: foo.com / *.foo.com / *.bar.com + tokenSeparators: + - ',' + - ' ' + - , + - 、 + - '|' + rules: + - type: domains + required: true + col: + span: 24 + order: -999 + helper: |- + 1、支持多个域名打到一个证书上,例如: foo.com,*.foo.com,*.bar.com + 2、子域名被通配符包含的不要填写,例如:www.foo.com已经被*.foo.com包含,不要填写www.foo.com + 3、泛域名只能通配*号那一级(*.foo.com的证书不能用于xxx.yyy.foo.com、不能用于foo.com) + 4、输入一个,空格之后,再输入下一个 + 5、如果设置了子域托管解析(比如免费的二级域名托管在CF或者阿里云),请先[设置托管子域名](#/certd/pipeline/subDomain) + email: + title: 邮箱 + component: + name: email-selector + vModel: value + rules: + - type: email + message: 请输入正确的邮箱 + required: true + order: -1 + helper: 请输入邮箱 + challengeType: + title: 域名验证方式 + value: dns + component: + name: a-select + vModel: value + options: + - value: dns + label: DNS直接验证 + - value: cname + label: CNAME代理验证 + - value: http + label: HTTP文件验证(IP证书只能选它) + - value: dnses + label: 多DNS提供商 + - value: auto + label: 自动匹配 + required: true + helper: > + 1. DNS直接验证:当域名dns解析已被本系统支持时(即下方DNS解析服务商选项中可选),推荐选择此方式 + + 2. + CNAME代理验证:支持任何注册商的域名,第一次需要手动添加[CNAME记录](#/certd/cname/record)(如果经常申请失败,建议将DNS服务器修改为阿里云/腾讯云的,然后使用DNS直接验证) + + 3. HTTP文件验证:不支持泛域名,需要配置网站文件上传(IP证书必须选它) + + 4. 多DNS提供商:每个域名可以选择独立的DNS提供商 + + 5. 自动匹配:此处无需选择校验方式,需要在[域名管理](#/certd/cert/domain)中提前配置好校验方式 + order: 0 + sslProvider: + title: 证书颁发机构 + value: letsencrypt + component: + name: icon-select + vModel: value + options: + - value: letsencrypt + label: Let's Encrypt(免费,新手推荐,支持IP证书) + icon: simple-icons:letsencrypt + - value: google + label: Google(免费) + icon: flat-color-icons:google + - value: zerossl + label: ZeroSSL(免费) + icon: emojione:digit-zero + - value: litessl + label: litessl(免费) + icon: roentgen:free + - value: sslcom + label: SSL.com(仅主域名和www免费) + icon: la:expeditedssl + - value: letsencrypt_staging + label: Let's Encrypt测试环境(仅供测试) + icon: simple-icons:letsencrypt + helper: |- + Let's Encrypt:申请最简单 + Google:大厂光环,兼容性好,仅首次需要翻墙获取EAB授权 + ZeroSSL:需要EAB授权,无需翻墙 + SSL.com:仅主域名和www免费,必须设置CAA记录 + required: true + order: 0 + dnsProviderType: + title: DNS解析服务商 + component: + name: dns-provider-selector + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.challengeType === 'dns' + }), + component:{ + onSelectedChange: ctx.compute(({form})=>{ + return ($event)=>{ + form.dnsProviderAccessType = $event.accessType + } + }) + } + } + + required: true + helper: |- + 您的域名注册商,或者域名的dns服务器属于哪个平台 + 如果这里没有,请选择CNAME代理验证校验方式 + order: 0 + dnsProviderAccess: + title: DNS解析授权 + component: + name: access-selector + required: true + helper: 请选择dns解析服务商授权 + mergeScript: |- + return { + component:{ + type: ctx.compute(({form})=>{ + return form.dnsProviderAccessType || form.dnsProviderType + }) + }, + show: ctx.compute(({form})=>{ + return form.challengeType === 'dns' + }) + } + + order: 0 + domainsVerifyPlan: + title: 域名验证配置 + component: + name: domains-verify-plan-editor + rules: + - type: checkDomainVerifyPlan + required: true + col: + span: 24 + mergeScript: |- + return { + component:{ + domains: ctx.compute(({form})=>{ + return form.domains + }), + defaultType: ctx.compute(({form})=>{ + return form.challengeType || 'cname' + }) + }, + show: ctx.compute(({form})=>{ + return form.challengeType === 'cname' || form.challengeType === 'http' || form.challengeType === 'dnses' + }), + helper: ctx.compute(({form})=>{ + if(form.challengeType === 'cname' ){ + return '请按照上面的提示,给要申请证书的域名添加CNAME记录,添加后,点击验证,验证成功后不要删除记录,申请和续期证书会一直用它' + }else if (form.challengeType === 'http'){ + return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录的.well-known/acme-challenge/目录下' + }else if (form.challengeType === 'http'){ + return '给每个域名单独配置dns提供商' + } + }) + } + + order: 0 + googleCommonEabAccessId: + title: Google公共EAB授权 + isSys: true + show: false + order: 0 + zerosslCommonEabAccessId: + title: ZeroSSL公共EAB授权 + isSys: true + show: false + order: 0 + sslcomCommonEabAccessId: + title: SSL.com公共EAB授权 + isSys: true + show: false + order: 0 + litesslCommonEabAccessId: + title: litessl公共EAB授权 + isSys: true + show: false + order: 0 + eabAccessId: + title: EAB授权 + component: + name: access-selector + type: eab + maybeNeed: true + required: false + helper: >- + 需要提供EAB授权 + + ZeroSSL:请前往[zerossl开发者中心](https://app.zerossl.com/developer),生成 'EAB + Credentials' + + Google:请查看[google获取eab帮助文档](https://certd.docmirror.cn/guide/use/google/),用过一次后会绑定邮箱,后续复用EAB要用同一个邮箱 + + SSL.com:[SSL.com账号页面](https://secure.ssl.com/account),然后点击api + credentials链接,然后点击编辑按钮,查看Secret key和HMAC key + + litessl:[litesslEAB页面](https://freessl.cn/automation/eab-manager),然后点击新增EAB + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return (form.sslProvider === 'zerossl' && !form.zerosslCommonEabAccessId) + || (form.sslProvider === 'google' && !form.googleCommonEabAccessId) + || (form.sslProvider === 'sslcom' && !form.sslcomCommonEabAccessId) + || (form.sslProvider === 'litessl' && !form.litesslCommonEabAccessId) + }) + } + + order: 0 + googleAccessId: + title: 服务账号授权 + component: + name: access-selector + type: google + maybeNeed: true + required: false + helper: >- + google服务账号授权与EAB授权选填其中一个,[服务账号授权获取方法](https://certd.docmirror.cn/guide/use/google/) + + 服务账号授权需要配置代理或者服务器本身在海外 + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.sslProvider === 'google' && !form.googleCommonEabAccessId + }) + } + + order: 0 + privateKeyType: + title: 加密算法 + value: rsa_2048 + component: + name: a-select + vModel: value + options: + - value: rsa_1024 + label: RSA 1024 + - value: rsa_2048 + label: RSA 2048 + - value: rsa_3072 + label: RSA 3072 + - value: rsa_4096 + label: RSA 4096 + - value: rsa_2048_pkcs1 + label: RSA 2048 pkcs1 (旧版) + - value: ec_256 + label: EC 256 + - value: ec_384 + label: EC 384 + helper: |- + 如无特殊需求,默认即可 + 选择RSA 2048 pkcs1可以获得旧版RSA证书 + required: true + order: 0 + certProfile: + title: 证书配置 + value: classic + component: + name: a-select + vModel: value + options: + - value: classic + label: 经典(classic) + - value: tlsserver + label: TLS服务器(tlsserver) + - value: shortlived + label: 短暂的(shortlived) + helper: 如无特殊需求,默认即可 + required: false + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.sslProvider === 'letsencrypt' + }) + } + + order: 0 + preferredChain: + title: 首选链 + component: + name: a-select + vModel: value + options: + - value: ISRG Root X1 + label: ISRG Root X1 + - value: ISRG Root X2 + label: ISRG Root X2 + helper: 如无特殊需求保持默认即可 + required: false + mergeScript: |2- + + const chainConfigs = {"letsencrypt":{"helper":"如无特殊需求保持默认即可","options":[{"value":"ISRG Root X1","label":"ISRG Root X1"},{"value":"ISRG Root X2","label":"ISRG Root X2"}]},"google":{"helper":"GlobalSign 提供对老旧设备更好的兼容性,但证书链会变长","options":[{"value":"GTS Root R1","label":"GTS Root R1"},{"value":"GlobalSign","label":"GlobalSign"}]}}; + const supportedProviders = ["letsencrypt","google"]; + const defaultProvider = "letsencrypt"; + const getConfig = (provider)=> chainConfigs[provider] || chainConfigs[defaultProvider]; + return { + show: ctx.compute(({form})=> supportedProviders.includes(form.sslProvider)), + component: { + options: ctx.compute(({form})=> getConfig(form.sslProvider).options) + }, + helper: ctx.compute(({form})=> getConfig(form.sslProvider).helper), + value: ctx.compute(({form})=>{ + const { options } = getConfig(form.sslProvider); + const allowed = options.map(item=>item.value); + const current = form.preferredChain; + if(allowed.includes(current)){ + return current; + } + return allowed[0]; + }) + }; + + order: 0 + useProxy: + title: 使用代理 + value: false + component: + name: a-switch + vModel: checked + helper: |- + 如果acme-v02.api.letsencrypt.org或dv.acme-v02.api.pki.goog被墙无法访问,请尝试开启此选项 + 默认情况会进行测试,如果无法访问,将会自动使用代理 + order: 0 + reverseProxy: + title: 自定义反代地址 + component: + placeholder: google.yourproxy.com + helper: |- + 填写你的自定义反代地址,不要带http:// + letsencrypt反代目标:acme-v02.api.letsencrypt.org + google反代目标:dv.acme-v02.api.pki.goog + order: 0 + skipLocalVerify: + title: 跳过本地校验DNS + value: false + component: + name: a-switch + vModel: checked + helper: 跳过本地校验可以加快申请速度,同时也会增加失败概率。 + order: 0 + maxCheckRetryCount: + title: 检查解析重试次数 + value: 20 + component: + name: a-input-number + vModel: value + helper: 检查域名验证解析记录重试次数,如果你的域名服务商解析生效速度慢,可以适当增加此值 + order: 0 + waitDnsDiffuseTime: + title: 等待解析生效时长 + value: 30 + component: + name: a-input-number + vModel: value + helper: 等待解析生效时长(秒),如果使用CNAME方式校验,本地验证失败,可以尝试延长此时间(比如5-10分钟) + order: 0 + pfxPassword: + title: 证书加密密码 + component: + name: input-password + vModel: value + required: false + order: 100 + helper: |- + 转换成PFX、jks格式证书是否需要加密 + jks必须设置密码,不传则默认123456 + pfx不传则为空密码 + pfxArgs: + title: PFX证书转换参数 + value: '-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + component: + name: a-auto-complete + vModel: value + options: + - value: '' + label: 兼容 Windows Server 最新 + - value: '-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + label: 兼容 Windows Server 2016 + - value: '-nomac -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + label: 兼容 Windows Server 2008 + required: false + order: 100 + helper: 兼容Windows Server各个版本 + renewDays: + title: 更新天数 + value: 18 + component: + name: a-input-number + vModel: value + required: true + order: 100 + helper: 到期前多少天后更新证书,注意:流水线默认不会自动运行,请设置定时器,每天定时运行本流水线 + successNotify: + title: 证书申请成功通知 + value: false + component: + name: a-switch + vModel: checked + order: 100 + helper: 证书申请成功后是否发送通知,优先使用默认通知渠道 +output: + cert: + title: 域名证书 + type: cert + certZip: + title: 域名证书压缩文件 + type: certZip +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-cert/plugin/cert-plugin/index.js diff --git a/packages/ui/certd-server/metadata/deploy_CertApplyGetFormAliyun.yaml b/packages/ui/certd-server/metadata/deploy_CertApplyGetFormAliyun.yaml new file mode 100644 index 000000000..aa47714d9 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_CertApplyGetFormAliyun.yaml @@ -0,0 +1,136 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 0 +name: CertApplyGetFormAliyun +icon: ph:certificate +title: 获取阿里云订阅证书 +group: cert +desc: 从阿里云拉取订阅模式的商用证书 +input: + domains: + title: 证书域名 + component: + name: a-select + vModel: value + mode: tags + open: false + placeholder: foo.com / *.foo.com / *.bar.com + tokenSeparators: + - ',' + - ' ' + - , + - 、 + - '|' + rules: + - type: domains + required: true + col: + span: 24 + order: -999 + helper: |- + 1、支持多个域名打到一个证书上,例如: foo.com,*.foo.com,*.bar.com + 2、子域名被通配符包含的不要填写,例如:www.foo.com已经被*.foo.com包含,不要填写www.foo.com + 3、泛域名只能通配*号那一级(*.foo.com的证书不能用于xxx.yyy.foo.com、不能用于foo.com) + 4、输入一个,空格之后,再输入下一个 + 5、如果设置了子域托管解析(比如免费的二级域名托管在CF或者阿里云),请先[设置托管子域名](#/certd/pipeline/subDomain) + email: + title: 邮箱 + component: + name: email-selector + vModel: value + rules: + - type: email + message: 请输入正确的邮箱 + required: true + order: -1 + helper: 请输入邮箱 + accessId: + title: Access授权 + helper: 阿里云授权AccessKeyId、AccessKeySecret + component: + name: access-selector + type: aliyun + required: true + order: 0 + orderId: + title: 证书订单ID + component: + name: RemoteAutoComplete + vModel: value + mode: tags + type: plugin + typeName: CertApplyGetFormAliyun + action: onGetOrderList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 订阅模式的证书订单Id + order: 0 + pfxPassword: + title: 证书加密密码 + component: + name: input-password + vModel: value + required: false + order: 100 + helper: |- + 转换成PFX、jks格式证书是否需要加密 + jks必须设置密码,不传则默认123456 + pfx不传则为空密码 + pfxArgs: + title: PFX证书转换参数 + value: '-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + component: + name: a-auto-complete + vModel: value + options: + - value: '' + label: 兼容 Windows Server 最新 + - value: '-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + label: 兼容 Windows Server 2016 + - value: '-nomac -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + label: 兼容 Windows Server 2008 + required: false + order: 100 + helper: 兼容Windows Server各个版本 + renewDays: + title: 更新天数 + value: 18 + component: + name: a-input-number + vModel: value + required: true + order: 100 + helper: 到期前多少天后更新证书,注意:流水线默认不会自动运行,请设置定时器,每天定时运行本流水线 + successNotify: + title: 证书申请成功通知 + value: false + component: + name: a-switch + vModel: checked + order: 100 + helper: 证书申请成功后是否发送通知,优先使用默认通知渠道 +output: + cert: + title: 域名证书 + type: cert + certZip: + title: 域名证书压缩文件 + type: certZip +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-cert/plugin/cert-plugin/getter/aliyun.js diff --git a/packages/ui/certd-server/metadata/deploy_CertApplyLego.yaml b/packages/ui/certd-server/metadata/deploy_CertApplyLego.yaml new file mode 100644 index 000000000..7ebae1ed5 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_CertApplyLego.yaml @@ -0,0 +1,173 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 0 + input: + renewDays: 35 + forceUpdate: false +name: CertApplyLego +icon: ph:certificate +title: 证书申请(Lego) +group: cert +desc: 支持海量DNS解析提供商,推荐使用,一样的免费通配符域名证书申请,支持多个域名打到同一个证书上 +input: + domains: + title: 证书域名 + component: + name: a-select + vModel: value + mode: tags + open: false + placeholder: foo.com / *.foo.com / *.bar.com + tokenSeparators: + - ',' + - ' ' + - , + - 、 + - '|' + rules: + - type: domains + required: true + col: + span: 24 + order: -999 + helper: |- + 1、支持多个域名打到一个证书上,例如: foo.com,*.foo.com,*.bar.com + 2、子域名被通配符包含的不要填写,例如:www.foo.com已经被*.foo.com包含,不要填写www.foo.com + 3、泛域名只能通配*号那一级(*.foo.com的证书不能用于xxx.yyy.foo.com、不能用于foo.com) + 4、输入一个,空格之后,再输入下一个 + 5、如果设置了子域托管解析(比如免费的二级域名托管在CF或者阿里云),请先[设置托管子域名](#/certd/pipeline/subDomain) + email: + title: 邮箱 + component: + name: email-selector + vModel: value + rules: + - type: email + message: 请输入正确的邮箱 + required: true + order: -1 + helper: 请输入邮箱 + dnsType: + title: DNS类型 + component: + name: a-input + vModel: value + placeholder: alidns + helper: 你的域名是通过哪家提供商进行解析的,具体应该配置什么请参考lego文档:https://go-acme.github.io/lego/dns/ + required: true + order: 0 + environment: + title: 环境变量 + component: + name: a-textarea + vModel: value + rows: 4 + placeholder: |- + ALICLOUD_ACCESS_KEY=abcdefghijklmnopqrstuvwx + ALICLOUD_SECRET_KEY=your-secret-key + required: true + helper: 一行一条,例如 appKeyId=xxxxx,具体配置请参考lego文档:https://go-acme.github.io/lego/dns/ + order: 0 + legoEabAccessId: + title: EAB授权 + component: + name: access-selector + type: eab + maybeNeed: true + helper: 如果需要提供EAB授权 + order: 0 + customArgs: + title: 自定义LEGO全局参数 + component: + name: a-input + vModel: value + placeholder: '--dns-timeout 30' + helper: 额外的lego全局命令行参数,参考文档:https://go-acme.github.io/lego/usage/cli/options/ + maybeNeed: true + order: 0 + customCommandOptions: + title: 自定义LEGO签名参数 + component: + name: a-input + vModel: value + placeholder: '--no-bundle' + helper: 额外的lego签名命令行参数,参考文档:https://go-acme.github.io/lego/usage/cli/options/ + maybeNeed: true + order: 0 + privateKeyType: + title: 加密算法 + value: ec256 + component: + name: a-select + vModel: value + options: + - value: rsa2048 + label: RSA 2048 + - value: rsa3072 + label: RSA 3072 + - value: rsa4096 + label: RSA 4096 + - value: rsa8192 + label: RSA 8192 + - value: ec256 + label: EC 256 + - value: ec384 + label: EC 384 + helper: 如无特殊需求,默认即可 + required: true + order: 0 + pfxPassword: + title: 证书加密密码 + component: + name: input-password + vModel: value + required: false + order: 100 + helper: |- + 转换成PFX、jks格式证书是否需要加密 + jks必须设置密码,不传则默认123456 + pfx不传则为空密码 + pfxArgs: + title: PFX证书转换参数 + value: '-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + component: + name: a-auto-complete + vModel: value + options: + - value: '' + label: 兼容 Windows Server 最新 + - value: '-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + label: 兼容 Windows Server 2016 + - value: '-nomac -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + label: 兼容 Windows Server 2008 + required: false + order: 100 + helper: 兼容Windows Server各个版本 + renewDays: + title: 更新天数 + value: 18 + component: + name: a-input-number + vModel: value + required: true + order: 100 + helper: 到期前多少天后更新证书,注意:流水线默认不会自动运行,请设置定时器,每天定时运行本流水线 + successNotify: + title: 证书申请成功通知 + value: false + component: + name: a-switch + vModel: checked + order: 100 + helper: 证书申请成功后是否发送通知,优先使用默认通知渠道 +output: + cert: + title: 域名证书 + type: cert + certZip: + title: 域名证书压缩文件 + type: certZip +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-cert/plugin/cert-plugin/lego/index.js diff --git a/packages/ui/certd-server/metadata/deploy_CertApplyUpload.yaml b/packages/ui/certd-server/metadata/deploy_CertApplyUpload.yaml new file mode 100644 index 000000000..9410bef3d --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_CertApplyUpload.yaml @@ -0,0 +1,150 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 0 +name: CertApplyUpload +icon: ph:certificate +title: 商用证书托管 +group: cert +desc: 手动上传自定义证书后,自动部署(每次证书有更新,都需要手动上传一次) +shortcut: + certUpdate: + title: 更新证书 + icon: ion:upload + action: onCertUpdate + form: + columns: + crt: + title: 证书 + type: text + form: + component: + name: pem-input + vModel: modelValue + textarea: + rows: 4 + placeholder: |- + -----BEGIN CERTIFICATE----- + ... + ... + -----END CERTIFICATE----- + rules: + - required: true + message: 此项必填 + col: + span: 24 + key: + title: 私钥 + type: text + form: + component: + name: pem-input + vModel: modelValue + textarea: + rows: 4 + placeholder: |- + -----BEGIN PRIVATE KEY----- + ... + ... + -----END PRIVATE KEY----- + rules: + - required: true + message: 此项必填 + col: + span: 24 +input: + uploadCert: + title: 手动上传证书 + component: + name: cert-info-updater + vModel: modelValue + helper: 手动上传证书 + order: -9999 + required: true + mergeScript: |2- + + return { + component:{ + on:{ + updated(scope){ + scope.form.input.domains = scope.$event?.domains + } + } + } + } + + domains: + title: 证书域名 + component: + name: a-select + vModel: value + mode: tags + open: false + placeholder: foo.com / *.foo.com / *.bar.com + tokenSeparators: + - ',' + - ' ' + - , + - 、 + - '|' + rules: + - type: domains + required: true + col: + span: 24 + order: -999 + helper: |- + 1、支持多个域名打到一个证书上,例如: foo.com,*.foo.com,*.bar.com + 2、子域名被通配符包含的不要填写,例如:www.foo.com已经被*.foo.com包含,不要填写www.foo.com + 3、泛域名只能通配*号那一级(*.foo.com的证书不能用于xxx.yyy.foo.com、不能用于foo.com) + 4、输入一个,空格之后,再输入下一个 + 5、如果设置了子域托管解析(比如免费的二级域名托管在CF或者阿里云),请先[设置托管子域名](#/certd/pipeline/subDomain) + pfxPassword: + title: 证书加密密码 + component: + name: input-password + vModel: value + required: false + order: 100 + helper: |- + 转换成PFX、jks格式证书是否需要加密 + jks必须设置密码,不传则默认123456 + pfx不传则为空密码 + pfxArgs: + title: PFX证书转换参数 + value: '-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + component: + name: a-auto-complete + vModel: value + options: + - value: '' + label: 兼容 Windows Server 最新 + - value: '-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + label: 兼容 Windows Server 2016 + - value: '-nomac -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES' + label: 兼容 Windows Server 2008 + required: false + order: 100 + helper: 兼容Windows Server各个版本 + renewDays: + title: 过期前提醒 + value: 10 + component: + name: a-input-number + vModel: value + required: true + order: 100 + helper: 到期前多少天提醒 +output: + cert: + title: 域名证书 + type: cert + certZip: + title: 域名证书压缩文件 + type: certZip + certMd5: + title: 证书MD5 + type: certMd5 +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-cert/plugin/cert-plugin/custom/index.js diff --git a/packages/ui/certd-server/metadata/deploy_CtyunDeployToCDN.yaml b/packages/ui/certd-server/metadata/deploy_CtyunDeployToCDN.yaml new file mode 100644 index 000000000..daf75380c --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_CtyunDeployToCDN.yaml @@ -0,0 +1,98 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: CtyunDeployToCDN +title: 天翼云-部署证书到CDN +icon: svg:icon-ctyun +group: cdn +desc: 部署证书到天翼云CDN和全站加速 +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: true + order: 0 + productCode: + title: 产品类型 + helper: 加速产品类型 + component: + name: a-select + options: + - label: 静态加速 + value: '001' + - label: 下载加速 + value: '003' + - label: 视频点播加速 + value: '004' + - label: CDN加速 + value: '008' + - label: 全站加速 + value: '006' + - label: 安全加速 + value: '007' + - label: 下载加速闲时 + value: '014' + required: true + order: 0 + accessId: + title: 天翼云授权 + helper: 天翼云授权 + component: + name: access-selector + type: ctyun + required: true + order: 0 + domains: + title: 加速域名 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: CtyunDeployToCDN + action: onGetDomainList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 请选择加速域名 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/ctyun/plugins/plugin-deploy-to-cdn.js diff --git a/packages/ui/certd-server/metadata/deploy_DeployCertToAliyunAck.yaml b/packages/ui/certd-server/metadata/deploy_DeployCertToAliyunAck.yaml new file mode 100644 index 000000000..fc129a473 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_DeployCertToAliyunAck.yaml @@ -0,0 +1,137 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: DeployCertToAliyunAck +title: 阿里云-部署到Ack +icon: svg:icon-aliyun +desc: 部署到阿里云Ack集群Ingress等通过Secret管理证书的应用 +group: aliyun +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: Access授权 + helper: 阿里云授权AccessKeyId、AccessKeySecret + component: + name: access-selector + type: aliyun + required: true + order: 0 + regionId: + title: 大区 + component: + name: a-auto-complete + vModel: value + options: + - value: cn-qingdao + label: 华北1(青岛) + - value: cn-beijing + label: 华北2(北京) + - value: cn-zhangjiakou + label: 华北3(张家口) + - value: cn-huhehaote + label: 华北5(呼和浩特) + - value: cn-wulanchabu + label: 华北6(乌兰察布) + - value: cn-hangzhou + label: 华东1(杭州) + - value: cn-shanghai + label: 华东2(上海) + - value: cn-shenzhen + label: 华南1(深圳) + - value: cn-guangzhou + label: 华南3(广州) + - value: ap-southeast-2 + label: 澳大利亚(悉尼) + - value: ap-southeast-3 + label: 马来西亚(吉隆坡) + - value: ap-northeast-1 + label: 日本(东京) + - value: cn-chengdu + label: 西南1(成都) + - value: ap-southeast-1 + label: 新加坡 + - value: ap-southeast-5 + label: 印度尼西亚(雅加达) + - value: cn-hongkong + label: 中国香港 + - value: eu-central-1 + label: 德国(法兰克福) + - value: us-east-1 + label: 美国(弗吉尼亚) + - value: us-west-1 + label: 美国(硅谷) + - value: eu-west-1 + label: 英国(伦敦) + - value: me-east-1 + label: 阿联酋(迪拜) + - value: cn-beijing-finance-1 + label: 华北2 金融云(邀测) + - value: cn-hangzhou-finance + label: 华东1 金融云 + - value: cn-shanghai-finance-1 + label: 华东2 金融云 + - value: cn-shenzhen-finance-1 + label: 华南1 金融云 + placeholder: 集群所属大区 + required: true + order: 0 + clusterId: + title: 集群id + component: + placeholder: 集群id + required: true + order: 0 + secretName: + title: 保密字典Id + component: + placeholder: 保密字典Id + helper: 原本存储证书的secret的name + required: true + order: 0 + namespace: + title: 命名空间 + value: default + component: + placeholder: 命名空间 + required: true + order: 0 + isPrivateIpAddress: + title: 是否私网ip + value: false + component: + name: a-switch + vModel: checked + placeholder: 集群连接端点是否是私网ip + helper: 如果您当前certd运行在同一个私网下,可以选择是。 + required: true + order: 0 + skipTLSVerify: + title: 忽略证书校验 + required: false + helper: 是否忽略证书校验 + component: + name: a-switch + vModel: checked + order: 0 + createOnNotFound: + title: Secret自动创建 + helper: 如果Secret不存在,则创建 + value: false + component: + name: a-switch + vModel: checked + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-aliyun/plugin/deploy-to-ack/index.js diff --git a/packages/ui/certd-server/metadata/deploy_HostDeployToIIS.yaml b/packages/ui/certd-server/metadata/deploy_HostDeployToIIS.yaml new file mode 100644 index 000000000..bd25a408f --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_HostDeployToIIS.yaml @@ -0,0 +1,77 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: HostDeployToIIS +title: IIS-部署到IIS站点 +icon: devicon:windows8 +group: host +needPlus: true +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: false + order: 0 + accessId: + title: 主机SSH授权 + component: + name: access-selector + type: ssh + required: true + order: 0 + siteNames: + title: 站点名称 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + action: onGetSiteList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 选择或手动输入网站名称 + order: 0 + pfxPassword: + title: 证书密码 + required: false + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-host/plugin/plugin-deploy-to-iis.js diff --git a/packages/ui/certd-server/metadata/deploy_K8sApply.yaml b/packages/ui/certd-server/metadata/deploy_K8sApply.yaml new file mode 100644 index 000000000..77c970f8f --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_K8sApply.yaml @@ -0,0 +1,52 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: K8sApply +title: K8S-Apply自定义yaml +icon: mdi:kubernetes +desc: apply自定义yaml到k8s +needPlus: true +group: panel +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + preOutput: + title: 前置任务输出 + helper: 请选择前置任务输出的内容 + component: + name: output-selector + from: + - '::' + required: false + order: 0 + accessId: + title: k8s授权 + helper: kubeconfig + component: + name: access-selector + type: k8s + required: true + order: 0 + yamlContent: + title: yaml + required: true + helper: >- + apply + yaml,模板变量:主域名=${mainDomain}、全部域名=${domains}、过期时间=${expiresTime}、证书PEM=${crt}、证书私钥=${key}、中间证书/CA证书=${ic}、前置任务输出=${preOutput} + component: + name: a-textarea + vModel: value + rows: 6 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/k8s/plugins/plugin-apply.js diff --git a/packages/ui/certd-server/metadata/deploy_K8sDeployToIngress.yaml b/packages/ui/certd-server/metadata/deploy_K8sDeployToIngress.yaml new file mode 100644 index 000000000..2b22a3cc8 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_K8sDeployToIngress.yaml @@ -0,0 +1,52 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: K8sDeployToIngress +title: K8S-Ingress 证书部署 +icon: mdi:kubernetes +desc: 部署证书到k8s的Ingress +needPlus: false +group: panel +input: + namespace: + title: 命名空间 + value: default + component: + placeholder: 命名空间 + required: true + order: 0 + ingressName: + title: IngressName + required: true + helper: Ingress名称,根据名称查找证书Secret,然后更新 + order: 0 + accessId: + title: k8s授权 + helper: kubeconfig + component: + name: access-selector + type: k8s + required: true + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + createOnNotFound: + title: Secret自动创建 + helper: 如果Secret不存在,则创建 + value: false + component: + name: a-switch + vModel: checked + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/k8s/plugins/plugin-ingress.js diff --git a/packages/ui/certd-server/metadata/deploy_K8sDeployToSecret.yaml b/packages/ui/certd-server/metadata/deploy_K8sDeployToSecret.yaml new file mode 100644 index 000000000..2f6c1d543 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_K8sDeployToSecret.yaml @@ -0,0 +1,67 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: K8sDeployToSecret +title: K8S-部署证书到Secret +icon: mdi:kubernetes +desc: 部署证书到k8s的secret +needPlus: false +group: panel +input: + namespace: + title: 命名空间 + value: default + component: + placeholder: 命名空间 + required: true + order: 0 + secretName: + title: 保密字典Id + component: + name: a-select + vModel: value + mode: tags + open: false + helper: 原本存储证书的secret的name + required: true + order: 0 + accessId: + title: k8s授权 + helper: kubeconfig + component: + name: access-selector + type: k8s + required: true + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + ingressName: + title: ingress名称 + required: false + helper: 填写之后会自动重启ingress + component: + name: a-select + vModel: value + mode: tags + open: false + order: 0 + createOnNotFound: + title: Secret自动创建 + helper: 如果Secret不存在,则创建 + value: false + component: + name: a-switch + vModel: checked + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/k8s/plugins/plugin-secret.js diff --git a/packages/ui/certd-server/metadata/deploy_KuocaiDeployToRCDN.yaml b/packages/ui/certd-server/metadata/deploy_KuocaiDeployToRCDN.yaml new file mode 100644 index 000000000..a7acb05fd --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_KuocaiDeployToRCDN.yaml @@ -0,0 +1,59 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: KuocaiDeployToRCDN +title: 括彩云-部署到括彩云CDN +icon: material-symbols:shield-outline +group: cdn +desc: 括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: 括彩云CDN授权 + helper: 括彩云CDN授权 + component: + name: access-selector + type: kuocaicdn + required: true + order: 0 + domains: + title: 域名列表 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: KuocaiDeployToCDNPlugin + action: onGetDomainList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 选择要部署证书的站点域名 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/kuocai/plugins/plugin-deploy-to-cdn.js diff --git a/packages/ui/certd-server/metadata/deploy_LeCDNUpdateCert.yaml b/packages/ui/certd-server/metadata/deploy_LeCDNUpdateCert.yaml new file mode 100644 index 000000000..2a97f974d --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_LeCDNUpdateCert.yaml @@ -0,0 +1,57 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: LeCDNUpdateCert +title: LeCDN-更新证书 +icon: material-symbols:shield-outline +group: cdn +needPlus: false +input: + accessId: + title: LeCDN授权 + component: + name: access-selector + type: lecdn + required: true + order: 0 + certIds: + title: 证书ID + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: LeCDNUpdateCert + action: onGetCertList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 选择要更新的证书id,注意域名是否与证书匹配 + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/lecdn/plugins/plugin-update-cert.js diff --git a/packages/ui/certd-server/metadata/deploy_LeCDNUpdateCertV2.yaml b/packages/ui/certd-server/metadata/deploy_LeCDNUpdateCertV2.yaml new file mode 100644 index 000000000..9c5042f2c --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_LeCDNUpdateCertV2.yaml @@ -0,0 +1,58 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: LeCDNUpdateCertV2 +title: LeCDN-更新证书V2 +desc: 支持新版本LeCDN +icon: material-symbols:shield-outline +group: cdn +needPlus: false +input: + accessId: + title: LeCDN授权 + component: + name: access-selector + type: lecdn + required: true + order: 0 + certIds: + title: 证书ID + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: LeCDNUpdateCertV2 + action: onGetCertList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 选择要更新的证书id,注意域名是否与证书匹配 + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/lecdn/plugins/plugin-update-cert-v2.js diff --git a/packages/ui/certd-server/metadata/deploy_LuckyUpdateCert.yaml b/packages/ui/certd-server/metadata/deploy_LuckyUpdateCert.yaml new file mode 100644 index 000000000..de0a39461 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_LuckyUpdateCert.yaml @@ -0,0 +1,74 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: LuckyUpdateCert +title: lucky-更新Lucky证书 +icon: svg:icon-lucky +group: panel +needPlus: true +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: false + order: 0 + accessId: + title: Lucky授权 + component: + name: access-selector + type: lucky + required: true + order: 0 + certList: + title: Lucky证书 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: LuckyUpdateCert + action: onGetCertList + search: false + pager: false + watches: + - certDomains + - accessId + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 要更新的Lucky证书 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/lucky/plugins/plugin-upload.js diff --git a/packages/ui/certd-server/metadata/deploy_MaoyunDeployToCdn.yaml b/packages/ui/certd-server/metadata/deploy_MaoyunDeployToCdn.yaml new file mode 100644 index 000000000..a2453a75b --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_MaoyunDeployToCdn.yaml @@ -0,0 +1,73 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: MaoyunDeployToCdn +title: Maoyun-更新猫云CDN证书 +icon: svg:icon-lucky +group: cdn +needPlus: true +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: false + order: 0 + accessId: + title: Maoyun授权 + component: + name: access-selector + type: maoyun + required: true + order: 0 + domainList: + title: CDN加速域名 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + action: onGetDomainList + search: false + pager: false + watches: + - certDomains + - accessId + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 要部署证书的域名 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/maoyun/plugins/plugin-deploy-to-cdn.js diff --git a/packages/ui/certd-server/metadata/deploy_PleskDeploySiteCert.yaml b/packages/ui/certd-server/metadata/deploy_PleskDeploySiteCert.yaml new file mode 100644 index 000000000..a970deb13 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_PleskDeploySiteCert.yaml @@ -0,0 +1,80 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: PleskDeploySiteCert +title: Plesk-部署Plesk网站证书 +icon: svg:icon-plesk +group: panel +needPlus: true +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: false + order: 0 + accessId: + title: Plesk授权 + component: + name: access-selector + type: plesk + required: true + order: 0 + siteDomainIds: + title: 网站域名列表 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + action: onGetDomainList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 选择要更新的站点域名,注意域名是否与证书匹配 + order: 0 + clearUnused: + title: 删除未使用证书 + component: + name: a-switch + vModel: checked + required: false + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/plesk/plugins/plugin-deploy-cert.js diff --git a/packages/ui/certd-server/metadata/deploy_PleskRefreshCert.yaml b/packages/ui/certd-server/metadata/deploy_PleskRefreshCert.yaml new file mode 100644 index 000000000..62d32d9bb --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_PleskRefreshCert.yaml @@ -0,0 +1,74 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: PleskRefreshCert +title: Plesk-更新证书 +icon: svg:icon-plesk +desc: 不会创建新证书记录,直接更新旧的证书 +group: panel +needPlus: true +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: false + order: 0 + accessId: + title: Plesk授权 + component: + name: access-selector + type: plesk + required: true + order: 0 + domainCertIds: + title: 证书列表 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + action: onGetCertList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 选择要更新的站点域名,注意域名是否与证书匹配 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/plesk/plugins/plugin-refresh-cert.js diff --git a/packages/ui/certd-server/metadata/deploy_QiniuDeployCertToOSS.yaml b/packages/ui/certd-server/metadata/deploy_QiniuDeployCertToOSS.yaml new file mode 100644 index 000000000..017265366 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_QiniuDeployCertToOSS.yaml @@ -0,0 +1,37 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: QiniuDeployCertToOSS +title: 七牛云-部署证书至OSS +icon: svg:icon-qiniuyun +group: qiniu +desc: 自动部署域名证书至七牛云KODO,注意是自定义源站域名,不是CDN域名 +input: + domainName: + title: 自定义源站域名 + helper: 你在七牛云上配置的OSS域名,比如:certd.handsfree.work + required: true + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书,或者上传到七牛云的证书id + component: + name: output-selector + from: + - ':cert:' + - QiniuCertUpload + required: true + order: 0 + accessId: + title: Access授权 + helper: 七牛云授权 + component: + name: access-selector + type: qiniu + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-qiniu/plugin/plugin-deploy-to-oss.js diff --git a/packages/ui/certd-server/metadata/deploy_SafelineDeployToWebsitePlugin.yaml b/packages/ui/certd-server/metadata/deploy_SafelineDeployToWebsitePlugin.yaml new file mode 100644 index 000000000..1b56450ba --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_SafelineDeployToWebsitePlugin.yaml @@ -0,0 +1,76 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: SafelineDeployToWebsitePlugin +title: 雷池-更新证书 +icon: svg:icon-safeline +desc: 更新长亭雷池WAF的证书 +group: panel +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + certDomains: + title: 当前证书域名 + component: + name: cert-domains-getter + mergeScript: |2- + + return { + component:{ + inputKey: ctx.compute(({form})=>{ + return form.cert + }), + } + } + + template: false + required: false + order: 0 + accessId: + title: 雷池授权 + helper: 长亭雷池授权 + component: + name: access-selector + type: safeline + required: true + order: 0 + certIds: + title: 雷池证书 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: SafelineDeployToWebsitePlugin + action: onGetCertIds + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 请选择要更新的雷池的证书Id,需要先手动到雷池控制台上传一次 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/safeline/plugins/deploy-to-website.js diff --git a/packages/ui/certd-server/metadata/deploy_SynologyDeployToPanel.yaml b/packages/ui/certd-server/metadata/deploy_SynologyDeployToPanel.yaml new file mode 100644 index 000000000..219e7e4e2 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_SynologyDeployToPanel.yaml @@ -0,0 +1,43 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: SynologyDeployToPanel +title: 群晖-部署证书到群晖面板 +icon: simple-icons:synology +group: panel +desc: Synology,支持6.x以上版本 +needPlus: true +input: + certName: + title: 群晖证书描述 + component: + name: a-input + vModel: value + placeholder: 群晖证书描述 + required: false + helper: |- + 在群晖证书管理页面里面,选择证书,点击操作,给证书设置描述,然后填写到这里 + 如果不填,则覆盖更新全部证书 + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: 群晖授权 + helper: 群晖登录授权,请确保账户是管理员用户组 + component: + name: access-selector + type: synology + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/synology/plugins/plugin-deploy-to-panel.js diff --git a/packages/ui/certd-server/metadata/deploy_UniCloudDeployToSpace.yaml b/packages/ui/certd-server/metadata/deploy_UniCloudDeployToSpace.yaml new file mode 100644 index 000000000..e814a85fa --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_UniCloudDeployToSpace.yaml @@ -0,0 +1,62 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: UniCloudDeployToSpace +title: uniCloud-部署到服务空间 +icon: material-symbols:shield-outline +group: panel +desc: 部署到服务空间 +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: uniCloud授权 + helper: uniCloud授权 + component: + name: access-selector + type: unicloud + required: true + order: 0 + spaceId: + title: 服务空间ID + component: + name: a-input + vModel: value + helper: spaceId + order: 0 + provider: + title: 空间提供商 + component: + name: a-select + vModel: value + options: + - label: 阿里云 + value: aliyun + - label: 腾讯云 + value: tencent + - label: 支付宝云 + value: alipay + helper: 空间提供商 + order: 0 + domains: + title: 空间域名 + component: + name: a-select + vModel: value + mode: tags + open: false + helper: 空间域名 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/unicloud/plugins/plugin-deploy-to-space.js diff --git a/packages/ui/certd-server/metadata/deploy_UploadCertToFTP.yaml b/packages/ui/certd-server/metadata/deploy_UploadCertToFTP.yaml new file mode 100644 index 000000000..189c656b1 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_UploadCertToFTP.yaml @@ -0,0 +1,169 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: UploadCertToFTP +title: FTP-上传证书到FTP +icon: mdi:folder-upload-outline +group: host +desc: 将证书上传到FTP服务器 +needPlus: false +input: + certType: + title: 证书格式 + helper: 要部署的证书格式,支持pem、pfx、der、jks + component: + name: a-select + options: + - value: pem + label: pem,Nginx等大部分应用 + - value: pfx + label: pfx,一般用于IIS + - value: der + label: der,一般用于Apache + - value: jks + label: jks,一般用于JAVA应用 + - value: one + label: 一体化证书,证书和私钥合并为一个pem文件 + required: true + order: 0 + crtPath: + title: PEM证书保存路径 + helper: 需要有写入权限,路径要包含文件名 + component: + placeholder: /test/fullchain.pem + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.certType === 'pem'; + }) + } + + required: true + rules: + - type: filepath + order: 0 + keyPath: + title: 私钥保存路径 + helper: 需要有写入权限,路径要包含文件名 + component: + placeholder: /test/privatekey.pem + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.certType === 'pem'; + }) + } + + required: true + rules: + - type: filepath + order: 0 + icPath: + title: 中间证书保存路径 + helper: 需要有写入权限,路径要包含文件名 + component: + placeholder: /test/immediate.pem + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.certType === 'pem'; + }) + } + + rules: + - type: filepath + order: 0 + pfxPath: + title: PFX证书保存路径 + helper: 需要有写入权限,路径要包含文件名 + component: + placeholder: /test/cert.pfx + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.certType === 'pfx'; + }) + } + + required: true + rules: + - type: filepath + order: 0 + derPath: + title: DER证书保存路径 + helper: |- + 需要有写入权限,路径要包含文件名 + .der和.cer是相同的东西,改个后缀名即可 + component: + placeholder: /test/cert.der 或 /test/cert.cer + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.certType === 'der'; + }) + } + + required: true + rules: + - type: filepath + order: 0 + jksPath: + title: jks证书保存路径 + helper: 证书原本的保存路径,路径要包含文件名 + component: + placeholder: /test/javaapp/cert.jks + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.certType === 'jks'; + }) + } + + required: true + rules: + - type: filepath + order: 0 + onePath: + title: 一体化证书保存路径 + helper: 证书原本的保存路径,路径要包含文件名 + component: + placeholder: /app/ssl/one.pem + mergeScript: |2- + + return { + show: ctx.compute(({form})=>{ + return form.certType === 'one'; + }) + } + + required: true + rules: + - type: filepath + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: FTP授权 + component: + name: access-selector + type: ftp + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/ftp/plugins/plugin-upload-to-ftp.js diff --git a/packages/ui/certd-server/metadata/deploy_YidunDeployToCDN.yaml b/packages/ui/certd-server/metadata/deploy_YidunDeployToCDN.yaml new file mode 100644 index 000000000..c5d75a98c --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_YidunDeployToCDN.yaml @@ -0,0 +1,46 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: YidunDeployToCDN +title: 易盾-部署到易盾DCDN +icon: material-symbols:shield-outline +group: cdn +desc: 主要是防御,http://user.yiduncdn.com/ +needPlus: false +input: + certId: + title: 证书ID + component: + name: a-input-number + vModel: value + helper: 证书ID,在证书管理页面查看,每条记录都有证书id + order: 0 + domain: + title: 网站域名 + component: + name: a-input + vModel: value + helper: 网站域名和证书ID选填其中一个,填了证书ID,则忽略网站域名 + order: 0 + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: 易盾授权 + helper: 易盾CDN授权 + component: + name: access-selector + type: yidun + required: true + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/yidun/plugins/plugin-deploy-to-cdn.js diff --git a/packages/ui/certd-server/metadata/deploy_YidunDeployToRCDN.yaml b/packages/ui/certd-server/metadata/deploy_YidunDeployToRCDN.yaml new file mode 100644 index 000000000..3d4f51ea3 --- /dev/null +++ b/packages/ui/certd-server/metadata/deploy_YidunDeployToRCDN.yaml @@ -0,0 +1,59 @@ +showRunStrategy: false +default: + strategy: + runStrategy: 1 +name: YidunDeployToRCDN +title: 易盾-部署到易盾RCDN +icon: material-symbols:shield-outline +group: cdn +desc: 易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) +needPlus: false +input: + cert: + title: 域名证书 + helper: 请选择前置任务输出的域名证书 + component: + name: output-selector + from: + - ':cert:' + required: true + order: 0 + accessId: + title: 易盾RCDN授权 + helper: 易盾RCDN授权 + component: + name: access-selector + type: yidunrcdn + required: true + order: 0 + domains: + title: 域名列表 + component: + name: remote-select + vModel: value + mode: tags + type: plugin + typeName: YidunDeployToRCDNPlugin + action: onGetDomainList + search: false + pager: false + watches: + - certDomains + - accessId + required: true + mergeScript: |2- + + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + + helper: 选择要部署证书的站点域名 + order: 0 +output: {} +pluginType: deploy +type: builtIn +scriptFilePath: ../../../plugins/plugin-plus/yidun/plugins/plugin-deploy-to-rcdn.js diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index 802f39f0f..20aeddb98 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -111,7 +111,7 @@ "openid-client": "^6.8.1", "otplib": "^12.0.1", "pg": "^8.12.0", - "psl": "^1.9.0", + "psl": "^1.15.0", "punycode.js": "^2.3.1", "qiniu": "^7.12.0", "qrcode": "^1.5.4", @@ -126,7 +126,11 @@ "tencentcloud-sdk-nodejs": "^4.1.112", "typeorm": "^0.3.20", "uuid": "^10.0.0", - "xml2js": "^0.6.2" + "xml2js": "^0.6.2", + "@google-cloud/publicca": "^1.3.0", + "jsrsasign": "^11.1.0", + "ssh2": "^1.17.0", + "@alicloud/openapi-util": "^0.3.2" }, "devDependencies": { "@midwayjs/mock": "3.20.11", @@ -138,7 +142,6 @@ "@types/mocha": "^10.0.1", "@types/node": "^18", "@types/nodemailer": "^6.4.8", - "@types/ssh2": "^1.15.0", "c8": "^10.1.2", "mocha": "^10.2.0", "prettier": "^2.8.8", diff --git a/packages/ui/certd-server/src/modules/auto/auto-b-load-plugins.ts b/packages/ui/certd-server/src/modules/auto/auto-b-load-plugins.ts index ba76300e0..8296438ff 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-b-load-plugins.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-b-load-plugins.ts @@ -11,11 +11,15 @@ export class AutoBLoadPlugins { @Init() async init() { - logger.info('加载插件开始'); - await this.pluginService.registerFromLocal("./metadata") + logger.info(`加载插件开始,加载模式:${process.env.certd_plugin_loadmode}`); + if (process.env.certd_plugin_loadmode === "metadata") { + await this.pluginService.registerFromLocal("./metadata") + }else{ + await import("../../plugins/index.js") + } // await import("../../plugins/index.js") await this.pluginService.registerFromDb() - logger.info('加载插件完成'); + logger.info(`加载插件完成,加载模式:${process.env.certd_plugin_loadmode}`); } } diff --git a/packages/ui/certd-server/src/modules/basic/sms/aliyun-sms.ts b/packages/ui/certd-server/src/modules/basic/sms/aliyun-sms.ts index 0fee0ac8b..5ab712df5 100644 --- a/packages/ui/certd-server/src/modules/basic/sms/aliyun-sms.ts +++ b/packages/ui/certd-server/src/modules/basic/sms/aliyun-sms.ts @@ -1,6 +1,6 @@ import { logger } from '@certd/basic'; import { ISmsService, PluginInputs, SmsPluginCtx } from './api.js'; -import { AliyunAccess, AliyunClient } from '@certd/plugin-lib'; +import { AliyunAccess, AliyunClient } from '../../../plugins/plugin-lib/aliyun/index.js'; export type AliyunSmsConfig = { accessId: string; diff --git a/packages/ui/certd-server/src/modules/basic/sms/tencent-sms.ts b/packages/ui/certd-server/src/modules/basic/sms/tencent-sms.ts index 4032aff48..2c5b31a61 100644 --- a/packages/ui/certd-server/src/modules/basic/sms/tencent-sms.ts +++ b/packages/ui/certd-server/src/modules/basic/sms/tencent-sms.ts @@ -1,5 +1,5 @@ +import { TencentAccess } from '../../../plugins/plugin-lib/tencent/access.js'; import {ISmsService, PluginInputs, SmsPluginCtx} from './api.js'; -import {TencentAccess} from "@certd/plugin-lib"; export type TencentSmsConfig = { accessId: string; diff --git a/packages/ui/certd-server/src/modules/basic/sms/yfy-sms.ts b/packages/ui/certd-server/src/modules/basic/sms/yfy-sms.ts index 1f6232b5e..743468271 100644 --- a/packages/ui/certd-server/src/modules/basic/sms/yfy-sms.ts +++ b/packages/ui/certd-server/src/modules/basic/sms/yfy-sms.ts @@ -1,6 +1,6 @@ import { http, utils } from '@certd/basic'; import { ISmsService, PluginInputs, SmsPluginCtx } from './api.js'; -import { YfySmsAccess } from '@certd/plugin-plus'; +import { YfySmsAccess } from '../../../plugins/plugin-plus/yidun/access-sms.js'; export type YfySmsConfig = { accessId: string; diff --git a/packages/ui/certd-server/src/plugins/index.ts b/packages/ui/certd-server/src/plugins/index.ts index 651c61b27..d4d275ee5 100644 --- a/packages/ui/certd-server/src/plugins/index.ts +++ b/packages/ui/certd-server/src/plugins/index.ts @@ -1,5 +1,3 @@ -export * from '@certd/plugin-cert'; -export * from '@certd/plugin-plus'; export * from './plugin-aliyun/index.js'; export * from './plugin-tencent/index.js'; export * from './plugin-host/index.js'; @@ -43,3 +41,6 @@ export * from './plugin-cmcc/index.js' export * from './plugin-template/index.js' export * from './plugin-ucloud/index.js' export * from './plugin-goedge/index.js' +export * from './plugin-lib/index.js' +export * from './plugin-plus/index.js' +export * from './plugin-cert/index.js' \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-admin/plugin-db-backup.ts b/packages/ui/certd-server/src/plugins/plugin-admin/plugin-db-backup.ts index 50aafb168..2420734ef 100644 --- a/packages/ui/certd-server/src/plugins/plugin-admin/plugin-db-backup.ts +++ b/packages/ui/certd-server/src/plugins/plugin-admin/plugin-db-backup.ts @@ -2,10 +2,11 @@ import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipel import fs from "fs"; import path from "path"; import dayjs from "dayjs"; -import { AbstractPlusTaskPlugin } from "@certd/plugin-plus"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; import JSZip from "jszip"; import * as os from "node:os"; -import { OssClientContext, ossClientFactory, OssClientRemoveByOpts, SshAccess, SshClient } from "@certd/plugin-lib"; +import { OssClientContext, ossClientFactory, OssClientRemoveByOpts} from "../plugin-lib/oss/index.js"; +import { SshAccess, SshClient } from "../plugin-lib/ssh/index.js"; import { pipeline } from "stream/promises"; const defaultBackupDir = "certd_backup"; const defaultFilePrefix = "db_backup"; diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliesa-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliesa-dns-provider.ts index 16f4b218d..4c26aed77 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliesa-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliesa-dns-provider.ts @@ -1,7 +1,7 @@ import { IAccessService } from '@certd/pipeline'; import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert'; +import { AliesaAccess, AliyunAccess, AliyunClientV2 } from '../../plugin-lib/aliyun/index.js'; -import { AliesaAccess, AliyunAccess, AliyunClientV2 } from '@certd/plugin-lib'; @IsDnsProvider({ name: 'aliesa', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts index 63b4bcfe6..3d1fdaae5 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts @@ -1,6 +1,7 @@ import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert'; +import { AliyunAccess } from '../../plugin-lib/aliyun/access/aliyun-access.js'; +import { AliyunClient } from '../../plugin-lib/aliyun/index.js'; -import { AliyunAccess, AliyunClient } from '@certd/plugin-lib'; @IsDnsProvider({ name: 'aliyun', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/index.ts index 096ebbc5d..67dfdda2d 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/index.ts @@ -1,2 +1,2 @@ export * from './dns-provider/index.js'; -export * from './plugin/index.js'; +export * from './plugin/index.js'; \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack/index.ts new file mode 100644 index 000000000..5bd447f08 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack/index.ts @@ -0,0 +1,277 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { utils } from "@certd/basic"; + +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { AliyunAccess, AliyunClient } from "../../../plugin-lib/aliyun/index.js"; + +@IsTaskPlugin({ + name: "DeployCertToAliyunAck", + title: "阿里云-部署到Ack", + icon: "svg:icon-aliyun", + desc: "部署到阿里云Ack集群Ingress等通过Secret管理证书的应用", + group: pluginGroups.aliyun.key, + needPlus: false, + input: {}, + output: {}, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class DeployCertToAliyunAckPlugin extends AbstractTaskPlugin { + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + @TaskInput({ + title: "Access授权", + helper: "阿里云授权AccessKeyId、AccessKeySecret", + component: { + name: "access-selector", + type: "aliyun", + }, + required: true, + }) + accessId!: string; + + @TaskInput({ + title: "大区", + component: { + name: "a-auto-complete", + vModel: "value", + options: [ + { value: "cn-qingdao", label: "华北1(青岛)" }, + { value: "cn-beijing", label: "华北2(北京)" }, + { value: "cn-zhangjiakou", label: "华北3(张家口)" }, + { value: "cn-huhehaote", label: "华北5(呼和浩特)" }, + { value: "cn-wulanchabu", label: "华北6(乌兰察布)" }, + { value: "cn-hangzhou", label: "华东1(杭州)" }, + { value: "cn-shanghai", label: "华东2(上海)" }, + { value: "cn-shenzhen", label: "华南1(深圳)" }, + { value: "cn-guangzhou", label: "华南3(广州)" }, + { value: "ap-southeast-2", label: "澳大利亚(悉尼)" }, + { value: "ap-southeast-3", label: "马来西亚(吉隆坡)" }, + { value: "ap-northeast-1", label: "日本(东京)" }, + { value: "cn-chengdu", label: "西南1(成都)" }, + { value: "ap-southeast-1", label: "新加坡" }, + { value: "ap-southeast-5", label: "印度尼西亚(雅加达)" }, + { value: "cn-hongkong", label: "中国香港" }, + { value: "eu-central-1", label: "德国(法兰克福)" }, + { value: "us-east-1", label: "美国(弗吉尼亚)" }, + { value: "us-west-1", label: "美国(硅谷)" }, + { value: "eu-west-1", label: "英国(伦敦)" }, + { value: "me-east-1", label: "阿联酋(迪拜)" }, + //金融云 + { value: "cn-beijing-finance-1", label: "华北2 金融云(邀测)" }, + { value: "cn-hangzhou-finance", label: "华东1 金融云" }, + { value: "cn-shanghai-finance-1", label: "华东2 金融云" }, + { value: "cn-shenzhen-finance-1", label: "华南1 金融云" }, + ], + placeholder: "集群所属大区", + }, + required: true, + }) + regionId!: string; + + @TaskInput({ + title: "集群id", + component: { + placeholder: "集群id", + }, + required: true, + }) + clusterId!: string; + + @TaskInput({ + title: "保密字典Id", + component: { + placeholder: "保密字典Id", + }, + helper: "原本存储证书的secret的name", + required: true, + }) + secretName!: string | string[]; + + @TaskInput({ + title: "命名空间", + value: "default", + component: { + placeholder: "命名空间", + }, + required: true, + }) + namespace = "default"; + + @TaskInput({ + title: "是否私网ip", + value: false, + component: { + name: "a-switch", + vModel: "checked", + placeholder: "集群连接端点是否是私网ip", + }, + helper: "如果您当前certd运行在同一个私网下,可以选择是。", + required: true, + }) + isPrivateIpAddress!: boolean; + + @TaskInput({ + title: "忽略证书校验", + required: false, + helper: "是否忽略证书校验", + component: { + name: "a-switch", + vModel: "checked", + }, + }) + skipTLSVerify!: boolean; + + @TaskInput({ + title: "Secret自动创建", + helper: "如果Secret不存在,则创建", + value: false, + component: { + name: "a-switch", + vModel: "checked", + }, + }) + createOnNotFound: boolean; + + K8sClient: any; + async onInstance() { + const sdk = await import("@certd/lib-k8s"); + this.K8sClient = sdk.K8sClient; + } + async execute(): Promise { + this.logger.info("开始部署证书到阿里云Ack"); + const { regionId, clusterId, isPrivateIpAddress, cert } = this; + const access = (await this.getAccess(this.accessId)) as AliyunAccess; + const client = await this.getClient(access, regionId); + const kubeConfigStr = await this.getKubeConfig(client, clusterId, isPrivateIpAddress); + + this.logger.info("kubeconfig已成功获取"); + const k8sClient = new this.K8sClient({ + kubeConfigStr, + logger: this.logger, + skipTLSVerify: this.skipTLSVerify, + }); + await this.patchCertSecret({ cert, k8sClient }); + + await utils.sleep(5000); // 停留5秒,等待secret部署完成 + + try { + await this.restartIngress({ k8sClient }); + } catch (e) { + this.logger.warn(`重启ingress失败:${e.message}`); + } + } + + async restartIngress(options: { k8sClient: any }) { + const { k8sClient } = options; + const { namespace } = this; + + const body = { + metadata: { + labels: { + certd: this.appendTimeSuffix("certd"), + }, + }, + }; + const ingressList = await k8sClient.getIngressList({ namespace }); + this.logger.info("ingressList:", ingressList); + if (!ingressList || !ingressList.items) { + return; + } + const ingressNames = ingressList.items + .filter((item: any) => { + if (!item.spec.tls) { + return false; + } + for (const tls of item.spec.tls) { + if (tls.secretName === this.secretName) { + return true; + } + } + return false; + }) + .map((item: any) => { + return item.metadata.name; + }); + for (const ingress of ingressNames) { + await k8sClient.patchIngress({ namespace, ingressName: ingress, body, createOnNotFound: this.createOnNotFound }); + this.logger.info(`ingress已重启:${ingress}`); + } + } + + async patchCertSecret(options: { cert: CertInfo; k8sClient: any }) { + const { cert, k8sClient } = options; + const crt = cert.crt; + const key = cert.key; + const crtBase64 = Buffer.from(crt).toString("base64"); + const keyBase64 = Buffer.from(key).toString("base64"); + + const { namespace, secretName } = this; + + const body = { + data: { + "tls.crt": crtBase64, + "tls.key": keyBase64, + }, + metadata: { + labels: { + certd: this.appendTimeSuffix("certd"), + }, + }, + }; + let secretNames: any = secretName; + if (typeof secretName === "string") { + secretNames = [secretName]; + } + for (const secret of secretNames) { + await k8sClient.patchSecret({ namespace, secretName: secret, body }); + this.logger.info(`cert secret已更新: ${secret}`); + } + } + + async getClient(aliyunProvider: any, regionId: string) { + const client = new AliyunClient({ logger: this.logger, useROAClient: true }); + await client.init({ + accessKeyId: aliyunProvider.accessKeyId, + accessKeySecret: aliyunProvider.accessKeySecret, + endpoint: `https://cs.${regionId}.aliyuncs.com`, + apiVersion: "2015-12-15", + }); + return client; + } + + async getKubeConfig(client: any, clusterId: string, isPrivateIpAddress = false) { + const httpMethod = "GET"; + const uriPath = `/k8s/${clusterId}/user_config`; + const queries = { + PrivateIpAddress: isPrivateIpAddress, + TemporaryDurationMinutes: 15, + }; + const body = {}; + const headers = { + "Content-Type": "application/json", + }; + const requestOption = {}; + + try { + const res = await client.request(httpMethod, uriPath, queries, body, headers, requestOption); + return res.config; + } catch (e) { + console.error("请求出错:", e); + throw e; + } + } +} + +new DeployCertToAliyunAckPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts index 06e309754..ae94d712c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts @@ -1,13 +1,11 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import { - AliyunAccess, - AliyunClient, - AliyunClientV2, - AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AliyunAccess, AliyunClientV2 } from "../../../plugin-lib/aliyun/access/index.js"; +import { AliyunClient, AliyunSslClient } from "../../../plugin-lib/aliyun/lib/index.js"; @IsTaskPlugin({ name: "AliyunDeployCertToALB", diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-all/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-all/index.ts new file mode 100644 index 000000000..f3f1e749c --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-all/index.ts @@ -0,0 +1,307 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; +import { AliyunSslClient } from "../../../plugin-lib/aliyun/lib/ssl-client.js"; + +@IsTaskPlugin({ + name: "AliyunDeployCertToAll", + title: "阿里云-部署至任意云资源", + icon: "svg:icon-aliyun", + group: pluginGroups.aliyun.key, + desc: "【不建议使用】需要消耗阿里云自动部署次数,支持SLB、LIVE、webHosting、VOD、CR、DCDN、DDoS、CDN、ALB、APIGateway、FC、GA、MSE、NLB、OSS、SAE、WAF等云产品", + needPlus: false, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class AliyunDeployCertToAll extends AbstractTaskPlugin { + @TaskInput({ + title: "域名证书", + helper: "请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量", + component: { + name: "output-selector", + from: [...CertApplyPluginNames, "uploadCertToAliyun"], + }, + required: true, + }) + cert!: CertInfo | number; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + @TaskInput({ + title: "接入点", + helper: "不会选就按默认", + value: "cas.aliyuncs.com", + component: { + name: "a-select", + options: [ + { value: "cas.aliyuncs.com", label: "中国大陆" }, + { value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" }, + { value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" }, + ], + }, + required: true, + }) + endpoint!: string; + + @TaskInput({ + title: "Access授权", + helper: "阿里云授权AccessKeyId、AccessKeySecret", + component: { + name: "access-selector", + type: "aliyun", + }, + required: true, + }) + accessId!: string; + + /** + * SLB:传统型负载均衡 CLB(仅中国站) + * LIVE:视频直播(仅中国站) + * webHosting:云虚拟主机(仅中国站) + * VOD:视频点播(仅中国站) + * CR:容器镜像服务(仅中国站) + * DCDN:全站加速 + * DDoS:DDos 防护 + * CDN:内容分发网络 + * ALB:应用负载均衡 + * APIGateway:API 网关 + * FC:函数计算 + * GA:全球加速 + * MSE:微服务引擎 + * NLB:网络型负载均衡 + * OSS:对象存储 + * SAE:Serverless 应用引擎 + * WAF:Web 应用防火墙 + */ + @TaskInput({ + title: "云产品类型", + helper: "请选择云产品类型", + component: { + name: "a-select", + vModel: "value", + options: [ + { value: "SLB", label: "SLB-传统型负载均衡 CLB(仅中国站)" }, + { value: "LIVE", label: "LIVE-视频直播(仅中国站)" }, + { value: "webHosting", label: "webHosting-云虚拟主机(仅中国站)" }, + { value: "VOD", label: "VOD-视频点播(仅中国站)" }, + { value: "CR", label: "CR-容器镜像服务(仅中国站)" }, + { value: "DCDN", label: "DCDN-全站加速" }, + { value: "DDoS", label: "DDos 防护" }, + { value: "CDN", label: "CDN-内容分发网络" }, + { value: "ALB", label: "ALB-应用负载均衡" }, + { value: "APIGateway", label: "APIGateway-API 网关" }, + { value: "FC", label: "FC-函数计算" }, + { value: "GA", label: "GA-全球加速" }, + { value: "MSE", label: "MSE-微服务引擎" }, + { value: "NLB", label: "NLB-网络型负载均衡" }, + { value: "OSS", label: "OSS-对象存储" }, + { value: "SAE", label: "SAE-Serverless应用引擎" }, + { value: "WAF", label: "WAF-Web应用防火墙" }, + ], + }, + required: true, + }) + cloudProduct!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "要部署证书的云产品", + helper: "请选择要部署证书的云产品,注意:新创建的云产品资源可能需要过1-2小时才会在此处显示", + typeName: "AliyunDeployCertToAll", + action: AliyunDeployCertToAll.prototype.onGetProductList.name, + watches: ["cloudProduct", "accessId"], + }) + ) + productIds!: string[]; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "联系人", + helper: "请选择联系人,如果没有,需要先到[阿里云控制台创建联系人](https://yundun.console.aliyun.com/?p=cas#/informationManagement/person/)", + typeName: "AliyunDeployCertToAll", + action: AliyunDeployCertToAll.prototype.onGetContactList.name, + }) + ) + contactIds!: string[]; + + @TaskInput({ + title: "检查超时时间", + helper: "检查部署任务超时时间,单位分钟", + value: 10, + component: { + name: "a-input-number", + vModel: "value", + }, + required: true, + }) + checkTimeout!: number; + + async onInstance() {} + + async execute(): Promise { + this.logger.info("开始部署证书到阿里云"); + const access = await this.getAccess(this.accessId); + const sslClient = new AliyunSslClient({ + access, + logger: this.logger, + endpoint: this.endpoint, + }); + + // + let certId: any = this.cert; + if (typeof this.cert === "object") { + certId = await sslClient.uploadCert({ + name: this.appendTimeSuffix("certd"), + cert: this.cert, + }); + } + + const jobId = await this.createDeployJob(sslClient, certId); + + await this.updateJobStatus(sslClient, jobId, "scheduling"); + + this.logger.info("开始检查部署任务执行结果"); + const startTime = Date.now(); + while (Date.now() < startTime + this.checkTimeout * 60 * 1000) { + this.checkSignal(); + await this.ctx.utils.sleep(10000); + let res: any = {}; + try { + res = await this.getJobDetail(sslClient, jobId); + } catch (e: any) { + this.logger.error(e); + break; + } + const status = res.Status; + if (status == "success") { + this.logger.info("部署任务执行成功:", status); + return; + } else if (status == "error") { + this.logger.error(`部署任务执行失败,请前往 https://yundun.console.aliyun.com/?p=cas#/deployDetail/user/${jobId} 查看失败原因: `, res); + + throw new Error("部署任务执行失败,"); + } else { + /** + * pending:待执行 + * editing:编辑中 + * scheduling:调度中 + * processing:部署中 + * error:部署失败 + * success:部署成功 + */ + this.logger.info("部署任务正在执行中: ", status); + } + } + + throw new Error("部署任务执行超时,请手动检查任务状态"); + } + + async updateJobStatus(sslClient: AliyunSslClient, jobId: string, status: string) { + const params = { + JobId: jobId, + Status: status, + }; + const requestOption = { + method: "POST", + formatParams: false, + }; + const res = await sslClient.doRequest("UpdateDeploymentJobStatus", params, requestOption); + this.logger.info("部署任务开始执行,部署需要时间,RequestId=", res.RequestId); + } + + async onGetProductList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + const access = await this.getAccess(this.accessId); + const sslClient = new AliyunSslClient({ + access, + logger: this.logger, + endpoint: this.endpoint, + }); + + if (!this.cloudProduct) { + throw new Error("请选择云产品类型"); + } + + const res = await sslClient.getResourceList({ + cloudProduct: this.cloudProduct, + }); + if (!res?.Data || res?.Data.length === 0) { + throw new Error("没有找到对应类型的云资源"); + } + const options = res.Data.map((item: any) => { + return { + label: `${item.Domain}<${item.Id}>`, + value: item.Id, + title: `${item.CloudProduct}:${item.CertName || "证书未命名"}`, + domain: item.Domain, + }; + }); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } + + async onGetContactList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + const access = await this.getAccess(this.accessId); + const sslClient = new AliyunSslClient({ + access, + logger: this.logger, + endpoint: this.endpoint, + }); + const res = await sslClient.getContactList(); + /* + "Email": "@qq.com", + "EmailStatus": 0, + "MobileStatus": 0, + "ContactId": 378992, + "Mobile": "", + "Name": "" + */ + if (!res?.ContactList || res?.ContactList.length === 0) { + throw new Error("没有找到联系人"); + } + return res.ContactList.map((item: any) => { + return { + label: `${item.Name}<${item.Email}:${item.ContactId}>`, + value: item.ContactId, + }; + }); + } + + async getJobDetail(sslClient: AliyunSslClient, jobId: number) { + const params = { + JobId: jobId, + }; + + const requestOption = { + method: "POST", + formatParams: false, + }; + + return await sslClient.doRequest("DescribeDeploymentJob", params, requestOption); + } + + private async createDeployJob(sslClient: AliyunSslClient, certId: any) { + const res = await sslClient.createDeploymentJob({ + name: "自动部署证书(By Certd)", + jobType: "user", + contactIds: this.contactIds, + resourceIds: this.productIds, + certIds: [certId], + }); + + const jobId = res.JobId; + this.logger.info("部署任务创建成功: jobId=", jobId); + return jobId; + } +} + +new AliyunDeployCertToAll(); diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-apig/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-apig/index.ts index 8340dadd4..15d4bfe26 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-apig/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-apig/index.ts @@ -1,10 +1,10 @@ import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline'; import { - AliyunAccess, - AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; +import { AliyunSslClient } from "../../../plugin-lib/aliyun/lib/ssl-client.js"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import {optionsUtils} from "@certd/basic"; diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-apigateway/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-apigateway/index.ts index 0cce821e7..5053121fb 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-apigateway/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-apigateway/index.ts @@ -1,7 +1,8 @@ import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline'; -import {AliyunAccess, createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib"; import {CertApplyPluginNames, CertInfo} from '@certd/plugin-cert'; import {optionsUtils} from "@certd/basic"; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; @IsTaskPlugin({ name: 'DeployCertToAliyunApiGateway', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts index 962b5539c..a5ec16f65 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts @@ -1,7 +1,9 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; -import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; import { optionsUtils } from '@certd/basic'; import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert"; +import { AliyunClient, AliyunSslClient } from "../../../plugin-lib/aliyun/lib/index.js"; @IsTaskPlugin({ name: 'DeployCertToAliyunCDN', title: '阿里云-部署证书至CDN', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts index e64c25157..f96793b49 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts @@ -1,14 +1,15 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import dayjs from 'dayjs'; import { - AliyunAccess, - AliyunClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; + import { CertInfo } from '@certd/plugin-cert'; import { CertApplyPluginNames} from '@certd/plugin-cert'; import { optionsUtils } from "@certd/basic"; +import { AliyunClient } from "../../../plugin-lib/aliyun/lib/index.js"; @IsTaskPlugin({ name: 'DeployCertToAliyunDCDN', title: '阿里云-部署证书至DCDN', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts index dd31f8242..f93e6c1c5 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts @@ -1,11 +1,11 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import { - AliyunAccess, AliyunClientV2, - AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AliyunAccess, AliyunClientV2 } from "../../../plugin-lib/aliyun/access/index.js"; +import { AliyunSslClient } from "../../../plugin-lib/aliyun/lib/ssl-client.js"; @IsTaskPlugin({ name: "AliyunDeployCertToESA", diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts index 1d4ba68a7..a372c272f 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts @@ -1,10 +1,11 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; -import { AliyunAccess, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import fs from "fs"; import path from "path"; import { tmpdir } from "node:os"; import { sp } from "@certd/basic"; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; @IsTaskPlugin({ name: 'AliyunDeployCertToFC', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts index ab46f3406..f91903c8c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts @@ -1,14 +1,12 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import { CertInfo, CertReader } from "@certd/plugin-cert"; import { - AliyunAccess, - AliyunClient, - AliyunClientV2, - AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { CertApplyPluginNames} from '@certd/plugin-cert'; +import { AliyunAccess, AliyunClientV2 } from "../../../plugin-lib/aliyun/access/index.js"; +import { AliyunClient, AliyunSslClient } from "../../../plugin-lib/aliyun/lib/index.js"; @IsTaskPlugin({ name: 'AliyunDeployCertToNLB', title: '阿里云-部署至NLB(网络负载均衡)', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts index 8bd9739f8..6469a3b1f 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts @@ -1,7 +1,5 @@ import {AbstractTaskPlugin, IsTaskPlugin, Pager, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline'; import { - AliyunAccess, - AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; @@ -9,6 +7,8 @@ import {CertInfo, CertReader} from '@certd/plugin-cert'; import { CertApplyPluginNames} from '@certd/plugin-cert'; import {optionsUtils} from "@certd/basic"; import {isArray} from "lodash-es"; +import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js'; +import { AliyunSslClient } from '../../../plugin-lib/aliyun/lib/index.js'; @IsTaskPlugin({ name: 'DeployCertToAliyunOSS', title: '阿里云-部署证书至OSS', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts index 91f3d118e..04b3e3486 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts @@ -1,14 +1,12 @@ import {AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline'; import {CertInfo} from '@certd/plugin-cert'; import { - AliyunAccess, - AliyunClient, - AliyunSslClient, - CasCertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; import {CertApplyPluginNames} from '@certd/plugin-cert'; +import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js'; +import { AliyunClient, AliyunSslClient, CasCertInfo } from '../../../plugin-lib/aliyun/lib/index.js'; @IsTaskPlugin({ name: 'AliyunDeployCertToSLB', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-vod/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-vod/index.ts index 5eee547bb..3742c8a03 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-vod/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-vod/index.ts @@ -1,6 +1,7 @@ import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; -import { AliyunAccess, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; @IsTaskPlugin({ name: "AliyunDeployCertToVod", diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/index.ts index 5bfa90e70..201c3dc76 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/index.ts @@ -1,12 +1,11 @@ import { AbstractTaskPlugin, IsTaskPlugin, Pager,PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import { - AliyunAccess, - AliyunClient, - AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; +import { AliyunClient, AliyunSslClient } from "../../../plugin-lib/aliyun/lib/index.js"; @IsTaskPlugin({ name: 'AliyunDeployCertToWaf', diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/index.ts index 7794faf0a..bcffbc082 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/index.ts @@ -11,3 +11,4 @@ export * from './deploy-to-esa/index.js'; export * from './deploy-to-vod/index.js'; export * from './deploy-to-apigateway/index.js'; export * from './deploy-to-apig/index.js'; +export * from './deploy-to-ack/index.js'; \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts index 67efb55f5..ba3f27443 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts @@ -1,7 +1,7 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline'; -import { AliyunAccess } from '@certd/plugin-lib'; -import { AliyunSslClient } from '@certd/plugin-lib'; import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert"; +import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js'; +import { AliyunSslClient } from '../../../plugin-lib/aliyun/lib/index.js'; /** * 华东1(杭州) cn-hangzhou cas.aliyuncs.com cas-vpc.cn-hangzhou.aliyuncs.com * 马来西亚(吉隆坡) ap-southeast-3 cas.ap-southeast-3.aliyuncs.com cas-vpc.ap-southeast-3.aliyuncs.com diff --git a/packages/ui/certd-server/src/plugins/plugin-apisix/plugins/plugin-refresh-cert.ts b/packages/ui/certd-server/src/plugins/plugin-apisix/plugins/plugin-refresh-cert.ts index 66c10e200..0426e210c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-apisix/plugins/plugin-refresh-cert.ts +++ b/packages/ui/certd-server/src/plugins/plugin-apisix/plugins/plugin-refresh-cert.ts @@ -1,8 +1,7 @@ import {IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput} from "@certd/pipeline"; import {CertApplyPluginNames, CertInfo} from "@certd/plugin-cert"; -import {createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib"; +import {AbstractPlusTaskPlugin, createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib"; import {ApisixAccess} from "../access.js"; -import {AbstractPlusTaskPlugin} from "@certd/plugin-plus"; @IsTaskPlugin({ //命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名 diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/tencent/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/tencent/index.ts index 7ddc98086..ec7f00f7d 100644 --- a/packages/ui/certd-server/src/plugins/plugin-captcha/tencent/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/tencent/index.ts @@ -1,6 +1,6 @@ import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server"; import { ICaptchaAddon } from "../api.js"; -import { TencentAccess } from "@certd/plugin-lib"; +import { TencentAccess } from "../../plugin-lib/tencent/access.js"; @IsAddon({ addonType: "captcha", diff --git a/packages/plugins/plugin-cert/src/access/eab-access.ts b/packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.ts similarity index 100% rename from packages/plugins/plugin-cert/src/access/eab-access.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.ts diff --git a/packages/plugins/plugin-cert/src/access/google-access.ts b/packages/ui/certd-server/src/plugins/plugin-cert/access/google-access.ts similarity index 100% rename from packages/plugins/plugin-cert/src/access/google-access.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/access/google-access.ts diff --git a/packages/plugins/plugin-cert/src/access/index.ts b/packages/ui/certd-server/src/plugins/plugin-cert/access/index.ts similarity index 100% rename from packages/plugins/plugin-cert/src/access/index.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/access/index.ts diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/index.ts b/packages/ui/certd-server/src/plugins/plugin-cert/index.ts new file mode 100644 index 000000000..3bc0807f8 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-cert/index.ts @@ -0,0 +1,2 @@ +export * from "./access/index.js"; +export * from "./plugin/index.js"; diff --git a/packages/plugins/plugin-cert/src/libs/google.ts b/packages/ui/certd-server/src/plugins/plugin-cert/libs/google.ts similarity index 100% rename from packages/plugins/plugin-cert/src/libs/google.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/libs/google.ts diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts similarity index 98% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts index ce196cb3f..7ae463124 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts @@ -2,12 +2,12 @@ import * as acme from "@certd/acme-client"; import { ClientExternalAccountBindingOptions, UrlMapping } from "@certd/acme-client"; import * as _ from "lodash-es"; -import { Challenge } from "@certd/acme-client/types/rfc8555"; +import { Challenge } from "@certd/acme-client/types/rfc8555.js"; import { IContext } from "@certd/pipeline"; import { ILogger, utils } from "@certd/basic"; -import { IDnsProvider, IDomainParser } from "../../dns-provider/index.js"; import punycode from "punycode.js"; -import { IOssClient } from "@certd/plugin-lib"; +import { IOssClient } from "../../../plugin-lib/index.js"; +import { IDnsProvider, IDomainParser } from "@certd/plugin-lib"; export type CnameVerifyPlan = { type?: string; domain: string; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base-convert.ts similarity index 97% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base-convert.ts index e8ad9d756..212502580 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base-convert.ts @@ -1,10 +1,9 @@ import { AbstractTaskPlugin, FileItem, IContext, Step, TaskInput, TaskOutput } from "@certd/pipeline"; import dayjs from "dayjs"; import type { CertInfo } from "./acme.js"; -import { CertReader } from "./cert-reader.js"; +import { CertReader,CertConverter, EVENT_CERT_APPLY_SUCCESS } from "@certd/plugin-lib"; import JSZip from "jszip"; -import { CertConverter } from "./convert.js"; -export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success"; + export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin { @TaskInput({ diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base.ts similarity index 99% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base.ts index 8c84f271a..2e2b952b9 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base.ts @@ -1,6 +1,6 @@ import { NotificationBody, Step, TaskInput } from "@certd/pipeline"; import dayjs from "dayjs"; -import { CertReader } from "./cert-reader.js"; +import { CertReader } from "@certd/plugin-lib"; import { pick } from "lodash-es"; import { CertApplyBaseConvertPlugin } from "./base-convert.js"; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/custom/index.ts similarity index 99% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/custom/index.ts index 94ed7613a..629c1d9ee 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/custom/index.ts @@ -1,6 +1,6 @@ import { IsTaskPlugin, pluginGroups, RunStrategy, Step, TaskInput, TaskOutput } from "@certd/pipeline"; import type { CertInfo } from "../acme.js"; -import { CertReader } from "../cert-reader.js"; +import { CertReader } from "@certd/plugin-lib"; import { CertApplyBaseConvertPlugin } from "../base-convert.js"; import dayjs from "dayjs"; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/getter/aliyun.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/getter/aliyun.ts similarity index 96% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/getter/aliyun.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/getter/aliyun.ts index e20c2c85e..5a4162cdc 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/getter/aliyun.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/getter/aliyun.ts @@ -1,8 +1,8 @@ import { IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; -import { AliyunAccess, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AliyunAccess } from "../../../../plugin-lib/aliyun/access/index.js"; import type { CertInfo } from "../acme.js"; import { CertApplyBasePlugin } from "../base.js"; -import { CertReader } from "../cert-reader.js"; +import { CertReader, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import dayjs from "dayjs"; export { CertReader }; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/index.ts similarity index 98% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/index.ts index 9568290c8..c9f09684f 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/index.ts @@ -1,19 +1,17 @@ import { CancelError, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { utils } from "@certd/basic"; -import { AcmeService, CertInfo, DomainsVerifyPlan, DomainVerifyPlan, PrivateKeyType, SSLProvider } from "./acme.js"; +import { AcmeService, DomainsVerifyPlan, DomainVerifyPlan, PrivateKeyType, SSLProvider } from "./acme.js"; import * as _ from "lodash-es"; -import { createDnsProvider, DnsProviderContext, DnsVerifier, DomainVerifiers, HttpVerifier, IDnsProvider, IDomainVerifierGetter, ISubDomainsGetter } from "../../dns-provider/index.js"; -import { CertReader } from "./cert-reader.js"; +import { createDnsProvider, DnsProviderContext, DnsVerifier, DomainVerifiers, HttpVerifier, IDnsProvider, IDomainVerifierGetter, ISubDomainsGetter } from "@certd/plugin-lib"; +import { CertReader } from "@certd/plugin-lib"; import { CertApplyBasePlugin } from "./base.js"; import { GoogleClient } from "../../libs/google.js"; -import { EabAccess } from "../../access"; -import { DomainParser } from "../../dns-provider/domain-parser.js"; -import { ossClientFactory } from "@certd/plugin-lib"; +import { EabAccess } from "../../access/index.js"; +import { DomainParser } from "@certd/plugin-lib"; +import { ossClientFactory } from "../../../plugin-lib/oss/factory.js"; export * from "./base.js"; -export type { CertInfo }; -export * from "./cert-reader.js"; export type CnameRecordInput = { id: number; status: string; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/dns.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/lego/dns.ts similarity index 100% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/dns.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/lego/dns.ts diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/lego/index.ts similarity index 99% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/lego/index.ts index 30cd19ae7..40ad819e6 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/lego/index.ts @@ -1,6 +1,6 @@ import { IsTaskPlugin, pluginGroups, RunStrategy, Step, TaskInput } from "@certd/pipeline"; import type { CertInfo } from "../acme.js"; -import { CertReader } from "../cert-reader.js"; +import { CertReader } from "@certd/plugin-lib"; import { CertApplyBasePlugin } from "../base.js"; import fs from "fs"; import { EabAccess } from "../../../access/index.js"; diff --git a/packages/plugins/plugin-cert/src/plugin/index.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/index.ts similarity index 59% rename from packages/plugins/plugin-cert/src/plugin/index.ts rename to packages/ui/certd-server/src/plugins/plugin-cert/plugin/index.ts index 289f7a4a9..9f1437df0 100644 --- a/packages/plugins/plugin-cert/src/plugin/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/index.ts @@ -1,7 +1,4 @@ -export { EVENT_CERT_APPLY_SUCCESS } from "./cert-plugin/base-convert.js"; - export * from "./cert-plugin/index.js"; export * from "./cert-plugin/lego/index.js"; export * from "./cert-plugin/custom/index.js"; export * from "./cert-plugin/getter/aliyun.js"; -export const CertApplyPluginNames = [":cert:"]; diff --git a/packages/ui/certd-server/src/plugins/plugin-cmcc/plugin-deploy-to-cdn.ts b/packages/ui/certd-server/src/plugins/plugin-cmcc/plugin-deploy-to-cdn.ts index f27d7473a..91037d3bb 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cmcc/plugin-deploy-to-cdn.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cmcc/plugin-deploy-to-cdn.ts @@ -7,7 +7,7 @@ import { } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; -import { AbstractPlusTaskPlugin } from "@certd/plugin-plus"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; import { CmccAccess } from "./access.js"; @IsTaskPlugin({ diff --git a/packages/ui/certd-server/src/plugins/plugin-fnos/index.ts b/packages/ui/certd-server/src/plugins/plugin-fnos/index.ts index 04e1cdd9e..8693be3ca 100644 --- a/packages/ui/certd-server/src/plugins/plugin-fnos/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-fnos/index.ts @@ -3,9 +3,9 @@ import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine, - SshAccess, - SshClient } from "@certd/plugin-lib"; +import { SshAccess } from "../plugin-lib/ssh/ssh-access.js"; +import { SshClient } from "../plugin-lib/ssh/ssh.js"; @IsTaskPlugin({ //命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名 diff --git a/packages/ui/certd-server/src/plugins/plugin-github/plugins/plugin-check-release.ts b/packages/ui/certd-server/src/plugins/plugin-github/plugins/plugin-check-release.ts index bad10d45a..660fa6ccb 100644 --- a/packages/ui/certd-server/src/plugins/plugin-github/plugins/plugin-check-release.ts +++ b/packages/ui/certd-server/src/plugins/plugin-github/plugins/plugin-check-release.ts @@ -1,6 +1,6 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline"; import { GithubAccess } from "../access.js"; -import {SshClient} from "@certd/plugin-lib"; +import { SshClient } from "../../plugin-lib/ssh/ssh.js"; @IsTaskPlugin({ //命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名 diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts index c4c44ca49..0bbcb34b2 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts @@ -1,5 +1,5 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; -import { SshClient } from '@certd/plugin-lib'; +import { SshClient } from '../../../plugin-lib/ssh/index.js'; @IsTaskPlugin({ name: 'hostShellExecute', title: '主机-执行远程主机脚本命令', diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/index.ts index bb1e4d363..03e172d46 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/index.ts @@ -2,3 +2,4 @@ export * from './host-shell-execute/index.js'; export * from './upload-to-host/index.js'; export * from './copy-to-local/index.js' export * from './plugin-upload-to-oss.js' +export * from './plugin-deploy-to-iis.js' \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/plugin-deploy-to-iis.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/plugin-deploy-to-iis.ts new file mode 100644 index 000000000..a6259adc4 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/plugin-deploy-to-iis.ts @@ -0,0 +1,283 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertInfo } from "@certd/plugin-cert"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import fs from "fs"; +import { utils } from "@certd/basic"; +import { CertApplyPluginNames } from "@certd/plugin-cert"; +import { SshAccess } from "../../plugin-lib/ssh/ssh-access.js"; +import { SshClient } from "../../plugin-lib/ssh/ssh.js"; +@IsTaskPlugin({ + name: "HostDeployToIIS", + title: "IIS-部署到IIS站点", + icon: "devicon:windows8", + group: pluginGroups.host.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: true, +}) +export class HostDeployToIIS extends AbstractPlusTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "主机SSH授权", + component: { + name: "access-selector", + type: "ssh", + }, + required: true, + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "站点名称", + helper: "选择或手动输入网站名称", + action: HostDeployToIIS.prototype.onGetSiteList.name, + }) + ) + siteNames!: string[]; + + //授权选择框 + @TaskInput({ + title: "证书密码", + required: false, + }) + pfxPassword!: string; + + async onInstance() {} + + async execute(): Promise { + const sshConf = await this.getAccess(this.accessId); + const sshClient = new SshClient(this.logger); + await this.checkTerminalType(sshClient, sshConf); + const tempDir = path.join(tmpdir(), "certd"); + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); + } + + const tempCertPath = path.join(tempDir, this.appendTimeSuffix("cert") + ".pfx"); + fs.writeFileSync(tempCertPath, Buffer.from(this.cert.pfx, "base64")); + + const remoteTmpDir = await sshClient.exec({ + connectConf: sshConf, + // 获取远程服务器的tmpdir + script: ["$env:temp"], + }); + const remoteTempCertPath = path.join(remoteTmpDir.trim(), "certd", "iis", path.basename(tempCertPath)); + await sshClient.uploadFiles({ + connectConf: sshConf, + transports: [ + { + localPath: tempCertPath, + remotePath: remoteTempCertPath, + }, + ], + mkdirs: true, + }); + + try { + for (const siteName of this.siteNames) { + const script = updateCertScriptTemplate + .replaceAll("{SITE_NAME}", siteName) + .replaceAll("{PASSWORD}", this.pfxPassword || "") + .replaceAll("{CERT_PATH}", remoteTempCertPath); + + await sshClient.exec({ + connectConf: sshConf, + script: [script], + throwOnStdErr: true, + }); + } + } finally { + fs.unlinkSync(tempCertPath); + } + + this.logger.info(`更新证书完成`); + } + + async onGetSiteList() { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + const sshConf = await this.getAccess(this.accessId); + const sshClient = new SshClient(this.logger); + await this.checkTerminalType(sshClient, sshConf); + + const res = await sshClient.exec({ + connectConf: sshConf, + script: [getAllBindingsScript], + }); + + /** + * + * SiteName HttpsBindings + * -------- ------------- + * first *:443:first.handsfree.work + */ + // 解析获取网站名称 + const siteOptions = []; + let start = false; + const lines = res.split("\n"); + lines.map((item: any) => { + if (item.includes("-------result start--------")) { + start = true; + return; + } + if (item.includes("-------result end--------")) { + start = false; + return; + } + if (!start) { + return; + } + item = item.trim(); + if (item === "" || !item.includes(" | ")) { + return; + } + const [name, value] = item.split(" | "); + const siteName = name.trim(); + let domain = value; + if (value.includes(":")) { + domain = value.split(":")[2].trim(); + } + siteOptions.push({ + label: `${siteName}<${domain}>`, + value: siteName, + domain: domain, + }); + }); + + return utils.options.buildGroupOptions(siteOptions, this.certDomains); + } + + private async checkTerminalType(sshClient: SshClient, sshConf: SshAccess) { + this.logger.info("检查终端类型"); + const isCmd = await sshClient.getIsCmd({ + connectConf: sshConf, + }); + this.logger.info("isCmd", isCmd); + if (isCmd) { + throw new Error("不支持cmd,请将OpenSSH的默认终端设置为powershell,如何设置请参考: https://certd.docmirror.cn/guide/use/host/windows.html"); + } + } +} + +const updateCertScriptTemplate = ` +try{ + $siteName = "{SITE_NAME}" + $password = "{PASSWORD}" + $certPath = "{CERT_PATH}" + if($password -eq "") { + Write-Host "cert password is empty" + }else{ + $password = ConvertTo-SecureString -String $password -AsPlainText -Force + } + + # 导入 PFX 证书 + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 + # $cert.Import($certPath, $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet) + $cert.Import( + $certPath, + $password, + [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor + [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet +) + # 打开 LocalMachine 的证书存储 + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") + $store.Open("ReadWrite") + $store.Add($cert) + $store.Close() + + Write-Host "import cert success" + + # 导入 IIS 模块 + Import-Module WebAdministration + + # 网站名称和证书指纹 + + $thumbprint = $cert.Thumbprint # 获取证书指纹 + Write-Host 'site name:' $siteName + Write-Host 'cert info:' $cert.Thumbprint + + # 绑定新的证书 + $binding = Get-WebBinding -Name $siteName -Protocol https + if ($binding) { + $binding.AddSslCertificate($thumbprint, 'My') + \tWrite-Host "update cert success" + } else { + Write-Error "HTTPS bind not found, cant add SSL cert,Https绑定不存在,请先创建https绑定" + } + + # 确认更改 + Get-WebBinding -Name $siteName -Protocol https + + # 重启 IIS + iisreset +} catch { + Write-Error "发生错误:$($_.Exception.Message)" +} +`; + +const getAllBindingsScript = ` +try{ + # 导入 WebAdministration 模块 + Import-Module WebAdministration + + # 获取所有 IIS 站点 + $sites = Get-IISSite + + # 创建一个列表用于存储结果 + $result = @() + + # 遍历每个站点 + foreach ($site in $sites) { + # 获取该站点的所有绑定 + $bindings = Get-WebBinding -Name $site.Name + + # 筛选出 HTTPS 绑定 + $httpsBindings = $bindings | Where-Object { $_.protocol -eq "https" } + + # 将站点名称和 HTTPS 绑定信息添加到结果 + $result += [PSCustomObject]@{ + SiteName = $site.Name + HttpsBindings = if ($httpsBindings) { + $httpsBindings | ForEach-Object { $_.bindingInformation } + } else { + "No HTTPS bindings" + } + } + } + + # 显示结果 + $result | Format-Table -AutoSize + Write-Output "-------result start--------" + foreach ($item in $result) { + Write-Output "$($item.SiteName) | $($item.HttpsBindings)" + } + Write-Output "-------result end--------" +} catch { + Write-Error "发生错误:$($_.Exception.Message)" +} +`; + +new HostDeployToIIS(); diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/plugin-upload-to-oss.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/plugin-upload-to-oss.ts index 6db724256..3b0ea547e 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/plugin-upload-to-oss.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/plugin-upload-to-oss.ts @@ -1,7 +1,7 @@ import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline'; import {CertInfo} from "@certd/plugin-cert"; -import {ossClientFactory} from "@certd/plugin-lib"; import {utils} from "@certd/basic"; +import { ossClientFactory } from '../../plugin-lib/oss/factory.js'; @IsTaskPlugin({ name: 'UploadCertToOss', diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts index 1bdb01ea7..f9ecf3895 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts @@ -1,7 +1,7 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline'; import { CertInfo, CertReader, CertReaderHandleContext } from '@certd/plugin-cert'; import dayjs from 'dayjs'; -import { SshAccess, SshClient } from '@certd/plugin-lib'; +import { SshAccess, SshClient } from '../../../plugin-lib/ssh/index.js'; import { CertApplyPluginNames} from '@certd/plugin-cert'; @IsTaskPlugin({ name: 'uploadCertToHost', diff --git a/packages/plugins/plugin-lib/src/aliyun/access/aliesa-access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/aliesa-access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/aliyun/access/aliesa-access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/aliesa-access.ts diff --git a/packages/plugins/plugin-lib/src/aliyun/access/alioss-access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/alioss-access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/aliyun/access/alioss-access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/alioss-access.ts diff --git a/packages/plugins/plugin-lib/src/aliyun/access/aliyun-access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/aliyun-access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/aliyun/access/aliyun-access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/aliyun-access.ts diff --git a/packages/plugins/plugin-lib/src/aliyun/access/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/aliyun/access/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/index.ts diff --git a/packages/plugins/plugin-lib/src/aliyun/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/index.ts similarity index 67% rename from packages/plugins/plugin-lib/src/aliyun/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/index.ts index f065ab262..7ee122d1a 100644 --- a/packages/plugins/plugin-lib/src/aliyun/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/index.ts @@ -1,2 +1,3 @@ -export * from "./lib/index.js"; export * from "./access/index.js"; +export * from "./lib/index.js"; +export * from "./utils/index.js"; \ No newline at end of file diff --git a/packages/plugins/plugin-lib/src/aliyun/lib/base-client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/base-client.ts similarity index 100% rename from packages/plugins/plugin-lib/src/aliyun/lib/base-client.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/base-client.ts diff --git a/packages/plugins/plugin-lib/src/aliyun/lib/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/aliyun/lib/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/index.ts diff --git a/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/oss-client.ts similarity index 100% rename from packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/oss-client.ts diff --git a/packages/plugins/plugin-lib/src/aliyun/lib/ssl-client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts similarity index 100% rename from packages/plugins/plugin-lib/src/aliyun/lib/ssl-client.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/utils/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/utils/index.ts similarity index 94% rename from packages/ui/certd-server/src/plugins/plugin-aliyun/utils/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/aliyun/utils/index.ts index b9832d14d..4985f5a27 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/utils/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/utils/index.ts @@ -1,6 +1,6 @@ import dayjs from "dayjs"; -import { AliyunSslClient } from "@certd/plugin-lib"; import { CertInfo, CertReader } from "@certd/plugin-cert"; +import { AliyunSslClient } from "../lib/index.js"; export const ZoneOptions = [{ value: 'cn-hangzhou' }]; export function appendTimeSuffix(name: string) { diff --git a/packages/plugins/plugin-lib/src/ftp/access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/ftp/access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/ftp/access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/ftp/access.ts diff --git a/packages/plugins/plugin-lib/src/ftp/client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/ftp/client.ts similarity index 97% rename from packages/plugins/plugin-lib/src/ftp/client.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/ftp/client.ts index 4a21c3535..cf6955e85 100644 --- a/packages/plugins/plugin-lib/src/ftp/client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/ftp/client.ts @@ -1,4 +1,4 @@ -import { FtpAccess } from "./access"; +import { FtpAccess } from "./access.js"; import { ILogger } from "@certd/basic"; import path from "node:path"; diff --git a/packages/plugins/plugin-lib/src/ftp/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/ftp/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/ftp/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/ftp/index.ts diff --git a/packages/ui/certd-server/src/plugins/plugin-lib/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/index.ts new file mode 100644 index 000000000..8b52cc8a0 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-lib/index.ts @@ -0,0 +1,7 @@ +export * from "./ssh/index.js"; +export * from "./ftp/index.js"; +export * from "./tencent/index.js"; +export * from "./qiniu/index.js"; +export * from "./oss/index.js"; +export * from "./s3/index.js"; +export * from "./aliyun/index.js"; diff --git a/packages/plugins/plugin-lib/src/oss/api.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/api.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/api.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/api.ts diff --git a/packages/plugins/plugin-lib/src/oss/factory.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/factory.ts similarity index 96% rename from packages/plugins/plugin-lib/src/oss/factory.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/factory.ts index 9bd128ed2..e05cabe09 100644 --- a/packages/plugins/plugin-lib/src/oss/factory.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/oss/factory.ts @@ -1,4 +1,4 @@ -import { OssClientContext } from "./api"; +import { OssClientContext } from "./api.js"; export class OssClientFactory { async getClassByType(type: string) { diff --git a/packages/plugins/plugin-lib/src/oss/impls/alioss.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/alioss.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/impls/alioss.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/alioss.ts diff --git a/packages/plugins/plugin-lib/src/oss/impls/ftp.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/ftp.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/impls/ftp.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/ftp.ts diff --git a/packages/plugins/plugin-lib/src/oss/impls/qiniuoss.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/qiniuoss.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/impls/qiniuoss.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/qiniuoss.ts diff --git a/packages/plugins/plugin-lib/src/oss/impls/s3.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/s3.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/impls/s3.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/s3.ts diff --git a/packages/plugins/plugin-lib/src/oss/impls/sftp.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/sftp.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/impls/sftp.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/sftp.ts diff --git a/packages/plugins/plugin-lib/src/oss/impls/ssh.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/ssh.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/impls/ssh.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/ssh.ts diff --git a/packages/plugins/plugin-lib/src/oss/impls/tencentcos.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/tencentcos.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/impls/tencentcos.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/tencentcos.ts diff --git a/packages/plugins/plugin-lib/src/oss/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/oss/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/oss/index.ts diff --git a/packages/plugins/plugin-lib/src/qiniu/access-oss.ts b/packages/ui/certd-server/src/plugins/plugin-lib/qiniu/access-oss.ts similarity index 100% rename from packages/plugins/plugin-lib/src/qiniu/access-oss.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/qiniu/access-oss.ts diff --git a/packages/plugins/plugin-lib/src/qiniu/access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/qiniu/access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/qiniu/access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/qiniu/access.ts diff --git a/packages/plugins/plugin-lib/src/qiniu/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/qiniu/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/qiniu/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/qiniu/index.ts diff --git a/packages/plugins/plugin-lib/src/qiniu/lib/sdk.ts b/packages/ui/certd-server/src/plugins/plugin-lib/qiniu/lib/sdk.ts similarity index 100% rename from packages/plugins/plugin-lib/src/qiniu/lib/sdk.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/qiniu/lib/sdk.ts diff --git a/packages/plugins/plugin-lib/src/s3/access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/s3/access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/s3/access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/s3/access.ts diff --git a/packages/plugins/plugin-lib/src/s3/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/s3/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/s3/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/s3/index.ts diff --git a/packages/plugins/plugin-lib/src/ssh/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/ssh/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/ssh/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/ssh/index.ts diff --git a/packages/plugins/plugin-lib/src/ssh/sftp-access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/ssh/sftp-access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/ssh/sftp-access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/ssh/sftp-access.ts diff --git a/packages/plugins/plugin-lib/src/ssh/ssh-access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/ssh/ssh-access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/ssh/ssh-access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/ssh/ssh-access.ts diff --git a/packages/plugins/plugin-lib/src/ssh/ssh.ts b/packages/ui/certd-server/src/plugins/plugin-lib/ssh/ssh.ts similarity index 99% rename from packages/plugins/plugin-lib/src/ssh/ssh.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/ssh/ssh.ts index e405b6d74..65ea3e011 100644 --- a/packages/plugins/plugin-lib/src/ssh/ssh.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/ssh/ssh.ts @@ -5,7 +5,7 @@ import { ILogger, safePromise } from "@certd/basic"; import { SshAccess } from "./ssh-access.js"; import fs from "fs"; -import { SocksProxyType } from "socks/typings/common/constants"; +import { SocksProxyType } from "socks/typings/common/constants.js"; export type TransportItem = { localPath: string; remotePath: string }; export interface SocksProxy { diff --git a/packages/plugins/plugin-lib/src/tencent/access-cos.ts b/packages/ui/certd-server/src/plugins/plugin-lib/tencent/access-cos.ts similarity index 100% rename from packages/plugins/plugin-lib/src/tencent/access-cos.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/tencent/access-cos.ts diff --git a/packages/plugins/plugin-lib/src/tencent/access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/tencent/access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/tencent/access.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/tencent/access.ts diff --git a/packages/plugins/plugin-lib/src/tencent/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/tencent/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/tencent/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/tencent/index.ts diff --git a/packages/plugins/plugin-lib/src/tencent/lib/cos-client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/tencent/lib/cos-client.ts similarity index 100% rename from packages/plugins/plugin-lib/src/tencent/lib/cos-client.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/tencent/lib/cos-client.ts diff --git a/packages/plugins/plugin-lib/src/tencent/lib/index.ts b/packages/ui/certd-server/src/plugins/plugin-lib/tencent/lib/index.ts similarity index 100% rename from packages/plugins/plugin-lib/src/tencent/lib/index.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/tencent/lib/index.ts diff --git a/packages/plugins/plugin-lib/src/tencent/lib/ssl-client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/tencent/lib/ssl-client.ts similarity index 100% rename from packages/plugins/plugin-lib/src/tencent/lib/ssl-client.ts rename to packages/ui/certd-server/src/plugins/plugin-lib/tencent/lib/ssl-client.ts diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/1panel/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/access.ts new file mode 100644 index 000000000..6a51129cb --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/access.ts @@ -0,0 +1,160 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +import { HttpClient } from "@certd/basic"; +import { OnePanelClient } from "./client.js"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "1panel", + title: "1panel授权", + desc: "账号和密码", + icon: "svg:icon-onepanel", +}) +export class OnePanelAccess extends BaseAccess { + @AccessInput({ + title: "1Panel面板的url", + component: { + placeholder: "http://xxxx.com:1231", + }, + helper: "不要带安全入口", + required: true, + }) + baseUrl = ""; + + @AccessInput({ + title: "安全入口", + component: { + placeholder: "登录的安全入口", + }, + encrypt: true, + required: false, + }) + safeEnter = ""; + + @AccessInput({ + title: "授权方式", + component: { + name: "a-select", + vModel: "value", + options: [ + { label: "模拟登录【不推荐】", value: "password" }, + { label: "接口密钥【推荐】", value: "apikey" }, + ], + }, + required: true, + }) + type = ""; + + @AccessInput({ + title: "接口版本", + value: "v1", + component: { + placeholder: "v1 / v2", + name: "a-select", + vModel: "value", + options: [ + { label: "v1", value: "v1" }, + { label: "v2", value: "v2" }, + ], + }, + required: true, + }) + apiVersion = "v1"; + + @AccessInput({ + title: "用户名", + component: { + placeholder: "username", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + `, + required: true, + }) + username = ""; + + @AccessInput({ + title: "密码", + component: { + placeholder: "password", + }, + helper: "", + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + `, + required: true, + encrypt: true, + }) + password = ""; + + @AccessInput({ + title: "接口密钥", + component: { + placeholder: "接口密钥", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'apikey'; + }) + } + `, + helper: "面板设置->API接口中获取", + required: true, + encrypt: true, + }) + apiKey = ""; + + @AccessInput({ + title: "忽略证书校验", + value: true, + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭", + }) + skipSslVerify = true; + + @AccessInput({ + title: "测试", + component: { + name: "api-test", + action: "onTestRequest", + }, + helper: "点击测试接口看是否正常\nIP需要加白名单,如果是同一台机器部署的,可以试试面板的url使用网卡docker0的ip,白名单使用172.16.0.0/12", + }) + testRequest = true; + + async onTestRequest() { + const http: HttpClient = this.ctx.http; + const client = new OnePanelClient({ + logger: this.ctx.logger, + http, + access: this, + utils: this.ctx.utils, + }); + + await client.doRequest({ + url: `/api/${this.apiVersion}/websites/ssl/search`, + method: "post", + data: { + page: 1, + pageSize: 1, + }, + }); + return "ok"; + } +} + +new OnePanelAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/1panel/client.ts b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/client.ts new file mode 100644 index 000000000..9cc4e6e2a --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/client.ts @@ -0,0 +1,228 @@ +import { HttpClient, HttpRequestConfig, ILogger } from "@certd/basic"; +import { OnePanelAccess } from "./access.js"; + +export class OnePanelClient { + access: OnePanelAccess; + http: HttpClient; + logger: ILogger; + utils: any; + token: string; + constructor(opts: { access: OnePanelAccess; http: HttpClient; logger: ILogger; utils: any }) { + this.access = opts.access; + this.http = opts.http; + this.logger = opts.logger; + this.utils = opts.utils; + } + + // + // //http://xxx:xxxx/1panel/swagger/index.html#/App/get_apps__key + // async execute(): Promise { + // //login 获取token + // /** + // * curl 'http://127.0.0.1:7001/api/v1/auth/login' --data-binary '{"name":"admin_test","password":"admin_test1234","ignoreCaptcha":true,"captcha":"","captchaID":"nY8Cqeut3TjZMfJMAz0k","authMethod":"jwt","language":"zh"}' -H 'EntranceCode: emhhbmd5eg==' + // * curl 'http://127.0.0.1:7001/api/v1/dashboard/current/all/all' -H 'PanelAuthorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MCwiTmFtZSI6ImFkbWluX3Rlc3QiLCJCdWZmZXJUaW1lIjozNjAwLCJpc3MiOiIxUGFuZWwiLCJleHAiOjE3MDkyODg4MDl9.pdknJdjLY4Fp8wCE9Gvaiic2rLoSdvUSJB9ossyya_I' + // */ + // const sslIds = this.sslIds; + // for (const sslId of sslIds) { + // try { + // const certRes = await this.get1PanelCertInfo(sslId); + // if (!this.isNeedUpdate(certRes)) { + // continue; + // } + // + // const uploadRes = await this.doRequest({ + // url: "/api/v1/websites/ssl/upload", + // method: "post", + // data: { + // sslIds, + // certificate: this.cert.crt, + // certificatePath: "", + // description: certRes.description || this.appendTimeSuffix("certd"), + // privateKey: this.cert.key, + // privateKeyPath: "", + // sslID: sslId, + // type: "paste", + // }, + // }); + // console.log("uploadRes", JSON.stringify(uploadRes)); + // } catch (e) { + // this.logger.warn(`更新证书(id:${sslId})失败`, e); + // this.logger.info("可能1Panel正在重启,等待10秒后检查证书是否更新成功"); + // await this.ctx.utils.sleep(10000); + // const certRes = await this.get1PanelCertInfo(sslId); + // if (!this.isNeedUpdate(certRes)) { + // continue; + // } + // throw e; + // } + // } + // } + + async get1PanelCertInfo(sslId: string) { + const certRes = await this.doRequest({ + url: `/api/${this.access.apiVersion}/websites/ssl/${sslId}`, + method: "get", + }); + if (!certRes) { + throw new Error(`没有找到证书(id:${sslId}),请先在1Panel中手动上传证书,后续才可以自动更新`); + } + return certRes; + } + + async doRequest(config: { currentNode?: string } & HttpRequestConfig) { + const tokenHeaders = await this.getAccessToken(); + config.headers = { + ...tokenHeaders, + }; + if (config.currentNode) { + config.headers.CurrentNode = this.getNodeValue(config.currentNode); + delete config.currentNode; + } + return await this.doRequestWithoutAuth(config); + } + + async doRequestWithoutAuth(config: HttpRequestConfig) { + config.baseURL = this.access.baseUrl; + config.skipSslVerify = this.access.skipSslVerify ?? false; + config.logRes = false; + config.logParams = false; + const res = await this.http.request(config); + if (config.returnOriginRes) { + return res; + } + if (res.code === 200) { + return res.data; + } + throw new Error(res.message); + } + + async getCookie(name: string) { + // https://www.docmirror.cn:20001/api/v1/auth/language + const response = await this.doRequestWithoutAuth({ + url: `/api/${this.access.apiVersion}/auth/language`, + method: "GET", + returnOriginRes: true, + }); + const cookies = response.headers["set-cookie"]; + //根据name 返回对应的cookie + const found = cookies.find(cookie => cookie.includes(name)); + if (!found) { + return null; + } + const cookie = found.split(";")[0]; + return cookie.substring(cookie.indexOf("=") + 1); + } + + async encryptPassword(password: string) { + const rsaPublicKeyText = await this.getCookie("panel_public_key"); + if (!rsaPublicKeyText) { + return password; + } + // 使用rsa加密 + const { encryptPassword } = await import("./util.js"); + return encryptPassword(rsaPublicKeyText, password); + } + + async getAccessToken() { + if (this.access.type === "apikey") { + return this.getAccessTokenByApiKey(); + } else { + return await this.getAccessTokenByPassword(); + } + } + + async getAccessTokenByApiKey() { + /** + * Token = md5('1panel' + API-Key + UnixTimestamp) + * 组成部分: + * 固定前缀: '1panel' + * API-Key: 面板 API 接口密钥 + * UnixTimestamp: 当前的时间戳(秒级) + * 请求 Header 设计¶ + * 每次请求必须携带以下两个 Header: + * + * Header 名称 说明 + * 1Panel-Token 自定义的 Token 值 + * 1Panel-Timestamp 当前时间戳 + * 示例请求头:¶ + * + * curl -X GET "http://localhost:4004/api/v1/dashboard/current" \ + * -H "1Panel-Token: <1panel_token>" \ + * -H "1Panel-Timestamp: " + */ + + const timestamp = Math.floor(Date.now() / 1000); + const token = this.utils.hash.md5(`1panel${this.access.apiKey}${timestamp}`); + return { + "1Panel-Token": token, + "1Panel-Timestamp": timestamp, + }; + } + + async getAccessTokenByPassword() { + // console.log("getAccessToken", this); + // const tokenCacheKey = `1panel-token-${this.accessId}`; + // let token = this.utils.cache.get(tokenCacheKey); + // if (token) { + // return token; + // } + if (this.token) { + return { + PanelAuthorization: this.token, + }; + } + let password = this.access.password; + password = await this.encryptPassword(password); + const loginRes = await this.doRequestWithoutAuth({ + url: `/api/${this.access.apiVersion}/auth/login`, + method: "post", + headers: { + EntranceCode: Buffer.from(this.access.safeEnter).toString("base64"), + }, + data: { + name: this.access.username, + password: password, + ignoreCaptcha: true, + captcha: "", + captchaID: "", + authMethod: "jwt", + language: "zh", + }, + }); + this.token = loginRes.token; + + return { + PanelAuthorization: this.token, + }; + } + + async onGetSSLIds() { + // if (!isPlus()) { + // throw new Error("自动获取站点列表为专业版功能,您可以手动输入证书id进行部署"); + // } + const res = await this.doRequest({ + url: `/api/${this.access.apiVersion}/websites/ssl/search`, + method: "post", + data: { + page: 1, + pageSize: 99999, + }, + }); + if (!res?.items) { + throw new Error("没有找到证书,请先在1Panel中手动上传证书,并关联站点,后续才可以自动更新"); + } + const options = res.items.map(item => { + return { + label: `${item.primaryDomain}<${item.id},${item.description || "无备注"}>`, + value: item.id, + }; + }); + return options; + } + + getNodeValue(node?: string) { + const node_master_key = "local"; + const _value = node || node_master_key; + return encodeURIComponent(_value); + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/1panel/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/index.ts new file mode 100644 index 000000000..09ca4f9f1 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/index.ts @@ -0,0 +1,3 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; +export * from "./client.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/1panel/plugins/deploy-to-website.ts b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/plugins/deploy-to-website.ts new file mode 100644 index 000000000..78dbdaf09 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/plugins/deploy-to-website.ts @@ -0,0 +1,212 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; + +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { OnePanelAccess } from "../access.js"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { OnePanelClient } from "../client.js"; + +@IsTaskPlugin({ + name: "1PanelDeployToWebsitePlugin", + title: "1Panel-部署证书到1Panel", + icon: "svg:icon-onepanel", + desc: "更新1Panel的证书", + group: pluginGroups.panel.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class OnePanelDeployToWebsitePlugin extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine()) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "1Panel授权", + helper: "1Panel授权", + component: { + name: "access-selector", + type: "1panel", + }, + required: true, + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "1Panel节点", + helper: "要更新的1Panel证书的节点信息,目前只有v2存在此概念", + typeName: "OnePanelDeployToWebsitePlugin", + action: OnePanelDeployToWebsitePlugin.prototype.onGetNodes.name, + value: "local", + required: true, + }) + ) + currentNode!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "1Panel证书ID", + typeName: "1PanelDeployToWebsitePlugin", + action: OnePanelDeployToWebsitePlugin.prototype.onGetSSLIds.name, + watches: ["accessId"], + helper: "要更新的1Panel证书id,选择授权之后,从下拉框中选择\nIP需要加白名单,如果是同一台机器部署的,可以试试172.16.0.0/12", + required: true, + }) + ) + sslIds!: string[]; + + access: OnePanelAccess; + async onInstance() { + this.access = await this.getAccess(this.accessId); + } + //http://xxx:xxxx/1panel/swagger/index.html#/App/get_apps__key + async execute(): Promise { + //login 获取token + /** + * curl 'http://127.0.0.1:7001/api/v1/auth/login' --data-binary '{"name":"admin_test","password":"admin_test1234","ignoreCaptcha":true,"captcha":"","captchaID":"nY8Cqeut3TjZMfJMAz0k","authMethod":"jwt","language":"zh"}' -H 'EntranceCode: emhhbmd5eg==' + * curl 'http://127.0.0.1:7001/api/v1/dashboard/current/all/all' -H 'PanelAuthorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MCwiTmFtZSI6ImFkbWluX3Rlc3QiLCJCdWZmZXJUaW1lIjozNjAwLCJpc3MiOiIxUGFuZWwiLCJleHAiOjE3MDkyODg4MDl9.pdknJdjLY4Fp8wCE9Gvaiic2rLoSdvUSJB9ossyya_I' + */ + + const client = new OnePanelClient({ + access: this.access, + http: this.http, + logger: this.logger, + utils: this.ctx.utils, + }); + + const sslIds = this.sslIds; + for (const sslId of sslIds) { + try { + const certRes = await this.get1PanelCertInfo(client, sslId); + if (!this.isNeedUpdate(certRes)) { + continue; + } + + const uploadRes = await client.doRequest({ + url: `/api/${this.access.apiVersion}/websites/ssl/upload`, + method: "post", + data: { + sslIds, + certificate: this.cert.crt, + certificatePath: "", + description: certRes.description || this.appendTimeSuffix("certd"), + privateKey: this.cert.key, + privateKeyPath: "", + sslID: sslId, + type: "paste", + }, + currentNode: this.currentNode, + }); + console.log("uploadRes", JSON.stringify(uploadRes)); + } catch (e) { + this.logger.warn(`更新证书(id:${sslId})失败`, e); + this.logger.info("可能1Panel正在重启,等待10秒后检查证书是否更新成功"); + await this.ctx.utils.sleep(10000); + const certRes = await this.get1PanelCertInfo(client, sslId); + if (!this.isNeedUpdate(certRes)) { + continue; + } + throw e; + } + } + } + + isNeedUpdate(certRes: any) { + if (certRes.pem === this.cert.crt && certRes.key === this.cert.key) { + this.logger.info(`证书(id:${certRes.id})已经是最新的了,不需要更新`); + return false; + } + return true; + } + + async get1PanelCertInfo(client: OnePanelClient, sslId: string) { + const certRes = await client.doRequest({ + url: `/api/${this.access.apiVersion}/websites/ssl/${sslId}`, + method: "get", + currentNode: this.currentNode, + }); + if (!certRes) { + throw new Error(`没有找到证书(id:${sslId}),请先在1Panel中手动上传证书,后续才可以自动更新`); + } + return certRes; + } + + async onGetNodes() { + const options = [{ label: "主节点", value: "local" }]; + if (this.access.apiVersion === "v1") { + return options; + } + if (!this.access) { + throw new Error("请先选择授权"); + } + const client = new OnePanelClient({ + access: this.access, + http: this.http, + logger: this.logger, + utils: this.ctx.utils, + }); + + const resp = await client.doRequest({ + url: `/api/${this.access.apiVersion}/core/nodes/list`, + method: "post", + data: {}, + }); + + // console.log('resp', resp) + return [...options, ...(resp?.map(item => ({ label: `${item.addr}(${item.name})`, value: item.name })) || [])]; + } + + // requestHandle + async onGetSSLIds() { + // if (!isPlus()) { + // throw new Error("自动获取站点列表为专业版功能,您可以手动输入证书id进行部署"); + // } + if (!this.access) { + throw new Error("请先选择授权"); + } + const client = new OnePanelClient({ + access: this.access, + http: this.http, + logger: this.logger, + utils: this.ctx.utils, + }); + const res = await client.doRequest({ + url: `api/${this.access.apiVersion}/websites/ssl/search`, + method: "post", + data: { + page: 1, + pageSize: 99999, + }, + currentNode: this.currentNode, + }); + if (!res?.items) { + throw new Error("没有找到证书,请先在1Panel中手动上传证书,并关联站点,后续才可以自动更新"); + } + const list = res.items.map(item => { + const domains = item.domains ? [] : item.domains.split(","); + const allDomains = [item.primaryDomain, ...domains]; + return { + label: `${item.primaryDomain}<${item.id},${item.description || "无备注"}>`, + value: item.id, + domain: allDomains, + }; + }); + return this.ctx.utils.options.buildGroupOptions(list, this.certDomains); + } +} +new OnePanelDeployToWebsitePlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/1panel/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/plugins/index.ts new file mode 100644 index 000000000..1c360e693 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/plugins/index.ts @@ -0,0 +1 @@ +export * from "./deploy-to-website.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/1panel/util.ts b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/util.ts new file mode 100644 index 000000000..c7f20ef79 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/1panel/util.ts @@ -0,0 +1,65 @@ +import CryptoJS from "crypto-js"; +import crypto from "crypto"; +function rsaEncrypt(data: string, publicKey: string) { + if (!data) { + return data; + } + // const jsEncrypt = new JSEncrypt(); + // jsEncrypt.setPublicKey(publicKey); + // return jsEncrypt.encrypt(data); + + // RSA encryption is not supported in browser + //换一种nodejs的实现 + return crypto + .publicEncrypt( + { + key: publicKey, + padding: crypto.constants.RSA_PKCS1_PADDING, // 明确指定填充方式 + }, + Buffer.from(data, "utf-8") + ) + .toString("base64"); +} + +function aesEncrypt(data: string, key: string) { + const keyBytes = CryptoJS.enc.Utf8.parse(key); + const iv = CryptoJS.lib.WordArray.random(16); + const encrypted = CryptoJS.AES.encrypt(data, keyBytes, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }); + return iv.toString(CryptoJS.enc.Base64) + ":" + encrypted.toString(); +} + +function urlDecode(value: string): string { + return decodeURIComponent(value.replace(/\+/g, " ")); +} + +function generateAESKey(): string { + const keyLength = 16; + const randomBytes = new Uint8Array(keyLength); + crypto.getRandomValues(randomBytes); + return Array.from(randomBytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +export const encryptPassword = (rsaPublicKeyText: string, password: string) => { + if (!password) { + return ""; + } + // let rsaPublicKeyText = getCookie("panel_public_key"); + if (!rsaPublicKeyText) { + console.log("RSA public key not found"); + return password; + } + rsaPublicKeyText = urlDecode(rsaPublicKeyText); + + const aesKey = generateAESKey(); + rsaPublicKeyText = rsaPublicKeyText.replaceAll('"', ""); + const rsaPublicKey = atob(rsaPublicKeyText); + const keyCipher = rsaEncrypt(aesKey, rsaPublicKey); + const passwordCipher = aesEncrypt(password, aesKey); + return `${keyCipher}:${passwordCipher}`; +}; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/alipay/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/alipay/access.ts new file mode 100644 index 000000000..ee5c5c484 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/alipay/access.ts @@ -0,0 +1,48 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +@IsAccess({ + name: "alipay", + title: "支付宝", + icon: "ion:logo-alipay", +}) +export class AlipayAccess extends BaseAccess { + /** + * appId: "<-- 请填写您的AppId,例如:2019091767145019 -->", + * privateKey: "<-- 请填写您的应用私钥,例如:MIIEvQIBADANB ... ... -->", + * alipayPublicKey: "<-- 请填写您的支付宝公钥,例如:MIIBIjANBg... -->", + */ + @AccessInput({ + title: "AppId", + component: { + placeholder: "201909176714xxxx", + }, + required: true, + encrypt: false, + }) + appId: string; + + @AccessInput({ + title: "应用私钥", + component: { + placeholder: "MIIEvQIBADANB...", + name: "a-textarea", + rows: 3, + }, + required: true, + encrypt: true, + }) + privateKey: string; + + @AccessInput({ + title: "支付宝公钥", + component: { + name: "a-textarea", + rows: 3, + placeholder: "MIIBIjANBg...", + }, + required: true, + encrypt: true, + }) + alipayPublicKey: string; +} + +new AlipayAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/alipay/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/alipay/index.ts new file mode 100644 index 000000000..655094d02 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/alipay/index.ts @@ -0,0 +1 @@ +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baidu/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/access.ts new file mode 100644 index 000000000..b6517646e --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/access.ts @@ -0,0 +1,61 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +import { BaiduYunCertClient } from "./client.js"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "baidu", + title: "百度云授权", + desc: "", + icon: "ant-design:baidu-outlined", + order: 2, +}) +export class BaiduAccess extends BaseAccess { + @AccessInput({ + title: "AccessKey", + component: { + placeholder: "AccessKey", + }, + helper: "[百度智能云->安全认证获取](https://console.bce.baidu.com/iam/#/iam/accesslist)", + required: true, + encrypt: false, + }) + accessKey = ""; + + @AccessInput({ + title: "SecretKey", + component: { + placeholder: "SecretKey", + }, + helper: "", + required: true, + encrypt: true, + }) + secretKey = ""; + + @AccessInput({ + title: "测试", + component: { + name: "api-test", + action: "onTestRequest", + }, + helper: "点击测试接口看是否正常", + }) + testRequest = true; + + async onTestRequest() { + const certClient = new BaiduYunCertClient({ + access: this, + logger: this.ctx.logger, + http: this.ctx.http, + }); + + const res = await certClient.getCertList(); + this.ctx.logger.info("测试接口返回", res); + return "ok"; + } +} + +new BaiduAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baidu/client.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/client.ts new file mode 100644 index 000000000..2e9d84fb9 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/client.ts @@ -0,0 +1,191 @@ +import { HttpClient, HttpRequestConfig, ILogger } from "@certd/basic"; +import { BaiduAccess } from "./access.js"; +import crypto from "crypto"; +import { CertInfo } from "@certd/plugin-cert"; + +export type BaiduYunClientOptions = { + access: BaiduAccess; + logger: ILogger; + http: HttpClient; +}; +export type BaiduYunReq = { + host: string; + uri: string; + body?: any; + headers?: any; + query?: any; + method: string; +}; +export class BaiduYunClient { + opts: BaiduYunClientOptions; + constructor(opts: BaiduYunClientOptions) { + this.opts = opts; + } + + // 调用百度云接口,传接口uri和json参数 + async doRequest(req: BaiduYunReq, config?: HttpRequestConfig) { + const host = req.host; + const timestamp = this.getTimestampString(); + const queryString = this.getQueryString(req.query); + const Authorization = this.getAuthString(host, req.method, req.uri, queryString, timestamp); + + const ContentType = "application/json; charset=utf-8"; + + let url = "https://" + host + req.uri; + if (req.query) { + url += "?" + queryString; + } + const res = await this.opts.http.request({ + url: url, + method: req.method, + data: req.body, + headers: { + Authorization: Authorization, + "Content-Type": ContentType, + Host: host, + "x-bce-date": timestamp, + ...req.headers, + }, + ...config, + }); + if (res.code) { + throw new Error(`请求失败:${res.message}`); + } + return res; + } + + // 获取UTC时间 + getTimestampString() { + return new Date().toISOString().replace(/\.\d*/, ""); + } + + // 获取参数拼接字符串 + getQueryString(params) { + let queryString = ""; + let paramKeyArray = []; + if (params) { + for (const key in params) { + paramKeyArray.push(key); + } + paramKeyArray = paramKeyArray.sort(); + } + if (paramKeyArray && paramKeyArray.length > 0) { + for (const key of paramKeyArray) { + queryString += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]) + "&"; + } + queryString = queryString.substring(0, queryString.length - 1); + } + return queryString; + } + + uriEncode(input: string, encodeSlash = false) { + let result = ""; + + for (let i = 0; i < input.length; i++) { + const ch = input.charAt(i); + + if ((ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9") || ch === "_" || ch === "-" || ch === "~" || ch === ".") { + result += ch; + } else if (ch === "/") { + result += encodeSlash ? "%2F" : ch; + } else { + result += this.toHexUTF8(ch); + } + } + + return result; + } + + toHexUTF8(ch) { + // Convert character to UTF-8 bytes and return the hex representation + const utf8Bytes = new TextEncoder().encode(ch); + let hexString = ""; + + for (const byte of utf8Bytes) { + hexString += "%" + byte.toString(16).padStart(2, "0").toUpperCase(); + } + + return hexString; + } + + // 签名 + getAuthString(Host: string, Method: string, CanonicalURI: string, CanonicalQueryString: string, timestamp: string) { + // 1 + const expirationPeriodInSeconds = 120; + const authStringPrefix = `bce-auth-v1/${this.opts.access.accessKey}/${timestamp}/${expirationPeriodInSeconds}`; + // 2 + const signedHeaders = "host;x-bce-date"; + const CanonicalHeaders = encodeURIComponent("host") + ":" + encodeURIComponent(Host) + "\n" + encodeURIComponent("x-bce-date") + ":" + encodeURIComponent(timestamp); + const CanonicalRequest = Method.toUpperCase() + "\n" + this.uriEncode(CanonicalURI, false) + "\n" + CanonicalQueryString + "\n" + CanonicalHeaders; + // 3 + const SigningKey = crypto.createHmac("sha256", this.opts.access.secretKey).update(authStringPrefix).digest().toString("hex"); + // 4 + const Signature = crypto.createHmac("sha256", SigningKey).update(CanonicalRequest).digest().toString("hex"); + // 5 + return `${authStringPrefix}/${signedHeaders}/${Signature}`; + } +} + +export class BaiduYunCertClient { + client: BaiduYunClient; + constructor(opts: BaiduYunClientOptions) { + this.client = new BaiduYunClient(opts); + } + + async createCert(opts: { certName: string; cert: CertInfo }) { + // /v1/certificate + const res = await this.client.doRequest({ + host: "certificate.baidubce.com", + uri: `/v1/certificate`, + method: "post", + body: { + /** + * certName String 必须 证书的名称。长度限制为1-65个字符,以字母开头,只允许包含字母、数字、’-‘、’/’、’.’、’’,Java正则表达式` ^[a-zA-Z]a-zA-Z0-9\-/\.]{2,64}$` + * certServerData String 必须 服务器证书的数据内容 (Base64编码) + * certPrivateData String 必须 证书的私钥数据内容 (Base64编码) + */ + certName: "certd_" + opts.certName, // 字母开头,且小于64长度 + certServerData: opts.cert.crt, + certPrivateData: opts.cert.key, + }, + }); + return res; + } + + async getCertList() { + /** + * GET /v1/certificate HTTP/1.1 + * HOST: certificate.baidubce.com + * Authorization: {authorization} + * Content-Type: application/json; charset=utf-8 + * x-bce-date: 2014-06-01T23:00:10Z + */ + return await this.client.doRequest({ + host: "certificate.baidubce.com", + uri: `/v1/certificate`, + method: "get", + }); + } + + async updateCert(opts: { certId: string; certName: string; cert: CertInfo }) { + /** + * /v1/certificate/{certId}?certData + * certName String 必须 证书的名称。长度限制为1-65个字符,以字母开头,只允许包含字母、数字、’-‘、’/’、’.’、’’,Java正则表达式` ^[a-zA-Z]a-zA-Z0-9\-/\.]{2,64}$` + * certServerData String 必须 服务器证书的数据内容 (Base64编码) + * certPrivateData String 必须 证书的私钥数据内容 (Base64编码) + * certLinkData String 可选 证书链数据内容 (Base64编码) + * certType Integer 可选 证书类型,非必填,默认为1 + */ + + return await this.client.doRequest({ + host: "certificate.baidubce.com", + uri: `/v1/certificate/${opts.certId}`, + method: "put", + body: { + certName: opts.certName, + certServerData: opts.cert.crt, + certPrivateData: opts.cert.key, + }, + }); + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baidu/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/index.ts new file mode 100644 index 000000000..09ca4f9f1 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/index.ts @@ -0,0 +1,3 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; +export * from "./client.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/index.ts new file mode 100644 index 000000000..02a15f578 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/index.ts @@ -0,0 +1,3 @@ +export * from "./plugin-deploy-to-cdn.js"; +export * from "./plugin-deploy-to-blb.js"; +export * from "./plugin-upload-to-baidu.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-deploy-to-blb.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-deploy-to-blb.ts new file mode 100644 index 000000000..36697347b --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-deploy-to-blb.ts @@ -0,0 +1,323 @@ +import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine } from "@certd/plugin-lib"; +import { BaiduYunCertClient, BaiduYunClient } from "../client.js"; + +@IsTaskPlugin({ + name: "BaiduDeployToBLB", + title: "百度云-部署证书到负载均衡", + icon: "ant-design:baidu-outlined", + group: pluginGroups.baidu.key, + desc: "部署到百度云负载均衡,包括BLB、APPBLB", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class BaiduDeployToBLBPlugin extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames, "BaiduUploadCert"], + }, + required: true, + }) + cert!: CertInfo | string; + + @TaskInput(createCertDomainGetterInputDefine()) + certDomains!: string[]; + + @TaskInput({ + title: "区域", + component: { + name: "a-select", + vModel: "value", + options: [ + /** + * 北京 blb.bj.baidubce.com HTTP/HTTPS + * 广州 blb.gz.baidubce.com HTTP/HTTPS + * 苏州 blb.su.baidubce.com HTTP/HTTPS + * 香港 blb.hkg.baidubce.com HTTP/HTTPS + * 武汉 blb.fwh.baidubce.com HTTP/HTTPS + * 保定 blb.bd.baidubce.com HTTP/HTTPS + * 上海 blb.fsh.baidubce.com HTTP/HTTPS + * 新加坡 blb.sin.baidubce.com HTTP/HTTPS + */ + { value: "bj", label: "北京" }, + { value: "fsh", label: "上海" }, + { value: "gz", label: "广州" }, + { value: "fwh", label: "武汉" }, + { value: "su", label: "苏州" }, + { value: "bd", label: "保定" }, + { value: "hkg", label: "香港" }, + { value: "sin", label: "新加坡" }, + ], + }, + required: true, + }) + region!: string; + + @TaskInput({ + title: "负载均衡类型", + component: { + name: "a-select", + vModel: "value", + options: [ + { value: "blb", label: "普通负载均衡" }, + { value: "appblb", label: "应用负载均衡" }, + ], + }, + required: true, + }) + blbType!: string; + + //授权选择框 + @TaskInput({ + title: "百度云授权", + helper: "百度云授权", + component: { + name: "access-selector", + type: "baidu", + }, + required: true, + }) + accessId!: string; + + @TaskInput({ + title: "负载均衡ID", + component: { + name: "remote-select", + vModel: "value", + mode: "tags", + action: "GetBLBList", + watches: ["certDomains", "blbType", "accessId"], + }, + required: true, + }) + blbIds!: string[]; + + @TaskInput({ + title: "监听器ID", + component: { + name: "remote-select", + vModel: "value", + mode: "tags", + action: "GetListenerList", + watches: ["certDomains", "accessId", "blbIds"], + }, + required: true, + }) + listenerIds!: string[]; + + async onInstance() {} + + async execute(): Promise { + this.logger.info("开始更新百度云监听器证书"); + const access = await this.getAccess(this.accessId); + const certClient = new BaiduYunCertClient({ + access, + logger: this.logger, + http: this.ctx.http, + }); + + let certId = this.cert as string; + if (typeof this.cert !== "string") { + this.logger.info("上传证书到百度云"); + const res = await certClient.createCert({ + cert: this.cert, + certName: CertReader.buildCertName(this.cert), + }); + certId = res.certId; + this.logger.info(`上传证书到百度云成功:${certId}`); + } + + const baiduyunClient = new BaiduYunClient({ + access, + logger: this.logger, + http: this.ctx.http, + }); + for (const listenerId of this.listenerIds) { + const listenerParams = listenerId.split("_"); + const blbId = listenerParams[0]; + const listenerType = listenerParams[1]; + const listenerPort = listenerParams[2]; + let additionalCertHost = null; + if (listenerParams.length > 3) { + additionalCertHost = listenerParams[3]; + } + this.logger.info(`更新监听器证书开始:${listenerId}`); + if (!additionalCertHost) { + await this.updateListenerCert({ + client: baiduyunClient, + blbId, + listenerType, + listenerPort, + certId, + }); + } else { + const listenerDomains = await this.getListeners(baiduyunClient, blbId, listenerType, listenerPort); + if (!listenerDomains || listenerDomains.length === 0) { + throw new Error(`未找到监听器:${listenerId}`); + } + const oldAdditionals = listenerDomains[0].additionalCertDomains; + for (const oldAddi of oldAdditionals) { + if (oldAddi.host === additionalCertHost) { + oldAddi.certId = certId; + } + } + await this.updateListenerCert({ + client: baiduyunClient, + blbId, + listenerType, + listenerPort, + certId, + additionalCertDomains: oldAdditionals, + }); + } + this.logger.info(`更新监听器证书成功:${listenerId}`); + await this.ctx.utils.sleep(3000); + } + + this.logger.info(`更新百度云监听器证书完成`); + } + + async onGetListenerList(data: PageSearch = {}) { + const access = await this.getAccess(this.accessId); + const client = new BaiduYunClient({ + access, + logger: this.logger, + http: this.ctx.http, + }); + + const listeners = []; + for (const blbId of this.blbIds) { + /** + * GET /v{version}/appblb/{blbId}/TCPlistener?listenerPort={listenerPort}&marker={marker}&maxKeys={maxKeys} HTTP/1.1 + * Host: blb.bj.baidubce.com + */ + const listenerTypes = ["HTTPSlistener", "SSLlistener"]; + for (const listenerType of listenerTypes) { + const list = await this.getListeners(client, blbId, listenerType); + if (list && list.length > 0) { + for (const item of list) { + const key = `${blbId}_${listenerType}_${item.listenerPort}`; + listeners.push({ + value: key, + label: key, + }); + + if (item.additionalCertDomains && item.additionalCertDomains.length > 0) { + for (const addi of item.additionalCertDomains) { + const addiKey = `${key}_${addi.host}`; + listeners.push({ + value: addiKey, + label: `${addiKey}【扩展】`, + }); + } + } + } + } + } + } + + if (!listeners || listeners.length === 0) { + throw new Error("未找到https/SSL监听器"); + } + return listeners; + } + + private async getListeners(client: BaiduYunClient, blbId: string, listenerType: string, listenerPort?: number | string) { + const query: any = { + maxItems: 1000, + }; + if (listenerPort) { + query.listenerPort = listenerPort; + } + const res = await client.doRequest({ + host: `blb.${this.region}.baidubce.com`, + uri: `/v1/${this.blbType}/${blbId}/${listenerType}`, + method: "GET", + query, + }); + return res.listenerList; + } + + async onGetBLBList(data: PageSearch = {}) { + const access = await this.getAccess(this.accessId); + const client = new BaiduYunClient({ + access, + logger: this.logger, + http: this.ctx.http, + }); + + /** + * GET /v{version}/appblb?address={address}&name={name}&blbId={blbId}&marker={marker}&maxKeys={maxKeys} HTTP/1.1 + * Host: blb.bj.baidubce.com + */ + const res = await client.doRequest({ + host: `blb.${this.region}.baidubce.com`, + uri: `/v1/${this.blbType}`, + method: "GET", + query: { + maxItems: 1000, + }, + }); + + const list = res.blbList; + + if (!list || list.length === 0) { + throw new Error("没有数据,你可以手动输入"); + } + const options: any[] = []; + for (const item of list) { + options.push({ + value: item.blbId, + label: item.name, + }); + } + return options; + } + + private async updateListenerCert(param: { client: BaiduYunClient; blbId: string; listenerType: string; listenerPort: string; certId?: any; additionalCertDomains?: any[] }) { + /** + * PUT /v{version}/appblb/{blbId}/SSLlistener?clientToken={clientToken}&listenerPort={listenerPort} HTTP/1.1 + * Host: blb.bj.baidubce.com + * Authorization: authorization string + * + * { + * "scheduler":scheduler, + * "certIds":[certId], + * "encryptionType":encryptionType, + * "encryptionProtocols":[protocol1, protacol2], + * "dualAuth":false, + * "clientCertIds":[clientCertId], + * "description":description + * } + */ + const { client, blbId, listenerType, listenerPort, certId, additionalCertDomains } = param; + const body: any = {}; + if (additionalCertDomains) { + body.additionalCertDomains = additionalCertDomains; + } + if (certId) { + body.certIds = [certId]; + } + const res = await client.doRequest({ + host: `blb.${this.region}.baidubce.com`, + uri: `/v1/${this.blbType}/${blbId}/${listenerType}`, + method: "PUT", + query: { + listenerPort, + }, + body, + }); + return res; + } +} + +new BaiduDeployToBLBPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-deploy-to-cdn.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-deploy-to-cdn.ts new file mode 100644 index 000000000..e8b48e069 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-deploy-to-cdn.ts @@ -0,0 +1,145 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; +import { BaiduYunCertClient, BaiduYunClient } from "../client.js"; +import { createCertDomainGetterInputDefine } from "@certd/plugin-lib"; + +@IsTaskPlugin({ + name: "BaiduDeployToCDN", + title: "百度云-部署证书到CDN", + icon: "ant-design:baidu-outlined", + group: pluginGroups.baidu.key, + desc: "部署到百度云CDN", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class BaiduDeployToCDNPlugin extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames, "BaiduUploadCert"], + }, + required: true, + }) + cert!: CertInfo | string; + + @TaskInput(createCertDomainGetterInputDefine()) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "百度云授权", + helper: "百度云授权", + component: { + name: "access-selector", + type: "baidu", + }, + required: true, + }) + accessId!: string; + + //证书选择,此项必须要有 + @TaskInput({ + title: "CDN域名", + component: { + name: "remote-select", + vModel: "value", + mode: "tags", + action: "GetDomainList", + watches: ["certDomains", "accessId"], + }, + required: true, + }) + domains!: string[]; + + async onInstance() {} + + async execute(): Promise { + const access = await this.getAccess(this.accessId); + const client = new BaiduYunClient({ + access, + logger: this.logger, + http: this.ctx.http, + }); + + const certClient = new BaiduYunCertClient({ + access, + logger: this.logger, + http: this.ctx.http, + }); + + let certId = this.cert as string; + if (typeof this.cert !== "string") { + this.logger.info("上传证书到百度云"); + const res = await certClient.createCert({ + cert: this.cert, + certName: CertReader.buildCertName(this.cert), + }); + certId = res.certId; + this.logger.info(`上传证书到百度云成功:${certId}`); + } + + const body = { + https: { + enabled: true, + certId: certId, + }, + }; + for (const domain of this.domains) { + await client.doRequest({ + host: "cdn.baidubce.com", + uri: `/v2/domain/${domain}/config`, + body, + query: { + https: "", + }, + method: "put", + }); + this.logger.info(`部署证书到${domain}成功`); + } + } + + async onGetDomainList() { + // if (!isPlus()) { + // throw new Error("自动获取站点列表为专业版功能,您可以手动输入站点域名/站点名称进行部署"); + // } + const access = await this.getAccess(this.accessId); + const client = new BaiduYunClient({ + access, + logger: this.logger, + http: this.ctx.http, + }); + + const res = await client.doRequest({ + host: "cdn.baidubce.com", + uri: `/v2/domain`, + method: "GET", + query: { + maxItems: 1000, + }, + }); + + const list = res.domains; + + if (!list || list.length === 0) { + throw new Error("未找到加速域名,你可以手动输入"); + } + const options: any[] = []; + for (const item of list) { + options.push({ + value: item.name, + label: item.name, + domain: item.name, + }); + } + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} + +new BaiduDeployToCDNPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-upload-to-baidu.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-upload-to-baidu.ts new file mode 100644 index 000000000..c4374e4ea --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baidu/plugins/plugin-upload-to-baidu.ts @@ -0,0 +1,68 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert"; +import { BaiduAccess } from "../access.js"; +import { BaiduYunCertClient } from "../client.js"; + +@IsTaskPlugin({ + name: "BaiduUploadCert", + title: "百度云-上传到证书托管", + icon: "ant-design:baidu-outlined", + desc: "上传证书到百度云证书托管中心", + group: pluginGroups.baidu.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class BaiduUploadCert extends AbstractTaskPlugin { + // @TaskInput({ title: '证书名称' }) + // name!: string; + + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: any; + + @TaskInput({ + title: "Access授权", + helper: "access授权", + component: { + name: "access-selector", + type: "baidu", + }, + required: true, + }) + accessId!: string; + + @TaskOutput({ + title: "百度云CertId", + }) + baiduCertId?: string; + + async execute(): Promise { + const access = await this.getAccess(this.accessId); + + const certClient = new BaiduYunCertClient({ + access, + logger: this.logger, + http: this.http, + }); + + const certItem = await certClient.createCert({ + cert: this.cert, + certName: CertReader.buildCertName(this.cert), + }); + + this.baiduCertId = certItem.certId; + this.logger.info(`上传成功,证书ID:${certItem.certId}`); + } +} + +new BaiduUploadCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baishan/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baishan/access.ts new file mode 100644 index 000000000..751eb5b67 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baishan/access.ts @@ -0,0 +1,26 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "baishan", + title: "白山云授权", + desc: "", + icon: "material-symbols:shield-outline", +}) +export class BaishanAccess extends BaseAccess { + @AccessInput({ + title: "token", + component: { + placeholder: "token", + }, + helper: "自行联系提供商申请", + required: true, + encrypt: true, + }) + token = ""; +} + +new BaishanAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baishan/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baishan/index.ts new file mode 100644 index 000000000..02dc3945d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baishan/index.ts @@ -0,0 +1,2 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baishan/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baishan/plugins/index.ts new file mode 100644 index 000000000..c924ba319 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baishan/plugins/index.ts @@ -0,0 +1 @@ +export * from "./plugin-update-cert.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baishan/plugins/plugin-update-cert.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baishan/plugins/plugin-update-cert.ts new file mode 100644 index 000000000..bcc944f4f --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baishan/plugins/plugin-update-cert.ts @@ -0,0 +1,103 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { BaishanAccess } from "../access.js"; + +@IsTaskPlugin({ + name: "BaishanUpdateCert", + title: "白山云-更新证书", + icon: "material-symbols:shield-outline", + group: pluginGroups.cdn.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class BaishanUpdateCert extends AbstractTaskPlugin { + //测试参数 + @TaskInput({ + title: "证书ID", + component: { + name: "a-input-number", + vModel: "value", + }, + helper: "证书ID,在证书管理页面查看,每条记录都有证书id", + }) + certId!: number; + + //测试参数 + @TaskInput({ + title: "证书名称", + component: { + name: "a-input", + vModel: "value", + }, + helper: "给证书设置一个名字,便于区分", + }) + certName!: string; + + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + //授权选择框 + @TaskInput({ + title: "白山云授权", + helper: "白山云授权", + component: { + name: "access-selector", + type: "baishan", + }, + required: true, + }) + accessId!: string; + + async onInstance() {} + + async execute(): Promise { + const access = await this.getAccess(this.accessId); + // https://cdn.api.baishan.com/v2/domain/certificate + try { + const res = await this.ctx.http.request({ + url: "/v2/domain/certificate?token=" + access.token, + baseURL: "https://cdn.api.baishan.com", + method: "post", + data: { + cert_id: this.certId, + name: this.certName, + certificate: this.cert.crt, + key: this.cert.key, + }, + }); + + if (res.code !== 0) { + throw new Error("修改证书失败:" + res.message || res.msg || JSON.stringify(res)); + } + } catch (e: any) { + if (e.message?.indexOf("this certificate is exists") > -1) { + // this certificate is exists, cert_id is (224995) + //提取id + const id = e.message.match(/\d+/); + if (id && id.length > 0 && id[0] !== this.certId + "") { + throw new Error("证书已存在,但证书id不一致,当前证书id为" + this.certId + ",已存在证书id为" + id); + } + this.logger.info("证书已存在,无需更新"); + return; + } + throw e; + } + + this.logger.info("证书更新成功"); + } +} + +new BaishanUpdateCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/access.ts new file mode 100644 index 000000000..e1611c3c9 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/access.ts @@ -0,0 +1,86 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; +import { HttpClient } from "@certd/basic"; + +import { BaotaClient } from "./lib/client.js"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "baota", + title: "baota授权", + desc: "", + icon: "svg:icon-bt", + order: 2, +}) +export class BaotaAccess extends BaseAccess { + @AccessInput({ + title: "宝塔URL地址", + component: { + placeholder: "http://192.168.42.237:41896", + }, + helper: "宝塔面板的url地址,不要带安全入口,例如:http://192.168.42.237:41896", + required: true, + }) + panelUrl = ""; + + @AccessInput({ + title: "接口密钥", + component: { + placeholder: "接口密钥", + }, + helper: "宝塔面板设置->面板设置->API接口->接口配置->接口密钥。\n必须要加IP白名单,您可以点击下方测试按钮,报错之后会打印IP,将IP加入白名单之后再次测试即可", + required: true, + encrypt: true, + }) + apiSecret = ""; + + @AccessInput({ + title: "忽略证书校验", + value: true, + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭", + }) + skipSslVerify = true; + + @AccessInput({ + title: "windows版", + value: false, + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "是否是windows版", + }) + isWindows = false; + + @AccessInput({ + title: "测试", + component: { + name: "api-test", + action: "TestRequest", + }, + helper: "点击测试接口看是否正常", + }) + testRequest = true; + + async onTestRequest() { + const http: HttpClient = this.ctx.http; + const client = new BaotaClient(this, http); + + if (this.isWindows) { + await client.doWindowsRequest("/site/get_site_types", {}, { skipCheckRes: false }); + return "ok"; + } + const url = "/site?action=get_site_types"; + const data = {}; + await client.doRequest(url, null, data, { skipCheckRes: false }); + return "ok"; + } +} + +new BaotaAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/index.ts new file mode 100644 index 000000000..b755db5ce --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/index.ts @@ -0,0 +1,4 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; +export * from "./waf-access.js"; +export * from "./lib/client.js"; \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/lib/client.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/lib/client.ts new file mode 100644 index 000000000..f64a1b5a7 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/lib/client.ts @@ -0,0 +1,88 @@ +import crypto from "node:crypto"; +import { BaotaAccess } from "../access.js"; +import { HttpClient, HttpRequestConfig } from "@certd/basic"; +import * as querystring from "node:querystring"; + +export class BaotaClient { + access: BaotaAccess; + http: HttpClient; + + constructor(access: BaotaAccess, http: HttpClient) { + this.access = access; + this.http = http; + } + + //将以上 java代码 翻译成nodejs 代码 + getRequestToken() { + const timestamps = Math.floor(new Date().getTime() / 1000); + const md5Sign = this.getMd5(this.access.apiSecret); + const temp = timestamps + md5Sign; + return { + request_token: this.getMd5(temp), + request_time: "" + timestamps, + }; + } + + getMd5(content: string) { + return crypto.createHash("md5").update(content).digest("hex"); + } + + async doRequest(path: string, action: string, data: any = {}, options?: HttpRequestConfig) { + const token = this.getRequestToken(); + const body = { + ...token, + ...data, + }; + const bodyStr = querystring.stringify(body); + // const agent = new https.Agent({ + // rejectUnauthorized: false, + // }); + let panelUrl = this.access.panelUrl; + if (panelUrl.endsWith("/")) { + panelUrl = panelUrl.substring(0, panelUrl.length - 1); + } + let url = `${panelUrl}${path}`; + if (action) { + url = `${url}?action=${action}`; + } + const res: any = await this.http.request({ + url: url, + method: "post", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + data: bodyStr, + // httpsAgent: agent, + ...options, + skipSslVerify: this.access.skipSslVerify ?? true, + }); + if (!options?.skipCheckRes && res.status === false) { + throw new Error(res.msg); + } + return res; + } + + async doWindowsRequest(path: string, data: any, options?: HttpRequestConfig) { + const token = this.getRequestToken(); + const body = { + ...token, + ...data, + }; + // const agent = new https.Agent({ + // rejectUnauthorized: false, + // }); + const url = `${this.access.panelUrl}${path}`; + const res: any = await this.http.request({ + url: url, + method: "post", + data: body, + // httpsAgent: agent, + ...options, + skipSslVerify: this.access.skipSslVerify ?? true, + }); + if (!options?.skipCheckRes && res.status === false) { + throw new Error(res.msg); + } + return res; + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/index.ts new file mode 100644 index 000000000..e99d24e32 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/index.ts @@ -0,0 +1,5 @@ +export * from "./plugin-deploy-to-panel.js"; +export * from "./plugin-deploy-to-website.js"; +export * from "./plugin-deploy-to-aawaf.js"; +export * from "./plugin-deploy-to-website-win.js"; +export * from "./plugin-delete-expiring-cert.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-delete-expiring-cert.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-delete-expiring-cert.ts new file mode 100644 index 000000000..60a2e4eb1 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-delete-expiring-cert.ts @@ -0,0 +1,69 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { BaotaClient } from "../lib/client.js"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +import dayjs from "dayjs"; +@IsTaskPlugin({ + name: "BaotaDeleteExpiringCert", + title: "宝塔-删除过期证书", + icon: "svg:icon-bt", + group: pluginGroups.panel.key, + desc: "删除证书夹中过期证书", + showRunStrategy: true, + default: { + strategy: { + runStrategy: RunStrategy.AlwaysRun, + }, + }, + needPlus: true, +}) +export class BaotaDeleteExpiringCert extends AbstractPlusTaskPlugin { + //授权选择框 + @TaskInput({ + title: "宝塔授权", + helper: "baota的接口密钥", + component: { + name: "access-selector", + type: "baota", + }, + required: true, + }) + accessId!: string; + + async onInstance() {} + async execute(): Promise { + const { accessId } = this; + const access = await this.getAccess(accessId); + const http = this.ctx.http; + const client = new BaotaClient(access, http); + + //https://baota.docmirror.cn:20001/ssl?action=get_cert_list + const res = await client.doRequest("/ssl", "get_cert_list", null); + + const now = new Date().getTime(); + for (const item of res) { + if (dayjs(item.not_after).valueOf() < now) { + //https://baota.docmirror.cn:20001/ssl?action=remove_cloud_cert + /** + * local: 1 + * ssl_hash: fbe087d5253b78ba37264486415181ab + */ + this.logger.info(`证书: ${item.name} 过期时间: ${item.not_after},已过期,删除`); + try { + await client.doRequest("/ssl", "remove_cloud_cert", { + local: 1, + ssl_hash: item.hash, + }); + } catch (e) { + this.logger.error(`删除证书: ${item.name} 失败`, e); + } + + await this.ctx.utils.sleep(1000); + } else { + this.logger.info(`证书: ${item.name} 过期时间: ${item.not_after},未过期`); + } + } + + this.logger.info(res?.msg); + } +} +new BaotaDeleteExpiringCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-aawaf.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-aawaf.ts new file mode 100644 index 000000000..9a2bf90f7 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-aawaf.ts @@ -0,0 +1,143 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; + +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine } from "@certd/plugin-lib"; +import { BaotaWafAccess } from "../waf-access.js"; + +type SiteItem = { + value: string; + label: string; + domain: string; +}; +@IsTaskPlugin({ + name: "BaotaDeployWAF", + title: "宝塔-WAF证书部署", + icon: "svg:icon-bt", + group: pluginGroups.panel.key, + desc: "部署宝塔云WAF/aaWAF", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class BaotaDeployWAF extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine()) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "宝塔WAF授权", + helper: "aaWAF的接口密钥", + component: { + name: "access-selector", + type: "baotawaf", + }, + required: true, + }) + accessId!: string; + + //证书选择,此项必须要有 + @TaskInput({ + title: "站点ID", + component: { + name: "remote-select", + vModel: "value", + mode: "tags", + action: "onGetSiteList", + search: true, + watches: ["certDomains", "accessId"], + }, + required: true, + helper: "将会自动获取证书匹配的站点,请选择要部署证书的站点", + }) + siteIds!: string[]; + + async onInstance() {} + async execute(): Promise { + const { cert, accessId } = this; + const access = await this.getAccess(accessId); + + const lockKey = `baota-lock-${accessId}`; + + for (const siteId of this.siteIds) { + this.logger.info(`为站点:${siteId}设置证书`); + const info = await this.getSiteInfo(access, siteId); + const listen_ssl_port = info.server.listen_ssl_port; + await this.ctx.utils.locker.execute(lockKey, async () => { + await access.doRequest({ + url: "/api/wafmastersite/modify_site", + data: { + types: "openCert", + site_id: siteId, + server: { + listen_ssl_port, + ssl: { is_ssl: 1, full_chain: cert.crt, private_key: cert.key }, + }, + }, + }); + }); + this.logger.info(`站点:${siteId} 证书部署成功`); + } + this.logger.info(`部署成功`); + } + + async getSiteInfo(access: BaotaWafAccess, siteId: string) { + // /api/wafmastersite/get_site_list + const res = await access.doRequest({ + url: "/api/wafmastersite/get_site_list", + data: { + site_id: siteId, + p_size: 1, + p: 1, + site_name: "", + }, + }); + + if (!res.list || res.list.length === 0) { + throw new Error(`未找到站点:${siteId}`); + } + return res.list[0]; + } + + async onGetSiteList(data: { searchKey?: string }) { + // if (!isPlus()) { + // throw new Error("自动获取站点列表为专业版功能,您可以手动输入站点域名/站点名称进行部署"); + // } + const access = await this.getAccess(this.accessId); + + const res = await access.getSiteList({ + pageNo: 1, + pageSize: 100, + query: data.searchKey || "", + }); + + const list = res.list; + if (!list || list.length === 0) { + throw new Error("未找到站点,你可以手动输入"); + } + const options: SiteItem[] = []; + for (const item of list) { + options.push({ + value: item.site_id, + label: `${item.site_name}<${item.site_id}>`, + domain: item.server.server_name, + }); + } + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} +new BaotaDeployWAF(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-panel.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-panel.ts new file mode 100644 index 000000000..95b5de3bf --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-panel.ts @@ -0,0 +1,68 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertInfo } from "@certd/plugin-cert"; +import { BaotaClient } from "../lib/client.js"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +import { CertApplyPluginNames } from "@certd/plugin-cert"; +@IsTaskPlugin({ + name: "BaotaDeployPanelCert", + title: "宝塔-面板证书部署", + icon: "svg:icon-bt", + group: pluginGroups.panel.key, + desc: "部署宝塔面板本身的ssl证书", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: true, +}) +export class BaotaDeployPanelCertPlugin extends AbstractPlusTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + //授权选择框 + @TaskInput({ + title: "宝塔授权", + helper: "baota的接口密钥", + component: { + name: "access-selector", + type: "baota", + }, + required: true, + }) + accessId!: string; + + async onInstance() {} + async execute(): Promise { + const { cert, accessId } = this; + const access = await this.getAccess(accessId); + const http = this.ctx.http; + const client = new BaotaClient(access, http); + + const lockKey = `baota-lock-${accessId}`; + await this.ctx.utils.locker.execute(lockKey, async () => { + const res = await client.doRequest( + "/config", + "SavePanelSSL", + { + privateKey: cert.key, + certPem: cert.crt, + }, + { + skipSslVerify: true, + } + ); + this.logger.info(res?.msg); + }); + } +} +new BaotaDeployPanelCertPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-website-win.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-website-win.ts new file mode 100644 index 000000000..e9fa99000 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-website-win.ts @@ -0,0 +1,143 @@ +import { HttpClient } from "@certd/basic"; +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; + +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { BaotaClient } from "../lib/client.js"; +import { createCertDomainGetterInputDefine } from "@certd/plugin-lib"; + +type SiteItem = { + value: string; + label: string; + domain: string; +}; +@IsTaskPlugin({ + name: "BaotaDeployWebSiteWin", + title: "宝塔win-网站证书部署", + icon: "svg:icon-bt", + group: pluginGroups.panel.key, + desc: "部署到Windows版宝塔管理的站点的ssl证书", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class BaotaDeployWebSiteWin extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine()) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "宝塔授权", + helper: "baota的接口密钥", + component: { + name: "access-selector", + type: "baota", + }, + required: true, + }) + accessId!: string; + + //证书选择,此项必须要有 + @TaskInput({ + title: "站点Id", + component: { + name: "remote-select", + vModel: "value", + mode: "tags", + action: "GetSiteList", + watches: ["certDomains", "accessId"], + }, + required: true, + mergeScript: ` + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + `, + helper: "将会自动获取证书匹配的站点名称", + }) + siteIds!: number[]; + + async onInstance() {} + async execute(): Promise { + const { cert, accessId } = this; + const access = await this.getAccess(accessId); + const http: HttpClient = this.ctx.http; + const client = new BaotaClient(access, http); + this.logger.info(`siteIds:${this.siteIds}`); + + const siteIds = this.siteIds ?? []; + + const lockKey = `baota-lock-${accessId}`; + for (const site of siteIds) { + await this.ctx.utils.locker.execute(lockKey, async () => { + this.logger.info(`为站点:${site}设置证书`); + const res = await client.doWindowsRequest("/site/set_site_ssl", { + siteid: site, + status: true, + sslType: "", + cert: cert.crt, + key: cert.key, + }); + this.logger.info(res?.msg); + }); + } + } + + async onGetSiteList() { + // if (!isPlus()) { + // throw new Error("自动获取站点列表为专业版功能,您可以手动输入站点域名/站点名称进行部署"); + // } + const access = await this.getAccess(this.accessId); + const http: HttpClient = this.ctx.http; + const client = new BaotaClient(access, http); + + const domains = this.certDomains; + let all = []; + const getPhpSite = async () => { + const url = "/datalist/get_data_list"; + const data = { + table: "sites", + limit: 500, + }; + const res = await client.doWindowsRequest(url, data, { skipCheckRes: false }); + this.logger.info(res.data); + all = res.data || []; + }; + + //查找docker 站点 + + await getPhpSite(); + + if (!all || all.length === 0) { + throw new Error("未找到站点,你可以手动输入"); + } + const options: SiteItem[] = []; + for (const item of all) { + options.push({ + value: item.id, + label: `${item.name}<${item.id}>`, + domain: item.name, + }); + } + return this.ctx.utils.options.buildGroupOptions(options, domains); + } +} +new BaotaDeployWebSiteWin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-website.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-website.ts new file mode 100644 index 000000000..f41e2b14a --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/plugins/plugin-deploy-to-website.ts @@ -0,0 +1,232 @@ +import { HttpClient } from "@certd/basic"; +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; + +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { BaotaClient } from "../lib/client.js"; +import { createCertDomainGetterInputDefine } from "@certd/plugin-lib"; +import { uniq } from "lodash-es"; + +export type SiteItem = { + value: string; + label: string; + domain: string; +}; +@IsTaskPlugin({ + name: "BaotaDeployWebSiteCert", + title: "宝塔-网站证书部署", + icon: "svg:icon-bt", + group: pluginGroups.panel.key, + desc: "部署宝塔管理的站点的ssl证书,目前支持宝塔网站站点、docker站点等。本插件也支持aaPanel。", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class BaotaDeployWebSiteCert extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine()) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "宝塔授权", + helper: "baota的接口密钥", + component: { + name: "access-selector", + type: "baota", + }, + required: true, + }) + accessId!: string; + + @TaskInput({ + title: "是否Docker站点", + value: false, + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "是否为docker站点", + required: true, + }) + isDockerSite = false; + + //证书选择,此项必须要有 + @TaskInput({ + title: "站点名称", + component: { + name: "remote-select", + vModel: "value", + mode: "tags", + action: "GetSiteList", + watches: ["certDomains", "accessId", "isDockerSite"], + }, + required: true, + mergeScript: ` + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + } + `, + helper: "将会自动获取证书匹配的站点名称\n宝塔版本低于9.0.0时,此处会获取失败,忽略错误,手动输入站点域名即可", + }) + siteName!: string | string[]; + + async onInstance() {} + async execute(): Promise { + const { cert, accessId } = this; + const access = await this.getAccess(accessId); + const http: HttpClient = this.ctx.http; + const client = new BaotaClient(access, http); + this.logger.info(`siteName:${this.siteName}`); + + const siteNames = []; + if (typeof this.siteName === "string") { + siteNames.push(this.siteName); + } else { + siteNames.push(...this.siteName); + } + + const lockKey = `baota-lock-${accessId}`; + + for (const site of siteNames) { + // 加锁,防止并发部署证书, 宝塔并发部署会导致nginx的conf错乱 + await this.ctx.utils.locker.execute(lockKey, async () => { + this.logger.info(`为站点:${site}设置证书,目前支持宝塔网站站点、docker站点`); + if (this.isDockerSite) { + const res = await client.doRequest("/mod/docker/com/set_ssl", "", { + site_name: site, + key: cert.key, + csr: cert.crt, + }); + this.logger.info(res?.msg); + } else { + const res = await client.doRequest("/site", "SetSSL", { + type: 0, + siteName: site, + key: cert.key, + csr: cert.crt, + }); + this.logger.info(res?.msg); + } + }); + } + + //上传证书 + // const uploadCertUrl = "/ssl/cert/save_cert"; + // const uploadCertData = { + // csr: cert.crt, + // key: cert.key, + // }; + // const uploadCertRes = await client.doRequest(uploadCertUrl, null, uploadCertData, { + // skipCheckRes: true, + // }); + // if (uploadCertRes.msg === "证书已存在") { + // this.logger.info(`证书已存在:${JSON.stringify(uploadCertRes)}`); + // } else if (uploadCertRes.status === false) { + // this.logger.info(`上传证书失败:${JSON.stringify(uploadCertRes)}`); + // } else { + // this.logger.info(`上传证书成功:${JSON.stringify(uploadCertRes)}`); + // } + + // const certHash = this.ctx.utils.hash.md5(cert.crt + "\n"); + // for (const site of siteNames) { + // const url = "/ssl/cert/SetCertToSite"; + // const data = { + // siteName: site, + // ssl_hash: certHash, + // }; + // this.logger.info(`开始部署站点【${site}】的证书:${JSON.stringify(data)}`); + // const res = await client.doRequest(url, null, data); + // this.logger.info(`站点【${site}】部署证书成功:${res.msg}`); + // } + + // const batchInfo = []; + // for (const site of siteNames) { + // batchInfo.push({ + // ssl_hash: certHash, + // siteName: site, + // certName: this.certDomains[0], + // }); + // } + // const batchInfoStr = JSON.stringify(batchInfo); + // const data = { BatchInfo: batchInfoStr }; + // this.logger.info("body=", data); + // const res = await client.doRequest("/ssl", "SetBatchCertToSite", data); + // if (res.failed > 0) { + // throw new Error(`部署失败:${JSON.stringify(res)}`); + // } + // this.logger.info(`部署成功:${JSON.stringify(res)}`); + } + + async onGetSiteList() { + // if (!isPlus()) { + // throw new Error("自动获取站点列表为专业版功能,您可以手动输入站点域名/站点名称进行部署"); + // } + const access = await this.getAccess(this.accessId); + const http: HttpClient = this.ctx.http; + const client = new BaotaClient(access, http); + + const domains = this.certDomains; + let all = []; + const getPhpSite = async () => { + const url = "/ssl?action=GetSiteDomain"; + const data = { + cert_list: JSON.stringify(domains), + }; + const res = await client.doRequest(url, null, data, { skipCheckRes: false }); + this.logger.info(res); + all = res.all || []; + }; + + //查找docker 站点 + const getDockerSite = async () => { + const url2 = "/mod/docker/com/get_site_list"; + const res2 = await client.doRequest(url2, null, {}); + this.logger.info(res2); + if (res2.data) { + const dockerDomains = res2.data.map(item => { + return item.name; + }); + all = [...all, ...dockerDomains]; + all = uniq(all); + } + }; + + if (this.isDockerSite) { + await getDockerSite(); + } else { + await getPhpSite(); + } + + if (!all || all.length === 0) { + throw new Error("未找到站点,你可以手动输入"); + } + const options: SiteItem[] = []; + for (const item of all) { + options.push({ + value: item, + label: item, + domain: item, + }); + } + return this.ctx.utils.options.buildGroupOptions(options, domains); + } +} +new BaotaDeployWebSiteCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/baota/waf-access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/baota/waf-access.ts new file mode 100644 index 000000000..e92d962c7 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/baota/waf-access.ts @@ -0,0 +1,128 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +import { HttpRequestConfig, utils } from "@certd/basic"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "baotawaf", + title: "宝塔云WAF授权", + desc: "用于连接和管理宝塔云WAF服务的授权配置", + icon: "svg:icon-bt", +}) +export class BaotaWafAccess extends BaseAccess { + @AccessInput({ + title: "在宝塔WAF URL", + component: { + placeholder: "http://192.168.42.237:41896", + }, + helper: "在宝塔WAF的URL地址,不要带安全入口,例如:http://192.168.42.237:41896", + required: true, + }) + panelUrl = ""; + + @AccessInput({ + title: "WAF API 密钥", + component: { + placeholder: "请输入WAF API接口密钥", + }, + helper: "在宝塔WAF设置页面 - API接口中获取的API密钥。\n必须添加IP白名单,请确保已将CertD服务器IP加入白名单", + required: true, + encrypt: true, + }) + apiSecret = ""; + + @AccessInput({ + title: "忽略SSL证书校验", + value: false, + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "如果面板使用的是自签名SSL证书,则需要开启此选项", + }) + skipSslVerify = false; + + @AccessInput({ + title: "测试", + component: { + name: "api-test", + action: "onTestRequest", + }, + helper: "点击测试WAF请求", + }) + testRequest = true; + + async onTestRequest() { + const body = { + p: 1, + p_size: 1, + site_name: "", + }; + // 发送测试请求 + await this.doRequest({ + url: "/api/wafmastersite/get_site_list", + data: body, + }); + } + + async getSiteList(req: { query?: string; pageNo?: number; pageSize?: number } = {}) { + const body = { + p: req.pageNo ?? 1, + p_size: req.pageSize ?? 100, + site_name: req.query ?? "", + }; + return await this.doRequest({ + url: "/api/wafmastersite/get_site_list", + data: body, + }); + } + + async doRequest(req: HttpRequestConfig) { + const http = this.ctx.http; + + let panelUrl = this.panelUrl; + if (panelUrl.endsWith("/")) { + panelUrl = panelUrl.substring(0, panelUrl.length - 1); + } + // 构建请求头 + /** + * __WAF_KEY = 接口密钥 (在WAF设置页面 - API 接口中获取) + * waf_request_time = 当前请求时间的 uinx 时间戳 ( php: time() / python: time.time() ) + * waf_request_token = md5(string(request_time) + md5(api_sk)) + */ + const timestamp = Math.floor(Date.now() / 1000); + + const token = utils.hash.md5(timestamp + utils.hash.md5(this.apiSecret)); + + const headers = { + waf_request_time: timestamp, + waf_request_token: token, + ...req.headers, + }; + + // 发送测试请求 + const response = await http.request({ + // https://192.168.182.201:8379/api/wafmastersite/get_site_list + baseURL: panelUrl, + method: "POST", + data: req.data, + skipSslVerify: this.skipSslVerify, + ...req, + headers: { + ...headers, + }, + }); + + // 检查响应是否成功 + if (response && response.code === 0) { + return response.res; + } else { + throw new Error(`请求失败: ${response.msg || "未知错误"}`); + } + } +} + +// 实例化插件 +new BaotaWafAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/access.ts new file mode 100644 index 000000000..be84be8aa --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/access.ts @@ -0,0 +1,198 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +import { HttpRequestConfig } from "@certd/basic"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "cdnfly", + title: "cdnfly授权", + desc: "", + icon: "majesticons:cloud-line", +}) +export class CdnflyAccess extends BaseAccess { + @AccessInput({ + title: "cdnfly系统网址", + component: { + name: "a-input", + vModel: "value", + }, + required: true, + helper: "例如:http://demo.cdnfly.cn", + }) + url!: string; + + @AccessInput({ + title: "授权方式", + value: "apikey", + component: { + name: "a-select", + vModel: "value", + options: [ + { label: "接口密钥", value: "apikey" }, + { label: "模拟登录", value: "password" }, + ], + }, + required: true, + }) + type = "apikey"; + + @AccessInput({ + title: "用户名", + component: { + placeholder: "username", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + `, + required: true, + }) + username = ""; + + @AccessInput({ + title: "密码", + component: { + placeholder: "password", + }, + helper: "", + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + `, + required: true, + encrypt: true, + }) + password = ""; + + @AccessInput({ + title: "api_key", + component: { + placeholder: "api_key", + }, + helper: "登录cdnfly控制台->账户中心->Api密钥,点击开启后获取", + required: true, + encrypt: true, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'apikey'; + }) + } + `, + }) + apiKey = ""; + + @AccessInput({ + title: "api_secret", + component: { + placeholder: "api_secret", + }, + helper: "登录cdnfly控制台->账户中心->Api密钥,点击开启后获取", + required: true, + encrypt: true, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.access.type === 'apikey'; + }) + } + `, + }) + apiSecret = ""; + + @AccessInput({ + title: "测试", + component: { + name: "api-test", + action: "onTestRequest", + }, + helper: "点击测试接口看是否正常\nIP需要加白名单,如果是同一台机器部署的,可以试试面板的url使用网卡docker0的ip,白名单使用172.16.0.0/12", + }) + testRequest = true; + + accessToken!: string; + async onTestRequest() { + const certUrl = `/v1/certs`; + const query: any = { + limit: 100, + }; + await this.doRequest({ + url: certUrl, + method: "GET", + data: query, + }); + return "ok"; + } + + async getToken() { + if (this.type !== "password") { + throw new Error("only support password type"); + } + if (this.accessToken) { + return this.accessToken; + } + + const res = await this.ctx.http.request({ + url: "/v1/login", + baseURL: this.url, + method: "POST", + data: { + account: this.username, + password: this.password, + }, + }); + if (res.code != 0) { + throw new Error(res.msg); + } + this.accessToken = res.data.access_token; + return this.accessToken; + } + + async doRequest(config?: HttpRequestConfig) { + const http = this.ctx.http; + + let headers: any = {}; + if (this.type === "password") { + //模拟登陆 + await this.getToken(); + headers = { + "Access-Token": `${this.accessToken}`, + }; + } else { + const { apiKey, apiSecret } = this; + headers = { + "api-key": apiKey, + "api-secret": apiSecret, + }; + } + const data = config.data; + const method = config.method || "POST"; + const baseURL = config.baseURL || this.url; + if (!baseURL) { + throw new Error("请配置授权内的url参数"); + } + const res: any = await http.request({ + url: config.url, + baseURL: baseURL, + method: method, + headers, + logRes: false, + params: method === "GET" ? data : {}, + data: method !== "GET" ? data : undefined, + }); + if (res.code != 0) { + throw new Error(res.msg); + } + return res; + } +} + +new CdnflyAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/index.ts new file mode 100644 index 000000000..02dc3945d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/index.ts @@ -0,0 +1,2 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/plugins/index.ts new file mode 100644 index 000000000..c1300473f --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/plugins/index.ts @@ -0,0 +1 @@ +export * from "./plugin-deploy-to-cdn.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/plugins/plugin-deploy-to-cdn.ts b/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/plugins/plugin-deploy-to-cdn.ts new file mode 100644 index 000000000..834fd9e7f --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/cdnfly/plugins/plugin-deploy-to-cdn.ts @@ -0,0 +1,261 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { CdnflyAccess } from "../access.js"; + +@IsTaskPlugin({ + name: "CdnflyDeployToCDN", + title: "cdnfly-部署证书到cdnfly", + icon: "majesticons:cloud-line", + group: pluginGroups.cdn.key, + desc: "cdnfly", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class CdnflyDeployToCDNPlugin extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput( + createCertDomainGetterInputDefine({ + props: { required: false }, + }) + ) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "cdnfly授权", + helper: "cdnfly授权", + component: { + name: "access-selector", + type: "cdnfly", + }, + required: true, + }) + accessId!: string; + + @TaskInput({ + title: "自动匹配站点", + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "是否自动匹配站点进行部署\n如果选择自动匹配,则下方参数无需填写", + }) + autoMatch!: boolean; + + //测试参数 + @TaskInput( + createRemoteSelectInputDefine({ + title: "证书ID", + helper: "请选择证书Id,需要先手动上传一次证书,后续可以自动更新证书【推荐】", + search: true, + typeName: "CdnflyDeployToCDNPlugin", + action: CdnflyDeployToCDNPlugin.prototype.onGetCertList.name, + watches: ["cert", "accessId"], + required: false, + }) + ) + certId!: number | number[]; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "网站Id", + helper: "请选择要部署证书的网站Id", + search: true, + action: CdnflyDeployToCDNPlugin.prototype.onGetSiteList.name, + watches: ["url", "cert", "accessId"], + required: false, + }) + ) + siteId!: number[]; + + access: CdnflyAccess; + + uploadCertId!: number; + + async onInstance() { + this.access = await this.getAccess(this.accessId); + } + + async execute(): Promise { + const { cert, siteId, certId } = this; + if (this.autoMatch) { + this.logger.info(`自动匹配站点更新证书`); + await this.updateByDomain(); + return; + } + + if (certId != null) { + let certIds = certId as number[]; + if (!Array.isArray(certId)) { + certIds = [certId]; + } + for (const item of certIds) { + await this.updateByCertId(cert, item); + } + } + if (siteId != null) { + let siteIds = siteId as number[]; + if (!Array.isArray(siteId)) { + siteIds = [siteId]; + } + for (const item of siteIds) { + await this.updateBySiteId(cert, item); + } + } + } + + private async updateByCertId(cert: CertInfo, certId: number | number[]) { + this.logger.info(`更新证书,证书ID:${certId}`); + const url = `/v1/certs/${certId}`; + await this.doRequest(url, "PUT", { + cert: cert.crt, + key: cert.key, + }); + } + + async doRequest(url: string, method: string, data: any) { + return await this.access.doRequest({ + url: url, + method: method, + data: data, + }); + } + + private async updateByDomain() { + //查询站点 + const sites = await this.querySite(); + for (const row of sites) { + const domains = row.domain.split(" "); + if (this.ctx.utils.domain.match(domains, this.certDomains)) { + this.logger.info(`站点:${row.id},${row.domain},域名已匹配`); + await this.updateBySiteId(this.cert, row.id); + } else { + this.logger.info(`站点:${row.id},${row.domain},域名未匹配`); + } + } + } + + private async updateBySiteId(cert: CertInfo, siteId: any) { + const siteInfoUrl = `/v1/sites/${siteId}`; + const site = await this.doRequest(siteInfoUrl, "GET", {}); + if (!site) { + throw new Error(`站点:${siteId}不存在`); + } + this.logger.info(`更新站点证书:${siteId}`); + + const data = site.data; + let https_listen = data.https_listen; + if (https_listen && typeof https_listen === "string") { + https_listen = JSON.parse(https_listen); + } + if (https_listen?.cert) { + //该网站已有证书id + const certId = https_listen.cert; + this.logger.info(`该站点已有证书,更新证书,证书ID:${certId}`); + await this.updateByCertId(cert, certId); + return; + } + if (!this.uploadCertId) { + //创建证书 + this.logger.info(`创建证书,域名:${this.certDomains}`); + const certUrl = `/v1/certs`; + const name = this.buildCertName(this.certDomains[0]); + await this.doRequest(certUrl, "POST", { + name, + type: "custom", + cert: cert.crt, + key: cert.key, + }); + + const certs: any = await this.doRequest(certUrl, "GET", { + name, + }); + this.uploadCertId = certs.data[0].id; + } + + const siteUrl = `/v1/sites`; + await this.doRequest(siteUrl, "PUT", { id: site.id, https_listen: { cert: this.uploadCertId } }); + } + + async querySite(domain?: string) { + const siteUrl = `/v1/sites`; + const query: any = { + limit: 100, + }; + if (domain) { + query.domain = domain; + } + const res = await this.doRequest(siteUrl, "GET", query); + return res.data; + } + + async queryCert(domain?: string) { + const certUrl = `/v1/certs`; + const query: any = { + limit: 100, + }; + if (domain) { + query.domain = domain; + } + const res = await this.doRequest(certUrl, "GET", query); + return res.data; + } + + async onGetSiteList(data: { searchKey: string }) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + + const list = await this.querySite(data?.searchKey); + if (!list || list.length === 0) { + throw new Error("没有找到任何站点,您可以手动输入网站Id"); + } + + const options = list.map((item: any) => { + return { + label: `${item.id}<${item.domain}>`, + value: item.id, + domain: item.domain.split(" "), + }; + }); + + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } + + async onGetCertList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + + const list = await this.queryCert(data?.searchKey); + if (!list || list.length === 0) { + throw new Error("没有找到证书列表,您可以手动输入证书Id"); + } + const options = list.map((item: any) => { + return { + label: `${item.id}<${item.domain}>`, + value: item.id, + domain: item.domain.split(" "), + }; + }); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} + +new CdnflyDeployToCDNPlugin(); diff --git a/packages/plugins/plugin-lib/src/ctyun/access/ctyun-access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/access/ctyun-access.ts similarity index 100% rename from packages/plugins/plugin-lib/src/ctyun/access/ctyun-access.ts rename to packages/ui/certd-server/src/plugins/plugin-plus/ctyun/access/ctyun-access.ts diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/index.ts new file mode 100644 index 000000000..87d7259bb --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/index.ts @@ -0,0 +1,3 @@ +export * from "./plugins/index.js"; +export * from "./access/ctyun-access.js"; +export * from "./lib.js"; \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/lib.ts b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/lib.ts new file mode 100644 index 000000000..82d6af73b --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/lib.ts @@ -0,0 +1,249 @@ +import { HttpClient, ILogger } from "@certd/basic"; +import * as querystring from "node:querystring"; +import { CtyunAccess } from "./access/ctyun-access.js"; +export type CtyunCdnDomainInfo = { + area_scope: number; + insert_date: number; + domain: string; + cname: string; + record_num: string; + product_code: string; + product_name: string; + status: number; +}; +export type CtyunClientOptions = { + access: CtyunAccess; + logger: ILogger; + http: HttpClient; +}; + +export class CtyunClient { + opts: CtyunClientOptions; + access: CtyunAccess; + + constructor(opts: CtyunClientOptions) { + this.opts = opts; + this.access = opts.access; + } + + encode(str) { + return Buffer.from(str, "utf-8").toString("base64"); + } + + // 解码 + decode(str) { + return Buffer.from(str, "base64").toString("utf-8"); + } + + // 用 - 和 _ 来代替 + 和 / ,以适应URL中的传输 + urlEncode(str) { + const encoded = this.encode(str); + return encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); + } + + urlDecode(str) { + str = str.replace(/-/g, "+").replace(/_/g, "/"); + // 补全字符数为4的整数 + while (str.length % 4) { + str += "="; + } + return this.decode(str); + } + + async hmacsha256(content, key) { + const crypto = await import("crypto"); + const h = crypto.createHmac("sha256", Buffer.from(key, "base64")).update(content).digest(); + return this.urlEncode(h); + } + + async getHeader(uri) { + // AK + const AK = this.access.accessKeyId; + // SK + const SK = this.access.securityKey; + + const dateNow = +new Date(); + const content = AK + "\n" + dateNow + "\n" + uri; + const authorizedKey = await this.hmacsha256(AK + ":" + parseInt(String(dateNow / 86400000)), SK); + const signature = await this.hmacsha256(content, authorizedKey); + + return { + "x-alogic-now": dateNow, + "x-alogic-app": AK, + "x-alogic-signature": signature, // 对本次调用信息的签名 + "x-alogic-ac": "app", // 访问控制器id,取固定值app + }; + } + + /** + * 接口描述:调用本接口创建证书。 + * 请求方式:post + * 请求路径:/v1/cert/create + * 使用说明: 单个用户一分钟限制调用10000次,并发不超过100; + * + * 请求参数说明: + * + * 参数 类型 是否必传 名称 描述 + * name string 是 证书备注名 + * key string 是 证书私钥 仅支持PEM格式 + * certs string 是 证书公钥 仅支持PEM格式 + * email string 否 用户邮箱 + * 返回参数说明: + * + * 参数 类型 是否必传 名称及描述 + * code int 是 状态码 + * message string 是 描述信息 + * id int 是 证书id + * 示例: + * 请求路径:https://open.ctcdn.cn/api/v1/cert/create + * + * 示例1: + * 请求参数: + * + * { + * "name": "xxxx", + * "certs": "xxxxx", + * "key": "xxxxxx" + * } + * + * 返回结果: + * + * { + * "code": 100000, + * "message": "success", + * "id": 7028 + * } + */ + async doRequest({ uri, method, data }: any) { + const http = this.opts.http; + + const body: any = {}; + if (method === "get") { + if (data) { + uri = uri + "?" + querystring.stringify(data); + } + } else { + body.data = data; + } + const header = await this.getHeader(uri); + + const url = "https://open.ctcdn.cn" + uri; + + const res = await http.request({ + url, + method, + ...body, + headers: { + ...header, + "Content-Type": "application/json", + }, + }); + if (res.code !== 100000) { + throw new Error(`请求失败:${res.message}`); + } + return res; + } + + async certCreate(name: string, crt: string, key: string) { + const uri = "/v1/cert/create"; + const method = "post"; + const data = { + name, + key, + certs: crt, + }; + return this.doRequest({ uri, method, data }); + } + + /** + * 接口描述:调用本接口查询域名列表及域名的基础信息 + * 请求方式:get + * 请求路径:/v2/domain/query + * 使用说明: + * + * 新建的域名需要配置部署完毕(预计10分钟),本接口才能查询到 + * 单个用户一分钟限制调用10000次,并发不超过10 + * 请求参数说明: + * + * 参数 + * 类型 + * 是否必填 + * 名称 + * 说明 + * access_mode int 否 接入方式 枚举值:1(域名接入方式),2(无域名接入方式),不传默认1 + * domain string 否 域名 域名,不填默认所有域名 + * instance string 否 实例名称 不超过10个字的中/英文/数字组合;当access_mode=2时,必填 + * product_code string 否 产品类型 支持产品类型:“001”(静态加速),“003”(下载加速),“004”(视频点播加速),“008”(CDN加速),“007”(安全加速),“005”(直播加速),“006”(全站加速),“009”(应用加速),“010”(web应用防火墙(边缘云版)),“011”(高防DDoS(边缘云版)),“014”(下载加速闲时),“020”(边缘安全加速),“024”(边缘接入),不填默认所有产品 + * status int 否 域名状态 枚举值:4(已启用),6(已停止);不填默认所有状态 + * area_scope int 否 加速范围 1(国内);2(海外);3(全球),不填默认所有加速范围 + * page int 否 页码 不填默认1 + * page_size int 否 每页条数 不填默认50,最大100 + * 返回参数说明: + * + * 参数 类型 是否必传 名称及描述 + * code int 是 状态码 + * message string 是 描述信息 + * total int 否 查询结果总条数 + * total_count int 否 查询结果总条数 + * page int 否 当前页数 + * page_size int 否 每页条数 + * page_count int 否 查询结果总页数 + * result list 否 返回结果列表 + * result[*].domain string 否 域名 + * result[*].cname string 否 cname + * result[*].product_code string 否 产品类型 + * result[*].product_name string 否 产品名称 + * result[*].status int 否 域名状态 + * result[*].insert_date int 否 域名创建时间,单位毫秒 + * result[*].area_scope int 否 加速范围 + * result[*].record_num string 否 备案号 + * 示例: + * 请求路径:https://open.ctcdn.cn/api/v2/domain/query + * + * 示例1:https://open.ctcdn.cn/api/v2/domain/query?page=1&page_size=2 + * 返回结果: + * + * { + * "code": 100000, + * "message": "success", + * "total": 52, + * "total_count": 52, + * "page": 1, + * "page_count": 26, + * "page_size": 2, + * "result": [ + * { + * "area_scope": 1, + * "insert_date": 1667882163000, + * "domain": "sd54sdhmytest.baidu.ctyun.cn", + * "cname": "sd54sdhmytest.baidu.ctyun.cn.ctadns.cn.", + * "record_num": "京ICP证030173号-1", + * "product_code": "008", + * "product_name": "CDN加速", + * "status": 4 + * }, + * { + * "area_scope": 1, + * "insert_date": 1666765345000, + * "domain": "sd54sd.baidu.ctyun.cn", + * "cname": "sd54sd.baidu.ctyun.cn.ctadns.cn.", + * "record_num": "京ICP证030173号-1", + * "product_code": "008", + * "product_name": "CDN加速", + * "status": 6 + * } + * ] + * } + * @param cert_list + */ + async getDomainList({ productCode }: any): Promise { + const uri = "/v2/domain/query"; + const method = "get"; + const data = { + product_code: productCode, + page_size: 100, + }; + const res = await this.doRequest({ uri, method, data }); + return res.result; + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/plugins/index.ts new file mode 100644 index 000000000..c1300473f --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/plugins/index.ts @@ -0,0 +1 @@ +export * from "./plugin-deploy-to-cdn.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/plugins/plugin-deploy-to-cdn.ts b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/plugins/plugin-deploy-to-cdn.ts new file mode 100644 index 000000000..a254b79ec --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/ctyun/plugins/plugin-deploy-to-cdn.ts @@ -0,0 +1,161 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { HttpClient } from "@certd/basic"; +import { CtyunClient } from "../lib.js"; +import { CertApplyPluginNames } from "@certd/plugin-cert"; +@IsTaskPlugin({ + name: "CtyunDeployToCDN", + title: "天翼云-部署证书到CDN", + icon: "svg:icon-ctyun", + group: pluginGroups.cdn.key, + desc: "部署证书到天翼云CDN和全站加速", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class CtyunDeployToCDN extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine()) + certDomains!: string[]; + + @TaskInput({ + title: "产品类型", + helper: "加速产品类型", + component: { + name: "a-select", + options: [ + /** + * “001”(静态加速),“003”:(下载加速), “004”(视频点播加速),“008”(CDN加速),“006”(全站加速),“007”(安全加速) ,“014”(下载加速闲时) + */ + { label: "静态加速", value: "001" }, + { label: "下载加速", value: "003" }, + { label: "视频点播加速", value: "004" }, + { label: "CDN加速", value: "008" }, + { label: "全站加速", value: "006" }, + { label: "安全加速", value: "007" }, + { label: "下载加速闲时", value: "014" }, + ], + }, + required: true, + }) + productCode: string; + + //授权选择框 + @TaskInput({ + title: "天翼云授权", + helper: "天翼云授权", + component: { + name: "access-selector", + type: "ctyun", + }, + required: true, + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "加速域名", + helper: "请选择加速域名", + typeName: "CtyunDeployToCDN", + action: CtyunDeployToCDN.prototype.onGetDomainList.name, + }) + ) + domains!: string[]; + + async onInstance() {} + async execute(): Promise { + /* + 接口描述:调用本接口批量修改加速域名配置信息 +请求方式:post +请求路径:/v1/domain/batch_update_configuration_information +使用说明: + +修改域名之前,您需要先开通对应产品类型的服务,且保证资源包/按需服务有效; +该域名没有在途工单; +单个用户一分钟限制调用10次 + */ + + const access = await this.getAccess(this.accessId); + + const client = new CtyunClient({ + access, + http: this.ctx.http, + logger: this.ctx.logger, + }); + + const certName = this.appendTimeSuffix("certd"); + await client.certCreate(certName, this.cert.crt, this.cert.key); + const uri = "/v1/domain/batch_update_configuration_information"; + + const lockKey = `ctyun-deploy-to-cdn-${this.accessId}`; + await this.ctx.utils.locker.execute(lockKey, async () => { + const res = await client.doRequest({ + uri, + method: "post", + data: { + domain: this.domains, + product_code: this.productCode, + cert_name: certName, + https_status: "on", + }, + }); + + const domain_details = res.domain_details; + const errorMessage = ""; + for (const domainDetail of domain_details) { + // "code":200002,"domain":"ctyun.handsfree.work","message":"参数cert_name只在https_status为on时才有效"} + if (domainDetail.code !== 100000) { + const thisMessage = `部署失败[${domainDetail.code}]:${domainDetail.domain}:${domainDetail.message}`; + if (thisMessage.includes("已有进行中的工单") || errorMessage.includes("域名正在操作中")) { + this.logger.warn(thisMessage); + } + } + } + if (errorMessage) { + throw new Error(errorMessage); + } + await this.ctx.utils.sleep(5000); + }); + + this.logger.info("部署成功"); + } + + async onGetDomainList() { + const access = await this.getAccess(this.accessId); + const http: HttpClient = this.ctx.http; + const client = new CtyunClient({ + access, + http, + logger: this.ctx.logger, + }); + + const all = await client.getDomainList({ productCode: this.productCode }); + + if (!all || all.length === 0) { + throw new Error("未找到加速域名,你可以手动输入"); + } + const options = all.map(item => { + return { + label: item.domain, + value: item.domain, + }; + }); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} +new CtyunDeployToCDN(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/ftp/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/ftp/index.ts new file mode 100644 index 000000000..68bc642b3 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/ftp/index.ts @@ -0,0 +1 @@ +export * from "./plugins/index.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/ftp/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/ftp/plugins/index.ts new file mode 100644 index 000000000..06a486f9e --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/ftp/plugins/index.ts @@ -0,0 +1 @@ +export * from "./plugin-upload-to-ftp.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/ftp/plugins/plugin-upload-to-ftp.ts b/packages/ui/certd-server/src/plugins/plugin-plus/ftp/plugins/plugin-upload-to-ftp.ts new file mode 100644 index 000000000..bd6d5fdf8 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/ftp/plugins/plugin-upload-to-ftp.ts @@ -0,0 +1,215 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; +import { FtpAccess } from "../../../plugin-lib/ftp/access.js"; +import { FtpClient } from "../../../plugin-lib/ftp/client.js"; + +@IsTaskPlugin({ + name: "UploadCertToFTP", + title: "FTP-上传证书到FTP", + icon: "mdi:folder-upload-outline", + group: pluginGroups.host.key, + desc: "将证书上传到FTP服务器", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class UploadCertToFTPPlugin extends AbstractTaskPlugin { + @TaskInput({ + title: "证书格式", + helper: "要部署的证书格式,支持pem、pfx、der、jks", + component: { + name: "a-select", + options: [ + { value: "pem", label: "pem,Nginx等大部分应用" }, + { value: "pfx", label: "pfx,一般用于IIS" }, + { value: "der", label: "der,一般用于Apache" }, + { value: "jks", label: "jks,一般用于JAVA应用" }, + { value: "one", label: "一体化证书,证书和私钥合并为一个pem文件" }, + ], + }, + required: true, + }) + certType!: string; + + @TaskInput({ + title: "PEM证书保存路径", + helper: "需要有写入权限,路径要包含文件名", + component: { + placeholder: "/test/fullchain.pem", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === 'pem'; + }) + } + `, + required: true, + rules: [{ type: "filepath" }], + }) + crtPath!: string; + + @TaskInput({ + title: "私钥保存路径", + helper: "需要有写入权限,路径要包含文件名", + component: { + placeholder: "/test/privatekey.pem", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === 'pem'; + }) + } + `, + required: true, + rules: [{ type: "filepath" }], + }) + keyPath!: string; + + @TaskInput({ + title: "中间证书保存路径", + helper: "需要有写入权限,路径要包含文件名", + component: { + placeholder: "/test/immediate.pem", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === 'pem'; + }) + } + `, + rules: [{ type: "filepath" }], + }) + icPath!: string; + + @TaskInput({ + title: "PFX证书保存路径", + helper: "需要有写入权限,路径要包含文件名", + component: { + placeholder: "/test/cert.pfx", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === 'pfx'; + }) + } + `, + required: true, + rules: [{ type: "filepath" }], + }) + pfxPath!: string; + + @TaskInput({ + title: "DER证书保存路径", + helper: "需要有写入权限,路径要包含文件名\n.der和.cer是相同的东西,改个后缀名即可", + component: { + placeholder: "/test/cert.der 或 /test/cert.cer", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === 'der'; + }) + } + `, + required: true, + rules: [{ type: "filepath" }], + }) + derPath!: string; + + @TaskInput({ + title: "jks证书保存路径", + helper: "证书原本的保存路径,路径要包含文件名", + component: { + placeholder: "/test/javaapp/cert.jks", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === 'jks'; + }) + } + `, + required: true, + rules: [{ type: "filepath" }], + }) + jksPath!: string; + + @TaskInput({ + title: "一体化证书保存路径", + helper: "证书原本的保存路径,路径要包含文件名", + component: { + placeholder: "/app/ssl/one.pem", + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === 'one'; + }) + } + `, + required: true, + rules: [{ type: "filepath" }], + }) + onePath!: string; + + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + //授权选择框 + @TaskInput({ + title: "FTP授权", + component: { + name: "access-selector", + type: "ftp", + }, + required: true, + }) + accessId!: string; + + async onInstance() {} + async execute(): Promise { + const { cert, accessId } = this; + const access = await this.getAccess(accessId); + const client = new FtpClient({ + access, + logger: this.logger, + }); + await client.connect(async () => { + const certReader = new CertReader(cert); + const handle = async ({ reader, tmpCrtPath, tmpKeyPath, tmpDerPath, tmpPfxPath, tmpIcPath, tmpJksPath, tmpOnePath }) => { + try { + await client.upload(tmpCrtPath, this.crtPath); + await client.upload(tmpKeyPath, this.keyPath); + await client.upload(tmpIcPath, this.icPath); + await client.upload(tmpPfxPath, this.pfxPath); + await client.upload(tmpDerPath, this.derPath); + await client.upload(tmpJksPath, this.jksPath); + await client.upload(tmpOnePath, this.onePath); + } catch (e) { + this.logger.error("请确认路径是否包含文件名,路径本身不能是目录,路径不能有*?之类的特殊符号,要有写入权限"); + throw e; + } + }; + await certReader.readCertFile({ logger: this.logger, handle }); + }); + + this.logger.info("执行完成"); + } +} +new UploadCertToFTPPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/index.ts new file mode 100644 index 000000000..db6d7be3a --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/index.ts @@ -0,0 +1,21 @@ +export * from "./baota/index.js"; +export * from "./yidun/index.js"; +export * from "./ftp/index.js"; +export * from "./cdnfly/index.js"; +export * from "./synology/index.js"; +export * from "./k8s/index.js"; +export * from "./1panel/index.js"; +export * from "./baidu/index.js"; +export * from "./lecdn/index.js"; +export * from "./baishan/index.js"; +export * from "./plesk/index.js"; +export * from "./yizhifu/index.js"; +export * from "./alipay/index.js"; +export * from "./wxpay/index.js"; +export * from "./safeline/index.js"; +export * from "./ctyun/index.js"; +export * from "./lucky/index.js"; +export * from "./kuocai/index.js"; +export * from "./unicloud/index.js"; +export * from "./maoyun/index.js"; +export * from "./xinnet/index.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/k8s/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/access.ts new file mode 100644 index 000000000..21315e247 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/access.ts @@ -0,0 +1,34 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +@IsAccess({ + name: "k8s", + title: "k8s授权", + desc: "", + icon: "mdi:kubernetes", +}) +export class K8sAccess extends BaseAccess { + @AccessInput({ + title: "kubeconfig", + component: { + name: "a-textarea", + vModel: "value", + placeholder: "kubeconfig", + }, + required: true, + encrypt: true, + }) + kubeconfig = ""; + + @AccessInput({ + title: "忽略证书校验", + component: { + name: "a-switch", + vModel: "checked", + }, + required: false, + encrypt: false, + }) + skipTLSVerify: boolean; +} + +new K8sAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/k8s/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/index.ts new file mode 100644 index 000000000..21a066db2 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/index.ts @@ -0,0 +1,2 @@ +export * from "./access.js"; +export * from "./plugins/index.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/index.ts new file mode 100644 index 000000000..83c1ae402 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/index.ts @@ -0,0 +1,3 @@ +export * from "./plugin-secret.js"; +export * from "./plugin-ingress.js"; +export * from "./plugin-apply.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-apply.ts b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-apply.ts new file mode 100644 index 000000000..6ee058a7c --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-apply.ts @@ -0,0 +1,135 @@ +import { utils } from "@certd/basic"; +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; +import dayjs from "dayjs"; +import { get } from "lodash-es"; +import { K8sAccess } from "../access.js"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +@IsTaskPlugin({ + name: "K8sApply", + title: "K8S-Apply自定义yaml", + icon: "mdi:kubernetes", + desc: "apply自定义yaml到k8s", + needPlus: true, + group: pluginGroups.panel.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class K8sApplyPlugin extends AbstractPlusTaskPlugin { + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput({ + title: "前置任务输出", + helper: "请选择前置任务输出的内容", + component: { + name: "output-selector", + from: ["::"], + }, + required: false, + }) + preOutput!: any; + + @TaskInput({ + title: "k8s授权", + helper: "kubeconfig", + component: { + name: "access-selector", + type: "k8s", + }, + required: true, + }) + accessId!: string; + + // @TaskInput({ + // title: "命名空间", + // value: "default", + // component: { + // placeholder: "命名空间", + // }, + // required: true, + // }) + // namespace!: string; + + @TaskInput({ + title: "yaml", + required: true, + helper: "apply yaml,模板变量:主域名=${mainDomain}、全部域名=${domains}、过期时间=${expiresTime}、证书PEM=${crt}、证书私钥=${key}、中间证书/CA证书=${ic}、前置任务输出=${preOutput}", + component: { + name: "a-textarea", + vModel: "value", + rows: 6, + }, + }) + yamlContent!: string; + + K8sClient: any; + async onInstance() { + const sdk = await import("@certd/lib-k8s"); + this.K8sClient = sdk.K8sClient; + } + async execute(): Promise { + const access: K8sAccess = await this.getAccess(this.accessId); + const client = new this.K8sClient({ + kubeConfigStr: access.kubeconfig, + logger: this.logger, + skipTLSVerify: access.skipTLSVerify, + }); + if (!this.yamlContent) { + throw new Error("yamlContent is empty"); + } + + const certReader = new CertReader(this.cert); + const mainDomain = certReader.getMainDomain(); + const domains = certReader.getAllDomains().join(","); + + const data = { + mainDomain, + domains, + expiresTime: dayjs(certReader.expires).format("YYYY-MM-DD HH:mm:ss"), + crt: this.cert.crt, + key: this.cert.key, + ic: this.cert.ic, + preOutput: this.preOutput, + }; + + const compile = this.compile(this.yamlContent); + const compiledYaml = compile(data); + // 解析 YAML 内容(可能包含多个文档) + // const yamlDocs = yaml.loadAll(compiledYaml); + + try { + // this.logger.info("apply yaml:", compiledYaml); + // this.logger.info("apply yamlDoc:", JSON.stringify(doc)); + const res = await client.apply(compiledYaml); + this.logger.info("apply result:", res); + } catch (e) { + if (e.response?.body) { + throw new Error(JSON.stringify(e.response.body)); + } + throw e; + } + await utils.sleep(5000); // 停留2秒,等待secret部署完成 + } + + compile(templateString: string) { + return function (data) { + return templateString.replace(/\${(.*?)}/g, (match, key) => { + const value = get(data, key, ""); + return String(value); + }); + }; + } +} +new K8sApplyPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-ingress.ts b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-ingress.ts new file mode 100644 index 000000000..c60e7f13c --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-ingress.ts @@ -0,0 +1,130 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { utils } from "@certd/basic"; + +import { CertInfo } from "@certd/plugin-cert"; +import { K8sAccess } from "../access.js"; +import { CertApplyPluginNames } from "@certd/plugin-cert"; +@IsTaskPlugin({ + name: "K8sDeployToIngress", + title: "K8S-Ingress 证书部署", + icon: "mdi:kubernetes", + desc: "部署证书到k8s的Ingress", + needPlus: false, + group: pluginGroups.panel.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class K8sDeployToIngressPlugin extends AbstractTaskPlugin { + @TaskInput({ + title: "命名空间", + value: "default", + component: { + placeholder: "命名空间", + }, + required: true, + }) + namespace!: string; + + @TaskInput({ + title: "IngressName", + required: true, + helper: "Ingress名称,根据名称查找证书Secret,然后更新", + }) + ingressName!: string; + + @TaskInput({ + title: "k8s授权", + helper: "kubeconfig", + component: { + name: "access-selector", + type: "k8s", + }, + required: true, + }) + accessId!: string; + + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput({ + title: "Secret自动创建", + helper: "如果Secret不存在,则创建", + value: false, + component: { + name: "a-switch", + vModel: "checked", + }, + }) + createOnNotFound: boolean; + + async execute(): Promise { + const access: K8sAccess = await this.getAccess(this.accessId); + const sdk = await import("@certd/lib-k8s"); + const K8sClient = sdk.K8sClient; + const k8sClient = new K8sClient({ + kubeConfigStr: access.kubeconfig, + logger: this.logger, + skipTLSVerify: access.skipTLSVerify, + }); + + const ingressList = await k8sClient.getIngressList({ + namespace: this.namespace, + }); + const ingress = ingressList.items.find((ingress: any) => ingress.metadata.name === this.ingressName); + if (!ingress) { + throw new Error(`Ingress不存在:${this.ingressName}`); + } + if (!ingress.spec.tls) { + throw new Error(`Ingress:${this.ingressName} 还未配置证书,请先手动配置好证书,创建一个Secret`); + } + const secretNames = ingress.spec.tls.map((tls: any) => tls.secretName); + if (!secretNames || secretNames.length === 0) { + throw new Error(`Ingress:${this.ingressName} 未找到证书Secret`); + } + await this.patchNginxCertSecret({ cert: this.cert, k8sClient, secretNames }); + await utils.sleep(5000); // 停留5秒,等待secret部署完成 + } + + async patchNginxCertSecret(options: { cert: CertInfo; k8sClient: any; secretNames: string[] }) { + const { cert, k8sClient } = options; + const crt = cert.crt; + const key = cert.key; + const crtBase64 = Buffer.from(crt).toString("base64"); + const keyBase64 = Buffer.from(key).toString("base64"); + + const { namespace } = this; + + const body: any = { + data: { + "tls.crt": crtBase64, + "tls.key": keyBase64, + }, + metadata: { + labels: { + certd: this.appendTimeSuffix("certd"), + }, + }, + }; + for (const secret of options.secretNames) { + this.logger.info(`更新ingress cert Secret:${secret}`); + await k8sClient.patchSecret({ namespace, secretName: secret, body, createOnNotFound: this.createOnNotFound }); + this.logger.info(`ingress cert Secret已更新:${secret}`); + } + await utils.sleep(5000); // 停留5秒,等待secret部署完成 + if (this.ingressName) { + await k8sClient.restartIngress(namespace, [this.ingressName], { certd: this.appendTimeSuffix("certd") }); + } + } +} +new K8sDeployToIngressPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-secret.ts b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-secret.ts new file mode 100644 index 000000000..2b2348f36 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/k8s/plugins/plugin-secret.ts @@ -0,0 +1,148 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { utils } from "@certd/basic"; +import { CertInfo } from "@certd/plugin-cert"; +import { K8sAccess } from "../access.js"; +import { CertApplyPluginNames } from "@certd/plugin-cert"; +@IsTaskPlugin({ + name: "K8sDeployToSecret", + title: "K8S-部署证书到Secret", + icon: "mdi:kubernetes", + desc: "部署证书到k8s的secret", + needPlus: false, + group: pluginGroups.panel.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class K8sDeployToSecretPlugin extends AbstractTaskPlugin { + @TaskInput({ + title: "命名空间", + value: "default", + component: { + placeholder: "命名空间", + }, + required: true, + }) + namespace!: string; + + @TaskInput({ + title: "保密字典Id", + component: { + name: "a-select", + vModel: "value", + mode: "tags", + open: false, + }, + helper: "原本存储证书的secret的name", + required: true, + }) + secretName!: string | string[]; + + @TaskInput({ + title: "k8s授权", + helper: "kubeconfig", + component: { + name: "access-selector", + type: "k8s", + }, + required: true, + }) + accessId!: string; + + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput({ + title: "ingress名称", + required: false, + helper: "填写之后会自动重启ingress", + component: { + name: "a-select", + vModel: "value", + mode: "tags", + open: false, + }, + }) + ingressName!: string[]; + + @TaskInput({ + title: "Secret自动创建", + helper: "如果Secret不存在,则创建", + value: false, + component: { + name: "a-switch", + vModel: "checked", + }, + }) + createOnNotFound: boolean; + + K8sClient: any; + async onInstance() { + const sdk = await import("@certd/lib-k8s"); + this.K8sClient = sdk.K8sClient; + } + async execute(): Promise { + const access: K8sAccess = await this.getAccess(this.accessId); + const k8sClient = new this.K8sClient({ + kubeConfigStr: access.kubeconfig, + logger: this.logger, + skipTLSVerify: access.skipTLSVerify, + }); + try { + await this.patchCertSecret({ cert: this.cert, k8sClient }); + } catch (e) { + if (e.response?.body) { + throw new Error(JSON.stringify(e.response.body)); + } + throw e; + } + + await utils.sleep(5000); // 停留2秒,等待secret部署完成 + } + + async patchCertSecret(options: { cert: CertInfo; k8sClient: any }) { + const { cert, k8sClient } = options; + const crt = cert.crt; + const key = cert.key; + const crtBase64 = Buffer.from(crt).toString("base64"); + const keyBase64 = Buffer.from(key).toString("base64"); + + const { namespace, secretName } = this; + + const body: any = { + data: { + "tls.crt": crtBase64, + "tls.key": keyBase64, + }, + metadata: { + labels: { + certd: this.appendTimeSuffix("certd"), + }, + }, + }; + let secretNames: any = secretName; + if (typeof secretName === "string") { + secretNames = [secretName]; + } + for (const secret of secretNames) { + body.metadata.name = secret; + await k8sClient.patchSecret({ namespace, secretName: secret, body, createOnNotFound: this.createOnNotFound }); + this.logger.info(`ingress cert Secret已更新:${secret}`); + } + await utils.sleep(5000); // 停留5秒,等待secret部署完成 + if (this.ingressName && this.ingressName.length > 0) { + await k8sClient.restartIngress(namespace, this.ingressName, { certd: this.appendTimeSuffix("certd") }); + } + } +} +new K8sDeployToSecretPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/access.ts new file mode 100644 index 000000000..0df03a0d5 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/access.ts @@ -0,0 +1,35 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "kuocaicdn", + title: "括彩云cdn授权", + icon: "material-symbols:shield-outline", + desc: "括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8)", +}) +export class KuocaiCdnAccess extends BaseAccess { + @AccessInput({ + title: "账户", + component: { + placeholder: "手机号", + }, + required: true, + encrypt: true, + }) + username = ""; + + @AccessInput({ + title: "密码", + component: { + placeholder: "password", + }, + required: true, + encrypt: true, + }) + password = ""; +} + +new KuocaiCdnAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/index.ts new file mode 100644 index 000000000..02dc3945d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/index.ts @@ -0,0 +1,2 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/plugins/index.ts new file mode 100644 index 000000000..c1300473f --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/plugins/index.ts @@ -0,0 +1 @@ +export * from "./plugin-deploy-to-cdn.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/plugins/plugin-deploy-to-cdn.ts b/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/plugins/plugin-deploy-to-cdn.ts new file mode 100644 index 000000000..c5561bd7c --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/kuocai/plugins/plugin-deploy-to-cdn.ts @@ -0,0 +1,168 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { KuocaiCdnAccess } from "../access.js"; + +@IsTaskPlugin({ + name: "KuocaiDeployToRCDN", + title: "括彩云-部署到括彩云CDN", + icon: "material-symbols:shield-outline", + group: pluginGroups.cdn.key, + desc: "括彩云CDN,每月免费30G,[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8)", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class KuocaiDeployToCDNPlugin extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + //授权选择框 + @TaskInput({ + title: "括彩云CDN授权", + helper: "括彩云CDN授权", + component: { + name: "access-selector", + type: "kuocaicdn", + }, + required: true, + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "域名列表", + helper: "选择要部署证书的站点域名", + typeName: "KuocaiDeployToCDNPlugin", + action: KuocaiDeployToCDNPlugin.prototype.onGetDomainList.name, + }) + ) + domains!: string[]; + + async onInstance() {} + async execute(): Promise { + const access = await this.getAccess(this.accessId); + const loginRes = await this.getLoginToken(access); + + const curl = "https://kuocaicdn.com/CdnDomainHttps/httpsConfiguration"; + for (const domain of this.domains) { + // const data = { + // doMainId: domain, + // https: { + // https_status: "off" + // }, + // } + // //先关闭https + // const res = await this.doRequest(curl, loginRes, data); + + const cert = this.cert; + const update = { + doMainId: domain, + https: { + https_status: "on", + certificate_name: this.appendTimeSuffix("certd"), + certificate_source: "0", + certificate_value: cert.crt, + private_key: cert.key, + }, + }; + await this.doRequest(curl, loginRes, update); + this.logger.info(`站点${domain}证书更新成功`); + } + } + + async getLoginToken(access: KuocaiCdnAccess) { + const url = "https://kuocaicdn.com/login/loginUser"; + const data = { + userAccount: access.username, + userPwd: access.password, + remember: true, + }; + const http = this.ctx.http; + const res: any = await http.request({ + url, + method: "POST", + data, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + returnOriginRes: true, + }); + if (!res.data?.success) { + throw new Error(res.data?.message); + } + + const jsessionId = this.ctx.utils.request.getCookie(res, "JSESSIONID"); + const token = res.data?.data; + return { + jsessionId, + token, + }; + } + + async getDomainList(loginRes: any) { + const url = "https://kuocaicdn.com/CdnDomain/queryForDatatables"; + const data = { + draw: 1, + start: 0, + length: 1000, + search: { + value: "", + regex: false, + }, + }; + + const res = await this.doRequest(url, loginRes, data); + return res.data?.data; + } + + private async doRequest(url: string, loginRes: any, data: any) { + const http = this.ctx.http; + const res: any = await http.request({ + url, + method: "POST", + headers: { + Cookie: `JSESSIONID=${loginRes.jsessionId};kuocai_cdn_token=${loginRes.token}`, + }, + data, + }); + if (!res.success) { + throw new Error(res.message); + } + return res; + } + + async onGetDomainList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + const access = await this.getAccess(this.accessId); + + const loginRes = await this.getLoginToken(access); + + const list = await this.getDomainList(loginRes); + + if (!list || list.length === 0) { + throw new Error("您账户下还没有站点域名,请先添加域名"); + } + return list.map((item: any) => { + return { + label: `${item.domainName}<${item.id}>`, + value: item.id, + }; + }); + } +} +new KuocaiDeployToCDNPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/access.ts new file mode 100644 index 000000000..2771afaaa --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/access.ts @@ -0,0 +1,90 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + */ +@IsAccess({ + name: "lecdn", + title: "LeCDN授权", + desc: "", + icon: "material-symbols:shield-outline", +}) +export class LeCDNAccess extends BaseAccess { + @AccessInput({ + title: "LeCDN系统网址", + component: { + name: "a-input", + vModel: "value", + }, + required: true, + helper: "例如:http://demo.xxxx.cn", + }) + url!: string; + + @AccessInput({ + title: "认证类型", + component: { + placeholder: "请选择", + name: "a-select", + vModel: "value", + options: [ + { value: "token", label: "API访问令牌" }, + { value: "password", label: "账号密码(旧版本)" }, + ], + }, + required: true, + }) + type!: string; + + @AccessInput({ + title: "用户名", + component: { + placeholder: "username", + }, + mergeScript: ` + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + `, + required: true, + encrypt: false, + }) + username = ""; + + @AccessInput({ + title: "登录密码", + component: { + placeholder: "password", + }, + required: true, + encrypt: true, + mergeScript: ` + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'password'; + }) + } + `, + }) + password = ""; + + @AccessInput({ + title: "Api访问令牌", + component: { + placeholder: "apiToken", + }, + required: true, + encrypt: true, + mergeScript: ` + return { + show:ctx.compute(({form})=>{ + return form.access.type === 'token'; + }) + } + `, + }) + apiToken = ""; +} + +new LeCDNAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/index.ts new file mode 100644 index 000000000..02dc3945d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/index.ts @@ -0,0 +1,2 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/index.ts new file mode 100644 index 000000000..e0fcd2673 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/index.ts @@ -0,0 +1,2 @@ +export * from "./plugin-update-cert.js"; +export * from "./plugin-update-cert-v2.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/plugin-update-cert-v2.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/plugin-update-cert-v2.ts new file mode 100644 index 000000000..d009209d2 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/plugin-update-cert-v2.ts @@ -0,0 +1,175 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { LeCDNAccess } from "../access.js"; +import { merge } from "lodash-es"; +import { createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { utils } from "@certd/basic"; + +@IsTaskPlugin({ + name: "LeCDNUpdateCertV2", + title: "LeCDN-更新证书V2", + desc: "支持新版本LeCDN", + icon: "material-symbols:shield-outline", + group: pluginGroups.cdn.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class LeCDNUpdateCertV2 extends AbstractTaskPlugin { + //授权选择框 + @TaskInput({ + title: "LeCDN授权", + component: { + name: "access-selector", + type: "lecdn", + }, + required: true, + }) + accessId!: string; + + //测试参数 + @TaskInput( + createRemoteSelectInputDefine({ + title: "证书ID", + helper: "选择要更新的证书id,注意域名是否与证书匹配", + typeName: "LeCDNUpdateCertV2", + action: LeCDNUpdateCertV2.prototype.onGetCertList.name, + }) + ) + certIds!: number[]; + + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + access: LeCDNAccess; + token: string; + + async onInstance() { + this.access = await this.getAccess(this.accessId); + this.token = await this.getToken(); + } + + async doRequest(config: any) { + const access = this.access; + const Authorization = this.access.type === "token" ? this.access.apiToken : `Bearer ${this.token}`; + const res = await this.ctx.http.request({ + baseURL: access.url, + headers: { + Authorization, + }, + ...config, + }); + this.checkRes(res); + return res.data; + } + + async getToken() { + if (this.access.type === "token") { + return this.access.apiToken; + } + // http://cdnadmin.kxfox.com/prod-api/login + const access = this.access; + const res = await this.ctx.http.request({ + url: `/prod-api/login`, + baseURL: access.url, + method: "post", + data: { + //新旧版本不一样,旧版本是username,新版本是email + email: access.username, + username: access.username, + password: access.password, + }, + }); + this.checkRes(res); + //新旧版本不一样,旧版本是access_token,新版本是token + return res.data.access_token || res.data.token; + } + + async getCertInfo(id: number) { + // http://cdnadmin.kxfox.com/prod-api/certificate/9 + // Bearer edGkiOiIJ8 + return await this.doRequest({ + url: `/prod-api/certificate/${id}`, + method: "get", + }); + } + + async updateCert(id: number, cert: CertInfo) { + const certInfo = await this.getCertInfo(id); + const body = { + ssl_key: utils.hash.base64(cert.key), + ssl_pem: utils.hash.base64(cert.crt), + }; + + merge(certInfo, body); + + this.logger.info(`证书名称:${certInfo.name}`); + + return await this.doRequest({ + url: `/prod-api/certificate/${id}`, + method: "put", + data: certInfo, + }); + } + + async execute(): Promise { + for (const certId of this.certIds) { + this.logger.info(`更新证书:${certId}`); + await this.updateCert(certId, this.cert); + this.logger.info(`更新证书成功:${certId}`); + } + + this.logger.info(`更新证书完成`); + } + + private checkRes(res: any) { + if (res.code !== 0 && res.code !== 200) { + throw new Error(res.message); + } + } + + async onGetCertList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + const res = await this.getCerts(); + //新旧版本不一样,一个data 一个是items + const list = res.items || res.data; + if (!res || list.length === 0) { + throw new Error("没有找到证书,请先手动上传一次证书,并让站点使用该证书"); + } + + return list.map((item: any) => { + return { + label: `${item.name}-${item.domain_name}<${item.id}>`, + value: item.id, + }; + }); + } + + private async getCerts() { + // http://cdnadmin.kxfox.com/prod-api/certificate?current_page=1&total=3&page_size=10 + return await this.doRequest({ + url: `/prod-api/certificate`, + method: "get", + params: { + current_page: 1, + page_size: 1000, + }, + }); + } +} + +new LeCDNUpdateCertV2(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/plugin-update-cert.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/plugin-update-cert.ts new file mode 100644 index 000000000..5948ed22d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lecdn/plugins/plugin-update-cert.ts @@ -0,0 +1,165 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertInfo } from "@certd/plugin-cert"; +import { LeCDNAccess } from "../access.js"; +import { merge } from "lodash-es"; +import { createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { CertApplyPluginNames } from "@certd/plugin-cert"; +@IsTaskPlugin({ + name: "LeCDNUpdateCert", + title: "LeCDN-更新证书", + icon: "material-symbols:shield-outline", + group: pluginGroups.cdn.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class LeCDNUpdateCert extends AbstractTaskPlugin { + //授权选择框 + @TaskInput({ + title: "LeCDN授权", + component: { + name: "access-selector", + type: "lecdn", + }, + required: true, + }) + accessId!: string; + + //测试参数 + @TaskInput( + createRemoteSelectInputDefine({ + title: "证书ID", + helper: "选择要更新的证书id,注意域名是否与证书匹配", + typeName: "LeCDNUpdateCert", + action: LeCDNUpdateCert.prototype.onGetCertList.name, + }) + ) + certIds!: number[]; + + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + access: LeCDNAccess; + token: string; + + async onInstance() { + this.access = await this.getAccess(this.accessId); + this.token = await this.getToken(); + } + + async doRequest(config: any) { + const access = this.access; + const res = await this.ctx.http.request({ + baseURL: access.url, + headers: { + Authorization: `Bearer ${this.token}`, + }, + ...config, + }); + this.checkRes(res); + return res.data; + } + + async getToken() { + // http://cdnadmin.kxfox.com/prod-api/login + const access = this.access; + const res = await this.ctx.http.request({ + url: `/prod-api/login`, + baseURL: access.url, + method: "post", + data: { + username: access.username, + password: access.password, + }, + }); + this.checkRes(res); + return res.data.access_token; + } + + async getCertInfo(id: number) { + // http://cdnadmin.kxfox.com/prod-api/certificate/9 + // Bearer edGkiOiIJ8 + return await this.doRequest({ + url: `/prod-api/certificate/${id}`, + method: "get", + }); + } + + async updateCert(id: number, cert: CertInfo) { + const certInfo = await this.getCertInfo(id); + const body = { + ssl_key: cert.key, + ssl_pem: cert.crt, + }; + + merge(certInfo, body); + + this.logger.info(`证书名称:${certInfo.name}`); + + return await this.doRequest({ + url: `/prod-api/certificate/${id}`, + method: "put", + data: certInfo, + }); + } + + async execute(): Promise { + for (const certId of this.certIds) { + this.logger.info(`更新证书:${certId}`); + await this.updateCert(certId, this.cert); + this.logger.info(`更新证书成功:${certId}`); + } + + this.logger.info(`更新证书完成`); + } + + private checkRes(res: any) { + if (res.code !== 0) { + throw new Error(res.message); + } + } + + async onGetCertList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + const res = await this.getCerts(); + + if (!res || res.data.length === 0) { + throw new Error("没有找到证书,请先手动上传一次证书,并让站点使用该证书"); + } + + return res.data.map((item: any) => { + return { + label: `${item.name}-${item.description}<${item.id}>`, + value: item.id, + }; + }); + } + + private async getCerts() { + // http://cdnadmin.kxfox.com/prod-api/certificate?current_page=1&total=3&page_size=10 + return await this.doRequest({ + url: `/prod-api/certificate`, + method: "get", + params: { + current_page: 1, + page_size: 1000, + }, + }); + } +} + +new LeCDNUpdateCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lucky/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lucky/access.ts new file mode 100644 index 000000000..53d0e3cea --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lucky/access.ts @@ -0,0 +1,54 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "lucky", + title: "lucky", + desc: "", + icon: "svg:icon-lucky", +}) +export class LuckyAccess extends BaseAccess { + /** + * 授权属性配置 + */ + @AccessInput({ + title: "访问url", + component: { + placeholder: "http://xxx.xx.xx:16301", + }, + helper: "不要带安全入口", + required: true, + encrypt: false, + }) + url = ""; + + @AccessInput({ + title: "安全入口", + component: { + placeholder: "/your_safe_path", + }, + helper: "请参考lucky设置中关于安全入口的配置,", + required: false, + encrypt: true, + }) + safePath = ""; + + /** + * 授权属性配置 + */ + @AccessInput({ + title: "OpenToken", + component: { + placeholder: "OpenToken", + }, + helper: "设置->最下面开发者设置->启用OpenToken", + required: true, + encrypt: true, + }) + openToken = ""; +} + +new LuckyAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lucky/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lucky/index.ts new file mode 100644 index 000000000..02dc3945d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lucky/index.ts @@ -0,0 +1,2 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lucky/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lucky/plugins/index.ts new file mode 100644 index 000000000..678f8bc6f --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lucky/plugins/index.ts @@ -0,0 +1 @@ +export * from "./plugin-upload.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/lucky/plugins/plugin-upload.ts b/packages/ui/certd-server/src/plugins/plugin-plus/lucky/plugins/plugin-upload.ts new file mode 100644 index 000000000..742ef2a26 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/lucky/plugins/plugin-upload.ts @@ -0,0 +1,170 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { LuckyAccess } from "../access.js"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +import { isArray } from "lodash-es"; + +@IsTaskPlugin({ + //命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名 + name: "LuckyUpdateCert", + title: "lucky-更新Lucky证书", + icon: "svg:icon-lucky", + //插件分组 + group: pluginGroups.panel.key, + needPlus: true, + default: { + //默认值配置照抄即可 + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +//类名规范,跟上面插件名称(name)一致 +export class LuckyUpdateCert extends AbstractPlusTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + // required: true, // 必填 + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "Lucky授权", + component: { + name: "access-selector", + type: "lucky", //固定授权类型 + }, + required: true, //必填 + }) + accessId!: string; + // + + @TaskInput( + createRemoteSelectInputDefine({ + title: "Lucky证书", + helper: "要更新的Lucky证书", + typeName: "LuckyUpdateCert", + action: LuckyUpdateCert.prototype.onGetCertList.name, + watches: ["accessId"], + }) + ) + certList!: string[]; + + //插件实例化时执行的方法 + async onInstance() {} + + //插件执行方法 + async execute(): Promise { + const { cert } = this; + + const access: LuckyAccess = await this.getAccess(this.accessId); + const list = await this.onGetCertList(); + + const certMap: any = {}; + list.forEach(item => { + certMap[item.value] = item.item; + }); + + for (const item of this.certList) { + this.logger.info(`开始更新证书:${item}`); + const old = certMap[item]; + if (!old) { + throw new Error(`没有找到证书:Key=${item},请确认该证书是否存在`); + } + const remark = old.Remark; + const res = await this.doRequest({ + access, + urlPath: "/api/ssl", + method: "PUT", + data: { + AddFrom: "file", + CertBase64: this.ctx.utils.hash.base64(cert.crt), + Enable: true, + Key: item, + MappingChangeScript: "", + MappingPath: "", + MappingToPath: false, + KeyBase64: this.ctx.utils.hash.base64(cert.key), + IssuerCertificate: "", + ExtParams: {}, + Remark: remark, + }, + }); + + this.logger.info(`更新成功:${JSON.stringify(res)}`); + } + + this.logger.info("部署成功"); + } + + async doRequest(req: { access: LuckyAccess; urlPath: string; data: any; method?: string }) { + const { access, urlPath, data, method } = req; + let url = `${access.url}/${access.safePath || ""}${urlPath}?_=${Math.floor(new Date().getTime())}`; + // 从第7个字符起,将//替换成/ + const protocol = url.substring(0, 7); + let suffix = url.substring(7); + suffix = suffix.replaceAll("//", "/"); + suffix = suffix.replaceAll("//", "/"); + url = protocol + suffix; + + const headers: any = { + // Origin: access.url, + "Content-Type": "application/json", + // "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36", + }; + headers["openToken"] = access.openToken; + const res = await this.http.request({ + method: method || "POST", + url, + data, + headers, + skipSslVerify: true, + }); + if (res.ret !== 0) { + throw new Error(`请求失败:${res.msg}`); + } + return res; + } + + async onGetCertList() { + const access: LuckyAccess = await this.getAccess(this.accessId); + const res = await this.doRequest({ + access, + urlPath: "/api/ssl", + data: {}, + method: "GET", + }); + const list = res.list; + if (!list || list.length === 0) { + throw new Error("没有找到证书,请先在SSL/TLS证书页面中手动上传一次证书"); + } + + const options = list.map((item: any) => { + const certsInfo = item.CertsInfo; + let label = ""; + if (isArray(certsInfo)) { + label = item.CertsInfo[0].Domains; + } else { + label = item.CertsInfo.Domains[0]; + } + return { + label: `${item.Remark}<${label}>`, + value: item.Key, + item: item, + }; + }); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} +//实例化一下,注册插件 +new LuckyUpdateCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/access.ts new file mode 100644 index 000000000..652b38f13 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/access.ts @@ -0,0 +1,54 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + */ +@IsAccess({ + name: "maoyun", + title: "猫云授权", + desc: "", + icon: "svg:icon-lucky", +}) +export class MaoyunAccess extends BaseAccess { + @AccessInput({ + title: "用户名", + component: { + placeholder: "username/手机号/email", + name: "a-input", + vModel: "value", + }, + helper: "用户名", + required: true, + }) + username!: string; + + @AccessInput({ + title: "password", + component: { + placeholder: "密码", + component: { + name: "a-input-password", + vModel: "value", + }, + }, + encrypt: true, + helper: "密码", + required: true, + }) + password!: string; + + @AccessInput({ + title: "HttpProxy", + component: { + placeholder: "http://192.168.x.x:10811", + component: { + name: "a-input", + vModel: "value", + }, + }, + encrypt: false, + required: false, + }) + httpProxy!: string; +} + +new MaoyunAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/client.ts b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/client.ts new file mode 100644 index 000000000..0b94bb755 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/client.ts @@ -0,0 +1,98 @@ +import { HttpClient, HttpRequestConfig, ILogger } from "@certd/basic"; +import { MaoyunAccess } from "./access.js"; + +export class MaoyunClient { + privateKeyPem = ""; + http: HttpClient; + logger: ILogger; + access: MaoyunAccess; + token: string; + + constructor(opts: { logger: ILogger; http: HttpClient; access: MaoyunAccess }) { + this.logger = opts.logger; + this.http = opts.http; + this.access = opts.access; + this.privateKeyPem = + "\n-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAt83xKlUSU0i09/pwwQ0MQQ0v71IULdVGJ3AFo+anwLX1TRCp\nxmY5i+xmT9tshHqiPGN8qeg+lDaqA+iwmS6zqi+KlNmmKJc3kUx/h24MI3nff0xy\nz605ZfDgJhwBkJpTI6Sk4+OLX+lZxOiET0nOT7jrhKiFCKX8+0ZXjTJ1cmdifKaj\nqXmjD+XYZzBwA2fCr1kPq2xKvU097Ksu6QvM+La5X/tt+FJOuedmuqZmsb6YQ+6O\n6mJ0bcY0kFDGNkoeY6dEyeJAkIDJbda3n0I71KwRR2J0CSN3TF+w1hSQa7Hp1rXw\n+zQvR6p7O2VY8zQeZZKRKGl7OGdKW5F79iz2fQIDAQABAoIBAEN0BaRGciI0VY2H\n0CdY1X1uDIBke9lSIpvIhZlfxYJ4hFxS2CtiSo4qJGX8HbgElVNaI17rR0P3R6+F\njoG43OCA7/euZEcTL6ZYD5kw7q16RWYfNSc36A+cNXZm4sAhko9LFeQ4FmcNaQ9V\nUXEToe4p6+zUN3Y0DEJezzSXJvjjjodT5L03i2HCW+/xZIHi6oh1DuXdy7h1Ah8s\nSxN188HsX7/SoDHAxDqi/SSGyoYg/SvtOetPtrcZCfqoHfxkR+jQHNaOTq3vGmsu\np8KPtRBoFvSPMxSSHNLb4qbIFvlWRLNXfIhYnenTPtmCnnqogotZZ9CoCHL9dX5R\nt4q5L6ECgYEA5jYhqpRIhqSZOTJopGgy3LBy5T1PHDTfedTuSxnoywYWCuGNwgjI\nRgd94jcUuizO9euobxvDUTdOZ6LdK1NStfwOspb2NojvlE+9SfC8JDv7ZeRz8egB\nClrT6jtCUr80K1I0eF31ha0YMjgi7WZJvTMp53fqI0b1yQO2FaBNgWUCgYEAzGT6\nay+QlO2Fdt9mqeIJy9QiugItC7lk75fQMg5fa8A8wj9DO86o/2k4rKhl7SPg0H+R\nSJQoZGuS4M2f9muEHnLmVF8EzizuHZoR3HO4mie2adVf9NfAmkFsCluRAZKtQkNc\nt/VwlJEC6dChoZkU8Wzd0fSJKrdhjik2ayGXmzkCgYEAuie9s5UyzIXfTSwhCAkm\nT+TzE8Iu7Y0nxPnVM6+g2kNyoZvgqK23XUGDnuCRhzbiqGPGkQovN8Z0RUOiev1m\n3bgUHoAKWvECYrjURS1AxkAmuy8wPsYvyTLHOBpxOD5bLkjMGyVHe7AL59gTDktv\nh2oPEZibIamo6MJyhCxbYC0CgYAIZhnYL7MsO3phgRqR3oTyiDwJEq/RLIQWSFG4\nzNhk8BhPDxRvL7XIEQXQKndNwEyrpKJOri/euIDnlet9z7s1GRmX2/OxmS0LsFoN\nif/K7djUDn2L7RWwAQI0hsC1pNZTw7raoE5I/JB3FSifIFA4/3U5/GdqhvCOS+k9\ni7rUGQKBgQDPspapfGj2ozgWChJ2xMTGBhJhynM81w3j9w7MLvO/7/U43zYzKzyc\n7YJzApQOSwX/nLdquzi+UIbvuCB3npZVZl52S4f7BBcgLNQpdmcfWrAbDv5lySfn\n/KTN22Wxmhh20QgiNSxj+o+KIgdAgZCgWt7NrkZ5UX7Lo+ZfYU1xbg==\n-----END RSA PRIVATE KEY-----"; + } + + async sign(data: string) { + const { KJUR, KEYUTIL, hextob64 } = await import("jsrsasign"); + const privateKey = KEYUTIL.getKey(this.privateKeyPem); + // 创建签名实例 + const signature = new KJUR.crypto.Signature({ + alg: "SHA256withRSA", + }); + + // 初始化私钥 + signature.init(privateKey); + + // 更新待签名数据(假设原文是字符串) + signature.updateString(data); + + // 生成签名(默认返回十六进制字符串) + const hexSignature = signature.sign(); + + // 转换为 Base64(假设 Ix 是 Base64 编码) + return hextob64(hexSignature); + } + + async doRequest(req: HttpRequestConfig) { + const timestamp = Date.now(); + + let data = ""; + if (req.method.toLowerCase() === "get") { + // area_codes=&channel_type=0,1,2&domain_name=&https_status=&nonce=1747242446238&order=&page=1&page_size=10&status=×tamp=1747242446238 + let queryList = []; + for (const key in req.params) { + queryList.push(`${key}=${req.params[key]}`); + } + queryList.push(`nonce=${timestamp}`); + queryList.push(`timestamp=${timestamp}`); + //sort + queryList = queryList.sort(); + data = queryList.join("&"); + } else { + data = `body=${JSON.stringify(req.data || {})}&nonce=${timestamp}×tamp=${timestamp}`; + } + const sign = await this.sign(data); + const headers: any = { + sign: sign, + timestamp: timestamp, + nonce: timestamp, + }; + + if (this.token) { + headers.Token = this.token; + } + + const res = await this.http.request({ + ...req, + headers, + baseURL: "https://testaa.5678.jp", + }); + + if (!res.success && res.code !== 200) { + throw new Error(`请求失败:${res.msg}`); + } + return res.data; + } + + async login() { + const req = { + email: this.access.username, + password: this.access.password, + accountType: 1, + }; + const res = await this.doRequest({ + url: "/api/vcloud/v1/userApi/noAuth/login", + method: "post", + data: req, + logRes: false, + logParams: false, + }); + const { token } = res; + this.logger.info(`登录成功`); + this.token = token; + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/index.ts new file mode 100644 index 000000000..b9436b7da --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/index.ts @@ -0,0 +1,4 @@ +// 隐藏 通过下载插件形式分发 +// export * from "./plugins/index.js"; +export * from "./access.js"; +export * from "./client.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/plugins/index.ts new file mode 100644 index 000000000..daed7c52e --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/plugins/index.ts @@ -0,0 +1 @@ +// export * from "./plugin-deploy-to-cdn.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/plugins/plugin-deploy-to-cdn.ts b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/plugins/plugin-deploy-to-cdn.ts new file mode 100644 index 000000000..ad43d47f4 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/maoyun/plugins/plugin-deploy-to-cdn.ts @@ -0,0 +1,150 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +import { MaoyunAccess } from "../access.js"; +import { MaoyunClient } from "../client.js"; + +@IsTaskPlugin({ + //命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名 + name: "MaoyunDeployToCdn", + title: "Maoyun-更新猫云CDN证书", + icon: "svg:icon-lucky", + //插件分组 + group: pluginGroups.cdn.key, + needPlus: true, + default: { + //默认值配置照抄即可 + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +//类名规范,跟上面插件名称(name)一致 +export class MaoyunDeployToCdn extends AbstractPlusTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + // required: true, // 必填 + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "Maoyun授权", + component: { + name: "access-selector", + type: "maoyun", //固定授权类型 + }, + required: true, //必填 + }) + accessId!: string; + // + + @TaskInput( + createRemoteSelectInputDefine({ + title: "CDN加速域名", + helper: "要部署证书的域名", + action: MaoyunDeployToCdn.prototype.onGetDomainList.name, + watches: ["accessId"], + }) + ) + domainList!: string[]; + + //插件实例化时执行的方法 + async onInstance() {} + + //插件执行方法 + async execute(): Promise { + const { cert } = this; + + const access: MaoyunAccess = await this.getAccess(this.accessId); + + const client = new MaoyunClient({ + http: this.ctx.http, + logger: this.logger, + access, + }); + await client.login(); + for (const item of this.domainList) { + this.logger.info(`开始更新证书:${item}`); + + // https://testaa.5678.jp/cdn/domain/6219/https_conf + /** + * {status: 1, new_certificate: {name: "certd",…}} + * new_certificate + * : + * {name: "certd",…} + * content + * : + * name + * : + * "certd" + * private_key + * : + * status + * : + * 1 + */ + await client.doRequest({ + method: "PUT", + url: `/cdn/domain/${item}/https_conf`, + data: { + status: 1, + new_certificate: { + name: this.appendTimeSuffix("certd"), + content: cert.crt, + private_key: cert.key, + }, + }, + }); + + this.logger.info(`部署${item}证书成功`); + } + + this.logger.info("部署完成"); + } + + async onGetDomainList() { + const access: MaoyunAccess = await this.getAccess(this.accessId); + const client = new MaoyunClient({ + http: this.ctx.http, + logger: this.logger, + access, + }); + await client.login(); + const res = await client.doRequest({ + url: "/cdn/domain", + data: {}, + params: { + channel_type: "0,1,2", + page: 1, + page_size: 1000, + }, + method: "GET", + }); + const list = res.data; + if (!list || list.length === 0) { + throw new Error("没有找到加速域名,请先在控制台添加加速域名"); + } + + const options = list.map((item: any) => { + return { + label: `${item.domain}<${item.id}>`, + value: item.id, + domain: item.domain, + }; + }); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} +//实例化一下,注册插件 +new MaoyunDeployToCdn(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/plesk/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/access.ts new file mode 100644 index 000000000..d1985d7c0 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/access.ts @@ -0,0 +1,204 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +import FormData from "form-data"; +import { HttpError, HttpRequestConfig } from "@certd/basic"; + +export type PleskReq = { + sessionId?: string; + token?: string; + formData?: FormData; + checkRes?: boolean; +} & HttpRequestConfig; +/** + */ +@IsAccess({ + name: "plesk", + title: "plesk授权", + desc: "", + icon: "svg:icon-plesk", +}) +export class PleskAccess extends BaseAccess { + @AccessInput({ + title: "Plesk网址", + component: { + name: "a-input", + vModel: "value", + }, + required: true, + helper: "例如:https://xxxx.xxxxx:8443/", + }) + url!: string; + + @AccessInput({ + title: "用户名", + component: { + placeholder: "username", + }, + required: true, + encrypt: false, + }) + username = ""; + + @AccessInput({ + title: "登录密码", + component: { + placeholder: "password", + }, + required: true, + encrypt: true, + }) + password = ""; + + @AccessInput({ + title: "测试", + component: { + name: "api-test", + action: "onTestRequest", + }, + helper: "点击测试接口看是否正常", + }) + testRequest = true; + + async onTestRequest() { + const sessionId = await this.getSessionId(); + if (!sessionId) { + throw new Error("获取sessionId失败"); + } + return "ok"; + } + + async getSessionId() { + const formData = new FormData(); + formData.append("login_name", this.username); + formData.append("passwd", this.password); + formData.append("locale_id", "default"); + + try { + await this.ctx.http.request({ + url: `/login_up.php`, + baseURL: this.url, + method: "post", + headers: { + origin: this.url, + ...formData.getHeaders(), + }, + data: formData, + withCredentials: true, + logRes: false, + returnOriginRes: true, + maxRedirects: 0, + }); + } catch (e: any) { + if (e && e instanceof HttpError && e.status === 303 && e.response) { + //获取cookie + const cookies = e.response.headers.get("set-cookie"); + const sessId = cookies[0].match(/PLESKSESSID=(.*?);/)[1]; + if (sessId) { + this.ctx.logger.info(`获取Plesk SessionId 成功`); + return sessId; + } + } + throw e; + } + } + + async getCertList(sessionId: string) { + const detail = await this.doGetRequest({ + url: "modules/sslit/index.php/main-page/index", + sessionId, + }); + + // "domainList": [ ... "domainSummaryEnabled": + + const listStr = detail.match(/"domainList":(.*?),"domainSummaryEnabled"/)[1]; + + const list = JSON.parse(listStr); + + const token = this.getTokenFromDetail(detail); + + return { list, token }; + } + + getTokenFromDetail(detail: string) { + return detail.match(/forgery_protection_token" content="(.*?)"/)[1]; + } + + async doGetRequest(req: PleskReq) { + const detail = await this.ctx.http.request({ + //https://vps-b6941c0f.vps.ovh.net:8443/modules/sslit/index.php/index/certificate/id/2 + url: req.url, + baseURL: this.url, + method: req.method ?? "get", + withCredentials: true, + headers: { + Cookie: `PLESKSESSID=${req.sessionId}`, + }, + logRes: false, + }); + return detail; + } + + async doEditRequest(req: PleskReq) { + const res = await this.ctx.http.request({ + url: req.url, + baseURL: this.url, + method: req.method ?? "post", + withCredentials: true, + headers: { + // Origin: + // https://vps-b6941c0f.vps.ovh.net:8443 + // Referer: + // https://vps-b6941c0f.vps.ovh.net:8443/modules/sslit/index.php/index/certificate/id/1 + Cookie: `PLESKSESSID=${req.sessionId}`, + Origin: this.url, + "X-Forgery-Protection-Token": req.token, + ...req.formData.getHeaders(), + }, + logRes: req.logRes ?? false, + data: req.formData, + }); + + if (req.checkRes === false) { + return res; + } + + if (res && res.status === "success") { + return res; + } else { + throw new Error(`${JSON.stringify(res.actionMessages || res.statusMessages || res)}`); + } + } + + async deleteUnusedCert(req: { sessionId: any; token: string; siteDomainId: number }) { + //查询哪些证书是未使用的 + let detail = await this.doGetRequest({ + url: `/smb/ssl-certificate/list/id/${req.siteDomainId}`, + sessionId: req.sessionId, + }); + detail = detail.substring(detail.indexOf("Plesk.require('app/ssl-certificate/list',")); + // "data": [....] "locale": + const listStr1 = detail.match(/"data":(.*?),"locale"/)[1]; + const listStr = listStr1.match(/"data":(.*)/)[1]; + const list = JSON.parse(listStr); + const unused = list.filter((item: any) => item.usageCount === "0").map(item => item.id); + if (unused.length === 0) { + this.ctx.logger.info(`没有未使用的证书`); + return; + } + const formData = new FormData(); + for (let i = 0; i < unused.length; i++) { + formData.append(`ids[${i}]`, unused[i]); + } + + await this.doEditRequest({ + url: `/smb/ssl-certificate/delete/id/${req.siteDomainId}`, + sessionId: req.sessionId, + method: "post", + formData, + token: req.token, + logRes: false, + }); + this.ctx.logger.info(`删除未使用的证书成功`); + } +} + +new PleskAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/plesk/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/index.ts new file mode 100644 index 000000000..02dc3945d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/index.ts @@ -0,0 +1,2 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/index.ts new file mode 100644 index 000000000..4554a4033 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/index.ts @@ -0,0 +1,3 @@ +export * from "./plugin-deploy-cert.js"; +// 暂时不可用,需要保持私钥不变才能更新证书 +// export * from "./plugin-refresh-cert.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/plugin-deploy-cert.ts b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/plugin-deploy-cert.ts new file mode 100644 index 000000000..3907989c0 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/plugin-deploy-cert.ts @@ -0,0 +1,141 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +import { PleskAccess } from "../access.js"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import FormData from "form-data"; + +@IsTaskPlugin({ + name: "PleskDeploySiteCert", + title: "Plesk-部署Plesk网站证书", + icon: "svg:icon-plesk", + group: pluginGroups.panel.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: true, +}) +export class PleskDeploySiteCert extends AbstractPlusTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "Plesk授权", + component: { + name: "access-selector", + type: "plesk", + }, + required: true, + }) + accessId!: string; + + //测试参数 + @TaskInput( + createRemoteSelectInputDefine({ + title: "网站域名列表", + helper: "选择要更新的站点域名,注意域名是否与证书匹配", + action: PleskDeploySiteCert.prototype.onGetDomainList.name, + }) + ) + siteDomainIds!: number[]; + + @TaskInput({ + title: "删除未使用证书", + component: { + name: "a-switch", + vModel: "checked", + }, + required: false, + }) + clearUnused!: boolean; + + async execute(): Promise { + const access = await this.getAccess(this.accessId); + //get cookie + const sessionId = await access.getSessionId(); + + for (const siteDomainId of this.siteDomainIds) { + const formData = new FormData(); + formData.append("id", siteDomainId); + formData.append("fileContent", this.cert.one); + formData.append("fileName", "cert.pem"); + + const detail = await access.doGetRequest({ + //https://vps-b6941c0f.vps.ovh.net:8443/modules/sslit/index.php/index/certificate/id/2 + url: `/modules/sslit/index.php/index/certificate/id/${siteDomainId}`, + sessionId, + }); + //获取防伪令牌 + // + const token = access.getTokenFromDetail(detail); + + await access.doEditRequest({ + url: `/modules/sslit/index.php/index/upload/`, + sessionId, + token, + formData, + }); + this.logger.info(`部署站点<${siteDomainId}>证书成功`); + + //删除未使用的证书 + if (this.clearUnused) { + await this.ctx.utils.sleep(3000); + this.logger.info(`开始删除未使用的证书`); + try { + await access.deleteUnusedCert({ sessionId, token, siteDomainId }); + } catch (e) { + this.logger.warn(`删除未使用的证书失败:${e.message}`); + } + } + } + + this.logger.info(`部署证书完成`); + } + + async onGetDomainList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + + const access = await this.getAccess(this.accessId); + + const res = await this.http.request({ + url: `/api/v2/domains`, + baseURL: access.url, + method: "get", + headers: { + // authorization: Basic ==' + Authorization: `Basic ${Buffer.from(`${access.username}:${access.password}`).toString("base64")}`, + }, + }); + + if (!res || res.length === 0) { + throw new Error("没有找站点域名"); + } + const options = res.map((item: any) => { + return { + label: item.name, + value: item.id, + domain: item.name, + }; + }); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} + +new PleskDeploySiteCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/plugin-refresh-cert.ts b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/plugin-refresh-cert.ts new file mode 100644 index 000000000..6ca9b8c1f --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/plesk/plugins/plugin-refresh-cert.ts @@ -0,0 +1,114 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +import { PleskAccess } from "../access.js"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import FormData from "form-data"; + +@IsTaskPlugin({ + name: "PleskRefreshCert", + title: "Plesk-更新证书", + icon: "svg:icon-plesk", + desc: "不会创建新证书记录,直接更新旧的证书", + group: pluginGroups.panel.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: true, +}) +export class PleskRefreshCert extends AbstractPlusTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "Plesk授权", + component: { + name: "access-selector", + type: "plesk", + }, + required: true, + }) + accessId!: string; + + //测试参数 + @TaskInput( + createRemoteSelectInputDefine({ + title: "证书列表", + helper: "选择要更新的站点域名,注意域名是否与证书匹配", + action: PleskRefreshCert.prototype.onGetCertList.name, + }) + ) + domainCertIds!: string[]; + + async execute(): Promise { + const access = await this.getAccess(this.accessId); + //get cookie + const sessionId = await access.getSessionId(); + + const { token } = await access.getCertList(sessionId); + + const certReader = new CertReader(this.cert); + for (const certIds of this.domainCertIds) { + const [domainId, certId] = certIds.split("_")[0]; + const formData = new FormData(); + formData.append("name", this.buildCertName(certReader.getMainDomain())); + formData.append("type", "sendText"); + formData.append("uploadText[certificateText]", this.cert.crt); + formData.append("uploadText[privateKeyText]", this.cert.key); // 这里没用 + formData.append("forgery_protection_token", token); + + const res = await access.doEditRequest({ + url: `/smb/ssl-certificate/edit/id/${domainId}/certificateId/${certId}`, + sessionId, + token, + formData, + checkRes: false, + }); + + this.logger.info(`更新证书成功:${certIds}_${certReader.getMainDomain()}`, res); + } + + this.logger.info(`部署证书完成`); + } + + async onGetCertList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + + const access = await this.getAccess(this.accessId); + + const sessionId = await access.getSessionId(); + + const { list } = await access.getCertList(sessionId); + + if (!list || list.length === 0) { + throw new Error("没有找到证书"); + } + const options = list.map((item: any) => { + return { + label: `${item.domainName}(${item.domainId}_${item.certificateId})`, + value: `${item.domainId}_${item.certificateId}`, + domain: item.domainName, + }; + }); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} + +new PleskRefreshCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/safeline/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/safeline/access.ts new file mode 100644 index 000000000..7aa9cb46e --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/safeline/access.ts @@ -0,0 +1,45 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "safeline", + title: "长亭雷池授权", + icon: "svg:icon-safeline", +}) +export class SafelineAccess extends BaseAccess { + @AccessInput({ + title: "雷池的访问url", + component: { + placeholder: "https://xxxx.com:9443", + }, + required: true, + }) + baseUrl = ""; + + @AccessInput({ + title: "ApiToken", + component: { + placeholder: "apiToken", + }, + helper: "", + required: true, + encrypt: true, + }) + apiToken = ""; + + @AccessInput({ + title: "忽略证书校验", + value: true, + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭", + }) + skipSslVerify = true; +} + +new SafelineAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/safeline/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/safeline/index.ts new file mode 100644 index 000000000..02dc3945d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/safeline/index.ts @@ -0,0 +1,2 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/safeline/plugins/deploy-to-website.ts b/packages/ui/certd-server/src/plugins/plugin-plus/safeline/plugins/deploy-to-website.ts new file mode 100644 index 000000000..f8679e3be --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/safeline/plugins/deploy-to-website.ts @@ -0,0 +1,124 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { HttpRequestConfig } from "@certd/basic"; + +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { SafelineAccess } from "../access.js"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; + +@IsTaskPlugin({ + name: "SafelineDeployToWebsitePlugin", + title: "雷池-更新证书", + icon: "svg:icon-safeline", + desc: "更新长亭雷池WAF的证书", + group: pluginGroups.panel.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class SafelineDeployToWebsitePlugin extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + //授权选择框 + @TaskInput({ + title: "雷池授权", + helper: "长亭雷池授权", + component: { + name: "access-selector", + type: "safeline", + }, + required: true, + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "雷池证书", + typeName: "SafelineDeployToWebsitePlugin", + action: SafelineDeployToWebsitePlugin.prototype.onGetCertIds.name, + helper: "请选择要更新的雷池的证书Id,需要先手动到雷池控制台上传一次", + required: true, + }) + ) + certIds!: number[]; + + access: SafelineAccess; + async onInstance() { + this.access = await this.getAccess(this.accessId); + } + async execute(): Promise { + for (const certId of this.certIds) { + await this.uploadCert(certId); + } + this.logger.info("雷池证书更新完成"); + } + + async uploadCert(certId: number) { + await this.doRequest({ + url: "/api/open/cert", + method: "post", + data: { + id: certId, + manual: { + crt: this.cert.crt, + key: this.cert.key, + }, + type: 2, + }, + }); + this.logger.info(`证书<${certId}>更新成功`); + } + + async doRequest(config: HttpRequestConfig) { + config.baseURL = this.access.baseUrl; + config.skipSslVerify = this.access.skipSslVerify ?? false; + config.logRes = false; + config.logParams = false; + config.headers = { + "X-SLCE-API-TOKEN": this.access.apiToken, + }; + const res = await this.ctx.http.request(config); + if (!res.err) { + return res.data; + } + throw new Error(res.msg); + } + + // requestHandle + + async onGetCertIds() { + const res = await this.doRequest({ + url: "/api/open/cert", + method: "get", + data: {}, + }); + const nodes = res?.nodes; + if (!nodes || nodes.length === 0) { + throw new Error("没有找到证书,请先在雷池控制台中手动上传证书,并关联防护站点,后续才可以自动更新"); + } + const options = nodes.map(item => { + return { + label: `<${item.id}>${item.domains.join(",")}`, + value: item.id, + domain: item.domains, + }; + }); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } +} +new SafelineDeployToWebsitePlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/safeline/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/safeline/plugins/index.ts new file mode 100644 index 000000000..1c360e693 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/safeline/plugins/index.ts @@ -0,0 +1 @@ +export * from "./deploy-to-website.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/synology/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/synology/access.ts new file mode 100644 index 000000000..9bd7ce530 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/synology/access.ts @@ -0,0 +1,135 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +import { SynologyClient } from "./client.js"; +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "synology", + title: "群晖登录授权", + desc: "", + icon: "simple-icons:synology", +}) +export class SynologyAccess extends BaseAccess { + @AccessInput({ + title: "群晖版本", + component: { + name: "a-select", + vModel: "value", + options: [ + { label: "7.x", value: "7" }, + { label: "6.x", value: "6" }, + ], + }, + required: true, + }) + version = "7"; + + @AccessInput({ + title: "群晖面板的url", + component: { + placeholder: "https://yourdomain:5006", + }, + helper: "群晖面板的访问地址,例如:https://yourdomain:5006", + required: true, + }) + baseUrl = ""; + + @AccessInput({ + title: "账号", + component: { + placeholder: "账号", + }, + helper: "群晖面板登录账号,必须是处于管理员用户组", + required: true, + }) + username = ""; + + @AccessInput({ + title: "密码", + component: { + placeholder: "密码", + }, + helper: "群晖面板登录密码", + required: true, + encrypt: true, + }) + password = ""; + + @AccessInput({ + title: "双重认证", + value: false, + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "是否启用了双重认证", + required: true, + }) + otp = false; + + @AccessInput({ + title: "设备ID", + component: { + placeholder: "设备ID", + name: "synology-device-id-getter", + type: "access", + typeName: "synology", + }, + mergeScript: ` + return { + component:{ + form: ctx.compute(({form})=>{ + return form + }) + }, + show: ctx.compute(({form})=>{ + return form.access.otp + }) + } + `, + helper: `1.如果开启了双重认证,需要获取设备ID +2.填好上面的必填项,然后点击获取设备ID,输入双重认证APP上的码,确认即可获得设备ID,此操作只需要做一次 +3.注意:必须勾选‘安全性->允许网页浏览器的用户通过信任设备来跳过双重验证 +4.注意:在群晖信任设备页面里面会生成一条记录,不要删除 +5.注意:需要将流水线证书申请过期前多少天设置为30天以下,避免设备ID过期`, + required: false, + encrypt: true, + }) + deviceId = ""; + + @AccessInput({ + title: "忽略证书校验", + value: true, + component: { + name: "a-switch", + vModel: "checked", + }, + helper: "如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭", + }) + skipSslVerify = true; + + /** + * 请求超时时间设置 + * @param data + */ + @AccessInput({ + title: "请求超时", + value: 120, + component: { + name: "a-input-number", + vModel: "value", + }, + helper: "请求超时时间,单位:秒", + }) + timeout = 120; + + onLoginWithOPTCode(data: { otpCode: string }) { + console.log("onLoginWithOPTCode", this); + const ctx = this.ctx; + const client = new SynologyClient(this, ctx.http, ctx.logger, this.skipSslVerify); + return client.doLoginWithOTPCode(data.otpCode); + } +} + +new SynologyAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/synology/client.ts b/packages/ui/certd-server/src/plugins/plugin-plus/synology/client.ts new file mode 100644 index 000000000..769941b49 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/synology/client.ts @@ -0,0 +1,190 @@ +import { SynologyAccess } from "./access.js"; +import { HttpClient, ILogger } from "@certd/basic"; +import qs from "querystring"; + +export type SynologyAccessToken = { + sid: string; + did?: string; + synotoken: string; +}; + +export type SynologyRequest = { + method?: string; + apiParams: { + api: string; + version: number; + method: string; + }; + params?: any; + data?: any; + form?: any; + headers?: any; + useSynoToken?: boolean; +}; + +const device_name = "certd"; + +export class SynologyClient { + access: SynologyAccess; + http: HttpClient; + logger: ILogger; + skipSslVerify: boolean; + + token: SynologyAccessToken; + constructor(access: SynologyAccess, http: HttpClient, logger: ILogger, skipSslVerify: boolean) { + this.access = access; + this.http = http; + this.logger = logger; + this.skipSslVerify = skipSslVerify; + } + + // 登录 DSM 的函数 + async doLogin() { + const access = this.access; + if (access.otp && access.deviceId != null) { + this.logger.info("OTP登录"); + return await this.doLoginWithDeviceId(access.deviceId); + } + this.logger.info("使用普通登录"); + + const loginUrl = this.getLoginUrl(); + const res = await this.http.request({ + url: loginUrl, + method: "GET", + params: { + api: "SYNO.API.Auth", + version: 6, + method: "login", + account: access.username, + passwd: access.password, + session: "Certd", + format: "sid", + enable_syno_token: "yes", + }, + skipSslVerify: this.skipSslVerify ?? true, + timeout: this.access.timeout * 1000 || 120000, + }); + + if (!res.success) { + throw new Error(`登录失败: `, res.error); + } + this.logger.info("登录成功"); + + this.token = res.data as SynologyAccessToken; + return this.token; + } + + async doLoginWithOTPCode(otpCode: string) { + const loginUrl = this.getLoginUrl(); + const access = this.access; + const res = await this.http.request({ + url: loginUrl, + method: "GET", + params: { + api: "SYNO.API.Auth", + version: 6, + method: "login", + account: access.username, + passwd: access.password, + otp_code: otpCode, + enable_device_token: "yes", + device_name, + }, + timeout: this.access.timeout * 1000 || 30000, + skipSslVerify: this.skipSslVerify ?? true, + }); + + if (!res.success) { + throw new Error(`登录失败: `, res.error); + } + this.logger.info("登录成功"); + + this.token = res.data as SynologyAccessToken; + return this.token; + } + + private getLoginUrl() { + const access = this.access; + const loginPath = access.version === "6" ? "auth.cgi" : "entry.cgi"; + return `${access.baseUrl}/webapi/${loginPath}`; + } + + async doLoginWithDeviceId(device_id: string) { + const access = this.access; + const loginUrl = this.getLoginUrl(); + const res = await this.http.request({ + url: loginUrl, + method: "GET", + params: { + api: "SYNO.API.Auth", + version: 6, + method: "login", + account: access.username, + passwd: access.password, + device_name, + device_id, + session: "Certd", + format: "sid", + enable_syno_token: "yes", + }, + timeout: this.access.timeout * 1000 || 30000, + skipSslVerify: this.skipSslVerify ?? true, + }); + + if (!res.success) { + throw new Error(`登录失败: `, res.error); + } + this.logger.info("登录成功"); + + this.token = res.data as SynologyAccessToken; + return this.token; + } + + async doRequest(req: SynologyRequest) { + const sid = this.token.sid; + const method = req.method || "POST"; + const params = { + ...req.apiParams, + _sid: sid, // 使用登录后获得的 session ID + ...req.params, + SynoToken: this.token.synotoken, + }; + + const res = await this.http.request({ + url: `${this.access.baseUrl}/webapi/entry.cgi?${qs.stringify(params)}`, + method, + data: req.data, + headers: req.headers, + skipSslVerify: this.skipSslVerify ?? true, + timeout: this.access.timeout * 1000 || 30000, + }); + if (!res.success) { + throw new Error(`API 调用失败: ${JSON.stringify(res.error)}`); + } + return res.data; + } + + async getCertList() { + this.logger.info("获取证书列表"); + return await this.doRequest({ + method: "GET", + apiParams: { + api: "SYNO.Core.Certificate.CRT", + version: 1, + method: "list", + }, + }); + } + + async getInfo() { + this.logger.info("获取信息"); + return await this.doRequest({ + method: "GET", + apiParams: { + api: "SYNO.API.Info", + version: 1, + method: "query", + }, + }); + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/synology/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/synology/index.ts new file mode 100644 index 000000000..09ca4f9f1 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/synology/index.ts @@ -0,0 +1,3 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; +export * from "./client.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/synology/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/synology/plugins/index.ts new file mode 100644 index 000000000..860db409f --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/synology/plugins/index.ts @@ -0,0 +1 @@ +export * from "./plugin-deploy-to-panel.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/synology/plugins/plugin-deploy-to-panel.ts b/packages/ui/certd-server/src/plugins/plugin-plus/synology/plugins/plugin-deploy-to-panel.ts new file mode 100644 index 000000000..1182a47ee --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/synology/plugins/plugin-deploy-to-panel.ts @@ -0,0 +1,139 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertInfo, CertReader } from "@certd/plugin-cert"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-lib"; +import { SynologyClient } from "../client.js"; +import fs from "fs"; +import FormData from "form-data"; +import { SynologyAccess } from "../access.js"; +import { CertApplyPluginNames } from "@certd/plugin-cert"; +@IsTaskPlugin({ + name: "SynologyDeployToPanel", + title: "群晖-部署证书到群晖面板", + icon: "simple-icons:synology", + group: pluginGroups.panel.key, + desc: "Synology,支持6.x以上版本", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: true, +}) +export class SynologyDeployToPanel extends AbstractPlusTaskPlugin { + //测试参数 + @TaskInput({ + title: "群晖证书描述", + component: { + name: "a-input", + vModel: "value", + placeholder: "群晖证书描述", + }, + required: false, + helper: "在群晖证书管理页面里面,选择证书,点击操作,给证书设置描述,然后填写到这里\n如果不填,则覆盖更新全部证书", + }) + certName!: string; + + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + //授权选择框 + @TaskInput({ + title: "群晖授权", + helper: "群晖登录授权,请确保账户是管理员用户组", + component: { + name: "access-selector", + type: "synology", + }, + required: true, + }) + accessId!: string; + + async onInstance() {} + async execute(): Promise { + const access: SynologyAccess = await this.getAccess(this.accessId); + const client = new SynologyClient(access, this.ctx.http, this.ctx.logger, access.skipSslVerify); + // await client.init(); + await client.doLogin(); + // const res = await client.getInfo(); + // this.logger.info(res); + const certListRes = await client.getCertList(); + if (this.certName) { + const certItem = certListRes.certificates.find((item: any) => { + return item.desc === this.certName || item.subject.common_name === this.certName; + }); + if (!certItem) { + throw new Error(`未找到证书: ${this.certName}`); + } + this.logger.info(`找到证书: ${certItem.id}`); + await this.updateCertToPanel(client, certItem); + } else { + this.logger.info("开始更新全部证书"); + for (const item of certListRes.certificates) { + this.logger.info(`更新证书: ${item.id}`); + await this.updateCertToPanel(client, item); + } + } + } + + async updateCertToPanel(client: SynologyClient, certItem: any) { + /** + * query + * api: SYNO.Core.Certificate + * method: import + * version: 1 + * SynoToken: Bvum9p7BNeSc6 + * + * key: (二进制) + * cert: (二进制) + * inter_cert: (二进制) + * id: yxTtcC + * desc: certd + * as_default: + */ + this.logger.info(`更新证书:${certItem.id}`); + const certReader = new CertReader(this.cert); + + return certReader.readCertFile({ + logger: this.logger, + handle: async (ctx) => { + const form = new FormData(); + const { tmpCrtPath, tmpKeyPath, tmpIcPath } = ctx; + this.logger.info(`上传证书:${tmpCrtPath},${tmpKeyPath}`); + form.append("key", fs.createReadStream(tmpKeyPath)); + form.append("cert", fs.createReadStream(tmpCrtPath)); + if (certReader.cert.ic) { + this.logger.info(`包含中间证书:${tmpIcPath}`); + form.append("inter_cert", fs.createReadStream(tmpIcPath)); + } + form.append("id", certItem.id); + form.append("desc", certItem.desc); + // form传输必须是string,bool要改成string + // form.append("as_default", certItem.is_default + ""); + + console.log(JSON.stringify(form.getHeaders())); + return await client.doRequest({ + method: "POST", + apiParams: { + api: "SYNO.Core.Certificate", + version: 1, + method: "import", + }, + data: form, + headers: { + ...form.getHeaders(), + }, + }); + }, + }); + } +} +new SynologyDeployToPanel(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/access.ts new file mode 100644 index 000000000..2b704c5de --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/access.ts @@ -0,0 +1,34 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + */ +@IsAccess({ + name: "unicloud", + title: "uniCloud", + icon: "material-symbols:shield-outline", + desc: "unicloud授权", +}) +export class UniCloudAccess extends BaseAccess { + @AccessInput({ + title: "账号", + component: { + placeholder: "email", + }, + helper: "登录邮箱", + required: true, + encrypt: false, + }) + email = ""; + + @AccessInput({ + title: "密码", + component: { + placeholder: "密码", + }, + required: true, + encrypt: true, + }) + password = ""; +} + +new UniCloudAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/client.ts b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/client.ts new file mode 100644 index 000000000..f94095cff --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/client.ts @@ -0,0 +1,169 @@ +import { UniCloudAccess } from "./access.js"; +import { http, HttpClient, HttpRequestConfig, ILogger } from "@certd/basic"; +import { CertInfo } from "@certd/plugin-cert"; + +type UniCloudClientOpts = { access: UniCloudAccess; logger: ILogger; http: HttpClient }; + +export class UniCloudClient { + opts: UniCloudClientOpts; + + deviceId: string; + xToken: string; + token: string; + cookie: string; + + constructor(opts: UniCloudClientOpts) { + this.opts = opts; + this.deviceId = new Date().getTime() + Math.floor(Math.random() * 1000000) + ""; + } + + async sign(data: any, secretKey: string) { + const Crypto = await import("crypto-js"); + const CryptoJS = Crypto.default; + let content = ""; + Object.keys(data) + .sort() + .forEach(function (key) { + if (data[key]) { + content = content + "&" + key + "=" + data[key]; + } + }); + content = content.slice(1); + return CryptoJS.HmacMD5(content, secretKey).toString(); + } + async doRequest(req: HttpRequestConfig) { + const res = await http.request({ + ...req, + logRes: false, + returnOriginRes: true, + }); + const data = res.data; + if (data.ret != null) { + if (data.ret !== 0) { + throw new Error(JSON.stringify(data)); + } + return data.data; + } + if (!data.success) { + throw new Error(JSON.stringify(data.error)); + } + if (data.data?.errCode) { + throw new Error(JSON.stringify(data.data)); + } + return data.data; + } + + async login() { + if (this.xToken) { + return this.xToken; + } + const deviceId = this.deviceId; + const username = this.opts.access.email; + const password = this.opts.access.password; + function getClientInfo(appId) { + return `{"PLATFORM":"web","OS":"windows","APPID":"${appId}","DEVICEID":"${deviceId}","scene":1001,"appId":"${appId}","appLanguage":"zh-Hans","appName":"账号中心","appVersion":"1.0.0","appVersionCode":"100","browserName":"chrome","browserVersion":"122.0.6261.95","deviceId":"174585375190823882061","deviceModel":"PC","deviceType":"pc","hostName":"chrome","hostVersion":"122.0.6261.95","osName":"windows","osVersion":"10 x64","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36","uniCompilerVersion":"4.45","uniPlatform":"web","uniRuntimeVersion":"4.45","locale":"zh-Hans","LOCALE":"zh-Hans"}`; + } + const clientInfo = getClientInfo("__UNI__uniid_server"); + const loginData = { + method: "serverless.function.runtime.invoke", + params: `{"functionTarget":"uni-id-co","functionArgs":{"method":"login","params":[{"password":"${password}","captcha":"","resetAppId":"__UNI__unicloud_console","resetUniPlatform":"web","isReturnToken":false,"email":"${username}"}],"clientInfo":${clientInfo}}}`, + spaceId: "uni-id-server", + timestamp: new Date().getTime(), + }; + + const secretKey = "ba461799-fde8-429f-8cc4-4b6d306e2339"; + const xSign = await this.sign(loginData, secretKey); + const res = await this.doRequest({ + url: "https://account.dcloud.net.cn/client", + method: "POST", + data: loginData, + headers: { + "X-Serverless-Sign": xSign, + Origin: "https://account.dcloud.net.cn", + Referer: "https://account.dcloud.net.cn", + }, + }); + + const token = res.newToken.token; + // const uid = res.data.uid; + this.xToken = token; + this.opts.logger.info("登录成功:", token); + return token; + } + + async getToken() { + if (this.token) { + return { + token: this.token, + cookie: this.cookie, + }; + } + const xToken = await this.login(); + + const deviceId = this.deviceId; + const secretKey = "4c1f7fbf-c732-42b0-ab10-4634a8bbe834"; + const clientInfo = `{"PLATFORM":"web","OS":"windows","APPID":"__UNI__unicloud_console","DEVICEID":"${deviceId}","scene":1001,"appId":"__UNI__unicloud_console","appLanguage":"zh-Hans","appName":"uniCloud控制台","appVersion":"1.0.0","appVersionCode":"100","browserName":"chrome","browserVersion":"122.0.6261.95","deviceId":"${deviceId}","deviceModel":"PC","deviceType":"pc","hostName":"chrome","hostVersion":"122.0.6261.95","osName":"windows","osVersion":"10 x64","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36","uniCompilerVersion":"4.57","uniPlatform":"web","uniRuntimeVersion":"4.57","locale":"zh-Hans","LOCALE":"zh-Hans"}`; + + const body = { + method: "serverless.function.runtime.invoke", + params: `{"functionTarget":"uni-cloud-kernel","functionArgs":{"action":"user/getUserToken","data":{"isLogin":true},"clientInfo":${clientInfo},"uniIdToken":"${xToken}"}}`, + spaceId: "dc-6nfabcn6ada8d3dd", + timestamp: new Date().getTime(), + }; + + const xSign = await this.sign(body, secretKey); + const res = await this.doRequest({ + url: "https://unicloud.dcloud.net.cn/client", + method: "POST", + data: body, + headers: { + "X-Client-Info": encodeURIComponent(clientInfo), + "X-Serverless-Sign": xSign, + "X-Client-Token": xToken, + Origin: "https://unicloud.dcloud.net.cn", + Referer: "https://unicloud.dcloud.net.cn", + }, + }); + + const token = res.data.data.token; + const cookies = res.headers["set-cookie"]; + let cookie = ""; + for (let i = 0; i < cookies.length; i++) { + const item = cookies[i].substring(0, cookies[i].indexOf(";")); + cookie += item + ";"; + } + this.token = token; + this.opts.logger.info("获取token成功:", token); + this.cookie = cookie; + return { + token, + cookie, + }; + } + + async createCert(req: { spaceId: string; domain: string; provider: string; cert: CertInfo }) { + await this.getToken(); + const { spaceId, domain, cert, provider } = req; + this.opts.logger.info(`开始部署证书, provider:${provider},spaceId:${spaceId},domain:${domain}`); + const crt = encodeURIComponent(cert.crt); + const key = encodeURIComponent(cert.key); + const body = { + appid: "", + provider, + spaceId: spaceId, + domain: domain, + cert: crt, + key, + }; + const res = await this.doRequest({ + url: "https://unicloud-api.dcloud.net.cn/unicloud/api/host/create-domain-with-cert", + method: "POST", + data: body, + headers: { + Token: this.token, + Cookie: this.cookie, + }, + }); + this.opts.logger.info("证书部署成功:", JSON.stringify(res)); + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/index.ts new file mode 100644 index 000000000..09ca4f9f1 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/index.ts @@ -0,0 +1,3 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; +export * from "./client.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/plugins/index.ts new file mode 100644 index 000000000..2f5e88a84 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/plugins/index.ts @@ -0,0 +1 @@ +export * from "./plugin-deploy-to-space.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/plugins/plugin-deploy-to-space.ts b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/plugins/plugin-deploy-to-space.ts new file mode 100644 index 000000000..50496f3e1 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/unicloud/plugins/plugin-deploy-to-space.ts @@ -0,0 +1,111 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { UniCloudAccess } from "../access.js"; +import { UniCloudClient } from "../client.js"; + +@IsTaskPlugin({ + name: "UniCloudDeployToSpace", + title: "uniCloud-部署到服务空间", + icon: "material-symbols:shield-outline", + group: pluginGroups.panel.key, + desc: "部署到服务空间", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class UniCloudDeployToSpace extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + //授权选择框 + @TaskInput({ + title: "uniCloud授权", + helper: "uniCloud授权", + component: { + name: "access-selector", + type: "unicloud", + }, + required: true, + }) + accessId!: string; + + //测试参数 + @TaskInput({ + title: "服务空间ID", + component: { + name: "a-input", + vModel: "value", + }, + helper: "spaceId", + }) + spaceId!: string; + + @TaskInput({ + title: "空间提供商", + component: { + name: "a-select", + vModel: "value", + options: [ + { + label: "阿里云", + value: "aliyun", + }, + { + label: "腾讯云", + value: "tencent", + }, + { + label: "支付宝云", + value: "alipay", + }, + ], + }, + helper: "空间提供商", + }) + provider!: string; + + @TaskInput({ + title: "空间域名", + component: { + name: "a-select", + vModel: "value", + mode: "tags", + open: false, + }, + helper: "空间域名", + }) + domains!: string[]; + + async onInstance() {} + async execute(): Promise { + const access = await this.getAccess(this.accessId); + const client = new UniCloudClient({ + access, + logger: this.logger, + http: this.http, + }); + + for (const domain of this.domains) { + await client.createCert({ + domain, + provider: this.provider, + spaceId: this.spaceId, + cert: this.cert, + }); + } + this.logger.info("部署成功"); + } +} +new UniCloudDeployToSpace(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/wxpay/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/wxpay/access.ts new file mode 100644 index 000000000..5210687f5 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/wxpay/access.ts @@ -0,0 +1,65 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +@IsAccess({ + name: "wxpay", + title: "微信支付", + icon: "tdesign:logo-wechatpay-filled", +}) +export class WxpayAccess extends BaseAccess { + /** + * appId: "<-- 请填写您的AppId,例如:2019091767145019 -->", + * privateKey: "<-- 请填写您的应用私钥,例如:MIIEvQIBADANB ... ... -->", + * alipayPublicKey: "<-- 请填写您的支付宝公钥,例如:MIIBIjANBg... -->", + */ + @AccessInput({ + title: "AppId", + component: { + placeholder: "201909176714xxxx", + }, + required: true, + encrypt: false, + }) + appId: string; + @AccessInput({ + title: "商户ID", + component: { + placeholder: "201909176714xxxx", + }, + required: true, + encrypt: false, + }) + mchid: string; + + @AccessInput({ + title: "公钥", + component: { + name: "a-textarea", + rows: 3, + placeholder: "MIIBIjANBg...", + }, + required: true, + encrypt: true, + }) + publicKey: string; + + @AccessInput({ + title: "私钥", + component: { + placeholder: "MIIEvQIBADANB...", + name: "a-textarea", + rows: 3, + }, + required: true, + encrypt: true, + }) + privateKey: string; + + @AccessInput({ + title: "APIv3密钥", + helper: "微信商户平台—>账户设置—>API安全—>设置APIv3密钥", + required: true, + encrypt: true, + }) + key: string; +} + +new WxpayAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/wxpay/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/wxpay/index.ts new file mode 100644 index 000000000..655094d02 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/wxpay/index.ts @@ -0,0 +1 @@ +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/client.ts b/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/client.ts new file mode 100644 index 000000000..97de47517 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/client.ts @@ -0,0 +1,314 @@ +import crypto from "crypto-js"; +import { HttpClient, HttpRequestConfig, ILogger, utils } from "@certd/basic"; +import { Pager, PageSearch } from "@certd/pipeline"; + +export class XinnetClient { + access = null; + http = null; + logger = null; + + xTickets = null; + loginCookies = null; + domainTokenCookie = null; + + userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0"; + + constructor(opts: { access: { username: string; password: string }; logger: ILogger; http: HttpClient }) { + this.access = opts.access; + this.http = opts.http; + this.logger = opts.logger; + } + + async doRedirectRequest(conf: HttpRequestConfig) { + let resRedirect = null; + try { + resRedirect = await this.http.request(conf); + } catch (e) { + resRedirect = e.response; + this.logger.info(resRedirect.headers); + if (!resRedirect) { + throw new Error("请求失败:", e); + } + } + return resRedirect; + } + + getCookie(response: any) { + const setCookie = response.headers["set-cookie"]; + if (!setCookie || setCookie.length === 0) { + throw new Error("未获取到cookie", response); + } + return setCookie + .map(item => { + return item.split(";")[0]; + }) + .join(";"); + } + + async getToken() { + const res = await this.http.request({ + url: "https://login.xinnet.com/queryUOne", + method: "get", + }); + this.logger.info("queryUOne", res.data); + const { uOne, uTwo } = res.data; + + const res1 = await this.doRedirectRequest({ + url: "https://login.xinnet.com/newlogin", + method: "get", + headers: { + Host: "login.xinnet.com", + Origin: "https://login.xinnet.com", + Referer: "https://login.xinnet.com/separatePage/?service=https://www.xinnet.com/", + }, + maxRedirects: 0, + withCredentials: true, + returnOriginRes: true, + }); + + const cookie = this.getCookie(res1); + this.logger.info("firstCookie", cookie); + + function encrypt(password, utwo) { + // return "" + crypto.encrypt(password, utwo); + return crypto.AES.encrypt(password, utwo).toString(); + } + + const data = { + username: this.access.username, + password: encrypt(this.access.password, uTwo), + uOne: uOne, + randStr: "", + ticket: "", + service: "", + isRemoteLogin: false, + }; + const formData = new FormData(); + for (const key in data) { + formData.append(key, data[key]); + } + const res2 = await this.http.request({ + url: "https://login.xinnet.com/newlogin", + method: "post", + headers: { + Origin: "https://login.xinnet.com", + Referer: "https://login.xinnet.com/separatePage/?service=https://www.xinnet.com/", + "Content-Type": "multipart/form-data", + Cookie: cookie, + }, + data: formData, + withCredentials: true, + returnOriginRes: true, + }); + // console.log(res2.data); + const loginedCookie = this.getCookie(res2); + this.logger.info("登录成功,loginCookie:", loginedCookie); + const tickets = res2.data.data.xTickets; + this.logger.info("tickets:", tickets); + + this.xTickets = tickets; + this.loginCookies = loginedCookie; + + const xticketArr = this.xTickets.split("###"); + // const ssoTiccket = xticketArr[0]; + const domainTicket = xticketArr[3]; + + // "jsonp_" + (Math.floor(1e5 * Math.random()) * Date.now()).toString(16) + const jsonp = "jsonp_" + (Math.floor(1e5 * Math.random()) * Date.now()).toString(16); + + const xtokenUrl = `https://domain.xinnet.com/domainsso/getXtoken?xticket=${domainTicket}&callback=${jsonp}`; + console.log("getxtoken-------", xtokenUrl); + const res4 = await this.doRedirectRequest({ + // https://domain.xinnet.com/domainsso/getXtoken?xticket=gZNBBDObcyxKaQqRVDj&callback=jsonp_6227d9fe0004c4 + url: xtokenUrl, + method: "get", + headers: { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0", + cookie: loginedCookie, + }, + maxRedirects: 0, + withCredentials: true, + returnOriginRes: true, + }); + + const cookie4 = this.getCookie(res4); + this.logger.info("获取domainXtoken成功:", cookie4); + this.domainTokenCookie = cookie4; + } + + async getDomainList(data: PageSearch): Promise<{ totalRows: number; list: { domainName: string; serviceCode: string }[] }> { + if (!this.domainTokenCookie) { + await this.getToken(); + } + + const pager = new Pager(data); + const domainListUrl = "https://domain.xinnet.com/domainManage/domainList"; + + const res = await this.doDomainRequest({ + url: domainListUrl, + method: "post", + data: { + pageNo: pager.pageNo, + pageSize: pager.pageSize, + domainName: data.searchKey ?? "", + }, + }); + return res; + } + + async doDomainRequest(conf: HttpRequestConfig) { + if (!this.domainTokenCookie) { + await this.getToken(); + } + const res = await this.http.request({ + url: conf.url, + method: conf.method ?? "post", + headers: { + Host: "domain.xinnet.com", + Origin: "https://domain.xinnet.com", + Referer: "https://domain.xinnet.com/", + "User-Agent": this.userAgent, + cookie: this.domainTokenCookie, + }, + data: conf.data, + withCredentials: true, + }); + return res; + } + + async getDcpCookie(opts: { serviceCode: string }) { + if (!this.domainTokenCookie) { + await this.getToken(); + } + const domainTokenCookie = this.domainTokenCookie; + const serviceCode = opts.serviceCode; + const redirectDcpUrl = "https://domain.xinnet.com/dcp?serviceCode=" + serviceCode + "&type=analytic"; + const res10 = await this.doRedirectRequest({ + url: redirectDcpUrl, + method: "get", + headers: { + cookie: domainTokenCookie, + }, + maxRedirects: 0, + withCredentials: true, + returnOriginRes: true, + }); + + const location = res10.headers["location"]; + console.log("跳转到dcp:", location); + + const resRedirect = await this.doRedirectRequest({ + url: location, + method: "get", + maxRedirects: 0, + withCredentials: true, + returnOriginRes: true, + }); + + const newCookie = this.getCookie(resRedirect); + this.logger.info("dcpCookie", newCookie); + return newCookie; + } + + async getDomainDnsList(opts: { serviceCode: string; recordValue?: string; dcpCookie }) { + const dnsListURL = "https://dcp.xinnet.com/dcp/domaincloudanalytic/list"; + const res = await this.http.request({ + url: dnsListURL, + method: "post", + headers: { + "Content-Type": "application/json; charset=UTF-8", + Host: "dcp.xinnet.com", + Origin: "https://dcp.xinnet.com", + Referer: "https://dcp.xinnet.com/dcpProduct.html", + cookie: opts.dcpCookie, + }, + data: { + type: "ALL", + content: opts.recordValue || "", + skip: 1, + limit: 10, + }, + withCredentials: true, + }); + if (res.code != 0) { + this.logger.error("获取DNS列表失败", JSON.stringify(res)); + throw new Error("获取DNS列表失败"); + } + return res.data?.list; + } + + async addDomainDnsRecord(req: { recordName: string; type: string; recordValue: string }, opts: { serviceCode: string; dcpCookie: string }) { + const addDnsUrl = "https://dcp.xinnet.com/dcp/domaincloudanalytic/add"; + const addRes = await this.doDcpRequest( + { + url: addDnsUrl, + method: "post", + data: { + recordName: req.recordName, + type: req.type, + content: req.recordValue, + ttl: 600, + phoneCode: 1, + }, + }, + opts + ); + this.logger.info(addRes); + + await utils.sleep(3000); + + const res = await this.getDomainDnsList({ + serviceCode: opts.serviceCode, + recordValue: req.recordValue, + dcpCookie: opts.dcpCookie, + }); + // console.log(res.data); + if (!res || res.length === 0) { + throw new Error("未找到添加的DNS记录"); + } + const item = res[0]; + return { + recordId: item.id, + recordFullName: item.name, + recordValue: item.content, + type: item.type, + }; + } + + async deleteDomainDnsRecord(req: { recordId: number; recordFullName: string; type: string; recordValue: string }, opts: { serviceCode: string; dcpCookie }) { + const delDnsUrl = "https://dcp.xinnet.com/dcp/domaincloudanalytic/delete"; + const res13 = await this.doDcpRequest( + { + url: delDnsUrl, + method: "post", + data: { + recordId: req.recordId, + recordName: req.recordFullName, + content: req.recordValue, + type: req.type, + isBatch: 0, + phoneCode: 1, + }, + }, + opts + ); + console.log(res13.data); + return res13; + } + + async doDcpRequest(req: HttpRequestConfig, opts: { serviceCode: string; dcpCookie: string }) { + return await this.http.request({ + url: req.url, + method: req.method ?? "post", + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + Host: "dcp.xinnet.com", + Origin: "https://dcp.xinnet.com", + Referer: "https://dcp.xinnet.com/dcpProduct.html", + cookie: opts.dcpCookie, + }, + withCredentials: true, + data: req.data, + }); + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/index.ts new file mode 100644 index 000000000..fcd2f4c90 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/index.ts @@ -0,0 +1 @@ +export * from "./client.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/test-checkCode.mjs b/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/test-checkCode.mjs new file mode 100644 index 000000000..913fe86f5 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/test-checkCode.mjs @@ -0,0 +1,161 @@ +import axios from "axios"; +import crypto from "crypto-js"; +import https from "https"; +import qs from "qs"; + +function getCookie(res1) { + let setCookie = res1.headers["set-cookie"]; + console.log(setCookie); + let cookie = setCookie + .map(item => { + return item.split(";")[0]; + }) + .join(";"); + return cookie; +} + +async function login() { + const httpsAgent = new https.Agent({ + rejectUnauthorized: false, // 这里可以设置为 false 来忽略 SSL 证书验证 + }); + const instance = axios.create({ + timeout: 3000, // 请求超时时间 + withCredentials: true, + httpsAgent, + headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0" }, // 设置请求头 + }); + + // + // const res = await instance.get("https://login.xinnet.com/queryUOne"); + // + // console.log(res.data?.data); + // const { uOne, uTwo } = res.data.data; + + let res1 = null; + try { + res1 = await instance.request({ + url: "https://dcp.xinnet.com/", + method: "get", + headers: {}, + maxRedirects: 0, + withCredentials: true, + }); + } catch (e) { + console.log(e.response.headers); + res1 = e.response; + } + + let cookie = getCookie(res1); + + console.log(cookie); + + function encrypt(password, secret) { + // return "" + crypto.encrypt(password, utwo); + return crypto.AES.encrypt(password, secret).toString(); + } + + const codeGetUrl = "https://dcp.xinnet.com/domain/getValidatePic"; + + const res2 = await instance.request({ + url: codeGetUrl, + method: "get", + responseType: "arraybuffer", + headers: { + "Content-Type": "application/json;charset=utf-8", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0", + Host: "dcp.xinnet.com", + cookie: cookie, + }, + // maxRedirects: 0, + withCredentials: true, + }); + console.log("status:", res2.status); + const imageData = res2.data; + let imageBuffer = Buffer.from(imageData, "binary"); + + const res3 = await axios.request({ + url: "https://ocr.com/ocr", + method: "post", + headers: { + Authorization: "Basic " + Buffer.from("username:password").toString("base64"), + }, + httpsAgent, + data: { + image: imageBuffer.toString("base64"), + }, + }); + + console.log(res3.data.result.ocr_response); + let text = res3.data.result.ocr_response.map(item => item.text.trim()).join(""); + text = text.replaceAll(" ", ""); + console.log(text); + + const url = "https://dcp.xinnet.com/domain/validEnter"; + const password = encrypt("jidian1zu", "this is temp before https"); + // const body = { + // domainName: "ulogin.top", + // password: encodeURIComponent(password), + // checkCode: encodeURIComponent( text), + // } + const body = { + domainName: "ulogin.top", + password: password, + checkCode: text, + }; + const query = qs.stringify(body); + console.log(query); + const res4 = await instance.request({ + url: url, + method: "post", + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + // cookie: cookie, + Host: "dcp.xinnet.com", + Origin: "https://dcp.xinnet.com", + Referer: "https://dcp.xinnet.com/", + cookie: cookie, + }, + data: body, + withCredentials: true, + }); + console.log(res4.data); + + const domainEnterUrl = "https://dcp.xinnet.com/domain/domainEnter"; + const res6 = await instance.request({ + url: domainEnterUrl, + method: "post", + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + Host: "dcp.xinnet.com", + Origin: "https://dcp.xinnet.com", + Referer: "https://dcp.xinnet.com/", + cookie: cookie, + }, + data: body, + withCredentials: true, + }); + + console.log(res6.data); + + const listUrl = "https://dcp.xinnet.com/dcp/domaincloudanalytic/list"; + const res5 = await instance.request({ + url: listUrl, + method: "post", + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + Host: "dcp.xinnet.com", + Origin: "https://dcp.xinnet.com", + Referer: "https://dcp.xinnet.com/dcpProduct.html", + cookie: cookie, + }, + withCredentials: true, + data: { + type: "ALL", + content: "", + limit: 10, + }, + }); + console.log(res5.data); +} + +login(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/test-login.mjs b/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/test-login.mjs new file mode 100644 index 000000000..726eb9f6a --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/xinnet/test-login.mjs @@ -0,0 +1,341 @@ +import axios from "axios"; +import crypto from "crypto-js"; + +function getCookie(res1) { + let setCookie = res1.headers["set-cookie"]; + console.log(setCookie); + let cookie = setCookie + .map(item => { + return item.split(";")[0]; + }) + .join(";"); + return cookie; +} + +async function login() { + const instance = axios.create({ + timeout: 3000, // 请求超时时间 + withCredentials: true, + headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0" }, // 设置请求头 + }); + + const res = await instance.get("https://login.xinnet.com/queryUOne"); + + console.log(res.data?.data); + const { uOne, uTwo } = res.data.data; + + let res1 = null; + try { + res1 = await instance.request({ + url: "https://login.xinnet.com/newlogin", + method: "get", + headers: { + Host: "login.xinnet.com", + Origin: "https://login.xinnet.com", + Referer: "https://login.xinnet.com/separatePage/?service=https://www.xinnet.com/", + }, + maxRedirects: 0, + withCredentials: true, + }); + } catch (e) { + console.log(e.response.headers); + res1 = e.response; + } + + let cookie = getCookie(res1); + + function encrypt(password, utwo) { + // return "" + crypto.encrypt(password, utwo); + return crypto.AES.encrypt(password, utwo).toString(); + } + + const data = { + username: 18603046467, + password: encrypt("xxxxxxxxxxxxxpassword", uTwo), + uOne: uOne, + randStr: "", + ticket: "", + service: "", + isRemoteLogin: false, + }; + const formData = new FormData(); + for (const key in data) { + formData.append(key, data[key]); + } + const res2 = await instance.request({ + url: "https://login.xinnet.com/newlogin", + method: "post", + headers: { + Origin: "https://login.xinnet.com", + Referer: "https://login.xinnet.com/separatePage/?service=https://www.xinnet.com/", + "Content-Type": "multipart/form-data", + Cookie: cookie, + }, + data: formData, + withCredentials: true, + }); + console.log(res2.data); + let loginedCookie = getCookie(res2); + console.log("loginCookie", loginedCookie); + + const tickets = res2.data.data.xTickets; + + const xticketArr = tickets.split("###"); + const ssoTiccket = xticketArr[0]; + const domainTicket = xticketArr[3]; + + // "jsonp_" + (Math.floor(1e5 * Math.random()) * Date.now()).toString(16) + const jsonp = "jsonp_" + (Math.floor(1e5 * Math.random()) * Date.now()).toString(16); + + // const ssoUrl = `https://www.xinnet.com/sso/getXtoken?xticket=${ssoTiccket}&callback=${jsonp}`; + // const res3 = await axios.request({ + // url: ssoUrl, + // method: "get", + // headers: { + // Host: "www.xinnet.com", + // Origin: "https://login.xinnet.com", + // Referer: "https://login.xinnet.com/separatePage/?service=https://www.xinnet.com/", + // "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/}" + // }, + // cookie:loginedCookie, + // maxRedirects: 0, + // withCredentials: true + // }); + // + // console.log(res3.data); + // let cookie2 = getCookie(res3); + // console.log(cookie2); + + let res4 = null; + try { + const xtokenUrl = `https://domain.xinnet.com/domainsso/getXtoken?xticket=${domainTicket}&callback=${jsonp}`; + console.log("getxtoken-------", xtokenUrl); + res4 = await instance.request({ + // https://domain.xinnet.com/domainsso/getXtoken?xticket=gZNBBDObcyxKaQqRVDj&callback=jsonp_6227d9fe0004c4 + url: xtokenUrl, + method: "get", + headers: { + /** + * Host: + * ssl.xinnet.com + * Referer: + * https://login.xinnet.com/separatePage/?service=https://www.xinnet.com/ + */ + // Host: "ssl.xinnet.com", + // Referer: "https://login.xinnet.com/separatePage/?service=https://www.xinnet.com/", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0", + cookie: loginedCookie, + }, + maxRedirects: 0, + withCredentials: true, + }); + } catch (e) { + res4 = e.response; + console.log(res4.headers); + } + console.log(res4.data); + let domainTokenCookie = getCookie(res4); + console.log("domainTokenCookie", domainTokenCookie); + + // + // let res8 = null; + // const consoleXtokenUrl = `https://console.xinnet.com/sso/getXtoken?xticket=${domainTicket}&callback=${jsonp}`; + // console.log("getConsolextoken-------", consoleXtokenUrl); + // res8 = await instance.request({ + // // https://domain.xinnet.com/domainsso/getXtoken?xticket=gZNBBDObcyxKaQqRVDj&callback=jsonp_6227d9fe0004c4 + // url: consoleXtokenUrl, + // method: "get", + // headers: { + // "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0", + // cookie: loginedCookie + // }, + // maxRedirects: 0, + // withCredentials: true + // }); + // + // console.log(res8.data); + // let consoleTokenCookie = getCookie(res8); + // console.log("consoleTokenCookie", consoleTokenCookie); + + // const consoleIdUrl = "https://console.xinnet.com/usercommon/getShopcartNum"; + // + // const res7 = await instance.request({ + // url: consoleIdUrl, + // method: "get", + // headers: { + // Host: "console.xinnet.com", + // Referer: "https://domain.xinnet.com/", + // cookie: consoleTokenCookie +";"+ loginedCookie + // } + // }); + // console.log(res7.data); + // let consoleIdCookie = getCookie(res7); + // console.log("consoleIdCookie",consoleIdCookie); + + const domainListUrl = "https://domain.xinnet.com/domainManage/domainList"; + + const res5 = await instance.request({ + url: domainListUrl, + method: "post", + headers: { + /** + * Host: + * domain.xinnet.com + * Origin: + * https://domain.xinnet.com + * Referer: + * https://domain.xinnet.com/ + */ + Host: "domain.xinnet.com", + Origin: "https://domain.xinnet.com", + Referer: "https://domain.xinnet.com/", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0", + cookie: domainTokenCookie, + }, + data: { + /** + * pageNo: 1 + * pageSize: 10 + * orderByProperty: expire_date + * orderByType: asc + */ + pageNo: 1, + pageSize: 10, + }, + withCredentials: true, + }); + console.log(res5.data); + + // const bindUrls = [ + // "https://domain.xinnet.com/domainManage/inspectDomainRealname", + // "https://domain.xinnet.com/domainManage/inspectDomainBindPhone", + // "https://domain.xinnet.com/domainManage/inspectDomainXinnetDns", + // "https://domain.xinnet.com/domainManage/inspectDomainEvents" + // ]; + // const consoleIdCookie = consoleTokenCookie.split(";")[0]; + // for (const url of bindUrls) { + // console.log("do bind:", url); + // const cookie1 = consoleIdCookie + ";" + domainTokenCookie; + // console.log("cookie1", cookie1); + // const res9 = await instance.request({ + // url: url, + // method: "post", + // headers: { + // "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + // Host: "domain.xinnet.com", + // Origin: "https://domain.xinnet.com", + // Referer: "https://domain.xinnet.com", + // cookie: cookie1 + // }, + // data: { + // domainName: "ulogin.top" + // }, + // withCredentials: true + // }); + // console.log(res9.data); + // } + + const serviceCode = "D76534287817377"; + const redirectDcpUrl = "https://domain.xinnet.com/dcp?serviceCode=" + serviceCode + "&type=analytic"; + let res10 = null; + try { + res10 = await instance.request({ + url: redirectDcpUrl, + method: "get", + headers: { + cookie: domainTokenCookie, + }, + maxRedirects: 0, + withCredentials: true, + }); + } catch (e) { + res10 = e.response; + console.log(res10.headers); + } + const location = res10.headers["location"]; + console.log("跳转到dcp:", location); + + let resRedirect = null; + try { + resRedirect = await instance.request({ + url: location, + method: "get", + maxRedirects: 0, + withCredentials: true, + }); + } catch (e) { + resRedirect = e.response; + console.log(resRedirect.headers); + } + + const newCookie = getCookie(resRedirect); + console.log("newCookie", newCookie); + + const dnsListURL = "https://dcp.xinnet.com/dcp/domaincloudanalytic/list"; + const res11 = await instance.request({ + url: dnsListURL, + method: "post", + headers: { + "Content-Type": "application/json; charset=UTF-8", + Host: "dcp.xinnet.com", + Origin: "https://dcp.xinnet.com", + Referer: "https://dcp.xinnet.com/dcpProduct.html", + cookie: newCookie, + }, + data: { + type: "ALL", + content: "", + skip: 1, + limit: 10, + }, + withCredentials: true, + }); + console.log(res11.data); + + //add dns + // const addDnsUrl = "https://dcp.xinnet.com/dcp/domaincloudanalytic/add" + // const res12 = await instance.request({ + // url: addDnsUrl, + // method: "post", + // headers: { + // "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + // Host: "dcp.xinnet.com", + // Origin: "https://dcp.xinnet.com", + // Referer: "https://dcp.xinnet.com/dcpProduct.html", + // cookie: newCookie + // }, + // data: { + // recordName: "343533", + // type: "TXT", + // content: "456456", + // ttl: 600, + // phoneCode: 1, + // } + // }) + // console.log(res12.data); + // + // const delDnsUrl = "https://dcp.xinnet.com/dcp/domaincloudanalytic/delete" + // + // const res13 = await instance.request({ + // url: delDnsUrl, + // method: "post", + // headers: { + // "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + // Host: "dcp.xinnet.com", + // Origin: "https://dcp.xinnet.com", + // Referer: "https://dcp.xinnet.com/dcpProduct.html", + // cookie: newCookie + // }, + // data:{ + // recordId: 167529045, + // recordName: "aaaa.ulogin.top", + // content: "aaaa", + // type: "TXT", + // isBatch: 0, + // phoneCode: 1, + // } + // }) + // console.log(res13.data); +} + +login(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access-rcdn.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access-rcdn.ts new file mode 100644 index 000000000..ac0a8d150 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access-rcdn.ts @@ -0,0 +1,35 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "yidunrcdn", + title: "易盾rcdn授权", + icon: "material-symbols:shield-outline", + desc: "易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8)", +}) +export class YidunRcdnAccess extends BaseAccess { + @AccessInput({ + title: "账户", + component: { + placeholder: "手机号", + }, + required: true, + encrypt: true, + }) + username = ""; + + @AccessInput({ + title: "密码", + component: { + placeholder: "password", + }, + required: true, + encrypt: true, + }) + password = ""; +} + +new YidunRcdnAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access-sms.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access-sms.ts new file mode 100644 index 000000000..717142106 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access-sms.ts @@ -0,0 +1,36 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "yfysms", + title: "易发云短信", + icon: "material-symbols:shield-outline", + desc: "sms.yfyidc.cn/", +}) +export class YfySmsAccess extends BaseAccess { + @AccessInput({ + title: "KeyID", + component: { + placeholder: "api_key", + }, + helper: "[获取密钥](http://sms.yfyidc.cn/user/index#)", + required: true, + encrypt: true, + }) + keyId = ""; + + @AccessInput({ + title: "KeySecret", + component: { + placeholder: "", + }, + required: true, + encrypt: true, + }) + keySecret = ""; +} + +new YfySmsAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access.ts new file mode 100644 index 000000000..0917aa92b --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/access.ts @@ -0,0 +1,37 @@ +import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "yidun", + title: "易盾DCDN授权", + icon: "material-symbols:shield-outline", + desc: "https://user.yiduncdn.com", +}) +export class YidunAccess extends BaseAccess { + @AccessInput({ + title: "api_key", + component: { + placeholder: "api_key", + }, + helper: "http://user.yiduncdn.com/console/index.html#/account/config/api,点击开启后获取", + required: true, + encrypt: true, + }) + apiKey = ""; + + @AccessInput({ + title: "api_secret", + component: { + placeholder: "api_secret", + }, + helper: "http://user.yiduncdn.com/console/index.html#/account/config/api,点击开启后获取", + required: true, + encrypt: true, + }) + apiSecret = ""; +} + +new YidunAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yidun/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/index.ts new file mode 100644 index 000000000..67f6f0203 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/index.ts @@ -0,0 +1,4 @@ +export * from "./plugins/index.js"; +export * from "./access.js"; +export * from "./access-rcdn.js"; +export * from "./access-sms.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/index.ts new file mode 100644 index 000000000..c8d4216a7 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/index.ts @@ -0,0 +1,2 @@ +export * from "./plugin-deploy-to-cdn.js"; +export * from "./plugin-deploy-to-rcdn.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/plugin-deploy-to-cdn.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/plugin-deploy-to-cdn.ts new file mode 100644 index 000000000..67f1a5cf2 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/plugin-deploy-to-cdn.ts @@ -0,0 +1,147 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; + +@IsTaskPlugin({ + name: "YidunDeployToCDN", + title: "易盾-部署到易盾DCDN", + icon: "material-symbols:shield-outline", + group: pluginGroups.cdn.key, + desc: "主要是防御,http://user.yiduncdn.com/", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class YidunDeployToCDNPlugin extends AbstractTaskPlugin { + //测试参数 + @TaskInput({ + title: "证书ID", + component: { + name: "a-input-number", + vModel: "value", + }, + helper: "证书ID,在证书管理页面查看,每条记录都有证书id", + }) + certId!: number; + + @TaskInput({ + title: "网站域名", + component: { + name: "a-input", + vModel: "value", + }, + helper: "网站域名和证书ID选填其中一个,填了证书ID,则忽略网站域名", + }) + domain!: number; + + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + //授权选择框 + @TaskInput({ + title: "易盾授权", + helper: "易盾CDN授权", + component: { + name: "access-selector", + type: "yidun", + }, + required: true, + }) + accessId!: string; + + async onInstance() {} + async execute(): Promise { + const { domain, certId, cert } = this; + if (!domain && !certId) { + throw new Error("证书ID和网站域名必须填写一个"); + } + + if (certId > 0) { + await this.updateByCertId(cert, certId); + } else { + await this.updateByDomain(cert); + } + } + + private async updateByCertId(cert: CertInfo, certId: number) { + this.logger.info(`更新证书,证书ID:${certId}`); + const url = `http://user.yiduncdn.com/v1/certs/${certId}`; + await this.doRequest(url, "PUT", { + cert: cert.crt, + key: cert.key, + }); + } + + async doRequest(url: string, method: string, data: any) { + const access = await this.getAccess(this.accessId); + const { apiKey, apiSecret } = access; + const http = this.ctx.http; + const res: any = await http.request({ + url, + method, + headers: { + "api-key": apiKey, + "api-secret": apiSecret, + }, + data, + }); + if (res.code != 0) { + throw new Error(res.msg); + } + return res; + } + + private async updateByDomain(cert: CertInfo) { + //查询站点 + const siteUrl = "http://user.yiduncdn.com/v1/sites"; + const res = await this.doRequest(siteUrl, "GET", { domain: this.domain }); + if (res.data.length === 0) { + throw new Error(`未找到域名相关站点:${this.domain}`); + } + let site = null; + for (const row of res.data) { + if (row.domain === this.domain) { + site = row; + } + } + if (!site) { + throw new Error(`未找到域名匹配的站点:${this.domain}`); + } + if (site.https_listen?.cert) { + //有证书id + const certId = site.https_listen.cert; + await this.updateByCertId(cert, certId); + } else { + //创建证书 + this.logger.info(`创建证书,域名:${this.domain}`); + const certUrl = `http://user.yiduncdn.com/v1/certs`; + const name = this.domain + "_" + new Date().getTime(); + await this.doRequest(certUrl, "POST", { + name, + type: "custom", + cert: cert.crt, + key: cert.key, + }); + + const certs: any = await this.doRequest(certUrl, "GET", { + name, + }); + const certId = certs.data[0].id; + + const siteUrl = "http://user.yiduncdn.com/v1/sites"; + await this.doRequest(siteUrl, "PUT", { id: site.id, https_listen: { cert: certId } }); + } + } +} +new YidunDeployToCDNPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/plugin-deploy-to-rcdn.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/plugin-deploy-to-rcdn.ts new file mode 100644 index 000000000..1ec70d301 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yidun/plugins/plugin-deploy-to-rcdn.ts @@ -0,0 +1,168 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { YidunRcdnAccess } from "../access-rcdn.js"; + +@IsTaskPlugin({ + name: "YidunDeployToRCDN", + title: "易盾-部署到易盾RCDN", + icon: "material-symbols:shield-outline", + group: pluginGroups.cdn.key, + desc: "易盾CDN,每月免费30G,[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8)", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + needPlus: false, +}) +export class YidunDeployToRCDNPlugin extends AbstractTaskPlugin { + //证书选择,此项必须要有 + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames], + }, + required: true, + }) + cert!: CertInfo; + + //授权选择框 + @TaskInput({ + title: "易盾RCDN授权", + helper: "易盾RCDN授权", + component: { + name: "access-selector", + type: "yidunrcdn", + }, + required: true, + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "域名列表", + helper: "选择要部署证书的站点域名", + typeName: "YidunDeployToRCDNPlugin", + action: YidunDeployToRCDNPlugin.prototype.onGetDomainList.name, + }) + ) + domains!: string[]; + + async onInstance() {} + async execute(): Promise { + const access = await this.getAccess(this.accessId); + const loginRes = await this.getLoginToken(access); + + const curl = "https://rhcdn.yiduncdn.com/CdnDomainHttps/httpsConfiguration"; + for (const domain of this.domains) { + // const data = { + // doMainId: domain, + // https: { + // https_status: "off" + // }, + // } + // //先关闭https + // const res = await this.doRequest(curl, loginRes, data); + + const cert = this.cert; + const update = { + doMainId: domain, + https: { + https_status: "on", + certificate_name: this.appendTimeSuffix("certd"), + certificate_source: "0", + certificate_value: cert.crt, + private_key: cert.key, + }, + }; + await this.doRequest(curl, loginRes, update); + this.logger.info(`站点${domain}证书更新成功`); + } + } + + async getLoginToken(access: YidunRcdnAccess) { + const url = "https://rhcdn.yiduncdn.com/login/loginUser"; + const data = { + userAccount: access.username, + userPwd: access.password, + remember: true, + }; + const http = this.ctx.http; + const res: any = await http.request({ + url, + method: "POST", + data, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + returnOriginRes: true, + }); + if (!res.data?.success) { + throw new Error(res.data?.message); + } + + const jsessionId = this.ctx.utils.request.getCookie(res, "JSESSIONID"); + const token = res.data?.data; + return { + jsessionId, + token, + }; + } + + async getDomainList(loginRes: any) { + const url = "https://rhcdn.yiduncdn.com/CdnDomain/queryForDatatables"; + const data = { + draw: 1, + start: 0, + length: 1000, + search: { + value: "", + regex: false, + }, + }; + + const res = await this.doRequest(url, loginRes, data); + return res.data?.data; + } + + private async doRequest(url: string, loginRes: any, data: any) { + const http = this.ctx.http; + const res: any = await http.request({ + url, + method: "POST", + headers: { + Cookie: `JSESSIONID=${loginRes.jsessionId};kuocai_cdn_token=${loginRes.token}`, + }, + data, + }); + if (!res.success) { + throw new Error(res.message); + } + return res; + } + + async onGetDomainList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + const access = await this.getAccess(this.accessId); + + const loginRes = await this.getLoginToken(access); + + const list = await this.getDomainList(loginRes); + + if (!list || list.length === 0) { + throw new Error("您账户下还没有站点域名,请先添加域名"); + } + return list.map((item: any) => { + return { + label: `${item.domainName}<${item.id}>`, + value: item.id, + }; + }); + } +} +new YidunDeployToRCDNPlugin(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yizhifu/access.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yizhifu/access.ts new file mode 100644 index 000000000..d6a6c1e56 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yizhifu/access.ts @@ -0,0 +1,70 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +@IsAccess({ + name: "yizhifu", + title: "易支付", + icon: "svg:icon-yizhifu", +}) +export class YizhifuAccess extends BaseAccess { + @AccessInput({ + title: "url", + component: { + placeholder: "https://pay.xxxx.com", + }, + helper: "易支付系统地址", + required: true, + encrypt: false, + }) + url: string; + @AccessInput({ + title: "商户id", + component: { + placeholder: "pid", + }, + required: true, + encrypt: false, + }) + pid: string; + @AccessInput({ + title: "key", + component: { + placeholder: "key", + }, + required: true, + encrypt: true, + }) + key: string; + + @AccessInput({ + title: "固定支付方式", + component: { + placeholder: "固定一种支付方式,也就是submit.php中的type参数", + }, + helper: "不填则跳转到收银台由用户自己选择,如果您的易支付系统不支持收银台,则必须填写", + required: false, + encrypt: false, + }) + payType: string; + + @AccessInput({ + title: "签名方式", + component: { + name: "a-select", + vModel: "value", + options: [ + { + label: "MD5", + value: "MD5", + }, + { + label: "SHA256", + value: "SHA256", + }, + ], + }, + required: true, + encrypt: false, + }) + signType: string; +} + +new YizhifuAccess(); diff --git a/packages/ui/certd-server/src/plugins/plugin-plus/yizhifu/index.ts b/packages/ui/certd-server/src/plugins/plugin-plus/yizhifu/index.ts new file mode 100644 index 000000000..655094d02 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-plus/yizhifu/index.ts @@ -0,0 +1 @@ +export * from "./access.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-proxmox/plugins/plugin-upload.ts b/packages/ui/certd-server/src/plugins/plugin-proxmox/plugins/plugin-upload.ts index 8e8ee1487..da44a96f2 100644 --- a/packages/ui/certd-server/src/plugins/plugin-proxmox/plugins/plugin-upload.ts +++ b/packages/ui/certd-server/src/plugins/plugin-proxmox/plugins/plugin-upload.ts @@ -1,6 +1,6 @@ import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import { CertInfo } from '@certd/plugin-cert'; -import { AbstractPlusTaskPlugin } from '@certd/plugin-plus'; +import { AbstractPlusTaskPlugin } from '@certd/plugin-lib'; import { ProxmoxAccess } from '../access.js'; import { createRemoteSelectInputDefine } from '@certd/plugin-lib'; import { CertApplyPluginNames} from '@certd/plugin-cert'; diff --git a/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/deploy-to-cdn/index.ts index 5516c2a99..e1dd8dbe8 100644 --- a/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/deploy-to-cdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/deploy-to-cdn/index.ts @@ -1,8 +1,11 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; -import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine, QiniuAccess, QiniuClient } from '@certd/plugin-lib'; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; import { CertInfo } from '@certd/plugin-cert'; import { optionsUtils } from '@certd/basic'; import { CertApplyPluginNames} from '@certd/plugin-cert'; +import { QiniuAccess } from '../../../plugin-lib/qiniu/access.js'; +import { QiniuClient } from '../../../plugin-lib/qiniu/index.js'; + @IsTaskPlugin({ name: 'QiniuDeployCertToCDN', title: '七牛云-部署证书至CDN/DCDN', diff --git a/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/index.ts b/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/index.ts index b1ada7856..dff1615ca 100644 --- a/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/index.ts @@ -1,2 +1,3 @@ export * from './deploy-to-cdn/index.js'; export * from './upload-cert/index.js'; +export * from './plugin-deploy-to-oss.js'; \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/plugin-deploy-to-oss.ts b/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/plugin-deploy-to-oss.ts new file mode 100644 index 000000000..60e491726 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/plugin-deploy-to-oss.ts @@ -0,0 +1,84 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { QiniuAccess, QiniuClient } from "../../plugin-lib/qiniu/index.js"; + +@IsTaskPlugin({ + name: "QiniuDeployCertToOSS", + title: "七牛云-部署证书至OSS", + icon: "svg:icon-qiniuyun", + group: pluginGroups.qiniu.key, + desc: "自动部署域名证书至七牛云KODO,注意是自定义源站域名,不是CDN域名", + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class QiniuDeployCertToOSS extends AbstractTaskPlugin { + @TaskInput({ + title: "自定义源站域名", + helper: "你在七牛云上配置的OSS域名,比如:certd.handsfree.work", + required: true, + }) + domainName!: string; + + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书,或者上传到七牛云的证书id", + component: { + name: "output-selector", + from: [...CertApplyPluginNames, "QiniuCertUpload"], + }, + required: true, + }) + cert!: CertInfo | string; + + @TaskInput({ + title: "Access授权", + helper: "七牛云授权", + component: { + name: "access-selector", + type: "qiniu", + }, + required: true, + }) + accessId!: string; + + async onInstance() {} + async execute(): Promise { + this.logger.info("开始部署证书到七牛云oss"); + const access = await this.getAccess(this.accessId); + const qiniuClient = new QiniuClient({ + http: this.ctx.http, + access, + logger: this.logger, + }); + + let certId = null; + if (typeof this.cert !== "string") { + // 是证书id,直接上传即可 + this.logger.info("先上传证书"); + certId = await qiniuClient.uploadCert(this.cert, this.appendTimeSuffix("certd")); + } else { + certId = this.cert; + } + + // const url1 = `https://uc.qiniuapi.com/v2/domains?tbl=handfree`; + // const ossDomains = await qiniuClient.doRequestV2({ + // url: url1, + // method: "get", + // body: null, + // contentType: "application/x-www-form-urlencoded", + // }); + // this.logger.info("ossDomains:", ossDomains); + // + // const res = await qiniuClient.getCertBindings(); + // this.logger.info(res); + + this.logger.info(`开始修改证书,certId:${certId},domain:${this.domainName}`); + await qiniuClient.bindCert({ certid: certId, domain: this.domainName }); + + this.logger.info("部署完成"); + } +} +new QiniuDeployCertToOSS(); diff --git a/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/upload-cert/index.ts b/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/upload-cert/index.ts index 349461a95..0f7fe456f 100644 --- a/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/upload-cert/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-qiniu/plugin/upload-cert/index.ts @@ -1,6 +1,7 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline'; import { CertInfo } from '@certd/plugin-cert'; -import { QiniuAccess, QiniuClient } from '@certd/plugin-lib'; +import { QiniuAccess } from '../../../plugin-lib/qiniu/access.js'; +import { QiniuClient } from '../../../plugin-lib/qiniu/index.js'; import { CertApplyPluginNames} from '@certd/plugin-cert'; @IsTaskPlugin({ name: 'QiniuCertUpload', diff --git a/packages/ui/certd-server/src/plugins/plugin-qnap/plugins/plugin-qnap.ts b/packages/ui/certd-server/src/plugins/plugin-qnap/plugins/plugin-qnap.ts index 42d6e87ea..6d65d6e14 100644 --- a/packages/ui/certd-server/src/plugins/plugin-qnap/plugins/plugin-qnap.ts +++ b/packages/ui/certd-server/src/plugins/plugin-qnap/plugins/plugin-qnap.ts @@ -1,9 +1,9 @@ import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import { CertInfo } from '@certd/plugin-cert'; -import { AbstractPlusTaskPlugin } from '@certd/plugin-plus'; +import { AbstractPlusTaskPlugin } from '@certd/plugin-lib'; import { tmpdir } from 'node:os'; import fs from 'fs'; -import { SshAccess, SshClient } from '@certd/plugin-lib'; +import { SshAccess, SshClient } from '../../plugin-lib/ssh/index.js'; import { CertApplyPluginNames} from '@certd/plugin-cert'; @IsTaskPlugin({ name: 'QnapDeploy', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/tencent-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/tencent-dns-provider.ts index 9be95d220..5018d03f8 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/tencent-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/tencent-dns-provider.ts @@ -1,5 +1,5 @@ import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert'; -import { TencentAccess } from '@certd/plugin-lib'; +import { TencentAccess } from '../../plugin-lib/tencent/index.js'; @IsDnsProvider({ name: 'tencent', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/teo-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/teo-dns-provider.ts index 5554f52a4..39c3b734e 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/teo-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/teo-dns-provider.ts @@ -1,5 +1,5 @@ import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert'; -import { TencentAccess } from '@certd/plugin-lib'; +import { TencentAccess } from '../../plugin-lib/tencent/access.js'; @IsDnsProvider({ name: 'tencent-eo', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts index f414c5229..bf6b83a99 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts @@ -1,8 +1,8 @@ import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; -import { AbstractPlusTaskPlugin } from '@certd/plugin-plus'; +import { AbstractPlusTaskPlugin } from '@certd/plugin-lib'; import dayjs from 'dayjs'; import { remove } from 'lodash-es'; -import { TencentAccess, TencentSslClient } from '@certd/plugin-lib'; +import { TencentAccess, TencentSslClient } from '../../../plugin-lib/tencent/index.js'; @IsTaskPlugin({ name: 'TencentDeleteExpiringCert', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-all/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-all/index.ts index 4ef96c2f8..f4d13c931 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-all/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-all/index.ts @@ -1,6 +1,6 @@ import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline'; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; -import { TencentSslClient } from "@certd/plugin-lib"; +import { TencentSslClient } from '../../../plugin-lib/tencent/index.js'; @IsTaskPlugin({ name: 'DeployCertToTencentAll', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn-v2/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn-v2/index.ts index ab96b5ca8..b32904c99 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn-v2/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn-v2/index.ts @@ -1,7 +1,7 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import { CertInfo ,CertReader} from '@certd/plugin-cert'; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; -import { TencentAccess, TencentSslClient } from '@certd/plugin-lib'; +import { TencentAccess, TencentSslClient } from '../../../plugin-lib/tencent/index.js'; import { CertApplyPluginNames} from '@certd/plugin-cert'; @IsTaskPlugin({ name: 'TencentDeployCertToCDNv2', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts index 6a5b156b7..33d5b8c9e 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts @@ -1,6 +1,6 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import { CertInfo } from '@certd/plugin-cert'; -import { TencentAccess } from '@certd/plugin-lib'; +import { TencentAccess } from '../../../plugin-lib/tencent/index.js'; import { CertApplyPluginNames} from '@certd/plugin-cert'; @IsTaskPlugin({ name: 'DeployCertToTencentCDN', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-clb/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-clb/index.ts index b53acf4e3..ce337f470 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-clb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-clb/index.ts @@ -1,6 +1,6 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import dayjs from 'dayjs'; -import { TencentAccess } from '@certd/plugin-lib'; +import { TencentAccess } from '../../../plugin-lib/tencent/index.js'; import { CertApplyPluginNames} from '@certd/plugin-cert'; @IsTaskPlugin({ name: 'DeployCertToTencentCLB', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts index 0515ade6f..7f8439519 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts @@ -1,6 +1,7 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import { CertInfo } from '@certd/plugin-cert'; -import { createRemoteSelectInputDefine, TencentSslClient } from '@certd/plugin-lib'; +import { createRemoteSelectInputDefine } from '@certd/plugin-lib'; +import { TencentSslClient } from '../../../plugin-lib/tencent/index.js'; import { CertApplyPluginNames} from '@certd/plugin-cert'; @IsTaskPlugin({ name: 'DeployCertToTencentCosPlugin', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts index 6265b889d..25dfc3d1f 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts @@ -2,10 +2,11 @@ import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine, - TencentAccess, - TencentSslClient } from "@certd/plugin-lib"; + import {CertApplyPluginNames, CertInfo, CertReader} from "@certd/plugin-cert"; +import { TencentAccess } from "../../../plugin-lib/tencent/access.js"; +import { TencentSslClient } from "../../../plugin-lib/tencent/index.js"; @IsTaskPlugin({ name: 'DeployCertToTencentEO', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-live/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-live/index.ts index 0bd4a63c7..0dc81142b 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-live/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-live/index.ts @@ -1,6 +1,8 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; -import { createRemoteSelectInputDefine, TencentAccess, TencentSslClient } from "@certd/plugin-lib"; +import { createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { TencentAccess } from "../../../plugin-lib/tencent/access.js"; +import { TencentSslClient } from "../../../plugin-lib/tencent/index.js"; @IsTaskPlugin({ name: 'TencentDeployCertToLive', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts index eee016781..d18fa557c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts @@ -9,7 +9,8 @@ import { } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; -import { TencentAccess, TencentSslClient } from "@certd/plugin-lib"; +import { TencentAccess } from "../../../plugin-lib/tencent/access.js"; +import { TencentSslClient } from "../../../plugin-lib/tencent/index.js"; import { omit } from "lodash-es"; @IsTaskPlugin({ //命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名 diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/start-instances/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/start-instances/index.ts index 5487a2161..1a90e6b8c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/start-instances/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/start-instances/index.ts @@ -1,5 +1,6 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; -import { createRemoteSelectInputDefine, TencentAccess } from "@certd/plugin-lib"; +import { createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { TencentAccess } from "../../../plugin-lib/tencent/access.js"; @IsTaskPlugin({ name: 'TencentActionInstancesPlugin', diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts index 2b07abb78..530f0c720 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts @@ -1,6 +1,7 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline"; import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert"; -import { TencentAccess, TencentSslClient } from "@certd/plugin-lib"; +import { TencentAccess } from "../../../plugin-lib/tencent/access.js"; +import { TencentSslClient } from "../../../plugin-lib/tencent/index.js"; @IsTaskPlugin({ name: 'UploadCertToTencent', diff --git a/packages/ui/certd-server/src/plugins/plugin-xinnet/access.ts b/packages/ui/certd-server/src/plugins/plugin-xinnet/access.ts index c1cad94fc..f366474e2 100644 --- a/packages/ui/certd-server/src/plugins/plugin-xinnet/access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-xinnet/access.ts @@ -1,5 +1,5 @@ import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline"; -import { XinnetClient } from "@certd/plugin-plus"; +import { XinnetClient } from "../plugin-plus/xinnet/client.js"; /** * 这个注解将注册一个授权配置 diff --git a/packages/ui/certd-server/src/plugins/plugin-xinnet/dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-xinnet/dns-provider.ts index f3a57e030..0c9c27800 100644 --- a/packages/ui/certd-server/src/plugins/plugin-xinnet/dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-xinnet/dns-provider.ts @@ -1,6 +1,6 @@ import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert"; import { XinnetAccess } from "./access.js"; -import { XinnetClient } from "@certd/plugin-plus"; +import { XinnetClient } from "../plugin-plus/xinnet/client.js"; export type XinnetRecord = { recordId: number; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46968b757..b78f131e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: packages/core/acme-client: dependencies: '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../basic '@peculiar/x509': specifier: ^1.11.0 @@ -210,11 +210,11 @@ importers: packages/core/pipeline: dependencies: '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../basic '@certd/plus-core': - specifier: ^1.37.16 - version: 1.37.16 + specifier: ^1.37.17 + version: link:../../pro/plus-core dayjs: specifier: ^1.11.7 version: 1.11.13 @@ -409,7 +409,7 @@ importers: packages/libs/lib-k8s: dependencies: '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/basic '@kubernetes/client-node': specifier: 0.21.0 @@ -449,20 +449,20 @@ importers: packages/libs/lib-server: dependencies: '@certd/acme-client': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../plugins/plugin-lib '@certd/plus-core': - specifier: ^1.37.16 - version: 1.37.16 + specifier: ^1.37.17 + version: link:../../pro/plus-core '@midwayjs/cache': specifier: 3.14.0 version: 3.14.0 @@ -607,38 +607,23 @@ importers: packages/plugins/plugin-cert: dependencies: '@certd/acme-client': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../plugin-lib - '@google-cloud/publicca': - specifier: ^1.3.0 - version: 1.3.0(encoding@0.1.13) - dayjs: - specifier: ^1.11.7 - version: 1.11.13 - jszip: - specifier: ^3.10.1 - version: 3.10.1 - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 psl: specifier: ^1.9.0 version: 1.15.0 punycode.js: specifier: ^2.3.1 version: 2.3.1 - rimraf: - specifier: ^5.0.5 - version: 5.0.10 devDependencies: '@types/chai': specifier: ^4.3.3 @@ -694,12 +679,18 @@ importers: '@aws-sdk/client-s3': specifier: ^3.787.0 version: 3.810.0(aws-crt@1.26.2) + '@certd/acme-client': + specifier: ^1.37.17 + version: link:../../core/acme-client '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/pipeline + '@certd/plus-core': + specifier: ^1.37.17 + version: link:../../pro/plus-core '@kubernetes/client-node': specifier: 0.21.0 version: 0.21.0 @@ -721,6 +712,12 @@ importers: lodash-es: specifier: ^4.17.21 version: 4.17.21 + psl: + specifier: ^1.15.0 + version: 1.15.0 + punycode.js: + specifier: ^2.3.1 + version: 2.3.1 qiniu: specifier: ^7.12.0 version: 7.14.0 @@ -783,19 +780,19 @@ importers: packages/pro/commercial-core: dependencies: '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/basic '@certd/lib-server': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../libs/lib-server '@certd/pipeline': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/pipeline '@certd/plugin-plus': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../plugin-plus '@certd/plus-core': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../plus-core '@midwayjs/core': specifier: 3.20.11 @@ -880,22 +877,22 @@ importers: specifier: ^1.0.2 version: 1.0.3 '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/basic '@certd/lib-k8s': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../libs/lib-k8s '@certd/pipeline': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../plugins/plugin-lib '@certd/plus-core': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../plus-core ali-oss: specifier: ^6.21.0 @@ -998,7 +995,7 @@ importers: packages/pro/plus-core: dependencies: '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/basic dayjs: specifier: ^1.11.7 @@ -1230,7 +1227,7 @@ importers: specifier: ^10.1.5 version: 10.1.6(postcss@8.5.6) psl: - specifier: ^1.9.0 + specifier: ^1.15.0 version: 1.15.0 qiniu-js: specifier: ^3.4.2 @@ -1294,10 +1291,10 @@ importers: version: 0.1.3(zod@3.24.4) devDependencies: '@certd/lib-iframe': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../libs/lib-iframe '@certd/pipeline': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/pipeline '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -1458,6 +1455,9 @@ importers: '@alicloud/openapi-client': specifier: ^0.4.12 version: 0.4.14 + '@alicloud/openapi-util': + specifier: ^0.3.2 + version: 0.3.2 '@alicloud/pop-core': specifier: ^1.7.10 version: 1.8.0 @@ -1483,47 +1483,50 @@ importers: specifier: ^3.705.0 version: 3.810.0(aws-crt@1.26.2) '@certd/acme-client': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/basic '@certd/commercial-core': - specifier: ^1.37.16 - version: 1.37.16(better-sqlite3@11.10.0)(encoding@0.1.13)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3)) + specifier: ^1.37.17 + version: link:../../pro/commercial-core '@certd/cv4pve-api-javascript': specifier: ^8.4.2 version: 8.4.2 '@certd/jdcloud': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../libs/lib-jdcloud '@certd/lib-huawei': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../libs/lib-huawei '@certd/lib-k8s': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../libs/lib-k8s '@certd/lib-server': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../libs/lib-server '@certd/midway-flyway-js': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../libs/midway-flyway-js '@certd/pipeline': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.37.16 + specifier: ^1.37.17 version: link:../../plugins/plugin-lib '@certd/plugin-plus': - specifier: ^1.37.16 - version: 1.37.16(encoding@0.1.13) + specifier: ^1.37.17 + version: link:../../pro/plugin-plus '@certd/plus-core': - specifier: ^1.37.16 - version: 1.37.16 + specifier: ^1.37.17 + version: link:../../pro/plus-core + '@google-cloud/publicca': + specifier: ^1.3.0 + version: 1.3.0(encoding@0.1.13) '@huaweicloud/huaweicloud-sdk-cdn': specifier: ^3.1.120 version: 3.1.149 @@ -1632,6 +1635,9 @@ importers: jsonwebtoken: specifier: ^9.0.0 version: 9.0.2 + jsrsasign: + specifier: ^11.1.0 + version: 11.1.0 jszip: specifier: ^3.10.1 version: 3.10.1 @@ -1678,7 +1684,7 @@ importers: specifier: ^8.12.0 version: 8.16.0 psl: - specifier: ^1.9.0 + specifier: ^1.15.0 version: 1.15.0 punycode.js: specifier: ^2.3.1 @@ -1707,6 +1713,9 @@ importers: socks-proxy-agent: specifier: ^8.0.4 version: 8.0.5 + ssh2: + specifier: ^1.17.0 + version: 1.17.0 strip-ansi: specifier: ^7.1.0 version: 7.1.0 @@ -1753,9 +1762,6 @@ importers: '@types/nodemailer': specifier: ^6.4.8 version: 6.4.17 - '@types/ssh2': - specifier: ^1.15.0 - version: 1.15.5 c8: specifier: ^10.1.2 version: 10.1.3 @@ -2879,18 +2885,9 @@ packages: '@better-scroll/zoom@2.5.1': resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==} - '@certd/commercial-core@1.37.16': - resolution: {integrity: sha512-JH6wlx88ljh2m2QiTJ1dvI/Up3jjOTEQqD/x3cfVmXHYfT//QFIL/O6cRIFKLWxN5Q+0k4YHmCDw/DI+WWdwlQ==} - '@certd/cv4pve-api-javascript@8.4.2': resolution: {integrity: sha512-udGce7ewrVl4DmZvX+17PjsnqsdDIHEDatr8QP0AVrY2p+8JkaSPW4mXCKiLGf82C9K2+GXgT+qNIqgW7tfF9Q==} - '@certd/plugin-plus@1.37.16': - resolution: {integrity: sha512-lzxyHyq9K+sDDFlPICs/0/W1nODvxTwk7N+qI1gskTbNpPt5CY1b47sPhwLb6oRTeW2/8iVdmt283GECFEAXXg==} - - '@certd/plus-core@1.37.16': - resolution: {integrity: sha512-PAyDMlLy/r5kx03A6pH4ICUAdPR9WZroGOx/ivJJo0Auk07uHU/kyXMfSCB6LytUHH0tOy36K+zuTCTdGr2NOA==} - '@certd/vue-js-cron-core@6.0.3': resolution: {integrity: sha512-kqzoAMhYz9j6FGNWEODRYtt4NpUEUwjpkU89z5WVg2tCtOcI5VhwyUGOd8AxiBCRfd6PtXvzuqw85PaOps9wrQ==} @@ -5165,9 +5162,6 @@ packages: '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - '@types/ssh2@1.15.5': - resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} - '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -15446,84 +15440,12 @@ snapshots: dependencies: '@better-scroll/core': 2.5.1 - '@certd/commercial-core@1.37.16(better-sqlite3@11.10.0)(encoding@0.1.13)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3))': - dependencies: - '@certd/basic': link:packages/core/basic - '@certd/lib-server': link:packages/libs/lib-server - '@certd/pipeline': link:packages/core/pipeline - '@certd/plugin-plus': 1.37.16(encoding@0.1.13) - '@certd/plus-core': 1.37.16 - '@midwayjs/core': 3.20.11 - '@midwayjs/koa': 3.20.13 - '@midwayjs/logger': 3.4.2 - '@midwayjs/typeorm': 3.20.11 - alipay-sdk: 4.14.0 - dayjs: 1.11.13 - typeorm: 0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3)) - wechatpay-node-v3: 2.2.1 - transitivePeerDependencies: - - '@google-cloud/spanner' - - '@sap/hana-client' - - babel-plugin-macros - - better-sqlite3 - - encoding - - hdb-pool - - ioredis - - mongodb - - mssql - - mysql2 - - oracledb - - pg - - pg-native - - pg-query-stream - - proxy-agent - - redis - - reflect-metadata - - sql.js - - sqlite3 - - supports-color - - ts-node - - typeorm-aurora-data-api-driver - '@certd/cv4pve-api-javascript@8.4.2': dependencies: debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@certd/plugin-plus@1.37.16(encoding@0.1.13)': - dependencies: - '@alicloud/pop-core': 1.8.0 - '@baiducloud/sdk': 1.0.3 - '@certd/basic': link:packages/core/basic - '@certd/lib-k8s': link:packages/libs/lib-k8s - '@certd/pipeline': link:packages/core/pipeline - '@certd/plugin-cert': link:packages/plugins/plugin-cert - '@certd/plugin-lib': link:packages/plugins/plugin-lib - '@certd/plus-core': 1.37.16 - ali-oss: 6.23.0 - baidu-aip-sdk: 4.16.16 - basic-ftp: 5.0.5 - cos-nodejs-sdk-v5: 2.14.7 - crypto-js: 4.2.0 - dayjs: 1.11.13 - form-data: 4.0.2 - https-proxy-agent: 7.0.6 - js-yaml: 4.1.0 - jsencrypt: 3.3.2 - jsrsasign: 11.1.0 - qiniu: 7.14.0 - tencentcloud-sdk-nodejs: 4.1.112(encoding@0.1.13) - transitivePeerDependencies: - - encoding - - proxy-agent - - supports-color - - '@certd/plus-core@1.37.16': - dependencies: - '@certd/basic': link:packages/core/basic - dayjs: 1.11.13 - '@certd/vue-js-cron-core@6.0.3': dependencies: mustache: 4.2.0 @@ -18425,10 +18347,6 @@ snapshots: '@types/node': 20.17.47 '@types/send': 0.17.4 - '@types/ssh2@1.15.5': - dependencies: - '@types/node': 18.19.100 - '@types/stack-utils@2.0.3': {} '@types/superagent@4.1.14':