Writing a replacement to OpenSSH using Go (1/2)

December 05, 2014 - 10 min read
Writing a replacement to OpenSSH using Go (1/2)

The second part is live: Writing a replacement to OpenSSH using Go (2/2)

SSH is a well-known protocol to access remote servers and OpenSSH is the most common implementation. This article will explain how to build a SSH server and client using Go.

TL;DR

  • Why OpenSSH Server is not sufficient in a distributed environment
  • What are the available tools and libraries in Go to use the SSH protocol
  • A few implementation examples

Git and SSH

In a Platform as a Service such as Appsdeck, the standard way to deploy an application is to use git push. Underneath, git is simply using the ssh command. The two lines below explain the link between them:

# With appsdeck remote: git@appsdeck.eu:myproject.git
~/myproject $ git push appsdeck master

# Git will execute:
~/myproject $ ssh git@appsdeck.eu git-receive-pack myproject.git

Actually, it is possible to tell git to use another SSH client, by setting the environment variable GIT_SSH.

OpenSSH limitation

To authenticate a user to a remote server using OpenSSH Server, the user’s public SSH key has to be appended in the ~/.ssh/authorized_keys file of the target user. If OpenSSH finds the public key in this file, the authentication is considered successful. By default, it would execute the shell of the user, defined by its account.

It is possible to execute a custom command by prepending it to the public SSH key in the authorized_keys file:

command="ssh-handler" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA[...]2bF26cl

In this case, instead of running the shell of the user, it will run the command “ssh-handler”. Our first prototype was using this feature to authenticate and authorize our users. However, as soon as you want to build a real distributed infrastructure, it’s not sufficient anymore. Indeed, with this method, it means that several hosts have to keep their authorized_keys file containing all the public keys of our users in sync. That’s two problems in one : we had to find a synchronization mechanism, and, sensitive data are spread out on several machines.

That’s why we decided it was time to build something more robust and scalable by writing our own custom SSH server, which would be able to use a custom backend to identify our users and then to forward the SSH connection to another host which will actually execute the GIT operation.

SSH in the Go world

We decided to write this custom SSH server with Go for different reasons. Except the fact that we had experience with the language, this official package (http://godoc.org/golang.org/x/crypto/ssh) implements everything we needed to build what we were looking for, while keeping it simple.

At Appsdeck, we are looking to respect the KISS principle. If a software is simple, it is easier to conserve readability, modularity and easy maintenance. By the way, that’s also why Go has become one of our core languages.

The SSH RFCs

While the Go package implements SSH connections setup (that includes transport and crypto), you still have to understand and to use SSH applicative protocol to build your software.

To achieve this, it is important to get how SSH is working, the following RFCs define the protocol:

Anytime you need to get explanations about a method or a constant of the Go package, refering to the RFCs is pretty straightforward with a simple text search.

(RFCs 4255 and 4256 also concern SSH but are less useful in this scope)

Code Examples

1. Basic server

The following code example defines a simple SSH server: https://github.com/Scalingo/go-ssh-examples/blob/master/server.go

This server only displays the kind of SSH key used for authentication (if any), accepts it, then closes the connection.

Server:

└> go run server.go
2014/12/05 15:41:27 [::1]:46428 authenticate with ssh-rsa
2014/12/05 15:41:27 Connection from [::1]:46428

Client:

└> ssh localhost -p 2222
Connection to localhost closed by remote host.
Connection to localhost closed.

As expected, our Go server succeeds to speak with OpenSSH, but then the connection is closed instantly.

2. Basic client

https://github.com/Scalingo/go-ssh-examples/blob/master/client.go

Usage:

go run client.go <user> <server:port> <command>

This client has only one job, it connects to a server, creates a sessions, executes a command, then prints the output and disconnects.

Output:

└> go run client.go foobar example.com:22 'ls /'
Password: *********
bin
boot
conf.d
dev
etc
home
initrd.img
lib[...]

Conclusion

This article introduces our problematic and how to use the SSH protocol with Go. In the next article we’ll explain how we use this library to solve our real problems (Authentication, Authorization, Connection Proxying).

To be continued…

… Ready? Second part is here: Writing a replacement to OpenSSH using Go (2/2)

Links

This article was the 2nd post of the serie #FridayTechnical, see you next time!

Credits

Gopher image by Renee French (Creative Commons Attributions 3.0) Puffy - OpenSSH logo

— Léo Unbekandt, CTO @ Appsdeck

Share the article
Léo Unbekandt
Léo Unbekandt
Léo is the founder and CTO of Scalingo. He studied in France as a cloud engineer (ENSIIE) and in England (Cranfield University). He is in charge of Scalingo's technical development and he manages Scalingo's tech team.

Try Scalingo for free

30-day free trial / No credit card required / Hosted in Europe