From fc1509eed48bf154e525f1e2984e8697b871a979 Mon Sep 17 00:00:00 2001 From: Sean Lane <5761232+seanlane@users.noreply.github.com> Date: Mon, 15 Jan 2018 18:15:17 -0700 Subject: [PATCH 01/19] Update README.md (change to ownership command) (#1970) * Update README.md I believe the owner and group of the `chown` command here are mixed up. As it was caused a permissions issue, with the service being unable to read the directory. * Update README.md * Update README.md Revert changes back to the original suggested changes --- dist/init/linux-systemd/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/init/linux-systemd/README.md b/dist/init/linux-systemd/README.md index 70d269a7c..aa3dd75a3 100644 --- a/dist/init/linux-systemd/README.md +++ b/dist/init/linux-systemd/README.md @@ -46,7 +46,7 @@ sudo useradd \ sudo mkdir /etc/caddy sudo chown -R root:www-data /etc/caddy sudo mkdir /etc/ssl/caddy -sudo chown -R www-data:root /etc/ssl/caddy +sudo chown -R root:www-data /etc/ssl/caddy sudo chmod 0770 /etc/ssl/caddy ``` From c296d7e7e06c3fe5e4877c2b1b0df153917a2b24 Mon Sep 17 00:00:00 2001 From: detaoin Date: Tue, 16 Jan 2018 02:17:27 +0100 Subject: [PATCH 02/19] caddymain: fix setCPU silently ignoring small percent values (#1969) * caddymain: fix setCPU silently ignoring small percent values the percent value is resolved in a GOMAXPROCS relative number by simple division, thus rounding down the non-integer quotient. If zero, the call to runtime.GOMAXPROCS is silently ignored. We decide here to exceptionally round up the CPU cap in case of percent values that are too small. * caddymain: gofmt -s --- caddy/caddymain/run.go | 5 +++++ caddy/caddymain/run_test.go | 1 + 2 files changed, 6 insertions(+) diff --git a/caddy/caddymain/run.go b/caddy/caddymain/run.go index 81f97f2a8..82087d8ff 100644 --- a/caddy/caddymain/run.go +++ b/caddy/caddymain/run.go @@ -221,6 +221,8 @@ func setVersion() { // setCPU parses string cpu and sets GOMAXPROCS // according to its value. It accepts either // a number (e.g. 3) or a percent (e.g. 50%). +// If the percent resolves to less than a single +// GOMAXPROCS, it rounds it up to GOMAXPROCS=1. func setCPU(cpu string) error { var numCPU int @@ -236,6 +238,9 @@ func setCPU(cpu string) error { } percent = float32(pctInt) / 100 numCPU = int(float32(availCPU) * percent) + if numCPU < 1 { + numCPU = 1 + } } else { // Number num, err := strconv.Atoi(cpu) diff --git a/caddy/caddymain/run_test.go b/caddy/caddymain/run_test.go index 141efe208..c26a54a9e 100644 --- a/caddy/caddymain/run_test.go +++ b/caddy/caddymain/run_test.go @@ -41,6 +41,7 @@ func TestSetCPU(t *testing.T) { {"invalid input", currentCPU, true}, {"invalid input%", currentCPU, true}, {"9999", maxCPU, false}, // over available CPU + {"1%", 1, false}, // under a single CPU; assume maxCPU < 100 } { err := setCPU(test.input) if test.shouldErr && err == nil { From d35719daed3d7c06e5855c0b258a54b2d837ba90 Mon Sep 17 00:00:00 2001 From: magikstm Date: Mon, 15 Jan 2018 20:18:25 -0500 Subject: [PATCH 03/19] browse: Correct 'modified' date alignment (#1954) * Correct browse modified date alignment * New solution to adjust alignment --- caddyhttp/browse/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caddyhttp/browse/setup.go b/caddyhttp/browse/setup.go index 6979308ba..a7cef6aa1 100644 --- a/caddyhttp/browse/setup.go +++ b/caddyhttp/browse/setup.go @@ -499,7 +499,7 @@ footer { return; } } - e.textContent = d.toLocaleString(); + e.textContent = d.toLocaleString([], {day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit"}); } var timeList = Array.prototype.slice.call(document.getElementsByTagName("time")); timeList.forEach(localizeDatetime); From 8a326d4dc16e7a9c83e8b8aa4e4d6d35b6c8e721 Mon Sep 17 00:00:00 2001 From: Andreas Ulm Date: Tue, 16 Jan 2018 02:22:53 +0100 Subject: [PATCH 04/19] implemented sourcing of default file for sysvinit (#1984) * implemented source of default file for sysvinit Signed-off-by: root360-AndreasUlm * added documentation in README Signed-off-by: root360-AndreasUlm * fixed sourcing command for sh Signed-off-by: root360-AndreasUlm * implemented source of default file for sysvinit Signed-off-by: root360-AndreasUlm * added documentation in README Signed-off-by: root360-AndreasUlm * fixed sourcing command for sh Signed-off-by: root360-AndreasUlm * implemented DAEMONOPTS overwrite Signed-off-by: root360-AndreasUlm --- dist/init/linux-sysvinit/README.md | 16 ++++++++++++++++ dist/init/linux-sysvinit/caddy | 18 +++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/dist/init/linux-sysvinit/README.md b/dist/init/linux-sysvinit/README.md index f5ce4de09..c2a8fced2 100644 --- a/dist/init/linux-sysvinit/README.md +++ b/dist/init/linux-sysvinit/README.md @@ -9,3 +9,19 @@ Usage * Ensure that the folder `/etc/caddy` exists and that the folder `/etc/ssl/caddy` is owned by `www-data`. * Create a Caddyfile in `/etc/caddy/Caddyfile` * Now you can use `service caddy start|stop|restart|reload|status` as `root`. + +Init script manipulation +----- + +The init script supports configuration via the following files: +* `/etc/default/caddy` ( Debian based https://www.debian.org/doc/manuals/debian-reference/ch03.en.html#_the_default_parameter_for_each_init_script ) +* `/etc/sysconfig/caddy` ( CentOS based https://www.centos.org/docs/5/html/5.2/Deployment_Guide/s1-sysconfig-files.html ) + +The following variables can be changed: +* DAEMON: path to the caddy binary file (default: `/usr/local/bin/caddy`) +* DAEMONUSER: user used to run caddy (default: `www-data`) +* PIDFILE: path to the pidfile (default: `/var/run/$NAME.pid`) +* LOGFILE: path to the log file for caddy daemon (not for access logs) (default: `/var/log/$NAME.log`) +* CONFIGFILE: path to the caddy configuration file (default: `/etc/caddy/Caddyfile`) +* CADDYPATH: path for SSL certificates managed by caddy (default: `/etc/ssl/caddy`) +* ULIMIT: open files limit (default: `8192`) diff --git a/dist/init/linux-sysvinit/caddy b/dist/init/linux-sysvinit/caddy index 88fc96398..b52a8345b 100644 --- a/dist/init/linux-sysvinit/caddy +++ b/dist/init/linux-sysvinit/caddy @@ -20,18 +20,30 @@ DAEMONUSER=www-data PIDFILE=/var/run/$NAME.pid LOGFILE=/var/log/$NAME.log CONFIGFILE=/etc/caddy/Caddyfile -DAEMONOPTS="-agree=true -log=$LOGFILE -conf=$CONFIGFILE" USERBIND="setcap cap_net_bind_service=+ep" STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}" +CADDYPATH=/etc/ssl/caddy +ULIMIT=8192 test -x $DAEMON || exit 0 +# allow overwriting variables +# Debian based +[ -e "/etc/default/caddy" ] && . /etc/default/caddy +# CentOS based +[ -e "/etc/sysconfig/caddy" ] && . /etc/sysconfig/caddy + +if [ -z "$DAEMONOPTS" ]; then + # daemon options + DAEMONOPTS="-agree=true -log=$LOGFILE -conf=$CONFIGFILE" +fi + # Set the CADDYPATH; Let's Encrypt certificates will be written to this directory. -export CADDYPATH=/etc/ssl/caddy +export CADDYPATH # Set the ulimits -ulimit -n 8192 +ulimit -n ${ULIMIT} start() { From 55a564df6dd34f4745ee8ba4d9c5671064b47992 Mon Sep 17 00:00:00 2001 From: Tw Date: Tue, 16 Jan 2018 09:27:55 +0800 Subject: [PATCH 05/19] template: add extension filter test and simplify test code (#1996) Signed-off-by: Tw --- caddyhttp/templates/templates_test.go | 159 ++++++++++------------ caddyhttp/templates/testdata/as_it_is.txt | 1 + 2 files changed, 70 insertions(+), 90 deletions(-) create mode 100644 caddyhttp/templates/testdata/as_it_is.txt diff --git a/caddyhttp/templates/templates_test.go b/caddyhttp/templates/templates_test.go index 839c39f76..289f1a85d 100644 --- a/caddyhttp/templates/templates_test.go +++ b/caddyhttp/templates/templates_test.go @@ -62,100 +62,79 @@ func TestTemplates(t *testing.T) { BufPool: &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}, } - // Test tmpl on /photos/test.html - req, err := http.NewRequest("GET", "/photos/test.html", nil) - if err != nil { - t.Fatalf("Test: Could not create HTTP request: %v", err) - } - req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)) - - rec := httptest.NewRecorder() - - tmpl.ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) - } - - respBody := rec.Body.String() - expectedBody := `test page

Header title

- -` - - if respBody != expectedBody { - t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) - } - - // Test tmpl on /images/img.htm - req, err = http.NewRequest("GET", "/images/img.htm", nil) - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)) - - rec = httptest.NewRecorder() - - tmpl.ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) - } - - respBody = rec.Body.String() - expectedBody = `img

Header title

- -` - - if respBody != expectedBody { - t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) - } - - // Test tmpl on /images/img2.htm - req, err = http.NewRequest("GET", "/images/img2.htm", nil) - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)) - - rec = httptest.NewRecorder() - - tmpl.ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) - } - - respBody = rec.Body.String() - expectedBody = `img{{.Include "header.html"}} -` - - if respBody != expectedBody { - t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) - } - - // Test tmplroot on /root.html - req, err = http.NewRequest("GET", "/root.html", nil) - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)) - - rec = httptest.NewRecorder() - // register custom function which is used in template httpserver.TemplateFuncs["root"] = func() string { return "root" } - tmplroot.ServeHTTP(rec, req) - if rec.Code != http.StatusOK { - t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) - } - - respBody = rec.Body.String() - expectedBody = `root

Header title

+ for _, c := range []struct { + tpl Templates + req string + respCode int + res string + }{ + { + tpl: tmpl, + req: "/photos/test.html", + respCode: http.StatusOK, + res: `test page

Header title

-` +`, + }, - if respBody != expectedBody { - t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) + { + tpl: tmpl, + req: "/images/img.htm", + respCode: http.StatusOK, + res: `img

Header title

+ +`, + }, + + { + tpl: tmpl, + req: "/images/img2.htm", + respCode: http.StatusOK, + res: `img{{.Include "header.html"}} +`, + }, + + { + tpl: tmplroot, + req: "/root.html", + respCode: http.StatusOK, + res: `root

Header title

+ +`, + }, + + // test extension filter + { + tpl: tmplroot, + req: "/as_it_is.txt", + respCode: http.StatusOK, + res: `as it is{{.Include "header.html"}} +`, + }, + } { + c := c + t.Run("", func(t *testing.T) { + req, err := http.NewRequest("GET", c.req, nil) + if err != nil { + t.Fatalf("Test: Could not create HTTP request: %v", err) + } + req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)) + + rec := httptest.NewRecorder() + + c.tpl.ServeHTTP(rec, req) + + if rec.Code != c.respCode { + t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, c.respCode) + } + + respBody := rec.Body.String() + if respBody != c.res { + t.Fatalf("Test: the expected body %v is different from the response one: %v", c.res, respBody) + } + }) } } diff --git a/caddyhttp/templates/testdata/as_it_is.txt b/caddyhttp/templates/testdata/as_it_is.txt new file mode 100644 index 000000000..487ee7273 --- /dev/null +++ b/caddyhttp/templates/testdata/as_it_is.txt @@ -0,0 +1 @@ +as it is{{.Include "header.html"}} From 1ba5512015ca498c2e99f4a6ed96bfb88d3945d2 Mon Sep 17 00:00:00 2001 From: Tw Date: Tue, 16 Jan 2018 09:32:19 +0800 Subject: [PATCH 06/19] ResponseBuffer: add missing header writing (#1997) Signed-off-by: Tw --- caddyhttp/httpserver/recorder.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/caddyhttp/httpserver/recorder.go b/caddyhttp/httpserver/recorder.go index b0f396218..f903f924d 100644 --- a/caddyhttp/httpserver/recorder.go +++ b/caddyhttp/httpserver/recorder.go @@ -115,6 +115,7 @@ type ResponseBuffer struct { shouldBuffer func(status int, header http.Header) bool stream bool rw http.ResponseWriter + wroteHeader bool } // NewResponseBuffer returns a new ResponseBuffer that will @@ -152,6 +153,11 @@ func (rb *ResponseBuffer) Header() http.Header { // upcoming body should be buffered, and then writes // the header to the response. func (rb *ResponseBuffer) WriteHeader(status int) { + if rb.wroteHeader { + return + } + rb.wroteHeader = true + rb.status = status rb.stream = !rb.shouldBuffer(status, rb.header) if rb.stream { @@ -163,6 +169,10 @@ func (rb *ResponseBuffer) WriteHeader(status int) { // Write writes buf to rb.Buffer if buffering, otherwise // to the ResponseWriter directly if streaming. func (rb *ResponseBuffer) Write(buf []byte) (int, error) { + if !rb.wroteHeader { + rb.WriteHeader(http.StatusOK) + } + if rb.stream { return rb.ResponseWriterWrapper.Write(buf) } @@ -190,6 +200,10 @@ func (rb *ResponseBuffer) CopyHeader() { // from ~8,200 to ~9,600 on templated files by ensuring that this type // implements io.ReaderFrom. func (rb *ResponseBuffer) ReadFrom(src io.Reader) (int64, error) { + if !rb.wroteHeader { + rb.WriteHeader(http.StatusOK) + } + if rb.stream { // first see if we can avoid any allocations at all if wt, ok := src.(io.WriterTo); ok { From c80c34ef458634e67130239b9416d7294a2d72d6 Mon Sep 17 00:00:00 2001 From: Heri Sim Date: Tue, 16 Jan 2018 12:00:59 +0800 Subject: [PATCH 07/19] proxy: Turn on KeepAlive in QuicConfig of RoundTripper (#1943) * Turn on KeepAlive in QuicConfig of RoundTripper * Update reverseproxy.go --- caddyhttp/proxy/reverseproxy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/caddyhttp/proxy/reverseproxy.go b/caddyhttp/proxy/reverseproxy.go index 2fac5aabc..d48894ff1 100644 --- a/caddyhttp/proxy/reverseproxy.go +++ b/caddyhttp/proxy/reverseproxy.go @@ -240,6 +240,7 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int) * rp.Transport = &h2quic.RoundTripper{ QuicConfig: &quic.Config{ HandshakeTimeout: defaultCryptoHandshakeTimeout, + KeepAlive: true, }, } } else if keepalive != http.DefaultMaxIdleConnsPerHost || strings.HasPrefix(target.Scheme, "srv") { From e9515425e0b13d9da15970e282db110bf7dadae9 Mon Sep 17 00:00:00 2001 From: Whitestrake Date: Wed, 17 Jan 2018 04:37:49 +1000 Subject: [PATCH 08/19] use import to handle globbed values for -conf flag (#1973) --- caddy/caddymain/run.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/caddy/caddymain/run.go b/caddy/caddymain/run.go index 82087d8ff..e6faa0513 100644 --- a/caddy/caddymain/run.go +++ b/caddy/caddymain/run.go @@ -170,10 +170,18 @@ func confLoader(serverType string) (caddy.Input, error) { return caddy.CaddyfileFromPipe(os.Stdin, serverType) } - contents, err := ioutil.ReadFile(conf) - if err != nil { - return nil, err + var contents []byte + if strings.Contains(conf, "*") { + // Let caddyfile.doImport logic handle the globbed path + contents = []byte("import " + conf) + } else { + var err error + contents, err = ioutil.ReadFile(conf) + if err != nil { + return nil, err + } } + return caddy.CaddyfileInput{ Contents: contents, Filepath: conf, From a76222f6071e3d32be38598d91f63b45694bc125 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 16 Jan 2018 22:55:33 +0000 Subject: [PATCH 09/19] sigtrap: allow graceful shutdown for SIGTERM on posix (#1995) * shutdown: allow graceful shutdown for SIGTERM on posix The signal is already trapped; make it do the same thing as SIGQUIT to be more inline with Unix/Linux shutdown expectations. Fixes #1993 * Implement comment feedback ideas --- sigtrap_posix.go | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/sigtrap_posix.go b/sigtrap_posix.go index 71b6969af..bed5b111d 100644 --- a/sigtrap_posix.go +++ b/sigtrap_posix.go @@ -25,25 +25,27 @@ import ( // trapSignalsPosix captures POSIX-only signals. func trapSignalsPosix() { + signalToString := map[os.Signal]string{syscall.SIGHUP: "SIGHUP", syscall.SIGUSR1: "SIGUSR1"} + go func() { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2) for sig := range sigchan { switch sig { - case syscall.SIGTERM: - log.Println("[INFO] SIGTERM: Terminating process") + case syscall.SIGQUIT: + log.Println("[INFO] SIGQUIT: Terminating process") if PidFile != "" { os.Remove(PidFile) } os.Exit(0) - case syscall.SIGQUIT: - log.Println("[INFO] SIGQUIT: Shutting down") - exitCode := executeShutdownCallbacks("SIGQUIT") + case syscall.SIGTERM: + log.Println("[INFO] SIGTERM: Shutting down") + exitCode := executeShutdownCallbacks(signalToString[sig]) err := Stop() if err != nil { - log.Printf("[ERROR] SIGQUIT stop: %v", err) + log.Printf("[ERROR] SIGTERM stop: %v", err) exitCode = 3 } if PidFile != "" { @@ -51,32 +53,25 @@ func trapSignalsPosix() { } os.Exit(exitCode) - case syscall.SIGHUP: - log.Println("[INFO] SIGHUP: Hanging up") - err := Stop() - if err != nil { - log.Printf("[ERROR] SIGHUP stop: %v", err) - } - - case syscall.SIGUSR1: - log.Println("[INFO] SIGUSR1: Reloading") + case syscall.SIGUSR1, syscall.SIGHUP: + log.Printf("[INFO] %s: Reloading", signalToString[sig]) // Start with the existing Caddyfile caddyfileToUse, inst, err := getCurrentCaddyfile() if err != nil { - log.Printf("[ERROR] SIGUSR1: %v", err) + log.Printf("[ERROR] %s: %v", signalToString[sig], err) continue } if loaderUsed.loader == nil { // This also should never happen - log.Println("[ERROR] SIGUSR1: no Caddyfile loader with which to reload Caddyfile") + log.Printf("[ERROR] %s: no Caddyfile loader with which to reload Caddyfile", signalToString[sig]) continue } // Load the updated Caddyfile newCaddyfile, err := loaderUsed.loader.Load(inst.serverType) if err != nil { - log.Printf("[ERROR] SIGUSR1: loading updated Caddyfile: %v", err) + log.Printf("[ERROR] %s: loading updated Caddyfile: %v", signalToString[sig], err) continue } if newCaddyfile != nil { @@ -86,7 +81,7 @@ func trapSignalsPosix() { // Kick off the restart; our work is done _, err = inst.Restart(caddyfileToUse) if err != nil { - log.Printf("[ERROR] SIGUSR1: %v", err) + log.Printf("[ERROR] %s: %v", signalToString[sig], err) } case syscall.SIGUSR2: From 106d62b0674b582c8fe26a5719378a4261849a94 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 26 Jan 2018 22:24:11 -0700 Subject: [PATCH 10/19] sigtrap: Fix log messages, and ignore SIGHUP (#1993) --- sigtrap_posix.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sigtrap_posix.go b/sigtrap_posix.go index bed5b111d..89e8c17a1 100644 --- a/sigtrap_posix.go +++ b/sigtrap_posix.go @@ -25,8 +25,6 @@ import ( // trapSignalsPosix captures POSIX-only signals. func trapSignalsPosix() { - signalToString := map[os.Signal]string{syscall.SIGHUP: "SIGHUP", syscall.SIGUSR1: "SIGUSR1"} - go func() { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2) @@ -34,15 +32,15 @@ func trapSignalsPosix() { for sig := range sigchan { switch sig { case syscall.SIGQUIT: - log.Println("[INFO] SIGQUIT: Terminating process") + log.Println("[INFO] SIGQUIT: Quitting process immediately") if PidFile != "" { os.Remove(PidFile) } os.Exit(0) case syscall.SIGTERM: - log.Println("[INFO] SIGTERM: Shutting down") - exitCode := executeShutdownCallbacks(signalToString[sig]) + log.Println("[INFO] SIGTERM: Shutting down servers then terminating") + exitCode := executeShutdownCallbacks("SIGTERM") err := Stop() if err != nil { log.Printf("[ERROR] SIGTERM stop: %v", err) @@ -53,25 +51,25 @@ func trapSignalsPosix() { } os.Exit(exitCode) - case syscall.SIGUSR1, syscall.SIGHUP: - log.Printf("[INFO] %s: Reloading", signalToString[sig]) + case syscall.SIGUSR1: + log.Println("[INFO] SIGUSR1: Reloading") // Start with the existing Caddyfile caddyfileToUse, inst, err := getCurrentCaddyfile() if err != nil { - log.Printf("[ERROR] %s: %v", signalToString[sig], err) + log.Printf("[ERROR] SIGUSR1: %v", err) continue } if loaderUsed.loader == nil { // This also should never happen - log.Printf("[ERROR] %s: no Caddyfile loader with which to reload Caddyfile", signalToString[sig]) + log.Println("[ERROR] SIGUSR1: no Caddyfile loader with which to reload Caddyfile") continue } // Load the updated Caddyfile newCaddyfile, err := loaderUsed.loader.Load(inst.serverType) if err != nil { - log.Printf("[ERROR] %s: loading updated Caddyfile: %v", signalToString[sig], err) + log.Printf("[ERROR] SIGUSR1: loading updated Caddyfile: %v", err) continue } if newCaddyfile != nil { @@ -81,7 +79,7 @@ func trapSignalsPosix() { // Kick off the restart; our work is done _, err = inst.Restart(caddyfileToUse) if err != nil { - log.Printf("[ERROR] %s: %v", signalToString[sig], err) + log.Printf("[ERROR] SIGUSR1: %v", err) } case syscall.SIGUSR2: @@ -89,6 +87,9 @@ func trapSignalsPosix() { if err := Upgrade(); err != nil { log.Printf("[ERROR] SIGUSR2: upgrading: %v", err) } + + case syscall.SIGHUP: + // ignore; this signal is sometimes sent outside of the user's control } } }() From 50ab4fe11e1a6a516a2afa060f64d67d5653c401 Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Tue, 30 Jan 2018 15:19:02 +0100 Subject: [PATCH 11/19] caddy.service: fix typo, s/retrict/restrict/ (#2008) --- dist/init/linux-systemd/caddy.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/init/linux-systemd/caddy.service b/dist/init/linux-systemd/caddy.service index e0ab5643e..649ec9556 100644 --- a/dist/init/linux-systemd/caddy.service +++ b/dist/init/linux-systemd/caddy.service @@ -41,7 +41,7 @@ ProtectSystem=full ReadWriteDirectories=/etc/ssl/caddy ; The following additional security directives only work with systemd v229 or later. -; They further retrict privileges that can be gained by caddy. Uncomment if you like. +; They further restrict privileges that can be gained by caddy. Uncomment if you like. ; Note that you may have to add capabilities required by any plugins in use. ;CapabilityBoundingSet=CAP_NET_BIND_SERVICE ;AmbientCapabilities=CAP_NET_BIND_SERVICE From e2997ac974d194c0fef32a2c98d07a84237876fd Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 2 Feb 2018 19:59:28 -0700 Subject: [PATCH 12/19] request_id: Allow reusing ID from header (closes #2012) --- caddyhttp/requestid/requestid.go | 24 +++++++++-- caddyhttp/requestid/requestid_test.go | 59 ++++++++++++++++++--------- caddyhttp/requestid/setup.go | 9 +++- caddyhttp/requestid/setup_test.go | 10 ++++- 4 files changed, 76 insertions(+), 26 deletions(-) diff --git a/caddyhttp/requestid/requestid.go b/caddyhttp/requestid/requestid.go index c3f69267f..b03c449f6 100644 --- a/caddyhttp/requestid/requestid.go +++ b/caddyhttp/requestid/requestid.go @@ -16,6 +16,7 @@ package requestid import ( "context" + "log" "net/http" "github.com/google/uuid" @@ -24,12 +25,29 @@ import ( // Handler is a middleware handler type Handler struct { - Next httpserver.Handler + Next httpserver.Handler + HeaderName string // (optional) header from which to read an existing ID } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - reqid := uuid.New().String() - c := context.WithValue(r.Context(), httpserver.RequestIDCtxKey, reqid) + var reqid uuid.UUID + + uuidFromHeader := r.Header.Get(h.HeaderName) + if h.HeaderName != "" && uuidFromHeader != "" { + // use the ID in the header field if it exists + var err error + reqid, err = uuid.Parse(uuidFromHeader) + if err != nil { + log.Printf("[NOTICE] Parsing request ID from %s header: %v", h.HeaderName, err) + reqid = uuid.New() + } + } else { + // otherwise, create a new one + reqid = uuid.New() + } + + // set the request ID on the context + c := context.WithValue(r.Context(), httpserver.RequestIDCtxKey, reqid.String()) r = r.WithContext(c) return h.Next.ServeHTTP(w, r) diff --git a/caddyhttp/requestid/requestid_test.go b/caddyhttp/requestid/requestid_test.go index 80968221f..e68c8d2c0 100644 --- a/caddyhttp/requestid/requestid_test.go +++ b/caddyhttp/requestid/requestid_test.go @@ -15,34 +15,53 @@ package requestid import ( - "context" "net/http" + "net/http/httptest" "testing" - "github.com/google/uuid" "github.com/mholt/caddy/caddyhttp/httpserver" ) -func TestRequestID(t *testing.T) { - request, err := http.NewRequest("GET", "http://localhost/", nil) +func TestRequestIDHandler(t *testing.T) { + handler := Handler{ + Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { + value, _ := r.Context().Value(httpserver.RequestIDCtxKey).(string) + if value == "" { + t.Error("Request ID should not be empty") + } + return 0, nil + }), + } + + req, err := http.NewRequest("GET", "http://localhost/", nil) if err != nil { t.Fatal("Could not create HTTP request:", err) } + rec := httptest.NewRecorder() - reqid := uuid.New().String() - - c := context.WithValue(request.Context(), httpserver.RequestIDCtxKey, reqid) - - request = request.WithContext(c) - - // See caddyhttp/replacer.go - value, _ := request.Context().Value(httpserver.RequestIDCtxKey).(string) - - if value == "" { - t.Fatal("Request ID should not be empty") - } - - if value != reqid { - t.Fatal("Request ID does not match") - } + handler.ServeHTTP(rec, req) +} + +func TestRequestIDFromHeader(t *testing.T) { + headerName := "X-Request-ID" + headerValue := "71a75329-d9f9-4d25-957e-e689a7b68d78" + handler := Handler{ + Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { + value, _ := r.Context().Value(httpserver.RequestIDCtxKey).(string) + if value != headerValue { + t.Errorf("Request ID should be '%s' but got '%s'", headerValue, value) + } + return 0, nil + }), + HeaderName: headerName, + } + + req, err := http.NewRequest("GET", "http://localhost/", nil) + if err != nil { + t.Fatal("Could not create HTTP request:", err) + } + req.Header.Set(headerName, headerValue) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) } diff --git a/caddyhttp/requestid/setup.go b/caddyhttp/requestid/setup.go index 4da5a3683..689f99e33 100644 --- a/caddyhttp/requestid/setup.go +++ b/caddyhttp/requestid/setup.go @@ -27,14 +27,19 @@ func init() { } func setup(c *caddy.Controller) error { + var headerName string + for c.Next() { if c.NextArg() { - return c.ArgErr() //no arg expected. + headerName = c.Val() + } + if c.NextArg() { + return c.ArgErr() } } httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Handler{Next: next} + return Handler{Next: next, HeaderName: headerName} }) return nil diff --git a/caddyhttp/requestid/setup_test.go b/caddyhttp/requestid/setup_test.go index aea123694..9c420787b 100644 --- a/caddyhttp/requestid/setup_test.go +++ b/caddyhttp/requestid/setup_test.go @@ -45,7 +45,15 @@ func TestSetup(t *testing.T) { } func TestSetupWithArg(t *testing.T) { - c := caddy.NewTestController("http", `requestid abc`) + c := caddy.NewTestController("http", `requestid X-Request-ID`) + err := setup(c) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } +} + +func TestSetupWithTooManyArgs(t *testing.T) { + c := caddy.NewTestController("http", `requestid foo bar`) err := setup(c) if err == nil { t.Errorf("Expected an error, got: %v", err) From fc6d62286ea3d6c3e40a5e1942c40eaa0ee64330 Mon Sep 17 00:00:00 2001 From: Tw Date: Sat, 3 Feb 2018 14:52:53 +0800 Subject: [PATCH 13/19] make eventHooks thread safe (Go 1.9) (#2009) Signed-off-by: Tw --- plugins.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/plugins.go b/plugins.go index f5372184e..d95177816 100644 --- a/plugins.go +++ b/plugins.go @@ -19,6 +19,7 @@ import ( "log" "net" "sort" + "sync" "github.com/mholt/caddy/caddyfile" ) @@ -38,7 +39,7 @@ var ( // eventHooks is a map of hook name to Hook. All hooks plugins // must have a name. - eventHooks = make(map[string]EventHook) + eventHooks = sync.Map{} // parsingCallbacks maps server type to map of directive // to list of callback functions. These aren't really @@ -67,12 +68,15 @@ func DescribePlugins() string { str += " " + defaultCaddyfileLoader.name + "\n" } - if len(eventHooks) > 0 { - // List the event hook plugins + // List the event hook plugins + hooks := "" + eventHooks.Range(func(k, _ interface{}) bool { + hooks += " hook." + k.(string) + "\n" + return true + }) + if hooks != "" { str += "\nEvent hook plugins:\n" - for hookPlugin := range eventHooks { - str += " hook." + hookPlugin + "\n" - } + str += hooks } // Let's alphabetize the rest of these... @@ -248,23 +252,23 @@ func RegisterEventHook(name string, hook EventHook) { if name == "" { panic("event hook must have a name") } - if _, dup := eventHooks[name]; dup { + _, dup := eventHooks.LoadOrStore(name, hook) + if dup { panic("hook named " + name + " already registered") } - eventHooks[name] = hook } // EmitEvent executes the different hooks passing the EventType as an // argument. This is a blocking function. Hook developers should // use 'go' keyword if they don't want to block Caddy. func EmitEvent(event EventName, info interface{}) { - for name, hook := range eventHooks { - err := hook(event, info) - + eventHooks.Range(func(k, v interface{}) bool { + err := v.(EventHook)(event, info) if err != nil { - log.Printf("error on '%s' hook: %v", name, err) + log.Printf("error on '%s' hook: %v", k.(string), err) } - } + return true + }) } // ParsingCallback is a function that is called after From e20779e40514b14c7e2471eaab22b8b9503fd4a5 Mon Sep 17 00:00:00 2001 From: Phillipp Engelke Date: Sat, 3 Feb 2018 07:53:40 +0100 Subject: [PATCH 14/19] Update README.md (#2004) Adding the bash command for downloading the caddy.service file from the reposetory. Because it was easy to forget where you find it. --- dist/init/linux-systemd/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/dist/init/linux-systemd/README.md b/dist/init/linux-systemd/README.md index aa3dd75a3..be548ae4d 100644 --- a/dist/init/linux-systemd/README.md +++ b/dist/init/linux-systemd/README.md @@ -91,6 +91,7 @@ Install the systemd service unit configuration file, reload the systemd daemon, and start caddy: ```bash +wget https://raw.githubusercontent.com/mholt/caddy/master/dist/init/linux-systemd/caddy.service sudo cp caddy.service /etc/systemd/system/ sudo chown root:root /etc/systemd/system/caddy.service sudo chmod 644 /etc/systemd/system/caddy.service From fd3fafa50caf0dcbe695d28b48198a1e2bf810bd Mon Sep 17 00:00:00 2001 From: magikstm Date: Sat, 3 Feb 2018 13:13:23 -0500 Subject: [PATCH 15/19] Disable PrivateDevices in systemd as it doesn't work for some devices (#1990) --- dist/init/linux-systemd/caddy.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/init/linux-systemd/caddy.service b/dist/init/linux-systemd/caddy.service index 649ec9556..61b70b1f3 100644 --- a/dist/init/linux-systemd/caddy.service +++ b/dist/init/linux-systemd/caddy.service @@ -30,8 +30,8 @@ LimitNPROC=512 ; Use private /tmp and /var/tmp, which are discarded after caddy stops. PrivateTmp=true -; Use a minimal /dev -PrivateDevices=true +; Use a minimal /dev (May bring additional security if switched to 'true', but it may not work on Raspberry Pi's or other devices, so it has been disabled in this dist.) +PrivateDevices=false ; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys. ProtectHome=true ; Make /usr, /boot, /etc and possibly some more folders read-only. From a50f3a4cfe0da94801f9c2561a812025806a22eb Mon Sep 17 00:00:00 2001 From: Toby Allen Date: Sat, 3 Feb 2018 21:48:02 +0000 Subject: [PATCH 16/19] gitignore: Ignore .bat files (#2013) --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4f3845ed4..425a29cf3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ Caddyfile og_static/ -.vscode/ \ No newline at end of file +.vscode/ + +*.bat \ No newline at end of file From 592d1993150f9cede58e4c5013bc79c0ba0cdbbe Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Sun, 11 Feb 2018 13:30:01 -0700 Subject: [PATCH 17/19] staticfiles: Prevent path-based open redirects Not a huge issue, but has security implications if OAuth tokens leaked --- caddyhttp/staticfiles/fileserver.go | 8 ++++++ caddyhttp/staticfiles/fileserver_test.go | 32 ++++++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/caddyhttp/staticfiles/fileserver.go b/caddyhttp/staticfiles/fileserver.go index 2b38212ea..91fb1a7f5 100644 --- a/caddyhttp/staticfiles/fileserver.go +++ b/caddyhttp/staticfiles/fileserver.go @@ -107,6 +107,10 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err if d.IsDir() { // ensure there is a trailing slash if urlCopy.Path[len(urlCopy.Path)-1] != '/' { + for strings.HasPrefix(urlCopy.Path, "//") { + // prevent path-based open redirects + urlCopy.Path = strings.TrimPrefix(urlCopy.Path, "/") + } urlCopy.Path += "/" http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently) return http.StatusMovedPermanently, nil @@ -131,6 +135,10 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err } if redir { + for strings.HasPrefix(urlCopy.Path, "//") { + // prevent path-based open redirects + urlCopy.Path = strings.TrimPrefix(urlCopy.Path, "/") + } http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently) return http.StatusMovedPermanently, nil } diff --git a/caddyhttp/staticfiles/fileserver_test.go b/caddyhttp/staticfiles/fileserver_test.go index 9cce77057..80d8f1a40 100644 --- a/caddyhttp/staticfiles/fileserver_test.go +++ b/caddyhttp/staticfiles/fileserver_test.go @@ -77,9 +77,9 @@ func TestServeHTTP(t *testing.T) { { url: "https://foo/dirwithindex/", expectedStatus: http.StatusOK, - expectedBodyContent: testFiles[webrootDirwithindexIndeHTML], + expectedBodyContent: testFiles[webrootDirwithindexIndexHTML], expectedEtag: `"2n9cw"`, - expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndeHTML])), + expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndexHTML])), }, // Test 4 - access folder with index file without trailing slash { @@ -235,16 +235,38 @@ func TestServeHTTP(t *testing.T) { expectedBodyContent: movedPermanently, }, { + // Test 27 - Check etag url: "https://foo/notindex.html", expectedStatus: http.StatusOK, expectedBodyContent: testFiles[webrootNotIndexHTML], expectedEtag: `"2n9cm"`, expectedContentLength: strconv.Itoa(len(testFiles[webrootNotIndexHTML])), }, + { + // Test 28 - Prevent path-based open redirects (directory) + url: "https://foo//example.com%2f..", + expectedStatus: http.StatusMovedPermanently, + expectedLocation: "https://foo/example.com/../", + expectedBodyContent: movedPermanently, + }, + { + // Test 29 - Prevent path-based open redirects (file) + url: "https://foo//example.com%2f../dirwithindex/index.html", + expectedStatus: http.StatusMovedPermanently, + expectedLocation: "https://foo/example.com/../dirwithindex/", + expectedBodyContent: movedPermanently, + }, + { + // Test 29 - Prevent path-based open redirects (extra leading slashes) + url: "https://foo///example.com%2f..", + expectedStatus: http.StatusMovedPermanently, + expectedLocation: "https://foo/example.com/../", + expectedBodyContent: movedPermanently, + }, } for i, test := range tests { - // set up response writer and rewuest + // set up response writer and request responseRecorder := httptest.NewRecorder() request, err := http.NewRequest("GET", test.url, nil) if err != nil { @@ -518,7 +540,7 @@ var ( webrootNotIndexHTML = filepath.Join(webrootName, "notindex.html") webrootDirFile2HTML = filepath.Join(webrootName, "dir", "file2.html") webrootDirHiddenHTML = filepath.Join(webrootName, "dir", "hidden.html") - webrootDirwithindexIndeHTML = filepath.Join(webrootName, "dirwithindex", "index.html") + webrootDirwithindexIndexHTML = filepath.Join(webrootName, "dirwithindex", "index.html") webrootSubGzippedHTML = filepath.Join(webrootName, "sub", "gzipped.html") webrootSubGzippedHTMLGz = filepath.Join(webrootName, "sub", "gzipped.html.gz") webrootSubGzippedHTMLBr = filepath.Join(webrootName, "sub", "gzipped.html.br") @@ -544,7 +566,7 @@ var testFiles = map[string]string{ webrootFile1HTML: "

file1.html

", webrootNotIndexHTML: "

notindex.html

", webrootDirFile2HTML: "

dir/file2.html

", - webrootDirwithindexIndeHTML: "

dirwithindex/index.html

", + webrootDirwithindexIndexHTML: "

dirwithindex/index.html

", webrootDirHiddenHTML: "

dir/hidden.html

", webrootSubGzippedHTML: "

gzipped.html

", webrootSubGzippedHTMLGz: "1.gzipped.html.gz", From 6a9aea04b1a939c8a3d7aa4774839ce4426562db Mon Sep 17 00:00:00 2001 From: Etienne Bruines Date: Sun, 11 Feb 2018 22:45:45 +0100 Subject: [PATCH 18/19] fastcig: GET requests send along the body (#1975) Fixes #1961 According to RFC 7231 and RFC 7230, there's no reason a GET-Request can't have a body (other than it possibly not being supported by existing software). It's use is simply not defined, and is left to the application. --- caddyhttp/fastcgi/fastcgi.go | 2 +- caddyhttp/fastcgi/fcgiclient.go | 6 +++--- caddyhttp/fastcgi/fcgiclient_test.go | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/caddyhttp/fastcgi/fastcgi.go b/caddyhttp/fastcgi/fastcgi.go index ee466a3e8..28ea55f9f 100644 --- a/caddyhttp/fastcgi/fastcgi.go +++ b/caddyhttp/fastcgi/fastcgi.go @@ -148,7 +148,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) case "HEAD": resp, err = fcgiBackend.Head(env) case "GET": - resp, err = fcgiBackend.Get(env) + resp, err = fcgiBackend.Get(env, r.Body, contentLength) case "OPTIONS": resp, err = fcgiBackend.Options(env) default: diff --git a/caddyhttp/fastcgi/fcgiclient.go b/caddyhttp/fastcgi/fcgiclient.go index adf37d09a..b5fd1d9ea 100644 --- a/caddyhttp/fastcgi/fcgiclient.go +++ b/caddyhttp/fastcgi/fcgiclient.go @@ -460,12 +460,12 @@ func (c *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Res } // Get issues a GET request to the fcgi responder. -func (c *FCGIClient) Get(p map[string]string) (resp *http.Response, err error) { +func (c *FCGIClient) Get(p map[string]string, body io.Reader, l int64) (resp *http.Response, err error) { p["REQUEST_METHOD"] = "GET" - p["CONTENT_LENGTH"] = "0" + p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10) - return c.Request(p, nil) + return c.Request(p, body) } // Head issues a HEAD request to the fcgi responder. diff --git a/caddyhttp/fastcgi/fcgiclient_test.go b/caddyhttp/fastcgi/fcgiclient_test.go index ef4981d48..9c5237f20 100644 --- a/caddyhttp/fastcgi/fcgiclient_test.go +++ b/caddyhttp/fastcgi/fcgiclient_test.go @@ -140,7 +140,8 @@ func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[ } resp, err = fcgi.PostForm(fcgiParams, values) } else { - resp, err = fcgi.Get(fcgiParams) + rd := bytes.NewReader(data) + resp, err = fcgi.Get(fcgiParams, rd, int64(rd.Len())) } default: From d29640699eca5e3f64ba15f0dd5a8d452c495058 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 13 Feb 2018 09:30:26 -0700 Subject: [PATCH 19/19] readme: Update logo image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d42d2e6c..bdbffd006 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Caddy + Caddy

Every Site on HTTPS

Caddy is a general-purpose HTTP/2 web server that serves HTTPS by default.