An account for many customers
This commit is contained in:
@@ -12,6 +12,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||
"github.com/usual2970/certimate/internal/repository"
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
@@ -78,9 +80,37 @@ type ApplyOption struct {
|
||||
}
|
||||
|
||||
type ApplyUser struct {
|
||||
Ca string
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
key string
|
||||
}
|
||||
|
||||
func newApplyUser(ca, email string) (*ApplyUser, error) {
|
||||
repo := getAcmeAccountRepository()
|
||||
rs := &ApplyUser{
|
||||
Ca: ca,
|
||||
Email: email,
|
||||
}
|
||||
resp, err := repo.GetByCAAndEmail(ca, email)
|
||||
if err != nil {
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyStr, err := x509.PrivateKeyToPEM(privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs.key = keyStr
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
rs.Registration = resp.Resource
|
||||
rs.key = resp.Key
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (u *ApplyUser) GetEmail() string {
|
||||
@@ -92,6 +122,15 @@ func (u ApplyUser) GetRegistration() *registration.Resource {
|
||||
}
|
||||
|
||||
func (u *ApplyUser) GetPrivateKey() crypto.PrivateKey {
|
||||
rs, _ := x509.ParsePrivateKeyFromPEM(u.key)
|
||||
return rs
|
||||
}
|
||||
|
||||
func (u *ApplyUser) hasRegistration() bool {
|
||||
return u.Registration != nil
|
||||
}
|
||||
|
||||
func (u *ApplyUser) getPrivateKeyString() string {
|
||||
return u.key
|
||||
}
|
||||
|
||||
@@ -182,21 +221,16 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
||||
}
|
||||
}
|
||||
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Some unified lego environment variables are configured here.
|
||||
// link: https://github.com/go-acme/lego/issues/1867
|
||||
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(option.DisableFollowCNAME))
|
||||
|
||||
myUser := ApplyUser{
|
||||
Email: option.Email,
|
||||
key: privateKey,
|
||||
myUser, err := newApplyUser(sslProvider.Provider, option.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := lego.NewConfig(&myUser)
|
||||
config := lego.NewConfig(myUser)
|
||||
|
||||
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
|
||||
config.CADirURL = sslProviderUrls[sslProvider.Provider]
|
||||
@@ -217,11 +251,13 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
||||
client.Challenge.SetDNS01Provider(provider, challengeOptions...)
|
||||
|
||||
// New users will need to register
|
||||
reg, err := getReg(client, sslProvider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to register: %w", err)
|
||||
if !myUser.hasRegistration() {
|
||||
reg, err := getReg(client, sslProvider, myUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to register: %w", err)
|
||||
}
|
||||
myUser.Registration = reg
|
||||
}
|
||||
myUser.Registration = reg
|
||||
|
||||
domains := strings.Split(option.Domain, ";")
|
||||
request := certificate.ObtainRequest{
|
||||
@@ -243,7 +279,16 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getReg(client *lego.Client, sslProvider *SSLProviderConfig) (*registration.Resource, error) {
|
||||
type AcmeAccountRepository interface {
|
||||
GetByCAAndEmail(ca, email string) (*domain.AcmeAccount, error)
|
||||
Save(ca, email, key string, resource *registration.Resource) error
|
||||
}
|
||||
|
||||
func getAcmeAccountRepository() AcmeAccountRepository {
|
||||
return repository.NewAcmeAccountRepository()
|
||||
}
|
||||
|
||||
func getReg(client *lego.Client, sslProvider *SSLProviderConfig, user *ApplyUser) (*registration.Resource, error) {
|
||||
var reg *registration.Resource
|
||||
var err error
|
||||
switch sslProvider.Provider {
|
||||
@@ -271,6 +316,12 @@ func getReg(client *lego.Client, sslProvider *SSLProviderConfig) (*registration.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := getAcmeAccountRepository()
|
||||
|
||||
if err := repo.Save(sslProvider.Provider, user.GetEmail(), user.getPrivateKeyString(), reg); err != nil {
|
||||
return nil, fmt.Errorf("failed to save registration: %w", err)
|
||||
}
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
|
||||
17
internal/domain/acme_accounts.go
Normal file
17
internal/domain/acme_accounts.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
type AcmeAccount struct {
|
||||
Id string
|
||||
Ca string
|
||||
Email string
|
||||
Resource *registration.Resource
|
||||
Key string
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package x509
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
@@ -46,3 +47,35 @@ func EqualCertificate(a, b *x509.Certificate) bool {
|
||||
a.Issuer.SerialNumber == b.Issuer.SerialNumber &&
|
||||
a.Subject.SerialNumber == b.Subject.SerialNumber
|
||||
}
|
||||
|
||||
// 将 ECDSA 私钥转换为 PEM 格式的字符串。
|
||||
func PrivateKeyToPEM(privateKey *ecdsa.PrivateKey) (string, error) {
|
||||
data, err := x509.MarshalECPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal EC private key: %w", err)
|
||||
}
|
||||
|
||||
block := &pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: data,
|
||||
}
|
||||
|
||||
return string(pem.EncodeToMemory(block)), nil
|
||||
}
|
||||
|
||||
// 从 PEM 编码的私钥字符串解析并返回一个 ECDSA 私钥对象。
|
||||
func ParsePrivateKeyFromPEM(privateKeyPem string) (*ecdsa.PrivateKey, error) {
|
||||
pemData := []byte(privateKeyPem)
|
||||
|
||||
block, _ := pem.Decode(pemData)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
|
||||
privateKey, err := x509.ParseECPrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
51
internal/repository/acme_account.go
Normal file
51
internal/repository/acme_account.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
)
|
||||
|
||||
type AcmeAccountRepository struct{}
|
||||
|
||||
func NewAcmeAccountRepository() *AcmeAccountRepository {
|
||||
return &AcmeAccountRepository{}
|
||||
}
|
||||
|
||||
func (r *AcmeAccountRepository) GetByCAAndEmail(ca, email string) (*domain.AcmeAccount, error) {
|
||||
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("acme_accounts", "ca={:ca} && email={:email}", dbx.Params{"ca": ca, "email": email})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resource := ®istration.Resource{}
|
||||
if err := resp.UnmarshalJSONField("resource", resource); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.AcmeAccount{
|
||||
Id: resp.GetString("id"),
|
||||
Ca: resp.GetString("ca"),
|
||||
Email: resp.GetString("email"),
|
||||
Key: resp.GetString("key"),
|
||||
Resource: resource,
|
||||
Created: resp.GetTime("created"),
|
||||
Updated: resp.GetTime("updated"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *AcmeAccountRepository) Save(ca, email, key string, resource *registration.Resource) error {
|
||||
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("acme_accounts")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := models.NewRecord(collection)
|
||||
record.Set("ca", ca)
|
||||
record.Set("email", email)
|
||||
record.Set("key", key)
|
||||
record.Set("resource", resource)
|
||||
return app.GetApp().Dao().Save(record)
|
||||
}
|
||||
Reference in New Issue
Block a user