mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
feat(provider): new deployment provider: aws clb
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user