Skip to content

Commit

Permalink
feat: start switching to libdns, update go
Browse files Browse the repository at this point in the history
Updates go to 1.19. It's not the latest, but was the most compatible
version I could bump up to while requiring the least changes. quic-go
had to be updated a few versions (again, not the latest) and required
minor changes.

This PR updates all tests to work for the certmagic changes, and they
are now all passing, except for the plugin count test.

Next up is to change all of the DNS providers in tmpim/dnsproviders to
use libdns. Eventually each provider should just become minimal glue
that takes `credentials ...string`, and all the environment variables
lego supported, and returns the configured libdns provider.

A temporary Cloudflare provider has been added in
`caskettls/dnsproviders.go` to show what that would look like. The
Cloudflare provider update already has a breaking change; legacy auth
tokens are no longer supported.
  • Loading branch information
Lemmmy committed Jan 26, 2024
1 parent dd12a8a commit d529a84
Show file tree
Hide file tree
Showing 19 changed files with 317 additions and 435 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.19
id: go

- name: Check out code into the Go module directory
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.19

- name: Checkout
uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Casketfile.*
!casketfile/
casket/go.mod
casket/go.sum
caskethttp/browse/tempTemplate*

og_static/

Expand Down
37 changes: 19 additions & 18 deletions casket/casketmain/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@ import (
_ "github.com/tmpim/casket-plugins/realip"
_ "github.com/tmpim/casket-plugins/tmpauth"
_ "github.com/tmpim/casket-plugins/webdav"
_ "github.com/tmpim/dnsproviders/azure"
_ "github.com/tmpim/dnsproviders/cloudflare"
_ "github.com/tmpim/dnsproviders/digitalocean"
_ "github.com/tmpim/dnsproviders/dnsimple"
_ "github.com/tmpim/dnsproviders/duckdns"
_ "github.com/tmpim/dnsproviders/dyn"
_ "github.com/tmpim/dnsproviders/godaddy"
_ "github.com/tmpim/dnsproviders/googlecloud"
_ "github.com/tmpim/dnsproviders/httpreq"
_ "github.com/tmpim/dnsproviders/lightsail"
_ "github.com/tmpim/dnsproviders/linode"
_ "github.com/tmpim/dnsproviders/namecheap"
_ "github.com/tmpim/dnsproviders/namedotcom"
_ "github.com/tmpim/dnsproviders/ovh"
_ "github.com/tmpim/dnsproviders/rackspace"
_ "github.com/tmpim/dnsproviders/rfc2136"
_ "github.com/tmpim/dnsproviders/route53"
_ "github.com/tmpim/dnsproviders/vultr"
// TODO: Temporary
// _ "github.com/tmpim/dnsproviders/azure"
// _ "github.com/tmpim/dnsproviders/cloudflare"
// _ "github.com/tmpim/dnsproviders/digitalocean"
// _ "github.com/tmpim/dnsproviders/dnsimple"
// _ "github.com/tmpim/dnsproviders/duckdns"
// _ "github.com/tmpim/dnsproviders/dyn"
// _ "github.com/tmpim/dnsproviders/godaddy"
// _ "github.com/tmpim/dnsproviders/googlecloud"
// _ "github.com/tmpim/dnsproviders/httpreq"
// _ "github.com/tmpim/dnsproviders/lightsail"
// _ "github.com/tmpim/dnsproviders/linode"
// _ "github.com/tmpim/dnsproviders/namecheap"
// _ "github.com/tmpim/dnsproviders/namedotcom"
// _ "github.com/tmpim/dnsproviders/ovh"
// _ "github.com/tmpim/dnsproviders/rackspace"
// _ "github.com/tmpim/dnsproviders/rfc2136"
// _ "github.com/tmpim/dnsproviders/route53"
// _ "github.com/tmpim/dnsproviders/vultr"
)
2 changes: 1 addition & 1 deletion caskethttp/bind/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func setupBind(c *casket.Controller) error {
if !c.Args(&config.ListenHost) {
return c.ArgErr()
}
config.TLS.Manager.ListenHost = config.ListenHost // necessary for ACME challenges, see issue #309
config.TLS.Issuer.ListenHost = config.ListenHost // necessary for ACME challenges, see issue #309
}
return nil
}
2 changes: 1 addition & 1 deletion caskethttp/bind/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestSetupBind(t *testing.T) {
if got, want := cfg.ListenHost, "1.2.3.4"; got != want {
t.Errorf("Expected the config's ListenHost to be %s, was %s", want, got)
}
if got, want := cfg.TLS.Manager.ListenHost, "1.2.3.4"; got != want {
if got, want := cfg.TLS.Issuer.ListenHost, "1.2.3.4"; got != want {
t.Errorf("Expected the TLS config's ListenHost to be %s, was %s", want, got)
}
}
6 changes: 5 additions & 1 deletion caskethttp/httpserver/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,13 @@ func GetConfig(c *casket.Controller) *SiteConfig {
// we should only get here during tests because directive
// actions typically skip the server blocks where we make
// the configs
magic := certmagic.NewDefault()
issuer := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{})
magic.Issuers = []certmagic.Issuer{issuer}

cfg := &SiteConfig{
Root: Root,
TLS: &caskettls.Config{Manager: certmagic.NewDefault()},
TLS: &caskettls.Config{Manager: magic, Issuer: issuer},
IndexPages: staticfiles.DefaultIndexPages,
}
ctx.saveConfig(key, cfg)
Expand Down
16 changes: 11 additions & 5 deletions caskethttp/httpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
"strings"
"time"

"github.com/lucas-clemente/quic-go/http3"
"github.com/quic-go/quic-go/http3"
"github.com/tmpim/casket"
"github.com/tmpim/casket/caskethttp/staticfiles"
"github.com/tmpim/casket/caskettls"
Expand Down Expand Up @@ -104,8 +104,14 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) {
if s.Server.TLSConfig != nil {
// enable QUIC if desired (requires HTTP/2)
if HTTP2 && QUIC {
s.quicServer = &http3.Server{Server: s.Server}
s.Server.Handler = s.wrapWithSvcHeaders(s.Server.Handler)
// As of https://github.com/quic-go/quic-go/pull/3397 this longer directly accepts a `Server` field
// TODO: Verify this correct
s.quicServer = &http3.Server{
Addr: s.Server.Addr,
Handler: s.Server.Handler,
TLSConfig: s.Server.TLSConfig,
MaxHeaderBytes: s.Server.MaxHeaderBytes,
}
}

// wrap the HTTP handler with a handler that does MITM detection
Expand Down Expand Up @@ -414,7 +420,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error)
// check for ACME challenge even if vhost is nil;
// could be a new host coming online soon - choose any
// vhost's cert manager configuration, I guess
if len(s.sites) > 0 && s.sites[0].TLS.Manager.HandleHTTPChallenge(w, r) {
if len(s.sites) > 0 && s.sites[0].TLS.Issuer.HandleHTTPChallenge(w, r) {
return 0, nil
}

Expand All @@ -431,7 +437,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error)

// we still check for ACME challenge if the vhost exists,
// because the HTTP challenge might be disabled by its config
if vhost.TLS.Manager.HandleHTTPChallenge(w, r) {
if vhost.TLS.Issuer.HandleHTTPChallenge(w, r) {
return 0, nil
}

Expand Down
4 changes: 2 additions & 2 deletions caskethttp/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ import (
"testing"
"time"

"github.com/lucas-clemente/quic-go/http3"
"github.com/tmpim/casket/caskethttp/httpserver"
"github.com/quic-go/quic-go/http3"
"github.com/tmpim/casket/casketfile"
"github.com/tmpim/casket/caskethttp/httpserver"

"golang.org/x/net/websocket"
)
Expand Down
7 changes: 4 additions & 3 deletions caskethttp/proxy/reverseproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ import (

"golang.org/x/net/http2"

"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/http3"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/tmpim/casket/caskethttp/httpserver"
)

Expand Down Expand Up @@ -257,7 +257,8 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int, t
rp.Transport = &http3.RoundTripper{
QuicConfig: &quic.Config{
HandshakeIdleTimeout: defaultCryptoHandshakeTimeout,
KeepAlive: true,
// https://github.com/quic-go/quic-go/pull/3444
KeepAlivePeriod: timeout / 2,
},
}
} else if keepalive != http.DefaultMaxIdleConnsPerHost || strings.HasPrefix(target.Scheme, "srv") {
Expand Down
2 changes: 1 addition & 1 deletion caskethttp/proxy/reverseproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"testing"
"time"

"github.com/lucas-clemente/quic-go/http3"
"github.com/quic-go/quic-go/http3"
)

const (
Expand Down
2 changes: 1 addition & 1 deletion caskethttp/proxy/upstream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"testing"
"time"

"github.com/lucas-clemente/quic-go/http3"
"github.com/quic-go/quic-go/http3"
"github.com/tmpim/casket/casketfile"
)

Expand Down
47 changes: 47 additions & 0 deletions caskettls/dnsproviders.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package caskettls

import (
"errors"
"fmt"
"github.com/caddyserver/certmagic"
"github.com/libdns/cloudflare"
"github.com/tmpim/casket/caskettls/env"
"strings"
)

const tokenErr = "cloudflare: email and API tokens are no longer supported in Casket, please use Scoped Tokens only. " +
"More info: https://pkg.go.dev/github.com/libdns/cloudflare#readme-authenticating"

func init() {
RegisterDNSProvider("cloudflare", func(credentials ...string) (certmagic.ACMEDNSProvider, error) {
switch len(credentials) {
case 0:
values, err := env.GetWithFallback([]string{
"CLOUDFLARE_ZONE_API_TOKEN",
"CF_ZONE_API_TOKEN",
"CLOUDFLARE_DNS_API_TOKEN",
"CF_DNS_API_TOKEN",
})
if err != nil {
return nil, fmt.Errorf("cloudflare: %v", err)
}

return &cloudflare.Provider{APIToken: values["CLOUDFLARE_ZONE_API_TOKEN"]}, nil
case 1:
return &cloudflare.Provider{APIToken: credentials[0]}, nil
case 2:
if strings.Contains(credentials[0], "@") {
return nil, errors.New(tokenErr)
}

switch credentials[0] {
case "zonetoken":
return &cloudflare.Provider{APIToken: credentials[1]}, nil
default:
return nil, errors.New(tokenErr)
}
default:
return nil, errors.New("invalid credentials length")
}
})
}
101 changes: 101 additions & 0 deletions caskettls/env/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package env

import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)

