mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-17 23:56:59 +01:00
feat(autocert): add EAB configuration support and corresponding tests
This commit is contained in:
@@ -25,10 +25,16 @@ type Config struct {
|
||||
KeyPath string `json:"key_path,omitempty"`
|
||||
ACMEKeyPath string `json:"acme_key_path,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
CADirURL string `json:"ca_dir_url,omitempty"`
|
||||
CACerts []string `json:"ca_certs,omitempty"`
|
||||
Options map[string]any `json:"options,omitempty"`
|
||||
|
||||
// Custom ACME CA
|
||||
CADirURL string `json:"ca_dir_url,omitempty"`
|
||||
CACerts []string `json:"ca_certs,omitempty"`
|
||||
|
||||
// EAB
|
||||
EABKid string `json:"eab_kid,omitempty" validate:"required_with=EABHmac"`
|
||||
EABHmac string `json:"eab_hmac,omitempty" validate:"required_with=EABKid"` // base64 encoded
|
||||
|
||||
HTTPClient *http.Client `json:"-"` // for tests only
|
||||
|
||||
challengeProvider challenge.Provider
|
||||
|
||||
31
internal/autocert/config_test.go
Normal file
31
internal/autocert/config_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/serialization"
|
||||
)
|
||||
|
||||
func TestEABConfigRequired(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg *Config
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "Missing EABKid", cfg: &Config{EABHmac: "1234567890"}, wantErr: true},
|
||||
{name: "Missing EABHmac", cfg: &Config{EABKid: "1234567890"}, wantErr: true},
|
||||
{name: "Valid EAB", cfg: &Config{EABKid: "1234567890", EABHmac: "1234567890"}, wantErr: false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
yaml := fmt.Appendf(nil, "eab_kid: %s\neab_hmac: %s", test.cfg.EABKid, test.cfg.EABHmac)
|
||||
cfg := Config{}
|
||||
err := serialization.UnmarshalValidateYAML(yaml, &cfg)
|
||||
if (err != nil) != test.wantErr {
|
||||
t.Errorf("Validate() error = %v, wantErr %v", err, test.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,10 @@ func (p *Provider) GetExpiries() CertExpiries {
|
||||
}
|
||||
|
||||
func (p *Provider) GetLastFailure() (time.Time, error) {
|
||||
if common.IsTest {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
if p.lastFailure.IsZero() {
|
||||
data, err := os.ReadFile(LastFailureFile)
|
||||
if err != nil {
|
||||
@@ -95,12 +99,18 @@ func (p *Provider) GetLastFailure() (time.Time, error) {
|
||||
}
|
||||
|
||||
func (p *Provider) UpdateLastFailure() error {
|
||||
if common.IsTest {
|
||||
return nil
|
||||
}
|
||||
t := time.Now()
|
||||
p.lastFailure = t
|
||||
return os.WriteFile(LastFailureFile, t.AppendFormat(nil, time.RFC3339), 0o600)
|
||||
}
|
||||
|
||||
func (p *Provider) ClearLastFailure() error {
|
||||
if common.IsTest {
|
||||
return nil
|
||||
}
|
||||
p.lastFailure = time.Time{}
|
||||
return os.Remove(LastFailureFile)
|
||||
}
|
||||
@@ -289,13 +299,23 @@ func (p *Provider) registerACME() error {
|
||||
if p.user.Registration != nil {
|
||||
return nil
|
||||
}
|
||||
if reg, err := p.client.Registration.ResolveAccountByKey(); err == nil {
|
||||
|
||||
reg, err := p.client.Registration.ResolveAccountByKey()
|
||||
if err == nil {
|
||||
p.user.Registration = reg
|
||||
log.Info().Msg("reused acme registration from private key")
|
||||
return nil
|
||||
}
|
||||
|
||||
reg, err := p.client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if p.cfg.EABKid != "" && p.cfg.EABHmac != "" {
|
||||
reg, err = p.client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: p.cfg.EABKid,
|
||||
HmacEncoded: p.cfg.EABHmac,
|
||||
})
|
||||
} else {
|
||||
reg, err = p.client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -138,6 +138,45 @@ func TestObtainCertFromCustomProvider(t *testing.T) {
|
||||
require.True(t, time.Now().Before(x509Cert.NotAfter))
|
||||
require.True(t, time.Now().After(x509Cert.NotBefore))
|
||||
})
|
||||
|
||||
t.Run("obtain cert with EAB from custom step-ca server", func(t *testing.T) {
|
||||
cfg := &autocert.Config{
|
||||
Email: "test@example.com",
|
||||
Domains: []string{"test.example.com"},
|
||||
Provider: autocert.ProviderCustom,
|
||||
CADirURL: acmeServer.URL() + "/acme/acme/directory",
|
||||
CertPath: "certs/stepca-eab-test.crt",
|
||||
KeyPath: "certs/stepca-eab-test.key",
|
||||
ACMEKeyPath: "certs/stepca-eab-test-acme.key",
|
||||
HTTPClient: acmeServer.httpClient(),
|
||||
EABKid: "kid-123",
|
||||
EABHmac: base64.RawURLEncoding.EncodeToString([]byte("secret")),
|
||||
}
|
||||
|
||||
err := error(cfg.Validate())
|
||||
require.NoError(t, err)
|
||||
|
||||
user, legoCfg, err := cfg.GetLegoConfig()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, user)
|
||||
require.NotNil(t, legoCfg)
|
||||
|
||||
provider := autocert.NewProvider(cfg, user, legoCfg)
|
||||
require.NotNil(t, provider)
|
||||
|
||||
err = provider.ObtainCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
cert, err := provider.GetCert(nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
|
||||
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, x509Cert.DNSNames, "test.example.com")
|
||||
require.True(t, time.Now().Before(x509Cert.NotAfter))
|
||||
require.True(t, time.Now().After(x509Cert.NotBefore))
|
||||
})
|
||||
}
|
||||
|
||||
// testACMEServer implements a minimal ACME server for testing.
|
||||
|
||||
Reference in New Issue
Block a user