From 4744048a38251001462e5d4b70534d6b242143e9 Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Thu, 10 Apr 2025 06:13:42 +0200 Subject: [PATCH] Add acme autocert configuration --- Dockerfile | 4 +++ cmd/go-away/main.go | 82 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index c8ee672..3eb2675 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,6 +44,8 @@ ENV GOAWAY_CLIENT_IP_HEADER="" ENV GOAWAY_JWT_PRIVATE_KEY_SEED="" ENV GOAWAY_BACKEND="" ENV GOAWAY_DNSBL="dnsbl.dronebl.org" +ENV GOAWAY_ACME_AUTOCERT="" +ENV GOAWAY_CACHE="/cache" EXPOSE 8080/tcp EXPOSE 8080/udp @@ -52,7 +54,9 @@ ENV JWT_PRIVATE_KEY_SEED="${GOAWAY_JWT_PRIVATE_KEY_SEED}" ENTRYPOINT /bin/go-away --bind ${GOAWAY_BIND} --bind-network ${GOAWAY_BIND_NETWORK} --socket-mode ${GOAWAY_SOCKET_MODE} \ --policy ${GOAWAY_POLICY} --client-ip-header ${GOAWAY_CLIENT_IP_HEADER} \ + --cache ${GOAWAY_CACHE} \ --dnsbl ${GOAWAY_DNSBL} \ --challenge-template ${GOAWAY_CHALLENGE_TEMPLATE} --challenge-template-theme ${GOAWAY_CHALLENGE_TEMPLATE_THEME} \ --slog-level ${GOAWAY_SLOG_LEVEL} \ + --acme-autocert ${GOAWAY_ACME_AUTOCERT} \ --backend ${GOAWAY_BACKEND} \ No newline at end of file diff --git a/cmd/go-away/main.go b/cmd/go-away/main.go index 07d270c..aa63335 100644 --- a/cmd/go-away/main.go +++ b/cmd/go-away/main.go @@ -11,6 +11,8 @@ import ( "git.gammaspectra.live/git/go-away/lib" "git.gammaspectra.live/git/go-away/lib/policy" "git.gammaspectra.live/git/go-away/utils" + "golang.org/x/crypto/acme" + "golang.org/x/crypto/acme/autocert" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" "gopkg.in/yaml.v3" @@ -20,6 +22,7 @@ import ( "net" "net/http" "os" + "path" "runtime/debug" "strconv" "strings" @@ -81,31 +84,65 @@ func (v *MultiVar) Set(value string) error { return nil } -func newServer(handler http.Handler) *http.Server { - h2s := &http2.Server{} +func newServer(handler http.Handler, manager *autocert.Manager) *http.Server { - // TODO: use Go 1.24 Server.Protocols to add H2C - // https://pkg.go.dev/net/http#Server.Protocols - h1s := &http.Server{ - Handler: h2c.NewHandler(handler, h2s), + if manager == nil { + h2s := &http2.Server{} + + // TODO: use Go 1.24 Server.Protocols to add H2C + // https://pkg.go.dev/net/http#Server.Protocols + h1s := &http.Server{ + Handler: h2c.NewHandler(handler, h2s), + } + + return h1s + } else { + return &http.Server{ + TLSConfig: manager.TLSConfig(), + Handler: handler, + } + } +} + +func newACMEManager(clientDirectory string, backends map[string]http.Handler) *autocert.Manager { + + var domains []string + for d := range backends { + parts := strings.Split(d, ":") + d = parts[0] + if net.ParseIP(d) != nil { + continue + } + domains = append(domains, d) } - return h1s + manager := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(domains...), + Client: &acme.Client{ + HTTPClient: http.DefaultClient, + DirectoryURL: clientDirectory, + }, + } + return manager } func main() { - bind := flag.String("bind", ":8080", "network address to bind HTTP to") + bind := flag.String("bind", ":8080", "network address to bind HTTP/HTTP(s) to") bindNetwork := flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp") socketMode := flag.String("socket-mode", "0770", "socket mode (permissions) for unix domain sockets.") slogLevel := flag.String("slog-level", "WARN", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)") debugMode := flag.Bool("debug", false, "debug mode with logs and server timings") passThrough := flag.Bool("passthrough", false, "passthrough mode sends all requests to matching backends until state is loaded") + acmeAutocert := flag.String("acme-autocert", "", "enables HTTP(s) mode and uses the provided ACME server URL or available service (available: letsencrypt)") clientIpHeader := flag.String("client-ip-header", "", "Client HTTP header to fetch their IP address from (X-Real-Ip, X-Client-Ip, X-Forwarded-For, Cf-Connecting-Ip, etc.)") dnsbl := flag.String("dnsbl", "dnsbl.dronebl.org", "blocklist for DNSBL (default DroneBL)") + cachePath := flag.String("cache", path.Join(os.TempDir(), "go_away_cache"), "path to temporary cache directory") + policyFile := flag.String("policy", "", "path to policy YAML file") challengeTemplate := flag.String("challenge-template", "anubis", "name or path of the challenge template to use (anubis, forgejo)") challengeTemplateTheme := flag.String("challenge-template-theme", "", "name of the challenge template theme to use (forgejo => [forgejo-dark, forgejo-light, gitea...])") @@ -202,6 +239,31 @@ func main() { createdBackends[k] = backend } + if *cachePath != "" { + err = os.MkdirAll(*cachePath, 0755) + if err != nil { + log.Fatal(fmt.Errorf("failed to create cache directory: %w", err)) + } + } + + var acmeManager *autocert.Manager + + if *acmeAutocert != "" { + switch *acmeAutocert { + case "letsencrypt": + *acmeAutocert = "https://acme-v02.api.letsencrypt.org/directory" + } + + acmeManager = newACMEManager(*acmeAutocert, createdBackends) + if *cachePath != "" { + err = os.MkdirAll(path.Join(*cachePath, "acme"), 0755) + if err != nil { + log.Fatal(fmt.Errorf("failed to create acme cache directory: %w", err)) + } + acmeManager.Cache = autocert.DirCache(path.Join(*cachePath, "acme")) + } + } + var wg sync.WaitGroup passThroughCtx, cancelFunc := context.WithCancel(context.Background()) @@ -220,7 +282,7 @@ func main() { } backend.ServeHTTP(w, r) - })) + }), acmeManager) listener, listenUrl := setupListener(*bindNetwork, *bind, *socketMode) slog.Warn( @@ -279,7 +341,7 @@ func main() { "url", listenUrl, ) - server := newServer(state) + server := newServer(state, acmeManager) if err := server.Serve(listener); !errors.Is(err, http.ErrServerClosed) { log.Fatal(err)