diff --git a/modules/ssh/mirror.go b/modules/ssh/mirror.go index cff8cf49c6..ff24972be2 100644 --- a/modules/ssh/mirror.go +++ b/modules/ssh/mirror.go @@ -72,3 +72,35 @@ func GetSSHKeypairForURL(ctx context.Context, repo *repo_model.Repository, url s } return GetSSHKeypairForRepository(ctx, repo) } + +// SetupMirrorSSHAgent prepares SSH key-based authentication for a mirror or +// migration git operation against remoteURL on behalf of repo. For non-SSH +// URLs (or when no keypair is available) it is a no-op. The returned cleanup +// is never nil and must always be called by the caller (typically via defer). +func SetupMirrorSSHAgent(ctx context.Context, repo *repo_model.Repository, remoteURL string) (sshAuthSock string, cleanup func(), err error) { + noop := func() {} + if !IsSSHURL(remoteURL) { + return "", noop, nil + } + + keypair, err := GetSSHKeypairForRepository(ctx, repo) + if err != nil { + return "", noop, fmt.Errorf("failed to get SSH keypair for repository: %w", err) + } + if keypair == nil { + return "", noop, nil + } + + privateKey, err := keypair.GetDecryptedPrivateKey() + if err != nil { + return "", noop, fmt.Errorf("failed to decrypt SSH private key: %w", err) + } + + socketPath, agentCleanup, err := CreateTemporaryAgent(privateKey) + if err != nil { + return "", noop, fmt.Errorf("failed to create SSH agent: %w", err) + } + + log.Debug("SSH agent ready for mirror %s (socket: %s)", repo.FullName(), socketPath) + return socketPath, agentCleanup, nil +} diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index aa70013e16..8a29d53901 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -23,7 +23,6 @@ import ( "code.gitea.io/gitea/modules/proxy" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" - ssh_module "code.gitea.io/gitea/modules/ssh" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/migrations" @@ -107,36 +106,6 @@ func checkRecoverableSyncError(stderrMessage string) bool { } // runSync returns true if sync finished without error. -// setupSSHAuth sets up SSH authentication for git operations if needed. -// It returns the SSH agent socket path, a cleanup function, and any error. -// If the URL is not SSH or no keypair is found, it returns empty string with a no-op cleanup. -func setupSSHAuth(ctx context.Context, repo *repo_model.Repository, remoteURL string) (string, func(), error) { - if !IsSSHURL(remoteURL) { - return "", func() {}, nil - } - - keypair, err := GetSSHKeypairForURL(ctx, repo, remoteURL) - if err != nil { - return "", nil, fmt.Errorf("failed to get SSH keypair: %w", err) - } - if keypair == nil { - return "", func() {}, nil - } - - privateKey, err := keypair.GetDecryptedPrivateKey() - if err != nil { - return "", nil, fmt.Errorf("failed to decrypt private key: %w", err) - } - - socketPath, cleanup, err := ssh_module.CreateTemporaryAgent(privateKey) - if err != nil { - return "", nil, fmt.Errorf("failed to create SSH agent: %w", err) - } - - log.Debug("SSH agent created for repository %s with socket: %s", repo.FullName(), socketPath) - return socketPath, cleanup, nil -} - func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResult, bool) { log.Trace("SyncMirrors [repo: %-v]: running git remote update...", m.Repo) @@ -149,7 +118,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second // Setup SSH authentication if needed - sshAuthSock, cleanup, sshErr := setupSSHAuth(ctx, m.Repo, remoteURL.String()) + sshAuthSock, cleanup, sshErr := SetupMirrorSSHAgent(ctx, m.Repo, remoteURL.String()) if sshErr != nil { log.Error("SyncMirrors [repo: %-v]: SSH setup error %v", m.Repo, sshErr) return nil, false diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index da3183a272..4e340d80da 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -21,7 +21,6 @@ import ( "code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" - ssh_module "code.gitea.io/gitea/modules/ssh" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/migrations" @@ -168,36 +167,13 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error { } // Setup SSH authentication - if IsSSHURL(remoteURL.String()) { - if repo.Owner == nil { - if err := repo.LoadOwner(ctx); err != nil { - log.Error("Failed to load repository owner for %s: %v", repo.FullName(), err) - return util.SanitizeErrorCredentialURLs(err) - } - } - keypair, err := GetSSHKeypairForRepository(ctx, repo) - if err != nil { - log.Error("Failed to get SSH keypair for repository %s: %v", repo.FullName(), err) - return util.SanitizeErrorCredentialURLs(err) - } - if keypair != nil { - privateKey, err := keypair.GetDecryptedPrivateKey() - if err != nil { - log.Error("Failed to decrypt private key for repository %s: %v", repo.FullName(), err) - return util.SanitizeErrorCredentialURLs(err) - } - - socketPath, cleanup, err := ssh_module.CreateTemporaryAgent(privateKey) - if err != nil { - log.Error("Failed to create SSH agent for repository %s: %v", repo.FullName(), err) - return util.SanitizeErrorCredentialURLs(err) - } - defer cleanup() - - pushOpts.SSHAuthSock = socketPath - log.Debug("SSH agent created for push mirror %s with socket: %s", repo.FullName(), socketPath) - } + sshAuthSock, cleanup, err := SetupMirrorSSHAgent(ctx, repo, remoteURL.String()) + if err != nil { + log.Error("Failed to set up SSH agent for push mirror %s: %v", repo.FullName(), err) + return util.SanitizeErrorCredentialURLs(err) } + defer cleanup() + pushOpts.SSHAuthSock = sshAuthSock if err := gitrepo.PushToExternal(ctx, storageRepo, pushOpts); err != nil { log.Error("Error pushing %s mirror[%d] remote %s: %v", storageRepo.RelativePath(), m.ID, m.RemoteName, err) diff --git a/services/mirror/ssh_keypair.go b/services/mirror/ssh_keypair.go index 00bd26fc0d..ce2e2c4c54 100644 --- a/services/mirror/ssh_keypair.go +++ b/services/mirror/ssh_keypair.go @@ -50,3 +50,10 @@ func IsSSHURL(url string) bool { func GetSSHKeypairForURL(ctx context.Context, repo *repo_model.Repository, url string) (*repo_model.UserSSHKeypair, error) { return ssh_module.GetSSHKeypairForURL(ctx, repo, url) } + +// SetupMirrorSSHAgent prepares SSH key-based authentication for a mirror or +// migration git operation against url on behalf of repo. The returned cleanup +// is never nil and must always be called by the caller (typically via defer). +func SetupMirrorSSHAgent(ctx context.Context, repo *repo_model.Repository, url string) (string, func(), error) { + return ssh_module.SetupMirrorSSHAgent(ctx, repo, url) +} diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 9b08efe387..77a48e6c71 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -28,34 +28,8 @@ import ( "code.gitea.io/gitea/modules/util" ) -func setupMigrationSSHAuth(ctx context.Context, repo *repo_model.Repository, remoteURL string) (string, func(), error) { - if !ssh_module.IsSSHURL(remoteURL) { - return "", func() {}, nil - } - - keypair, err := ssh_module.GetSSHKeypairForRepository(ctx, repo) - if err != nil { - return "", nil, fmt.Errorf("failed to get SSH keypair for repository: %w", err) - } - if keypair == nil { - return "", func() {}, nil - } - - privateKey, err := keypair.GetDecryptedPrivateKey() - if err != nil { - return "", nil, fmt.Errorf("failed to decrypt private key: %w", err) - } - - socketPath, cleanup, err := ssh_module.CreateTemporaryAgent(privateKey) - if err != nil { - return "", nil, fmt.Errorf("failed to create SSH agent: %w", err) - } - - return socketPath, cleanup, nil -} - func cloneExternalRepoWithSSHAuth(ctx context.Context, repo *repo_model.Repository, remoteURL string, storageRepo gitrepo.Repository, cloneOpts git.CloneRepoOptions) error { - sshAuthSock, cleanup, err := setupMigrationSSHAuth(ctx, repo, remoteURL) + sshAuthSock, cleanup, err := ssh_module.SetupMirrorSSHAgent(ctx, repo, remoteURL) if err != nil { return err }