Search code examples
gitsshgo-git

In Go Git, how to authenticate using the SSH agent?


I've set up Git with SSH following https://docs.github.com/en/authentication/connecting-to-github-with-ssh; specifically, I've added my key to the SSH agent following https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#adding-your-ssh-key-to-the-ssh-agent. I'd like to use go-git and have tried the following example (following this comment):

package main

import (
    "log"
    "os"

    "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing/transport/ssh"
)

func main() {
    authMethod, err := ssh.DefaultAuthBuilder("keymaster")
    if err != nil {
        log.Fatalf("default auth builder: %v", err)
    }

    if _, err := git.PlainClone("/tmp/foo", false, &git.CloneOptions{
        URL:      "[email protected]:org/repo.git",
        Progress: os.Stdout,
        Auth:     authMethod,
    }); err != nil {
        log.Fatalf("plain clone: %v", err)
    }
}

However, when I run it I get the following error:

> go run main.go
2023/02/20 06:55:25 plain clone: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain
exit status 1

It would appear from https://github.com/go-git/go-git/blob/c35b8082c863f2106de1c3c95ba9ed21d30f9371/plumbing/transport/ssh/common.go#L37-L39 that DefaultAuthBuilder is just an alias for NewSSHAgentAuth, but it is unclear to me how to provide the correct username to obtain the auth method. Does anyone know how to do this?


Solution

  • It looks like the simplest way to get go-git to authenticate using your agent is to let it pick an authentication method by itself. The following seems to work perfectly:

    package main
    
    import (
      "log"
      "os"
    
      "github.com/go-git/go-git/v5"
    )
    
    func main() {
      repo := os.Args[1]
      dir := os.Args[2]
    
      if _, err := git.PlainClone(dir, false, &git.CloneOptions{
        URL:      repo,
        Progress: os.Stdout,
      }); err != nil {
        log.Fatalf("plain clone: %v", err)
      }
    }
    

    If I point this at a private repository (./gg [email protected]/larsks/somepriverepo myrepo), it successfully authenticates using a key from my agent and produces a local clone of the repository.


    It looks like the username argument to ssh.DefaultAuthBuilder needs to be the target username on the remote system...which in this case is git; so this also works, as long as you're fetching a git@... URL:

    package main
    
    import (
      "log"
      "os"
    
      "github.com/go-git/go-git/v5"
      "github.com/go-git/go-git/v5/plumbing/transport/ssh"
    )
    
    func main() {
      repo := os.Args[1]
      dir := os.Args[2]
    
      authMethod, err := ssh.DefaultAuthBuilder("git")
      if err != nil {
        log.Fatalf("default auth builder: %v", err)
      }
    
      if _, err := git.PlainClone(dir, false, &git.CloneOptions{
        URL:      repo,
        Progress: os.Stdout,
        Auth:     authMethod,
      }); err != nil {
        log.Fatalf("plain clone: %v", err)
      }
    }
    

    For a more general solution you would need to parse the username from the repository URL.