How to setup private Git repos with Golang Workspace (and Keybase)

If you have more than one GitHub accounts, for example, one for your real-life profile building public projects, and another for your alter-online-ego contributing underground works using pseudonym. I'm going to show you how to properly setup multiple identities for Git and SSH; how to use Golang Workspace with private Git repos; and even better - how to use end-to-end encrypted Keybase Git repo for your Golang modules.

Create SSH identities

Generate separate SSH keys and add them to your GitHub accounts. Don't reuse these keys anywhere else for security.

Then edit $HOME/.ssh/config and define your SSH identities:

Host PeterParker
    HostName github.com
    User git
    IdentityFile /home/root/.ssh/PeterParker.key
    IdentitiesOnly yes

Host SpiderMan
    HostName github.com
    User git
    IdentityFile /home/root/.ssh/SpiderMan.key
    IdentitiesOnly yes

Now, if you wan to contribute to git@github.com:school/homework.git, you can use the repo URL ssh://PeterParker/school/homework.git instead. Git+SSH would still connect to github.com but will use your PeterParker SSH key for authentication.

Likewise, when it's time to do something awesome for the private repo git@github.com:StarkIndustries/SuperArmor.git, you can instead use the repo URL ssh://SpiderMan/StarkIndustries/SuperArmor.git. This time your SpiderMan SSH key would be used for authentication for github.com server.

Save password for SSH keys

If your SSH keys are encrypted, every time you connect to a server using the key, Git would ask you for password. We can use a background process to keep the decrypted SSH keys for easier access.

Linux users can follow this guide to enable ssh-agent service.

Windows users can follow this guide to enable ssh-agent service.

Computer restart might be needed for the service to start correctly.

After adding your SSH keys for all your identities (have to use the exact same absolute key paths in $HOME/.ssh/config) using ssh-add, you can test it with e.g. ssh -T PeterParker or ssh -T SpiderMan. It should be able to connect to the server without prompting for password anymore.

Configure Git

If you have multiple SSH clients installed on your system, especially for Windows system, Git may not pick the correct one. It's recommended to explicitly tell Git which SSH command it should use, by defining the following environment variable:

GIT_SSH_COMMAND="C:/Windows/System32/OpenSSH/ssh.exe -o ControlMaster=no -o BatchMode=yes"

And also set it in Git's global config:

git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe -o ControlMaster=no -o BatchMode=yes"

It's recommended to use the SSH client integrated in the Windows system, not the one shipped together with Git for Windows or other applications.

Using private GitHub repos for Golang modules

Golang modules have special mechanism in resolving and locating modules from the internet. If you have a private repo at git@github.com:StarkIndustries/SuperArmor.git containing a Golang module, you cannot just import "github.com/StarkIndustries/SuperArmor" and hope it to work correctly.

What we need here is to setup Golang in a way it can resolve the module into a Git repo with the SSH URL that we have an identity defined for it.

We can do this with a free Cloudflare Workers service. First you need a Cloudflare account. You can register one with just an email. You don't need to own a domain to use Workers. Simply go to the Workers tab and you can setup a free Worker subdomain under <your_sub_domain>.workers.dev. Then you create a new Worker Service at e.g. ssh.<your_sub_domain>.workers.dev. Use the online Quick Edit tool to deploy the following Worker script:

export default {
  async fetch(request, env) {
    try {
      const { host, pathname } = new URL(request.url);
      const components = pathname.split('/');
      if (components.length < 4) {
        return new Response('not found', { status: 404 });
      }
      const sshHost = decodeURIComponent(components[1]);
      const repoPath = components.slice(2, 4).join('/');
      const repoRoot = `${host}/${sshHost}/${repoPath}`;
      const repoURL = `ssh://${sshHost}/${repoPath}`;
      const html = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import"/>
</head>
</html>`;
      return new HTMLRewriter()
        .on('meta[name="go-import"]', {
          element(meta) {
            meta.setAttribute('content', `${repoRoot} git ${repoURL}`);
          },
        })
        .transform(new Response(html, {
          headers: {
            'Content-Type': 'text/html; charset=utf-8',
          },
        }));
    } catch(e) {
      console.error(e);
      return new Response(err.stack);
    }
  }
}

Then add the following environment variable so that we bypass any public GOPROXY services for our private modules.

# Do not go through GOPROXY and do not check SumDB
GOPRIVATE="ssh.<your_sub_domain>.workers.dev"

Now in your Golang projects, you can import "ssh.<your_sub_domain>.workers.dev/MarvelUniverse/StarkIndustries/SuperArmor" and it will be resolved to ssh://MarvelUniverse/StarkIndustries/SuperArmor. You can define a new SSH config for the MarvelUniverse host to use the SpiderMan SSH key:

Host MarvelUniverse
    HostName github.com
    User git
    IdentityFile /home/root/.ssh/SpiderMan.key
    IdentitiesOnly yes

And your collaborator, e.g. IronMan, can also define his SSH config on his computer, to use his IronMan SSH key for modules belonging to the MarvelUniverse host:

Host MarvelUniverse
    HostName github.com
    User git
    IdentityFile /home/root/.ssh/IronMan.key
    IdentitiesOnly yes

Encrypted Git with Keybase

What if your underground project is so secret that you need end-to-end encryption for all its data? You can use Keybase for that. Keybase provides free encrypted Git service. Its Git integration is achieved with a custom Git transport protocol. A Keybase repo Git URL looks like this: keybase://team/StarkIndustries/SuperArmor.

Using Keybase with Git alone works out of the box. You only need to have the Keybase desktop client installed and all the Git integration should be ready to use. Git won't even prompt for any credentials when you are logged in with the Keybase client.

However, if you want to use Keybase Git repo for Golang modules, it becomes a bit tricky. But the solution is similar to using Git+SSH private repos for Golang. This time create a new Cloudflare Worker Service at keybase.<your_sub_domain>.workers.dev and deploy the following script:

export default {
  async fetch(request, env) {
    try {
      const { host, pathname } = new URL(request.url);
      const components = pathname.split('/');
      if (components.length < 4) {
        return new Response('not found', { status: 404 });
      }
      const repoPath = components.slice(1, 4).join('/');
      const repoRoot = `${host}/${repoPath}`;
      const repoURL = `keybase://${repoPath}`;
      const html = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import"/>
</head>
</html>`;
      return new HTMLRewriter()
        .on('meta[name="go-import"]', {
          element(meta) {
            meta.setAttribute('content', `${repoRoot} git ${repoURL}`);
          },
        })
        .transform(new Response(html, {
          headers: {
            'Content-Type': 'text/html; charset=utf-8',
          },
        }));
    } catch(e) {
      console.error(e);
      return new Response(err.stack);
    }
  }
}

In addition, to enable Golang for the keybase:// custom Git protocol, we need to setup the following environment variables:

# Allow keybase:// scheme to be used for Golang
GOINSECURE="keybase.<your_sub_domain>.workers.dev"

# Explicitly allow keybase:// protocol for Git
GIT_ALLOW_PROTOCOL="file:git:ssh:http:https:keybase"

And also add a global Git config:

git config --global protocol.keybase.allow always

Now you can use import "keybase.<your_sub_domain>.workers.dev/team/StarkIndustries/SuperArmor" in your Go projects and it will be resolved to using Git repo with URL keybase://team/StarkIndustries/SuperArmor.