Fiery SSH Servers in Golang

Update 01/5/21

Hey guys! It's been a while since I last posted something here (almost 3 months). My schedule is a bit erratic, so posts arrive at quite random times. I plan on writing a post every week and setting up the commenting system soon, so stay tuned for that!

Regularly scheduled programming

SSH is cool, and I recently just went down the deep rabbit hole regarding the topic. There are so many cool applications to build with this technology!

Don't know what I'm talking about? Go enter this into your terminal:

ssh me.rbansal.dev

Cool right? I'll be going through the detailed process of building an SSH app like this so you can make your own!

Requirements

  • Go installed, basic knowledge of syntax
  • A MacOS, Windows, or Linux workstation
  • A Linux VPS to deploy the finished product (optional)
  • An IDE to write code with

How Does It Work?

In my experience building this about a week ago, I had more trouble setting up the deployment environment than actually writing the script. For example, I had to switch out the default port for SSH to free it up for the purposes of this custom server, as well as install the script environment.

SSH Definitions

  1. SSH runs over TCP (Transmission Control Protocol). A connection is handled by an SSH server (usually OpenSSH) that can send all the right TCP commands back to the client to secure the connection.
  2. The default SSH port is 22, but it can be changed to anything you like (within bounds).
  3. SSH makes use of an encryption system that requires a key on both the server and the client to authenticate. Most of the time, this is auto-generated.

Go Definitions

  1. Go is an open-source programming language developed by Google.
  2. It is statically typed and compiled, with syntax extremely similar to C, Java, and Javascript.

Building the Server

We're ready to get started! The SSH library we'll be using to build this script is this one. We'll print out an awesome dragon like in the thumbnail!

Writing the Go script

Let's begin by creating a new file called server.go in a directory of your choice. At the top of the file, write in the following:

package main

import (
	"fmt"
	"io"
	"log"
	"runtime"
	"strings"
	"time"
	"github.com/gliderlabs/ssh"
)

If you use an IDE such as Visual Studio Code, there is a chance that the import block will be deleted on save. This is because Go does not allow programs to contain unused imports, and at this point during our code-writing process, we don't have any code that uses those imports yet.

