Order now

Go: Four weeks later (Building a password manager, part 2)

After the client of our password manager is done in Electron, the next step is the server side.

Node.js all the things?

With the client made with electron/node.js, why not use it for the server as well? After all, that’s the whole idea behind Node.

Coming more from the sysadmin corner myself, my top priority is ease of deployment. And frankly, I don’t think node.js performs well here. It’s lacking all the mature load-balancing infrastructure of other interpreted languages (like Passenger for WSGI/Ruby/… or even FPM for PHP), and we cannot rely on our SMB customers having container infrastructure either to make up for it. Apart from that, my dislike of npm and the whole Node ecosystem hasn’t improved, so I’d rather not use it more than necessary.

Nor can I make assumptions about what other languages our customers support (and at what version) – and since we prefer native libsodium for cryptographic primitives, not interpreted ports, installation would be a hassle for all of them.

So Go ticks two important checkboxes: I can (very easily) get a binary I can run everywhere, and it can make use of native threading internally, so I don’t necessarily need external load balancing in front of it to start up/shut down worker processes.

Besides, I wanted to give it a try anyway.

Scope

We have a somewhat special case with our password manager: Virtually all data is encrypted, so the backend cannot do much work with it, except validating and serving as a thin CRUD wrapper for it. And since the clients do most of the cryptography too, there isn’t much of that in the server either.

As a result, the code is a downright trivial wrapper around an ORM (gorm, to be precise), which means we’re spared most of the deeper details of Go.

Dependency management

Since we’re creating fully static binaries, we have to do all the dependency management ourselves. Thankfully, the Go ecosystem is much saner than Node’s – it helps that Go has a much better standard library than JavaScript and doesn’t have to worry about “useless” functions in libraries, as they’re easily stripped, with the option to share libraries between projects (and vendoring them only where needed).

For vendoring we use Glide, which is a nice package manager to make maintaining vendored dependencies easier, supporting Go’s native vendor/ mechanism and supporting features like version/revision locking. Others exist, but as glide works fine, we didn’t look much further into it.

Working with Go

Coming largely from C and Python, Go is somewhat easy to get started with – the “it’s just C with a garbage collector” crowd isn’t entirely wrong. It certainly doesn’t have many surprising features, once you wrapped your head around goroutines – and in many cases you don’t even need them explicitly, because they’re wrapped by library functions like the built-in HTTP dispatcher. (Much more pleasant than anything concurrency-related in Python.)

On the downside, working with more complex data structures than a one-dimensional array also reminds me of C. It’s not just the lack of generics (or at least allowing multiple function definitions with different argument types, like Java did as work-around), but generally allocating and manipulating data is extremely verbose. Thankfully, we never really needed much of it (see above), and for similar microservices it will probably be fine. But Go is not a language I would use for anything doing heavy data transformation.

Additionally, managing state is something I found lacking in Go 1.6, but thankfully it’s been partially solved in Go 1.7 about two weeks after I needed it. (Unfortunate timing aside, the pace of Go development seems to be excellent.)

Drawbacks

Still, I find myself using global state and goto far more often than I’d like, and flow control is much more awkward without the ability to bail out of a function with a throw, or to manage error handling in a concise manner with block-scoped try/catch mechanisms. goto fail technically works, but it makes code illegible fast:

// If we had a language using exceptions, we could just not catch
// any of these and let the caller handle this
_, err = rand.Read (seed)
if err != nil { goto ERROR }

_, err = rand.Read (pw_salt)
if err != nil { goto ERROR }

_, err = rand.Read (nonce)
if err != nil { goto ERROR }

// or:

// If try/catch was a thing, I wouldn’t need to worry about forgetting a
// Rollback() or having to declare all variables up front so I can use goto

if notfound {
    tx.Rollback()
    return E_NOT_FOUND
}
if !foo.Verify() {
    tx.Rollback()
    return E_NOT_AUTHORIZED
}

var bar []string
e := json.Unmarshal([]byte(baz), &bar)
if e != nil {
    tx.Rollback()
    return errors.new ("Error parsing password tags: " + e.Error())
}

These and related tasks are something that (as far as I could see) does not yet have any satisfactory patterns/guidelines that makes me confident in using Go for larger projects – originally, we had planned to use it for the client as well, but between the pains of scaling out writing Go code and the lack of a suitable cross-platform toolkit, these plans were shelved in favour of Electron.

Conclusion

Go is a curious language: It definitely has its niche in baggage-free microservices (compile once, run anywhere!), but at the same time, the authors’ resistance to making the language any more advanced than C was (and focusing on making it just a better C) makes it clunky and unergonomic to program in.

Nevertheless, it was the right choice for the (micro-)servers used for our password manager, and going forward, we might adopt it for similar projects.

Published on Sep 30, 2016 by Sven Schwedas


Sven Schwedas
Sven Schwedas