From 18d15be4e8a8065ca391fbf1f2fb98cd607993b8 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 30 Aug 2023 18:57:36 +0300 Subject: [PATCH] Pull request 1985: 2998-hsts Updates #2998. Updates #4941. Squashed commit of the following: commit ef6ed6acb89b10c4bf1b0c7ba34002f9d7f2e68c Author: Ainar Garipov Date: Wed Aug 30 18:43:25 2023 +0300 all: imp chlog commit 0957a85d53edcd5eba591e42301191db42a258ad Author: Ainar Garipov Date: Wed Aug 30 18:31:46 2023 +0300 home: add hsts when force_https is true --- CHANGELOG.md | 12 +++++++-- internal/aghhttp/header.go | 5 ++-- internal/home/control.go | 52 +++++++++++++++++++++++--------------- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0974e46..3bc29610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,9 +25,13 @@ NOTE: Add new changes BELOW THIS COMMENT. ### Added +- [`Strict-Transport-Security`][hsts] header in the HTTP API and DNS-over-HTTPS + responses when HTTPS is forced ([#2998]). See [RFC 6979][rfc6797]. +- UI for the schedule of the service-blocking pause ([#951]). - IPv6 hints are now filtered in case IPv6 addresses resolving is disabled ([#6122]). -- The ability to set fallback DNS servers in the configuration file ([#3701]). +- The ability to set fallback DNS servers in the configuration file and the UI + ([#3701]). - While adding or updating blocklists, the title can now be parsed from `! Title:` definition of the blocklist's source ([#6020]). - The ability to filter DNS HTTPS records including IPv4/v6 hints ([#6053]). @@ -149,6 +153,7 @@ In this release, the schema version has changed from 24 to 26. ([#5948]). [#1453]: https://github.com/AdguardTeam/AdGuardHome/issues/1453 +[#2998]: https://github.com/AdguardTeam/AdGuardHome/issues/2998 [#3701]: https://github.com/AdguardTeam/AdGuardHome/issues/3701 [#5948]: https://github.com/AdguardTeam/AdGuardHome/issues/5948 [#6020]: https://github.com/AdguardTeam/AdGuardHome/issues/6020 @@ -159,6 +164,9 @@ In this release, the schema version has changed from 24 to 26. [#6122]: https://github.com/AdguardTeam/AdGuardHome/issues/6122 [#6133]: https://github.com/AdguardTeam/AdGuardHome/issues/6133 +[hsts]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security +[rfc6797]: https://datatracker.ietf.org/doc/html/rfc6797 + @@ -675,7 +683,7 @@ In this release, the schema version has changed from 17 to 20. [#5701]: https://github.com/AdguardTeam/AdGuardHome/issues/5701 [ms-v0.107.28]: https://github.com/AdguardTeam/AdGuardHome/milestone/64?closed=1 -[rfc6761]: https://www.rfc-editor.org/rfc/rfc6761 +[rfc6761]: https://datatracker.ietf.org/doc/html/rfc6761 diff --git a/internal/aghhttp/header.go b/internal/aghhttp/header.go index eff50473..a1ece086 100644 --- a/internal/aghhttp/header.go +++ b/internal/aghhttp/header.go @@ -4,6 +4,7 @@ package aghhttp // HTTP header value constants. const ( - HdrValApplicationJSON = "application/json" - HdrValTextPlain = "text/plain" + HdrValApplicationJSON = "application/json" + HdrValStrictTransportSecurity = "max-age=31536000; includeSubDomains" + HdrValTextPlain = "text/plain" ) diff --git a/internal/home/control.go b/internal/home/control.go index 51d6efe4..4a675e93 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -321,9 +321,10 @@ func preInstallHandler(handler http.Handler) http.Handler { return &preInstallHandlerStruct{handler} } -// handleHTTPSRedirect redirects the request to HTTPS, if needed. If ok is -// true, the middleware must continue handling the request. -func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) { +// handleHTTPSRedirect redirects the request to HTTPS, if needed, and adds some +// HTTPS-related headers. If proceed is true, the middleware must continue +// handling the request. +func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (proceed bool) { web := Context.web if web.httpsServer.server == nil { return true @@ -362,21 +363,17 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) { respHdr.Set(httphdr.AltSvc, altSvc) } - if r.TLS == nil && forceHTTPS { - hostPort := host - if portHTTPS != defaultPortHTTPS { - hostPort = netutil.JoinHostPort(host, portHTTPS) + if forceHTTPS { + if r.TLS == nil { + u := httpsURL(r.URL, host, portHTTPS) + http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect) + + return false } - httpsURL := &url.URL{ - Scheme: aghhttp.SchemeHTTPS, - Host: hostPort, - Path: r.URL.Path, - RawQuery: r.URL.RawQuery, - } - http.Redirect(w, r, httpsURL.String(), http.StatusTemporaryRedirect) - - return false + // TODO(a.garipov): Consider adding a configurable max-age. Currently, + // the default is 365 days. + respHdr.Set(httphdr.StrictTransportSecurity, aghhttp.HdrValStrictTransportSecurity) } // Allow the frontend from the HTTP origin to send requests to the HTTPS @@ -395,6 +392,22 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) { return true } +// httpsURL returns a copy of u for redirection to the HTTPS version, taking the +// hostname and the HTTPS port into account. +func httpsURL(u *url.URL, host string, portHTTPS int) (redirectURL *url.URL) { + hostPort := host + if portHTTPS != defaultPortHTTPS { + hostPort = netutil.JoinHostPort(host, portHTTPS) + } + + return &url.URL{ + Scheme: aghhttp.SchemeHTTPS, + Host: hostPort, + Path: u.Path, + RawQuery: u.RawQuery, + } +} + // postInstall lets the handler to run only if firstRun is false. Otherwise, it // redirects to /install.html. It also enforces HTTPS if it is enabled and // configured and sets appropriate access control headers. @@ -408,11 +421,10 @@ func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.Res return } - if !handleHTTPSRedirect(w, r) { - return + proceed := handleHTTPSRedirect(w, r) + if proceed { + handler(w, r) } - - handler(w, r) } }