[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 0baefb546a)
(cherry picked from commit e8ee41880b)
(cherry picked from commit 7eca4f3bf1)
(cherry picked from commit 6dde3992dc)
(cherry picked from commit fb3a37fbfc)
(cherry picked from commit 8304af1e9d)
(cherry picked from commit 0543a7d12a)
(cherry picked from commit c3a22933b7)
(cherry picked from commit e243707694)
(cherry picked from commit 7eb6d1bcf7)
(cherry picked from commit 1d7b9535cd)
(cherry picked from commit 05920dce67)
(cherry picked from commit f173f27d7c)
(cherry picked from commit 90e1c9340e)
(cherry picked from commit de68610ea7)
(cherry picked from commit 8d5757ea04)
(cherry picked from commit c7a7fff316)
(cherry picked from commit 39ac8b8fc1)
(cherry picked from commit 9889203301)

[PRIVACY]: Adjust update checker description

- Resolves #323
- Adjust the description of the update check function on the
installation page to describe the privacy method instead of the HTTP
method by checking gitea.io

(cherry picked from commit 61eae5b105)
(cherry picked from commit 091def20a1)
(cherry picked from commit d5d11bf45a)
(cherry picked from commit 71863d4707)
(cherry picked from commit 11ece4aab1)
(cherry picked from commit afdd7e714f)
(cherry picked from commit 39170e2f1d)
(cherry picked from commit 4b3a52aab8)
(cherry picked from commit 9d763c5fc8)
(cherry picked from commit 638db15482)
(cherry picked from commit a52bfdd8e7)
(cherry picked from commit dc93d00e85)
(cherry picked from commit 0bc4b3508c)
(cherry picked from commit 3f760d85a4)
(cherry picked from commit ecc2716785)
(cherry picked from commit 6334d5677e)

Conflicts:
	modules/updatechecker/update_checker.go
	UpdateRemoteVersion now has a context argument. However, in
	the updated code from Gitea the context comes from the HTTP
	request and does not actually provide any useful context.
	Replace that with context.Background()
(cherry picked from commit ca2200767e)
(cherry picked from commit f46feca224)
(cherry picked from commit a800a66ca8)
(cherry picked from commit 21f017454e)
(cherry picked from commit 762d18e09a)
(cherry picked from commit 6d28e120c1)
(cherry picked from commit 4fcd7e7cbf)
(cherry picked from commit 9516285112)
(cherry picked from commit 0dd752a262)
(cherry picked from commit b1f1af7fe0)
This commit is contained in:
Gusted 2023-01-27 01:07:33 +01:00 committed by Earl Warren
parent 6f9f415644
commit 71937deaa5
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
6 changed files with 77 additions and 11 deletions

View file

@ -2189,6 +2189,7 @@ LEVEL = Info
;ENABLE_SUCCESS_NOTICE = false ;ENABLE_SUCCESS_NOTICE = false
;SCHEDULE = @every 168h ;SCHEDULE = @every 168h
;HTTP_ENDPOINT = https://dl.gitea.com/gitea/version.json ;HTTP_ENDPOINT = https://dl.gitea.com/gitea/version.json
;DOMAIN_ENDPOINT = release.forgejo.org
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -5,8 +5,11 @@ package updatechecker
import ( import (
"context" "context"
"errors"
"io" "io"
"net"
"net/http" "net/http"
"strings"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/proxy"
@ -27,7 +30,51 @@ func (r *CheckerState) Name() string {
} }
// GiteaUpdateChecker returns error when new version of Gitea is available // 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(context.Background(), 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{ httpClient := &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
Proxy: proxy.Proxy(), Proxy: proxy.Proxy(),
@ -36,16 +83,16 @@ func GiteaUpdateChecker(httpEndpoint string) error {
req, err := http.NewRequest("GET", httpEndpoint, nil) req, err := http.NewRequest("GET", httpEndpoint, nil)
if err != nil { if err != nil {
return err return "", err
} }
resp, err := httpClient.Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
return err return "", err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return "", err
} }
type respType struct { type respType struct {
@ -56,10 +103,9 @@ func GiteaUpdateChecker(httpEndpoint string) error {
respData := respType{} respData := respType{}
err = json.Unmarshal(body, &respData) err = json.Unmarshal(body, &respData)
if err != nil { if err != nil {
return err return "", err
} }
return respData.Latest.Version, nil
return UpdateRemoteVersion(req.Context(), respData.Latest.Version)
} }
// UpdateRemoteVersion updates the latest available version of Gitea // UpdateRemoteVersion updates the latest available version of Gitea

View file

@ -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)
}

View file

@ -286,6 +286,7 @@ run_user_not_match = The 'run as' username is not the current username: %s -> %s
internal_token_failed = Failed to generate internal token: %v internal_token_failed = Failed to generate internal token: %v
secret_key_failed = Failed to generate secret key: %v secret_key_failed = Failed to generate secret key: %v
save_config_failed = Failed to save configuration: %v save_config_failed = Failed to save configuration: %v
enable_update_checker_helper_forgejo = Periodically checks for new Forgejo versions by checking a DNS TXT record at release.forgejo.org.
invalid_admin_setting = Administrator account setting is invalid: %v invalid_admin_setting = Administrator account setting is invalid: %v
invalid_log_root_path = The log path is invalid: %v invalid_log_root_path = The log path is invalid: %v
default_keep_email_private = Hide Email Addresses by Default default_keep_email_private = Hide Email Addresses by Default

View file

@ -142,7 +142,8 @@ func registerDeleteOldActions() {
func registerUpdateGiteaChecker() { func registerUpdateGiteaChecker() {
type UpdateCheckerConfig struct { type UpdateCheckerConfig struct {
BaseConfig BaseConfig
HTTPEndpoint string HTTPEndpoint string
DomainEndpoint string
} }
RegisterTaskFatal("update_checker", &UpdateCheckerConfig{ RegisterTaskFatal("update_checker", &UpdateCheckerConfig{
BaseConfig: BaseConfig{ BaseConfig: BaseConfig{
@ -150,10 +151,11 @@ func registerUpdateGiteaChecker() {
RunAtStart: false, RunAtStart: false,
Schedule: "@every 168h", Schedule: "@every 168h",
}, },
HTTPEndpoint: "https://dl.gitea.com/gitea/version.json", HTTPEndpoint: "https://dl.gitea.com/gitea/version.json",
DomainEndpoint: "release.forgejo.org",
}, func(ctx context.Context, _ *user_model.User, config Config) error { }, func(ctx context.Context, _ *user_model.User, config Config) error {
updateCheckerConfig := config.(*UpdateCheckerConfig) updateCheckerConfig := config.(*UpdateCheckerConfig)
return updatechecker.GiteaUpdateChecker(updateCheckerConfig.HTTPEndpoint) return updatechecker.GiteaUpdateChecker(updateCheckerConfig.HTTPEndpoint, updateCheckerConfig.DomainEndpoint)
}) })
} }

View file

@ -152,7 +152,7 @@
<label>{{ctx.Locale.Tr "install.enable_update_checker"}}</label> <label>{{ctx.Locale.Tr "install.enable_update_checker"}}</label>
<input name="enable_update_checker" type="checkbox"> <input name="enable_update_checker" type="checkbox">
</div> </div>
<span class="help">{{ctx.Locale.Tr "install.enable_update_checker_helper"}}</span> <span class="help">{{ctx.Locale.Tr "install.enable_update_checker_helper_forgejo"}}</span>
</div> </div>
<!-- Optional Settings --> <!-- Optional Settings -->