From c3a22933b7ca8201c96267d360281848fb2a1c7e Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 27 Jan 2023 01:07:33 +0100 Subject: [PATCH] [PRIVACY] Add a DNS method to fetch new updates - Use TXT records in order to determine the latest available version. - This addresses a valid privacy issue, as with HTTP requests the server can keep track(estimated) of how many instances are using Forgejo, with DNS that's basically not possible as the server will never receive any data, as the only ones receiving data are DNS resolvers. (cherry picked from commit 0baefb546ab96bc3c06d90feffdb14873c2c2a3a) (cherry picked from commit e8ee41880b775532e6a68bd2052ed96d369dee78) (cherry picked from commit 7eca4f3bf1faa3f063c9668d1bb354b842361007) (cherry picked from commit 6dde3992dc504f105f2285c34fa6445fa24da0b1) (cherry picked from commit fb3a37fbfc73d35e3ba8d793b6051aeddcbb0634) (cherry picked from commit 8304af1e9d94b89ee700b0d454d74d0dc5cfac64) (cherry picked from commit 0543a7d12a4bac012db8b9e683e879e6f265a9dc) --- custom/conf/app.example.ini | 1 + .../config-cheat-sheet.en-us.md | 1 + modules/updatechecker/update_checker.go | 60 ++++++++++++++++--- modules/updatechecker/update_checker_test.go | 16 +++++ services/cron/tasks_extended.go | 8 ++- 5 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 modules/updatechecker/update_checker_test.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 7f1e20afe4..8cb34b5f52 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2225,6 +2225,7 @@ ROUTER = console ;ENABLE_SUCCESS_NOTICE = false ;SCHEDULE = @every 168h ;HTTP_ENDPOINT = https://dl.gitea.io/gitea/version.json +;DOMAIN_ENDPOINT = release.forgejo.org ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/doc/administration/config-cheat-sheet.en-us.md b/docs/content/doc/administration/config-cheat-sheet.en-us.md index 67618d05b6..9f79653397 100644 --- a/docs/content/doc/administration/config-cheat-sheet.en-us.md +++ b/docs/content/doc/administration/config-cheat-sheet.en-us.md @@ -1056,6 +1056,7 @@ Default templates for project boards: - `ENABLE_SUCCESS_NOTICE`: **true**: Set to false to switch off success notices. - `SCHEDULE`: **@every 168h**: Cron syntax for scheduling a work, e.g. `@every 168h`. - `HTTP_ENDPOINT`: **https://dl.gitea.io/gitea/version.json**: the endpoint that Gitea will check for newer versions +- `DOMAIN_ENDPOINT`: **release.forgejo.org**: the domain that, if specified, Gitea will check for newer versions. This is preferred over `HTTP_ENDPOINT`. #### Cron - Delete all old system notices from database (`cron.delete_old_system_notices`) diff --git a/modules/updatechecker/update_checker.go b/modules/updatechecker/update_checker.go index bc3f93aad7..d3008ab122 100644 --- a/modules/updatechecker/update_checker.go +++ b/modules/updatechecker/update_checker.go @@ -4,8 +4,11 @@ package updatechecker import ( + "errors" "io" + "net" "net/http" + "strings" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/proxy" @@ -26,7 +29,51 @@ func (r *CheckerState) Name() string { } // GiteaUpdateChecker returns error when new version of Gitea is available -func GiteaUpdateChecker(httpEndpoint string) error { +func GiteaUpdateChecker(httpEndpoint, domainEndpoint string) error { + var version string + var err error + if domainEndpoint != "" { + version, err = getVersionDNS(domainEndpoint) + } else { + version, err = getVersionHTTP(httpEndpoint) + } + + if err != nil { + return err + } + + return UpdateRemoteVersion(version) +} + +// getVersionDNS will request the TXT records for the domain. If a record starts +// with "forgejo_versions=" everything after that will be used as the latest +// version available. +func getVersionDNS(domainEndpoint string) (version string, err error) { + records, err := net.LookupTXT(domainEndpoint) + if err != nil { + return "", err + } + + if len(records) == 0 { + return "", errors.New("no TXT records were found") + } + + for _, record := range records { + if strings.HasPrefix(record, "forgejo_versions=") { + // Get all supported versions, separated by a comma. + supportedVersions := strings.Split(strings.TrimPrefix(record, "forgejo_versions="), ",") + // For now always return the latest supported version. + return supportedVersions[len(supportedVersions)-1], nil + } + } + + return "", errors.New("there is no TXT record with a valid value") +} + +// getVersionHTTP will make an HTTP request to the endpoint, and the returned +// content is JSON. The "latest.version" path's value will be used as the latest +// version available. +func getVersionHTTP(httpEndpoint string) (version string, err error) { httpClient := &http.Client{ Transport: &http.Transport{ Proxy: proxy.Proxy(), @@ -35,16 +82,16 @@ func GiteaUpdateChecker(httpEndpoint string) error { req, err := http.NewRequest("GET", httpEndpoint, nil) if err != nil { - return err + return "", err } resp, err := httpClient.Do(req) if err != nil { - return err + return "", err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return err + return "", err } type respType struct { @@ -55,10 +102,9 @@ func GiteaUpdateChecker(httpEndpoint string) error { respData := respType{} err = json.Unmarshal(body, &respData) if err != nil { - return err + return "", err } - - return UpdateRemoteVersion(respData.Latest.Version) + return respData.Latest.Version, nil } // UpdateRemoteVersion updates the latest available version of Gitea diff --git a/modules/updatechecker/update_checker_test.go b/modules/updatechecker/update_checker_test.go new file mode 100644 index 0000000000..301afd95e4 --- /dev/null +++ b/modules/updatechecker/update_checker_test.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package updatechecker + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDNSUpdate(t *testing.T) { + version, err := getVersionDNS("release.forgejo.org") + assert.NoError(t, err) + assert.NotEmpty(t, version) +} diff --git a/services/cron/tasks_extended.go b/services/cron/tasks_extended.go index af419144a9..39568ff7ed 100644 --- a/services/cron/tasks_extended.go +++ b/services/cron/tasks_extended.go @@ -142,7 +142,8 @@ func registerDeleteOldActions() { func registerUpdateGiteaChecker() { type UpdateCheckerConfig struct { BaseConfig - HTTPEndpoint string + HTTPEndpoint string + DomainEndpoint string } RegisterTaskFatal("update_checker", &UpdateCheckerConfig{ BaseConfig: BaseConfig{ @@ -150,10 +151,11 @@ func registerUpdateGiteaChecker() { RunAtStart: false, Schedule: "@every 168h", }, - HTTPEndpoint: "https://dl.gitea.io/gitea/version.json", + HTTPEndpoint: "https://dl.gitea.io/gitea/version.json", + DomainEndpoint: "release.forgejo.org", }, func(ctx context.Context, _ *user_model.User, config Config) error { updateCheckerConfig := config.(*UpdateCheckerConfig) - return updatechecker.GiteaUpdateChecker(updateCheckerConfig.HTTPEndpoint) + return updatechecker.GiteaUpdateChecker(updateCheckerConfig.HTTPEndpoint, updateCheckerConfig.DomainEndpoint) }) }