mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-29 05:11:51 +02: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"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
ACMEKeyPath string `json:"acme_key_path,omitempty"`
|
ACMEKeyPath string `json:"acme_key_path,omitempty"`
|
||||||
Provider string `json:"provider,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"`
|
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
|
HTTPClient *http.Client `json:"-"` // for tests only
|
||||||
|
|
||||||
challengeProvider challenge.Provider
|
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) {
|
func (p *Provider) GetLastFailure() (time.Time, error) {
|
||||||
|
if common.IsTest {
|
||||||
|
return time.Time{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if p.lastFailure.IsZero() {
|
if p.lastFailure.IsZero() {
|
||||||
data, err := os.ReadFile(LastFailureFile)
|
data, err := os.ReadFile(LastFailureFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -95,12 +99,18 @@ func (p *Provider) GetLastFailure() (time.Time, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) UpdateLastFailure() error {
|
func (p *Provider) UpdateLastFailure() error {
|
||||||
|
if common.IsTest {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
p.lastFailure = t
|
p.lastFailure = t
|
||||||
return os.WriteFile(LastFailureFile, t.AppendFormat(nil, time.RFC3339), 0o600)
|
return os.WriteFile(LastFailureFile, t.AppendFormat(nil, time.RFC3339), 0o600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) ClearLastFailure() error {
|
func (p *Provider) ClearLastFailure() error {
|
||||||
|
if common.IsTest {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
p.lastFailure = time.Time{}
|
p.lastFailure = time.Time{}
|
||||||
return os.Remove(LastFailureFile)
|
return os.Remove(LastFailureFile)
|
||||||
}
|
}
|
||||||
@@ -289,13 +299,23 @@ func (p *Provider) registerACME() error {
|
|||||||
if p.user.Registration != nil {
|
if p.user.Registration != nil {
|
||||||
return 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
|
p.user.Registration = reg
|
||||||
log.Info().Msg("reused acme registration from private key")
|
log.Info().Msg("reused acme registration from private key")
|
||||||
return nil
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,45 @@ func TestObtainCertFromCustomProvider(t *testing.T) {
|
|||||||
require.True(t, time.Now().Before(x509Cert.NotAfter))
|
require.True(t, time.Now().Before(x509Cert.NotAfter))
|
||||||
require.True(t, time.Now().After(x509Cert.NotBefore))
|
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.
|
// testACMEServer implements a minimal ACME server for testing.
|
||||||
|
|||||||
Reference in New Issue
Block a user