fmt, io, log, and strings are base packages we import to deal with I/O and printing to terminal. runtime is used later to detect if the operating system was running Windows (you'll see why later). time was used to build a "typewriter" function that emulates a person typing. Lastly, the external package at the bottom is a key libraries we use to build out this SSH program.

Next, we want to print color to the terminal (Note this doesn't work on Windows, that's why we need to detect that). Go ahead and add these ASCII codes that indicate color:

var Reset = "\033[0m"
var Red = "\033[31m"
var Green = "\033[32m"
var Yellow = "\033[33m"
var Blue = "\033[34m"
var Purple = "\033[35m"
var Cyan = "\033[36m"
var Gray = "\033[37m"
var White = "\033[97m"

The first variable, Reset, resets the color after we use it. Let's create a function now that utilizes these colors to typewrite something to the terminal! Go ahead and add this at the bottom of the file:

func typewrite(w io.Writer, content string) {
	chars := strings.Split(content, "")

	for _, c := range chars {
		fmt.Fprint(w, c)
		time.Sleep(30 * time.Millisecond)
	}
}

Let's go over this part by part.

func typewrite(w io.Writer, content string) {

We first declare a function that takes two inputs: an io.Writer object, and a string object. The first parameter will be the "terminal" we instantiate later. The second is the string we want to type!

chars := strings.Split(content, "")

Next, we create a list of characters by splitting the string up into letters.

for _, c := range chars {
    fmt.Fprint(w, c)
    time.Sleep(30 * time.Millisecond)
}

Last, we iterate over the list of characters and print out the letter we are currently on, then put the thread to "sleep" for a few milliseconds. This results in a really cool typewriter effect!

typewriting!

Now, in that GIF, you can see that I colored my name! Let's go ahead and do that with our typewriting function. To keep things modular, let's create a new function called colorType that uses typewrite but with color!

func colorType(w io.Writer, text string, color string) {
	typewrite(w, color+text+Reset)
}

This function basically takes the same parameters as typewrite, but with an extra one called color. We just set the color of the text inputted and continue to pass it to our typewrite function!

Last but not least, we'll build our final helper function that prints empty lines to space out text.

func printEnd(w io.Writer, ends int) {
	for i := 0; i < ends; i++ {
		fmt.Fprint(w, "\n")
	}
}

We iterate n amount of times, printing spaces (\n) as we go.

Alright, it's time to write the main function! Let's add the following code at the bottom of the file:


func main() {

	if runtime.GOOS == "windows" {
		Reset = ""
		Red = ""
		Green = ""
		Yellow = ""
		Blue = ""
		Purple = ""
		Cyan = ""
		Gray = ""
		White = ""
	}
}

What does this if statement do? Well, since Windows cannot interpret ASCII color codes, we have to remove them 😭.

Now for the fun part! To intercept an SSH connection and do cool things with it, we need to add an SSH Handler and listen for connections. Let's do so like this (put it inside the main function, under the if statement):

ssh.Handle(func(s ssh.Session) {
    
})
           
log.Println("Started SSH server on port 2222")
log.Println("System: " + runtime.GOOS)
log.Fatal(ssh.ListenAndServe(":2222", nil))

The ssh.Handle() function is where we're going to be printing our dragon. The other three lines log some essential information and start the server on a specific port, which is 2222.

Before we test this out, let's print something to the terminal! To do this, we need to create an object that represents our terminal instance. Inside the ssh.Handle() function, add the following:

ssh.Handle(func(s ssh.Session) {
    // add the next 3 lines
    term := terminal.NewTerminal(s, "")
	colorType(term, "hello world", Purple)
	printEnd(term, 1)
})

The first line instantiates a terminal, with the prompt being empty. In this tutorial, you'll never see this prompt, but if you decide to expand on this feel free to change the text to "Press [Enter] To Continue..." or something!

Now go ahead and save the file so we can test it out! If you're on Mac/Linux, enter the following into your terminal in the project directory:

go run server.go

This code runs the server. You should see the log output, which details that the server started and the operating system. Next, enter this command (Windows will have to use PuTTY):

ssh -p 2222 <hostname>@127.0.0.1

This allows you to SSH into your own machine to test out the program! Replace <hostname> with the hostname of your computer.

Do you see this?

If you see output like above, you did it! SSH is now set up! As a bonus, let's replace that "hello" with a dragon:

ssh.Handle(func(s ssh.Session) {
		term := terminal.NewTerminal(s, "")
		colorType(term, `___
						/ \  //\
		|\___/|      /   \//  \\
		/0  0  \__  /    //  | \ \    
		/     /  \/_/    //   |  \  \  
		@_^_@'/   \/_   //    |   \   \ 
		//_^_/     \/_ //     |    \    \
	( //) |        \///      |     \     \
	( / /) _|_ /   )  //       |      \     _\
( // /) '/,_ _ _/  ( ; -.    |    _ _\.-~        .-~~~^-.
(( / / )) ,-{        _       -.|.-~-.           .~          .
(( // / ))  '/\      /                 ~-. _ .-~      .-~^-.  \
(( /// ))       .   {            }                   /      \  \
(( / ))     .----~-.\        \-'                 .~         \   . \^-.
			///.----..>        \             _ -~              .  ^-   ^-_
			///-._ _ _ _ _ _ _}^ - - - - ~                     ~-- ,.-~
																/.-~`, Purple)
		printEnd(term, 1)
})

Remember to copy and paste this exactly as it is, or the ASCII art will mess up a bit. Feel free to replace the dragon with whatever you want!

Extra - Deployment to a VPS

I won't go too in-depth about deployment here, as much of it can be found online. I will, however, outline the steps here to put this online!

  1. Find a VPS (Virtual Private Server)
  2. SSH into the VPS using the default port (usually 22)
  3. Modify the default port in /etc/ssh/sshd_config and make it another arbitrary one (e.g. 4242)
  4. Use rsync , scp, or ftp to copy your Go script into the VPS. Install Go as well!
  5. Using nano, change the default port at the end of the main() function in the script to 22
  6. Set up a domain name that points to your VPS so anyone can SSH
  7. Run the Go script with go run server.go
  8. Test it out! On a different computer try to ssh into your VPS's IP address

Final Notes

My personal SSH server code is not up on GitHub yet; you'll have to refer to this article for the time being. I was heavily inspired by an SSH game I came across by Zach Latta called SSHTron. Go SSH into sshtron.zachlatta.com to check it out!

Again, I know I haven't posted in a while, but I made a new year's resolution to start writing a bit here every weekend. That way, I can look back on it in the future and remember!

Anyway, I hope you all had fun building this project, as I think there aren't many tutorials out there yet on the subject. Feel free to contact me via the methods on my website if you have any questions or want to share a cool creation!


Permalink: https://blog.rbansal.dev/ssh-servers/