From 2ae1e055ebc087fefa64982c59f1afd9d0f7a592 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Sun, 17 May 2026 01:59:44 +0300 Subject: [PATCH] auto ssghost key verification conf --- custom/conf/app.example.ini | 6 ++++++ modules/git/gitcmd/command.go | 23 +++++++++++++++++++++++ modules/setting/migrations.go | 10 ++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 2498050f5d..37740fb4cb 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2717,6 +2717,12 @@ LEVEL = Info ;; Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291 (false by default) ;; If a domain is allowed by ALLOWED_DOMAINS, this option will be ignored. ;ALLOW_LOCALNETWORKS = false +;; +;; SSH host key verification for SSH migrations and mirrors. One of: +;; "accept-new" (default) - trust a host on first use, reject if its key later changes +;; "yes" - strict: the host key must already be known +;; "no" - disable host key verification (insecure, MITM risk) +;SSH_HOST_KEY_CHECKING = accept-new ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index ca74c3380a..9e402a3cd7 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/gtprof" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) @@ -56,6 +57,27 @@ type Command struct { cmdManagedStderr *bytes.Buffer } +// managedSSHCommand builds the GIT_SSH_COMMAND used for Gitea-managed SSH +// operations (migration / mirror with a generated keypair). ssh runs +// non-interactively (BatchMode) so the worker never hangs on an unknown host, +// and the configured host-key policy is applied. +func managedSSHCommand() string { + mode := setting.Migrations.SSHHostKeyChecking + if mode == "no" { + return "ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=" + util.ShellEscape(os.DevNull) + } + cmd := "ssh -o BatchMode=yes -o StrictHostKeyChecking=" + mode + // Persist accepted host keys in a Gitea-managed file so a later key change + // is detected (TOFU); fall back to ssh's default known_hosts if unset. + if setting.AppDataPath != "" { + knownHosts := filepath.Join(setting.AppDataPath, "home", ".ssh", "known_hosts") + if err := os.MkdirAll(filepath.Dir(knownHosts), 0o700); err == nil { + cmd += " -o UserKnownHostsFile=" + util.ShellEscape(knownHosts) + } + } + return cmd +} + func logArgSanitize(arg string) string { if filepath.IsAbs(arg) { base := filepath.Base(arg) @@ -453,6 +475,7 @@ func (c *Command) Start(ctx context.Context) (retErr error) { if c.opts.SSHAuthSock != "" { c.cmd.Env = append(c.cmd.Env, "SSH_AUTH_SOCK="+c.opts.SSHAuthSock) + c.cmd.Env = append(c.cmd.Env, "GIT_SSH_COMMAND="+managedSSHCommand()) } c.cmd.Dir = c.opts.Dir diff --git a/modules/setting/migrations.go b/modules/setting/migrations.go index 5a6079b6e2..6317a90b5f 100644 --- a/modules/setting/migrations.go +++ b/modules/setting/migrations.go @@ -11,9 +11,14 @@ var Migrations = struct { BlockedDomains string AllowLocalNetworks bool SkipTLSVerify bool + // SSHHostKeyChecking controls StrictHostKeyChecking for SSH migrations/mirrors: + // "accept-new" (default, trust on first use, reject changed keys), "yes" (strict, + // host must already be known) or "no" (disable verification). + SSHHostKeyChecking string }{ - MaxAttempts: 3, - RetryBackoff: 3, + MaxAttempts: 3, + RetryBackoff: 3, + SSHHostKeyChecking: "accept-new", } func loadMigrationsFrom(rootCfg ConfigProvider) { @@ -25,4 +30,5 @@ func loadMigrationsFrom(rootCfg ConfigProvider) { Migrations.BlockedDomains = sec.Key("BLOCKED_DOMAINS").MustString("") Migrations.AllowLocalNetworks = sec.Key("ALLOW_LOCALNETWORKS").MustBool(false) Migrations.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool(false) + Migrations.SSHHostKeyChecking = sec.Key("SSH_HOST_KEY_CHECKING").In("accept-new", []string{"accept-new", "yes", "no"}) }