diff --git a/internal/certdeploy/deployers/sp_aliyun_apigw.go b/internal/certdeploy/deployers/sp_aliyun_apigw.go index d7fd88816..f457361dc 100644 --- a/internal/certdeploy/deployers/sp_aliyun_apigw.go +++ b/internal/certdeploy/deployers/sp_aliyun_apigw.go @@ -17,14 +17,15 @@ func init() { } provider, err := aliyunapigw.NewSSLDeployerProvider(&aliyunapigw.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - ServiceType: xmaps.GetString(options.ProviderExtendedConfig, "serviceType"), - GatewayId: xmaps.GetString(options.ProviderExtendedConfig, "gatewayId"), - GroupId: xmaps.GetString(options.ProviderExtendedConfig, "groupId"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + ServiceType: xmaps.GetString(options.ProviderExtendedConfig, "serviceType"), + GatewayId: xmaps.GetString(options.ProviderExtendedConfig, "gatewayId"), + GroupId: xmaps.GetString(options.ProviderExtendedConfig, "groupId"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_aliyun_cdn.go b/internal/certdeploy/deployers/sp_aliyun_cdn.go index 82631c2d2..a24bad5be 100644 --- a/internal/certdeploy/deployers/sp_aliyun_cdn.go +++ b/internal/certdeploy/deployers/sp_aliyun_cdn.go @@ -17,11 +17,12 @@ func init() { } provider, err := aliyuncdn.NewSSLDeployerProvider(&aliyuncdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_aliyun_dcdn.go b/internal/certdeploy/deployers/sp_aliyun_dcdn.go index 03556df6c..6855d337b 100644 --- a/internal/certdeploy/deployers/sp_aliyun_dcdn.go +++ b/internal/certdeploy/deployers/sp_aliyun_dcdn.go @@ -17,11 +17,12 @@ func init() { } provider, err := aliyundcdn.NewSSLDeployerProvider(&aliyundcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_aliyun_ddospro.go b/internal/certdeploy/deployers/sp_aliyun_ddospro.go index 906cafece..7271794e6 100644 --- a/internal/certdeploy/deployers/sp_aliyun_ddospro.go +++ b/internal/certdeploy/deployers/sp_aliyun_ddospro.go @@ -17,11 +17,12 @@ func init() { } provider, err := aliyunddospro.NewSSLDeployerProvider(&aliyunddospro.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_aliyun_fc.go b/internal/certdeploy/deployers/sp_aliyun_fc.go index 76e2d5d16..017e2252b 100644 --- a/internal/certdeploy/deployers/sp_aliyun_fc.go +++ b/internal/certdeploy/deployers/sp_aliyun_fc.go @@ -17,12 +17,13 @@ func init() { } provider, err := aliyunfc.NewSSLDeployerProvider(&aliyunfc.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - ServiceVersion: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + ServiceVersion: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_aliyun_live.go b/internal/certdeploy/deployers/sp_aliyun_live.go index 76a456078..0fd78d88a 100644 --- a/internal/certdeploy/deployers/sp_aliyun_live.go +++ b/internal/certdeploy/deployers/sp_aliyun_live.go @@ -17,10 +17,11 @@ func init() { } provider, err := aliyunlive.NewSSLDeployerProvider(&aliyunlive.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_aliyun_vod.go b/internal/certdeploy/deployers/sp_aliyun_vod.go index 3c59db40a..f7e23133e 100644 --- a/internal/certdeploy/deployers/sp_aliyun_vod.go +++ b/internal/certdeploy/deployers/sp_aliyun_vod.go @@ -17,11 +17,12 @@ func init() { } provider, err := aliyunvod.NewSSLDeployerProvider(&aliyunvod.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_baiducloud_cdn.go b/internal/certdeploy/deployers/sp_baiducloud_cdn.go index a32670a58..aa7d02c7e 100644 --- a/internal/certdeploy/deployers/sp_baiducloud_cdn.go +++ b/internal/certdeploy/deployers/sp_baiducloud_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := baiducloudcdn.NewSSLDeployerProvider(&baiducloudcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - SecretAccessKey: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_baishan_cdn.go b/internal/certdeploy/deployers/sp_baishan_cdn.go index 693a8458f..d458db203 100644 --- a/internal/certdeploy/deployers/sp_baishan_cdn.go +++ b/internal/certdeploy/deployers/sp_baishan_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := baishancdn.NewSSLDeployerProvider(&baishancdn.SSLDeployerProviderConfig{ - ApiToken: credentials.ApiToken, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + ApiToken: credentials.ApiToken, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_byteplus_cdn.go b/internal/certdeploy/deployers/sp_byteplus_cdn.go index d3c814e2f..13d491a60 100644 --- a/internal/certdeploy/deployers/sp_byteplus_cdn.go +++ b/internal/certdeploy/deployers/sp_byteplus_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := bytepluscdn.NewSSLDeployerProvider(&bytepluscdn.SSLDeployerProviderConfig{ - AccessKey: credentials.AccessKey, - SecretKey: credentials.SecretKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKey: credentials.AccessKey, + SecretKey: credentials.SecretKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_ctcccloud_ao.go b/internal/certdeploy/deployers/sp_ctcccloud_ao.go index f28dcbce4..01f6658db 100644 --- a/internal/certdeploy/deployers/sp_ctcccloud_ao.go +++ b/internal/certdeploy/deployers/sp_ctcccloud_ao.go @@ -17,9 +17,10 @@ func init() { } provider, err := ctcccloudao.NewSSLDeployerProvider(&ctcccloudao.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - SecretAccessKey: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_ctcccloud_cdn.go b/internal/certdeploy/deployers/sp_ctcccloud_cdn.go index 72d44d392..5d3b7349a 100644 --- a/internal/certdeploy/deployers/sp_ctcccloud_cdn.go +++ b/internal/certdeploy/deployers/sp_ctcccloud_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := ctcccloudcdn.NewSSLDeployerProvider(&ctcccloudcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - SecretAccessKey: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_ctcccloud_icdn.go b/internal/certdeploy/deployers/sp_ctcccloud_icdn.go index bce31b39d..44b160488 100644 --- a/internal/certdeploy/deployers/sp_ctcccloud_icdn.go +++ b/internal/certdeploy/deployers/sp_ctcccloud_icdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := ctcccloudicdn.NewSSLDeployerProvider(&ctcccloudicdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - SecretAccessKey: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_ctcccloud_lvdn.go b/internal/certdeploy/deployers/sp_ctcccloud_lvdn.go index 18a088344..b5e720c66 100644 --- a/internal/certdeploy/deployers/sp_ctcccloud_lvdn.go +++ b/internal/certdeploy/deployers/sp_ctcccloud_lvdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := ctcccloudlvdn.NewSSLDeployerProvider(&ctcccloudlvdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - SecretAccessKey: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_dogecloud_cdn.go b/internal/certdeploy/deployers/sp_dogecloud_cdn.go index 89dfa5ce4..eb39415f8 100644 --- a/internal/certdeploy/deployers/sp_dogecloud_cdn.go +++ b/internal/certdeploy/deployers/sp_dogecloud_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := pDogeCDN.NewSSLDeployerProvider(&pDogeCDN.SSLDeployerProviderConfig{ - AccessKey: credentials.AccessKey, - SecretKey: credentials.SecretKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKey: credentials.AccessKey, + SecretKey: credentials.SecretKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_huaweicloud_cdn.go b/internal/certdeploy/deployers/sp_huaweicloud_cdn.go index 439325d30..349c8907f 100644 --- a/internal/certdeploy/deployers/sp_huaweicloud_cdn.go +++ b/internal/certdeploy/deployers/sp_huaweicloud_cdn.go @@ -21,6 +21,7 @@ func init() { SecretAccessKey: credentials.SecretAccessKey, EnterpriseProjectId: credentials.EnterpriseProjectId, Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/certdeploy/deployers/sp_jdcloud_cdn.go b/internal/certdeploy/deployers/sp_jdcloud_cdn.go index 871196b6b..97dd9c370 100644 --- a/internal/certdeploy/deployers/sp_jdcloud_cdn.go +++ b/internal/certdeploy/deployers/sp_jdcloud_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := jdcloudcdn.NewSSLDeployerProvider(&jdcloudcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_jdcloud_live.go b/internal/certdeploy/deployers/sp_jdcloud_live.go index 141f26fcd..7cb5df754 100644 --- a/internal/certdeploy/deployers/sp_jdcloud_live.go +++ b/internal/certdeploy/deployers/sp_jdcloud_live.go @@ -17,9 +17,10 @@ func init() { } provider, err := jdcloudlive.NewSSLDeployerProvider(&jdcloudlive.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_jdcloud_vod.go b/internal/certdeploy/deployers/sp_jdcloud_vod.go index 0c39162bb..9070e14e0 100644 --- a/internal/certdeploy/deployers/sp_jdcloud_vod.go +++ b/internal/certdeploy/deployers/sp_jdcloud_vod.go @@ -17,9 +17,10 @@ func init() { } provider, err := jdcloudvod.NewSSLDeployerProvider(&jdcloudvod.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_ksyun_cdn.go b/internal/certdeploy/deployers/sp_ksyun_cdn.go index 2aafdbfe6..40866f2b7 100644 --- a/internal/certdeploy/deployers/sp_ksyun_cdn.go +++ b/internal/certdeploy/deployers/sp_ksyun_cdn.go @@ -17,10 +17,11 @@ func init() { } provider, err := ksyuncdn.NewSSLDeployerProvider(&ksyuncdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - SecretAccessKey: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_qiniu_cdn.go b/internal/certdeploy/deployers/sp_qiniu_cdn.go index e93f7a7e1..4fde037df 100644 --- a/internal/certdeploy/deployers/sp_qiniu_cdn.go +++ b/internal/certdeploy/deployers/sp_qiniu_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := qiniucdn.NewSSLDeployerProvider(&qiniucdn.SSLDeployerProviderConfig{ - AccessKey: credentials.AccessKey, - SecretKey: credentials.SecretKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKey: credentials.AccessKey, + SecretKey: credentials.SecretKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_qiniu_kodo.go b/internal/certdeploy/deployers/sp_qiniu_kodo.go index 69ba9e264..147e62bce 100644 --- a/internal/certdeploy/deployers/sp_qiniu_kodo.go +++ b/internal/certdeploy/deployers/sp_qiniu_kodo.go @@ -19,6 +19,7 @@ func init() { provider, err := qiniukodo.NewSSLDeployerProvider(&qiniukodo.SSLDeployerProviderConfig{ AccessKey: credentials.AccessKey, SecretKey: credentials.SecretKey, + Bucket: xmaps.GetString(options.ProviderExtendedConfig, "bucket"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/certdeploy/deployers/sp_qiniu_pili.go b/internal/certdeploy/deployers/sp_qiniu_pili.go index 06349fcc7..ccf68fd1a 100644 --- a/internal/certdeploy/deployers/sp_qiniu_pili.go +++ b/internal/certdeploy/deployers/sp_qiniu_pili.go @@ -17,10 +17,11 @@ func init() { } provider, err := qiniupili.NewSSLDeployerProvider(&qiniupili.SSLDeployerProviderConfig{ - AccessKey: credentials.AccessKey, - SecretKey: credentials.SecretKey, - Hub: xmaps.GetString(options.ProviderExtendedConfig, "hub"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKey: credentials.AccessKey, + SecretKey: credentials.SecretKey, + Hub: xmaps.GetString(options.ProviderExtendedConfig, "hub"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_rainyun_rcdn.go b/internal/certdeploy/deployers/sp_rainyun_rcdn.go index 27d3c5330..6ae3bfbb1 100644 --- a/internal/certdeploy/deployers/sp_rainyun_rcdn.go +++ b/internal/certdeploy/deployers/sp_rainyun_rcdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := rainyunrcdn.NewSSLDeployerProvider(&rainyunrcdn.SSLDeployerProviderConfig{ - ApiKey: credentials.ApiKey, - InstanceId: xmaps.GetInt64(options.ProviderExtendedConfig, "instanceId"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + ApiKey: credentials.ApiKey, + InstanceId: xmaps.GetInt64(options.ProviderExtendedConfig, "instanceId"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_tencentcloud_css.go b/internal/certdeploy/deployers/sp_tencentcloud_css.go index bb976ca2d..eb728d707 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_css.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_css.go @@ -17,10 +17,11 @@ func init() { } provider, err := tencentcloudcss.NewSSLDeployerProvider(&tencentcloudcss.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_tencentcloud_scf.go b/internal/certdeploy/deployers/sp_tencentcloud_scf.go index 4a85c436c..f28b1dbc9 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_scf.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_scf.go @@ -17,11 +17,12 @@ func init() { } provider, err := tencentcloudscf.NewSSLDeployerProvider(&tencentcloudscf.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_tencentcloud_vod.go b/internal/certdeploy/deployers/sp_tencentcloud_vod.go index 169c585f2..975de9157 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_vod.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_vod.go @@ -17,11 +17,12 @@ func init() { } provider, err := tencentcloudvod.NewSSLDeployerProvider(&tencentcloudvod.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - SubAppId: xmaps.GetInt64(options.ProviderExtendedConfig, "subAppId"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + SubAppId: xmaps.GetInt64(options.ProviderExtendedConfig, "subAppId"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_upyun_cdn.go b/internal/certdeploy/deployers/sp_upyun_cdn.go index d078dd717..d16751d80 100644 --- a/internal/certdeploy/deployers/sp_upyun_cdn.go +++ b/internal/certdeploy/deployers/sp_upyun_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := upyuncdn.NewSSLDeployerProvider(&upyuncdn.SSLDeployerProviderConfig{ - Username: credentials.Username, - Password: credentials.Password, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + Username: credentials.Username, + Password: credentials.Password, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_upyun_file.go b/internal/certdeploy/deployers/sp_upyun_file.go index 1b6b8f9c9..2a82fb9be 100644 --- a/internal/certdeploy/deployers/sp_upyun_file.go +++ b/internal/certdeploy/deployers/sp_upyun_file.go @@ -5,7 +5,7 @@ import ( "github.com/certimate-go/certimate/internal/domain" "github.com/certimate-go/certimate/pkg/core" - upyuncdn "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/upyun-cdn" + upyunfile "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/upyun-file" xmaps "github.com/certimate-go/certimate/pkg/utils/maps" ) @@ -16,9 +16,10 @@ func init() { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } - provider, err := upyuncdn.NewSSLDeployerProvider(&upyuncdn.SSLDeployerProviderConfig{ + provider, err := upyunfile.NewSSLDeployerProvider(&upyunfile.SSLDeployerProviderConfig{ Username: credentials.Username, Password: credentials.Password, + Bucket: xmaps.GetString(options.ProviderExtendedConfig, "bucket"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/certdeploy/deployers/sp_volcengine_dcdn.go b/internal/certdeploy/deployers/sp_volcengine_dcdn.go index 64d6aa74b..51d2b3530 100644 --- a/internal/certdeploy/deployers/sp_volcengine_dcdn.go +++ b/internal/certdeploy/deployers/sp_volcengine_dcdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := volcenginedcdn.NewSSLDeployerProvider(&volcenginedcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_wangsu_cdn.go b/internal/certdeploy/deployers/sp_wangsu_cdn.go index 9e4ad7daa..964f782dd 100644 --- a/internal/certdeploy/deployers/sp_wangsu_cdn.go +++ b/internal/certdeploy/deployers/sp_wangsu_cdn.go @@ -20,9 +20,10 @@ func init() { } provider, err := wangsucdn.NewSSLDeployerProvider(&wangsucdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Domains: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "domains"), ";"), func(s string, _ int) bool { return s != "" }), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domains: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "domains"), ";"), func(s string, _ int) bool { return s != "" }), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_wangsu_cdnpro.go b/internal/certdeploy/deployers/sp_wangsu_cdnpro.go index 1c70b545a..081441004 100644 --- a/internal/certdeploy/deployers/sp_wangsu_cdnpro.go +++ b/internal/certdeploy/deployers/sp_wangsu_cdnpro.go @@ -17,13 +17,14 @@ func init() { } provider, err := wangsucdnpro.NewSSLDeployerProvider(&wangsucdnpro.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ApiKey: credentials.ApiKey, - Environment: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "environment", "production"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), - WebhookId: xmaps.GetString(options.ProviderExtendedConfig, "webhookId"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ApiKey: credentials.ApiKey, + Environment: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "environment", "production"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + WebhookId: xmaps.GetString(options.ProviderExtendedConfig, "webhookId"), }) return provider, err }) diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go index 0694634c1..1319d6765 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go @@ -106,7 +106,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } siteName := dns01.UnFqdn(authZone) - siteID, err := d.getSiteId(siteName) + siteID, err := d.findSiteIdByName(siteName) if err != nil { return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", siteName, err) } @@ -158,17 +158,17 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getSiteId(siteName string) (int64, error) { - pageNumber := 1 - pageSize := 500 +func (d *DNSProvider) findSiteIdByName(siteName string) (int64, error) { + aliListSitesPageNumber := 1 + aliListSitesPageSize := 500 for { // REF: https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-listsites aliListSitesReq := &aliesa.ListSitesRequest{ SiteName: tea.String(siteName), SiteSearchType: tea.String("exact"), - PageNumber: tea.Int32(int32(pageNumber)), - PageSize: tea.Int32(int32(pageSize)), AccessType: tea.String("NS"), + PageNumber: tea.Int32(int32(aliListSitesPageNumber)), + PageSize: tea.Int32(int32(aliListSitesPageSize)), } aliListSitesResp, err := d.client.ListSites(aliListSitesReq) if err != nil { @@ -177,20 +177,20 @@ func (d *DNSProvider) getSiteId(siteName string) (int64, error) { if aliListSitesResp.Body == nil { break - } else { - for _, site := range aliListSitesResp.Body.Sites { - if *site.GetSiteName() == siteName { - return *site.GetSiteId(), nil - } - } - - if len(aliListSitesResp.Body.Sites) < pageSize { - break - } - - pageNumber++ } + + for _, siteItem := range aliListSitesResp.Body.Sites { + if *siteItem.GetSiteName() == siteName { + return *siteItem.GetSiteId(), nil + } + } + + if len(aliListSitesResp.Body.Sites) < aliListSitesPageSize { + break + } + + aliListSitesPageNumber++ } - return 0, errors.New("site not found") + return 0, fmt.Errorf("could not find site '%s'", siteName) } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go index 794bbf749..eee099510 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go @@ -145,31 +145,31 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { } func (d *DNSProvider) findDNSRecord(zoneName, subDomain, tokenValue string) (*bcedns.Record, error) { - pageMarker := "" - pageSize := 1000 + bceListRecordPageMarker := "" for { // REF: https://cloud.baidu.com/doc/DNS/s/El4s7lssr#%E6%9F%A5%E8%AF%A2%E8%A7%A3%E6%9E%90%E8%AE%B0%E5%BD%95%E5%88%97%E8%A1%A8 bceListRecordReq := &bcedns.ListRecordRequest{} bceListRecordReq.Rr = subDomain - bceListRecordReq.Marker = pageMarker - bceListRecordReq.MaxKeys = pageSize + bceListRecordReq.Marker = bceListRecordPageMarker + bceListRecordReq.MaxKeys = 1000 - ceListRecordResp, err := d.client.ListRecord(zoneName, bceListRecordReq) + bceListRecordResp, err := d.client.ListRecord(zoneName, bceListRecordReq) if err != nil { return nil, err } - for _, record := range ceListRecordResp.Records { + for _, record := range bceListRecordResp.Records { if record.Type == "TXT" && record.Rr == subDomain && record.Value == tokenValue { return &record, nil } } - pageMarker = ceListRecordResp.NextMarker - if pageMarker == "" { + if bceListRecordResp.NextMarker == "" { break } + + bceListRecordPageMarker = bceListRecordResp.NextMarker } - return nil, errors.New("record not found") + return nil, errors.New("could not find record") } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go index f0707d9e2..52f3441ec 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go @@ -102,7 +102,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("dnsla: %w", err) } - zone, err := d.getDNSZone(dns01.UnFqdn(authZone)) + zone, err := d.findZone(dns01.UnFqdn(authZone)) if err != nil { return fmt.Errorf("dnsla: error when list zones: %w", err) } @@ -149,34 +149,36 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getDNSZone(zoneName string) (*dnslasdk.DomainRecord, error) { - pageIndex := int32(1) - pageSize := int32(100) +func (d *DNSProvider) findZone(zoneName string) (*dnslasdk.DomainRecord, error) { + dnslaListDomainsPageIndex := 1 + dnslaListDomainsPageSize := 100 for { // REF: https://www.dnsla.cn/docs/ApiDoc dnslaListDomainsReq := &dnslasdk.ListDomainsRequest{ - PageIndex: &pageIndex, - PageSize: &pageSize, + PageIndex: lo.ToPtr(int32(dnslaListDomainsPageIndex)), + PageSize: lo.ToPtr(int32(dnslaListDomainsPageSize)), } dnslaListDomainsResp, err := d.client.ListDomains(dnslaListDomainsReq) if err != nil { return nil, err } - if dnslaListDomainsResp.Data != nil { - for _, item := range dnslaListDomainsResp.Data.Results { - if strings.TrimRight(item.Domain, ".") == zoneName || strings.TrimRight(item.DisplayDomain, ".") == zoneName { - return item, nil - } - } - } - - if dnslaListDomainsResp.Data == nil || len(dnslaListDomainsResp.Data.Results) < int(pageSize) { + if dnslaListDomainsResp.Data == nil { break } - pageIndex++ + for _, domainItem := range dnslaListDomainsResp.Data.Results { + if strings.TrimRight(domainItem.Domain, ".") == zoneName || strings.TrimRight(domainItem.DisplayDomain, ".") == zoneName { + return domainItem, nil + } + } + + if len(dnslaListDomainsResp.Data.Results) < dnslaListDomainsPageSize { + break + } + + dnslaListDomainsPageIndex++ } - return nil, fmt.Errorf("zone '%s' not found", zoneName) + return nil, fmt.Errorf("could not find zone '%s'", zoneName) } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go index 13590f6e6..f46d39cd9 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go @@ -137,5 +137,5 @@ func (d *DNSProvider) findDNSRecord(zoneName, subDomain, tokenValue string) (lib } } - return nil, errors.New("record not found") + return nil, errors.New("could not find record") } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go index 95322aa6c..bbef553aa 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go @@ -101,7 +101,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("jdcloud: %w", err) } - zone, err := d.getDNSZone(dns01.UnFqdn(authZone)) + zone, err := d.findZone(dns01.UnFqdn(authZone)) if err != nil { return fmt.Errorf("jdcloud: error when list zones: %w", err) } @@ -144,7 +144,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("jdcloud: unknown record ID for '%s'", info.EffectiveFQDN) } - zone, err := d.getDNSZone(dns01.UnFqdn(authZone)) + zone, err := d.findZone(dns01.UnFqdn(authZone)) if err != nil { return fmt.Errorf("jdcloud: error when list zones: %w", err) } @@ -166,15 +166,15 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getDNSZone(zoneName string) (*jddnsmodel.DomainInfo, error) { - pageNumber := 1 - pageSize := 10 +func (d *DNSProvider) findZone(zoneName string) (*jddnsmodel.DomainInfo, error) { + jddnsDescribeDomainsPageNumber := 1 + jddnsDescribeDomainsPageSize := 10 for { // REF: https://docs.jdcloud.com/cn/jd-cloud-dns/api/describedomains jddnsDescribeDomainsReq := jddns.NewDescribeDomainsRequestWithoutParam() jddnsDescribeDomainsReq.SetRegionId(d.config.RegionId) - jddnsDescribeDomainsReq.SetPageNumber(pageNumber) - jddnsDescribeDomainsReq.SetPageSize(pageSize) + jddnsDescribeDomainsReq.SetPageNumber(jddnsDescribeDomainsPageNumber) + jddnsDescribeDomainsReq.SetPageSize(jddnsDescribeDomainsPageSize) jddnsDescribeDomainsReq.SetDomainName(zoneName) jddnsDescribeDomainsResp, err := d.client.DescribeDomains(jddnsDescribeDomainsReq) @@ -182,18 +182,18 @@ func (d *DNSProvider) getDNSZone(zoneName string) (*jddnsmodel.DomainInfo, error return nil, err } - for _, item := range jddnsDescribeDomainsResp.Result.DataList { - if item.DomainName == zoneName { - return &item, nil + for _, domainItem := range jddnsDescribeDomainsResp.Result.DataList { + if domainItem.DomainName == zoneName { + return &domainItem, nil } } - if len(jddnsDescribeDomainsResp.Result.DataList) < pageSize { + if len(jddnsDescribeDomainsResp.Result.DataList) < jddnsDescribeDomainsPageSize { break } - pageNumber++ + jddnsDescribeDomainsPageNumber++ } - return nil, fmt.Errorf("jdcloud: zone %s not found", zoneName) + return nil, fmt.Errorf("could not find zone '%s'", zoneName) } diff --git a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go index 65836437e..fb111520e 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go @@ -135,7 +135,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer getLoadBalancerAttributeReq := &alialb.GetLoadBalancerAttributeRequest{ LoadBalancerId: tea.String(d.config.LoadbalancerId), } - getLoadBalancerAttributeResp, err := d.sdkClients.ALB.GetLoadBalancerAttributeWithContext(context.TODO(), getLoadBalancerAttributeReq, &dara.RuntimeOptions{}) + getLoadBalancerAttributeResp, err := d.sdkClients.ALB.GetLoadBalancerAttributeWithContext(ctx, getLoadBalancerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.GetLoadBalancerAttribute'", slog.Any("request", getLoadBalancerAttributeReq), slog.Any("response", getLoadBalancerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.GetLoadBalancerAttribute': %w", err) @@ -144,8 +144,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners listenerIds := make([]string, 0) - listListenersLimit := int32(100) - var listListenersToken *string = nil + listListenersToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -154,28 +153,30 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer } listListenersReq := &alialb.ListListenersRequest{ - MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, - LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + MaxResults: tea.Int32(100), + LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("HTTPS"), } - listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) + listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(ctx, listListenersReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.ListListeners': %w", err) } - if listListenersResp.Body.Listeners != nil { - for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) - } + if listListenersResp.Body == nil { + break + } + + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break - } else { - listListenersToken = listListenersResp.Body.NextToken } + + listListenersToken = listListenersResp.Body.NextToken } // 查询 QUIC 监听列表 @@ -189,28 +190,30 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer } listListenersReq := &alialb.ListListenersRequest{ - MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, - LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + MaxResults: tea.Int32(100), + LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("QUIC"), } - listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) + listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(ctx, listListenersReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.ListListeners': %w", err) } - if listListenersResp.Body.Listeners != nil { - for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) - } + if listListenersResp.Body == nil { + break + } + + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break - } else { - listListenersToken = listListenersResp.Body.NextToken } + + listListenersToken = listListenersResp.Body.NextToken } // 遍历更新监听证书 @@ -258,7 +261,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo getListenerAttributeReq := &alialb.GetListenerAttributeRequest{ ListenerId: tea.String(cloudListenerId), } - getListenerAttributeResp, err := d.sdkClients.ALB.GetListenerAttributeWithContext(context.TODO(), getListenerAttributeReq, &dara.RuntimeOptions{}) + getListenerAttributeResp, err := d.sdkClients.ALB.GetListenerAttributeWithContext(ctx, getListenerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.GetListenerAttribute'", slog.Any("request", getListenerAttributeReq), slog.Any("response", getListenerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.GetListenerAttribute': %w", err) @@ -275,7 +278,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo CertificateId: tea.String(cloudCertId), }}, } - updateListenerAttributeResp, err := d.sdkClients.ALB.UpdateListenerAttributeWithContext(context.TODO(), updateListenerAttributeReq, &dara.RuntimeOptions{}) + updateListenerAttributeResp, err := d.sdkClients.ALB.UpdateListenerAttributeWithContext(ctx, updateListenerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.UpdateListenerAttribute'", slog.Any("request", updateListenerAttributeReq), slog.Any("response", updateListenerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.UpdateListenerAttribute': %w", err) @@ -286,8 +289,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo // 查询监听证书列表 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlistenercertificates listenerCertificates := make([]alialb.ListListenerCertificatesResponseBodyCertificates, 0) - listListenerCertificatesLimit := int32(100) - var listListenerCertificatesToken *string = nil + listListenerCertificatesToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -297,30 +299,32 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo listListenerCertificatesReq := &alialb.ListListenerCertificatesRequest{ NextToken: listListenerCertificatesToken, - MaxResults: tea.Int32(listListenerCertificatesLimit), + MaxResults: tea.Int32(100), ListenerId: tea.String(cloudListenerId), CertificateType: tea.String("Server"), } - listListenerCertificatesResp, err := d.sdkClients.ALB.ListListenerCertificatesWithContext(context.TODO(), listListenerCertificatesReq, &dara.RuntimeOptions{}) + listListenerCertificatesResp, err := d.sdkClients.ALB.ListListenerCertificatesWithContext(ctx, listListenerCertificatesReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.ListListenerCertificates'", slog.Any("request", listListenerCertificatesReq), slog.Any("response", listListenerCertificatesResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.ListListenerCertificates': %w", err) } - if listListenerCertificatesResp.Body.Certificates != nil { - for _, listenerCertificate := range listListenerCertificatesResp.Body.Certificates { - listenerCertificates = append(listenerCertificates, *listenerCertificate) - } + if listListenerCertificatesResp.Body == nil { + break + } + + for _, listenerCertificate := range listListenerCertificatesResp.Body.Certificates { + listenerCertificates = append(listenerCertificates, *listenerCertificate) } if len(listListenerCertificatesResp.Body.Certificates) == 0 || listListenerCertificatesResp.Body.NextToken == nil { break - } else { - listListenerCertificatesToken = listListenerCertificatesResp.Body.NextToken } + + listListenerCertificatesToken = listListenerCertificatesResp.Body.NextToken } - // 遍历查询监听证书,并找出需要解除关联的证书 + // 查询监听证书,并找出需要解除关联的证书 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlistenercertificates // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-getusercertificatedetail certificateIsAlreadyAssociated := false @@ -354,7 +358,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ CertId: tea.Int64(certificateIdAsInt64), } - getUserCertificateDetailResp, err := d.sdkClients.CAS.GetUserCertificateDetailWithContext(context.TODO(), getUserCertificateDetailReq, &dara.RuntimeOptions{}) + getUserCertificateDetailResp, err := d.sdkClients.CAS.GetUserCertificateDetailWithContext(ctx, getUserCertificateDetailReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) if err != nil { if sdkerr, ok := err.(*tea.SDKError); ok { @@ -397,7 +401,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo }, }, } - associateAdditionalCertificatesFromListenerResp, err := d.sdkClients.ALB.AssociateAdditionalCertificatesWithListenerWithContext(context.TODO(), associateAdditionalCertificatesFromListenerReq, &dara.RuntimeOptions{}) + associateAdditionalCertificatesFromListenerResp, err := d.sdkClients.ALB.AssociateAdditionalCertificatesWithListenerWithContext(ctx, associateAdditionalCertificatesFromListenerReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.AssociateAdditionalCertificatesWithListener'", slog.Any("request", associateAdditionalCertificatesFromListenerReq), slog.Any("response", associateAdditionalCertificatesFromListenerResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.AssociateAdditionalCertificatesWithListener': %w", err) @@ -418,7 +422,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo ListenerId: tea.String(cloudListenerId), Certificates: dissociateAdditionalCertificates, } - dissociateAdditionalCertificatesFromListenerResp, err := d.sdkClients.ALB.DissociateAdditionalCertificatesFromListenerWithContext(context.TODO(), dissociateAdditionalCertificatesFromListenerReq, &dara.RuntimeOptions{}) + dissociateAdditionalCertificatesFromListenerResp, err := d.sdkClients.ALB.DissociateAdditionalCertificatesFromListenerWithContext(ctx, dissociateAdditionalCertificatesFromListenerReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.DissociateAdditionalCertificatesFromListener'", slog.Any("request", dissociateAdditionalCertificatesFromListenerReq), slog.Any("response", dissociateAdditionalCertificatesFromListenerResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.DissociateAdditionalCertificatesFromListener': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go index 486596bf3..84200ab77 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -18,6 +18,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-apigw/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -37,6 +39,9 @@ type SSLDeployerProviderConfig struct { // API 分组 ID。 // 服务类型为 [SERVICE_TYPE_TRADITIONAL] 时必填。 GroupId string `json:"groupId,omitempty"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 自定义域名(支持泛域名)。 Domain string `json:"domain"` } @@ -116,23 +121,87 @@ func (d *SSLDeployerProvider) deployToTraditional(ctx context.Context, certPEM s if d.config.GroupId == "" { return errors.New("config `groupId` is required") } - if d.config.Domain == "" { - return errors.New("config `domain` is required") + + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getTraditionalAllDomainsByGroupId(ctx, d.config.GroupId) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + domainCandidates, err := d.getTraditionalAllDomainsByGroupId(ctx, d.config.GroupId) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by certificate") + } + } + + default: + return fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 为自定义域名添加 SSL 证书 - // REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-setdomaincertificate - setDomainCertificateReq := &alicloudapi.SetDomainCertificateRequest{ - GroupId: tea.String(d.config.GroupId), - DomainName: tea.String(d.config.Domain), - CertificateName: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())), - CertificateBody: tea.String(certPEM), - CertificatePrivateKey: tea.String(privkeyPEM), - } - setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificateWithContext(context.TODO(), setDomainCertificateReq, &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apigateway.SetDomainCertificate'", slog.Any("request", setDomainCertificateReq), slog.Any("response", setDomainCertificateResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apigateway.SetDomainCertificate': %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no apigw domains to deploy") + } else { + d.logger.Info("found apigw domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateTraditionalDomainCertificate(ctx, d.config.GroupId, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } } return nil @@ -142,66 +211,6 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s if d.config.GatewayId == "" { return errors.New("config `gatewayId` is required") } - if d.config.Domain == "" { - return errors.New("config `domain` is required") - } - - // 遍历查询域名列表,获取域名 ID - // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains - var domainId string - listDomainsPageNumber := int32(1) - listDomainsPageSize := int32(10) - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - listDomainsReq := &aliapig.ListDomainsRequest{ - ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - GatewayId: tea.String(d.config.GatewayId), - NameLike: tea.String(d.config.Domain), - PageNumber: tea.Int32(listDomainsPageNumber), - PageSize: tea.Int32(listDomainsPageSize), - } - listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomainsWithContext(context.TODO(), listDomainsReq, make(map[string]*string), &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apig.ListDomains': %w", err) - } - - if listDomainsResp.Body.Data.Items != nil { - for _, domainInfo := range listDomainsResp.Body.Data.Items { - if strings.EqualFold(tea.StringValue(domainInfo.Name), d.config.Domain) { - domainId = tea.StringValue(domainInfo.DomainId) - break - } - } - - if domainId != "" { - break - } - } - - if listDomainsResp.Body.Data.Items == nil || len(listDomainsResp.Body.Data.Items) < int(listDomainsPageSize) { - break - } else { - listDomainsPageNumber++ - } - } - if domainId == "" { - return errors.New("domain not found") - } - - // 查询域名 - // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain - getDomainReq := &aliapig.GetDomainRequest{} - getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomainWithContext(context.TODO(), tea.String(domainId), getDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apig.GetDomain'", slog.String("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apig.GetDomain': %w", err) - } // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) @@ -211,6 +220,196 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getCloudNativeAllDomainsByGatewayId(ctx, d.config.GatewayId) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + domainCandidates, err := d.getCloudNativeAllDomainsByGatewayId(ctx, d.config.GatewayId) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by certificate") + } + } + + default: + return fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no apigw domains to deploy") + } else { + d.logger.Info("found apigw domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return ctx.Err() + default: + certId := upres.ExtendedData["CertIdentifier"].(string) + if err := d.updateCloudNativeDomainCertificate(ctx, d.config.GatewayId, domain, certId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *SSLDeployerProvider) getTraditionalAllDomainsByGroupId(ctx context.Context, cloudGroupId string) ([]string, error) { + domains := make([]string, 0) + + // 查询 API 分组详情 + // REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-describeapigroup + describeApiGroupReq := &alicloudapi.DescribeApiGroupRequest{ + GroupId: tea.String(cloudGroupId), + } + describeApiGroupResp, err := d.sdkClients.TraditionalAPIGateway.DescribeApiGroupWithContext(ctx, describeApiGroupReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apigateway.DescribeApiGroup'", slog.Any("request", describeApiGroupReq), slog.Any("response", describeApiGroupResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'apigateway.DescribeApiGroup': %w", err) + } + + for _, domainItem := range describeApiGroupResp.Body.CustomDomains.DomainItem { + if strings.EqualFold(tea.StringValue(domainItem.DomainBindingStatus), "BINDING") { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) getCloudNativeAllDomainsByGatewayId(ctx context.Context, cloudGatewayId string) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains + listDomainsPageNumber := 1 + listDomainsPageSize := 10 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainsReq := &aliapig.ListDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + GatewayId: tea.String(cloudGatewayId), + PageNumber: tea.Int32(int32(listDomainsPageNumber)), + PageSize: tea.Int32(int32(listDomainsPageSize)), + } + listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomainsWithContext(ctx, listDomainsReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'apig.ListDomains': %w", err) + } + + if listDomainsResp.Body == nil || listDomainsResp.Body.Data == nil { + break + } + + for _, domainItem := range listDomainsResp.Body.Data.Items { + if strings.EqualFold(tea.StringValue(domainItem.Status), "Published") { + domains = append(domains, tea.StringValue(domainItem.Name)) + } + } + + if len(listDomainsResp.Body.Data.Items) < listDomainsPageSize { + break + } + + listDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateTraditionalDomainCertificate(ctx context.Context, cloudGroupId string, domain string, certPEM, privkeyPEM string) error { + // 为自定义域名添加 SSL 证书 + // REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-setdomaincertificate + setDomainCertificateReq := &alicloudapi.SetDomainCertificateRequest{ + GroupId: tea.String(cloudGroupId), + DomainName: tea.String(domain), + CertificateName: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())), + CertificateBody: tea.String(certPEM), + CertificatePrivateKey: tea.String(privkeyPEM), + } + setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificateWithContext(ctx, setDomainCertificateReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apigateway.SetDomainCertificate'", slog.Any("request", setDomainCertificateReq), slog.Any("response", setDomainCertificateResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'apigateway.SetDomainCertificate': %w", err) + } + + return nil +} + +func (d *SSLDeployerProvider) updateCloudNativeDomainCertificate(ctx context.Context, cloudGatewayId string, domain string, cloudCertId string) error { + // 获取域名 ID + domainId, err := d.findCloudNativeDomainIdByDomain(ctx, cloudGatewayId, domain) + if err != nil { + return err + } + + // 查询域名 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain + getDomainReq := &aliapig.GetDomainRequest{} + getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomainWithContext(ctx, tea.String(domainId), getDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.GetDomain'", slog.String("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'apig.GetDomain': %w", err) + } + // 更新域名 // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-updatedomain updateDomainReq := &aliapig.UpdateDomainRequest{ @@ -221,9 +420,9 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s TlsMin: getDomainResp.Body.Data.TlsMin, TlsMax: getDomainResp.Body.Data.TlsMax, TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig, - CertIdentifier: tea.String(upres.ExtendedData["CertIdentifier"].(string)), + CertIdentifier: tea.String(cloudCertId), } - updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomainWithContext(context.TODO(), tea.String(domainId), updateDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomainWithContext(ctx, tea.String(domainId), updateDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.String("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'apig.UpdateDomain': %w", err) @@ -232,6 +431,51 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s return nil } +func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Context, cloudGatewayId string, domain string) (string, error) { + // 查询域名列表 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains + listDomainsPageNumber := 1 + listDomainsPageSize := 10 + for { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + listDomainsReq := &aliapig.ListDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + GatewayId: tea.String(cloudGatewayId), + NameLike: tea.String(domain), + PageNumber: tea.Int32(int32(listDomainsPageNumber)), + PageSize: tea.Int32(int32(listDomainsPageSize)), + } + listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomainsWithContext(ctx, listDomainsReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return "", fmt.Errorf("failed to execute sdk request 'apig.ListDomains': %w", err) + } + + if listDomainsResp.Body == nil || listDomainsResp.Body.Data == nil { + break + } + + for _, domainItem := range listDomainsResp.Body.Data.Items { + if strings.EqualFold(tea.StringValue(domainItem.Name), domain) { + return tea.StringValue(domainItem.DomainId), nil + } + } + + if len(listDomainsResp.Body.Data.Items) < listDomainsPageSize { + break + } + + listDomainsPageNumber++ + } + + return "", fmt.Errorf("could not find domain '%s'", domain) +} + func createSDKClients(accessKeyId, accessKeySecret, region string) (*wSDKClients, error) { // 接入点一览 https://api.aliyun.com/product/APIG var cloudNativeAPIGEndpoint string diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw_test.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw_test.go index ef522b00d..2ed1b0527 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw_test.go @@ -69,13 +69,14 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - ServiceType: fServiceType, - GatewayId: fGatewayId, - GroupId: fGroupId, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ServiceType: fServiceType, + GatewayId: fGatewayId, + GroupId: fGroupId, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/consts.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/consts.go index 7a767b08b..a37be7845 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/consts.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/consts.go @@ -6,3 +6,12 @@ const ( // 服务类型:云原生 API 网关。 SERVICE_TYPE_CLOUDNATIVE = "cloudnative" ) + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/internal/client.go index b737a5252..b5d80a4e7 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/internal/client.go @@ -197,6 +197,48 @@ func NewCloudapiClient(config *openapiutil.Config) (*CloudapiClient, error) { return client, err } +func (client *CloudapiClient) DescribeApiGroupWithContext(ctx context.Context, request *alicloudapi.DescribeApiGroupRequest, runtime *dara.RuntimeOptions) (_result *alicloudapi.DescribeApiGroupResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.GroupId) { + query["GroupId"] = request.GroupId + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeApiGroup"), + Version: dara.String("2016-07-14"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alicloudapi.DescribeApiGroupResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *CloudapiClient) SetDomainCertificateWithContext(ctx context.Context, request *alicloudapi.SetDomainCertificateRequest, runtime *dara.RuntimeOptions) (_result *alicloudapi.SetDomainCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go b/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go index d016af23d..eeb148f0b 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go @@ -102,7 +102,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke ShowSize: tea.Int32(1), CurrentPage: tea.Int32(1), } - listContactResp, err := d.sdkClient.ListContactWithContext(context.TODO(), listContactReq, &dara.RuntimeOptions{}) + listContactResp, err := d.sdkClient.ListContactWithContext(ctx, listContactReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cas.ListContact'", slog.Any("request", listContactReq), slog.Any("response", listContactResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.ListContact': %w", err) @@ -122,7 +122,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke ResourceIds: tea.String(strings.Join(d.config.ResourceIds, ",")), ContactIds: tea.String(strings.Join(contactIds, ",")), } - createDeploymentJobResp, err := d.sdkClient.CreateDeploymentJobWithContext(context.TODO(), createDeploymentJobReq, &dara.RuntimeOptions{}) + createDeploymentJobResp, err := d.sdkClient.CreateDeploymentJobWithContext(ctx, createDeploymentJobReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cas.CreateDeploymentJob'", slog.Any("request", createDeploymentJobReq), slog.Any("response", createDeploymentJobResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.CreateDeploymentJob': %w", err) @@ -140,17 +140,16 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke describeDeploymentJobReq := &alicas.DescribeDeploymentJobRequest{ JobId: createDeploymentJobResp.Body.JobId, } - describeDeploymentJobResp, err := d.sdkClient.DescribeDeploymentJobWithContext(context.TODO(), describeDeploymentJobReq, &dara.RuntimeOptions{}) + describeDeploymentJobResp, err := d.sdkClient.DescribeDeploymentJobWithContext(ctx, describeDeploymentJobReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cas.DescribeDeploymentJob'", slog.Any("request", describeDeploymentJobReq), slog.Any("response", describeDeploymentJobResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.DescribeDeploymentJob': %w", err) } - if describeDeploymentJobResp.Body.Status == nil || *describeDeploymentJobResp.Body.Status == "editing" { + status := tea.StringValue(describeDeploymentJobResp.Body.Status) + if status == "" || status == "editing" { return nil, errors.New("unexpected aliyun deployment job status") - } - - if *describeDeploymentJobResp.Body.Status == "success" || *describeDeploymentJobResp.Body.Status == "error" { + } else if status == "success" || status == "error" { break } diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go index 917360669..94aa8b820 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -17,6 +17,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-cdn/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -28,6 +30,9 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -80,10 +85,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -92,28 +93,163 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + // "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-describeuserdomains + describeUserDomainsPageNumber := 1 + describeUserDomainsPageSize := 500 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeUserDomainsReq := &alicdn.DescribeUserDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + PageNumber: tea.Int32(int32(describeUserDomainsPageNumber)), + PageSize: tea.Int32(int32(describeUserDomainsPageSize)), + } + describeUserDomainsResp, err := d.sdkClient.DescribeUserDomainsWithContext(ctx, describeUserDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'cdn.DescribeUserDomains'", slog.Any("request", describeUserDomainsReq), slog.Any("response", describeUserDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeUserDomains': %w", err) + } + + if describeUserDomainsResp.Body == nil || describeUserDomainsResp.Body.Domains == nil { + break + } + + ignoredStatuses := []string{"offline", "checking", "check_failed", "stopping", "deleting"} + for _, domainItem := range describeUserDomainsResp.Body.Domains.PageData { + if lo.Contains(ignoredStatuses, tea.StringValue(domainItem.DomainStatus)) { + continue + } + + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(describeUserDomainsResp.Body.Domains.PageData) < describeUserDomainsPageSize { + break + } + + describeUserDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId int64) error { // 设置 CDN 域名域名证书 // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate - certId, _ := strconv.ParseInt(upres.CertId, 10, 64) setCdnDomainSSLCertificateReq := &alicdn.SetCdnDomainSSLCertificateRequest{ DomainName: tea.String(domain), CertType: tea.String("cas"), - CertId: tea.Int64(certId), + CertId: tea.Int64(cloudCertId), CertRegion: lo. If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), } - setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificateWithContext(context.TODO(), setCdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) + setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificateWithContext(ctx, setCdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cdn.SetCdnDomainSSLCertificate'", slog.Any("request", setCdnDomainSSLCertificateReq), slog.Any("response", setCdnDomainSSLCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.CdnClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn_test.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn_test.go index 271edc3d3..664d9a721 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/consts.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/consts.go new file mode 100644 index 000000000..618edec4d --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/consts.go @@ -0,0 +1,10 @@ +package aliyuncdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/internal/client.go index 1f921cd16..34b49f184 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/internal/client.go @@ -35,6 +35,96 @@ func (client *CdnClient) Init(config *openapiutil.Config) (_err error) { return nil } +func (client *CdnClient) DescribeUserDomainsWithContext(ctx context.Context, request *alicdn.DescribeUserDomainsRequest, runtime *dara.RuntimeOptions) (_result *alicdn.DescribeUserDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.CdnType) { + query["CdnType"] = request.CdnType + } + + if !dara.IsNil(request.ChangeEndTime) { + query["ChangeEndTime"] = request.ChangeEndTime + } + + if !dara.IsNil(request.ChangeStartTime) { + query["ChangeStartTime"] = request.ChangeStartTime + } + + if !dara.IsNil(request.CheckDomainShow) { + query["CheckDomainShow"] = request.CheckDomainShow + } + + if !dara.IsNil(request.Coverage) { + query["Coverage"] = request.Coverage + } + + if !dara.IsNil(request.DomainName) { + query["DomainName"] = request.DomainName + } + + if !dara.IsNil(request.DomainSearchType) { + query["DomainSearchType"] = request.DomainSearchType + } + + if !dara.IsNil(request.DomainStatus) { + query["DomainStatus"] = request.DomainStatus + } + + if !dara.IsNil(request.OwnerId) { + query["OwnerId"] = request.OwnerId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.ResourceGroupId) { + query["ResourceGroupId"] = request.ResourceGroupId + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Source) { + query["Source"] = request.Source + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeUserDomains"), + Version: dara.String("2018-05-10"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alicdn.DescribeUserDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *CdnClient) SetCdnDomainSSLCertificateWithContext(ctx context.Context, request *alicdn.SetCdnDomainSSLCertificateRequest, runtime *dara.RuntimeOptions) (_result *alicdn.SetCdnDomainSSLCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go b/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go index 79939fbc6..84957baae 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go @@ -132,8 +132,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners listenerPorts := make([]int32, 0) - describeLoadBalancerListenersLimit := int32(100) - var describeLoadBalancerListenersToken *string = nil + describeLoadBalancerListenersToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -143,9 +142,9 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer describeLoadBalancerListenersReq := &alislb.DescribeLoadBalancerListenersRequest{ RegionId: tea.String(d.config.Region), - MaxResults: tea.Int32(describeLoadBalancerListenersLimit), NextToken: describeLoadBalancerListenersToken, - LoadBalancerId: []*string{tea.String(d.config.LoadbalancerId)}, + MaxResults: tea.Int32(100), + LoadBalancerId: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("https"), } describeLoadBalancerListenersResp, err := d.sdkClient.DescribeLoadBalancerListeners(describeLoadBalancerListenersReq) @@ -154,17 +153,19 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer return fmt.Errorf("failed to execute sdk request 'slb.DescribeLoadBalancerListeners': %w", err) } - if describeLoadBalancerListenersResp.Body.Listeners != nil { - for _, listener := range describeLoadBalancerListenersResp.Body.Listeners { - listenerPorts = append(listenerPorts, *listener.ListenerPort) - } + if describeLoadBalancerListenersResp.Body == nil { + break + } + + for _, listener := range describeLoadBalancerListenersResp.Body.Listeners { + listenerPorts = append(listenerPorts, *listener.ListenerPort) } if len(describeLoadBalancerListenersResp.Body.Listeners) == 0 || describeLoadBalancerListenersResp.Body.NextToken == nil { break - } else { - describeLoadBalancerListenersToken = describeLoadBalancerListenersResp.Body.NextToken } + + describeLoadBalancerListenersToken = describeLoadBalancerListenersResp.Body.NextToken } // 遍历更新监听证书 @@ -254,7 +255,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo return fmt.Errorf("failed to execute sdk request 'slb.DescribeDomainExtensions': %w", err) } - // 遍历修改扩展域名 + // 遍历修改扩展域名证书 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setdomainextensionattribute if describeDomainExtensionsResp.Body.DomainExtensions != nil && describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension != nil { var errs []error diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index f34278488..bd6eeb80b 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -17,6 +17,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -28,6 +30,9 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -80,10 +85,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -92,28 +93,164 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + // "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no dcdn domains to deploy") + } else { + d.logger.Info("found dcdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-describedcdnuserdomains + describeUserDomainsPageNumber := 1 + describeUserDomainsPageSize := 500 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeDcdnUserDomainsReq := &alidcdn.DescribeDcdnUserDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + CheckDomainShow: tea.Bool(true), + PageNumber: tea.Int32(int32(describeUserDomainsPageNumber)), + PageSize: tea.Int32(int32(describeUserDomainsPageSize)), + } + describeDcdnUserDomainsResp, err := d.sdkClient.DescribeDcdnUserDomainsWithContext(ctx, describeDcdnUserDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'dcdn.DescribeDcdnUserDomains'", slog.Any("request", describeDcdnUserDomainsReq), slog.Any("response", describeDcdnUserDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'dcdn.DescribeDcdnUserDomains': %w", err) + } + + if describeDcdnUserDomainsResp.Body == nil || describeDcdnUserDomainsResp.Body.Domains == nil { + break + } + + ignoredStatuses := []string{"offline", "checking", "check_failed", "stopping", "deleting"} + for _, domainItem := range describeDcdnUserDomainsResp.Body.Domains.PageData { + if lo.Contains(ignoredStatuses, tea.StringValue(domainItem.DomainStatus)) { + continue + } + + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(describeDcdnUserDomainsResp.Body.Domains.PageData) < describeUserDomainsPageNumber { + break + } + + describeUserDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId int64) error { // 配置域名证书 // REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate - certId, _ := strconv.ParseInt(upres.CertId, 10, 64) setDcdnDomainSSLCertificateReq := &alidcdn.SetDcdnDomainSSLCertificateRequest{ DomainName: tea.String(domain), CertType: tea.String("cas"), - CertId: tea.Int64(int64(certId)), + CertId: tea.Int64(cloudCertId), CertRegion: lo. If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), } - setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificateWithContext(context.TODO(), setDcdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) + setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificateWithContext(ctx, setDcdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'dcdn.SetDcdnDomainSSLCertificate'", slog.Any("request", setDcdnDomainSSLCertificateReq), slog.Any("response", setDcdnDomainSSLCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.DcdnClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go index 33c213c54..3f35d5634 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/consts.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/consts.go new file mode 100644 index 000000000..c7b97a047 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/consts.go @@ -0,0 +1,10 @@ +package aliyundcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal/client.go index fbff22aa4..8d91ef0aa 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal/client.go @@ -35,6 +35,92 @@ func (client *DcdnClient) Init(config *openapiutil.Config) (_err error) { return nil } +func (client *DcdnClient) DescribeDcdnUserDomainsWithContext(ctx context.Context, request *alidcdn.DescribeDcdnUserDomainsRequest, runtime *dara.RuntimeOptions) (_result *alidcdn.DescribeDcdnUserDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.ChangeEndTime) { + query["ChangeEndTime"] = request.ChangeEndTime + } + + if !dara.IsNil(request.ChangeStartTime) { + query["ChangeStartTime"] = request.ChangeStartTime + } + + if !dara.IsNil(request.CheckDomainShow) { + query["CheckDomainShow"] = request.CheckDomainShow + } + + if !dara.IsNil(request.Coverage) { + query["Coverage"] = request.Coverage + } + + if !dara.IsNil(request.DomainName) { + query["DomainName"] = request.DomainName + } + + if !dara.IsNil(request.DomainSearchType) { + query["DomainSearchType"] = request.DomainSearchType + } + + if !dara.IsNil(request.DomainStatus) { + query["DomainStatus"] = request.DomainStatus + } + + if !dara.IsNil(request.OwnerId) { + query["OwnerId"] = request.OwnerId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.ResourceGroupId) { + query["ResourceGroupId"] = request.ResourceGroupId + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + if !dara.IsNil(request.WebSiteType) { + query["WebSiteType"] = request.WebSiteType + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeDcdnUserDomains"), + Version: dara.String("2018-01-15"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alidcdn.DescribeDcdnUserDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *DcdnClient) SetDcdnDomainSSLCertificateWithContext(ctx context.Context, request *alidcdn.SetDcdnDomainSSLCertificateRequest, runtime *dara.RuntimeOptions) (_result *alidcdn.SetDcdnDomainSSLCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go index 4490da2cb..1984de8d6 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go @@ -16,6 +16,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -27,6 +29,9 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 网站域名(支持泛域名)。 Domain string `json:"domain"` } @@ -81,10 +86,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -93,21 +94,129 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 为网站业务转发规则关联 SSL 证书 - // REF: https://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-associatewebcert - associateWebCertReq := &aliddoscoo.AssociateWebCertRequest{ - Domain: tea.String(d.config.Domain), - CertIdentifier: tea.String(upres.ExtendedData["CertIdentifier"].(string)), + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - associateWebCertResp, err := d.sdkClient.AssociateWebCertWithContext(context.TODO(), associateWebCertReq, &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'dcdn.AssociateWebCert'", slog.Any("request", associateWebCertReq), slog.Any("response", associateWebCertResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'dcdn.AssociateWebCert': %w", err) + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no ddoscoo domains to deploy") + } else { + d.logger.Info("found ddoscoo domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + certId := upres.ExtendedData["CertIdentifier"].(string) + if err := d.updateDomainCertificate(ctx, domain, certId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询已配置网站业务转发规则的域名 + // REF: https://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-describedomains + describeDomainsReq := &aliddoscoo.DescribeDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + } + describeDomainsResp, err := d.sdkClient.DescribeDomainsWithContext(ctx, describeDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'aliddoscoo.DescribeLiveUserDomains'", slog.Any("request", describeDomainsReq), slog.Any("response", describeDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'aliddoscoo.DescribeDomains': %w", err) + } + + for _, domain := range describeDomainsResp.Body.Domains { + domains = append(domains, tea.StringValue(domain)) + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 为网站业务转发规则关联 SSL 证书 + // REF: https://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-associatewebcert + associateWebCertReq := &aliddoscoo.AssociateWebCertRequest{ + Domain: tea.String(domain), + CertIdentifier: tea.String(cloudCertId), + } + associateWebCertResp, err := d.sdkClient.AssociateWebCertWithContext(ctx, associateWebCertReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'dcdn.AssociateWebCert'", slog.Any("request", associateWebCertReq), slog.Any("response", associateWebCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'dcdn.AssociateWebCert': %w", err) + } + + return nil +} + func createSDKClient(accessKeyId, accessKeySecret, region string) (*internal.DdoscooClient, error) { // 接入点一览 https://api.aliyun.com/product/ddoscoo var endpoint string diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro_test.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro_test.go index 458e1928a..aec9989a6 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddospro/consts.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/consts.go new file mode 100644 index 000000000..2c25f12d6 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/consts.go @@ -0,0 +1,10 @@ +package aliyunddospro + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal/client.go index 837d3507b..989e0831f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal/client.go @@ -92,3 +92,41 @@ func (client *DdoscooClient) AssociateWebCertWithContext(ctx context.Context, re _err = dara.Convert(_body, &_result) return _result, _err } + +func (client *DdoscooClient) DescribeDomainsWithContext(ctx context.Context, request *aliddoscoo.DescribeDomainsRequest, runtime *dara.RuntimeOptions) (_result *aliddoscoo.DescribeDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.InstanceIds) { + query["InstanceIds"] = request.InstanceIds + } + + if !dara.IsNil(request.ResourceGroupId) { + query["ResourceGroupId"] = request.ResourceGroupId + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeDomains"), + Version: dara.String("2020-01-01"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &aliddoscoo.DescribeDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} diff --git a/pkg/core/ssl-deployer/providers/aliyun-esa/aliyun_esa.go b/pkg/core/ssl-deployer/providers/aliyun-esa/aliyun_esa.go index ebcede0e6..fdf849eb9 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-esa/aliyun_esa.go +++ b/pkg/core/ssl-deployer/providers/aliyun-esa/aliyun_esa.go @@ -103,7 +103,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke CasId: tea.Int64(certId), Region: tea.String(d.config.Region), } - setCertificateResp, err := d.sdkClient.SetCertificateWithContext(context.TODO(), setCertificateReq, &dara.RuntimeOptions{}) + setCertificateResp, err := d.sdkClient.SetCertificateWithContext(ctx, setCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'esa.SetCertificate'", slog.Any("request", setCertificateReq), slog.Any("response", setCertificateResp)) if err != nil { var sdkError *tea.SDKError diff --git a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go index 3359a7aef..7f595c94f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "strings" "time" aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" @@ -12,9 +13,12 @@ import ( alifc2 "github.com/alibabacloud-go/fc-open-20210406/v2/client" "github.com/alibabacloud-go/tea/dara" "github.com/alibabacloud-go/tea/tea" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-fc/internal" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -29,6 +33,9 @@ type SSLDeployerProviderConfig struct { // 服务版本。 // 可取值 "2.0"、"3.0"。 ServiceVersion string `json:"serviceVersion"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 自定义域名(支持泛域名)。 Domain string `json:"domain"` } @@ -91,16 +98,270 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } func (d *SSLDeployerProvider) deployToFC3(ctx context.Context, certPEM string, privkeyPEM string) error { - if d.config.Domain == "" { - return errors.New("config `domain` is required") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getFC3AllDomains(ctx) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + domainCandidates, err := d.getFC3AllDomains(ctx) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by certificate") + } + } + + default: + return fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no fc domains to deploy") + } else { + d.logger.Info("found fc domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateFC3DomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *SSLDeployerProvider) deployToFC2(ctx context.Context, certPEM string, privkeyPEM string) error { + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getFC2AllDomains(ctx) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + domainCandidates, err := d.getFC2AllDomains(ctx) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by certificate") + } + } + + default: + return fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no fc domains to deploy") + } else { + d.logger.Info("found fc domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateFC2DomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *SSLDeployerProvider) getFC3AllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 列出自定义域名 + // REF: https://help.aliyun.com/zh/functioncompute/fc/developer-reference/api-fc-2023-03-30-listcustomdomains + listCustomDomainsNextToken := (*string)(nil) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listCustomDomainsReq := &alifc3.ListCustomDomainsRequest{ + NextToken: listCustomDomainsNextToken, + Limit: tea.Int32(100), + } + listCustomDomainsResp, err := d.sdkClients.FC3.ListCustomDomainsWithContext(ctx, listCustomDomainsReq, make(map[string]*string, 0), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'fc.ListCustomDomains'", slog.Any("request", listCustomDomainsReq), slog.Any("response", listCustomDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'fc.ListCustomDomains': %w", err) + } + + if listCustomDomainsResp.Body == nil { + break + } + + for _, domainItem := range listCustomDomainsResp.Body.CustomDomains { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(listCustomDomainsResp.Body.CustomDomains) == 0 || listCustomDomainsResp.Body.NextToken == nil { + break + } + + listCustomDomainsNextToken = listCustomDomainsResp.Body.NextToken + } + + return domains, nil +} + +func (d *SSLDeployerProvider) getFC2AllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 列出自定义域名 + // REF: https://help.aliyun.com/zh/functioncompute/fc-2-0/developer-reference/api-fc-open-2021-04-06-listcustomdomains + listCustomDomainsNextToken := (*string)(nil) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listCustomDomainsReq := &alifc2.ListCustomDomainsRequest{ + NextToken: listCustomDomainsNextToken, + Limit: tea.Int32(100), + } + listCustomDomainsResp, err := d.sdkClients.FC2.ListCustomDomains(listCustomDomainsReq) + d.logger.Debug("sdk request 'fc.ListCustomDomains'", slog.Any("request", listCustomDomainsReq), slog.Any("response", listCustomDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'fc.ListCustomDomains': %w", err) + } + + if listCustomDomainsResp.Body == nil { + break + } + + for _, domainItem := range listCustomDomainsResp.Body.CustomDomains { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(listCustomDomainsResp.Body.CustomDomains) == 0 || listCustomDomainsResp.Body.NextToken == nil { + break + } + + listCustomDomainsNextToken = listCustomDomainsResp.Body.NextToken + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateFC3DomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 获取自定义域名 // REF: https://help.aliyun.com/zh/functioncompute/fc-3-0/developer-reference/api-fc-2023-03-30-getcustomdomain - getCustomDomainResp, err := d.sdkClients.FC3.GetCustomDomainWithContext(context.TODO(), tea.String(d.config.Domain), make(map[string]*string), &dara.RuntimeOptions{}) + getCustomDomainResp, err := d.sdkClients.FC3.GetCustomDomainWithContext(ctx, tea.String(domain), make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'fc.GetCustomDomain'", slog.Any("response", getCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.GetCustomDomain': %w", err) + } else { + if getCustomDomainResp.Body.CertConfig != nil && tea.StringValue(getCustomDomainResp.Body.CertConfig.Certificate) == certPEM { + return nil + } } // 更新自定义域名 @@ -119,7 +380,7 @@ func (d *SSLDeployerProvider) deployToFC3(ctx context.Context, certPEM string, p if tea.StringValue(updateCustomDomainReq.Body.Protocol) == "HTTP" { updateCustomDomainReq.Body.Protocol = tea.String("HTTP,HTTPS") } - updateCustomDomainResp, err := d.sdkClients.FC3.UpdateCustomDomainWithContext(context.TODO(), tea.String(d.config.Domain), updateCustomDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + updateCustomDomainResp, err := d.sdkClients.FC3.UpdateCustomDomainWithContext(ctx, tea.String(domain), updateCustomDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'fc.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.UpdateCustomDomain': %w", err) @@ -128,17 +389,17 @@ func (d *SSLDeployerProvider) deployToFC3(ctx context.Context, certPEM string, p return nil } -func (d *SSLDeployerProvider) deployToFC2(ctx context.Context, certPEM string, privkeyPEM string) error { - if d.config.Domain == "" { - return errors.New("config `domain` is required") - } - +func (d *SSLDeployerProvider) updateFC2DomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 获取自定义域名 // REF: https://help.aliyun.com/zh/functioncompute/fc-2-0/developer-reference/api-fc-open-2021-04-06-getcustomdomain - getCustomDomainResp, err := d.sdkClients.FC2.GetCustomDomain(tea.String(d.config.Domain)) + getCustomDomainResp, err := d.sdkClients.FC2.GetCustomDomain(tea.String(domain)) d.logger.Debug("sdk request 'fc.GetCustomDomain'", slog.Any("response", getCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.GetCustomDomain': %w", err) + } else { + if getCustomDomainResp.Body.CertConfig != nil && tea.StringValue(getCustomDomainResp.Body.CertConfig.Certificate) == certPEM { + return nil + } } // 更新自定义域名 @@ -155,7 +416,7 @@ func (d *SSLDeployerProvider) deployToFC2(ctx context.Context, certPEM string, p if tea.StringValue(updateCustomDomainReq.Protocol) == "HTTP" { updateCustomDomainReq.Protocol = tea.String("HTTP,HTTPS") } - updateCustomDomainResp, err := d.sdkClients.FC2.UpdateCustomDomain(tea.String(d.config.Domain), updateCustomDomainReq) + updateCustomDomainResp, err := d.sdkClients.FC2.UpdateCustomDomain(tea.String(domain), updateCustomDomainReq) d.logger.Debug("sdk request 'fc.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.UpdateCustomDomain': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc_test.go b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc_test.go index de03a75d2..e33ec12ee 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc_test.go @@ -57,10 +57,12 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ServiceVersion: "3.0", + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-fc/consts.go b/pkg/core/ssl-deployer/providers/aliyun-fc/consts.go new file mode 100644 index 000000000..3365c700f --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/consts.go @@ -0,0 +1,10 @@ +package aliyunfc + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/aliyun-fc/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-fc/internal/client.go index 3270dc2e5..923c9fdec 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-fc/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/internal/client.go @@ -63,6 +63,49 @@ func (client *FcClient) GetCustomDomainWithContext(ctx context.Context, domainNa return _result, _err } +func (client *FcClient) ListCustomDomainsWithContext(ctx context.Context, request *alifc.ListCustomDomainsRequest, headers map[string]*string, runtime *dara.RuntimeOptions) (_result *alifc.ListCustomDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.Limit) { + query["limit"] = request.Limit + } + + if !dara.IsNil(request.NextToken) { + query["nextToken"] = request.NextToken + } + + if !dara.IsNil(request.Prefix) { + query["prefix"] = request.Prefix + } + + req := &openapiutilv2.OpenApiRequest{ + Headers: headers, + Query: openapiutil.Query(query), + } + params := &openapiutilv2.Params{ + Action: dara.String("ListCustomDomains"), + Version: dara.String("2023-03-30"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/2023-03-30/custom-domains"), + Method: dara.String("GET"), + AuthType: dara.String("AK"), + Style: dara.String("ROA"), + ReqBodyType: dara.String("json"), + BodyType: dara.String("json"), + } + _result = &alifc.ListCustomDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *FcClient) UpdateCustomDomainWithContext(ctx context.Context, domainName *string, request *alifc.UpdateCustomDomainRequest, headers map[string]*string, runtime *dara.RuntimeOptions) (_result *alifc.UpdateCustomDomainResponse, _err error) { _err = request.Validate() if _err != nil { @@ -172,6 +215,82 @@ func (client *FcopenClient) GetCustomDomainWithOptions(domainName *string, heade return _result, _err } +func (client *FcopenClient) ListCustomDomains(request *alifcopen.ListCustomDomainsRequest) (_result *alifcopen.ListCustomDomainsResponse, _err error) { + runtime := &util.RuntimeOptions{} + headers := &alifcopen.ListCustomDomainsHeaders{} + _result = &alifcopen.ListCustomDomainsResponse{} + _body, _err := client.ListCustomDomainsWithOptions(request, headers, runtime) + if _err != nil { + return _result, _err + } + _result = _body + return _result, _err +} + +func (client *FcopenClient) ListCustomDomainsWithOptions(request *alifcopen.ListCustomDomainsRequest, headers *alifcopen.ListCustomDomainsHeaders, runtime *util.RuntimeOptions) (_result *alifcopen.ListCustomDomainsResponse, _err error) { + _err = util.ValidateModel(request) + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !tea.BoolValue(util.IsUnset(request.Limit)) { + query["limit"] = request.Limit + } + + if !tea.BoolValue(util.IsUnset(request.NextToken)) { + query["nextToken"] = request.NextToken + } + + if !tea.BoolValue(util.IsUnset(request.Prefix)) { + query["prefix"] = request.Prefix + } + + if !tea.BoolValue(util.IsUnset(request.StartKey)) { + query["startKey"] = request.StartKey + } + + realHeaders := make(map[string]*string) + if !tea.BoolValue(util.IsUnset(headers.CommonHeaders)) { + realHeaders = headers.CommonHeaders + } + + if !tea.BoolValue(util.IsUnset(headers.XFcAccountId)) { + realHeaders["X-Fc-Account-Id"] = util.ToJSONString(headers.XFcAccountId) + } + + if !tea.BoolValue(util.IsUnset(headers.XFcDate)) { + realHeaders["X-Fc-Date"] = util.ToJSONString(headers.XFcDate) + } + + if !tea.BoolValue(util.IsUnset(headers.XFcTraceId)) { + realHeaders["X-Fc-Trace-Id"] = util.ToJSONString(headers.XFcTraceId) + } + + req := &openapi.OpenApiRequest{ + Headers: realHeaders, + Query: openapiutil.Query(query), + } + params := &openapi.Params{ + Action: tea.String("ListCustomDomains"), + Version: tea.String("2021-04-06"), + Protocol: tea.String("HTTPS"), + Pathname: tea.String("/2021-04-06/custom-domains"), + Method: tea.String("GET"), + AuthType: tea.String("AK"), + Style: tea.String("ROA"), + ReqBodyType: tea.String("json"), + BodyType: tea.String("json"), + } + _result = &alifcopen.ListCustomDomainsResponse{} + _body, _err := client.CallApi(params, req, runtime) + if _err != nil { + return _result, _err + } + _err = tea.Convert(_body, &_result) + return _result, _err +} + func (client *FcopenClient) UpdateCustomDomain(domainName *string, request *alifcopen.UpdateCustomDomainRequest) (_result *alifcopen.UpdateCustomDomainResponse, _err error) { runtime := &util.RuntimeOptions{} headers := &alifcopen.UpdateCustomDomainHeaders{} diff --git a/pkg/core/ssl-deployer/providers/aliyun-ga/aliyun_ga.go b/pkg/core/ssl-deployer/providers/aliyun-ga/aliyun_ga.go index 8f0f5bd19..0eec25028 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ga/aliyun_ga.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ga/aliyun_ga.go @@ -119,8 +119,8 @@ func (d *SSLDeployerProvider) deployToAccelerator(ctx context.Context, cloudCert // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlisteners listenerIds := make([]string, 0) - listListenersPageNumber := int32(1) - listListenersPageSize := int32(50) + listListenersPageNumber := 1 + listListenersPageSize := 50 for { select { case <-ctx.Done(): @@ -131,8 +131,8 @@ func (d *SSLDeployerProvider) deployToAccelerator(ctx context.Context, cloudCert listListenersReq := &aliga.ListListenersRequest{ RegionId: tea.String("cn-hangzhou"), AcceleratorId: tea.String(d.config.AcceleratorId), - PageNumber: tea.Int32(listListenersPageNumber), - PageSize: tea.Int32(listListenersPageSize), + PageNumber: tea.Int32(int32(listListenersPageNumber)), + PageSize: tea.Int32(int32(listListenersPageSize)), } listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) d.logger.Debug("sdk request 'ga.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) @@ -140,19 +140,21 @@ func (d *SSLDeployerProvider) deployToAccelerator(ctx context.Context, cloudCert return fmt.Errorf("failed to execute sdk request 'ga.ListListeners': %w", err) } - if listListenersResp.Body.Listeners != nil { - for _, listener := range listListenersResp.Body.Listeners { - if strings.EqualFold(tea.StringValue(listener.Protocol), "https") { - listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) - } + if listListenersResp.Body == nil { + break + } + + for _, listener := range listListenersResp.Body.Listeners { + if strings.EqualFold(tea.StringValue(listener.Protocol), "https") { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } } - if len(listListenersResp.Body.Listeners) < int(listListenersPageSize) { + if len(listListenersResp.Body.Listeners) < listListenersPageSize { break - } else { - listListenersPageNumber++ } + + listListenersPageNumber++ } // 遍历更新监听证书 @@ -200,9 +202,9 @@ func (d *SSLDeployerProvider) deployToListener(ctx context.Context, cloudCertId func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, cloudAcceleratorId string, cloudListenerId string, cloudCertId string) error { // 查询监听绑定的证书列表 // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlistenercertificates - var listenerDefaultCertificate *aliga.ListListenerCertificatesResponseBodyCertificates - var listenerAdditionalCertificates []*aliga.ListListenerCertificatesResponseBodyCertificates = make([]*aliga.ListListenerCertificatesResponseBodyCertificates, 0) - var listListenerCertificatesNextToken *string + listenerDefaultCertificate := (*aliga.ListListenerCertificatesResponseBodyCertificates)(nil) + listenerAdditionalCertificates := make([]*aliga.ListListenerCertificatesResponseBodyCertificates, 0) + listListenerCertificatesNextToken := (*string)(nil) for { listListenerCertificatesReq := &aliga.ListListenerCertificatesRequest{ RegionId: tea.String("cn-hangzhou"), @@ -217,21 +219,23 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo return fmt.Errorf("failed to execute sdk request 'ga.ListListenerCertificates': %w", err) } - if listListenerCertificatesResp.Body.Certificates != nil { - for _, certificate := range listListenerCertificatesResp.Body.Certificates { - if tea.BoolValue(certificate.IsDefault) { - listenerDefaultCertificate = certificate - } else { - listenerAdditionalCertificates = append(listenerAdditionalCertificates, certificate) - } + if listListenerCertificatesResp.Body == nil { + break + } + + for _, certItem := range listListenerCertificatesResp.Body.Certificates { + if tea.BoolValue(certItem.IsDefault) { + listenerDefaultCertificate = certItem + } else { + listenerAdditionalCertificates = append(listenerAdditionalCertificates, certItem) } } - if listListenerCertificatesResp.Body.NextToken == nil { + if len(listListenerCertificatesResp.Body.Certificates) == 0 || listListenerCertificatesResp.Body.NextToken == nil { break - } else { - listListenerCertificatesNextToken = listListenerCertificatesResp.Body.NextToken } + + listListenerCertificatesNextToken = listListenerCertificatesResp.Body.NextToken } if d.config.Domain == "" { diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go index 27357ee64..e0bdb827c 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go +++ b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go @@ -12,9 +12,12 @@ import ( alilive "github.com/alibabacloud-go/live-20161101/v2/client" "github.com/alibabacloud-go/tea/dara" "github.com/alibabacloud-go/tea/tea" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-live/internal" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -26,6 +29,9 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 直播流域名(支持泛域名)。 Domain string `json:"domain"` } @@ -64,13 +70,141 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + // "*.example.com" → ".example.com",适配阿里云 Live 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // "*.example.com" → ".example.com",适配阿里云 Live 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no live domains to deploy") + } else { + d.logger.Info("found live domains to deploy", slog.Any("domains", domains)) + var errs []error + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询用户名下所有的直播域名 + // REF: https://help.aliyun.com/zh/live/developer-reference/api-live-2016-11-01-describeliveuserdomains + describeUserLiveDomainsPageNumber := 1 + describeUserLiveDomainsPageSize := 50 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeUserLiveDomainsReq := &alilive.DescribeLiveUserDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + RegionName: tea.String(d.config.Region), + DomainStatus: tea.String("online"), + PageNumber: tea.Int32(int32(describeUserLiveDomainsPageNumber)), + PageSize: tea.Int32(int32(describeUserLiveDomainsPageSize)), + } + describeUserLiveDomainsResp, err := d.sdkClient.DescribeLiveUserDomainsWithContext(ctx, describeUserLiveDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'live.DescribeLiveUserDomains'", slog.Any("request", describeUserLiveDomainsReq), slog.Any("response", describeUserLiveDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'live.DescribeLiveUserDomains': %w", err) + } + + if describeUserLiveDomainsResp.Body == nil || describeUserLiveDomainsResp.Body.Domains == nil { + break + } + + for _, domainItem := range describeUserLiveDomainsResp.Body.Domains.PageData { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(describeUserLiveDomainsResp.Body.Domains.PageData) < describeUserLiveDomainsPageSize { + break + } + + describeUserLiveDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 设置域名证书 // REF: https://help.aliyun.com/zh/live/developer-reference/api-live-2016-11-01-setlivedomaincertificate setLiveDomainSSLCertificateReq := &alilive.SetLiveDomainCertificateRequest{ @@ -81,13 +215,13 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke SSLPub: tea.String(certPEM), SSLPri: tea.String(privkeyPEM), } - setLiveDomainSSLCertificateResp, err := d.sdkClient.SetLiveDomainCertificateWithContext(context.TODO(), setLiveDomainSSLCertificateReq, &dara.RuntimeOptions{}) + setLiveDomainSSLCertificateResp, err := d.sdkClient.SetLiveDomainCertificateWithContext(ctx, setLiveDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'live.SetLiveDomainCertificate'", slog.Any("request", setLiveDomainSSLCertificateReq), slog.Any("response", setLiveDomainSSLCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret, region string) (*internal.LiveClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live_test.go b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live_test.go index 772f21b27..7ae6f3748 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/consts.go b/pkg/core/ssl-deployer/providers/aliyun-live/consts.go new file mode 100644 index 000000000..6b4089379 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-live/consts.go @@ -0,0 +1,10 @@ +package aliyunlive + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-live/internal/client.go index 2f3d39c5b..bbeed402e 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-live/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-live/internal/client.go @@ -35,6 +35,80 @@ func (client *LiveClient) Init(config *openapiutil.Config) (_err error) { return nil } +func (client *LiveClient) DescribeLiveUserDomainsWithContext(ctx context.Context, request *alilive.DescribeLiveUserDomainsRequest, runtime *dara.RuntimeOptions) (_result *alilive.DescribeLiveUserDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.DomainName) { + query["DomainName"] = request.DomainName + } + + if !dara.IsNil(request.DomainSearchType) { + query["DomainSearchType"] = request.DomainSearchType + } + + if !dara.IsNil(request.DomainStatus) { + query["DomainStatus"] = request.DomainStatus + } + + if !dara.IsNil(request.LiveDomainType) { + query["LiveDomainType"] = request.LiveDomainType + } + + if !dara.IsNil(request.OwnerId) { + query["OwnerId"] = request.OwnerId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.RegionName) { + query["RegionName"] = request.RegionName + } + + if !dara.IsNil(request.ResourceGroupId) { + query["ResourceGroupId"] = request.ResourceGroupId + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeLiveUserDomains"), + Version: dara.String("2016-11-01"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alilive.DescribeLiveUserDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *LiveClient) SetLiveDomainCertificateWithContext(ctx context.Context, request *alilive.SetLiveDomainCertificateRequest, runtime *dara.RuntimeOptions) (_result *alilive.SetLiveDomainCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go b/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go index f51f7b8ee..33ef757db 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -124,7 +124,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer getLoadBalancerAttributeReq := &alinlb.GetLoadBalancerAttributeRequest{ LoadBalancerId: tea.String(d.config.LoadbalancerId), } - getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttributeWithContext(context.TODO(), getLoadBalancerAttributeReq, &dara.RuntimeOptions{}) + getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttributeWithContext(ctx, getLoadBalancerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'nlb.GetLoadBalancerAttribute'", slog.Any("request", getLoadBalancerAttributeReq), slog.Any("response", getLoadBalancerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'nlb.GetLoadBalancerAttribute': %w", err) @@ -133,8 +133,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 TCPSSL 监听列表 // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners listenerIds := make([]string, 0) - listListenersLimit := int32(100) - var listListenersToken *string = nil + listListenersToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -143,28 +142,30 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer } listListenersReq := &alinlb.ListListenersRequest{ - MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, - LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + MaxResults: tea.Int32(100), + LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("TCPSSL"), } - listListenersResp, err := d.sdkClient.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) + listListenersResp, err := d.sdkClient.ListListenersWithContext(ctx, listListenersReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'nlb.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'nlb.ListListeners': %w", err) } - if listListenersResp.Body.Listeners != nil { - for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) - } + if listListenersResp.Body == nil { + break + } + + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break - } else { - listListenersToken = listListenersResp.Body.NextToken } + + listListenersToken = listListenersResp.Body.NextToken } // 遍历更新监听证书 @@ -212,7 +213,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo getListenerAttributeReq := &alinlb.GetListenerAttributeRequest{ ListenerId: tea.String(cloudListenerId), } - getListenerAttributeResp, err := d.sdkClient.GetListenerAttributeWithContext(context.TODO(), getListenerAttributeReq, &dara.RuntimeOptions{}) + getListenerAttributeResp, err := d.sdkClient.GetListenerAttributeWithContext(ctx, getListenerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'nlb.GetListenerAttribute'", slog.Any("request", getListenerAttributeReq), slog.Any("response", getListenerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'nlb.GetListenerAttribute': %w", err) @@ -224,7 +225,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo ListenerId: tea.String(cloudListenerId), CertificateIds: []*string{tea.String(cloudCertId)}, } - updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttributeWithContext(context.TODO(), updateListenerAttributeReq, &dara.RuntimeOptions{}) + updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttributeWithContext(ctx, updateListenerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'nlb.UpdateListenerAttribute'", slog.Any("request", updateListenerAttributeReq), slog.Any("response", updateListenerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'nlb.UpdateListenerAttribute': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-oss/aliyun_oss.go b/pkg/core/ssl-deployer/providers/aliyun-oss/aliyun_oss.go index 152f16867..4ca28bc34 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-oss/aliyun_oss.go +++ b/pkg/core/ssl-deployer/providers/aliyun-oss/aliyun_oss.go @@ -82,7 +82,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke }, }, } - putCnameResp, err := d.sdkClient.PutCname(context.TODO(), putCnameReq) + putCnameResp, err := d.sdkClient.PutCname(ctx, putCnameReq) d.logger.Debug("sdk request 'oss.PutCname'", slog.Any("request", putCnameReq), slog.Any("response", putCnameResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'oss.PutCname': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go index ec5c3a595..62e66bab0 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go @@ -17,6 +17,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-vod/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -28,7 +30,10 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` - // 点播加速域名(不支持泛域名)。 + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` + // 点播加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -80,38 +85,155 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 上传证书 - upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) - if err != nil { - return nil, fmt.Errorf("failed to upload certificate file: %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no vod domains to deploy") } else { - d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + d.logger.Info("found vod domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询加速域名列表 + // REF: https://help.aliyun.com/zh/live/developer-reference/api-live-2016-11-01-describeliveuserdomains + describeVodUserDomainsPageNumber := 1 + describeVodUserDomainsPageSize := 50 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeVodUserDomainsReq := &alivod.DescribeVodUserDomainsRequest{ + DomainStatus: tea.String("online"), + PageNumber: tea.Int32(int32(describeVodUserDomainsPageNumber)), + PageSize: tea.Int32(int32(describeVodUserDomainsPageSize)), + } + describeVodUserDomainsResp, err := d.sdkClient.DescribeVodUserDomainsWithContext(ctx, describeVodUserDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'vod.DescribeVodUserDomains'", slog.Any("request", describeVodUserDomainsReq), slog.Any("response", describeVodUserDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'vod.DescribeLiveUserDomains': %w", err) + } + + if describeVodUserDomainsResp.Body == nil || describeVodUserDomainsResp.Body.Domains == nil { + break + } + + for _, domainItem := range describeVodUserDomainsResp.Body.Domains.PageData { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(describeVodUserDomainsResp.Body.Domains.PageData) < describeVodUserDomainsPageSize { + break + } + + describeVodUserDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { // 设置域名证书 // REF: https://help.aliyun.com/zh/vod/developer-reference/api-vod-2017-03-21-setvoddomainsslcertificate - certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + certId, _ := strconv.ParseInt(cloudCertId, 10, 64) setVodDomainSSLCertificateReq := &alivod.SetVodDomainSSLCertificateRequest{ - DomainName: tea.String(d.config.Domain), + DomainName: tea.String(domain), CertType: tea.String("cas"), CertId: tea.Int64(certId), - CertName: tea.String(upres.CertName), + CertName: tea.String(cloudCertName), CertRegion: lo. If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), } - setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificateWithContext(context.TODO(), setVodDomainSSLCertificateReq, &dara.RuntimeOptions{}) + setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificateWithContext(ctx, setVodDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'live.SetVodDomainSSLCertificate'", slog.Any("request", setVodDomainSSLCertificateReq), slog.Any("response", setVodDomainSSLCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'live.SetVodDomainSSLCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'live.SetVodDomainSSLCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret, region string) (*internal.VodClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod_test.go b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod_test.go index 3cf7ec28d..de18f1441 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/consts.go b/pkg/core/ssl-deployer/providers/aliyun-vod/consts.go new file mode 100644 index 000000000..110fd2a58 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/consts.go @@ -0,0 +1,10 @@ +package aliyunvod + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-vod/internal/client.go index a5acc0412..ad23a53af 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/internal/client.go @@ -35,6 +35,68 @@ func (client *VodClient) Init(config *openapiutil.Config) (_err error) { return nil } +func (client *VodClient) DescribeVodUserDomainsWithContext(ctx context.Context, request *alivod.DescribeVodUserDomainsRequest, runtime *dara.RuntimeOptions) (_result *alivod.DescribeVodUserDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.DomainName) { + query["DomainName"] = request.DomainName + } + + if !dara.IsNil(request.DomainSearchType) { + query["DomainSearchType"] = request.DomainSearchType + } + + if !dara.IsNil(request.DomainStatus) { + query["DomainStatus"] = request.DomainStatus + } + + if !dara.IsNil(request.OwnerId) { + query["OwnerId"] = request.OwnerId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeVodUserDomains"), + Version: dara.String("2017-03-21"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alivod.DescribeVodUserDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *VodClient) SetVodDomainSSLCertificateWithContext(ctx context.Context, request *alivod.SetVodDomainSSLCertificateRequest, runtime *dara.RuntimeOptions) (_result *alivod.SetVodDomainSSLCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go b/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go index 3d88c15eb..f12321f9f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go +++ b/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go @@ -118,10 +118,10 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedefaulthttps describeDefaultHttpsReq := &aliwaf.DescribeDefaultHttpsRequest{ ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), } - describeDefaultHttpsResp, err := d.sdkClient.DescribeDefaultHttpsWithContext(context.TODO(), describeDefaultHttpsReq, &dara.RuntimeOptions{}) + describeDefaultHttpsResp, err := d.sdkClient.DescribeDefaultHttpsWithContext(ctx, describeDefaultHttpsReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.DescribeDefaultHttps'", slog.Any("request", describeDefaultHttpsReq), slog.Any("response", describeDefaultHttpsResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'waf.DescribeDefaultHttps': %w", err) @@ -131,8 +131,8 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-modifydefaulthttps modifyDefaultHttpsReq := &aliwaf.ModifyDefaultHttpsRequest{ ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string)), TLSVersion: tea.String("tlsv1"), EnableTLSv3: tea.Bool(true), @@ -145,7 +145,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, modifyDefaultHttpsReq.EnableTLSv3 = describeDefaultHttpsResp.Body.DefaultHttps.EnableTLSv3 } } - modifyDefaultHttpsResp, err := d.sdkClient.ModifyDefaultHttpsWithContext(context.TODO(), modifyDefaultHttpsReq, &dara.RuntimeOptions{}) + modifyDefaultHttpsResp, err := d.sdkClient.ModifyDefaultHttpsWithContext(ctx, modifyDefaultHttpsReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.ModifyDefaultHttps'", slog.Any("request", modifyDefaultHttpsReq), slog.Any("response", modifyDefaultHttpsResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'waf.ModifyDefaultHttps': %w", err) @@ -156,11 +156,11 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, // 查询 CNAME 接入详情 // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedomaindetail describeDomainDetailReq := &aliwaf.DescribeDomainDetailRequest{ - InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), Domain: tea.String(d.config.Domain), } - describeDomainDetailResp, err := d.sdkClient.DescribeDomainDetailWithContext(context.TODO(), describeDomainDetailReq, &dara.RuntimeOptions{}) + describeDomainDetailResp, err := d.sdkClient.DescribeDomainDetailWithContext(ctx, describeDomainDetailReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.DescribeDomainDetail'", slog.Any("request", describeDomainDetailReq), slog.Any("response", describeDomainDetailResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'waf.DescribeDomainDetail': %w", err) @@ -169,14 +169,14 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, // 修改 CNAME 接入资源 // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-modifydomain modifyDomainReq := &aliwaf.ModifyDomainRequest{ - InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), Domain: tea.String(d.config.Domain), Listen: &aliwaf.ModifyDomainRequestListen{CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string))}, Redirect: &aliwaf.ModifyDomainRequestRedirect{Loadbalance: tea.String("iphash")}, } - modifyDomainReq = assign(modifyDomainReq, describeDomainDetailResp.Body) - modifyDomainResp, err := d.sdkClient.ModifyDomainWithContext(context.TODO(), modifyDomainReq, &dara.RuntimeOptions{}) + modifyDomainReq = _assign(modifyDomainReq, describeDomainDetailResp.Body) + modifyDomainResp, err := d.sdkClient.ModifyDomainWithContext(ctx, modifyDomainReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.ModifyDomain'", slog.Any("request", modifyDomainReq), slog.Any("response", modifyDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'waf.ModifyDomain': %w", err) @@ -210,7 +210,7 @@ func createSDKClient(accessKeyId, accessKeySecret, region string) (*internal.Waf return client, nil } -func assign(source *aliwaf.ModifyDomainRequest, target *aliwaf.DescribeDomainDetailResponseBody) *aliwaf.ModifyDomainRequest { +func _assign(source *aliwaf.ModifyDomainRequest, target *aliwaf.DescribeDomainDetailResponseBody) *aliwaf.ModifyDomainRequest { // `ModifyDomain` 中不传的字段表示使用默认值、而非保留原值, // 因此这里需要把原配置中的参数重新赋值回去。 diff --git a/pkg/core/ssl-deployer/providers/aws-acm/aws_acm.go b/pkg/core/ssl-deployer/providers/aws-acm/aws_acm.go index 30d627dbe..b71b7821a 100644 --- a/pkg/core/ssl-deployer/providers/aws-acm/aws_acm.go +++ b/pkg/core/ssl-deployer/providers/aws-acm/aws_acm.go @@ -98,7 +98,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke CertificateChain: ([]byte)(intermediaCertPEM), PrivateKey: ([]byte)(privkeyPEM), } - importCertificateResp, err := d.sdkClient.ImportCertificate(context.TODO(), importCertificateReq) + importCertificateResp, err := d.sdkClient.ImportCertificate(ctx, importCertificateReq) d.logger.Debug("sdk request 'acm.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.ImportCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aws-cloudfront/aws_cloudfront.go b/pkg/core/ssl-deployer/providers/aws-cloudfront/aws_cloudfront.go index 79f4fe50d..cbaa9a0a2 100644 --- a/pkg/core/ssl-deployer/providers/aws-cloudfront/aws_cloudfront.go +++ b/pkg/core/ssl-deployer/providers/aws-cloudfront/aws_cloudfront.go @@ -113,7 +113,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke getDistributionConfigReq := &cloudfront.GetDistributionConfigInput{ Id: aws.String(d.config.DistributionId), } - getDistributionConfigResp, err := d.sdkClient.GetDistributionConfig(context.TODO(), getDistributionConfigReq) + getDistributionConfigResp, err := d.sdkClient.GetDistributionConfig(ctx, getDistributionConfigReq) d.logger.Debug("sdk request 'cloudfront.GetDistributionConfig'", slog.Any("request", getDistributionConfigReq), slog.Any("response", getDistributionConfigResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cloudfront.GetDistributionConfig': %w", err) @@ -145,7 +145,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke updateDistributionReq.DistributionConfig.ViewerCertificate.SSLSupportMethod = types.SSLSupportMethodSniOnly } } - updateDistributionResp, err := d.sdkClient.UpdateDistribution(context.TODO(), updateDistributionReq) + updateDistributionResp, err := d.sdkClient.UpdateDistribution(ctx, updateDistributionReq) d.logger.Debug("sdk request 'cloudfront.UpdateDistribution'", slog.Any("request", updateDistributionReq), slog.Any("response", updateDistributionResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cloudfront.UpdateDistribution': %w", err) diff --git a/pkg/core/ssl-deployer/providers/azure-keyvault/azure_keyvault.go b/pkg/core/ssl-deployer/providers/azure-keyvault/azure_keyvault.go index bd113189e..1261f8cd0 100644 --- a/pkg/core/ssl-deployer/providers/azure-keyvault/azure_keyvault.go +++ b/pkg/core/ssl-deployer/providers/azure-keyvault/azure_keyvault.go @@ -106,7 +106,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } else { // 获取证书 // REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificate/get-certificate - getCertificateResp, err := d.sdkClient.GetCertificate(context.TODO(), d.config.CertificateName, "", nil) + getCertificateResp, err := d.sdkClient.GetCertificate(ctx, d.config.CertificateName, "", nil) d.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", d.config.CertificateName), slog.Any("response", getCertificateResp)) if err != nil { var respErr *azcore.ResponseError @@ -134,7 +134,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke "certimate/cert-sn": to.Ptr(certX509.SerialNumber.Text(16)), }, } - importCertificateResp, err := d.sdkClient.ImportCertificate(context.TODO(), d.config.CertificateName, importCertificateParams, nil) + importCertificateResp, err := d.sdkClient.ImportCertificate(ctx, d.config.CertificateName, importCertificateParams, nil) d.logger.Debug("sdk request 'keyvault.ImportCertificate'", slog.String("request.certificateName", d.config.CertificateName), slog.Any("request.parameters", importCertificateParams), slog.Any("response", importCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'keyvault.ImportCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/baiducloud-appblb/baiducloud_appblb.go b/pkg/core/ssl-deployer/providers/baiducloud-appblb/baiducloud_appblb.go index e95034cef..4c5847057 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-appblb/baiducloud_appblb.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-appblb/baiducloud_appblb.go @@ -255,7 +255,7 @@ func (d *SSLDeployerProvider) updateHttpsListenerCertificate(ctx context.Context if err != nil { return fmt.Errorf("failed to execute sdk request 'appblb.DescribeAppHTTPSListeners': %w", err) } else if len(describeAppHTTPSListenersResp.ListenerList) == 0 { - return fmt.Errorf("listener %s:%d not found", cloudLoadbalancerId, cloudHttpsListenerPort) + return fmt.Errorf("cloud not find listener '%s:%d'", cloudLoadbalancerId, cloudHttpsListenerPort) } if d.config.Domain == "" { diff --git a/pkg/core/ssl-deployer/providers/baiducloud-blb/baiducloud_blb.go b/pkg/core/ssl-deployer/providers/baiducloud-blb/baiducloud_blb.go index e6fd15239..55d11ef5d 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-blb/baiducloud_blb.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-blb/baiducloud_blb.go @@ -255,7 +255,7 @@ func (d *SSLDeployerProvider) updateHttpsListenerCertificate(ctx context.Context if err != nil { return fmt.Errorf("failed to execute sdk request 'blb.DescribeHTTPSListeners': %w", err) } else if len(describeHTTPSListenersResp.ListenerList) == 0 { - return fmt.Errorf("listener %s:%d not found", cloudLoadbalancerId, cloudHttpsListenerPort) + return fmt.Errorf("could not find listener '%s:%d'", cloudLoadbalancerId, cloudHttpsListenerPort) } if d.config.Domain == "" { diff --git a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go index c8e181b84..42416da7c 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -5,11 +5,16 @@ import ( "errors" "fmt" "log/slog" + "strings" "time" bcecdn "github.com/baidubce/bce-sdk-go/services/cdn" bcecdnapi "github.com/baidubce/bce-sdk-go/services/cdn/api" "github.com/certimate-go/certimate/pkg/core" + "github.com/samber/lo" + + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -17,6 +22,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 百度智能云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -55,14 +63,127 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://cloud.baidu.com/doc/CDN/s/sjwvyewt1 + listDomainsMarker := "" + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainsRespDomains, listDomainsNextMarker, err := d.sdkClient.ListDomains(listDomainsMarker) + d.logger.Debug("sdk request 'cdn.ListDomains'", slog.String("request.marker", listDomainsMarker), slog.Any("response.domains", listDomainsRespDomains)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListDomains': %w", err) + } + + domains = append(domains, listDomainsRespDomains...) + + if listDomainsNextMarker == "" { + break + } + + listDomainsMarker = listDomainsNextMarker + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 修改域名证书 // REF: https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8 putCertResp, err := d.sdkClient.PutCert( - d.config.Domain, + domain, &bcecdnapi.UserCertificate{ CertName: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), ServerData: certPEM, @@ -70,12 +191,12 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke }, "ON", ) - d.logger.Debug("sdk request 'cdn.PutCert'", slog.String("request.domain", d.config.Domain), slog.Any("response", putCertResp)) + d.logger.Debug("sdk request 'cdn.PutCert'", slog.String("request.domain", domain), slog.Any("response", putCertResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.PutCert': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.PutCert': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, secretAccessKey string) (*bcecdn.Client, error) { diff --git a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go index 068e6fade..15ca0ec76 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/baiducloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/baiducloud-cdn/consts.go new file mode 100644 index 000000000..cee9f2705 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/consts.go @@ -0,0 +1,10 @@ +package baiducloudcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn.go b/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn.go index 7ded574b7..7db318aab 100644 --- a/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn.go +++ b/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn.go @@ -19,6 +19,9 @@ import ( type SSLDeployerProviderConfig struct { // 白山云 API Token。 ApiToken string `json:"apiToken"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` // 证书 ID。 @@ -103,7 +106,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'baishan.GetDomainConfig': %w", err) } else if len(getDomainConfigResp.Data) == 0 { - return nil, fmt.Errorf("domain %s not found", d.config.Domain) + return nil, fmt.Errorf("could not find domain '%s'", d.config.Domain) } // 设置域名配置 diff --git a/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn_test.go b/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn_test.go index e989785c0..b9aad6b89 100644 --- a/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn_test.go @@ -49,8 +49,9 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - ApiToken: fApiToken, - Domain: fDomain, + ApiToken: fApiToken, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/baishan-cdn/consts.go b/pkg/core/ssl-deployer/providers/baishan-cdn/consts.go new file mode 100644 index 000000000..7b86619c4 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/baishan-cdn/consts.go @@ -0,0 +1,6 @@ +package baishancdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go b/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go index 59a543d76..27a32ee61 100644 --- a/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go +++ b/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go @@ -74,48 +74,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, fmt.Errorf("failed to execute sdk request 'bt.PanelGetConfig': %w", err) } - // 遍历查询网站列表,获取网站 ID - var siteId int32 - datalistGetDataListPage := int32(1) - datalistGetDataListLimit := int32(10) - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - datalistGetDataListReq := &btsdk.DatalistGetDataListRequest{ - Table: lo.ToPtr("sites"), - SearchString: lo.ToPtr(d.config.SiteName), - Page: lo.ToPtr(datalistGetDataListPage), - Limit: lo.ToPtr(datalistGetDataListLimit), - } - datalistGetDataListResp, err := d.sdkClient.DatalistGetDataList(datalistGetDataListReq) - d.logger.Debug("sdk request 'bt.DatalistGetDataList'", slog.Any("request", datalistGetDataListReq), slog.Any("response", datalistGetDataListResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'bt.DatalistGetDataList': %w", err) - } - - for _, siteInfo := range datalistGetDataListResp.Data { - if strings.EqualFold(siteInfo.Name, d.config.SiteName) { - siteId = siteInfo.Id - break - } - } - - if siteId != 0 { - break - } - - if len(datalistGetDataListResp.Data) < int(datalistGetDataListLimit) { - break - } else { - datalistGetDataListPage++ - } - } - if siteId == 0 { - return nil, errors.New("website not found") + // 获取网站 ID + siteId, err := d.findSiteIdByName(ctx, d.config.SiteName) + if err != nil { + return nil, err } if panelGetConfigResp.Site != nil && strings.EqualFold(panelGetConfigResp.Site.WebServer, "iis") { @@ -174,6 +136,45 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) findSiteIdByName(ctx context.Context, siteName string) (int32, error) { + // 查询网站列表 + datalistGetDataListPage := 1 + datalistGetDataListLimit := 10 + for { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + + datalistGetDataListReq := &btsdk.DatalistGetDataListRequest{ + Table: lo.ToPtr("sites"), + SearchString: lo.ToPtr(d.config.SiteName), + Page: lo.ToPtr(int32(datalistGetDataListPage)), + Limit: lo.ToPtr(int32(datalistGetDataListLimit)), + } + datalistGetDataListResp, err := d.sdkClient.DatalistGetDataList(datalistGetDataListReq) + d.logger.Debug("sdk request 'bt.DatalistGetDataList'", slog.Any("request", datalistGetDataListReq), slog.Any("response", datalistGetDataListResp)) + if err != nil { + return 0, fmt.Errorf("failed to execute sdk request 'bt.DatalistGetDataList': %w", err) + } + + for _, siteItem := range datalistGetDataListResp.Data { + if strings.EqualFold(siteItem.Name, d.config.SiteName) { + return siteItem.Id, nil + } + } + + if len(datalistGetDataListResp.Data) < datalistGetDataListLimit { + break + } + + datalistGetDataListPage++ + } + + return 0, fmt.Errorf("could not find site '%s'", siteName) +} + func createSDKClient(serverUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) { client, err := btsdk.NewClient(serverUrl, apiKey) if err != nil { diff --git a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go index d1408d9e1..e5a9dc72a 100644 --- a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go +++ b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go @@ -68,46 +68,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.config.SitePort = 443 } - // 遍历获取网站列表,获取网站 ID - // REF: https://support.huaweicloud.com/api-waf/ListHost.html - siteId := "" - getSitListPage := int32(1) - getSitListPageSize := int32(100) - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - getSiteListReq := &btwafsdk.GetSiteListRequest{ - SiteName: lo.ToPtr(d.config.SiteName), - Page: lo.ToPtr(getSitListPage), - PageSize: lo.ToPtr(getSitListPageSize), - } - getSiteListResp, err := d.sdkClient.GetSiteList(getSiteListReq) - d.logger.Debug("sdk request 'bt.GetSiteList'", slog.Any("request", getSiteListReq), slog.Any("response", getSiteListResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'bt.GetSiteList': %w", err) - } - - if getSiteListResp.Result != nil && getSiteListResp.Result.List != nil { - for _, siteItem := range getSiteListResp.Result.List { - if siteItem.SiteName == d.config.SiteName { - siteId = siteItem.SiteId - break - } - } - } - - if getSiteListResp.Result == nil || len(getSiteListResp.Result.List) < int(getSitListPageSize) { - break - } else { - getSitListPage++ - } - } - if siteId == "" { - return nil, errors.New("site not found") + // 获取网站 ID + siteId, err := d.findSiteIdByName(ctx, d.config.SiteName) + if err != nil { + return nil, err } // 修改站点配置 @@ -132,6 +96,48 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) findSiteIdByName(ctx context.Context, siteName string) (string, error) { + // 查询网站列表 + getSiteListPage := 1 + getSiteListPageSize := 100 + for { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + getSiteListReq := &btwafsdk.GetSiteListRequest{ + SiteName: lo.ToPtr(d.config.SiteName), + Page: lo.ToPtr(int32(getSiteListPage)), + PageSize: lo.ToPtr(int32(getSiteListPageSize)), + } + getSiteListResp, err := d.sdkClient.GetSiteList(getSiteListReq) + d.logger.Debug("sdk request 'bt.GetSiteList'", slog.Any("request", getSiteListReq), slog.Any("response", getSiteListResp)) + if err != nil { + return "", fmt.Errorf("failed to execute sdk request 'bt.GetSiteList': %w", err) + } + + if getSiteListResp.Result == nil { + break + } + + for _, siteItem := range getSiteListResp.Result.List { + if siteItem.SiteName == d.config.SiteName { + return siteItem.SiteId, nil + } + } + + if len(getSiteListResp.Result.List) < getSiteListPageSize { + break + } + + getSiteListPage++ + } + + return "", fmt.Errorf("could not find site '%s'", siteName) +} + func createSDKClient(serverUrl, apiKey string, skipTlsVerify bool) (*btwafsdk.Client, error) { client, err := btwafsdk.NewClient(serverUrl, apiKey) if err != nil { diff --git a/pkg/core/ssl-deployer/providers/bunny-cdn/bunny_cdn.go b/pkg/core/ssl-deployer/providers/bunny-cdn/bunny_cdn.go index a86803e40..0f03b7825 100644 --- a/pkg/core/ssl-deployer/providers/bunny-cdn/bunny_cdn.go +++ b/pkg/core/ssl-deployer/providers/bunny-cdn/bunny_cdn.go @@ -16,7 +16,7 @@ type SSLDeployerProviderConfig struct { ApiKey string `json:"apiKey"` // Bunny Pull Zone ID。 PullZoneId string `json:"pullZoneId"` - // Bunny CDN Hostname(支持泛域名)。 + // Bunny CDN Hostname。 Hostname string `json:"hostname"` } diff --git a/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go b/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go index b2c4dd5df..40a54380d 100644 --- a/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -8,9 +8,11 @@ import ( "strings" bpcdn "github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn" + bp "github.com/volcengine/volcengine-go-sdk/volcengine" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/byteplus-cdn" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -18,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKey string `json:"accessKey"` // BytePlus SecretKey。 SecretKey string `json:"secretKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -75,61 +80,63 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 domains := make([]string, 0) - if strings.HasPrefix(d.config.Domain, "*.") { - // 获取指定证书可关联的域名 - // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-describecertconfig-9ea17 - describeCertConfigReq := &bpcdn.DescribeCertConfigRequest{ - CertId: upres.CertId, - } - describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq) - d.logger.Debug("sdk request 'cdn.DescribeCertConfig'", slog.Any("request", describeCertConfigReq), slog.Any("response", describeCertConfigResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeCertConfig': %w", err) - } - - if describeCertConfigResp.Result.CertNotConfig != nil { - for i := range describeCertConfigResp.Result.CertNotConfig { - domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain) + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") } + + domains = []string{d.config.Domain} } - if describeCertConfigResp.Result.OtherCertConfig != nil { - for i := range describeCertConfigResp.Result.OtherCertConfig { - domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain) + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") } - } - if len(domains) == 0 { - if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 { - // 所有可关联的域名都配置了该证书,跳过部署 - d.logger.Info("no domains to deploy") + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + if err != nil { + return nil, err + } + + domains = domainCandidates } else { - return nil, errors.New("domain not found") + domains = []string{d.config.Domain} } } - } else { - domains = append(domains, d.config.Domain) + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + domainCandidates, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + if err != nil { + return nil, err + } + + domains = domainCandidates + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - if len(domains) > 0 { + // 遍历绑定证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) var errs []error for _, domain := range domains { select { case <-ctx.Done(): return nil, ctx.Err() - default: - // 关联证书与加速域名 - // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert - batchDeployCertReq := &bpcdn.BatchDeployCertRequest{ - CertId: upres.CertId, - Domain: domain, - } - batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) - d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) - if err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -142,3 +149,96 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } + +func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { + domains := make([]string, 0) + + // 查询加速域名列表,获取匹配的域名 + // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/ListCdnDomains_en-us + listCdnDomainsPageNum := 1 + listCdnDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listCdnDomainsReq := &bpcdn.ListCdnDomainsRequest{ + Domain: bp.String(strings.TrimPrefix(wildcardDomain, "*.")), + Status: bp.String("online"), + PageNum: bp.Int64(int64(listCdnDomainsPageNum)), + PageSize: bp.Int64(int64(listCdnDomainsPageSize)), + } + listCdnDomainsResp, err := d.sdkClient.ListCdnDomains(listCdnDomainsReq) + d.logger.Debug("sdk request 'cdn.ListCdnDomains'", slog.Any("request", listCdnDomainsReq), slog.Any("response", listCdnDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCdnDomains': %w", err) + } + + for _, domainItem := range listCdnDomainsResp.Result.Data { + if xcerthostname.IsMatch(wildcardDomain, domainItem.Domain) { + domains = append(domains, domainItem.Domain) + } + } + + if len(listCdnDomainsResp.Result.Data) < listCdnDomainsPageSize { + break + } + + listCdnDomainsPageSize++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, cloudCertId string) ([]string, error) { + domains := make([]string, 0) + + // 获取指定证书可关联的域名 + // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-describecertconfig-9ea17 + describeCertConfigReq := &bpcdn.DescribeCertConfigRequest{ + CertId: cloudCertId, + } + describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq) + d.logger.Debug("sdk request 'cdn.DescribeCertConfig'", slog.Any("request", describeCertConfigReq), slog.Any("response", describeCertConfigResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeCertConfig': %w", err) + } + + if describeCertConfigResp.Result.CertNotConfig != nil { + for i := range describeCertConfigResp.Result.CertNotConfig { + domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain) + } + } + + if describeCertConfigResp.Result.OtherCertConfig != nil { + for i := range describeCertConfigResp.Result.OtherCertConfig { + domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain) + } + } + + if len(domains) == 0 { + if len(describeCertConfigResp.Result.SpecifiedCertConfig) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 关联证书与加速域名 + // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert + batchDeployCertReq := &bpcdn.BatchDeployCertRequest{ + CertId: cloudCertId, + Domain: domain, + } + batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) + d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.BatchDeployCert': %w", err) + } + + return nil +} diff --git a/pkg/core/ssl-deployer/providers/byteplus-cdn/consts.go b/pkg/core/ssl-deployer/providers/byteplus-cdn/consts.go new file mode 100644 index 000000000..81c2dab50 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/byteplus-cdn/consts.go @@ -0,0 +1,10 @@ +package bytepluscdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/consts.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/consts.go new file mode 100644 index 000000000..4ad94ec53 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/consts.go @@ -0,0 +1,10 @@ +package ctcccloudao + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go index 6dd6c8107..91b5fe11d 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go @@ -6,12 +6,15 @@ import ( "fmt" "log/slog" "strconv" + "strings" "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-ao" ctyunao "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/ao" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -19,6 +22,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 天翼云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -67,10 +73,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -79,22 +81,156 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no accessone domains to deploy") + } else { + d.logger.Info("found accessone domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertName); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13816&data=174&isNormal=1&vid=167 + queryDomainsPage := 1 + queryDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + queryDomainsReq := &ctyunao.QueryDomainsRequest{ + Page: lo.ToPtr(int32(queryDomainsPage)), + PageSize: lo.ToPtr(int32(queryDomainsPageSize)), + ProductCode: lo.ToPtr("020"), + } + queryDomainsResp, err := d.sdkClient.QueryDomains(queryDomainsReq) + d.logger.Debug("sdk request 'cdn.QueryDomains'", slog.Any("request", queryDomainsReq), slog.Any("response", queryDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomains': %w", err) + } + + if queryDomainsResp.ReturnObj == nil { + break + } + + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainItem := range queryDomainsResp.ReturnObj.Results { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(queryDomainsResp.ReturnObj.Results) < queryDomainsPageSize { + break + } + + queryDomainsPage++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertName string) error { // 域名基础及加速配置查询 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13412&data=174&isNormal=1&vid=167 getDomainConfigReq := &ctyunao.GetDomainConfigRequest{ - Domain: lo.ToPtr(d.config.Domain), + Domain: lo.ToPtr(domain), ProductCode: lo.ToPtr("020"), } getDomainConfigResp, err := d.sdkClient.GetDomainConfig(getDomainConfigReq) d.logger.Debug("sdk request 'cdn.GetDomainConfig'", slog.Any("request", getDomainConfigReq), slog.Any("response", getDomainConfigResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainConfig': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.GetDomainConfig': %w", err) } // 域名基础及加速配置修改 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13413&data=174&isNormal=1&vid=167 modifyDomainConfigReq := &ctyunao.ModifyDomainConfigRequest{ - Domain: lo.ToPtr(d.config.Domain), + Domain: lo.ToPtr(domain), ProductCode: lo.ToPtr(getDomainConfigResp.ReturnObj.ProductCode), Origin: lo.Map(getDomainConfigResp.ReturnObj.Origin, func(item *ctyunao.DomainOriginConfigWithWeight, _ int) *ctyunao.DomainOriginConfig { weight := item.Weight @@ -108,15 +244,15 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } }), HttpsStatus: lo.ToPtr("on"), - CertName: lo.ToPtr(upres.CertName), + CertName: lo.ToPtr(cloudCertName), } modifyDomainConfigResp, err := d.sdkClient.ModifyDomainConfig(modifyDomainConfigReq) d.logger.Debug("sdk request 'cdn.ModifyDomainConfig'", slog.Any("request", modifyDomainConfigReq), slog.Any("response", modifyDomainConfigResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainConfig': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainConfig': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunao.Client, error) { diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao_test.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao_test.go index 3fc1413f8..7f0b0f4cb 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao_test.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/consts.go new file mode 100644 index 000000000..2999eb8d7 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/consts.go @@ -0,0 +1,10 @@ +package ctcccloudcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go index 687c3ec30..dbf90e70e 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go @@ -5,12 +5,15 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-cdn" ctyuncdn "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/cdn" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -18,6 +21,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 天翼云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -66,10 +72,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -78,33 +80,171 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 查询域名配置信息 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11304&data=161&isNormal=1&vid=154 - queryDomainDetailReq := &ctyuncdn.QueryDomainDetailRequest{ - Domain: lo.ToPtr(d.config.Domain), - } - queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) - d.logger.Debug("sdk request 'cdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainDetail': %w", err) + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 修改域名配置 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11308&data=161&isNormal=1&vid=154 - updateDomainReq := &ctyuncdn.UpdateDomainRequest{ - Domain: lo.ToPtr(d.config.Domain), - HttpsStatus: lo.ToPtr("on"), - CertName: lo.ToPtr(upres.CertName), - } - updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) - d.logger.Debug("sdk request 'cdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.UpdateDomain': %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertName); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11307&data=161&isNormal=1&vid=154 + queryDomainListPage := 1 + queryDomainListPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + queryDomainListReq := &ctyuncdn.QueryDomainListRequest{ + Page: lo.ToPtr(int32(queryDomainListPage)), + PageSize: lo.ToPtr(int32(queryDomainListPageSize)), + ProductCode: lo.ToPtr("020"), + } + queryDomainListResp, err := d.sdkClient.QueryDomainList(queryDomainListReq) + d.logger.Debug("sdk request 'cdn.QueryDomainList'", slog.Any("request", queryDomainListReq), slog.Any("response", queryDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainList': %w", err) + } + + if queryDomainListResp.ReturnObj == nil { + break + } + + filteredProductCodes := []string{"001", "003", "004", "008"} + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainItem := range queryDomainListResp.ReturnObj.Results { + if !lo.Contains(filteredProductCodes, domainItem.ProductCode) { + continue + } + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(queryDomainListResp.ReturnObj.Results) < queryDomainListPageSize { + break + } + + queryDomainListPage++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertName string) error { + // 查询域名配置信息 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11304&data=161&isNormal=1&vid=154 + queryDomainDetailReq := &ctyuncdn.QueryDomainDetailRequest{ + Domain: lo.ToPtr(domain), + } + queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) + d.logger.Debug("sdk request 'cdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainDetail': %w", err) + } + + // 修改域名配置 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11308&data=161&isNormal=1&vid=154 + updateDomainReq := &ctyuncdn.UpdateDomainRequest{ + Domain: lo.ToPtr(domain), + HttpsStatus: lo.ToPtr("on"), + CertName: lo.ToPtr(cloudCertName), + } + updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) + d.logger.Debug("sdk request 'cdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.UpdateDomain': %w", err) + } + + return nil +} + func createSDKClient(accessKeyId, secretAccessKey string) (*ctyuncdn.Client, error) { return ctyuncdn.NewClient(accessKeyId, secretAccessKey) } diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn_test.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn_test.go index de706e606..41e082f8a 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-elb/ctcccloud_elb.go b/pkg/core/ssl-deployer/providers/ctcccloud-elb/ctcccloud_elb.go index 805255cbb..380af98be 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-elb/ctcccloud_elb.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-elb/ctcccloud_elb.go @@ -111,13 +111,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询监听列表 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=24&api=5654&data=88&isNormal=1&vid=82 listenerIds := make([]string, 0) - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - + { listListenersReq := &ctyunelb.ListListenersRequest{ RegionID: lo.ToPtr(d.config.RegionId), LoadBalancerID: lo.ToPtr(d.config.LoadbalancerId), @@ -133,8 +127,6 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer listenerIds = append(listenerIds, listener.ID) } } - - break } // 遍历更新监听证书 diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/consts.go b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/consts.go new file mode 100644 index 000000000..0ccc5ad71 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/consts.go @@ -0,0 +1,10 @@ +package ctcccloudicdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go index ad589ce08..3b2107b3b 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go @@ -5,12 +5,15 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-icdn" ctyunicdn "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/icdn" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -18,6 +21,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 天翼云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -66,10 +72,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -78,33 +80,167 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 查询域名配置信息 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10849&data=173&isNormal=1&vid=166 - queryDomainDetailReq := &ctyunicdn.QueryDomainDetailRequest{ - Domain: lo.ToPtr(d.config.Domain), - } - queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) - d.logger.Debug("sdk request 'icdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryDomainDetail': %w", err) + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 修改域名配置 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10853&data=173&isNormal=1&vid=166 - updateDomainReq := &ctyunicdn.UpdateDomainRequest{ - Domain: lo.ToPtr(d.config.Domain), - HttpsStatus: lo.ToPtr("on"), - CertName: lo.ToPtr(upres.CertName), - } - updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) - d.logger.Debug("sdk request 'icdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'icdn.UpdateDomain': %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no icdn domains to deploy") + } else { + d.logger.Info("found icdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertName); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10852&data=173&isNormal=1&vid=166 + queryDomainsPage := 1 + queryDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + queryDomainListReq := &ctyunicdn.QueryDomainListRequest{ + Page: lo.ToPtr(int32(queryDomainsPage)), + PageSize: lo.ToPtr(int32(queryDomainsPageSize)), + ProductCode: lo.ToPtr("006"), + } + queryDomainListResp, err := d.sdkClient.QueryDomainList(queryDomainListReq) + d.logger.Debug("sdk request 'cdn.QueryDomainList'", slog.Any("request", queryDomainListReq), slog.Any("response", queryDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainList': %w", err) + } + + if queryDomainListResp.ReturnObj == nil { + break + } + + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainItem := range queryDomainListResp.ReturnObj.Results { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(queryDomainListResp.ReturnObj.Results) < queryDomainsPageSize { + break + } + + queryDomainsPage++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertName string) error { + // 查询域名配置信息 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10849&data=173&isNormal=1&vid=166 + queryDomainDetailReq := &ctyunicdn.QueryDomainDetailRequest{ + Domain: lo.ToPtr(domain), + } + queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) + d.logger.Debug("sdk request 'icdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'icdn.QueryDomainDetail': %w", err) + } + + // 修改域名配置 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10853&data=173&isNormal=1&vid=166 + updateDomainReq := &ctyunicdn.UpdateDomainRequest{ + Domain: lo.ToPtr(domain), + HttpsStatus: lo.ToPtr("on"), + CertName: lo.ToPtr(cloudCertName), + } + updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) + d.logger.Debug("sdk request 'icdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'icdn.UpdateDomain': %w", err) + } + + return nil +} + func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunicdn.Client, error) { return ctyunicdn.NewClient(accessKeyId, secretAccessKey) } diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn_test.go b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn_test.go index 8c7665dfa..b7544a85d 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn_test.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/consts.go b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/consts.go new file mode 100644 index 000000000..cb9a08634 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/consts.go @@ -0,0 +1,8 @@ +package ctcccloudlvdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go index 971796e0f..31146541b 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go @@ -11,6 +11,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-lvdn" ctyunlvdn "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/lvdn" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -18,6 +19,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 天翼云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -66,10 +70,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -78,35 +78,146 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 查询域名配置信息 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11473&data=183&isNormal=1&vid=261 - queryDomainDetailReq := &ctyunlvdn.QueryDomainDetailRequest{ - Domain: lo.ToPtr(d.config.Domain), - ProductCode: lo.ToPtr("005"), - } - queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) - d.logger.Debug("sdk request 'lvdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryDomainDetail': %w", err) + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 修改域名配置 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11308&data=161&isNormal=1&vid=154 - updateDomainReq := &ctyunlvdn.UpdateDomainRequest{ - Domain: lo.ToPtr(d.config.Domain), - ProductCode: lo.ToPtr("005"), - HttpsSwitch: lo.ToPtr(int32(1)), - CertName: lo.ToPtr(upres.CertName), - } - updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) - d.logger.Debug("sdk request 'lvdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'lvdn.UpdateDomain': %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no lvdn domains to deploy") + } else { + d.logger.Info("found lvdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertName); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11559&data=183&isNormal=1&vid=261 + queryDomainsPage := 1 + queryDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + queryDomainListReq := &ctyunlvdn.QueryDomainListRequest{ + Page: lo.ToPtr(int32(queryDomainsPage)), + PageSize: lo.ToPtr(int32(queryDomainsPageSize)), + ProductCode: lo.ToPtr("005"), + } + queryDomainListResp, err := d.sdkClient.QueryDomainList(queryDomainListReq) + d.logger.Debug("sdk request 'cdn.QueryDomainList'", slog.Any("request", queryDomainListReq), slog.Any("response", queryDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainList': %w", err) + } + + if queryDomainListResp.ReturnObj == nil { + break + } + + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainItem := range queryDomainListResp.ReturnObj.Results { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(queryDomainListResp.ReturnObj.Results) < queryDomainsPageSize { + break + } + + queryDomainsPage++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertName string) error { + // 查询域名配置信息 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11473&data=183&isNormal=1&vid=261 + queryDomainDetailReq := &ctyunlvdn.QueryDomainDetailRequest{ + Domain: lo.ToPtr(domain), + ProductCode: lo.ToPtr("005"), + } + queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) + d.logger.Debug("sdk request 'lvdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'lvdn.QueryDomainDetail': %w", err) + } + + // 修改域名配置 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11308&data=161&isNormal=1&vid=154 + updateDomainReq := &ctyunlvdn.UpdateDomainRequest{ + Domain: lo.ToPtr(domain), + ProductCode: lo.ToPtr("005"), + HttpsSwitch: lo.ToPtr(int32(1)), + CertName: lo.ToPtr(cloudCertName), + } + updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) + d.logger.Debug("sdk request 'lvdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'lvdn.UpdateDomain': %w", err) + } + + return nil +} + func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunlvdn.Client, error) { return ctyunlvdn.NewClient(accessKeyId, secretAccessKey) } diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn_test.go b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn_test.go index 0dcab6d5b..34b1eb273 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn_test.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/dogecloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/dogecloud-cdn/consts.go new file mode 100644 index 000000000..c468db8d8 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/dogecloud-cdn/consts.go @@ -0,0 +1,8 @@ +package dogecloudcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/dogecloud-cdn/dogecloud_cdn.go b/pkg/core/ssl-deployer/providers/dogecloud-cdn/dogecloud_cdn.go index 4681f2a35..e756710c9 100644 --- a/pkg/core/ssl-deployer/providers/dogecloud-cdn/dogecloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/dogecloud-cdn/dogecloud_cdn.go @@ -7,9 +7,12 @@ import ( "log/slog" "strconv" + "github.com/samber/lo" + "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/dogecloud" dogesdk "github.com/certimate-go/certimate/pkg/sdk3rd/dogecloud" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -17,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKey string `json:"accessKey"` // 多吉云 SecretKey。 SecretKey string `json:"secretKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -67,10 +73,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, fmt.Errorf("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -79,22 +81,110 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 绑定证书 - // REF: https://docs.dogecloud.com/cdn/api-cert-bind - bindCdnCertId, _ := strconv.ParseInt(upres.CertId, 10, 64) - bindCdnCertReq := &dogesdk.BindCdnCertRequest{ - CertId: bindCdnCertId, - Domain: d.config.Domain, + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - bindCdnCertResp, err := d.sdkClient.BindCdnCert(bindCdnCertReq) - d.logger.Debug("sdk request 'cdn.BindCdnCert'", slog.Any("request", bindCdnCertReq), slog.Any("response", bindCdnCertResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.BindCdnCert': %w", err) + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + if err := d.updateDomainCertificate(ctx, domain, certId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 获取域名列表 + // REF: https://docs.dogecloud.com/cdn/api-domain-list + listCdnDomainResp, err := d.sdkClient.ListCdnDomain() + d.logger.Debug("sdk request 'cdn.ListCdnDomain'", slog.Any("response", listCdnDomainResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCdnDomain': %w", err) + } + + if listCdnDomainResp.Data != nil { + ignoredStatuses := []string{"offline"} + for _, domainItem := range listCdnDomainResp.Data.Domains { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Name) + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId int64) error { + // 绑定证书 + // REF: https://docs.dogecloud.com/cdn/api-cert-bind + bindCdnCertReq := &dogesdk.BindCdnCertRequest{ + CertId: cloudCertId, + Domain: domain, + } + bindCdnCertResp, err := d.sdkClient.BindCdnCert(bindCdnCertReq) + d.logger.Debug("sdk request 'cdn.BindCdnCert'", slog.Any("request", bindCdnCertReq), slog.Any("response", bindCdnCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.BindCdnCert': %w", err) + } + + return nil +} + func createSDKClient(accessKey, secretKey string) (*dogesdk.Client, error) { return dogesdk.NewClient(accessKey, secretKey) } diff --git a/pkg/core/ssl-deployer/providers/gcore-cdn/gcore_cdn.go b/pkg/core/ssl-deployer/providers/gcore-cdn/gcore_cdn.go index 92735e23c..cd43c755b 100644 --- a/pkg/core/ssl-deployer/providers/gcore-cdn/gcore_cdn.go +++ b/pkg/core/ssl-deployer/providers/gcore-cdn/gcore_cdn.go @@ -96,7 +96,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } else { // 获取证书 // REF: https://api.gcore.com/docs/cdn#tag/SSL-certificates/paths/~1cdn~1sslData~1%7Bssl_id%7D/get - getCertificateDetailResp, err := d.sdkClients.SSLCerts.Get(context.TODO(), d.config.CertificateId) + getCertificateDetailResp, err := d.sdkClients.SSLCerts.Get(ctx, d.config.CertificateId) d.logger.Debug("sdk request 'sslcerts.Get'", slog.Int64("sslId", d.config.CertificateId), slog.Any("response", getCertificateDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Get': %w", err) @@ -110,7 +110,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke PrivateKey: privkeyPEM, ValidateRootCA: false, } - changeCertificateResp, err := d.sdkClients.SSLCerts.Update(context.TODO(), getCertificateDetailResp.ID, changeCertificateReq) + changeCertificateResp, err := d.sdkClients.SSLCerts.Update(ctx, getCertificateDetailResp.ID, changeCertificateReq) d.logger.Debug("sdk request 'sslcerts.Update'", slog.Int64("sslId", getCertificateDetailResp.ID), slog.Any("request", changeCertificateReq), slog.Any("response", changeCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Update': %w", err) @@ -121,7 +121,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 获取 CDN 资源详情 // REF: https://api.gcore.com/docs/cdn#tag/CDN-resources/paths/~1cdn~1resources~1%7Bresource_id%7D/get - getResourceResp, err := d.sdkClients.Resources.Get(context.TODO(), d.config.ResourceId) + getResourceResp, err := d.sdkClients.Resources.Get(ctx, d.config.ResourceId) d.logger.Debug("sdk request 'resources.Get'", slog.Any("resourceId", d.config.ResourceId), slog.Any("response", getResourceResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'resources.Get': %w", err) @@ -146,7 +146,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if getResourceResp.ProxySSLData != 0 { updateResourceReq.ProxySSLData = &getResourceResp.ProxySSLData } - updateResourceResp, err := d.sdkClients.Resources.Update(context.TODO(), d.config.ResourceId, updateResourceReq) + updateResourceResp, err := d.sdkClients.Resources.Update(ctx, d.config.ResourceId, updateResourceReq) d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'resources.Update': %w", err) diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/consts.go new file mode 100644 index 000000000..65ac52c5c --- /dev/null +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/consts.go @@ -0,0 +1,10 @@ +package huaweicloudcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index e716d4b71..a88812443 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global" hccdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2" @@ -15,6 +16,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/huaweicloud-scm" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -26,6 +29,9 @@ type SSLDeployerProviderConfig struct { EnterpriseProjectId string `json:"enterpriseProjectId,omitempty"` // 华为云区域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -81,10 +87,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, fmt.Errorf("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -93,28 +95,162 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertId, upres.CertName); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://support.huaweicloud.com/api-cdn/ListDomains.html + listDomainsPageNumber := 1 + listDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainsReq := &hccdnmodel.ListDomainsRequest{ + EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), + PageNumber: lo.ToPtr(int32(listDomainsPageNumber)), + PageSize: lo.ToPtr(int32(listDomainsPageSize)), + } + listDomainsResp, err := d.sdkClient.ListDomains(listDomainsReq) + d.logger.Debug("sdk request 'cdn.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListDomains': %w", err) + } + + if listDomainsResp.Domains == nil { + break + } + + ignoredStatuses := []string{"offline", "checking", "check_failed", "deleting"} + for _, domainItem := range *listDomainsResp.Domains { + if lo.Contains(ignoredStatuses, lo.FromPtr(domainItem.DomainStatus)) { + continue + } + + domains = append(domains, lo.FromPtr(domainItem.DomainName)) + } + + if len(*listDomainsResp.Domains) < listDomainsPageSize { + break + } + + listDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { // 查询加速域名配置 // REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html showDomainFullConfigReq := &hccdnmodel.ShowDomainFullConfigRequest{ EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), - DomainName: d.config.Domain, + DomainName: domain, } showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq) d.logger.Debug("sdk request 'cdn.ShowDomainFullConfig'", slog.Any("request", showDomainFullConfigReq), slog.Any("response", showDomainFullConfigResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.ShowDomainFullConfig': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.ShowDomainFullConfig': %w", err) } // 更新加速域名配置 // REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html // REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html updateDomainMultiCertificatesReqBodyContent := &hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent{} - updateDomainMultiCertificatesReqBodyContent.DomainName = d.config.Domain + updateDomainMultiCertificatesReqBodyContent.DomainName = domain updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1 updateDomainMultiCertificatesReqBodyContent.CertificateType = lo.ToPtr(int32(2)) - updateDomainMultiCertificatesReqBodyContent.ScmCertificateId = lo.ToPtr(upres.CertId) - updateDomainMultiCertificatesReqBodyContent.CertName = lo.ToPtr(upres.CertName) - updateDomainMultiCertificatesReqBodyContent = assign(updateDomainMultiCertificatesReqBodyContent, showDomainFullConfigResp.Configs) + updateDomainMultiCertificatesReqBodyContent.ScmCertificateId = lo.ToPtr(cloudCertId) + updateDomainMultiCertificatesReqBodyContent.CertName = lo.ToPtr(cloudCertName) + updateDomainMultiCertificatesReqBodyContent = _assign(updateDomainMultiCertificatesReqBodyContent, showDomainFullConfigResp.Configs) updateDomainMultiCertificatesReq := &hccdnmodel.UpdateDomainMultiCertificatesRequest{ EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), Body: &hccdnmodel.UpdateDomainMultiCertificatesRequestBody{ @@ -124,10 +260,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke updateDomainMultiCertificatesResp, err := d.sdkClient.UpdateDomainMultiCertificates(updateDomainMultiCertificatesReq) d.logger.Debug("sdk request 'cdn.UpdateDomainMultiCertificates'", slog.Any("request", updateDomainMultiCertificatesReq), slog.Any("response", updateDomainMultiCertificatesResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.UpdateDomainMultiCertificates': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.UpdateDomainMultiCertificates': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, secretAccessKey, region string) (*internal.CdnClient, error) { @@ -160,7 +296,7 @@ func createSDKClient(accessKeyId, secretAccessKey, region string) (*internal.Cdn return client, nil } -func assign(source *hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent, target *hccdnmodel.ConfigsGetBody) *hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent { +func _assign(source *hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent, target *hccdnmodel.ConfigsGetBody) *hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent { // `UpdateDomainMultiCertificates` 中不传的字段表示使用默认值、而非保留原值, // 因此这里需要把原配置中的参数重新赋值回去。 diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go index fa4191bed..236db7e45 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal/client.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal/client.go index f9f5f0406..fc8366f9b 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal/client.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal/client.go @@ -16,6 +16,16 @@ func NewCdnClient(hcClient *httpclient.HcHttpClient) *CdnClient { return &CdnClient{HcClient: hcClient} } +func (c *CdnClient) ListDomains(request *model.ListDomainsRequest) (*model.ListDomainsResponse, error) { + requestDef := hwcdn.GenReqDefForListDomains() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*model.ListDomainsResponse), nil + } +} + func (c *CdnClient) ShowDomainFullConfig(request *model.ShowDomainFullConfigRequest) (*model.ShowDomainFullConfigResponse, error) { requestDef := hwcdn.GenReqDefForShowDomainFullConfig() diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go index f324e9e0d..93b37448c 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -160,8 +160,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM // 查询监听器列表 // REF: https://support.huaweicloud.com/api-elb/ListListeners.html listenerIds := make([]string, 0) - listListenersLimit := int32(2000) - var listListenersMarker *string = nil + listListenersMarker := (*string)(nil) for { select { case <-ctx.Done(): @@ -170,8 +169,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM } listListenersReq := &hcelbmodel.ListListenersRequest{ - Limit: lo.ToPtr(listListenersLimit), Marker: listListenersMarker, + Limit: lo.ToPtr(int32(2000)), Protocol: &[]string{"HTTPS", "TERMINATED_HTTPS"}, LoadbalancerId: &[]string{showLoadBalancerResp.Loadbalancer.Id}, } @@ -184,17 +183,19 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM return fmt.Errorf("failed to execute sdk request 'elb.ListListeners': %w", err) } - if listListenersResp.Listeners != nil { - for _, listener := range *listListenersResp.Listeners { - listenerIds = append(listenerIds, listener.Id) - } + if listListenersResp.Listeners == nil { + break } - if listListenersResp.Listeners == nil || len(*listListenersResp.Listeners) < int(listListenersLimit) { - break - } else { - listListenersMarker = listListenersResp.PageInfo.NextMarker + for _, listener := range *listListenersResp.Listeners { + listenerIds = append(listenerIds, listener.Id) } + + if len(*listListenersResp.Listeners) == 0 || listListenersResp.PageInfo.NextMarker == nil { + break + } + + listListenersMarker = listListenersResp.PageInfo.NextMarker } // 上传证书 @@ -217,7 +218,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM case <-ctx.Done(): return ctx.Err() default: - if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { + if err := d.updateListenerCertificate(ctx, listenerId, upres.CertId); err != nil { errs = append(errs, err) } } @@ -245,14 +246,14 @@ func (d *SSLDeployerProvider) deployToListener(ctx context.Context, certPEM stri } // 更新监听器证书 - if err := d.modifyListenerCertificate(ctx, d.config.ListenerId, upres.CertId); err != nil { + if err := d.updateListenerCertificate(ctx, d.config.ListenerId, upres.CertId); err != nil { return err } return nil } -func (d *SSLDeployerProvider) modifyListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { // 查询监听器详情 // REF: https://support.huaweicloud.com/api-elb/ShowListener.html showListenerReq := &hcelbmodel.ShowListenerRequest{ diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go b/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go index d7e932fbb..38f13e09f 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go @@ -171,11 +171,11 @@ func (d *SSLDeployerProvider) deployToCloudServer(ctx context.Context, certPEM s d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 遍历查询云模式防护域名列表,获取防护域名 ID + // 查询云模式防护域名列表,获取防护域名 ID // REF: https://support.huaweicloud.com/api-waf/ListHost.html hostId := "" - listHostPage := int32(1) - listHostPageSize := int32(100) + listHostPage := 1 + listHostPageSize := 100 for { select { case <-ctx.Done(): @@ -186,8 +186,8 @@ func (d *SSLDeployerProvider) deployToCloudServer(ctx context.Context, certPEM s listHostReq := &hcwafmodel.ListHostRequest{ EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), Hostname: lo.ToPtr(strings.TrimPrefix(d.config.Domain, "*")), - Page: lo.ToPtr(listHostPage), - Pagesize: lo.ToPtr(listHostPageSize), + Page: lo.ToPtr(int32(listHostPage)), + Pagesize: lo.ToPtr(int32(listHostPageSize)), } listHostResp, err := d.sdkClient.ListHost(listHostReq) d.logger.Debug("sdk request 'waf.ListHost'", slog.Any("request", listHostReq), slog.Any("response", listHostResp)) @@ -195,23 +195,25 @@ func (d *SSLDeployerProvider) deployToCloudServer(ctx context.Context, certPEM s return fmt.Errorf("failed to execute sdk request 'waf.ListHost': %w", err) } - if listHostResp.Items != nil { - for _, hostItem := range *listHostResp.Items { - if strings.TrimPrefix(d.config.Domain, "*") == *hostItem.Hostname { - hostId = *hostItem.Id - break - } + if listHostResp.Items == nil { + break + } + + for _, hostItem := range *listHostResp.Items { + if strings.TrimPrefix(d.config.Domain, "*") == *hostItem.Hostname { + hostId = *hostItem.Id + break } } - if listHostResp.Items == nil || len(*listHostResp.Items) < int(listHostPageSize) { + if len(*listHostResp.Items) < listHostPageSize { break - } else { - listHostPage++ } + + listHostPage++ } if hostId == "" { - return errors.New("host not found") + return fmt.Errorf("could not find cloudserver host '%s'", d.config.Domain) } // 更新云模式防护域名的配置 @@ -246,11 +248,11 @@ func (d *SSLDeployerProvider) deployToPremiumHost(ctx context.Context, certPEM s d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 遍历查询独享模式域名列表,获取防护域名 ID + // 查询独享模式域名列表,获取防护域名 ID // REF: https://support.huaweicloud.com/api-waf/ListPremiumHost.html hostId := "" - listPremiumHostPage := int32(1) - listPremiumHostPageSize := int32(100) + listPremiumHostPage := 1 + listPremiumHostPageSize := 100 for { select { case <-ctx.Done(): @@ -270,23 +272,25 @@ func (d *SSLDeployerProvider) deployToPremiumHost(ctx context.Context, certPEM s return fmt.Errorf("failed to execute sdk request 'waf.ListPremiumHost': %w", err) } - if listPremiumHostResp.Items != nil { - for _, hostItem := range *listPremiumHostResp.Items { - if strings.TrimPrefix(d.config.Domain, "*") == *hostItem.Hostname { - hostId = *hostItem.Id - break - } + if listPremiumHostResp.Items == nil { + break + } + + for _, hostItem := range *listPremiumHostResp.Items { + if strings.TrimPrefix(d.config.Domain, "*") == *hostItem.Hostname { + hostId = *hostItem.Id + break } } - if listPremiumHostResp.Items == nil || len(*listPremiumHostResp.Items) < int(listPremiumHostPageSize) { + if len(*listPremiumHostResp.Items) < listPremiumHostPageSize { break - } else { - listPremiumHostPage++ } + + listPremiumHostPage++ } if hostId == "" { - return errors.New("host not found") + return fmt.Errorf("could not find premium host '%s'", d.config.Domain) } // 修改独享模式域名配置 diff --git a/pkg/core/ssl-deployer/providers/jdcloud-alb/jdcloud_alb.go b/pkg/core/ssl-deployer/providers/jdcloud-alb/jdcloud_alb.go index b69d2cf86..c4e7f534d 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-alb/jdcloud_alb.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-alb/jdcloud_alb.go @@ -156,11 +156,11 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer } } - if len(describeListenersResp.Result.Listeners) < int(describeListenersPageSize) { + if len(describeListenersResp.Result.Listeners) < describeListenersPageSize { break - } else { - describeListenersPageNumber++ } + + describeListenersPageNumber++ } // 遍历更新监听器证书 @@ -236,7 +236,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo return extCertSpec.Domain == d.config.Domain }) if len(extCertSpecs) == 0 { - return errors.New("extension certificate spec not found") + return errors.New("could not find any extension certificates") } // 批量修改扩展证书 diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/consts.go new file mode 100644 index 000000000..9d7f0a707 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/consts.go @@ -0,0 +1,10 @@ +package jdcloudcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal/client.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal/client.go index 44d4971da..47ebd9465 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal/client.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal/client.go @@ -33,6 +33,26 @@ func NewCdnClient(credential *core.Credential) *CdnClient { } } +func (c *CdnClient) GetDomainList(request *cdn.GetDomainListRequest) (*cdn.GetDomainListResponse, error) { + if request == nil { + return nil, errors.New("Request object is nil.") + } + + resp, err := c.Send(request, c.ServiceName) + if err != nil { + return nil, err + } + + jdResp := &cdn.GetDomainListResponse{} + err = json.Unmarshal(resp, jdResp) + if err != nil { + c.Logger.Log(core.LogError, "Unmarshal json failed, resp: %s", string(resp)) + return nil, err + } + + return jdResp, err +} + func (c *CdnClient) QueryDomainConfig(request *cdn.QueryDomainConfigRequest) (*cdn.QueryDomainConfigResponse, error) { if request == nil { return nil, errors.New("Request object is nil.") diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go index 64866b1c0..541d46f2c 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go @@ -5,13 +5,17 @@ import ( "errors" "fmt" "log/slog" + "strings" jdcore "github.com/jdcloud-api/jdcloud-sdk-go/core" jdcdn "github.com/jdcloud-api/jdcloud-sdk-go/services/cdn/apis" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/jdcloud-ssl" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -19,6 +23,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 京东云 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -69,20 +76,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, fmt.Errorf("config `domain` is required") - } - - // 查询域名配置信息 - // REF: https://docs.jdcloud.com/cn/cdn/api/querydomainconfig - queryDomainConfigReq := jdcdn.NewQueryDomainConfigRequestWithoutParam() - queryDomainConfigReq.SetDomain(d.config.Domain) - queryDomainConfigResp, err := d.sdkClient.QueryDomainConfig(queryDomainConfigReq) - d.logger.Debug("sdk request 'cdn.QueryDomainConfig'", slog.Any("request", queryDomainConfigReq), slog.Any("response", queryDomainConfigResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainConfig': %w", err) - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -91,23 +84,159 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://docs.jdcloud.com/cn/cdn/api/getdomainlist + getDomainListPageNumber := 1 + getDomainListPageSize := 50 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + getDomainListReq := jdcdn.NewGetDomainListRequestWithoutParam() + getDomainListReq.SetPageNumber(getDomainListPageNumber) + getDomainListReq.SetPageSize(getDomainListPageSize) + getDomainListResp, err := d.sdkClient.GetDomainList(getDomainListReq) + d.logger.Debug("sdk request 'cdn.GetDomainList'", slog.Any("request", getDomainListReq), slog.Any("response", getDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainList': %w", err) + } + + ignoredStatuses := []string{"offline"} + for _, domainItem := range getDomainListResp.Result.Domains { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(getDomainListResp.Result.Domains) < getDomainListPageSize { + break + } + + getDomainListPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 查询域名配置信息 + // REF: https://docs.jdcloud.com/cn/cdn/api/querydomainconfig + queryDomainConfigReq := jdcdn.NewQueryDomainConfigRequestWithoutParam() + queryDomainConfigReq.SetDomain(domain) + queryDomainConfigResp, err := d.sdkClient.QueryDomainConfig(queryDomainConfigReq) + d.logger.Debug("sdk request 'cdn.QueryDomainConfig'", slog.Any("request", queryDomainConfigReq), slog.Any("response", queryDomainConfigResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainConfig': %w", err) + } + // 设置通讯协议 // REF: https://docs.jdcloud.com/cn/cdn/api/sethttptype setHttpTypeReq := jdcdn.NewSetHttpTypeRequestWithoutParam() - setHttpTypeReq.SetDomain(d.config.Domain) + setHttpTypeReq.SetDomain(domain) setHttpTypeReq.SetHttpType("https") - setHttpTypeReq.SetCertificate(certPEM) - setHttpTypeReq.SetRsaKey(privkeyPEM) setHttpTypeReq.SetCertFrom("ssl") - setHttpTypeReq.SetSslCertId(upres.CertId) + setHttpTypeReq.SetSslCertId(cloudCertId) setHttpTypeReq.SetJumpType(queryDomainConfigResp.Result.HttpsJumpType) setHttpTypeResp, err := d.sdkClient.SetHttpType(setHttpTypeReq) d.logger.Debug("sdk request 'cdn.QueryDomainConfig'", slog.Any("request", setHttpTypeReq), slog.Any("response", setHttpTypeResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.SetHttpType': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.SetHttpType': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.CdnClient, error) { diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn_test.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn_test.go index 2ecd406b9..af85d5e05 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-live/consts.go b/pkg/core/ssl-deployer/providers/jdcloud-live/consts.go new file mode 100644 index 000000000..854b44fab --- /dev/null +++ b/pkg/core/ssl-deployer/providers/jdcloud-live/consts.go @@ -0,0 +1,8 @@ +package jdcloudlive + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-live/internal/client.go b/pkg/core/ssl-deployer/providers/jdcloud-live/internal/client.go index bdbc6044c..76479d1bf 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-live/internal/client.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-live/internal/client.go @@ -33,6 +33,25 @@ func NewLiveClient(credential *core.Credential) *LiveClient { } } +func (c *LiveClient) DescribeLiveDomains(request *live.DescribeLiveDomainsRequest) (*live.DescribeLiveDomainsResponse, error) { + if request == nil { + return nil, errors.New("Request object is nil.") + } + resp, err := c.Send(request, c.ServiceName) + if err != nil { + return nil, err + } + + jdResp := &live.DescribeLiveDomainsResponse{} + err = json.Unmarshal(resp, jdResp) + if err != nil { + c.Logger.Log(core.LogError, "Unmarshal json failed, resp: %s", string(resp)) + return nil, err + } + + return jdResp, err +} + func (c *LiveClient) SetLiveDomainCertificate(request *live.SetLiveDomainCertificateRequest) (*live.SetLiveDomainCertificateResponse, error) { if request == nil { return nil, errors.New("Request object is nil.") diff --git a/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live.go b/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live.go index ae2da9acc..2dcaa0c83 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live.go @@ -9,8 +9,10 @@ import ( "github.com/certimate-go/certimate/pkg/core" jdcore "github.com/jdcloud-api/jdcloud-sdk-go/core" jdlive "github.com/jdcloud-api/jdcloud-sdk-go/services/live/apis" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/jdcloud-live/internal" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -18,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 京东云 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 直播播放域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -56,24 +61,127 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, fmt.Errorf("config `domain` is required") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no live domains to deploy") + } else { + d.logger.Info("found live domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://docs.jdcloud.com/cn/live-video/api/describelivedomains + describeLiveDomainsPageNumber := 1 + describeLiveDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeLiveDomainsReq := jdlive.NewDescribeLiveDomainsRequestWithoutParam() + describeLiveDomainsReq.SetPageNum(describeLiveDomainsPageNumber) + describeLiveDomainsReq.SetPageSize(describeLiveDomainsPageSize) + describeLiveDomainsResp, err := d.sdkClient.DescribeLiveDomains(describeLiveDomainsReq) + d.logger.Debug("sdk request 'live.DescribeLiveDomainsRequest'", slog.Any("request", describeLiveDomainsReq), slog.Any("response", describeLiveDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'live.DescribeLiveDomainsRequest': %w", err) + } + + ignoredStatuses := []string{"offline", "checking", "check_failed"} + for _, domainItem := range describeLiveDomainsResp.Result.DomainDetails { + for _, playDomainItem := range domainItem.PlayDomains { + if lo.Contains(ignoredStatuses, playDomainItem.DomainStatus) { + continue + } + + domains = append(domains, playDomainItem.PlayDomain) + } + } + + if len(describeLiveDomainsResp.Result.DomainDetails) < describeLiveDomainsPageSize { + break + } + + describeLiveDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 设置直播证书 // REF: https://docs.jdcloud.com/cn/live-video/api/setlivedomaincertificate setLiveDomainCertificateReq := jdlive.NewSetLiveDomainCertificateRequestWithoutParam() - setLiveDomainCertificateReq.SetPlayDomain(d.config.Domain) + setLiveDomainCertificateReq.SetPlayDomain(domain) setLiveDomainCertificateReq.SetCertStatus("on") setLiveDomainCertificateReq.SetCert(certPEM) setLiveDomainCertificateReq.SetKey(privkeyPEM) setLiveDomainCertificateResp, err := d.sdkClient.SetLiveDomainCertificate(setLiveDomainCertificateReq) d.logger.Debug("sdk request 'live.SetLiveDomainCertificate'", slog.Any("request", setLiveDomainCertificateReq), slog.Any("response", setLiveDomainCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.LiveClient, error) { diff --git a/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live_test.go b/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live_test.go index 4853dde5e..fa7179e32 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live_test.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/consts.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/consts.go new file mode 100644 index 000000000..2ccb60447 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/consts.go @@ -0,0 +1,8 @@ +package jdcloudvod + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go index 82d90eb2f..c1462c685 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go @@ -11,8 +11,10 @@ import ( "github.com/certimate-go/certimate/pkg/core" jdcore "github.com/jdcloud-api/jdcloud-sdk-go/core" jdvod "github.com/jdcloud-api/jdcloud-sdk-go/services/vod/apis" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/jdcloud-vod/internal" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -20,6 +22,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 京东云 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 点播加速域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -58,9 +63,73 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no vod domains to deploy") + } else { + d.logger.Info("found vod domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + // 查询域名列表 // REF: https://docs.jdcloud.com/cn/video-on-demand/api/listdomains - var domainId int listDomainsPageNumber := 1 listDomainsPageSize := 100 for { @@ -79,21 +148,30 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, fmt.Errorf("failed to execute sdk request 'vod.ListDomains': %w", err) } - for _, domain := range listDomainsResp.Result.Content { - if domain.Name == d.config.Domain { - domainId, _ = strconv.Atoi(domain.Id) - break + ignoredStatuses := []string{"init", "stopped"} + for _, domainItem := range listDomainsResp.Result.Content { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue } + + domains = append(domains, domainItem.Name) } if len(listDomainsResp.Result.Content) < listDomainsPageSize { break - } else { - listDomainsPageNumber++ } + + listDomainsPageNumber++ } - if domainId == 0 { - return nil, errors.New("domain not found") + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { + // 获取域名 ID + domainId, err := d.findDomainIdByDomain(ctx, domain) + if err != nil { + return err } // 查询域名 SSL 配置 @@ -103,7 +181,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke getHttpSslResp, err := d.sdkClient.GetHttpSsl(getHttpSslReq) d.logger.Debug("sdk request 'vod.GetHttpSsl'", slog.Any("request", getHttpSslReq), slog.Any("response", getHttpSslResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'vod.GetHttpSsl': %w", err) + return fmt.Errorf("failed to execute sdk request 'vod.GetHttpSsl': %w", err) } // 设置域名 SSL 配置 @@ -119,10 +197,48 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke setHttpSslResp, err := d.sdkClient.SetHttpSsl(setHttpSslReq) d.logger.Debug("sdk request 'vod.SetHttpSsl'", slog.Any("request", setHttpSslReq), slog.Any("response", setHttpSslResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'vod.SetHttpSsl': %w", err) + return fmt.Errorf("failed to execute sdk request 'vod.SetHttpSsl': %w", err) } - return &core.SSLDeployResult{}, nil + return nil +} + +func (d *SSLDeployerProvider) findDomainIdByDomain(ctx context.Context, domain string) (int, error) { + // 查询域名列表 + // REF: https://docs.jdcloud.com/cn/video-on-demand/api/listdomains + listDomainsPageNumber := 1 + listDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + + listDomainsReq := jdvod.NewListDomainsRequestWithoutParam() + listDomainsReq.SetPageNumber(listDomainsPageNumber) + listDomainsReq.SetPageSize(listDomainsPageSize) + listDomainsResp, err := d.sdkClient.ListDomains(listDomainsReq) + d.logger.Debug("sdk request 'vod.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return 0, fmt.Errorf("failed to execute sdk request 'vod.ListDomains': %w", err) + } + + for _, domainItem := range listDomainsResp.Result.Content { + if domainItem.Name == domain { + domainId, _ := strconv.Atoi(domainItem.Id) + return domainId, nil + } + } + + if len(listDomainsResp.Result.Content) < listDomainsPageSize { + break + } + + listDomainsPageNumber++ + } + + return 0, fmt.Errorf("could not find domain '%s'", domain) } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.VodClient, error) { diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod_test.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod_test.go index c09933202..dd910d7ab 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod_test.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go index 0387fd8f3..1a74602a2 100644 --- a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go +++ b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go @@ -112,7 +112,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } // 获取 Secret 实例,如果不存在则创建 - secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(context.TODO(), d.config.SecretName, k8smeta.GetOptions{}) + secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(ctx, d.config.SecretName, k8smeta.GetOptions{}) if err != nil { if !k8serrors.IsNotFound(err) { return nil, fmt.Errorf("failed to get kubernetes secret: %w", err) @@ -134,7 +134,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke secretPayload.Data[d.config.SecretDataKeyForCrt] = []byte(certPEM) secretPayload.Data[d.config.SecretDataKeyForKey] = []byte(privkeyPEM) - secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Create(context.TODO(), secretPayload, k8smeta.CreateOptions{}) + secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Create(ctx, secretPayload, k8smeta.CreateOptions{}) d.logger.Debug("kubernetes operate 'Secrets.Create'", slog.String("namespace", d.config.Namespace), slog.Any("secret", secretPayload)) if err != nil { return nil, fmt.Errorf("failed to create kubernetes secret: %w", err) @@ -164,7 +164,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } secretPayload.Data[d.config.SecretDataKeyForCrt] = []byte(certPEM) secretPayload.Data[d.config.SecretDataKeyForKey] = []byte(privkeyPEM) - secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Update(context.TODO(), secretPayload, k8smeta.UpdateOptions{}) + secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Update(ctx, secretPayload, k8smeta.UpdateOptions{}) d.logger.Debug("kubernetes operate 'Secrets.Update'", slog.String("namespace", d.config.Namespace), slog.Any("secret", secretPayload)) if err != nil { return nil, fmt.Errorf("failed to update kubernetes secret: %w", err) diff --git a/pkg/core/ssl-deployer/providers/kong/kong.go b/pkg/core/ssl-deployer/providers/kong/kong.go index 77511f980..8f659e27b 100644 --- a/pkg/core/ssl-deployer/providers/kong/kong.go +++ b/pkg/core/ssl-deployer/providers/kong/kong.go @@ -100,7 +100,7 @@ func (d *SSLDeployerProvider) deployToCertificate(ctx context.Context, certPEM s Key: kong.String(privkeyPEM), SNIs: kong.StringSlice(certX509.DNSNames...), } - updateCertificateResp, err := d.sdkClient.Certificates.Update(context.TODO(), updateCertificateReq) + updateCertificateResp, err := d.sdkClient.Certificates.Update(ctx, updateCertificateReq) d.logger.Debug("sdk request 'kong.UpdateCertificate'", slog.String("sslId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'kong.UpdateCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/ksyun-cdn/consts.go b/pkg/core/ssl-deployer/providers/ksyun-cdn/consts.go new file mode 100644 index 000000000..950343558 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/consts.go @@ -0,0 +1,6 @@ +package ksyuncdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go index ead9dbc54..c7909b6b8 100644 --- a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go @@ -20,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 金山云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` // 证书 ID。 @@ -61,112 +64,142 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - // // 如果原证书 ID 为空,则创建证书;否则更新证书。 + // 如果原证书 ID 为空,则创建证书;否则更新证书。 if d.config.CertificateId == "" { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - - // 遍历查询域名列表,获取域名 ID - // https://docs.ksyun.com/documents/198 - var domainId string - getCdnDomainsPageNumber := int32(1) - getCdnDomainsPageSize := int32(100) - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - getCdnDomainsInput := map[string]any{ - "PageNumber": getCdnDomainsPageNumber, - "PageSize": getCdnDomainsPageSize, - "DomainName": d.config.Domain, - "FuzzyMatch": "off", - } - getCdnDomainsReq, getCdnDomainsOutput := d.sdkClient.GetCdnDomainsPostRequest(&getCdnDomainsInput) - getCdnDomainsErr := getCdnDomainsReq.Send() - d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput)) - if getCdnDomainsErr != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", getCdnDomainsErr) - } - - type GetCdnDomainsResponse struct { - PageNumber int32 `json:"PageNumber"` - PageSize int32 `json:"PageSize"` - TotalCount int32 `json:"TotalCount"` - Domains []*struct { - DomainId string `json:"DomainId"` - DomainName string `json:"DomainName"` - Cname string `json:"Cname"` - CdnType string `json:"CdnType"` - CreatedTime string `json:"CreatedTime"` - ModifiedTime string `json:"ModifiedTime"` - Region string `json:"Region"` - } `json:"Domains"` - } - var getCdnDomainsResp *GetCdnDomainsResponse - mapstructure.Decode(getCdnDomainsOutput, &getCdnDomainsResp) - - if getCdnDomainsResp != nil { - for _, domainItem := range getCdnDomainsResp.Domains { - if strings.EqualFold(domainItem.DomainName, d.config.Domain) { - domainId = domainItem.DomainId - break - } - } - - if domainId != "" { - break - } - } - - if getCdnDomainsResp == nil || len(getCdnDomainsResp.Domains) < int(getCdnDomainsPageSize) { - break - } else { - getCdnDomainsPageNumber++ - } - } - if domainId == "" { - return nil, errors.New("domain not found") - } - - // 为加速域名配置证书接口 - // https://docs.ksyun.com/documents/261 - configCertificateInput := map[string]any{ - "Enable": "on", - "DomainIds": domainId, - "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), - "ServerCertificate": certPEM, - "PrivateKey": privkeyPEM, - } - configCertificateReq, configCertificateOutput := d.sdkClient.ConfigCertificatePostRequest(&configCertificateInput) - configCertificateErr := configCertificateReq.Send() - d.logger.Debug("sdk request 'cdn.ConfigCertificate'", slog.Any("request", configCertificateInput), slog.Any("response", configCertificateOutput)) - if configCertificateErr != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.ConfigCertificate': %w", configCertificateErr) + if err := d.deployToDomain(ctx, certPEM, privkeyPEM); err != nil { + return nil, err } } else { - // 更新证书 - // https://docs.ksyun.com/documents/259 - setCertificateInput := map[string]any{ - "CertificateId": d.config.CertificateId, - "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), - "ServerCertificate": certPEM, - "PrivateKey": privkeyPEM, - } - setCertificateReq, setCertificateOutput := d.sdkClient.SetCertificatePostRequest(&setCertificateInput) - setCertificateErr := setCertificateReq.Send() - d.logger.Debug("sdk request 'cdn.SetCertificate'", slog.Any("request", setCertificateInput), slog.Any("response", setCertificateOutput)) - if setCertificateErr != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.SetCertificate': %w", setCertificateErr) + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) deployToDomain(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + // 获取域名 ID + domainId, err := d.findDomainIdByDomain(ctx, d.config.Domain) + if err != nil { + return err + } + + if err := d.updateDomainCertificate(ctx, domainId, certPEM, privkeyPEM); err != nil { + return err + } + + return nil +} + +func (d *SSLDeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == "" { + return errors.New("config `certificateId` is required") + } + + // 更新证书 + // https://docs.ksyun.com/documents/259 + setCertificateInput := map[string]any{ + "CertificateId": d.config.CertificateId, + "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), + "ServerCertificate": certPEM, + "PrivateKey": privkeyPEM, + } + setCertificateReq, setCertificateOutput := d.sdkClient.SetCertificatePostRequest(&setCertificateInput) + setCertificateErr := setCertificateReq.Send() + d.logger.Debug("sdk request 'cdn.SetCertificate'", slog.Any("request", setCertificateInput), slog.Any("response", setCertificateOutput)) + if setCertificateErr != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.SetCertificate': %w", setCertificateErr) + } + + return nil +} + +func (d *SSLDeployerProvider) findDomainIdByDomain(ctx context.Context, domain string) (string, error) { + // 查询域名列表 + // https://docs.ksyun.com/documents/198 + getCdnDomainsPageNumber := 1 + getCdnDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + getCdnDomainsInput := map[string]any{ + "PageNumber": getCdnDomainsPageNumber, + "PageSize": getCdnDomainsPageSize, + "DomainName": domain, + "FuzzyMatch": "off", + } + getCdnDomainsReq, getCdnDomainsOutput := d.sdkClient.GetCdnDomainsPostRequest(&getCdnDomainsInput) + getCdnDomainsErr := getCdnDomainsReq.Send() + d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput)) + if getCdnDomainsErr != nil { + return "", fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", getCdnDomainsErr) + } + + type GetCdnDomainsResponse struct { + PageNumber int32 `json:"PageNumber"` + PageSize int32 `json:"PageSize"` + TotalCount int32 `json:"TotalCount"` + Domains []*struct { + DomainId string `json:"DomainId"` + DomainName string `json:"DomainName"` + Cname string `json:"Cname"` + CdnType string `json:"CdnType"` + CreatedTime string `json:"CreatedTime"` + ModifiedTime string `json:"ModifiedTime"` + Region string `json:"Region"` + } `json:"Domains"` + } + var getCdnDomainsResp *GetCdnDomainsResponse + mapstructure.Decode(getCdnDomainsOutput, &getCdnDomainsResp) + if getCdnDomainsResp == nil { + break + } + + for _, domainItem := range getCdnDomainsResp.Domains { + if strings.EqualFold(domainItem.DomainName, domain) { + return domainItem.DomainId, nil + } + } + + if len(getCdnDomainsResp.Domains) < getCdnDomainsPageSize { + break + } + + getCdnDomainsPageNumber++ + } + + return "", fmt.Errorf("could not find domain '%s'", domain) +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domainId string, certPEM string, privkeyPEM string) error { + // 为加速域名配置证书接口 + // https://docs.ksyun.com/documents/261 + configCertificateInput := map[string]any{ + "Enable": "on", + "DomainIds": domainId, + "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), + "ServerCertificate": certPEM, + "PrivateKey": privkeyPEM, + } + configCertificateReq, configCertificateOutput := d.sdkClient.ConfigCertificatePostRequest(&configCertificateInput) + configCertificateErr := configCertificateReq.Send() + d.logger.Debug("sdk request 'cdn.ConfigCertificate'", slog.Any("request", configCertificateInput), slog.Any("response", configCertificateOutput)) + if configCertificateErr != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.ConfigCertificate': %w", configCertificateErr) + } + + return nil +} + func createSDKClient(accessKeyId, secretAccessKey string) (*ksccdnv1.Cdnv1, error) { region := "cn-beijing-6" client := ksccdnv1.SdkNew(ksc.NewClient(accessKeyId, secretAccessKey), &ksc.Config{Region: ®ion}) diff --git a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn_test.go b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn_test.go index e5fdb5f8c..5c51bc079 100644 --- a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Domain: fDomain, - CertificateId: fCertificateId, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, + CertificateId: fCertificateId, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/proxmoxve/proxmoxve.go b/pkg/core/ssl-deployer/providers/proxmoxve/proxmoxve.go index 37a02a5de..2fc751c47 100644 --- a/pkg/core/ssl-deployer/providers/proxmoxve/proxmoxve.go +++ b/pkg/core/ssl-deployer/providers/proxmoxve/proxmoxve.go @@ -71,14 +71,14 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 获取节点信息 // REF: https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node} - node, err := d.sdkClient.Node(context.TODO(), d.config.NodeName) + node, err := d.sdkClient.Node(ctx, d.config.NodeName) if err != nil { return nil, fmt.Errorf("failed to get node '%s': %w", d.config.NodeName, err) } // 上传自定义证书 // REF: https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node}/certificates/custom - err = node.UploadCustomCertificate(context.TODO(), &proxmox.CustomCertificate{ + err = node.UploadCustomCertificate(ctx, &proxmox.CustomCertificate{ Certificates: certPEM, Key: privkeyPEM, Force: true, diff --git a/pkg/core/ssl-deployer/providers/qiniu-cdn/consts.go b/pkg/core/ssl-deployer/providers/qiniu-cdn/consts.go new file mode 100644 index 000000000..a4bbfb19e --- /dev/null +++ b/pkg/core/ssl-deployer/providers/qiniu-cdn/consts.go @@ -0,0 +1,10 @@ +package qiniucdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go index a18816644..b2d60e9f7 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go +++ b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -8,10 +8,13 @@ import ( "strings" "github.com/qiniu/go-sdk/v7/auth" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/qiniu-sslcert" qiniusdk "github.com/certimate-go/certimate/pkg/sdk3rd/qiniu" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -19,6 +22,9 @@ type SSLDeployerProviderConfig struct { AccessKey string `json:"accessKey"` // 七牛云 SecretKey。 SecretKey string `json:"secretKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -66,10 +72,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, fmt.Errorf("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -78,32 +80,158 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // "*.example.com" → ".example.com",适配七牛云 CDN 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } - // 获取域名信息 - // REF: https://developer.qiniu.com/fusion/4246/the-domain-name - getDomainInfoResp, err := d.sdkClient.GetDomainInfo(context.TODO(), domain) - d.logger.Debug("sdk request 'cdn.GetDomainInfo'", slog.String("request.domain", domain), slog.Any("response", getDomainInfoResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainInfo': %w", err) + // "*.example.com" → ".example.com",适配七牛云 CDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS - // REF: https://developer.qiniu.com/fusion/4246/the-domain-name - if getDomainInfoResp.Https == nil || getDomainInfoResp.Https.CertID == "" { - enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(context.TODO(), domain, upres.CertId, true, true) - d.logger.Debug("sdk request 'cdn.EnableDomainHttps'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", enableDomainHttpsResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.EnableDomainHttps': %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { + errs = append(errs, err) + } + } } - } else if getDomainInfoResp.Https.CertID != upres.CertId { - modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) - d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", modifyDomainHttpsConfResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainHttpsConf': %w", err) + + if len(errs) > 0 { + return nil, errors.Join(errs...) } } return &core.SSLDeployResult{}, nil } + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + getDomainListMarker := "" + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + getDomainListResp, err := d.sdkClient.GetDomainList(ctx, getDomainListMarker, 100) + d.logger.Debug("sdk request 'cdn.GetDomainList'", slog.String("request.marker", getDomainListMarker), slog.Any("response", getDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainList': %w", err) + } + + ignoredStatuses := []string{"frozen", "offlined"} + for _, domainItem := range getDomainListResp.Domains { + if lo.Contains(ignoredStatuses, domainItem.OperatingState) { + continue + } + + domains = append(domains, domainItem.Name) + } + + if len(getDomainListResp.Domains) == 0 || getDomainListResp.Marker == "" { + break + } + + getDomainListMarker = getDomainListResp.Marker + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 获取域名信息 + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + getDomainInfoResp, err := d.sdkClient.GetDomainInfo(ctx, domain) + d.logger.Debug("sdk request 'cdn.GetDomainInfo'", slog.String("request.domain", domain), slog.Any("response", getDomainInfoResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.GetDomainInfo': %w", err) + } + + // 判断域名是否已启用 HTTPS + // 如果已启用,修改域名证书;否则,启用 HTTPS + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + if getDomainInfoResp.Https == nil || getDomainInfoResp.Https.CertID == "" { + enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(ctx, domain, cloudCertId, true, true) + d.logger.Debug("sdk request 'cdn.EnableDomainHttps'", slog.String("request.domain", domain), slog.String("request.certId", cloudCertId), slog.Any("response", enableDomainHttpsResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.EnableDomainHttps': %w", err) + } + } else if getDomainInfoResp.Https.CertID != cloudCertId { + modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(ctx, domain, cloudCertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) + d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", cloudCertId), slog.Any("response", modifyDomainHttpsConfResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainHttpsConf': %w", err) + } + } + + return nil +} diff --git a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn_test.go b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn_test.go index 5ad633c37..62682b6b0 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKey: fAccessKey, - SecretKey: fSecretKey, - Domain: fDomain, + AccessKey: fAccessKey, + SecretKey: fSecretKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go index 7dcec1724..c9b41d2c9 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go +++ b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go @@ -18,6 +18,8 @@ type SSLDeployerProviderConfig struct { AccessKey string `json:"accessKey"` // 七牛云 SecretKey。 SecretKey string `json:"secretKey"` + // 存储桶名。暂时无用。 + Bucket string `json:"bucket"` // 自定义域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -78,7 +80,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } // 绑定空间域名证书 - bindBucketCertResp, err := d.sdkClient.BindBucketCert(context.TODO(), d.config.Domain, upres.CertId) + bindBucketCertResp, err := d.sdkClient.BindBucketCert(ctx, d.config.Domain, upres.CertId) d.logger.Debug("sdk request 'kodo.BindCert'", slog.String("request.domain", d.config.Domain), slog.String("request.certId", upres.CertId), slog.Any("response", bindBucketCertResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'kodo.BindCert': %w", err) diff --git a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go index 3dfcf4561..4b8df52a9 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go +++ b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go @@ -16,6 +16,7 @@ var ( fInputKeyPath string fAccessKey string fSecretKey string + fBucket string fDomain string ) @@ -26,6 +27,7 @@ func init() { flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") } @@ -37,6 +39,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_QINIUKODO_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_SSLDEPLOYER_QINIUKODO_ACCESSKEY="your-access-key" \ --CERTIMATE_SSLDEPLOYER_QINIUKODO_SECRETKEY="your-secret-key" \ + --CERTIMATE_SSLDEPLOYER_QINIUKODO_BUCKET="your-bucket" \ --CERTIMATE_SSLDEPLOYER_QINIUKODO_DOMAIN="example.com" */ func TestDeploy(t *testing.T) { @@ -49,12 +52,14 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("ACCESSKEY: %v", fAccessKey), fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("BUCKET: %v", fBucket), fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ AccessKey: fAccessKey, SecretKey: fSecretKey, + Bucket: fBucket, Domain: fDomain, }) if err != nil { diff --git a/pkg/core/ssl-deployer/providers/qiniu-pili/consts.go b/pkg/core/ssl-deployer/providers/qiniu-pili/consts.go new file mode 100644 index 000000000..256a9beeb --- /dev/null +++ b/pkg/core/ssl-deployer/providers/qiniu-pili/consts.go @@ -0,0 +1,8 @@ +package qiniupili + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go index e4e99c4d6..684321324 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go +++ b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go @@ -7,9 +7,11 @@ import ( "log/slog" "github.com/qiniu/go-sdk/v7/pili" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/qiniu-sslcert" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -19,6 +21,9 @@ type SSLDeployerProviderConfig struct { SecretKey string `json:"secretKey"` // 直播空间名。 Hub string `json:"hub"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 直播流域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -78,18 +83,102 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 修改域名证书配置 - // REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#66 - setDomainCertReq := pili.SetDomainCertRequest{ - Hub: d.config.Hub, - Domain: d.config.Domain, - CertName: upres.CertName, + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomainsByHub(ctx, d.config.Hub) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - err = d.sdkClient.SetDomainCert(context.TODO(), setDomainCertReq) - d.logger.Debug("sdk request 'pili.SetDomainCert'", slog.Any("request", setDomainCertReq)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'pili.SetDomainCert': %w", err) + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no pili domains to deploy") + } else { + d.logger.Info("found pili domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, d.config.Hub, domain, upres.CertName); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } return &core.SSLDeployResult{}, nil } + +func (d *SSLDeployerProvider) getAllDomainsByHub(ctx context.Context, hub string) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#6 + getDomainListReq := pili.GetDomainsListRequest{ + Hub: hub, + } + getDomainListResp, err := d.sdkClient.GetDomainsList(ctx, getDomainListReq) + d.logger.Debug("sdk request 'pili.GetDomainsList'", slog.Any("request", getDomainListReq), slog.Any("response", getDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'pili.GetDomainsList': %w", err) + } + + for _, domainItem := range getDomainListResp.Domains { + domains = append(domains, domainItem.Domain) + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, hub string, domain string, cloudCertName string) error { + // 修改域名证书配置 + // REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#6 + setDomainCertReq := pili.SetDomainCertRequest{ + Hub: hub, + Domain: domain, + CertName: cloudCertName, + } + err := d.sdkClient.SetDomainCert(ctx, setDomainCertReq) + d.logger.Debug("sdk request 'pili.SetDomainCert'", slog.Any("request", setDomainCertReq)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'pili.SetDomainCert': %w", err) + } + + return nil +} diff --git a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili_test.go b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili_test.go index 5ab99fd97..df178eb5e 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili_test.go +++ b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili_test.go @@ -57,9 +57,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKey: fAccessKey, - SecretKey: fSecretKey, - Domain: fDomain, + AccessKey: fAccessKey, + SecretKey: fSecretKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/rainyun-rcdn/consts.go b/pkg/core/ssl-deployer/providers/rainyun-rcdn/consts.go new file mode 100644 index 000000000..5b3b3eefa --- /dev/null +++ b/pkg/core/ssl-deployer/providers/rainyun-rcdn/consts.go @@ -0,0 +1,6 @@ +package rainyunrcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go index 853b3cf53..4e4a726b9 100644 --- a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go +++ b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go @@ -17,6 +17,9 @@ type SSLDeployerProviderConfig struct { ApiKey string `json:"apiKey"` // RCDN 实例 ID。 InstanceId int64 `json:"instanceId"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } diff --git a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn_test.go b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn_test.go index e9838ed30..07508f2d8 100644 --- a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn_test.go +++ b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - ApiKey: fApiKey, - InstanceId: fInstanceId, - Domain: fDomain, + ApiKey: fApiKey, + InstanceId: fInstanceId, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go index 960022a60..f2aac07f8 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -98,7 +98,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } case DOMAIN_MATCH_PATTERN_WILDCARD: @@ -108,29 +108,29 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } if strings.HasPrefix(d.config.Domain, "*.") { - temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + domainCandidates, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } else { - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } } case DOMAIN_MATCH_PATTERN_CERTSAN: { - temp, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + domainCandidates, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } default: - return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 遍历更新域名证书 @@ -145,7 +145,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.updateDomainHttpsServerCert(ctx, domain, upres.CertId); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -162,10 +162,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名基本信息,获取匹配的域名 + // 查询域名基本信息,获取匹配的域名 // REF: https://cloud.tencent.com/document/api/228/41118 - describeDomainsOffset := int64(0) - describeDomainsLimit := int64(100) + describeDomainsOffset := 0 + describeDomainsLimit := 100 for { select { case <-ctx.Done(): @@ -181,27 +181,29 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w Fuzzy: common.BoolPtr(true), }, } - describeDomainsReq.Offset = common.Int64Ptr(describeDomainsOffset) - describeDomainsReq.Limit = common.Int64Ptr(describeDomainsLimit) + describeDomainsReq.Offset = common.Int64Ptr(int64(describeDomainsOffset)) + describeDomainsReq.Limit = common.Int64Ptr(int64(describeDomainsLimit)) describeDomainsResp, err := d.sdkClient.DescribeDomains(describeDomainsReq) d.logger.Debug("sdk request 'cdn.DescribeDomains'", slog.Any("request", describeDomainsReq), slog.Any("response", describeDomainsResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeDomains': %w", err) } - if describeDomainsResp.Response.Domains != nil { - for _, domain := range describeDomainsResp.Response.Domains { - if lo.FromPtr(domain.Product) == "cdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domain.Domain)) { - domains = append(domains, *domain.Domain) - } + if describeDomainsResp.Response == nil { + break + } + + for _, domainItem := range describeDomainsResp.Response.Domains { + if lo.FromPtr(domainItem.Product) == "cdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domainItem.Domain)) { + domains = append(domains, lo.FromPtr(domainItem.Domain)) } } - if len(describeDomainsResp.Response.Domains) < int(describeDomainsLimit) { + if len(describeDomainsResp.Response.Domains) < describeDomainsLimit { break - } else { - describeDomainsOffset += describeDomainsLimit } + + describeDomainsOffset += describeDomainsLimit } return domains, nil @@ -229,7 +231,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo return domains, nil } -func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, domain string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 查询域名详细配置 // REF: https://cloud.tencent.com/document/api/228/41117 describeDomainsConfigReq := tccdn.NewDescribeDomainsConfigRequest() @@ -246,7 +248,7 @@ func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, d if err != nil { return fmt.Errorf("failed to execute sdk request 'cdn.DescribeDomainsConfig': %w", err) } else if len(describeDomainsConfigResp.Response.Domains) == 0 { - return fmt.Errorf("domain %s not found", domain) + return fmt.Errorf("could not find domain '%s'", domain) } domainConfig := describeDomainsConfigResp.Response.Domains[0] diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go index b9dfe8c31..6d4948054 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - Domain: fDomain, + SecretId: fSecretId, + SecretKey: fSecretKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go index a76f50cfa..5fa884a4a 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -159,7 +159,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer case <-ctx.Done(): return ctx.Err() default: - if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { errs = append(errs, err) } } @@ -182,7 +182,7 @@ func (d *SSLDeployerProvider) deployToListener(ctx context.Context, cloudCertId } // 更新监听器证书 - if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerId, cloudCertId); err != nil { + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerId, cloudCertId); err != nil { return err } @@ -233,9 +233,10 @@ func (d *SSLDeployerProvider) deployToRuleDomain(ctx context.Context, cloudCertI return fmt.Errorf("failed to execute sdk request 'clb.DescribeTaskStatus': %w", err) } - if describeTaskStatusResp.Response.Status == nil || *describeTaskStatusResp.Response.Status == 1 { + status := lo.FromPtr(describeTaskStatusResp.Response.Status) + if status == 1 { return errors.New("unexpected tencentcloud task status") - } else if *describeTaskStatusResp.Response.Status == 0 { + } else if status == 0 { break } @@ -246,7 +247,7 @@ func (d *SSLDeployerProvider) deployToRuleDomain(ctx context.Context, cloudCertI return nil } -func (d *SSLDeployerProvider) modifyListenerCertificate(ctx context.Context, cloudLoadbalancerId, cloudListenerId, cloudCertId string) error { +func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, cloudLoadbalancerId, cloudListenerId, cloudCertId string) error { // 查询负载均衡的监听器列表 // REF: https://cloud.tencent.com/document/api/214/30686 describeListenersReq := tcclb.NewDescribeListenersRequest() @@ -257,7 +258,7 @@ func (d *SSLDeployerProvider) modifyListenerCertificate(ctx context.Context, clo if err != nil { return fmt.Errorf("failed to execute sdk request 'clb.DescribeListeners': %w", err) } else if len(describeListenersResp.Response.Listeners) == 0 { - return fmt.Errorf("listener %s not found", cloudListenerId) + return fmt.Errorf("could not find listener '%s'", cloudListenerId) } // 修改监听器属性 @@ -295,9 +296,10 @@ func (d *SSLDeployerProvider) modifyListenerCertificate(ctx context.Context, clo return fmt.Errorf("failed to execute sdk request 'clb.DescribeTaskStatus': %w", err) } - if describeTaskStatusResp.Response.Status == nil || *describeTaskStatusResp.Response.Status == 1 { + status := lo.FromPtr(describeTaskStatusResp.Response.Status) + if status == 1 { return errors.New("unexpected tencentcloud task status") - } else if *describeTaskStatusResp.Response.Status == 0 { + } else if status == 0 { break } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-cos/tencentcloud_cos.go b/pkg/core/ssl-deployer/providers/tencentcloud-cos/tencentcloud_cos.go index dc35aaca0..49e5ca4cd 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cos/tencentcloud_cos.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cos/tencentcloud_cos.go @@ -7,6 +7,7 @@ import ( "log/slog" "time" + "github.com/samber/lo" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" @@ -134,21 +135,11 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if describeHostDeployRecordDetailResp.Response.TotalCount == nil { return nil, errors.New("unexpected tencentcloud deployment job status") } else { - if describeHostDeployRecordDetailResp.Response.PendingTotalCount != nil { - pendingCount = *describeHostDeployRecordDetailResp.Response.PendingTotalCount - } - if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { - runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount - } - if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { - succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount - } - if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { - failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount - } - if describeHostDeployRecordDetailResp.Response.TotalCount != nil { - totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount - } + pendingCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.PendingTotalCount) + runningCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.RunningTotalCount) + succeededCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.SuccessTotalCount) + failedCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.FailedTotalCount) + totalCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.TotalCount) if succeededCount+failedCount == totalCount { if failedCount > 0 { @@ -168,8 +159,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) checkIsBind(ctx context.Context, cloudCertId string) (bool, error) { // 查询证书 COS 云资源部署实例列表 // REF: https://cloud.tencent.com/document/api/400/91661 - describeHostCosInstanceListLimit := int64(100) - describeHostCosInstanceListOffset := int64(0) + describeHostCosInstanceListLimit := 100 + describeHostCosInstanceListOffset := 0 for { select { case <-ctx.Done(): @@ -181,33 +172,39 @@ func (d *SSLDeployerProvider) checkIsBind(ctx context.Context, cloudCertId strin describeHostCosInstanceListReq.OldCertificateId = common.StringPtr(cloudCertId) describeHostCosInstanceListReq.ResourceType = common.StringPtr("cos") describeHostCosInstanceListReq.IsCache = common.Uint64Ptr(0) - describeHostCosInstanceListReq.Offset = common.Int64Ptr(describeHostCosInstanceListOffset) - describeHostCosInstanceListReq.Limit = common.Int64Ptr(describeHostCosInstanceListLimit) + describeHostCosInstanceListReq.Offset = common.Int64Ptr(int64(describeHostCosInstanceListOffset)) + describeHostCosInstanceListReq.Limit = common.Int64Ptr(int64(describeHostCosInstanceListLimit)) describeHostCosInstanceListResp, err := d.sdkClient.SSL.DescribeHostCosInstanceList(describeHostCosInstanceListReq) d.logger.Debug("sdk request 'ssl.DescribeHostCosInstanceList'", slog.Any("request", describeHostCosInstanceListReq), slog.Any("response", describeHostCosInstanceListResp)) if err != nil { return false, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostCosInstanceList': %w", err) } + if describeHostCosInstanceListResp.Response == nil { + break + } + for _, instance := range describeHostCosInstanceListResp.Response.InstanceList { - if instance.Bucket == nil || *instance.Bucket != d.config.Bucket { + if lo.FromPtr(instance.Bucket) != d.config.Bucket { continue } - if instance.Domain == nil || *instance.Domain != d.config.Domain { + if lo.FromPtr(instance.Domain) != d.config.Domain { continue } - if instance.Status == nil || *instance.Status != "ENABLED" { + if lo.FromPtr(instance.Status) != "ENABLED" { continue } return true, nil } - if len(describeHostCosInstanceListResp.Response.InstanceList) < int(describeHostCosInstanceListLimit) { - return false, nil - } else { - describeHostCosInstanceListOffset += describeHostCosInstanceListLimit + if len(describeHostCosInstanceListResp.Response.InstanceList) < describeHostCosInstanceListLimit { + break } + + describeHostCosInstanceListOffset += describeHostCosInstanceListLimit } + + return false, nil } func createSDKClients(secretId, secretKey, region string) (*wSDKClients, error) { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-css/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-css/consts.go new file mode 100644 index 000000000..add86a6ac --- /dev/null +++ b/pkg/core/ssl-deployer/providers/tencentcloud-css/consts.go @@ -0,0 +1,8 @@ +package tencentcloudcss + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-css/internal/client.go b/pkg/core/ssl-deployer/providers/tencentcloud-css/internal/client.go index 621dfc23f..1b7aefada 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-css/internal/client.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-css/internal/client.go @@ -23,6 +23,27 @@ func NewLiveClient(credential common.CredentialIface, region string, clientProfi return } +func (c *LiveClient) DescribeLiveDomains(request *tclive.DescribeLiveDomainsRequest) (response *tclive.DescribeLiveDomainsResponse, err error) { + return c.DescribeLiveDomainsWithContext(context.Background(), request) +} + +func (c *LiveClient) DescribeLiveDomainsWithContext(ctx context.Context, request *tclive.DescribeLiveDomainsRequest) (response *tclive.DescribeLiveDomainsResponse, err error) { + if request == nil { + request = tclive.NewDescribeLiveDomainsRequest() + } + c.InitBaseRequest(&request.BaseRequest, "live", tclive.APIVersion, "DescribeLiveDomains") + + if c.GetCredential() == nil { + return nil, errors.New("DescribeLiveDomains require credential") + } + + request.SetContext(ctx) + + response = tclive.NewDescribeLiveDomainsResponse() + err = c.Send(request, response) + return +} + func (c *LiveClient) ModifyLiveDomainCertBindings(request *tclive.ModifyLiveDomainCertBindingsRequest) (response *tclive.ModifyLiveDomainCertBindingsResponse, err error) { return c.ModifyLiveDomainCertBindingsWithContext(context.Background(), request) } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-css/tencentcloud_css.go b/pkg/core/ssl-deployer/providers/tencentcloud-css/tencentcloud_css.go index f29ba15d1..b8602636b 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-css/tencentcloud_css.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-css/tencentcloud_css.go @@ -15,6 +15,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/tencentcloud-css/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/tencentcloud-ssl" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -24,6 +25,9 @@ type SSLDeployerProviderConfig struct { SecretKey string `json:"secretKey"` // 腾讯云接口端点。 Endpoint string `json:"endpoint,omitempty"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 直播播放域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -77,10 +81,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -89,15 +89,51 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 绑定证书对应的播放域名 + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 批量绑定证书对应的播放域名 // REF: https://cloud.tencent.com/document/api/267/78655 modifyLiveDomainCertBindingsReq := tclive.NewModifyLiveDomainCertBindingsRequest() - modifyLiveDomainCertBindingsReq.DomainInfos = []*tclive.LiveCertDomainInfo{ - { - DomainName: common.StringPtr(d.config.Domain), + modifyLiveDomainCertBindingsReq.DomainInfos = lo.Map(domains, func(domain string, _ int) *tclive.LiveCertDomainInfo { + return &tclive.LiveCertDomainInfo{ + DomainName: common.StringPtr(domain), Status: common.Int64Ptr(1), - }, - } + } + }) modifyLiveDomainCertBindingsReq.CloudCertId = common.StringPtr(upres.CertId) modifyLiveDomainCertBindingsResp, err := d.sdkClient.ModifyLiveDomainCertBindings(modifyLiveDomainCertBindingsReq) d.logger.Debug("sdk request 'live.ModifyLiveDomainCertBindings'", slog.Any("request", modifyLiveDomainCertBindingsReq), slog.Any("response", modifyLiveDomainCertBindingsResp)) @@ -108,6 +144,49 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://cloud.tencent.com/document/api/267/33856 + describeLiveDomainsPageNum := 1 + describeLiveDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeLiveDomainsReq := tclive.NewDescribeLiveDomainsRequest() + describeLiveDomainsReq.DomainStatus = common.Uint64Ptr(1) + describeLiveDomainsReq.DomainType = common.Uint64Ptr(1) + describeLiveDomainsReq.PageNum = common.Uint64Ptr(uint64(describeLiveDomainsPageNum)) + describeLiveDomainsReq.PageSize = common.Uint64Ptr(uint64(describeLiveDomainsPageSize)) + describeLiveDomainsResp, err := d.sdkClient.DescribeLiveDomains(describeLiveDomainsReq) + d.logger.Debug("sdk request 'live.DescribeLiveDomains'", slog.Any("request", describeLiveDomainsReq), slog.Any("response", describeLiveDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'live.DescribeLiveDomains': %w", err) + } + + if describeLiveDomainsResp.Response == nil { + break + } + + for _, domainItem := range describeLiveDomainsResp.Response.DomainList { + domains = append(domains, *domainItem.Name) + } + + if len(describeLiveDomainsResp.Response.DomainList) < describeLiveDomainsPageSize { + break + } + + describeLiveDomainsPageNum++ + } + + return domains, nil +} + func createSDKClient(secretId, secretKey, endpoint string) (*internal.LiveClient, error) { credential := common.NewCredential(secretId, secretKey) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go index 98c78d022..c8a2cce64 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -98,7 +98,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } case DOMAIN_MATCH_PATTERN_WILDCARD: @@ -108,29 +108,29 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } if strings.HasPrefix(d.config.Domain, "*.") { - temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + domainCandidates, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } else { - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } } case DOMAIN_MATCH_PATTERN_CERTSAN: { - temp, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + domainCandidates, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } default: - return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 遍历更新域名证书 @@ -145,7 +145,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.updateDomainHttpsServerCert(ctx, domain, upres.CertId); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -162,10 +162,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名基本信息,获取匹配的域名 + // 查询域名基本信息,获取匹配的域名 // REF: https://cloud.tencent.com/document/api/228/41118 - describeDomainsOffset := int64(0) - describeDomainsLimit := int64(100) + describeDomainsOffset := 0 + describeDomainsLimit := 100 for { select { case <-ctx.Done(): @@ -181,27 +181,29 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w Fuzzy: common.BoolPtr(true), }, } - describeDomainsReq.Offset = common.Int64Ptr(describeDomainsOffset) - describeDomainsReq.Limit = common.Int64Ptr(describeDomainsLimit) + describeDomainsReq.Offset = common.Int64Ptr(int64(describeDomainsOffset)) + describeDomainsReq.Limit = common.Int64Ptr(int64(describeDomainsLimit)) describeDomainsResp, err := d.sdkClient.DescribeDomains(describeDomainsReq) d.logger.Debug("sdk request 'cdn.DescribeDomains'", slog.Any("request", describeDomainsReq), slog.Any("response", describeDomainsResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeDomains': %w", err) } - if describeDomainsResp.Response.Domains != nil { - for _, domain := range describeDomainsResp.Response.Domains { - if lo.FromPtr(domain.Product) == "ecdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domain.Domain)) { - domains = append(domains, *domain.Domain) - } + if describeDomainsResp.Response == nil { + break + } + + for _, domainItem := range describeDomainsResp.Response.Domains { + if lo.FromPtr(domainItem.Product) == "ecdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domainItem.Domain)) { + domains = append(domains, *domainItem.Domain) } } - if len(describeDomainsResp.Response.Domains) < int(describeDomainsLimit) { + if len(describeDomainsResp.Response.Domains) < describeDomainsLimit { break - } else { - describeDomainsOffset += describeDomainsLimit } + + describeDomainsOffset += describeDomainsLimit } return domains, nil @@ -229,7 +231,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo return domains, nil } -func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, domain string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 查询域名详细配置 // REF: https://cloud.tencent.com/document/api/228/41117 describeDomainsConfigReq := tccdn.NewDescribeDomainsConfigRequest() @@ -246,7 +248,7 @@ func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, d if err != nil { return fmt.Errorf("failed to execute sdk request 'cdn.DescribeDomainsConfig': %w", err) } else if len(describeDomainsConfigResp.Response.Domains) == 0 { - return fmt.Errorf("domain %s not found", domain) + return fmt.Errorf("could not find domain '%s'", domain) } domainConfig := describeDomainsConfigResp.Response.Domains[0] diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go index ac6eac0e6..6a219492d 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - Domain: fDomain, + SecretId: fSecretId, + SecretKey: fSecretKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/consts.go index 026b4b08f..d4312c86f 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/consts.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/consts.go @@ -5,4 +5,6 @@ const ( DOMAIN_MATCH_PATTERN_EXACT = "exact" // 匹配模式:通配符匹配。 DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" ) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go index 5eafbd905..9aff904e5 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go @@ -15,6 +15,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/tencentcloud-eo/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/tencentcloud-ssl" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) @@ -86,9 +87,6 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if d.config.ZoneId == "" { return nil, errors.New("config `zoneId` is required") } - if len(d.config.Domains) == 0 { - return nil, errors.New("config `domains` is required") - } // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) @@ -98,6 +96,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 var domains []string switch d.config.DomainMatchPattern { case "", DOMAIN_MATCH_PATTERN_EXACT: @@ -115,12 +114,12 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domains` is required") } - domainsInZone, err := d.getDomainsInZone(ctx, d.config.ZoneId) + domainCandidates, err := d.getAllDomainsInZone(ctx, d.config.ZoneId) if err != nil { return nil, err } - domains = lo.Filter(domainsInZone, func(domain string, _ int) bool { + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { for _, configDomain := range d.config.Domains { if xcerthostname.IsMatch(configDomain, domain) { return true @@ -128,14 +127,33 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } return false }) - if len(domains) == 0 { - return nil, errors.New("no domains matched in wildcard mode") + return nil, errors.New("could not find any domains matched by wildcard") + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomainsInZone(ctx, d.config.ZoneId) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") } } default: - return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 配置域名证书 @@ -154,7 +172,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } -func (d *SSLDeployerProvider) getDomainsInZone(ctx context.Context, zoneId string) ([]string, error) { +func (d *SSLDeployerProvider) getAllDomainsInZone(ctx context.Context, zoneId string) ([]string, error) { var domainsInZone []string const pageSize = 200 @@ -177,11 +195,12 @@ func (d *SSLDeployerProvider) getDomainsInZone(ctx context.Context, zoneId strin return nil, fmt.Errorf("failed to execute sdk request 'teo.DescribeAccelerationDomains': %w", err) } - for _, accelerationDomain := range describeAccelerationDomainsResp.Response.AccelerationDomains { - if accelerationDomain == nil || accelerationDomain.DomainName == nil { + for _, domainItem := range describeAccelerationDomainsResp.Response.AccelerationDomains { + if domainItem == nil || domainItem.DomainName == nil { continue } - domainsInZone = append(domainsInZone, *accelerationDomain.DomainName) + + domainsInZone = append(domainsInZone, *domainItem.DomainName) } if len(describeAccelerationDomainsResp.Response.AccelerationDomains) < pageSize { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go index 51a440c1b..1c3321d5c 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - ZoneId: fZoneId, - Domains: strings.Split(fDomains, ";"), + SecretId: fSecretId, + SecretKey: fSecretKey, + ZoneId: fZoneId, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domains: strings.Split(fDomains, ";"), }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go b/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go index 3aa13b81c..61c41f752 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go @@ -111,14 +111,14 @@ func (d *SSLDeployerProvider) deployToListener(ctx context.Context, cloudCertId } // 更新监听器证书 - if err := d.modifyHttpsListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { + if err := d.updateHttpsListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { return err } return nil } -func (d *SSLDeployerProvider) modifyHttpsListenerCertificate(ctx context.Context, cloudListenerId, cloudCertId string) error { +func (d *SSLDeployerProvider) updateHttpsListenerCertificate(ctx context.Context, cloudListenerId, cloudCertId string) error { // 查询 HTTPS 监听器信息 // REF: https://cloud.tencent.com/document/api/608/37001 describeHTTPSListenersReq := tcgaap.NewDescribeHTTPSListenersRequest() @@ -130,7 +130,7 @@ func (d *SSLDeployerProvider) modifyHttpsListenerCertificate(ctx context.Context if err != nil { return fmt.Errorf("failed to execute sdk request 'gaap.DescribeHTTPSListeners': %w", err) } else if len(describeHTTPSListenersResp.Response.ListenerSet) == 0 { - return fmt.Errorf("listener %s not found", cloudListenerId) + return fmt.Errorf("could not find listener '%s'", cloudListenerId) } // 修改 HTTPS 监听器配置 diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/consts.go new file mode 100644 index 000000000..853d0a31f --- /dev/null +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/consts.go @@ -0,0 +1,8 @@ +package tencentcloudscf + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal/client.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal/client.go index cf1661896..3f7df8b1e 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal/client.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal/client.go @@ -43,6 +43,27 @@ func (c *ScfClient) GetCustomDomainWithContext(ctx context.Context, request *tcs return } +func (c *ScfClient) ListCustomDomains(request *tcscf.ListCustomDomainsRequest) (response *tcscf.ListCustomDomainsResponse, err error) { + return c.ListCustomDomainsWithContext(context.Background(), request) +} + +func (c *ScfClient) ListCustomDomainsWithContext(ctx context.Context, request *tcscf.ListCustomDomainsRequest) (response *tcscf.ListCustomDomainsResponse, err error) { + if request == nil { + request = tcscf.NewListCustomDomainsRequest() + } + c.InitBaseRequest(&request.BaseRequest, "scf", tcscf.APIVersion, "ListCustomDomains") + + if c.GetCredential() == nil { + return nil, errors.New("ListCustomDomains require credential") + } + + request.SetContext(ctx) + + response = tcscf.NewListCustomDomainsResponse() + err = c.Send(request, response) + return +} + func (c *ScfClient) UpdateCustomDomain(request *tcscf.UpdateCustomDomainRequest) (response *tcscf.UpdateCustomDomainResponse, err error) { return c.UpdateCustomDomainWithContext(context.Background(), request) } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go index 6353ec8e6..701aca45e 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go @@ -15,6 +15,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/tencentcloud-ssl" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -26,6 +27,9 @@ type SSLDeployerProviderConfig struct { Endpoint string `json:"endpoint,omitempty"` // 腾讯云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 自定义域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -79,20 +83,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - - // 查看云函数自定义域名详情 - // REF: https://cloud.tencent.com/document/api/583/111924 - getCustomDomainReq := tcscf.NewGetCustomDomainRequest() - getCustomDomainReq.Domain = common.StringPtr(d.config.Domain) - getCustomDomainResp, err := d.sdkClient.GetCustomDomain(getCustomDomainReq) - d.logger.Debug("sdk request 'scf.GetCustomDomain'", slog.Any("request", getCustomDomainReq), slog.Any("response", getCustomDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'scf.GetCustomDomain': %w", err) - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -101,23 +91,144 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 更新云函数自定义域名 - // REF: https://cloud.tencent.com/document/api/583/111922 - updateCustomDomainReq := tcscf.NewUpdateCustomDomainRequest() - updateCustomDomainReq.Domain = common.StringPtr(d.config.Domain) - updateCustomDomainReq.CertConfig = &tcscf.CertConf{ - CertificateId: common.StringPtr(upres.CertId), + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - updateCustomDomainReq.Protocol = getCustomDomainResp.Response.Protocol - updateCustomDomainResp, err := d.sdkClient.UpdateCustomDomain(updateCustomDomainReq) - d.logger.Debug("sdk request 'scf.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'scf.UpdateCustomDomain': %w", err) + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no scf domains to deploy") + } else { + d.logger.Info("found scf domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 获取云函数自定义域名列表 + // REF: https://cloud.tencent.com/document/api/583/111923 + listCustomDomainsOffset := 0 + listCustomDomainsLimit := 20 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeLiveDomainsReq := tcscf.NewListCustomDomainsRequest() + describeLiveDomainsReq.Offset = common.Uint64Ptr(uint64(listCustomDomainsOffset)) + describeLiveDomainsReq.Limit = common.Uint64Ptr(uint64(listCustomDomainsLimit)) + describeLiveDomainsResp, err := d.sdkClient.ListCustomDomains(describeLiveDomainsReq) + d.logger.Debug("sdk request 'scf.DescribeLiveDomains'", slog.Any("request", describeLiveDomainsReq), slog.Any("response", describeLiveDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'scf.DescribeLiveDomains': %w", err) + } + + if describeLiveDomainsResp.Response == nil { + break + } + + for _, domainItem := range describeLiveDomainsResp.Response.Domains { + domains = append(domains, *domainItem.Domain) + } + + if len(describeLiveDomainsResp.Response.Domains) < listCustomDomainsLimit { + break + } + + listCustomDomainsOffset++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 查看云函数自定义域名详情 + // REF: https://cloud.tencent.com/document/api/583/111924 + getCustomDomainReq := tcscf.NewGetCustomDomainRequest() + getCustomDomainReq.Domain = common.StringPtr(domain) + getCustomDomainResp, err := d.sdkClient.GetCustomDomain(getCustomDomainReq) + d.logger.Debug("sdk request 'scf.GetCustomDomain'", slog.Any("request", getCustomDomainReq), slog.Any("response", getCustomDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'scf.GetCustomDomain': %w", err) + } else { + if getCustomDomainResp.Response.CertConfig != nil && getCustomDomainResp.Response.CertConfig.CertificateId != nil && *getCustomDomainResp.Response.CertConfig.CertificateId == cloudCertId { + return nil + } + } + + // 更新云函数自定义域名 + // REF: https://cloud.tencent.com/document/api/583/111922 + updateCustomDomainReq := tcscf.NewUpdateCustomDomainRequest() + updateCustomDomainReq.Domain = common.StringPtr(domain) + updateCustomDomainReq.CertConfig = &tcscf.CertConf{ + CertificateId: common.StringPtr(cloudCertId), + } + updateCustomDomainReq.Protocol = getCustomDomainResp.Response.Protocol + if updateCustomDomainReq.Protocol == nil || *updateCustomDomainReq.Protocol == "HTTP" { + updateCustomDomainReq.Protocol = common.StringPtr("HTTP&HTTPS") + } + updateCustomDomainResp, err := d.sdkClient.UpdateCustomDomain(updateCustomDomainReq) + d.logger.Debug("sdk request 'scf.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'scf.UpdateCustomDomain': %w", err) + } + + return nil +} + func createSDKClient(secretId, secretKey, endpoint, region string) (*internal.ScfClient, error) { credential := common.NewCredential(secretId, secretKey) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go index 0357028d4..de654e373 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - Region: fRegion, - Domain: fDomain, + SecretId: fSecretId, + SecretKey: fSecretKey, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go index 7d1cb4de3..b7fd0abde 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go @@ -7,6 +7,7 @@ import ( "log/slog" "time" + "github.com/samber/lo" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" @@ -130,21 +131,11 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if describeHostDeployRecordDetailResp.Response.TotalCount == nil { return nil, errors.New("unexpected tencentcloud deployment job status") } else { - if describeHostDeployRecordDetailResp.Response.PendingTotalCount != nil { - pendingCount = *describeHostDeployRecordDetailResp.Response.PendingTotalCount - } - if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { - runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount - } - if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { - succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount - } - if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { - failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount - } - if describeHostDeployRecordDetailResp.Response.TotalCount != nil { - totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount - } + pendingCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.PendingTotalCount) + runningCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.RunningTotalCount) + succeededCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.SuccessTotalCount) + failedCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.FailedTotalCount) + totalCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.TotalCount) if succeededCount+failedCount == totalCount { if failedCount > 0 { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go index d16247166..925ca2a47 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go @@ -8,6 +8,7 @@ import ( "slices" "time" + "github.com/samber/lo" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" @@ -163,21 +164,11 @@ func (d *SSLDeployerProvider) executeUpdateCertificateInstance(ctx context.Conte if describeHostUpdateRecordDetailResp.Response.TotalCount == nil { return errors.New("unexpected tencentcloud deployment job status") } else { - if describeHostUpdateRecordDetailResp.Response.PendingTotalCount != nil { - pendingCount = *describeHostUpdateRecordDetailResp.Response.PendingTotalCount - } - if describeHostUpdateRecordDetailResp.Response.RunningTotalCount != nil { - runningCount = *describeHostUpdateRecordDetailResp.Response.RunningTotalCount - } - if describeHostUpdateRecordDetailResp.Response.SuccessTotalCount != nil { - succeededCount = *describeHostUpdateRecordDetailResp.Response.SuccessTotalCount - } - if describeHostUpdateRecordDetailResp.Response.FailedTotalCount != nil { - failedCount = *describeHostUpdateRecordDetailResp.Response.FailedTotalCount - } - if describeHostUpdateRecordDetailResp.Response.TotalCount != nil { - totalCount = *describeHostUpdateRecordDetailResp.Response.TotalCount - } + pendingCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.PendingTotalCount) + runningCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.RunningTotalCount) + succeededCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.SuccessTotalCount) + failedCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.FailedTotalCount) + totalCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.TotalCount) if succeededCount+failedCount == totalCount { if failedCount > 0 { @@ -250,18 +241,10 @@ func (d *SSLDeployerProvider) executeUploadUpdateCertificateInstance(ctx context return errors.New("unexpected tencentcloud deployment job status") } else { for _, record := range describeHostUploadUpdateRecordDetailResp.Response.DeployRecordDetail { - if record.RunningTotalCount != nil { - runningCount = *record.RunningTotalCount - } - if record.SuccessTotalCount != nil { - succeededCount = *record.SuccessTotalCount - } - if record.FailedTotalCount != nil { - failedCount = *record.FailedTotalCount - } - if record.TotalCount != nil { - totalCount = *record.TotalCount - } + runningCount += lo.FromPtr(record.RunningTotalCount) + succeededCount += lo.FromPtr(record.SuccessTotalCount) + failedCount += lo.FromPtr(record.FailedTotalCount) + totalCount += lo.FromPtr(record.TotalCount) } if succeededCount+failedCount == totalCount { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-vod/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-vod/consts.go new file mode 100644 index 000000000..5714a78ce --- /dev/null +++ b/pkg/core/ssl-deployer/providers/tencentcloud-vod/consts.go @@ -0,0 +1,8 @@ +package tencentcloudvod + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-vod/internal/client.go b/pkg/core/ssl-deployer/providers/tencentcloud-vod/internal/client.go index 578b83fd1..501a841a6 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-vod/internal/client.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-vod/internal/client.go @@ -23,6 +23,27 @@ func NewVodClient(credential common.CredentialIface, region string, clientProfil return } +func (c *VodClient) DescribeVodDomains(request *tcvod.DescribeVodDomainsRequest) (response *tcvod.DescribeVodDomainsResponse, err error) { + return c.DescribeVodDomainsWithContext(context.Background(), request) +} + +func (c *VodClient) DescribeVodDomainsWithContext(ctx context.Context, request *tcvod.DescribeVodDomainsRequest) (response *tcvod.DescribeVodDomainsResponse, err error) { + if request == nil { + request = tcvod.NewDescribeVodDomainsRequest() + } + c.InitBaseRequest(&request.BaseRequest, "vod", tcvod.APIVersion, "DescribeVodDomains") + + if c.GetCredential() == nil { + return nil, errors.New("DescribeVodDomains require credential") + } + + request.SetContext(ctx) + + response = tcvod.NewDescribeVodDomainsResponse() + err = c.Send(request, response) + return +} + func (c *VodClient) SetVodDomainCertificate(request *tcvod.SetVodDomainCertificateRequest) (response *tcvod.SetVodDomainCertificateResponse, err error) { return c.SetVodDomainCertificateWithContext(context.Background(), request) } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod.go b/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod.go index 8fd72d911..8e662e320 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod.go @@ -26,6 +26,9 @@ type SSLDeployerProviderConfig struct { Endpoint string `json:"endpoint,omitempty"` // 点播应用 ID。 SubAppId int64 `json:"subAppId"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 点播加速域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -79,10 +82,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -91,22 +90,124 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的 ECDN 实例 + domains := make([]string, 0) + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = domainCandidates + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no vod domains to deploy") + } else { + d.logger.Info("found vod domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询点播域名列表 + // REF: https://cloud.tencent.com/document/api/266/54176 + describeVodDomainsOffset := 0 + describeVodDomainsLimit := 20 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeVodDomainsReq := tcvod.NewDescribeVodDomainsRequest() + describeVodDomainsReq.Offset = common.Uint64Ptr(uint64(describeVodDomainsOffset)) + describeVodDomainsReq.Limit = common.Uint64Ptr(uint64(describeVodDomainsLimit)) + if d.config.SubAppId != 0 { + describeVodDomainsReq.SubAppId = common.Uint64Ptr(uint64(d.config.SubAppId)) + } + describeVodDomainsResp, err := d.sdkClient.DescribeVodDomains(describeVodDomainsReq) + d.logger.Debug("sdk request 'vod.DescribeVodDomains'", slog.Any("request", describeVodDomainsReq), slog.Any("response", describeVodDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'vod.DescribeVodDomains': %w", err) + } + + if describeVodDomainsResp.Response == nil { + break + } + + ignoredStatuses := []string{"Locked"} + for _, domainItem := range describeVodDomainsResp.Response.DomainSet { + if lo.Contains(ignoredStatuses, *domainItem.DeployStatus) { + continue + } + + domains = append(domains, *domainItem.Domain) + } + + if len(describeVodDomainsResp.Response.DomainSet) < describeVodDomainsLimit { + break + } + + describeVodDomainsOffset += describeVodDomainsLimit + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 设置点播域名 HTTPS 证书 // REF: https://cloud.tencent.com/document/api/266/102015 setVodDomainCertificateReq := tcvod.NewSetVodDomainCertificateRequest() - setVodDomainCertificateReq.Domain = common.StringPtr(d.config.Domain) + setVodDomainCertificateReq.Domain = common.StringPtr(domain) setVodDomainCertificateReq.Operation = common.StringPtr("Set") - setVodDomainCertificateReq.CertID = common.StringPtr(upres.CertId) + setVodDomainCertificateReq.CertID = common.StringPtr(cloudCertId) if d.config.SubAppId != 0 { setVodDomainCertificateReq.SubAppId = common.Uint64Ptr(uint64(d.config.SubAppId)) } setVodDomainCertificateResp, err := d.sdkClient.SetVodDomainCertificate(setVodDomainCertificateReq) d.logger.Debug("sdk request 'vod.SetVodDomainCertificate'", slog.Any("request", setVodDomainCertificateReq), slog.Any("response", setVodDomainCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'vod.SetVodDomainCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'vod.SetVodDomainCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(secretId, secretKey, endpoint string) (*internal.VodClient, error) { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go index a15e60403..1b79e9593 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go @@ -59,10 +59,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - SubAppId: fSubAppId, - Domain: fDomain, + SecretId: fSecretId, + SecretKey: fSecretKey, + SubAppId: fSubAppId, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/ucloud-ucdn/ucloud_ucdn.go b/pkg/core/ssl-deployer/providers/ucloud-ucdn/ucloud_ucdn.go index 822680ae5..71be215c1 100644 --- a/pkg/core/ssl-deployer/providers/ucloud-ucdn/ucloud_ucdn.go +++ b/pkg/core/ssl-deployer/providers/ucloud-ucdn/ucloud_ucdn.go @@ -97,7 +97,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'ucdn.GetUcdnDomainConfig': %w", err) } else if len(getUcdnDomainConfigResp.DomainList) == 0 { - return nil, fmt.Errorf("domain %s not found", d.config.DomainId) + return nil, fmt.Errorf("could not find domain '%s'", d.config.DomainId) } // 更新 HTTPS 加速配置 diff --git a/pkg/core/ssl-deployer/providers/upyun-cdn/consts.go b/pkg/core/ssl-deployer/providers/upyun-cdn/consts.go new file mode 100644 index 000000000..6a119706b --- /dev/null +++ b/pkg/core/ssl-deployer/providers/upyun-cdn/consts.go @@ -0,0 +1,10 @@ +package upyuncdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/upyun-cdn/upyun_cdn.go b/pkg/core/ssl-deployer/providers/upyun-cdn/upyun_cdn.go index 9d83508d3..69941c329 100644 --- a/pkg/core/ssl-deployer/providers/upyun-cdn/upyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/upyun-cdn/upyun_cdn.go @@ -5,12 +5,15 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/upyun-ssl" upyunsdk "github.com/certimate-go/certimate/pkg/sdk3rd/upyun/console" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -18,6 +21,9 @@ type SSLDeployerProviderConfig struct { Username string `json:"username"` // 又拍云账号密码。 Password string `json:"password"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -68,10 +74,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -80,44 +82,185 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 获取域名证书配置 - getHttpsServiceManagerResp, err := d.sdkClient.GetHttpsServiceManager(d.config.Domain) - d.logger.Debug("sdk request 'console.GetHttpsServiceManager'", slog.String("request.domain", d.config.Domain), slog.Any("response", getHttpsServiceManagerResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'console.GetHttpsServiceManager': %w", err) + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 判断域名是否已启用 HTTPS。如果已启用,迁移域名证书;否则,设置新证书 - _, lastCertIndex, _ := lo.FindIndexOf(getHttpsServiceManagerResp.Data.Domains, func(item upyunsdk.HttpsServiceManagerDomain) bool { - return item.Https - }) - if lastCertIndex == -1 { - updateHttpsCertificateManagerReq := &upyunsdk.UpdateHttpsCertificateManagerRequest{ - CertificateId: upres.CertId, - Domain: d.config.Domain, - Https: true, - ForceHttps: true, + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { + errs = append(errs, err) + } + } } - updateHttpsCertificateManagerResp, err := d.sdkClient.UpdateHttpsCertificateManager(updateHttpsCertificateManagerReq) - d.logger.Debug("sdk request 'console.EnableDomainHttps'", slog.Any("request", updateHttpsCertificateManagerReq), slog.Any("response", updateHttpsCertificateManagerResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'console.UpdateHttpsCertificateManager': %w", err) - } - } else if getHttpsServiceManagerResp.Data.Domains[lastCertIndex].CertificateId != upres.CertId { - migrateHttpsDomainReq := &upyunsdk.MigrateHttpsDomainRequest{ - CertificateId: upres.CertId, - Domain: d.config.Domain, - } - migrateHttpsDomainResp, err := d.sdkClient.MigrateHttpsDomain(migrateHttpsDomainReq) - d.logger.Debug("sdk request 'console.MigrateHttpsDomain'", slog.Any("request", migrateHttpsDomainReq), slog.Any("response", migrateHttpsDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'console.MigrateHttpsDomain': %w", err) + + if len(errs) > 0 { + return nil, errors.Join(errs...) } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 获取服务列表 + getBucketsPage := 1 + getBucketsPerPage := 10 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + getBucketsReq := &upyunsdk.GetBucketsRequest{ + Type: "ucdn", + Tag: "all", + Status: "all", + IsSecurityCDN: false, + WithDomains: true, + Page: int32(getBucketsPage), + PerPage: int32(getBucketsPerPage), + } + getBucketsResp, err := d.sdkClient.GetBuckets(getBucketsReq) + d.logger.Debug("sdk request 'console.GetBuckets'", slog.Any("request", getBucketsReq), slog.Any("response", getBucketsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'console.GetBuckets': %w", err) + } + + if getBucketsResp.Data == nil { + break + } + + for _, bucketItem := range getBucketsResp.Data.Buckets { + if !bucketItem.Visible { + continue + } + + for _, domainItem := range bucketItem.Domains { + if strings.EqualFold(domainItem.Status, "NORMAL") && !strings.HasSuffix(domainItem.Domain, ".test.upcdn.net") { + domains = append(domains, domainItem.Domain) + } + } + } + + if len(getBucketsResp.Data.Buckets) < getBucketsPerPage { + break + } + + getBucketsPage++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 获取域名证书配置 + getHttpsServiceManagerResp, err := d.sdkClient.GetHttpsServiceManager(domain) + d.logger.Debug("sdk request 'console.GetHttpsServiceManager'", slog.String("request.domain", domain), slog.Any("response", getHttpsServiceManagerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'console.GetHttpsServiceManager': %w", err) + } + + // 判断域名是否已启用 HTTPS + // 如果已启用,迁移域名证书;否则,设置新证书 + _, lastCertIndex, _ := lo.FindIndexOf(getHttpsServiceManagerResp.Data.Domains, func(item upyunsdk.HttpsServiceManagerDomain) bool { + return item.Https + }) + if lastCertIndex == -1 { + updateHttpsCertificateManagerReq := &upyunsdk.UpdateHttpsCertificateManagerRequest{ + CertificateId: cloudCertId, + Domain: domain, + Https: true, + ForceHttps: true, + } + updateHttpsCertificateManagerResp, err := d.sdkClient.UpdateHttpsCertificateManager(updateHttpsCertificateManagerReq) + d.logger.Debug("sdk request 'console.EnableDomainHttps'", slog.Any("request", updateHttpsCertificateManagerReq), slog.Any("response", updateHttpsCertificateManagerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'console.UpdateHttpsCertificateManager': %w", err) + } + } else if getHttpsServiceManagerResp.Data.Domains[lastCertIndex].CertificateId != cloudCertId { + migrateHttpsDomainReq := &upyunsdk.MigrateHttpsDomainRequest{ + CertificateId: cloudCertId, + Domain: domain, + } + migrateHttpsDomainResp, err := d.sdkClient.MigrateHttpsDomain(migrateHttpsDomainReq) + d.logger.Debug("sdk request 'console.MigrateHttpsDomain'", slog.Any("request", migrateHttpsDomainReq), slog.Any("response", migrateHttpsDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'console.MigrateHttpsDomain': %w", err) + } + } + + return nil +} + func createSDKClient(username, password string) (*upyunsdk.Client, error) { return upyunsdk.NewClient(username, password) } diff --git a/pkg/core/ssl-deployer/providers/upyun-file/upyun_file.go b/pkg/core/ssl-deployer/providers/upyun-file/upyun_file.go new file mode 100644 index 000000000..5b2d07cf1 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/upyun-file/upyun_file.go @@ -0,0 +1,122 @@ +package upyunfile + +import ( + "context" + "errors" + "fmt" + "log/slog" + + "github.com/samber/lo" + + "github.com/certimate-go/certimate/pkg/core" + sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/upyun-ssl" + upyunsdk "github.com/certimate-go/certimate/pkg/sdk3rd/upyun/console" +) + +type SSLDeployerProviderConfig struct { + // 又拍云账号用户名。 + Username string `json:"username"` + // 又拍云账号密码。 + Password string `json:"password"` + // 存储桶名。暂时无用。 + Bucket string `json:"bucket"` + // 自定义域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type SSLDeployerProvider struct { + config *SSLDeployerProviderConfig + logger *slog.Logger + sdkClient *upyunsdk.Client + sslManager core.SSLManager +} + +var _ core.SSLDeployer = (*SSLDeployerProvider)(nil) + +func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the ssl deployer provider is nil") + } + + client, err := createSDKClient(config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("could not create sdk client: %w", err) + } + + sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{ + Username: config.Username, + Password: config.Password, + }) + if err != nil { + return nil, fmt.Errorf("could not create ssl manager: %w", err) + } + + return &SSLDeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslManager: sslmgr, + }, nil +} + +func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } + + d.sslManager.SetLogger(logger) +} + +func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { + // 上传证书 + upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 获取域名证书配置 + getHttpsServiceManagerResp, err := d.sdkClient.GetHttpsServiceManager(d.config.Domain) + d.logger.Debug("sdk request 'console.GetHttpsServiceManager'", slog.String("request.domain", d.config.Domain), slog.Any("response", getHttpsServiceManagerResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'console.GetHttpsServiceManager': %w", err) + } + + // 判断域名是否已启用 HTTPS + // 如果已启用,迁移域名证书;否则,设置新证书 + _, lastCertIndex, _ := lo.FindIndexOf(getHttpsServiceManagerResp.Data.Domains, func(item upyunsdk.HttpsServiceManagerDomain) bool { + return item.Https + }) + if lastCertIndex == -1 { + updateHttpsCertificateManagerReq := &upyunsdk.UpdateHttpsCertificateManagerRequest{ + CertificateId: upres.CertId, + Domain: d.config.Domain, + Https: true, + ForceHttps: true, + } + updateHttpsCertificateManagerResp, err := d.sdkClient.UpdateHttpsCertificateManager(updateHttpsCertificateManagerReq) + d.logger.Debug("sdk request 'console.EnableDomainHttps'", slog.Any("request", updateHttpsCertificateManagerReq), slog.Any("response", updateHttpsCertificateManagerResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'console.UpdateHttpsCertificateManager': %w", err) + } + } else if getHttpsServiceManagerResp.Data.Domains[lastCertIndex].CertificateId != upres.CertId { + migrateHttpsDomainReq := &upyunsdk.MigrateHttpsDomainRequest{ + CertificateId: upres.CertId, + Domain: d.config.Domain, + } + migrateHttpsDomainResp, err := d.sdkClient.MigrateHttpsDomain(migrateHttpsDomainReq) + d.logger.Debug("sdk request 'console.MigrateHttpsDomain'", slog.Any("request", migrateHttpsDomainReq), slog.Any("response", migrateHttpsDomainResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'console.MigrateHttpsDomain': %w", err) + } + } + + return &core.SSLDeployResult{}, nil +} + +func createSDKClient(username, password string) (*upyunsdk.Client, error) { + return upyunsdk.NewClient(username, password) +} diff --git a/pkg/core/ssl-deployer/providers/upyun-file/upyun_file_test.go b/pkg/core/ssl-deployer/providers/upyun-file/upyun_file_test.go new file mode 100644 index 000000000..f10e0919b --- /dev/null +++ b/pkg/core/ssl-deployer/providers/upyun-file/upyun_file_test.go @@ -0,0 +1,80 @@ +package upyunfile_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/upyun-file" +) + +var ( + fInputCertPath string + fInputKeyPath string + fUsername string + fPassword string + fBucket string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_SSLDEPLOYER_UPYUNFILE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") + flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./upyun_file_test.go -args \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_USERNAME="your-username" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_PASSWORD="your-password" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_BUCKET="your-bucket" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_DOMAIN="example.com" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("BUCKET: %v", fBucket), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ + Username: fUsername, + Password: fPassword, + Bucket: fBucket, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/pkg/core/ssl-deployer/providers/volcengine-alb/volcengine_alb.go b/pkg/core/ssl-deployer/providers/volcengine-alb/volcengine_alb.go index 9ab152188..2b9e2210b 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-alb/volcengine_alb.go +++ b/pkg/core/ssl-deployer/providers/volcengine-alb/volcengine_alb.go @@ -129,8 +129,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 HTTPS 监听器列表 // REF: https://www.volcengine.com/docs/6767/113684 listenerIds := make([]string, 0) - describeListenersPageSize := int64(100) - describeListenersPageNumber := int64(1) + describeListenersPageSize := 100 + describeListenersPageNumber := 1 for { select { case <-ctx.Done(): @@ -141,8 +141,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer describeListenersReq := &vealb.DescribeListenersInput{ LoadBalancerId: ve.String(d.config.LoadbalancerId), Protocol: ve.String("HTTPS"), - PageNumber: ve.Int64(describeListenersPageNumber), - PageSize: ve.Int64(describeListenersPageSize), + PageNumber: ve.Int64(int64(describeListenersPageNumber)), + PageSize: ve.Int64(int64(describeListenersPageSize)), } describeListenersResp, err := d.sdkClient.DescribeListeners(describeListenersReq) d.logger.Debug("sdk request 'alb.DescribeListeners'", slog.Any("request", describeListenersReq), slog.Any("response", describeListenersResp)) @@ -154,11 +154,11 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer listenerIds = append(listenerIds, *listener.ListenerId) } - if len(describeListenersResp.Listeners) < int(describeListenersPageSize) { + if len(describeListenersResp.Listeners) < describeListenersPageSize { break - } else { - describeListenersPageNumber++ } + + describeListenersPageNumber++ } // 遍历更新监听证书 diff --git a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go index 0389d1f2a..d37617606 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -83,7 +83,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 获取待部署的 CDN 实例 + // 获取待部署的域名列表 domains := make([]string, 0) switch d.config.DomainMatchPattern { case "", DOMAIN_MATCH_PATTERN_EXACT: @@ -92,7 +92,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } case DOMAIN_MATCH_PATTERN_WILDCARD: @@ -102,29 +102,29 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } if strings.HasPrefix(d.config.Domain, "*.") { - temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + domainCandidates, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } else { - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } } case DOMAIN_MATCH_PATTERN_CERTSAN: { - temp, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + domainCandidates, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } default: - return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 遍历绑定证书 @@ -139,7 +139,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.bindCert(ctx, domain, upres.CertId); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -156,10 +156,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { domains := make([]string, 0) - // 遍历获取加速域名列表,获取匹配的域名 + // 查询加速域名列表,获取匹配的域名 // REF: https://www.volcengine.com/docs/6454/75269 - listCdnDomainsPageNum := int64(1) - listCdnDomainsPageSize := int64(100) + listCdnDomainsPageNum := 1 + listCdnDomainsPageSize := 100 for { select { case <-ctx.Done(): @@ -170,8 +170,8 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w listCdnDomainsReq := &vecdn.ListCdnDomainsInput{ Domain: ve.String(strings.TrimPrefix(wildcardDomain, "*.")), Status: ve.String("online"), - PageNum: ve.Int64(listCdnDomainsPageNum), - PageSize: ve.Int64(listCdnDomainsPageSize), + PageNum: ve.Int64(int64(listCdnDomainsPageNum)), + PageSize: ve.Int64(int64(listCdnDomainsPageSize)), } listCdnDomainsResp, err := d.sdkClient.ListCdnDomains(listCdnDomainsReq) d.logger.Debug("sdk request 'cdn.ListCdnDomains'", slog.Any("request", listCdnDomainsReq), slog.Any("response", listCdnDomainsResp)) @@ -179,19 +179,21 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCdnDomains': %w", err) } - if listCdnDomainsResp.Data != nil { - for _, domain := range listCdnDomainsResp.Data { - if xcerthostname.IsMatch(wildcardDomain, ve.StringValue(domain.Domain)) { - domains = append(domains, ve.StringValue(domain.Domain)) - } + for _, domainItem := range listCdnDomainsResp.Data { + if xcerthostname.IsMatch(wildcardDomain, ve.StringValue(domainItem.Domain)) { + domains = append(domains, ve.StringValue(domainItem.Domain)) } } - if len(listCdnDomainsResp.Data) < int(listCdnDomainsPageSize) { + if len(listCdnDomainsResp.Data) < listCdnDomainsPageSize { break - } else { - listCdnDomainsPageSize++ } + + listCdnDomainsPageSize++ + } + + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") } return domains, nil @@ -225,14 +227,14 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo if len(domains) == 0 { if len(describeCertConfigResp.SpecifiedCertConfig) == 0 { - return nil, errors.New("domains not found") + return nil, errors.New("could not find any domains matched by certificate") } } return domains, nil } -func (d *SSLDeployerProvider) bindCert(ctx context.Context, domain string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 关联证书与加速域名 // REF: https://www.volcengine.com/docs/6454/125712 batchDeployCertReq := &vecdn.BatchDeployCertInput{ diff --git a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn_test.go b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn_test.go index 0d83f202e..5209d2da3 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/volcengine-clb/volcengine_clb.go b/pkg/core/ssl-deployer/providers/volcengine-clb/volcengine_clb.go index 98f3d1223..2bfaaa9a0 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-clb/volcengine_clb.go +++ b/pkg/core/ssl-deployer/providers/volcengine-clb/volcengine_clb.go @@ -125,8 +125,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 HTTPS 监听器列表 // REF: https://www.volcengine.com/docs/6406/71776 listenerIds := make([]string, 0) - describeListenersPageSize := int64(100) - describeListenersPageNumber := int64(1) + describeListenersPageSize := 100 + describeListenersPageNumber := 1 for { select { case <-ctx.Done(): @@ -137,8 +137,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer describeListenersReq := &veclb.DescribeListenersInput{ LoadBalancerId: ve.String(d.config.LoadbalancerId), Protocol: ve.String("HTTPS"), - PageNumber: ve.Int64(describeListenersPageNumber), - PageSize: ve.Int64(describeListenersPageSize), + PageNumber: ve.Int64(int64(describeListenersPageNumber)), + PageSize: ve.Int64(int64(describeListenersPageSize)), } describeListenersResp, err := d.sdkClient.DescribeListeners(describeListenersReq) d.logger.Debug("sdk request 'clb.DescribeListeners'", slog.Any("request", describeListenersReq), slog.Any("response", describeListenersResp)) @@ -150,11 +150,11 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer listenerIds = append(listenerIds, *listener.ListenerId) } - if len(describeListenersResp.Listeners) < int(describeListenersPageSize) { + if len(describeListenersResp.Listeners) < describeListenersPageSize { break - } else { - describeListenersPageNumber++ } + + describeListenersPageNumber++ } // 遍历更新监听证书 diff --git a/pkg/core/ssl-deployer/providers/volcengine-dcdn/consts.go b/pkg/core/ssl-deployer/providers/volcengine-dcdn/consts.go new file mode 100644 index 000000000..11466778d --- /dev/null +++ b/pkg/core/ssl-deployer/providers/volcengine-dcdn/consts.go @@ -0,0 +1,6 @@ +package volcenginedcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go index 9742e19de..350655341 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go @@ -23,6 +23,9 @@ type SSLDeployerProviderConfig struct { AccessKeySecret string `json:"accessKeySecret"` // 火山引擎地域。 Region string `json:"region"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -74,10 +77,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -86,15 +85,30 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // "*.example.com" → ".example.com",适配火山引擎 DCDN 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + domains := make([]string, 0) + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } - // 绑定证书 + // "*.example.com" → ".example.com",适配火山引擎 DCDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{domain} + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 批量绑定证书 // REF: https://www.volcengine.com/docs/6559/1250189 createCertBindReq := &vedcdn.CreateCertBindInput{ CertSource: ve.String("volc"), CertId: ve.String(upres.CertId), - DomainNames: ve.StringSlice([]string{domain}), + DomainNames: ve.StringSlice(domains), } createCertBindResp, err := d.sdkClient.CreateCertBind(createCertBindReq) d.logger.Debug("sdk request 'dcdn.CreateCertBind'", slog.Any("request", createCertBindReq), slog.Any("response", createCertBindResp)) diff --git a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn_test.go b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn_test.go index 86270ff23..5dbc8db6e 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn_test.go +++ b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/volcengine-imagex/volcengine_imagex.go b/pkg/core/ssl-deployer/providers/volcengine-imagex/volcengine_imagex.go index a129b33bf..f84081e42 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-imagex/volcengine_imagex.go +++ b/pkg/core/ssl-deployer/providers/volcengine-imagex/volcengine_imagex.go @@ -94,7 +94,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke ServiceID: d.config.ServiceId, DomainName: d.config.Domain, } - getDomainConfigResp, err := d.sdkClient.GetDomainConfig(context.TODO(), getDomainConfigReq) + getDomainConfigResp, err := d.sdkClient.GetDomainConfig(ctx, getDomainConfigReq) d.logger.Debug("sdk request 'imagex.GetDomainConfig'", slog.Any("request", getDomainConfigReq), slog.Any("response", getDomainConfigResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'imagex.GetDomainConfig': %w", err) @@ -123,7 +123,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke updateHttpsReq.UpdateHTTPSBody.HTTPS.ForceRedirectType = getDomainConfigResp.Result.HTTPSConfig.ForceRedirectType updateHttpsReq.UpdateHTTPSBody.HTTPS.ForceRedirectCode = getDomainConfigResp.Result.HTTPSConfig.ForceRedirectCode } - updateHttpsResp, err := d.sdkClient.UpdateHTTPS(context.TODO(), updateHttpsReq) + updateHttpsResp, err := d.sdkClient.UpdateHTTPS(ctx, updateHttpsReq) d.logger.Debug("sdk request 'imagex.UpdateHttps'", slog.Any("request", updateHttpsReq), slog.Any("response", updateHttpsResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'imagex.UpdateHttps': %w", err) diff --git a/pkg/core/ssl-deployer/providers/volcengine-live/consts.go b/pkg/core/ssl-deployer/providers/volcengine-live/consts.go index 8665260fc..84801f8d6 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/consts.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/consts.go @@ -5,4 +5,6 @@ const ( DOMAIN_MATCH_PATTERN_EXACT = "exact" // 匹配模式:通配符匹配。 DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" ) diff --git a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go index 418308d31..53ceefa0d 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -7,11 +7,13 @@ import ( "log/slog" "strings" + "github.com/samber/lo" velive "github.com/volcengine/volc-sdk-golang/service/live/v20230101" ve "github.com/volcengine/volcengine-go-sdk/volcengine" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/volcengine-live" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) @@ -99,19 +101,44 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } if strings.HasPrefix(d.config.Domain, "*.") { - temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + domainCandidates, err := d.getAllDomains(ctx) if err != nil { return nil, err } - domains = temp + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by wildcard") + } } else { domains = append(domains, d.config.Domain) } } + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("could not find any domains matched by certificate") + } + } + default: - return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 遍历绑定证书 @@ -126,7 +153,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.bindCert(ctx, domain, upres.CertId); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -140,13 +167,13 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } -func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表,获取匹配的域名 - // REF: https://www.volcengine.com/docs/6469/1186277#%E6%9F%A5%E8%AF%A2%E5%9F%9F%E5%90%8D%E5%88%97%E8%A1%A8 - listDomainDetailPageNum := int32(1) - listDomainDetailPageSize := int32(1000) + // 查询域名列表 + // REF: https://www.volcengine.com/docs/6469/1126815 + listDomainDetailPageNum := 1 + listDomainDetailPageSize := 1000 for { select { case <-ctx.Done(): @@ -156,8 +183,8 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w listDomainDetailReq := &velive.ListDomainDetailBody{ DomainStatusList: ve.Int32Slice([]int32{0}), - PageNum: listDomainDetailPageNum, - PageSize: listDomainDetailPageSize, + PageNum: int32(listDomainDetailPageNum), + PageSize: int32(listDomainDetailPageSize), } listDomainDetailResp, err := d.sdkClient.ListDomainDetail(ctx, listDomainDetailReq) d.logger.Debug("sdk request 'live.ListDomainDetail'", slog.Any("request", listDomainDetailReq), slog.Any("response", listDomainDetailResp)) @@ -165,31 +192,27 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w return nil, fmt.Errorf("failed to execute sdk request 'live.ListDomainDetail': %w", err) } - if listDomainDetailResp.Result.DomainList != nil { - for _, domain := range listDomainDetailResp.Result.DomainList { - if xcerthostname.IsMatch(wildcardDomain, domain.Domain) { - domains = append(domains, domain.Domain) - } - } - } - - if len(listDomainDetailResp.Result.DomainList) < int(listDomainDetailPageSize) { + if listDomainDetailResp.Result == nil { break - } else { - listDomainDetailPageNum++ } - } - if len(domains) == 0 { - return nil, errors.New("domain not found") + for _, domainItem := range listDomainDetailResp.Result.DomainList { + domains = append(domains, domainItem.Domain) + } + + if len(listDomainDetailResp.Result.DomainList) < listDomainDetailPageSize { + break + } + + listDomainDetailPageNum++ } return domains, nil } -func (d *SSLDeployerProvider) bindCert(ctx context.Context, domain string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 绑定证书 - // REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6 + // REF: https://www.volcengine.com/docs/6469/1126820 bindCertReq := &velive.BindCertBody{ ChainID: cloudCertId, Domain: domain, diff --git a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live_test.go b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live_test.go index ce123dc2b..e03bbe152 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live_test.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/volcengine-tos/volcengine_tos.go b/pkg/core/ssl-deployer/providers/volcengine-tos/volcengine_tos.go index 6ff808540..38c31ddbb 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-tos/volcengine_tos.go +++ b/pkg/core/ssl-deployer/providers/volcengine-tos/volcengine_tos.go @@ -96,7 +96,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke CertID: upres.CertId, }, } - putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomain(context.TODO(), putBucketCustomDomainReq) + putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomain(ctx, putBucketCustomDomainReq) d.logger.Debug("sdk request 'tos.PutBucketCustomDomain'", slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'tos.PutBucketCustomDomain': %w", err) diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdn/consts.go b/pkg/core/ssl-deployer/providers/wangsu-cdn/consts.go new file mode 100644 index 000000000..d4493904c --- /dev/null +++ b/pkg/core/ssl-deployer/providers/wangsu-cdn/consts.go @@ -0,0 +1,6 @@ +package wangsucdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go b/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go index 88c9a95ea..768626829 100644 --- a/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go +++ b/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go @@ -20,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 网宿云 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名数组(支持泛域名)。 Domains []string `json:"domains"` } @@ -68,10 +71,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if len(d.config.Domains) == 0 { - return nil, errors.New("config `domains` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -80,15 +79,31 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + domains := make([]string, 0) + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if len(d.config.Domains) == 0 { + return nil, errors.New("config `domains` is required") + } + + // "*.example.com" → ".example.com",适配网宿云 CDN 要求的泛域名格式 + domains = lo.Map(d.config.Domains, func(domain string, _ int) string { + return strings.TrimPrefix(domain, "*") + }) + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + // 批量修改域名证书配置 // REF: https://www.wangsu.com/document/api-doc/37447 certId, _ := strconv.ParseInt(upres.CertId, 10, 64) batchUpdateCertificateConfigReq := &wangsusdk.BatchUpdateCertificateConfigRequest{ CertificateId: certId, - DomainNames: lo.Map(d.config.Domains, func(domain string, _ int) string { - // "*.example.com" → ".example.com",适配网宿云 CDN 要求的泛域名格式 - return strings.TrimPrefix(domain, "*") - }), + DomainNames: domains, } batchUpdateCertificateConfigResp, err := d.sdkClient.BatchUpdateCertificateConfig(batchUpdateCertificateConfigReq) d.logger.Debug("sdk request 'cdn.BatchUpdateCertificateConfig'", slog.Any("request", batchUpdateCertificateConfigReq), slog.Any("response", batchUpdateCertificateConfigResp)) diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdnpro/consts.go b/pkg/core/ssl-deployer/providers/wangsu-cdnpro/consts.go new file mode 100644 index 000000000..5cde7cfa8 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/wangsu-cdnpro/consts.go @@ -0,0 +1,6 @@ +package wangsucdnpro + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go b/pkg/core/ssl-deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go index 4c06d5a42..055dbaaab 100644 --- a/pkg/core/ssl-deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go +++ b/pkg/core/ssl-deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go @@ -32,6 +32,9 @@ type SSLDeployerProviderConfig struct { ApiKey string `json:"apiKey"` // 网宿云环境。 Environment string `json:"environment"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` // 证书 ID。 diff --git a/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go b/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go index e656323ff..1c5b82da0 100644 --- a/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go +++ b/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go @@ -59,8 +59,8 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历证书列表,避免重复上传 - if res, err := m.findCertIfExists(ctx, certPEM, privkeyPEM); err != nil { + // 避免重复上传 + if res, err := m.tryFindCert(ctx, certPEM, privkeyPEM); err != nil { return nil, err } else if res != nil { m.logger.Info("ssl certificate already exists") @@ -106,20 +106,20 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey panic("sdk client is not implemented") } - // 遍历证书列表,获取刚刚上传证书 ID - if res, err := m.findCertIfExists(ctx, certPEM, privkeyPEM); err != nil { + // 获取刚刚上传证书 ID + if res, err := m.tryFindCert(ctx, certPEM, privkeyPEM); err != nil { return nil, err } else if res == nil { - return nil, fmt.Errorf("no ssl certificate found, may be upload failed") + return nil, fmt.Errorf("could not find ssl certificate, may be upload failed") } else { return res, nil } } -func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - searchWebsiteSSLPageNumber := int32(1) - searchWebsiteSSLPageSize := int32(100) - searchWebsiteSSLItemsCount := int32(0) +func (m *SSLManagerProvider) tryFindCert(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { + searchWebsiteSSLPageNumber := 1 + searchWebsiteSSLPageSize := 100 + searchWebsiteSSLItemsCount := 0 for { select { case <-ctx.Done(): @@ -131,8 +131,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin case *onepanelsdk.Client: { searchWebsiteSSLReq := &onepanelsdk.SearchWebsiteSSLRequest{ - Page: searchWebsiteSSLPageNumber, - PageSize: searchWebsiteSSLPageSize, + Page: int32(searchWebsiteSSLPageNumber), + PageSize: int32(searchWebsiteSSLPageSize), } searchWebsiteSSLResp, err := sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq) m.logger.Debug("sdk request '1panel.SearchWebsiteSSL'", slog.Any("request", searchWebsiteSSLReq), slog.Any("response", searchWebsiteSSLResp)) @@ -140,27 +140,29 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request '1panel.SearchWebsiteSSL': %w", err) } - if searchWebsiteSSLResp.Data != nil { - for _, sslItem := range searchWebsiteSSLResp.Data.Items { - if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) && - strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) { - // 如果已存在相同证书,直接返回 - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", sslItem.ID), - CertName: sslItem.Description, - }, nil - } + if searchWebsiteSSLResp.Data == nil { + break + } + + for _, sslItem := range searchWebsiteSSLResp.Data.Items { + if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) && + strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) { + // 如果已存在相同证书,直接返回 + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", sslItem.ID), + CertName: sslItem.Description, + }, nil } } - searchWebsiteSSLItemsCount = searchWebsiteSSLResp.Data.Total + searchWebsiteSSLItemsCount = int(searchWebsiteSSLResp.Data.Total) } case *onepanelsdkv2.Client: { searchWebsiteSSLReq := &onepanelsdkv2.SearchWebsiteSSLRequest{ - Page: searchWebsiteSSLPageNumber, - PageSize: searchWebsiteSSLPageSize, + Page: int32(searchWebsiteSSLPageNumber), + PageSize: int32(searchWebsiteSSLPageSize), } searchWebsiteSSLResp, err := sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq) m.logger.Debug("sdk request '1panel.SearchWebsiteSSL'", slog.Any("request", searchWebsiteSSLReq), slog.Any("response", searchWebsiteSSLResp)) @@ -168,20 +170,22 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request '1panel.SearchWebsiteSSL': %w", err) } - if searchWebsiteSSLResp.Data != nil { - for _, sslItem := range searchWebsiteSSLResp.Data.Items { - if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) && - strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) { - // 如果已存在相同证书,直接返回 - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", sslItem.ID), - CertName: sslItem.Description, - }, nil - } + if searchWebsiteSSLResp.Data == nil { + break + } + + for _, sslItem := range searchWebsiteSSLResp.Data.Items { + if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) && + strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) { + // 如果已存在相同证书,直接返回 + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", sslItem.ID), + CertName: sslItem.Description, + }, nil } } - searchWebsiteSSLItemsCount = searchWebsiteSSLResp.Data.Total + searchWebsiteSSLItemsCount = int(searchWebsiteSSLResp.Data.Total) } default: diff --git a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go index 7b87cdba6..178711738 100644 --- a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go +++ b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go @@ -73,8 +73,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listusercertificateorder // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-getusercertificatedetail - listUserCertificateOrderPage := int64(1) - listUserCertificateOrderLimit := int64(50) + listUserCertificateOrderPage := 1 + listUserCertificateOrderLimit := 50 for { select { case <-ctx.Done(): @@ -84,63 +84,65 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listUserCertificateOrderReq := &alicas.ListUserCertificateOrderRequest{ ResourceGroupId: lo.EmptyableToPtr(m.config.ResourceGroupId), - CurrentPage: tea.Int64(listUserCertificateOrderPage), - ShowSize: tea.Int64(listUserCertificateOrderLimit), + CurrentPage: tea.Int64(int64(listUserCertificateOrderPage)), + ShowSize: tea.Int64(int64(listUserCertificateOrderLimit)), OrderType: tea.String("CERT"), } - listUserCertificateOrderResp, err := m.sdkClient.ListUserCertificateOrderWithContext(context.TODO(), listUserCertificateOrderReq, &dara.RuntimeOptions{}) + listUserCertificateOrderResp, err := m.sdkClient.ListUserCertificateOrderWithContext(ctx, listUserCertificateOrderReq, &dara.RuntimeOptions{}) m.logger.Debug("sdk request 'cas.ListUserCertificateOrder'", slog.Any("request", listUserCertificateOrderReq), slog.Any("response", listUserCertificateOrderResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.ListUserCertificateOrder': %w", err) } - if listUserCertificateOrderResp.Body.CertificateOrderList != nil { - for _, certOrder := range listUserCertificateOrderResp.Body.CertificateOrderList { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(certOrder.CommonName)) { - continue - } - - // 再对比证书序列号 - // 注意阿里云 CAS 会在序列号前补零,需去除后再比较 - oldCertSN := strings.TrimLeft(tea.StringValue(certOrder.SerialNo), "0") - newCertSN := strings.TrimLeft(certX509.SerialNumber.Text(16), "0") - if !strings.EqualFold(newCertSN, oldCertSN) { - continue - } - - // 最后对比证书内容 - getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ - CertId: certOrder.CertificateId, - } - getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(context.TODO(), getUserCertificateDetailReq, &dara.RuntimeOptions{}) - m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err) - } else { - if !xcert.EqualCertificatesFromPEM(certPEM, tea.StringValue(getUserCertificateDetailResp.Body.Cert)) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", tea.Int64Value(certOrder.CertificateId)), - CertName: *certOrder.Name, - ExtendedData: map[string]any{ - "InstanceId": tea.StringValue(getUserCertificateDetailResp.Body.InstanceId), - "CertIdentifier": tea.StringValue(getUserCertificateDetailResp.Body.CertIdentifier), - }, - }, nil - } - } - - if listUserCertificateOrderResp.Body.CertificateOrderList == nil || len(listUserCertificateOrderResp.Body.CertificateOrderList) < int(listUserCertificateOrderLimit) { + if listUserCertificateOrderResp.Body == nil { break - } else { - listUserCertificateOrderPage++ } + + for _, certItem := range listUserCertificateOrderResp.Body.CertificateOrderList { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(certItem.CommonName)) { + continue + } + + // 对比证书序列号 + // 注意阿里云 CAS 会在序列号前补零,需去除后再比较 + oldCertSN := strings.TrimLeft(tea.StringValue(certItem.SerialNo), "0") + newCertSN := strings.TrimLeft(certX509.SerialNumber.Text(16), "0") + if !strings.EqualFold(newCertSN, oldCertSN) { + continue + } + + // 对比证书内容 + getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ + CertId: certItem.CertificateId, + } + getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(ctx, getUserCertificateDetailReq, &dara.RuntimeOptions{}) + m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err) + } else { + if !xcert.EqualCertificatesFromPEM(certPEM, tea.StringValue(getUserCertificateDetailResp.Body.Cert)) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", tea.Int64Value(certItem.CertificateId)), + CertName: *certItem.Name, + ExtendedData: map[string]any{ + "InstanceId": tea.StringValue(getUserCertificateDetailResp.Body.InstanceId), + "CertIdentifier": tea.StringValue(getUserCertificateDetailResp.Body.CertIdentifier), + }, + }, nil + } + + if len(listUserCertificateOrderResp.Body.CertificateOrderList) < listUserCertificateOrderLimit { + break + } + + listUserCertificateOrderPage++ } // 生成新证书名(需符合阿里云命名规则) @@ -154,7 +156,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey Cert: tea.String(certPEM), Key: tea.String(privkeyPEM), } - uploadUserCertificateResp, err := m.sdkClient.UploadUserCertificateWithContext(context.TODO(), uploadUserCertificateReq, &dara.RuntimeOptions{}) + uploadUserCertificateResp, err := m.sdkClient.UploadUserCertificateWithContext(ctx, uploadUserCertificateReq, &dara.RuntimeOptions{}) m.logger.Debug("sdk request 'cas.UploadUserCertificate'", slog.Any("request", uploadUserCertificateReq), slog.Any("response", uploadUserCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.UploadUserCertificate': %w", err) @@ -166,7 +168,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey CertId: uploadUserCertificateResp.Body.CertId, CertFilter: tea.Bool(true), } - getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(context.TODO(), getUserCertificateDetailReq, &dara.RuntimeOptions{}) + getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(ctx, getUserCertificateDetailReq, &dara.RuntimeOptions{}) m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err) diff --git a/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go b/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go index 398a9fb9d..e9e18dd7e 100644 --- a/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go +++ b/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go @@ -87,22 +87,22 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if describeServerCertificatesResp.Body.ServerCertificates != nil && describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate != nil { fingerprint := sha256.Sum256(certX509.Raw) fingerprintHex := hex.EncodeToString(fingerprint[:]) - for _, serverCert := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate { - if tea.Int32Value(serverCert.IsAliCloudCertificate) != 0 { + for _, certItem := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate { + if tea.Int32Value(certItem.IsAliCloudCertificate) != 0 { continue } - if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(serverCert.CommonName)) { + if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(certItem.CommonName)) { continue } - if !strings.EqualFold(fingerprintHex, strings.ReplaceAll(tea.StringValue(serverCert.Fingerprint), ":", "")) { + if !strings.EqualFold(fingerprintHex, strings.ReplaceAll(tea.StringValue(certItem.Fingerprint), ":", "")) { continue } // 如果已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: *serverCert.ServerCertificateId, - CertName: *serverCert.ServerCertificateName, + CertId: *certItem.ServerCertificateId, + CertName: *certItem.ServerCertificateName, }, nil } } diff --git a/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go b/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go index 4ed3482dc..0e3dbe2c5 100644 --- a/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go +++ b/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go @@ -73,8 +73,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 获取证书列表,避免重复上传 // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html - var listCertificatesNextToken *string = nil - var listCertificatesMaxItems int32 = 1000 + // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_GetCertificate.html + listCertificatesNextToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -84,34 +84,33 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listCertificatesReq := &awsacm.ListCertificatesInput{ NextToken: listCertificatesNextToken, - MaxItems: aws.Int32(listCertificatesMaxItems), + MaxItems: aws.Int32(1000), } - listCertificatesResp, err := m.sdkClient.ListCertificates(context.TODO(), listCertificatesReq) + listCertificatesResp, err := m.sdkClient.ListCertificates(ctx, listCertificatesReq) m.logger.Debug("sdk request 'acm.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.ListCertificates': %w", err) } - for _, certSummary := range listCertificatesResp.CertificateSummaryList { - // 先对比证书有效期 - if certSummary.NotBefore == nil || !certSummary.NotBefore.Equal(certX509.NotBefore) { + for _, certItem := range listCertificatesResp.CertificateSummaryList { + // 对比证书有效期 + if certItem.NotBefore == nil || !certItem.NotBefore.Equal(certX509.NotBefore) { continue } - if certSummary.NotAfter == nil || !certSummary.NotAfter.Equal(certX509.NotAfter) { + if certItem.NotAfter == nil || !certItem.NotAfter.Equal(certX509.NotAfter) { continue } - // 再对比证书多域名 - if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certSummary.SubjectAlternativeNameSummaries, ",")) { + // 对比证书多域名 + if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certItem.SubjectAlternativeNameSummaries, ",")) { continue } - // 最后对比证书内容 - // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_GetCertificate.html + // 对比证书内容 getCertificateReq := &awsacm.GetCertificateInput{ - CertificateArn: certSummary.CertificateArn, + CertificateArn: certItem.CertificateArn, } - getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), getCertificateReq) + getCertificateResp, err := m.sdkClient.GetCertificate(ctx, getCertificateReq) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.GetCertificate': %w", err) } else { @@ -123,15 +122,15 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: *certSummary.CertificateArn, + CertId: *certItem.CertificateArn, }, nil } - if listCertificatesResp.NextToken == nil || len(listCertificatesResp.CertificateSummaryList) < int(listCertificatesMaxItems) { + if len(listCertificatesResp.CertificateSummaryList) == 0 || listCertificatesResp.NextToken == nil { break - } else { - listCertificatesNextToken = listCertificatesResp.NextToken } + + listCertificatesNextToken = listCertificatesResp.NextToken } // 导入证书 @@ -141,7 +140,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey CertificateChain: ([]byte)(intermediaCertPEM), PrivateKey: ([]byte)(privkeyPEM), } - importCertificateResp, err := m.sdkClient.ImportCertificate(context.TODO(), importCertificateReq) + importCertificateResp, err := m.sdkClient.ImportCertificate(ctx, importCertificateReq) m.logger.Debug("sdk request 'acm.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.ImportCertificate': %w", err) diff --git a/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go b/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go index ab2c7be89..e4cdee8da 100644 --- a/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go +++ b/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go @@ -76,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 获取证书列表,避免重复上传 // REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_ListServerCertificates.html - var listServerCertificatesMarker *string = nil - var listServerCertificatesMaxItems int32 = 1000 + // REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_GetServerCertificate.html + listServerCertificatesMarker := (*string)(nil) for { select { case <-ctx.Done(): @@ -87,34 +87,33 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listServerCertificatesReq := &awsiam.ListServerCertificatesInput{ Marker: listServerCertificatesMarker, - MaxItems: aws.Int32(listServerCertificatesMaxItems), + MaxItems: aws.Int32(1000), } if m.config.CertificatePath != "" { listServerCertificatesReq.PathPrefix = aws.String(m.config.CertificatePath) } - listServerCertificatesResp, err := m.sdkClient.ListServerCertificates(context.TODO(), listServerCertificatesReq) + listServerCertificatesResp, err := m.sdkClient.ListServerCertificates(ctx, listServerCertificatesReq) m.logger.Debug("sdk request 'iam.ListServerCertificates'", slog.Any("request", listServerCertificatesReq), slog.Any("response", listServerCertificatesResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'iam.ListServerCertificates': %w", err) } - for _, certMeta := range listServerCertificatesResp.ServerCertificateMetadataList { - // 先对比证书路径 - if m.config.CertificatePath != "" && aws.ToString(certMeta.Path) != m.config.CertificatePath { + for _, certItem := range listServerCertificatesResp.ServerCertificateMetadataList { + // 对比证书路径 + if m.config.CertificatePath != "" && aws.ToString(certItem.Path) != m.config.CertificatePath { continue } - // 先对比证书有效期 - if certMeta.Expiration == nil || !certMeta.Expiration.Equal(certX509.NotAfter) { + // 对比证书有效期 + if certItem.Expiration == nil || !certItem.Expiration.Equal(certX509.NotAfter) { continue } - // 最后对比证书内容 - // REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_GetServerCertificate.html + // 对比证书内容 getServerCertificateReq := &awsiam.GetServerCertificateInput{ - ServerCertificateName: certMeta.ServerCertificateName, + ServerCertificateName: certItem.ServerCertificateName, } - getServerCertificateResp, err := m.sdkClient.GetServerCertificate(context.TODO(), getServerCertificateReq) + getServerCertificateResp, err := m.sdkClient.GetServerCertificate(ctx, getServerCertificateReq) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'iam.GetServerCertificate': %w", err) } else { @@ -126,16 +125,16 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: aws.ToString(certMeta.ServerCertificateId), - CertName: aws.ToString(certMeta.ServerCertificateName), + CertId: aws.ToString(certItem.ServerCertificateId), + CertName: aws.ToString(certItem.ServerCertificateName), }, nil } - if listServerCertificatesResp.Marker == nil || len(listServerCertificatesResp.ServerCertificateMetadataList) < int(listServerCertificatesMaxItems) { + if len(listServerCertificatesResp.ServerCertificateMetadataList) == 0 || listServerCertificatesResp.Marker == nil { break - } else { - listServerCertificatesMarker = listServerCertificatesResp.Marker } + + listServerCertificatesMarker = listServerCertificatesResp.Marker } // 生成新证书名(需符合 AWS IAM 命名规则) @@ -153,7 +152,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if m.config.CertificatePath == "" { uploadServerCertificateReq.Path = aws.String("/") } - uploadServerCertificateResp, err := m.sdkClient.UploadServerCertificate(context.TODO(), uploadServerCertificateReq) + uploadServerCertificateResp, err := m.sdkClient.UploadServerCertificate(ctx, uploadServerCertificateReq) m.logger.Debug("sdk request 'iam.UploadServerCertificate'", slog.Any("request", uploadServerCertificateReq), slog.Any("response", uploadServerCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'iam.UploadServerCertificate': %w", err) diff --git a/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go b/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go index beda65415..0087348a3 100644 --- a/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go +++ b/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go @@ -86,35 +86,35 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'keyvault.GetCertificates': %w", err) } - for _, certProp := range page.Value { - // 先对比证书有效期 - if certProp.Attributes == nil { + for _, certItem := range page.Value { + // 对比证书有效期 + if certItem.Attributes == nil { continue } - if certProp.Attributes.NotBefore == nil || !certProp.Attributes.NotBefore.Equal(certX509.NotBefore) { + if certItem.Attributes.NotBefore == nil || !certItem.Attributes.NotBefore.Equal(certX509.NotBefore) { continue } - if certProp.Attributes.Expires == nil || !certProp.Attributes.Expires.Equal(certX509.NotAfter) { + if certItem.Attributes.Expires == nil || !certItem.Attributes.Expires.Equal(certX509.NotAfter) { continue } - // 再对比 Tag 中的通用名称 - if v, ok := certProp.Tags[TAG_CERTCN]; !ok || v == nil { + // 对比 Tag 中的通用名称 + if v, ok := certItem.Tags[TAG_CERTCN]; !ok || v == nil { continue } else if *v != certCN { continue } - // 再对比 Tag 中的序列号 - if v, ok := certProp.Tags[TAG_CERTSN]; !ok || v == nil { + // 对比 Tag 中的序列号 + if v, ok := certItem.Tags[TAG_CERTSN]; !ok || v == nil { continue } else if *v != certSN { continue } - // 最后对比证书内容 - getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), certProp.ID.Name(), certProp.ID.Version(), nil) - m.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", certProp.ID.Name()), slog.String("request.certificateVersion", certProp.ID.Version()), slog.Any("response", getCertificateResp)) + // 对比证书内容 + getCertificateResp, err := m.sdkClient.GetCertificate(ctx, certItem.ID.Name(), certItem.ID.Version(), nil) + m.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", certItem.ID.Name()), slog.String("request.certificateVersion", certItem.ID.Version()), slog.Any("response", getCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'keyvault.GetCertificate': %w", err) } else { @@ -126,8 +126,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: string(*certProp.ID), - CertName: certProp.ID.Name(), + CertId: string(*certItem.ID), + CertName: certItem.ID.Name(), }, nil } } @@ -157,7 +157,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey TAG_CERTSN: to.Ptr(certSN), }, } - importCertificateResp, err := m.sdkClient.ImportCertificate(context.TODO(), certName, importCertificateParams, nil) + importCertificateResp, err := m.sdkClient.ImportCertificate(ctx, certName, importCertificateParams, nil) m.logger.Debug("sdk request 'keyvault.ImportCertificate'", slog.String("request.certificateName", certName), slog.Any("request.parameters", importCertificateParams), slog.Any("response", importCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'keyvault.ImportCertificate': %w", err) diff --git a/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go b/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go index a99b1a8d5..240822f84 100644 --- a/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go +++ b/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go @@ -60,34 +60,34 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, err } - // 遍历证书列表,避免重复上传 + // 查看证书列表 // REF: https://cloud.baidu.com/doc/Reference/s/Gjwvz27xu#35-%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E5%88%97%E8%A1%A8%E8%AF%A6%E6%83%85 listCertDetail, err := m.sdkClient.ListCertDetail() m.logger.Debug("sdk request 'cert.ListCertDetail'", slog.Any("response", listCertDetail)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cert.ListCertDetail': %w", err) } else { - for _, certDetail := range listCertDetail.Certs { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certDetail.CertCommonName) { + for _, certItem := range listCertDetail.Certs { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CertCommonName) { continue } - // 再对比证书有效期 - oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certDetail.CertStartTime) - oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certDetail.CertStopTime) + // 对比证书有效期 + oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certItem.CertStartTime) + oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certItem.CertStopTime) if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { continue } - // 再对比证书多域名 - if certDetail.CertDNSNames != strings.Join(certX509.DNSNames, ",") { + // 对比证书多域名 + if certItem.CertDNSNames != strings.Join(certX509.DNSNames, ",") { continue } - // 最后对比证书内容 - getCertDetailResp, err := m.sdkClient.GetCertRawData(certDetail.CertId) - m.logger.Debug("sdk request 'cert.GetCertRawData'", slog.Any("certId", certDetail.CertId), slog.Any("response", getCertDetailResp)) + // 对比证书内容 + getCertDetailResp, err := m.sdkClient.GetCertRawData(certItem.CertId) + m.logger.Debug("sdk request 'cert.GetCertRawData'", slog.Any("certId", certItem.CertId), slog.Any("response", getCertDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cert.GetCertRawData': %w", err) } else { @@ -99,8 +99,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certDetail.CertId, - CertName: certDetail.CertName, + CertId: certItem.CertId, + CertName: certItem.CertName, }, nil } } diff --git a/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go b/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go index 32a4b6ed8..bd089f486 100644 --- a/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go +++ b/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go @@ -65,14 +65,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-listcertinfo - listCertInfoPageNum := int64(1) - listCertInfoPageSize := int64(100) - listCertInfoTotal := 0 - listCertInfoReq := &bytepluscdn.ListCertInfoRequest{ - PageNum: bytepluscdn.GetInt64Ptr(listCertInfoPageNum), - PageSize: bytepluscdn.GetInt64Ptr(listCertInfoPageSize), - Source: bytepluscdn.GetStrPtr("cert_center"), - } + listCertInfoPageNum := 1 + listCertInfoPageSize := 100 for { select { case <-ctx.Done(): @@ -80,36 +74,43 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey default: } + listCertInfoReq := &bytepluscdn.ListCertInfoRequest{ + PageNum: bytepluscdn.GetInt64Ptr(int64(listCertInfoPageNum)), + PageSize: bytepluscdn.GetInt64Ptr(int64(listCertInfoPageSize)), + Source: bytepluscdn.GetStrPtr("cert_center"), + } listCertInfoResp, err := m.sdkClient.ListCertInfo(listCertInfoReq) m.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCertInfo': %w", err) } - if listCertInfoResp.Result.CertInfo != nil { - for _, certInfo := range listCertInfoResp.Result.CertInfo { - fingerprintSha1 := sha1.Sum(certX509.Raw) - fingerprintSha256 := sha256.Sum256(certX509.Raw) - isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certInfo.CertFingerprint.Sha1) && - strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certInfo.CertFingerprint.Sha256) - // 如果已存在相同证书,直接返回 - if isSameCert { - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certInfo.CertId, - CertName: certInfo.Desc, - }, nil - } + for _, certItem := range listCertInfoResp.Result.CertInfo { + // 对比证书 SHA-1 摘要 + fingerprintSha1 := sha1.Sum(certX509.Raw) + if !strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certItem.CertFingerprint.Sha1) { + continue } + + // 对比证书 SHA-256 摘要 + fingerprintSha256 := sha256.Sum256(certX509.Raw) + if !strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certItem.CertFingerprint.Sha256) { + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.CertId, + CertName: certItem.Desc, + }, nil } - listCertInfoLen := len(listCertInfoResp.Result.CertInfo) - if listCertInfoLen < int(listCertInfoPageSize) || int(listCertInfoResp.Result.Total) <= listCertInfoTotal+listCertInfoLen { + if len(listCertInfoResp.Result.CertInfo) < listCertInfoPageSize { break - } else { - listCertInfoPageNum++ - listCertInfoTotal += listCertInfoLen } + + listCertInfoPageNum++ } // 生成新证书名(需符合 BytePlus 命名规则) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go index 3814a3723..2d96b2722 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go @@ -65,8 +65,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询用户名下证书列表,避免重复上传 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13175&data=174&isNormal=1&vid=167 - listCertPage := int32(1) - listCertPerPage := int32(1000) + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13015&data=174&isNormal=1&vid=167 + listCertPage := 1 + listCertPerPage := 1000 for { select { case <-ctx.Done(): @@ -75,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } listCertsReq := &ctyunao.ListCertsRequest{ - Page: lo.ToPtr(listCertPage), - PerPage: lo.ToPtr(listCertPerPage), + Page: lo.ToPtr(int32(listCertPage)), + PerPage: lo.ToPtr(int32(listCertPerPage)), UsageMode: lo.ToPtr(int32(0)), } listCertsResp, err := m.sdkClient.ListCerts(listCertsReq) @@ -85,55 +86,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'ao.ListCerts': %w", err) } - if listCertsResp.ReturnObj != nil { - for _, certRecord := range listCertsResp.ReturnObj.Results { - // 对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) { - continue - } - - // 对比证书扩展名称 - if !slices.Equal(certX509.DNSNames, certRecord.SANs) { - continue - } - - // 对比证书有效期 - if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) { - continue - } else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) { - continue - } - - // 最后对比证书内容 - // 查询证书详情 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13015&data=174&isNormal=1&vid=167 - queryCertReq := &ctyunao.QueryCertRequest{ - Id: lo.ToPtr(certRecord.Id), - } - queryCertResp, err := m.sdkClient.QueryCert(queryCertReq) - m.logger.Debug("sdk request 'ao.QueryCert'", slog.Any("request", queryCertReq), slog.Any("response", queryCertResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'ao.QueryCert': %w", err) - } else if queryCertResp.ReturnObj != nil && queryCertResp.ReturnObj.Result != nil { - if !xcert.EqualCertificatesFromPEM(certPEM, queryCertResp.ReturnObj.Result.Certs) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", queryCertResp.ReturnObj.Result.Id), - CertName: queryCertResp.ReturnObj.Result.Name, - }, nil - } - } - - if listCertsResp.ReturnObj == nil || len(listCertsResp.ReturnObj.Results) < int(listCertPerPage) { + if listCertsResp.ReturnObj == nil { break - } else { - listCertPage++ } + + for _, certItem := range listCertsResp.ReturnObj.Results { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CN) { + continue + } + + // 对比证书扩展名称 + if !slices.Equal(certX509.DNSNames, certItem.SANs) { + continue + } + + // 对比证书有效期 + if !certX509.NotBefore.Equal(time.Unix(certItem.IssueTime, 0).UTC()) { + continue + } else if !certX509.NotAfter.Equal(time.Unix(certItem.ExpiresTime, 0).UTC()) { + continue + } + + // 对比证书内容 + queryCertReq := &ctyunao.QueryCertRequest{ + Id: lo.ToPtr(certItem.Id), + } + queryCertResp, err := m.sdkClient.QueryCert(queryCertReq) + m.logger.Debug("sdk request 'ao.QueryCert'", slog.Any("request", queryCertReq), slog.Any("response", queryCertResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ao.QueryCert': %w", err) + } else if queryCertResp.ReturnObj != nil && queryCertResp.ReturnObj.Result != nil { + if !xcert.EqualCertificatesFromPEM(certPEM, queryCertResp.ReturnObj.Result.Certs) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", queryCertResp.ReturnObj.Result.Id), + CertName: queryCertResp.ReturnObj.Result.Name, + }, nil + } + + if len(listCertsResp.ReturnObj.Results) < listCertPerPage { + break + } + + listCertPage++ } // 生成新证书名(需符合天翼云命名规则) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go b/pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go index b5647cc3f..8feb53996 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go @@ -65,8 +65,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10901&data=161&isNormal=1&vid=154 - queryCertListPage := int32(1) - queryCertListPerPage := int32(1000) + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10899&data=161&isNormal=1&vid=154 + queryCertListPage := 1 + queryCertListPerPage := 1000 for { select { case <-ctx.Done(): @@ -75,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } queryCertListReq := &ctyuncdn.QueryCertListRequest{ - Page: lo.ToPtr(queryCertListPage), - PerPage: lo.ToPtr(queryCertListPerPage), + Page: lo.ToPtr(int32(queryCertListPage)), + PerPage: lo.ToPtr(int32(queryCertListPerPage)), UsageMode: lo.ToPtr(int32(0)), } queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq) @@ -85,55 +86,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryCertList': %w", err) } - if queryCertListResp.ReturnObj != nil { - for _, certRecord := range queryCertListResp.ReturnObj.Results { - // 对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) { - continue - } - - // 对比证书扩展名称 - if !slices.Equal(certX509.DNSNames, certRecord.SANs) { - continue - } - - // 对比证书有效期 - if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) { - continue - } else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) { - continue - } - - // 最后对比证书内容 - // 查询证书详情 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10899&data=161&isNormal=1&vid=154 - queryCertDetailReq := &ctyuncdn.QueryCertDetailRequest{ - Id: lo.ToPtr(certRecord.Id), - } - queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) - m.logger.Debug("sdk request 'cdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryCertDetail': %w", err) - } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { - if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), - CertName: queryCertDetailResp.ReturnObj.Result.Name, - }, nil - } - } - - if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) { + if queryCertListResp.ReturnObj == nil { break - } else { - queryCertListPage++ } + + for _, certItem := range queryCertListResp.ReturnObj.Results { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CN) { + continue + } + + // 对比证书扩展名称 + if !slices.Equal(certX509.DNSNames, certItem.SANs) { + continue + } + + // 对比证书有效期 + if !certX509.NotBefore.Equal(time.Unix(certItem.IssueTime, 0).UTC()) { + continue + } else if !certX509.NotAfter.Equal(time.Unix(certItem.ExpiresTime, 0).UTC()) { + continue + } + + // 对比证书内容 + queryCertDetailReq := &ctyuncdn.QueryCertDetailRequest{ + Id: lo.ToPtr(certItem.Id), + } + queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) + m.logger.Debug("sdk request 'cdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryCertDetail': %w", err) + } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { + if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), + CertName: queryCertDetailResp.ReturnObj.Result.Name, + }, nil + } + + if len(queryCertListResp.ReturnObj.Results) < queryCertListPerPage { + break + } + + queryCertListPage++ } // 生成新证书名(需符合天翼云命名规则) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go b/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go index 104ccce62..8b1ffb16b 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go @@ -58,8 +58,11 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历证书列表,避免重复上传 - if res, _ := m.findCertIfExists(ctx, certPEM); res != nil { + // 避免重复上传 + if res, err := m.tryFindCert(ctx, certPEM); err != nil { + return nil, err + } else if res != nil { + m.logger.Info("ssl certificate already exists") return res, nil } @@ -85,7 +88,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey m.logger.Debug("sdk request 'cms.UploadCertificate'", slog.Any("request", uploadCertificateReq), slog.Any("response", uploadCertificateResp)) if err != nil { if uploadCertificateResp != nil && uploadCertificateResp.GetError() == "CCMS_100000067" { - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res == nil { return nil, errors.New("ctyun cms: no certificate found") @@ -98,17 +101,17 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'cms.UploadCertificate': %w", err) } - // 遍历证书列表,获取刚刚上传证书 ID - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + // 获取刚刚上传证书 ID + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res == nil { - return nil, fmt.Errorf("no ssl certificate found, may be upload failed") + return nil, fmt.Errorf("could not find ssl certificate, may be upload failed") } else { return res, nil } } -func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { +func (m *SSLManagerProvider) tryFindCert(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { // 解析证书内容 certX509, err := xcert.ParseCertificateFromPEM(certPEM) if err != nil { @@ -117,8 +120,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin // 查询用户证书列表 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=152&api=17233&data=204&isNormal=1&vid=283 - getCertificateListPageNum := int32(1) - getCertificateListPageSize := int32(10) + getCertificateListPageNum := 1 + getCertificateListPageSize := 10 for { select { case <-ctx.Done(): @@ -127,8 +130,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin } getCertificateListReq := &ctyuncms.GetCertificateListRequest{ - PageNum: lo.ToPtr(getCertificateListPageNum), - PageSize: lo.ToPtr(getCertificateListPageSize), + PageNum: lo.ToPtr(int32(getCertificateListPageNum)), + PageSize: lo.ToPtr(int32(getCertificateListPageSize)), Keyword: lo.ToPtr(certX509.Subject.CommonName), Origin: lo.ToPtr("UPLOAD"), } @@ -138,44 +141,45 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request 'cms.GetCertificateList': %w", err) } - if getCertificateListResp.ReturnObj != nil { + if getCertificateListResp.ReturnObj == nil { + break + } + + for _, certItem := range getCertificateListResp.ReturnObj.List { + // 对比证书名称 + if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), certItem.DomainName) { + continue + } + + // 对比证书有效期 + oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certItem.IssueTime) + oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certItem.ExpireTime) + if !certX509.NotBefore.Equal(oldCertNotBefore) { + continue + } else if !certX509.NotAfter.Equal(oldCertNotAfter) { + continue + } + + // 对比证书指纹 fingerprint := sha1.Sum(certX509.Raw) fingerprintHex := hex.EncodeToString(fingerprint[:]) - - for _, certRecord := range getCertificateListResp.ReturnObj.List { - // 对比证书名称 - if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), certRecord.DomainName) { - continue - } - - // 对比证书有效期 - oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certRecord.IssueTime) - oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certRecord.ExpireTime) - if !certX509.NotBefore.Equal(oldCertNotBefore) { - continue - } else if !certX509.NotAfter.Equal(oldCertNotAfter) { - continue - } - - // 对比证书指纹 - if !strings.EqualFold(fingerprintHex, certRecord.Fingerprint) { - continue - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: string(*&certRecord.Id), - CertName: certRecord.Name, - }, nil + if !strings.EqualFold(fingerprintHex, certItem.Fingerprint) { + continue } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.Id, + CertName: certItem.Name, + }, nil } - if getCertificateListResp.ReturnObj == nil || len(getCertificateListResp.ReturnObj.List) < int(getCertificateListPageSize) { + if len(getCertificateListResp.ReturnObj.List) < getCertificateListPageSize { break - } else { - getCertificateListPageNum++ } + + getCertificateListPageNum++ } return nil, nil diff --git a/pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go b/pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go index 2d974ea53..d5808632f 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go @@ -68,13 +68,13 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err) } else { - for _, certRecord := range listCertificatesResp.ReturnObj { + for _, certItem := range listCertificatesResp.ReturnObj { // 如果已存在相同证书,直接返回 - if xcert.EqualCertificatesFromPEM(certPEM, certRecord.Certificate) { + if xcert.EqualCertificatesFromPEM(certPEM, certItem.Certificate) { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certRecord.ID, - CertName: certRecord.Name, + CertId: certItem.ID, + CertName: certItem.Name, }, nil } } diff --git a/pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go b/pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go index ebe8ee406..04a1b88fd 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go @@ -65,8 +65,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10838&data=173&isNormal=1&vid=166 - queryCertListPage := int32(1) - queryCertListPerPage := int32(1000) + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10837&data=173&isNormal=1&vid=166 + queryCertListPage := 1 + queryCertListPerPage := 1000 for { select { case <-ctx.Done(): @@ -75,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } queryCertListReq := &ctyunicdn.QueryCertListRequest{ - Page: lo.ToPtr(queryCertListPage), - PerPage: lo.ToPtr(queryCertListPerPage), + Page: lo.ToPtr(int32(queryCertListPage)), + PerPage: lo.ToPtr(int32(queryCertListPerPage)), UsageMode: lo.ToPtr(int32(0)), } queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq) @@ -85,55 +86,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryCertList': %w", err) } - if queryCertListResp.ReturnObj != nil { - for _, certRecord := range queryCertListResp.ReturnObj.Results { - // 对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) { - continue - } - - // 对比证书扩展名称 - if !slices.Equal(certX509.DNSNames, certRecord.SANs) { - continue - } - - // 对比证书有效期 - if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) { - continue - } else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) { - continue - } - - // 最后对比证书内容 - // 查询证书详情 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10837&data=173&isNormal=1&vid=166 - queryCertDetailReq := &ctyunicdn.QueryCertDetailRequest{ - Id: lo.ToPtr(certRecord.Id), - } - queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) - m.logger.Debug("sdk request 'icdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryCertDetail': %w", err) - } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { - if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), - CertName: queryCertDetailResp.ReturnObj.Result.Name, - }, nil - } - } - - if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) { + if queryCertListResp.ReturnObj == nil { break - } else { - queryCertListPage++ } + + for _, certItem := range queryCertListResp.ReturnObj.Results { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CN) { + continue + } + + // 对比证书扩展名称 + if !slices.Equal(certX509.DNSNames, certItem.SANs) { + continue + } + + // 对比证书有效期 + if !certX509.NotBefore.Equal(time.Unix(certItem.IssueTime, 0).UTC()) { + continue + } else if !certX509.NotAfter.Equal(time.Unix(certItem.ExpiresTime, 0).UTC()) { + continue + } + + // 对比证书内容 + queryCertDetailReq := &ctyunicdn.QueryCertDetailRequest{ + Id: lo.ToPtr(certItem.Id), + } + queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) + m.logger.Debug("sdk request 'icdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryCertDetail': %w", err) + } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { + if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), + CertName: queryCertDetailResp.ReturnObj.Result.Name, + }, nil + } + + if len(queryCertListResp.ReturnObj.Results) < queryCertListPerPage { + break + } + + queryCertListPage++ } // 生成新证书名(需符合天翼云命名规则) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go b/pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go index dafa4c034..73066322a 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go @@ -65,8 +65,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11452&data=183&isNormal=1&vid=261 - queryCertListPage := int32(1) - queryCertListPerPage := int32(1000) + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11449&data=183&isNormal=1&vid=261 + queryCertListPage := 1 + queryCertListPerPage := 1000 for { select { case <-ctx.Done(): @@ -75,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } queryCertListReq := &ctyunlvdn.QueryCertListRequest{ - Page: lo.ToPtr(queryCertListPage), - PerPage: lo.ToPtr(queryCertListPerPage), + Page: lo.ToPtr(int32(queryCertListPage)), + PerPage: lo.ToPtr(int32(queryCertListPerPage)), UsageMode: lo.ToPtr(int32(0)), } queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq) @@ -85,55 +86,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryCertList': %w", err) } - if queryCertListResp.ReturnObj != nil { - for _, certRecord := range queryCertListResp.ReturnObj.Results { - // 对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) { - continue - } - - // 对比证书扩展名称 - if !slices.Equal(certX509.DNSNames, certRecord.SANs) { - continue - } - - // 对比证书有效期 - if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) { - continue - } else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) { - continue - } - - // 最后对比证书内容 - // 查询证书详情 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11449&data=183&isNormal=1&vid=261 - queryCertDetailReq := &ctyunlvdn.QueryCertDetailRequest{ - Id: lo.ToPtr(certRecord.Id), - } - queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) - m.logger.Debug("sdk request 'lvdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryCertDetail': %w", err) - } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { - if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), - CertName: queryCertDetailResp.ReturnObj.Result.Name, - }, nil - } - } - - if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) { + if queryCertListResp.ReturnObj == nil { break - } else { - queryCertListPage++ } + + for _, certItem := range queryCertListResp.ReturnObj.Results { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CN) { + continue + } + + // 对比证书扩展名称 + if !slices.Equal(certX509.DNSNames, certItem.SANs) { + continue + } + + // 对比证书有效期 + if !certX509.NotBefore.Equal(time.Unix(certItem.IssueTime, 0).UTC()) { + continue + } else if !certX509.NotAfter.Equal(time.Unix(certItem.ExpiresTime, 0).UTC()) { + continue + } + + // 对比证书内容 + queryCertDetailReq := &ctyunlvdn.QueryCertDetailRequest{ + Id: lo.ToPtr(certItem.Id), + } + queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) + m.logger.Debug("sdk request 'lvdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryCertDetail': %w", err) + } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { + if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), + CertName: queryCertDetailResp.ReturnObj.Result.Name, + }, nil + } + + if len(queryCertListResp.ReturnObj.Results) < queryCertListPerPage { + break + } + + queryCertListPage++ } // 生成新证书名(需符合天翼云命名规则) diff --git a/pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go b/pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go index 5b5b6b5af..d455ba279 100644 --- a/pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go +++ b/pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go @@ -62,7 +62,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey Automated: false, ValidateRootCA: false, } - createCertificateResp, err := m.sdkClient.Create(context.TODO(), createCertificateReq) + createCertificateResp, err := m.sdkClient.Create(ctx, createCertificateReq) m.logger.Debug("sdk request 'sslcerts.Create'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Create': %w", err) diff --git a/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go b/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go index 10c2a9f4a..7ce04cd81 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go @@ -67,10 +67,9 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历查询已有证书,避免重复上传 + // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-elb/ListCertificates.html - listCertificatesLimit := int32(2000) - var listCertificatesMarker *string = nil + listCertificatesMarker := (*string)(nil) for { select { case <-ctx.Done(): @@ -79,9 +78,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } listCertificatesReq := &hcelbmodel.ListCertificatesRequest{ - Limit: lo.ToPtr(listCertificatesLimit), Marker: listCertificatesMarker, - Type: &[]string{"server"}, + Limit: lo.ToPtr(int32(2000)), + Type: lo.ToPtr([]string{"server"}), } listCertificatesResp, err := m.sdkClient.ListCertificates(listCertificatesReq) m.logger.Debug("sdk request 'elb.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp)) @@ -89,24 +88,26 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err) } - if listCertificatesResp.Certificates != nil { - for _, certInfo := range *listCertificatesResp.Certificates { - // 如果已存在相同证书,直接返回 - if xcert.EqualCertificatesFromPEM(certPEM, certInfo.Certificate) { - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certInfo.Id, - CertName: certInfo.Name, - }, nil - } + if listCertificatesResp.Certificates == nil { + break + } + + for _, certItem := range *listCertificatesResp.Certificates { + // 如果已存在相同证书,直接返回 + if xcert.EqualCertificatesFromPEM(certPEM, certItem.Certificate) { + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.Id, + CertName: certItem.Name, + }, nil } } - if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) { + if len(*listCertificatesResp.Certificates) == 0 || listCertificatesResp.PageInfo.NextMarker == nil { break - } else { - listCertificatesMarker = listCertificatesResp.PageInfo.NextMarker } + + listCertificatesMarker = listCertificatesResp.PageInfo.NextMarker } // 获取项目 ID diff --git a/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go b/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go index 674ffbe11..a2cb650ae 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go @@ -70,11 +70,11 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, err } - // 遍历查询已有证书,避免重复上传 + // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-ccm/ListCertificates.html // REF: https://support.huaweicloud.com/api-ccm/ExportCertificate_0.html - listCertificatesLimit := int32(50) - listCertificatesOffset := int32(0) + listCertificatesLimit := 50 + listCertificatesOffset := 0 for { select { case <-ctx.Done(): @@ -84,8 +84,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listCertificatesReq := &hcscmmodel.ListCertificatesRequest{ EnterpriseProjectId: lo.EmptyableToPtr(m.config.EnterpriseProjectId), - Limit: lo.ToPtr(listCertificatesLimit), - Offset: lo.ToPtr(listCertificatesOffset), + Limit: lo.ToPtr(int32(listCertificatesLimit)), + Offset: lo.ToPtr(int32(listCertificatesOffset)), SortDir: lo.ToPtr("DESC"), SortKey: lo.ToPtr("certExpiredTime"), } @@ -95,51 +95,51 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'scm.ListCertificates': %w", err) } - if listCertificatesResp.Certificates != nil { - for _, certDetail := range *listCertificatesResp.Certificates { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certDetail.Domain) { - continue - } - - // 再对比证书有效期 - if certX509.NotAfter.Local().Format(time.DateTime) != strings.TrimSuffix(certDetail.ExpireTime, ".0") { - continue - } - - // 最后对比证书内容 - // 导出证书 - // REF: https://support.huaweicloud.com/api-ccm/ExportCertificate_0.html - exportCertificateReq := &hcscmmodel.ExportCertificateRequest{ - CertificateId: certDetail.Id, - } - exportCertificateResp, err := m.sdkClient.ExportCertificate(exportCertificateReq) - m.logger.Debug("sdk request 'scm.ExportCertificate'", slog.Any("request", exportCertificateReq), slog.Any("response", exportCertificateResp)) - if err != nil { - if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 { - continue - } - return nil, fmt.Errorf("failed to execute sdk request 'scm.ExportCertificate': %w", err) - } else { - if !xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(exportCertificateResp.Certificate)) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certDetail.Id, - CertName: certDetail.Name, - }, nil - } - } - - if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) { + if listCertificatesResp.Certificates == nil { break - } else { - listCertificatesOffset += listCertificatesLimit } + + for _, certItem := range *listCertificatesResp.Certificates { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.Domain) { + continue + } + + // 对比证书有效期 + if certX509.NotAfter.Local().Format(time.DateTime) != strings.TrimSuffix(certItem.ExpireTime, ".0") { + continue + } + + // 对比证书内容 + exportCertificateReq := &hcscmmodel.ExportCertificateRequest{ + CertificateId: certItem.Id, + } + exportCertificateResp, err := m.sdkClient.ExportCertificate(exportCertificateReq) + m.logger.Debug("sdk request 'scm.ExportCertificate'", slog.Any("request", exportCertificateReq), slog.Any("response", exportCertificateResp)) + if err != nil { + if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 { + continue + } + return nil, fmt.Errorf("failed to execute sdk request 'scm.ExportCertificate': %w", err) + } else { + if !xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(exportCertificateResp.Certificate)) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.Id, + CertName: certItem.Name, + }, nil + } + + if len(*listCertificatesResp.Certificates) < listCertificatesLimit { + break + } + + listCertificatesOffset += listCertificatesLimit } // 生成新证书名(需符合华为云命名规则) diff --git a/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go b/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go index 7db54c57c..13f5014af 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go @@ -67,11 +67,11 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历查询已有证书,避免重复上传 + // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-waf/ListCertificates.html // REF: https://support.huaweicloud.com/api-waf/ShowCertificate.html - listCertificatesPage := int32(1) - listCertificatesPageSize := int32(100) + listCertificatesPage := 1 + listCertificatesPageSize := 100 for { select { case <-ctx.Done(): @@ -81,8 +81,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listCertificatesReq := &hcwafmodel.ListCertificatesRequest{ EnterpriseProjectId: lo.EmptyableToPtr(m.config.EnterpriseProjectId), - Page: lo.ToPtr(listCertificatesPage), - Pagesize: lo.ToPtr(listCertificatesPageSize), + Page: lo.ToPtr(int32(listCertificatesPage)), + Pagesize: lo.ToPtr(int32(listCertificatesPageSize)), } listCertificatesResp, err := m.sdkClient.ListCertificates(listCertificatesReq) m.logger.Debug("sdk request 'waf.ShowCertificate'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp)) @@ -90,34 +90,36 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'waf.ListCertificates': %w", err) } - if listCertificatesResp.Items != nil { - for _, certItem := range *listCertificatesResp.Items { - showCertificateReq := &hcwafmodel.ShowCertificateRequest{ - EnterpriseProjectId: lo.EmptyableToPtr(m.config.EnterpriseProjectId), - CertificateId: certItem.Id, - } - showCertificateResp, err := m.sdkClient.ShowCertificate(showCertificateReq) - m.logger.Debug("sdk request 'waf.ShowCertificate'", slog.Any("request", showCertificateReq), slog.Any("response", showCertificateResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'waf.ShowCertificate': %w", err) - } + if listCertificatesResp.Items == nil { + break + } - // 如果已存在相同证书,直接返回 - if xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(showCertificateResp.Content)) { - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certItem.Id, - CertName: certItem.Name, - }, nil - } + for _, certItem := range *listCertificatesResp.Items { + showCertificateReq := &hcwafmodel.ShowCertificateRequest{ + EnterpriseProjectId: lo.EmptyableToPtr(m.config.EnterpriseProjectId), + CertificateId: certItem.Id, + } + showCertificateResp, err := m.sdkClient.ShowCertificate(showCertificateReq) + m.logger.Debug("sdk request 'waf.ShowCertificate'", slog.Any("request", showCertificateReq), slog.Any("response", showCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'waf.ShowCertificate': %w", err) + } + + // 如果已存在相同证书,直接返回 + if xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(showCertificateResp.Content)) { + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.Id, + CertName: certItem.Name, + }, nil } } - if listCertificatesResp.Items == nil || len(*listCertificatesResp.Items) < int(listCertificatesPageSize) { + if len(*listCertificatesResp.Items) < listCertificatesPageSize { break - } else { - listCertificatesPage++ } + + listCertificatesPage++ } // 生成新证书名(需符合华为云命名规则) diff --git a/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go b/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go index 7f3861f27..9e913d8ce 100644 --- a/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go +++ b/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go @@ -71,7 +71,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey privkeyPEM = strings.ReplaceAll(privkeyPEM, "\n", "\r\n") privkeyPEM = privkeyPEM + "\r\n" - // 遍历查看证书列表,避免重复上传 + // 查看证书列表 // REF: https://docs.jdcloud.com/cn/ssl-certificate/api/describecerts describeCertsPageNumber := 1 describeCertsPageSize := 10 @@ -92,36 +92,36 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeCerts': %w", err) } - for _, certDetail := range describeCertsResp.Result.CertListDetails { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certDetail.CommonName) { + for _, certItem := range describeCertsResp.Result.CertListDetails { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CommonName) { continue } - // 再对比证书多域名 - if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certDetail.DnsNames, ",")) { + // 对比证书多域名 + if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certItem.DnsNames, ",")) { continue } - // 再对比证书有效期 - oldCertNotBefore, _ := time.Parse(time.RFC3339, certDetail.StartTime) - oldCertNotAfter, _ := time.Parse(time.RFC3339, certDetail.EndTime) + // 对比证书有效期 + oldCertNotBefore, _ := time.Parse(time.RFC3339, certItem.StartTime) + oldCertNotAfter, _ := time.Parse(time.RFC3339, certItem.EndTime) if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { continue } - // 最后对比私钥摘要 + // 对比私钥 SHA-256 摘要 newKeyDigest := sha256.Sum256([]byte(privkeyPEM)) newKeyDigestHex := hex.EncodeToString(newKeyDigest[:]) - if !strings.EqualFold(newKeyDigestHex, certDetail.Digest) { + if !strings.EqualFold(newKeyDigestHex, certItem.Digest) { continue } // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certDetail.CertId, - CertName: certDetail.CertName, + CertId: certItem.CertId, + CertName: certItem.CertName, }, nil } diff --git a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go index dcbe26b03..3c072c3c2 100644 --- a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go +++ b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go @@ -67,9 +67,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 生成新证书名(需符合七牛云命名规则) certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) - // 遍历查询已有证书,避免重复上传 + // 查询已有证书,避免重复上传 getSslCertListMarker := "" - getSslCertListLimit := int32(200) for { select { case <-ctx.Done(): @@ -77,67 +76,65 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey default: } - getSslCertListResp, err := m.sdkClient.GetSslCertList(context.TODO(), getSslCertListMarker, getSslCertListLimit) + getSslCertListResp, err := m.sdkClient.GetSslCertList(ctx, getSslCertListMarker, 200) m.logger.Debug("sdk request 'sslcert.GetList'", slog.Any("request.marker", getSslCertListMarker), slog.Any("response", getSslCertListResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcert.GetList': %w", err) } - if getSslCertListResp.Certs != nil { - for _, sslCert := range getSslCertListResp.Certs { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, sslCert.CommonName) { - continue - } - - // 再对比证书多域名 - if !slices.Equal(certX509.DNSNames, sslCert.DnsNames) { - continue - } - - // 再对比证书有效期 - if certX509.NotBefore.Unix() != sslCert.NotBefore || certX509.NotAfter.Unix() != sslCert.NotAfter { - continue - } - - // 最后对比证书公钥算法 - switch certX509.PublicKeyAlgorithm { - case x509.RSA: - if !strings.EqualFold(sslCert.Encrypt, "RSA") { - continue - } - case x509.ECDSA: - if !strings.EqualFold(sslCert.Encrypt, "ECDSA") { - continue - } - case x509.Ed25519: - if !strings.EqualFold(sslCert.Encrypt, "ED25519") { - continue - } - default: - // 未知算法,跳过 - continue - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: sslCert.CertID, - CertName: sslCert.Name, - }, nil + for _, sslItem := range getSslCertListResp.Certs { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, sslItem.CommonName) { + continue } + + // 对比证书多域名 + if !slices.Equal(certX509.DNSNames, sslItem.DnsNames) { + continue + } + + // 对比证书有效期 + if certX509.NotBefore.Unix() != sslItem.NotBefore || certX509.NotAfter.Unix() != sslItem.NotAfter { + continue + } + + // 对比证书公钥算法 + switch certX509.PublicKeyAlgorithm { + case x509.RSA: + if !strings.EqualFold(sslItem.Encrypt, "RSA") { + continue + } + case x509.ECDSA: + if !strings.EqualFold(sslItem.Encrypt, "ECDSA") { + continue + } + case x509.Ed25519: + if !strings.EqualFold(sslItem.Encrypt, "ED25519") { + continue + } + default: + // 未知算法,跳过 + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: sslItem.CertID, + CertName: sslItem.Name, + }, nil } - if len(getSslCertListResp.Certs) < int(getSslCertListLimit) || getSslCertListResp.Marker == "" { + if len(getSslCertListResp.Certs) == 0 || getSslCertListResp.Marker == "" { break - } else { - getSslCertListMarker = getSslCertListResp.Marker } + + getSslCertListMarker = getSslCertListResp.Marker } // 上传新证书 // REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate - uploadSslCertResp, err := m.sdkClient.UploadSslCert(context.TODO(), certName, certX509.Subject.CommonName, certPEM, privkeyPEM) + uploadSslCertResp, err := m.sdkClient.UploadSslCert(ctx, certName, certX509.Subject.CommonName, certPEM, privkeyPEM) m.logger.Debug("sdk request 'sslcert.Upload'", slog.Any("response", uploadSslCertResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcert.Upload': %w", err) diff --git a/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go b/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go index 9b56fcb24..66dd537d5 100644 --- a/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go +++ b/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go @@ -10,6 +10,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" rainyunsdk "github.com/certimate-go/certimate/pkg/sdk3rd/rainyun" xcert "github.com/certimate-go/certimate/pkg/utils/cert" + "github.com/samber/lo" ) type SSLManagerProviderConfig struct { @@ -51,8 +52,8 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历证书列表,避免重复上传 - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + // 避免重复上传 + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res != nil { m.logger.Info("ssl certificate already exists") @@ -71,28 +72,28 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Create': %w", err) } - // 遍历证书列表,获取刚刚上传证书 ID - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + // 获取刚刚上传证书 ID + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res == nil { - return nil, errors.New("no ssl certificate found, may be upload failed") + return nil, errors.New("could not find ssl certificate, may be upload failed") } else { return res, nil } } -func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { +func (m *SSLManagerProvider) tryFindCert(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { // 解析证书内容 certX509, err := xcert.ParseCertificateFromPEM(certPEM) if err != nil { return nil, err } - // 遍历 SSL 证书列表 + // 获取 SSL 证书列表 // REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046 // REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943048 - sslCenterListPage := int32(1) - sslCenterListPerPage := int32(100) + sslCenterListPage := 1 + sslCenterListPerPage := 100 for { select { case <-ctx.Done(): @@ -104,8 +105,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin Filters: &rainyunsdk.SslCenterListFilters{ Domain: &certX509.Subject.CommonName, }, - Page: &sslCenterListPage, - PerPage: &sslCenterListPerPage, + Page: lo.ToPtr(int32(sslCenterListPage)), + PerPage: lo.ToPtr(int32(sslCenterListPerPage)), } sslCenterListResp, err := m.sdkClient.SslCenterList(sslCenterListReq) m.logger.Debug("sdk request 'sslcenter.List'", slog.Any("request", sslCenterListReq), slog.Any("response", sslCenterListResp)) @@ -113,40 +114,42 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.List': %w", err) } - if sslCenterListResp.Data != nil && sslCenterListResp.Data.Records != nil { - for _, sslRecord := range sslCenterListResp.Data.Records { - // 先对比证书的多域名 - if sslRecord.Domain != strings.Join(certX509.DNSNames, ", ") { - continue - } - - // 再对比证书的有效期 - if sslRecord.StartDate != certX509.NotBefore.Unix() || sslRecord.ExpireDate != certX509.NotAfter.Unix() { - continue - } - - // 最后对比证书内容 - sslCenterGetResp, err := m.sdkClient.SslCenterGet(sslRecord.ID) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Get': %w", err) - } else { - if !xcert.EqualCertificatesFromPEM(certPEM, sslCenterGetResp.Data.Cert) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", sslRecord.ID), - }, nil - } - } - - if sslCenterListResp.Data == nil || len(sslCenterListResp.Data.Records) < int(sslCenterListPerPage) { + if sslCenterListResp.Data == nil { break - } else { - sslCenterListPage++ } + + for _, sslItem := range sslCenterListResp.Data.Records { + // 对比证书的多域名 + if sslItem.Domain != strings.Join(certX509.DNSNames, ", ") { + continue + } + + // 对比证书的有效期 + if sslItem.StartDate != certX509.NotBefore.Unix() || sslItem.ExpireDate != certX509.NotAfter.Unix() { + continue + } + + // 对比证书内容 + sslCenterGetResp, err := m.sdkClient.SslCenterGet(sslItem.ID) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Get': %w", err) + } else { + if !xcert.EqualCertificatesFromPEM(certPEM, sslCenterGetResp.Data.Cert) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", sslItem.ID), + }, nil + } + + if len(sslCenterListResp.Data.Records) < sslCenterListPerPage { + break + } + + sslCenterListPage++ } return nil, nil diff --git a/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go b/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go index 83fe9deac..2d7d590ba 100644 --- a/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go +++ b/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go @@ -86,10 +86,10 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey m.logger.Debug("sdk request 'ussl.UploadNormalCertificate'", slog.Any("request", uploadNormalCertificateReq), slog.Any("response", uploadNormalCertificateResp)) if err != nil { if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 { - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res == nil { - return nil, errors.New("ucloud ssl: no certificate found") + return nil, errors.New("could not find ssl certificate, may be upload failed") } else { m.logger.Info("ssl certificate already exists") return res, nil @@ -108,18 +108,18 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey }, nil } -func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { +func (m *SSLManagerProvider) tryFindCert(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { // 解析证书内容 certX509, err := xcert.ParseCertificateFromPEM(certPEM) if err != nil { return nil, err } - // 遍历获取用户证书列表 + // 查询用户证书列表 // REF: https://docs.ucloud.cn/api/usslcertificate-api/get_certificate_list // REF: https://docs.ucloud.cn/api/usslcertificate-api/download_certificate - getCertificateListPage := int(1) - getCertificateListLimit := int(1000) + getCertificateListPage := 1 + getCertificateListLimit := 1000 for { select { case <-ctx.Done(): @@ -142,90 +142,88 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request 'ussl.GetCertificateList': %w", err) } - if getCertificateListResp.CertificateList != nil { - for _, certItem := range getCertificateListResp.CertificateList { - // 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试对比来判断是否为同一证书 - // 先分别对比证书的多域名、品牌、有效期,再对比签名算法 + for _, certItem := range getCertificateListResp.CertificateList { + // 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试对比来判断是否为同一证书 + // 先分别对比证书的多域名、品牌、有效期,再对比签名算法 - if len(certX509.DNSNames) == 0 || certItem.Domains != strings.Join(certX509.DNSNames, ",") { - continue - } - - if len(certX509.Issuer.Organization) == 0 || certItem.Brand != certX509.Issuer.Organization[0] { - continue - } - - if int64(certItem.NotBefore) != certX509.NotBefore.UnixMilli() || int64(certItem.NotAfter) != certX509.NotAfter.UnixMilli() { - continue - } - - getCertificateDetailInfoReq := m.sdkClient.NewGetCertificateDetailInfoRequest() - getCertificateDetailInfoReq.CertificateID = ucloud.Int(certItem.CertificateID) - if m.config.ProjectId != "" { - getCertificateDetailInfoReq.ProjectId = ucloud.String(m.config.ProjectId) - } - getCertificateDetailInfoResp, err := m.sdkClient.GetCertificateDetailInfo(getCertificateDetailInfoReq) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'ussl.GetCertificateDetailInfo': %w", err) - } - - switch certX509.SignatureAlgorithm { - case x509.SHA256WithRSA: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSA") { - continue - } - case x509.SHA384WithRSA: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSA") { - continue - } - case x509.SHA512WithRSA: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSA") { - continue - } - case x509.SHA256WithRSAPSS: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSAPSS") { - continue - } - case x509.SHA384WithRSAPSS: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSAPSS") { - continue - } - case x509.SHA512WithRSAPSS: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSAPSS") { - continue - } - case x509.ECDSAWithSHA256: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA256") { - continue - } - case x509.ECDSAWithSHA384: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA384") { - continue - } - case x509.ECDSAWithSHA512: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA512") { - continue - } - default: - // 未知签名算法,跳过 - continue - } - - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", certItem.CertificateID), - CertName: certItem.Name, - ExtendedData: map[string]any{ - "ResourceId": certItem.CertificateSN, - }, - }, nil + if len(certX509.DNSNames) == 0 || certItem.Domains != strings.Join(certX509.DNSNames, ",") { + continue } + + if len(certX509.Issuer.Organization) == 0 || certItem.Brand != certX509.Issuer.Organization[0] { + continue + } + + if int64(certItem.NotBefore) != certX509.NotBefore.UnixMilli() || int64(certItem.NotAfter) != certX509.NotAfter.UnixMilli() { + continue + } + + getCertificateDetailInfoReq := m.sdkClient.NewGetCertificateDetailInfoRequest() + getCertificateDetailInfoReq.CertificateID = ucloud.Int(certItem.CertificateID) + if m.config.ProjectId != "" { + getCertificateDetailInfoReq.ProjectId = ucloud.String(m.config.ProjectId) + } + getCertificateDetailInfoResp, err := m.sdkClient.GetCertificateDetailInfo(getCertificateDetailInfoReq) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ussl.GetCertificateDetailInfo': %w", err) + } + + switch certX509.SignatureAlgorithm { + case x509.SHA256WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSA") { + continue + } + case x509.SHA384WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSA") { + continue + } + case x509.SHA512WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSA") { + continue + } + case x509.SHA256WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSAPSS") { + continue + } + case x509.SHA384WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSAPSS") { + continue + } + case x509.SHA512WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSAPSS") { + continue + } + case x509.ECDSAWithSHA256: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA256") { + continue + } + case x509.ECDSAWithSHA384: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA384") { + continue + } + case x509.ECDSAWithSHA512: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA512") { + continue + } + default: + // 未知签名算法,跳过 + continue + } + + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", certItem.CertificateID), + CertName: certItem.Name, + ExtendedData: map[string]any{ + "ResourceId": certItem.CertificateSN, + }, + }, nil } - if getCertificateListResp.CertificateList == nil || len(getCertificateListResp.CertificateList) < int(getCertificateListLimit) { + if len(getCertificateListResp.CertificateList) < getCertificateListLimit { break - } else { - getCertificateListPage++ } + + getCertificateListPage++ } return nil, nil diff --git a/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go b/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go index 39acc4c71..5e3075a34 100644 --- a/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go @@ -69,14 +69,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://www.volcengine.com/docs/6454/125709 - listCertInfoPageNum := int32(1) - listCertInfoPageSize := int32(100) - listCertInfoTotal := 0 - listCertInfoReq := &vecdn.ListCertInfoInput{ - Source: ve.String("volc_cert_center"), - PageNum: ve.Int32(listCertInfoPageNum), - PageSize: ve.Int32(listCertInfoPageSize), - } + listCertInfoPageNum := 1 + listCertInfoPageSize := 100 for { select { case <-ctx.Done(): @@ -84,40 +78,43 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey default: } + listCertInfoReq := &vecdn.ListCertInfoInput{ + Source: ve.String("volc_cert_center"), + PageNum: ve.Int32(int32(listCertInfoPageNum)), + PageSize: ve.Int32(int32(listCertInfoPageSize)), + } listCertInfoResp, err := m.sdkClient.ListCertInfo(listCertInfoReq) m.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCertInfo': %w", err) } - if listCertInfoResp.CertInfo != nil { - for _, certInfo := range listCertInfoResp.CertInfo { - fingerprintSha1 := sha1.Sum(certX509.Raw) - if !strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), ve.StringValue(certInfo.CertFingerprint.Sha1)) { - continue - } - - fingerprintSha256 := sha256.Sum256(certX509.Raw) - if !strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), ve.StringValue(certInfo.CertFingerprint.Sha256)) { - continue - } - - // 如果已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: ve.StringValue(certInfo.CertId), - CertName: ve.StringValue(certInfo.Desc), - }, nil + for _, certItem := range listCertInfoResp.CertInfo { + // 对比证书 SHA-1 摘要 + fingerprintSha1 := sha1.Sum(certX509.Raw) + if !strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), ve.StringValue(certItem.CertFingerprint.Sha1)) { + continue } + + // 对比证书 SHA-256 摘要 + fingerprintSha256 := sha256.Sum256(certX509.Raw) + if !strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), ve.StringValue(certItem.CertFingerprint.Sha256)) { + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: ve.StringValue(certItem.CertId), + CertName: ve.StringValue(certItem.Desc), + }, nil } - listCertInfoLen := len(listCertInfoResp.CertInfo) - if listCertInfoLen < int(listCertInfoPageSize) || int(ve.Int64Value(listCertInfoResp.Total)) <= listCertInfoTotal+listCertInfoLen { + if len(listCertInfoResp.CertInfo) < listCertInfoPageSize { break - } else { - listCertInfoPageNum++ - listCertInfoTotal += listCertInfoLen } + + listCertInfoPageNum++ } // 生成新证书名(需符合火山引擎命名规则) diff --git a/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go b/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go index 3fafa0c11..fde52ef3b 100644 --- a/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go @@ -64,11 +64,11 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'live.ListCertV2': %w", err) } if listCertResp.Result.CertList != nil { - for _, certInfo := range listCertResp.Result.CertList { + for _, certItem := range listCertResp.Result.CertList { // 查询证书详细信息 // REF: https://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E8%AF%A6%E6%83%85 describeCertDetailSecretReq := &velive.DescribeCertDetailSecretV2Body{ - ChainID: ve.String(certInfo.ChainID), + ChainID: ve.String(certItem.ChainID), } describeCertDetailSecretResp, err := m.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq) m.logger.Debug("sdk request 'live.DescribeCertDetailSecretV2'", slog.Any("request", describeCertDetailSecretReq), slog.Any("response", describeCertDetailSecretResp)) @@ -81,8 +81,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if xcert.EqualCertificatesFromPEM(certPEM, oldCertPEM) { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certInfo.ChainID, - CertName: certInfo.CertName, + CertId: certItem.ChainID, + CertName: certItem.CertName, }, nil } } @@ -94,13 +94,12 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 上传新证书 // REF: https://www.volcengine.com/docs/6469/1186278#%E6%B7%BB%E5%8A%A0%E8%AF%81%E4%B9%A6 createCertReq := &velive.CreateCertBody{ - CertName: ve.String(certName), - UseWay: "https", - ProjectName: ve.String("default"), + CertName: ve.String(certName), Rsa: velive.CreateCertBodyRsa{ Prikey: privkeyPEM, Pubkey: certPEM, }, + UseWay: "https", } createCertResp, err := m.sdkClient.CreateCert(ctx, createCertReq) m.logger.Debug("sdk request 'live.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp)) diff --git a/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go b/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go index d74829aff..73441998d 100644 --- a/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go +++ b/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go @@ -72,16 +72,16 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } if listCertificatesResp.Certificates != nil { - for _, certRecord := range listCertificatesResp.Certificates { + for _, certItem := range listCertificatesResp.Certificates { // 对比证书序列号 - if !strings.EqualFold(certX509.SerialNumber.Text(16), certRecord.Serial) { + if !strings.EqualFold(certX509.SerialNumber.Text(16), certItem.Serial) { continue } - // 再对比证书有效期 - cstzone := time.FixedZone("CST", 8*60*60) - oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certRecord.ValidityFrom, cstzone) - oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certRecord.ValidityTo, cstzone) + // 对比证书有效期 + timezoneOfCST := time.FixedZone("CST", 8*60*60) + oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certItem.ValidityFrom, timezoneOfCST) + oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certItem.ValidityTo, timezoneOfCST) if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { continue } @@ -89,8 +89,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certRecord.CertificateId, - CertName: certRecord.Name, + CertId: certItem.CertificateId, + CertName: certItem.Name, }, nil } } diff --git a/pkg/sdk3rd/baishan/api_get_domain_list.go b/pkg/sdk3rd/baishan/api_get_domain_list.go new file mode 100644 index 000000000..f4c5166b0 --- /dev/null +++ b/pkg/sdk3rd/baishan/api_get_domain_list.go @@ -0,0 +1,55 @@ +package baishan + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +type GetDomainListRequest struct { + PageNumber *int32 `json:"page_number,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + DomainStatus *string `json:"domain_status,omitempty"` +} + +type GetDomainListResponse struct { + apiResponseBase + + Data []*struct { + List []*DomainRecord `json:"list"` + PageNumber json.Number `json:"page_number"` + PageSize json.Number `json:"page_size"` + TotalNumber json.Number `json:"total_number"` + } `json:"data,omitempty"` +} + +func (c *Client) GetDomainList(req *GetDomainListRequest) (*GetDomainListResponse, error) { + return c.GetDomainListWithContext(context.Background(), req) +} + +func (c *Client) GetDomainListWithContext(ctx context.Context, req *GetDomainListRequest) (*GetDomainListResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v2/domain/list") + if err != nil { + return nil, err + } else { + if req.PageNumber != nil { + httpreq.SetQueryParam("page_number", fmt.Sprintf("%d", *req.PageNumber)) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_number", fmt.Sprintf("%d", *req.PageSize)) + } + if req.DomainStatus != nil { + httpreq.SetQueryParam("domain_status", *req.DomainStatus) + } + + httpreq.SetContext(ctx) + } + + result := &GetDomainListResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/baishan/types.go b/pkg/sdk3rd/baishan/types.go index b6bda59f7..c06e859b8 100644 --- a/pkg/sdk3rd/baishan/types.go +++ b/pkg/sdk3rd/baishan/types.go @@ -30,6 +30,17 @@ func (r *apiResponseBase) GetMessage() string { var _ apiResponse = (*apiResponseBase)(nil) +type DomainRecord struct { + Id string `json:"id"` + Domain string `json:"domain"` + Type string `json:"type"` + Status string `json:"status"` + Cname string `json:"cname"` + Area string `json:"area"` + CreateTime string `json:"create_time"` + UpdateTime string `json:"update_time"` +} + type DomainCertificate struct { CertId json.Number `json:"cert_id"` Name string `json:"name"` diff --git a/pkg/sdk3rd/ctyun/ao/api_query_domains.go b/pkg/sdk3rd/ctyun/ao/api_query_domains.go new file mode 100644 index 000000000..96f947df5 --- /dev/null +++ b/pkg/sdk3rd/ctyun/ao/api_query_domains.go @@ -0,0 +1,67 @@ +package ao + +import ( + "context" + "net/http" + "strconv" +) + +type QueryDomainsRequest struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + Domain *string `json:"domain,omitempty"` + ProductCode *string `json:"product_code,omitempty"` + Status *int32 `json:"status,omitempty"` + AreaScope *int32 `json:"area_scope,omitempty"` +} + +type QueryDomainsResponse struct { + apiResponseBase + + ReturnObj *struct { + Results []*DomainRecord `json:"result,omitempty"` + Page int32 `json:"page,omitempty"` + PageSize int32 `json:"page_size,omitempty"` + PageCount int32 `json:"page_count,omitempty"` + Total int32 `json:"total,omitempty"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) QueryDomains(req *QueryDomainsRequest) (*QueryDomainsResponse, error) { + return c.QueryDomainsWithContext(context.Background(), req) +} + +func (c *Client) QueryDomainsWithContext(ctx context.Context, req *QueryDomainsRequest) (*QueryDomainsResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/ctapi/v2/domain/query") + if err != nil { + return nil, err + } else { + if req.Page != nil { + httpreq.SetQueryParam("page", strconv.Itoa(int(*req.Page))) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_size", strconv.Itoa(int(*req.PageSize))) + } + if req.Domain != nil { + httpreq.SetQueryParam("domain", *req.Domain) + } + if req.ProductCode != nil { + httpreq.SetQueryParam("product_code", *req.ProductCode) + } + if req.Status != nil { + httpreq.SetQueryParam("status", strconv.Itoa(int(*req.Status))) + } + if req.AreaScope != nil { + httpreq.SetQueryParam("area_scope", strconv.Itoa(int(*req.AreaScope))) + } + + httpreq.SetContext(ctx) + } + + result := &QueryDomainsResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/ctyun/ao/types.go b/pkg/sdk3rd/ctyun/ao/types.go index c22b19e40..46720c230 100644 --- a/pkg/sdk3rd/ctyun/ao/types.go +++ b/pkg/sdk3rd/ctyun/ao/types.go @@ -89,6 +89,16 @@ type CertDetail struct { Key string `json:"key"` } +type DomainRecord struct { + Domain string `json:"domain"` + Cname string `json:"cname"` + ProductCode string `json:"product_code"` + ProductName string `json:"product_name"` + Status int32 `json:"status"` + AreaScope int32 `json:"area_scope"` + CreatedTime int64 `json:"insert_date"` +} + type DomainOriginConfig struct { Origin string `json:"origin"` Role string `json:"role"` diff --git a/pkg/sdk3rd/ctyun/cdn/api_query_domain_detail.go b/pkg/sdk3rd/ctyun/cdn/api_query_domain_detail.go index f66cc44ab..b2de12738 100644 --- a/pkg/sdk3rd/ctyun/cdn/api_query_domain_detail.go +++ b/pkg/sdk3rd/ctyun/cdn/api_query_domain_detail.go @@ -14,23 +14,7 @@ type QueryDomainDetailRequest struct { type QueryDomainDetailResponse struct { apiResponseBase - ReturnObj *struct { - Domain string `json:"domain"` - ProductCode string `json:"product_code"` - Status int32 `json:"status"` - AreaScope int32 `json:"area_scope"` - Cname string `json:"cname"` - HttpsStatus string `json:"https_status"` - HttpsBasic *struct { - HttpsForce string `json:"https_force"` - HttpForce string `json:"http_force"` - ForceStatus string `json:"force_status"` - OriginProtocol string `json:"origin_protocol"` - } `json:"https_basic,omitempty"` - CertName string `json:"cert_name"` - Ssl string `json:"ssl"` - SslStapling string `json:"ssl_stapling"` - } `json:"returnObj,omitempty"` + ReturnObj *DomainDetail `json:"returnObj,omitempty"` } func (c *Client) QueryDomainDetail(req *QueryDomainDetailRequest) (*QueryDomainDetailResponse, error) { diff --git a/pkg/sdk3rd/ctyun/cdn/api_query_domain_list.go b/pkg/sdk3rd/ctyun/cdn/api_query_domain_list.go new file mode 100644 index 000000000..d2a316e93 --- /dev/null +++ b/pkg/sdk3rd/ctyun/cdn/api_query_domain_list.go @@ -0,0 +1,67 @@ +package cdn + +import ( + "context" + "net/http" + "strconv" +) + +type QueryDomainListRequest struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + Domain *string `json:"domain,omitempty"` + ProductCode *string `json:"product_code,omitempty"` + Status *int32 `json:"status,omitempty"` + AreaScope *int32 `json:"area_scope,omitempty"` +} + +type QueryDomainListResponse struct { + apiResponseBase + + ReturnObj *struct { + Results []*DomainRecord `json:"result,omitempty"` + Page int32 `json:"page,omitempty"` + PageSize int32 `json:"page_size,omitempty"` + PageCount int32 `json:"page_count,omitempty"` + Total int32 `json:"total,omitempty"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) QueryDomainList(req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + return c.QueryDomainListWithContext(context.Background(), req) +} + +func (c *Client) QueryDomainListWithContext(ctx context.Context, req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v1/domain/query-domain-list") + if err != nil { + return nil, err + } else { + if req.Page != nil { + httpreq.SetQueryParam("page", strconv.Itoa(int(*req.Page))) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_size", strconv.Itoa(int(*req.PageSize))) + } + if req.Domain != nil { + httpreq.SetQueryParam("domain", *req.Domain) + } + if req.ProductCode != nil { + httpreq.SetQueryParam("product_code", *req.ProductCode) + } + if req.Status != nil { + httpreq.SetQueryParam("status", strconv.Itoa(int(*req.Status))) + } + if req.AreaScope != nil { + httpreq.SetQueryParam("area_scope", strconv.Itoa(int(*req.AreaScope))) + } + + httpreq.SetContext(ctx) + } + + result := &QueryDomainListResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/ctyun/cdn/types.go b/pkg/sdk3rd/ctyun/cdn/types.go index 32054a0c6..f495caeda 100644 --- a/pkg/sdk3rd/ctyun/cdn/types.go +++ b/pkg/sdk3rd/ctyun/cdn/types.go @@ -70,6 +70,32 @@ func (r *apiResponseBase) GetErrorMessage() string { var _ apiResponse = (*apiResponseBase)(nil) +type DomainRecord struct { + Domain string `json:"domain"` + Cname string `json:"cname"` + ProductCode string `json:"product_code"` + ProductName string `json:"product_name"` + AreaScope int32 `json:"area_scope"` + Status int32 `json:"status"` + CreatedTime int64 `json:"insert_date"` +} + +type DomainDetail struct { + DomainRecord + HttpsStatus string `json:"https_status"` + HttpsBasic *DomainHttpsBasicConfig `json:"https_basic,omitempty"` + CertName string `json:"cert_name"` + Ssl string `json:"ssl"` + SslStapling string `json:"ssl_stapling"` +} + +type DomainHttpsBasicConfig struct { + HttpsForce string `json:"https_force"` + HttpForce string `json:"http_force"` + ForceStatus string `json:"force_status"` + OriginProtocol string `json:"origin_protocol"` +} + type CertRecord struct { Id int64 `json:"id"` Name string `json:"name"` diff --git a/pkg/sdk3rd/ctyun/icdn/api_query_domain_detail.go b/pkg/sdk3rd/ctyun/icdn/api_query_domain_detail.go index ace1bddb6..bf735c2ae 100644 --- a/pkg/sdk3rd/ctyun/icdn/api_query_domain_detail.go +++ b/pkg/sdk3rd/ctyun/icdn/api_query_domain_detail.go @@ -14,23 +14,7 @@ type QueryDomainDetailRequest struct { type QueryDomainDetailResponse struct { apiResponseBase - ReturnObj *struct { - Domain string `json:"domain"` - ProductCode string `json:"product_code"` - Status int32 `json:"status"` - AreaScope int32 `json:"area_scope"` - Cname string `json:"cname"` - HttpsStatus string `json:"https_status"` - HttpsBasic *struct { - HttpsForce string `json:"https_force"` - HttpForce string `json:"http_force"` - ForceStatus string `json:"force_status"` - OriginProtocol string `json:"origin_protocol"` - } `json:"https_basic,omitempty"` - CertName string `json:"cert_name"` - Ssl string `json:"ssl"` - SslStapling string `json:"ssl_stapling"` - } `json:"returnObj,omitempty"` + ReturnObj *DomainDetail `json:"returnObj,omitempty"` } func (c *Client) QueryDomainDetail(req *QueryDomainDetailRequest) (*QueryDomainDetailResponse, error) { diff --git a/pkg/sdk3rd/ctyun/icdn/api_query_domain_list.go b/pkg/sdk3rd/ctyun/icdn/api_query_domain_list.go new file mode 100644 index 000000000..318fea11b --- /dev/null +++ b/pkg/sdk3rd/ctyun/icdn/api_query_domain_list.go @@ -0,0 +1,67 @@ +package icdn + +import ( + "context" + "net/http" + "strconv" +) + +type QueryDomainListRequest struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + Domain *string `json:"domain,omitempty"` + ProductCode *string `json:"product_code,omitempty"` + Status *int32 `json:"status,omitempty"` + AreaScope *int32 `json:"area_scope,omitempty"` +} + +type QueryDomainListResponse struct { + apiResponseBase + + ReturnObj *struct { + Results []*DomainRecord `json:"result,omitempty"` + Page int32 `json:"page,omitempty"` + PageSize int32 `json:"page_size,omitempty"` + PageCount int32 `json:"page_count,omitempty"` + Total int32 `json:"total,omitempty"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) QueryDomainList(req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + return c.QueryDomainListWithContext(context.Background(), req) +} + +func (c *Client) QueryDomainListWithContext(ctx context.Context, req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v1/domain/query-domain-list") + if err != nil { + return nil, err + } else { + if req.Page != nil { + httpreq.SetQueryParam("page", strconv.Itoa(int(*req.Page))) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_size", strconv.Itoa(int(*req.PageSize))) + } + if req.Domain != nil { + httpreq.SetQueryParam("domain", *req.Domain) + } + if req.ProductCode != nil { + httpreq.SetQueryParam("product_code", *req.ProductCode) + } + if req.Status != nil { + httpreq.SetQueryParam("status", strconv.Itoa(int(*req.Status))) + } + if req.AreaScope != nil { + httpreq.SetQueryParam("area_scope", strconv.Itoa(int(*req.AreaScope))) + } + + httpreq.SetContext(ctx) + } + + result := &QueryDomainListResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/ctyun/icdn/types.go b/pkg/sdk3rd/ctyun/icdn/types.go index 0d1e8fb64..a48ed4658 100644 --- a/pkg/sdk3rd/ctyun/icdn/types.go +++ b/pkg/sdk3rd/ctyun/icdn/types.go @@ -70,6 +70,32 @@ func (r *apiResponseBase) GetErrorMessage() string { var _ apiResponse = (*apiResponseBase)(nil) +type DomainRecord struct { + Domain string `json:"domain"` + Cname string `json:"cname"` + ProductCode string `json:"product_code"` + ProductName string `json:"product_name"` + AreaScope int32 `json:"area_scope"` + Status int32 `json:"status"` + CreatedTime int64 `json:"insert_date"` +} + +type DomainDetail struct { + DomainRecord + HttpsStatus string `json:"https_status"` + HttpsBasic *DomainHttpsBasicConfig `json:"https_basic,omitempty"` + CertName string `json:"cert_name"` + Ssl string `json:"ssl"` + SslStapling string `json:"ssl_stapling"` +} + +type DomainHttpsBasicConfig struct { + HttpsForce string `json:"https_force"` + HttpForce string `json:"http_force"` + ForceStatus string `json:"force_status"` + OriginProtocol string `json:"origin_protocol"` +} + type CertRecord struct { Id int64 `json:"id"` Name string `json:"name"` diff --git a/pkg/sdk3rd/ctyun/lvdn/api_query_domain_detail.go b/pkg/sdk3rd/ctyun/lvdn/api_query_domain_detail.go index fde83efb6..188834817 100644 --- a/pkg/sdk3rd/ctyun/lvdn/api_query_domain_detail.go +++ b/pkg/sdk3rd/ctyun/lvdn/api_query_domain_detail.go @@ -13,15 +13,7 @@ type QueryDomainDetailRequest struct { type QueryDomainDetailResponse struct { apiResponseBase - ReturnObj *struct { - Domain string `json:"domain"` - ProductCode string `json:"product_code"` - Status int32 `json:"status"` - AreaScope int32 `json:"area_scope"` - Cname string `json:"cname"` - HttpsSwitch int32 `json:"https_switch"` - CertName string `json:"cert_name"` - } `json:"returnObj,omitempty"` + ReturnObj *DomainDetail `json:"returnObj,omitempty"` } func (c *Client) QueryDomainDetail(req *QueryDomainDetailRequest) (*QueryDomainDetailResponse, error) { diff --git a/pkg/sdk3rd/ctyun/lvdn/api_query_domain_list.go b/pkg/sdk3rd/ctyun/lvdn/api_query_domain_list.go new file mode 100644 index 000000000..14f669b0b --- /dev/null +++ b/pkg/sdk3rd/ctyun/lvdn/api_query_domain_list.go @@ -0,0 +1,67 @@ +package lvdn + +import ( + "context" + "net/http" + "strconv" +) + +type QueryDomainListRequest struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + Domain *string `json:"domain,omitempty"` + ProductCode *string `json:"product_code,omitempty"` + Status *int32 `json:"status,omitempty"` + AreaScope *int32 `json:"area_scope,omitempty"` +} + +type QueryDomainListResponse struct { + apiResponseBase + + ReturnObj *struct { + Results []*DomainRecord `json:"result,omitempty"` + Page int32 `json:"page,omitempty"` + PageSize int32 `json:"page_size,omitempty"` + PageCount int32 `json:"page_count,omitempty"` + Total int32 `json:"total,omitempty"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) QueryDomainList(req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + return c.QueryDomainListWithContext(context.Background(), req) +} + +func (c *Client) QueryDomainListWithContext(ctx context.Context, req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v1/domain/query-domain-list") + if err != nil { + return nil, err + } else { + if req.Page != nil { + httpreq.SetQueryParam("page", strconv.Itoa(int(*req.Page))) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_size", strconv.Itoa(int(*req.PageSize))) + } + if req.Domain != nil { + httpreq.SetQueryParam("domain", *req.Domain) + } + if req.ProductCode != nil { + httpreq.SetQueryParam("product_code", *req.ProductCode) + } + if req.Status != nil { + httpreq.SetQueryParam("status", strconv.Itoa(int(*req.Status))) + } + if req.AreaScope != nil { + httpreq.SetQueryParam("area_scope", strconv.Itoa(int(*req.AreaScope))) + } + + httpreq.SetContext(ctx) + } + + result := &QueryDomainListResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/ctyun/lvdn/types.go b/pkg/sdk3rd/ctyun/lvdn/types.go index 838fc38f2..b10c4a647 100644 --- a/pkg/sdk3rd/ctyun/lvdn/types.go +++ b/pkg/sdk3rd/ctyun/lvdn/types.go @@ -70,6 +70,22 @@ func (r *apiResponseBase) GetErrorMessage() string { var _ apiResponse = (*apiResponseBase)(nil) +type DomainRecord struct { + Domain string `json:"domain"` + Cname string `json:"cname"` + ProductCode string `json:"product_code"` + ProductName string `json:"product_name"` + AreaScope int32 `json:"area_scope"` + Status int32 `json:"status"` + CreatedTime int64 `json:"insert_date"` +} + +type DomainDetail struct { + DomainRecord + HttpsSwitch int32 `json:"https_switch"` + CertName string `json:"cert_name"` +} + type CertRecord struct { Id int64 `json:"id"` Name string `json:"name"` diff --git a/pkg/sdk3rd/dogecloud/api_list_cdn_domain.go b/pkg/sdk3rd/dogecloud/api_list_cdn_domain.go new file mode 100644 index 000000000..070a5f37a --- /dev/null +++ b/pkg/sdk3rd/dogecloud/api_list_cdn_domain.go @@ -0,0 +1,44 @@ +package dogecloud + +import ( + "context" + "encoding/json" + "net/http" +) + +type ListCdnDomainResponse struct { + apiResponseBase + + Data *struct { + Domains []*struct { + Id int64 `json:"id"` + Name string `json:"name"` + Cname string `json:"cname"` + ServiceType string `json:"service_type"` + Status string `json:"status"` + Source json.RawMessage `json:"source"` + CreateTime string `json:"ctime"` + CertId int64 `json:"cert_id"` + } `json:"domains"` + } `json:"data,omitempty"` +} + +func (c *Client) ListCdnDomain() (*ListCdnDomainResponse, error) { + return c.ListCdnDomainWithContext(context.Background()) +} + +func (c *Client) ListCdnDomainWithContext(ctx context.Context) (*ListCdnDomainResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/cdn/domain/list.json") + if err != nil { + return nil, err + } else { + httpreq.SetContext(ctx) + } + + result := &ListCdnDomainResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/qiniu/cdn.go b/pkg/sdk3rd/qiniu/cdn.go index 74745d0ba..d658bbeaa 100644 --- a/pkg/sdk3rd/qiniu/cdn.go +++ b/pkg/sdk3rd/qiniu/cdn.go @@ -2,7 +2,9 @@ package qiniu import ( "context" + "fmt" "net/http" + "net/url" "github.com/qiniu/go-sdk/v7/auth" "github.com/qiniu/go-sdk/v7/client" @@ -21,6 +23,37 @@ func NewCdnManager(mac *auth.Credentials) *CdnManager { return &CdnManager{client: client} } +type GetDomainListResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` + Marker string `json:"marker"` + Domains []*struct { + Name string `json:"name"` + Type string `json:"type"` + CName string `json:"cname"` + OperatingState string `json:"operatingState"` + OperatingStateDesc string `json:"operatingStateDesc"` + CreateAt string `json:"createAt"` + ModifyAt string `json:"modifyAt"` + } `json:"domains"` +} + +func (m *CdnManager) GetDomainList(ctx context.Context, marker string, limit int) (*GetDomainListResponse, error) { + query := url.Values{} + if marker != "" { + query.Set("marker", marker) + } + if limit > 0 { + query.Set("limit", fmt.Sprintf("%d", limit)) + } + + resp := new(GetDomainListResponse) + if err := m.client.Call(ctx, resp, http.MethodGet, "domain?"+query.Encode(), nil); err != nil { + return nil, err + } + return resp, nil +} + type GetDomainInfoResponse struct { Code *int `json:"code,omitempty"` Error *string `json:"error,omitempty"` diff --git a/pkg/sdk3rd/upyun/console/api_get_buckets.go b/pkg/sdk3rd/upyun/console/api_get_buckets.go new file mode 100644 index 000000000..8ddccc80e --- /dev/null +++ b/pkg/sdk3rd/upyun/console/api_get_buckets.go @@ -0,0 +1,85 @@ +package console + +import ( + "context" + "fmt" + "net/http" +) + +type GetBucketsRequest struct { + BucketName string `json:"status"` + BusinessType string `json:"business_type"` + Type string `json:"type"` + Status string `json:"state"` + Tag string `json:"tag"` + IsSecurityCDN bool `json:"security_cdn"` + WithDomains bool `json:"with_domains"` + Page int32 `json:"page"` + PerPage int32 `json:"perPage"` +} + +type GetBucketsResponse struct { + apiResponseBase + Data *struct { + apiResponseBaseData + Buckets []*BucketInfo `json:"buckets"` + Pager BucketPager `json:"pager"` + } `json:"data,omitempty"` +} + +type BucketInfo struct { + BucketName string `json:"bucket_name"` + BusinessType string `json:"business_type"` + Type string `json:"type"` + Status string `json:"status"` + Tag string `json:"tag"` + IsFusionCDN bool `json:"fusion_cdn"` + IsSecurityCDN bool `json:"security_cdn"` + Domains []*BucketDomain `json:"domains"` + Visible bool `json:"visible"` + CreatedAt string `json:"created_at"` +} + +type BucketDomain struct { + Domain string `json:"domain"` + Status string `json:"status"` +} + +type BucketPager struct { + Page int32 `json:"page"` + Pages int64 `json:"pages"` + Total int64 `json:"total"` +} + +func (c *Client) GetBuckets(req *GetBucketsRequest) (*GetBucketsResponse, error) { + return c.GetBucketsWithContext(context.Background(), req) +} + +func (c *Client) GetBucketsWithContext(ctx context.Context, req *GetBucketsRequest) (*GetBucketsResponse, error) { + if err := c.ensureCookieExists(); err != nil { + return nil, err + } + + httpreq, err := c.newRequest(http.MethodGet, "/api/v2/buckets") + if err != nil { + return nil, err + } else { + httpreq.SetQueryParam("bucket_name", req.BucketName) + httpreq.SetQueryParam("business_type", req.BusinessType) + httpreq.SetQueryParam("type", req.Type) + httpreq.SetQueryParam("state", req.Status) + httpreq.SetQueryParam("tag", req.Tag) + httpreq.SetQueryParam("security_cdn", fmt.Sprintf("%v", req.IsSecurityCDN)) + httpreq.SetQueryParam("with_domains", fmt.Sprintf("%v", req.WithDomains)) + httpreq.SetQueryParam("page", fmt.Sprintf("%d", req.Page)) + httpreq.SetQueryParam("perPage", fmt.Sprintf("%d", req.PerPage)) + httpreq.SetContext(ctx) + } + + result := &GetBucketsResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/utils/cert/hostname/hostname.go b/pkg/utils/cert/hostname/hostname.go index 35e593e5e..00d76996b 100644 --- a/pkg/utils/cert/hostname/hostname.go +++ b/pkg/utils/cert/hostname/hostname.go @@ -19,14 +19,13 @@ func IsMatch(match, candidate string) bool { return false } - if !strings.Contains(match, "*") { - return strings.EqualFold(match, candidate) - } - mockCert := &x509.Certificate{} if ip := net.ParseIP(match); ip != nil { mockCert.IPAddresses = []net.IP{ip} } else { + if strings.EqualFold(match, candidate) { + return true + } mockCert.DNSNames = []string{match} } return mockCert.VerifyHostname(candidate) == nil diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx index 9abd5ce5a..78e63827a 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form, Input, Select } from "antd"; +import { Form, Input, Radio, Select } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -11,6 +11,10 @@ import { useFormNestedFieldsContext } from "./_context"; const SERVICE_TYPE_CLOUDNATIVE = "cloudnative" as const; const SERVICE_TYPE_TRADITIONAL = "traditional" as const; +const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const; +const DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" as const; +const DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => { const { i18n, t } = useTranslation(); @@ -23,6 +27,7 @@ const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => { const initialValues = getInitialValues(); const fieldServiceType = Form.useWatch([parentNamePath, "serviceType"], formInst); + const fieldDomainMatchPattern = Form.useWatch([parentNamePath, "domainMatchPattern"], { form: formInst, preserve: true }); return ( <> @@ -77,13 +82,37 @@ const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => {