feat(provider): new deployment provider: s3

This commit is contained in:
Fu Diwei
2026-01-19 11:31:35 +08:00
committed by RHQYZ
parent edb858cfec
commit c0bcf6d0fd
21 changed files with 703 additions and 85 deletions
@@ -62,8 +62,7 @@ type provider struct {
func (p *provider) Present(domain, token, keyAuth string) error {
objectKey := strings.Trim(http01.ChallengePath(token), "/")
reader := strings.NewReader(keyAuth)
if err := p.client.PutObject(context.Background(), p.bucket, objectKey, reader); err != nil {
if err := p.client.PutObjectString(context.Background(), p.bucket, objectKey, keyAuth); err != nil {
return fmt.Errorf("s3: failed to upload token to s3: %w", err)
}
+8 -4
View File
@@ -1,9 +1,7 @@
package local
const (
OUTPUT_FORMAT_PEM = "PEM"
OUTPUT_FORMAT_PFX = "PFX"
OUTPUT_FORMAT_JKS = "JKS"
import (
"github.com/certimate-go/certimate/internal/domain"
)
const (
@@ -11,3 +9,9 @@ const (
SHELL_ENV_CMD = "cmd"
SHELL_ENV_POWERSHELL = "powershell"
)
const (
OUTPUT_FORMAT_PEM = string(domain.CertificateFormatTypePEM)
OUTPUT_FORMAT_PFX = string(domain.CertificateFormatTypePFX)
OUTPUT_FORMAT_JKS = string(domain.CertificateFormatTypeJKS)
)
+40 -34
View File
@@ -104,53 +104,59 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
// 写入证书和私钥文件
switch d.config.OutputFormat {
case OUTPUT_FORMAT_PEM:
if err := xfile.WriteString(d.config.OutputCertPath, certPEM); err != nil {
return nil, fmt.Errorf("failed to save certificate file: %w", err)
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
if d.config.OutputServerCertPath != "" {
if err := xfile.WriteString(d.config.OutputServerCertPath, serverCertPEM); err != nil {
return nil, fmt.Errorf("failed to save server certificate file: %w", err)
{
if err := xfile.WriteString(d.config.OutputCertPath, certPEM); err != nil {
return nil, fmt.Errorf("failed to save certificate file: %w", err)
}
d.logger.Info("ssl server certificate file saved", slog.String("path", d.config.OutputServerCertPath))
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
if d.config.OutputIntermediaCertPath != "" {
if err := xfile.WriteString(d.config.OutputIntermediaCertPath, intermediaCertPEM); err != nil {
return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err)
if d.config.OutputServerCertPath != "" {
if err := xfile.WriteString(d.config.OutputServerCertPath, serverCertPEM); err != nil {
return nil, fmt.Errorf("failed to save server certificate file: %w", err)
}
d.logger.Info("ssl server certificate file saved", slog.String("path", d.config.OutputServerCertPath))
}
d.logger.Info("ssl intermedia certificate file saved", slog.String("path", d.config.OutputIntermediaCertPath))
}
if err := xfile.WriteString(d.config.OutputKeyPath, privkeyPEM); err != nil {
return nil, fmt.Errorf("failed to save private key file: %w", err)
if d.config.OutputIntermediaCertPath != "" {
if err := xfile.WriteString(d.config.OutputIntermediaCertPath, intermediaCertPEM); err != nil {
return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err)
}
d.logger.Info("ssl intermedia certificate file saved", slog.String("path", d.config.OutputIntermediaCertPath))
}
if err := xfile.WriteString(d.config.OutputKeyPath, privkeyPEM); err != nil {
return nil, fmt.Errorf("failed to save private key file: %w", err)
}
d.logger.Info("ssl private key file saved", slog.String("path", d.config.OutputKeyPath))
}
d.logger.Info("ssl private key file saved", slog.String("path", d.config.OutputKeyPath))
case OUTPUT_FORMAT_PFX:
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
}
d.logger.Info("ssl certificate transformed to pfx")
{
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
}
d.logger.Info("ssl certificate transformed to pfx")
if err := xfile.Write(d.config.OutputCertPath, pfxData); err != nil {
return nil, fmt.Errorf("failed to save certificate file: %w", err)
if err := xfile.Write(d.config.OutputCertPath, pfxData); err != nil {
return nil, fmt.Errorf("failed to save certificate file: %w", err)
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
case OUTPUT_FORMAT_JKS:
jksData, err := xcert.TransformCertificateFromPEMToJKS(certPEM, privkeyPEM, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to JKS: %w", err)
}
d.logger.Info("ssl certificate transformed to jks")
{
jksData, err := xcert.TransformCertificateFromPEMToJKS(certPEM, privkeyPEM, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to JKS: %w", err)
}
d.logger.Info("ssl certificate transformed to jks")
if err := xfile.Write(d.config.OutputCertPath, jksData); err != nil {
return nil, fmt.Errorf("failed to save certificate file: %w", err)
if err := xfile.Write(d.config.OutputCertPath, jksData); err != nil {
return nil, fmt.Errorf("failed to save certificate file: %w", err)
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
}
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
default:
return nil, fmt.Errorf("unsupported output format '%s'", d.config.OutputFormat)
+11
View File
@@ -0,0 +1,11 @@
package s3
import (
"github.com/certimate-go/certimate/internal/domain"
)
const (
OUTPUT_FORMAT_PEM = string(domain.CertificateFormatTypePEM)
OUTPUT_FORMAT_PFX = string(domain.CertificateFormatTypePFX)
OUTPUT_FORMAT_JKS = string(domain.CertificateFormatTypeJKS)
)
+169
View File
@@ -0,0 +1,169 @@
package s3
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/certimate-go/certimate/internal/tools/s3"
"github.com/certimate-go/certimate/pkg/core/deployer"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
)
type DeployerConfig struct {
// S3 Endpoint。
Endpoint string `json:"endpoint"`
// S3 AccessKey。
AccessKey string `json:"accessKey"`
// S3 SecretKey。
SecretKey string `json:"secretKey"`
// S3 签名版本。
// 可取值 "v2"、"v4"。
// 零值时默认值 "v4"。
SignatureVersion string `json:"signatureVersion,omitempty"`
// 是否使用路径风格。
UsePathStyle bool `json:"usePathStyle,omitempty"`
// 存储区域。
Region string `json:"region"`
// 存储桶名。
Bucket string `json:"bucket"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 输出证书格式。
OutputFormat string `json:"outputFormat,omitempty"`
// 输出证书文件路径。
OutputCertObjectKey string `json:"outputCertObjectKey,omitempty"`
// 输出服务器证书文件路径。
// 选填。
OutputServerCertObjectKey string `json:"outputServerCertObjectKey,omitempty"`
// 输出中间证书文件路径。
// 选填。
OutputIntermediaCertObjectKey string `json:"outputIntermediaCertObjectKey,omitempty"`
// 输出私钥文件路径。
OutputKeyObjectKey string `json:"outputKeyObjectKey,omitempty"`
// PFX 导出密码。
// 证书格式为 PFX 时必填。
PfxPassword string `json:"pfxPassword,omitempty"`
// JKS 别名。
// 证书格式为 JKS 时必填。
JksAlias string `json:"jksAlias,omitempty"`
// JKS 密钥密码。
// 证书格式为 JKS 时必填。
JksKeypass string `json:"jksKeypass,omitempty"`
// JKS 存储密码。
// 证书格式为 JKS 时必填。
JksStorepass string `json:"jksStorepass,omitempty"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
s3Client *s3.Client
}
var _ deployer.Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, errors.New("the configuration of the deployer provider is nil")
}
client, err := s3.NewClient(&s3.Config{
Endpoint: config.Endpoint,
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
SignatureVersion: config.SignatureVersion,
UsePathStyle: config.UsePathStyle,
Region: config.Region,
SkipTlsVerify: config.AllowInsecureConnections,
})
if err != nil {
return nil, fmt.Errorf("s3: failed to create s3 client: %w", err)
}
return &Deployer{
config: config,
logger: slog.Default(),
s3Client: client,
}, nil
}
func (d *Deployer) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*deployer.DeployResult, error) {
// 提取服务器证书和中间证书
serverCertPEM, intermediaCertPEM, err := xcert.ExtractCertificatesFromPEM(certPEM)
if err != nil {
return nil, fmt.Errorf("failed to extract certs: %w", err)
}
// 写入证书和私钥文件
switch d.config.OutputFormat {
case OUTPUT_FORMAT_PEM:
{
if err := d.s3Client.PutObjectString(ctx, d.config.Bucket, d.config.OutputCertObjectKey, certPEM); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
}
d.logger.Info("ssl certificate file uploaded", slog.String("bucket", d.config.Bucket), slog.String("object", d.config.OutputCertObjectKey))
if d.config.OutputServerCertObjectKey != "" {
if err := d.s3Client.PutObjectString(ctx, d.config.Bucket, d.config.OutputServerCertObjectKey, serverCertPEM); err != nil {
return nil, fmt.Errorf("failed to upload server certificate file: %w", err)
}
d.logger.Info("ssl server certificate file uploaded", slog.String("bucket", d.config.Bucket), slog.String("object", d.config.OutputServerCertObjectKey))
}
if d.config.OutputIntermediaCertObjectKey != "" {
if err := d.s3Client.PutObjectString(ctx, d.config.Bucket, d.config.OutputIntermediaCertObjectKey, intermediaCertPEM); err != nil {
return nil, fmt.Errorf("failed to upload intermedia certificate file: %w", err)
}
d.logger.Info("ssl intermedia certificate file uploaded", slog.String("bucket", d.config.Bucket), slog.String("object", d.config.OutputIntermediaCertObjectKey))
}
if err := d.s3Client.PutObjectString(ctx, d.config.Bucket, d.config.OutputKeyObjectKey, privkeyPEM); err != nil {
return nil, fmt.Errorf("failed to upload private key file: %w", err)
}
d.logger.Info("ssl private key file uploaded", slog.String("bucket", d.config.Bucket), slog.String("object", d.config.OutputKeyObjectKey))
}
case OUTPUT_FORMAT_PFX:
{
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
}
d.logger.Info("ssl certificate transformed to pfx")
if err := d.s3Client.PutObjectBytes(ctx, d.config.Bucket, d.config.OutputCertObjectKey, pfxData); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
}
d.logger.Info("ssl certificate file uploaded", slog.String("bucket", d.config.Bucket), slog.String("object", d.config.OutputCertObjectKey))
}
case OUTPUT_FORMAT_JKS:
{
jksData, err := xcert.TransformCertificateFromPEMToJKS(certPEM, privkeyPEM, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to JKS: %w", err)
}
d.logger.Info("ssl certificate transformed to jks")
if err := d.s3Client.PutObjectBytes(ctx, d.config.Bucket, d.config.OutputCertObjectKey, jksData); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
}
d.logger.Info("ssl certificate file uploaded", slog.String("bucket", d.config.Bucket), slog.String("object", d.config.OutputCertObjectKey))
}
default:
return nil, fmt.Errorf("unsupported output format '%s'", d.config.OutputFormat)
}
return &deployer.DeployResult{}, nil
}
+8 -3
View File
@@ -1,5 +1,9 @@
package ssh
import (
"github.com/certimate-go/certimate/internal/domain"
)
const (
AUTH_METHOD_NONE = "none"
AUTH_METHOD_PASSWORD = "password"
@@ -7,7 +11,8 @@ const (
)
const (
OUTPUT_FORMAT_PEM = "PEM"
OUTPUT_FORMAT_PFX = "PFX"
OUTPUT_FORMAT_JKS = "JKS"
OUTPUT_FORMAT_PEM = string(domain.CertificateFormatTypePEM)
OUTPUT_FORMAT_PFX = string(domain.CertificateFormatTypePFX)
OUTPUT_FORMAT_JKS = string(domain.CertificateFormatTypeJKS)
)
+41 -35
View File
@@ -202,53 +202,59 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
// 上传证书和私钥文件
switch d.config.OutputFormat {
case OUTPUT_FORMAT_PEM:
if err := xssh.WriteRemoteString(client, d.config.OutputKeyPath, privkeyPEM, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to upload private key file: %w", err)
}
d.logger.Info("ssl private key file uploaded", slog.String("path", d.config.OutputKeyPath))
if err := xssh.WriteRemoteString(client, d.config.OutputCertPath, certPEM, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
if d.config.OutputServerCertPath != "" {
if err := xssh.WriteRemoteString(client, d.config.OutputServerCertPath, serverCertPEM, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to save server certificate file: %w", err)
{
if err := xssh.WriteRemoteString(client, d.config.OutputCertPath, certPEM, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
}
d.logger.Info("ssl server certificate file uploaded", slog.String("path", d.config.OutputServerCertPath))
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
if d.config.OutputIntermediaCertPath != "" {
if err := xssh.WriteRemoteString(client, d.config.OutputIntermediaCertPath, intermediaCertPEM, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err)
if d.config.OutputServerCertPath != "" {
if err := xssh.WriteRemoteString(client, d.config.OutputServerCertPath, serverCertPEM, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to save server certificate file: %w", err)
}
d.logger.Info("ssl server certificate file uploaded", slog.String("path", d.config.OutputServerCertPath))
}
d.logger.Info("ssl intermedia certificate file uploaded", slog.String("path", d.config.OutputIntermediaCertPath))
if d.config.OutputIntermediaCertPath != "" {
if err := xssh.WriteRemoteString(client, d.config.OutputIntermediaCertPath, intermediaCertPEM, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err)
}
d.logger.Info("ssl intermedia certificate file uploaded", slog.String("path", d.config.OutputIntermediaCertPath))
}
if err := xssh.WriteRemoteString(client, d.config.OutputKeyPath, privkeyPEM, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to upload private key file: %w", err)
}
d.logger.Info("ssl private key file uploaded", slog.String("path", d.config.OutputKeyPath))
}
case OUTPUT_FORMAT_PFX:
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
}
d.logger.Info("ssl certificate transformed to pfx")
{
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
}
d.logger.Info("ssl certificate transformed to pfx")
if err := xssh.WriteRemote(client, d.config.OutputCertPath, pfxData, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
if err := xssh.WriteRemote(client, d.config.OutputCertPath, pfxData, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
case OUTPUT_FORMAT_JKS:
jksData, err := xcert.TransformCertificateFromPEMToJKS(certPEM, privkeyPEM, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to JKS: %w", err)
}
d.logger.Info("ssl certificate transformed to jks")
{
jksData, err := xcert.TransformCertificateFromPEMToJKS(certPEM, privkeyPEM, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate to JKS: %w", err)
}
d.logger.Info("ssl certificate transformed to jks")
if err := xssh.WriteRemote(client, d.config.OutputCertPath, jksData, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
if err := xssh.WriteRemote(client, d.config.OutputCertPath, jksData, d.config.UseSCP); err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
}
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
default:
return nil, fmt.Errorf("unsupported output format '%s'", d.config.OutputFormat)