feat(provider): new deployment provider: aws clb

This commit is contained in:
Fu Diwei
2026-06-04 18:55:48 +08:00
committed by RHQYZ
parent ed9f5b3322
commit fc7975972d
19 changed files with 434 additions and 38 deletions
+11 -5
View File
@@ -77,8 +77,8 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
}
// 获取证书列表,避免重复上传
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_GetCertificate.html
// REF: https://docs.aws.amazon.com/acm/latest/APIReference/API_ListCertificates.html
// REF: https://docs.aws.amazon.com/acm/latest/APIReference/API_GetCertificate.html
listCertificatesNextToken := (*string)(nil)
for {
select {
@@ -127,7 +127,10 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
// 如果以上信息都一致,则视为已存在相同证书,直接返回
c.logger.Info("ssl certificate already exists")
return &UploadResult{
CertId: *certItem.CertificateArn,
CertId: aws.ToString(certItem.CertificateArn),
ExtendedData: map[string]any{
"Arn": aws.ToString(certItem.CertificateArn),
},
}, nil
}
@@ -139,7 +142,7 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
}
// 导入证书
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
// REF: https://docs.aws.amazon.com/acm/latest/APIReference/API_ImportCertificate.html
importCertificateReq := &awsacm.ImportCertificateInput{
Certificate: ([]byte)(serverCertPEM),
CertificateChain: ([]byte)(intermediaCertPEM),
@@ -153,6 +156,9 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
return &UploadResult{
CertId: aws.ToString(importCertificateResp.CertificateArn),
ExtendedData: map[string]any{
"Arn": aws.ToString(importCertificateResp.CertificateArn),
},
}, nil
}
@@ -164,7 +170,7 @@ func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, pri
}
// 导入证书
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
// REF: https://docs.aws.amazon.com/acm/latest/APIReference/API_ImportCertificate.html
importCertificateReq := &awsacm.ImportCertificateInput{
CertificateArn: aws.String(certIdOrName),
Certificate: ([]byte)(serverCertPEM),
+14 -8
View File
@@ -1,6 +1,7 @@
package awsiam
import (
"cmp"
"context"
"fmt"
"log/slog"
@@ -80,8 +81,8 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
}
// 获取证书列表,避免重复上传
// REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_ListServerCertificates.html
// REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_GetServerCertificate.html
// REF: https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListServerCertificates.html
// REF: https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetServerCertificate.html
listServerCertificatesMarker := (*string)(nil)
for {
select {
@@ -132,6 +133,10 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
return &UploadResult{
CertId: aws.ToString(certItem.ServerCertificateId),
CertName: aws.ToString(certItem.ServerCertificateName),
ExtendedData: map[string]any{
"Arn": aws.ToString(certItem.Arn),
"Path": aws.ToString(certItem.Path),
},
}, nil
}
@@ -146,17 +151,14 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 导入证书
// REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_UploadServerCertificate.html
// REF: https://docs.aws.amazon.com/IAM/latest/APIReference/API_UploadServerCertificate.html
uploadServerCertificateReq := &awsiam.UploadServerCertificateInput{
ServerCertificateName: aws.String(certName),
Path: aws.String(c.config.CertificatePath),
Path: aws.String(cmp.Or(c.config.CertificatePath, "/")),
CertificateBody: aws.String(serverCertPEM),
CertificateChain: aws.String(intermediaCertPEM),
PrivateKey: aws.String(privkeyPEM),
}
if c.config.CertificatePath == "" {
uploadServerCertificateReq.Path = aws.String("/")
}
uploadServerCertificateResp, err := c.sdkClient.UploadServerCertificate(ctx, uploadServerCertificateReq)
c.logger.Debug("sdk request 'iam.UploadServerCertificate'", slog.Any("request", uploadServerCertificateReq), slog.Any("response", uploadServerCertificateResp))
if err != nil {
@@ -165,7 +167,11 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*Uplo
return &UploadResult{
CertId: aws.ToString(uploadServerCertificateResp.ServerCertificateMetadata.ServerCertificateId),
CertName: certName,
CertName: aws.ToString(uploadServerCertificateResp.ServerCertificateMetadata.ServerCertificateName),
ExtendedData: map[string]any{
"Arn": aws.ToString(uploadServerCertificateResp.ServerCertificateMetadata.Arn),
"Path": aws.ToString(uploadServerCertificateResp.ServerCertificateMetadata.Path),
},
}, nil
}
@@ -0,0 +1,146 @@
package awsclb
import (
"context"
"fmt"
"log/slog"
aws "github.com/aws/aws-sdk-go-v2/aws"
awscfg "github.com/aws/aws-sdk-go-v2/config"
awscred "github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
"github.com/certimate-go/certimate/pkg/core"
cmgrimplacm "github.com/certimate-go/certimate/pkg/core/certmgr/providers/aws-acm"
cmgrimpliam "github.com/certimate-go/certimate/pkg/core/certmgr/providers/aws-iam"
)
type (
Provider = core.Deployer
DeployResult = core.DeployerDeployResult
)
type DeployerConfig struct {
// AWS AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// AWS SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// AWS 区域。
Region string `json:"region"`
// AWS CLB 负载均衡器名称。
LoadbalancerName string `json:"loadbalancerName"`
// AWS CLB 负载均衡器端口。
LoadbalancerPort int32 `json:"loadbalancerPort"`
// AWS CLB 证书来源。
// 可取值 "ACM"、"IAM"。
CertificateSource string `json:"certificateSource"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *elasticloadbalancing.Client
sdkCertmgr core.Certmgr
}
var _ Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
var pcertmgr core.Certmgr
switch config.CertificateSource {
case CERTIFICATE_SOURCE_ACM:
pcertmgr, err = cmgrimplacm.NewCertmgr(&cmgrimplacm.CertmgrConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
Region: config.Region,
})
if err != nil {
return nil, fmt.Errorf("could not create certmgr: %w", err)
}
case CERTIFICATE_SOURCE_IAM:
pcertmgr, err = cmgrimpliam.NewCertmgr(&cmgrimpliam.CertmgrConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
Region: config.Region,
CertificatePath: "/elb/",
})
if err != nil {
return nil, fmt.Errorf("could not create certmgr: %w", err)
}
default:
return nil, fmt.Errorf("unsupported certificate source: '%s'", config.CertificateSource)
}
return &Deployer{
config: config,
logger: slog.Default(),
sdkClient: client,
sdkCertmgr: pcertmgr,
}, nil
}
func (d *Deployer) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
d.sdkCertmgr.SetLogger(logger)
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
if d.config.LoadbalancerName == "" {
return nil, fmt.Errorf("config `loadbalancerName` is required")
}
if d.config.LoadbalancerPort == 0 {
return nil, fmt.Errorf("config `loadbalancerPort` is required")
}
// 上传证书
upres, err := d.sdkCertmgr.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))
}
// 替换 HTTPS 侦听器 SSL 证书
// REF: https://docs.aws.amazon.com/elasticloadbalancing/2012-06-01/APIReference/API_SetLoadBalancerListenerSSLCertificate.html
setLoadBalancerListenerSSLCertificateReq := &elasticloadbalancing.SetLoadBalancerListenerSSLCertificateInput{
LoadBalancerName: aws.String(d.config.LoadbalancerName),
LoadBalancerPort: d.config.LoadbalancerPort,
SSLCertificateId: aws.String(upres.ExtendedData["Arn"].(string)),
}
setLoadBalancerListenerSSLCertificateResp, err := d.sdkClient.SetLoadBalancerListenerSSLCertificate(ctx, setLoadBalancerListenerSSLCertificateReq)
d.logger.Debug("sdk request 'elasticloadbalancing.SetLoadBalancerListenerSSLCertificate'", slog.Any("request", setLoadBalancerListenerSSLCertificateReq), slog.Any("response", setLoadBalancerListenerSSLCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'elasticloadbalancing.SetLoadBalancerListenerSSLCertificate': %w", err)
}
return &DeployResult{}, nil
}
func createSDKClient(accessKeyId, secretAccessKey, region string) (*elasticloadbalancing.Client, error) {
cfg, err := awscfg.LoadDefaultConfig(context.Background())
if err != nil {
return nil, err
}
client := elasticloadbalancing.NewFromConfig(cfg, func(o *elasticloadbalancing.Options) {
o.Region = region
o.Credentials = aws.NewCredentialsCache(awscred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, ""))
})
return client, nil
}
@@ -0,0 +1,62 @@
package awsclb_test
import (
"testing"
"github.com/certimate-go/certimate/pkg/core/deployer/internal/tester"
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/aws-clb"
)
var (
fp = tester.Args("AWSCLB_")
fTestCertPath string
fTestKeyPath string
fAccessKeyId string
fSecretAccessKey string
fRegion string
fLoadbalancerName string
fLoadbalancerPort int
)
func init() {
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY")
fp.DefineString(&fRegion, "REGION")
fp.DefineString(&fLoadbalancerName, "LOADBALANCERNAME")
fp.DefineInt(&fLoadbalancerPort, "LOADBALANCERPORT")
}
/*
Shell command to run this test:
go test -v ./aws_clb_test.go -args \
--AWSCLB_TESTCERTPATH="/path/to/your-test-cert.pem" \
--AWSCLB_TESTKEYPATH="/path/to/your-test-key.pem" \
--AWSCLB_ACCESSKEYID="your-access-key-id" \
--AWSCLB_SECRETACCESSKEY="your-secret-access-id" \
--AWSCLB_REGION="us-east-1" \
--AWSCLB_LOADBALANCERNAME="your-loadbalancer-name" \
--AWSCLB_LOADBALANCERPORT=443
*/
func TestProvider(t *testing.T) {
fp.Parse()
t.Run("Deploy", func(t *testing.T) {
provider, err := impl.NewDeployer(&impl.DeployerConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
Region: fRegion,
LoadbalancerName: fLoadbalancerName,
LoadbalancerPort: int32(fLoadbalancerPort),
CertificateSource: impl.CERTIFICATE_SOURCE_ACM,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
})
}
@@ -0,0 +1,6 @@
package awsclb
const (
CERTIFICATE_SOURCE_ACM = "ACM"
CERTIFICATE_SOURCE_IAM = "IAM"
)
@@ -101,7 +101,7 @@ func (d *Deployer) SetLogger(logger *slog.Logger) {
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
if d.config.DistributionId == "" {
return nil, fmt.Errorf("config `distribuitionId` is required")
return nil, fmt.Errorf("config `distributionId` is required")
}
// 上传证书
@@ -113,7 +113,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
}
// 获取分配配置
// REF: https://docs.aws.amazon.com/en_us/cloudfront/latest/APIReference/API_GetDistributionConfig.html
// REF: https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_GetDistributionConfig.html
getDistributionConfigReq := &cloudfront.GetDistributionConfigInput{
Id: aws.String(d.config.DistributionId),
}
@@ -124,7 +124,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
}
// 更新分配配置
// REF: https://docs.aws.amazon.com/zh_cn/cloudfront/latest/APIReference/API_UpdateDistribution.html
// REF: https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_UpdateDistribution.html
updateDistributionReq := &cloudfront.UpdateDistributionInput{
Id: aws.String(d.config.DistributionId),
DistributionConfig: getDistributionConfigResp.DistributionConfig,
@@ -14,7 +14,7 @@ var (
fAccessKeyId string
fSecretAccessKey string
fRegion string
fDistribuitionId string
fDistributionId string
)
func init() {
@@ -23,7 +23,7 @@ func init() {
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY")
fp.DefineString(&fRegion, "REGION")
fp.DefineString(&fDistribuitionId, "DISTRIBUTIONID")
fp.DefineString(&fDistributionId, "DISTRIBUTIONID")
}
/*
@@ -42,10 +42,11 @@ func TestProvider(t *testing.T) {
t.Run("Deploy", func(t *testing.T) {
provider, err := impl.NewDeployer(&impl.DeployerConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
Region: fRegion,
DistributionId: fDistribuitionId,
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
Region: fRegion,
DistributionId: fDistributionId,
CertificateSource: impl.CERTIFICATE_SOURCE_ACM,
})
if err != nil {
t.Errorf("err: %+v", err)