mirror of
https://github.com/go-acme/lego
synced 2026-03-14 14:35:48 +01:00
289 lines
8.3 KiB
Go
289 lines
8.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-acme/lego/v5/challenge"
|
|
"github.com/go-acme/lego/v5/challenge/dns01"
|
|
"github.com/go-acme/lego/v5/challenge/dnspersist01"
|
|
"github.com/go-acme/lego/v5/challenge/http01"
|
|
"github.com/go-acme/lego/v5/challenge/tlsalpn01"
|
|
"github.com/go-acme/lego/v5/lego"
|
|
"github.com/go-acme/lego/v5/log"
|
|
"github.com/go-acme/lego/v5/providers/dns"
|
|
"github.com/go-acme/lego/v5/providers/http/memcached"
|
|
"github.com/go-acme/lego/v5/providers/http/s3"
|
|
"github.com/go-acme/lego/v5/providers/http/webroot"
|
|
"github.com/go-acme/lego/v5/registration"
|
|
"github.com/urfave/cli/v3"
|
|
)
|
|
|
|
//nolint:gocyclo // challenge setup dispatch is expected to branch by enabled challenge type.
|
|
func setupChallenges(cmd *cli.Command, client *lego.Client, account registration.User) {
|
|
if !cmd.Bool(flgHTTP) && !cmd.Bool(flgTLS) && !cmd.IsSet(flgDNS) && !cmd.Bool(flgDNSPersist) {
|
|
log.Fatal(fmt.Sprintf("No challenge selected. You must specify at least one challenge: `--%s`, `--%s`, `--%s`, `--%s`.",
|
|
flgHTTP, flgTLS, flgDNS, flgDNSPersist))
|
|
}
|
|
|
|
if cmd.Bool(flgHTTP) {
|
|
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(cmd), http01.SetDelay(cmd.Duration(flgHTTPDelay)))
|
|
if err != nil {
|
|
log.Fatal("Could not set HTTP challenge provider.", log.ErrorAttr(err))
|
|
}
|
|
}
|
|
|
|
if cmd.Bool(flgTLS) {
|
|
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(cmd), tlsalpn01.SetDelay(cmd.Duration(flgTLSDelay)))
|
|
if err != nil {
|
|
log.Fatal("Could not set TLS challenge provider.", log.ErrorAttr(err))
|
|
}
|
|
}
|
|
|
|
if cmd.IsSet(flgDNS) {
|
|
err := setupDNS(cmd, client)
|
|
if err != nil {
|
|
log.Fatal("Could not set DNS challenge provider.", log.ErrorAttr(err))
|
|
}
|
|
}
|
|
|
|
if cmd.Bool(flgDNSPersist) {
|
|
err := setupDNSPersist(cmd, client, account)
|
|
if err != nil {
|
|
log.Fatal("Could not set DNS-PERSIST-01 challenge provider.", log.ErrorAttr(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
//nolint:gocyclo // the complexity is expected.
|
|
func setupHTTPProvider(cmd *cli.Command) challenge.Provider {
|
|
switch {
|
|
case cmd.IsSet(flgHTTPWebroot):
|
|
ps, err := webroot.NewHTTPProvider(cmd.String(flgHTTPWebroot))
|
|
if err != nil {
|
|
log.Fatal("Could not create the webroot provider.",
|
|
slog.String("flag", flgHTTPWebroot),
|
|
slog.String("webRoot", cmd.String(flgHTTPWebroot)),
|
|
log.ErrorAttr(err),
|
|
)
|
|
}
|
|
|
|
return ps
|
|
|
|
case cmd.IsSet(flgHTTPMemcachedHost):
|
|
ps, err := memcached.NewMemcachedProvider(cmd.StringSlice(flgHTTPMemcachedHost))
|
|
if err != nil {
|
|
log.Fatal("Could not create the memcached provider.",
|
|
slog.String("flag", flgHTTPMemcachedHost),
|
|
slog.String("memcachedHosts", strings.Join(cmd.StringSlice(flgHTTPMemcachedHost), ", ")),
|
|
log.ErrorAttr(err),
|
|
)
|
|
}
|
|
|
|
return ps
|
|
|
|
case cmd.IsSet(flgHTTPS3Bucket):
|
|
ps, err := s3.NewHTTPProvider(cmd.String(flgHTTPS3Bucket))
|
|
if err != nil {
|
|
log.Fatal("Could not create the S3 provider.",
|
|
slog.String("flag", flgHTTPS3Bucket),
|
|
slog.String("bucket", cmd.String(flgHTTPS3Bucket)),
|
|
log.ErrorAttr(err),
|
|
)
|
|
}
|
|
|
|
return ps
|
|
|
|
case cmd.IsSet(flgHTTPPort):
|
|
iface := cmd.String(flgHTTPPort)
|
|
|
|
if !strings.Contains(iface, ":") {
|
|
log.Fatal(
|
|
fmt.Sprintf("The --%s switch only accepts interface:port or :port for its argument.", flgHTTPPort),
|
|
slog.String("flag", flgHTTPPort),
|
|
slog.String("port", cmd.String(flgHTTPPort)),
|
|
)
|
|
}
|
|
|
|
host, port, err := net.SplitHostPort(iface)
|
|
if err != nil {
|
|
log.Fatal("Could not split host and port.", slog.String("iface", iface), log.ErrorAttr(err))
|
|
}
|
|
|
|
srv := http01.NewProviderServerWithOptions(http01.Options{
|
|
Network: getNetworkStack(cmd).Network("tcp"),
|
|
Address: net.JoinHostPort(host, port),
|
|
})
|
|
|
|
if header := cmd.String(flgHTTPProxyHeader); header != "" {
|
|
srv.SetProxyHeader(header)
|
|
}
|
|
|
|
return srv
|
|
|
|
case cmd.Bool(flgHTTP):
|
|
srv := http01.NewProviderServerWithOptions(http01.Options{
|
|
Network: getNetworkStack(cmd).Network("tcp"),
|
|
Address: net.JoinHostPort("", ":80"),
|
|
})
|
|
|
|
if header := cmd.String(flgHTTPProxyHeader); header != "" {
|
|
srv.SetProxyHeader(header)
|
|
}
|
|
|
|
return srv
|
|
|
|
default:
|
|
log.Fatal("Invalid HTTP challenge options.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func setupTLSProvider(cmd *cli.Command) challenge.Provider {
|
|
switch {
|
|
case cmd.IsSet(flgTLSPort):
|
|
iface := cmd.String(flgTLSPort)
|
|
if !strings.Contains(iface, ":") {
|
|
log.Fatal(fmt.Sprintf("The --%s switch only accepts interface:port or :port for its argument.", flgTLSPort))
|
|
}
|
|
|
|
host, port, err := net.SplitHostPort(iface)
|
|
if err != nil {
|
|
log.Fatal("Could not split host and port.", slog.String("iface", iface), log.ErrorAttr(err))
|
|
}
|
|
|
|
return tlsalpn01.NewProviderServerWithOptions(tlsalpn01.Options{
|
|
Network: getNetworkStack(cmd).Network("tcp"),
|
|
Host: host,
|
|
Port: port,
|
|
})
|
|
|
|
case cmd.Bool(flgTLS):
|
|
return tlsalpn01.NewProviderServerWithOptions(tlsalpn01.Options{
|
|
Network: getNetworkStack(cmd).Network("tcp"),
|
|
})
|
|
|
|
default:
|
|
log.Fatal("Invalid HTTP challenge options.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func setupDNS(cmd *cli.Command, client *lego.Client) error {
|
|
err := validatePropagationExclusiveOptions(cmd, flgDNSPropagationWait, flgDNSPropagationDisableANS, flgDNSPropagationDisableRNS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
provider, err := dns.NewDNSChallengeProviderByName(cmd.String(flgDNS))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
opts := &dns01.Options{RecursiveNameservers: cmd.StringSlice(flgDNSResolvers)}
|
|
|
|
if cmd.IsSet(flgDNSTimeout) {
|
|
opts.Timeout = time.Duration(cmd.Int(flgDNSTimeout)) * time.Second
|
|
}
|
|
|
|
opts.NetworkStack = getNetworkStack(cmd)
|
|
|
|
dns01.SetDefaultClient(dns01.NewClient(opts))
|
|
|
|
shouldWait := cmd.IsSet(flgDNSPropagationWait)
|
|
|
|
err = client.Challenge.SetDNS01Provider(provider,
|
|
dns01.CondOptions(shouldWait,
|
|
dns01.PropagationWait(cmd.Duration(flgDNSPropagationWait), true),
|
|
),
|
|
dns01.CondOptions(!shouldWait,
|
|
dns01.CondOptions(cmd.Bool(flgDNSPropagationDisableANS),
|
|
dns01.DisableAuthoritativeNssPropagationRequirement(),
|
|
),
|
|
dns01.CondOptions(cmd.Bool(flgDNSPropagationDisableRNS),
|
|
dns01.DisableRecursiveNSsPropagationRequirement(),
|
|
),
|
|
),
|
|
)
|
|
|
|
return err
|
|
}
|
|
|
|
func setupDNSPersist(cmd *cli.Command, client *lego.Client, account registration.User) error {
|
|
if account == nil || account.GetRegistration() == nil || account.GetRegistration().URI == "" {
|
|
return errors.New("dns-persist-01 requires a registered account with an account URI")
|
|
}
|
|
|
|
err := validatePropagationExclusiveOptions(cmd, flgDNSPersistPropagationWait, flgDNSPersistPropagationDisableANS, flgDNSPersistIssuerDomainName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
opts := &dnspersist01.Options{RecursiveNameservers: cmd.StringSlice(flgDNSPersistResolvers)}
|
|
|
|
if cmd.IsSet(flgDNSPersistTimeout) {
|
|
opts.Timeout = time.Duration(cmd.Int(flgDNSPersistTimeout)) * time.Second
|
|
}
|
|
|
|
opts.NetworkStack = getNetworkStack(cmd)
|
|
|
|
dnspersist01.SetDefaultClient(dnspersist01.NewClient(opts))
|
|
|
|
shouldWait := cmd.IsSet(flgDNSPersistPropagationWait)
|
|
|
|
return client.Challenge.SetDNSPersist01(
|
|
dnspersist01.WithAccountURI(account.GetRegistration().URI),
|
|
dnspersist01.WithIssuerDomainName(cmd.String(flgDNSPersistIssuerDomainName)),
|
|
dnspersist01.CondOptions(cmd.IsSet(flgDNSPersistPersistUntil),
|
|
dnspersist01.WithPersistUntil(cmd.Timestamp(flgDNSPersistPersistUntil)),
|
|
),
|
|
dnspersist01.CondOptions(shouldWait,
|
|
dnspersist01.PropagationWait(cmd.Duration(flgDNSPersistPropagationWait), true),
|
|
),
|
|
dnspersist01.CondOptions(!shouldWait,
|
|
dnspersist01.CondOptions(cmd.Bool(flgDNSPersistPropagationDisableANS),
|
|
dnspersist01.DisableAuthoritativeNssPropagationRequirement(),
|
|
),
|
|
dnspersist01.CondOptions(cmd.Bool(flgDNSSPersistPropagationDisableRNS),
|
|
dnspersist01.DisableRecursiveNSsPropagationRequirement(),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
func validatePropagationExclusiveOptions(cmd *cli.Command, flgWait, flgANS, flgDNS string) error {
|
|
if !cmd.IsSet(flgWait) {
|
|
return nil
|
|
}
|
|
|
|
if isSetBool(cmd, flgANS) {
|
|
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgWait, flgANS)
|
|
}
|
|
|
|
if isSetBool(cmd, flgDNS) {
|
|
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgWait, flgDNS)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getNetworkStack(cmd *cli.Command) challenge.NetworkStack {
|
|
switch {
|
|
case cmd.Bool(flgIPv4Only):
|
|
return challenge.IPv4Only
|
|
|
|
case cmd.Bool(flgIPv6Only):
|
|
return challenge.IPv6Only
|
|
|
|
default:
|
|
return challenge.DualStack
|
|
}
|
|
}
|
|
|
|
func isSetBool(cmd *cli.Command, name string) bool {
|
|
return cmd.IsSet(name) && cmd.Bool(name)
|
|
}
|