// Utilities for getting environment variables, to be used by the DNS providers in tmpim/dnsproviders. These utility
// functions are based on lego's config/env/env.go, which is licensed under the MIT license. See
// https://github.com/go-acme/lego/blob/master/LICENSE for more details.

// GetWithFallback Get environment variable values.
// The first name in each group is use as key in the result map.
//
// case 1:
//
// // LEGO_ONE="ONE"
// // LEGO_TWO="TWO"
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
// // => "LEGO_ONE" = "ONE"
//
// case 2:
//
// // LEGO_ONE=""
// // LEGO_TWO="TWO"
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
// // => "LEGO_ONE" = "TWO"
//
// case 3:
//
// // LEGO_ONE=""
// // LEGO_TWO=""
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
// // => error
func GetWithFallback(groups ...[]string) (map[string]string, error) {
values := map[string]string{}

var missingEnvVars []string
for _, names := range groups {
if len(names) == 0 {
return nil, errors.New("undefined environment variable names")
}

value, envVar := getOneWithFallback(names[0], names[1:]...)
if len(value) == 0 {
missingEnvVars = append(missingEnvVars, envVar)
continue
}
values[envVar] = value
}

if len(missingEnvVars) > 0 {
return nil, fmt.Errorf("some credentials information are missing: %s", strings.Join(missingEnvVars, ","))
}

return values, nil
}

func getOneWithFallback(main string, names ...string) (string, string) {
value := GetOrFile(main)
if len(value) > 0 {
return value, main
}

for _, name := range names {
value := GetOrFile(name)
if len(value) > 0 {
return value, main
}
}

return "", main
}

// GetOrFile Attempts to resolve 'key' as an environment variable.
// Failing that, it will check to see if '<key>_FILE' exists.
// If so, it will attempt to read from the referenced file to populate a value.
func GetOrFile(envVar string) string {
envVarValue := os.Getenv(envVar)
if envVarValue != "" {
return envVarValue
}

fileVar := envVar + "_FILE"
fileVarValue := os.Getenv(fileVar)
if fileVarValue == "" {
return envVarValue
}

fileContents, err := ioutil.ReadFile(fileVarValue)
if err != nil {
log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
return ""
}

return strings.TrimSuffix(string(fileContents), "\n")
}
Loading

0 comments on commit d529a84

Please sign in to comment.