physician, update thyself
Posted
In my continued pursuit of pretty much any excuse to sit down and knock around a bunch of Go for a few hours, I decided to finally try out go-update.
For some reason I’d been intimidated by the idea of using this library, but as soon as I dug in, I realized my anxiety was unwarranted.
It took me more time to weave in all of the parameterization I wanted using package flag
than it did to implement go-update
itself.
I haven’t yet tried to implement binary patching, which seems like the way to go. I can’t recommend using this in production without checksum verification and code signature verification.
The client can easily update itself:
import (
"net/http"
"github.com/inconshreveable/go-update"
"github.com/pkg/errors"
)
func UpdateFrom(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
err := update.Apply(resp.Body, update.Options{})
if err != nil {
if rerr := update.RollbackError(err); rerr != nil {
err = errors.Wrap(rerr, "rollback failed")
}
}
return err
}
…and that’s it. Of course, I needed to write the server half of this conversation as well, which boils down to this:
import (
"io"
"net/http"
"os"
"strconv"
"github.com/gorilla/mux"
"github.com/pkg/errors"
)
func UpdateHandler(binaryPath string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
f, err := os.Open(binaryPath)
defer f.Close()
if err != nil {
code := http.StatusNoContent
err = errors.Wrap(err, http.StatusText(code))
// ...log the error...
http.Error(w, err.Error(), code)
return
}
header := make([]byte, 512)
f.Read(header)
contentType := http.DetectContentType(header)
stat, _ := f.Stat()
size := strconv.FormatInt(stat.Size(), 10)
w.Header().Set("Content-Disposition", "attachment; filename="+binaryPath)
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Length", size)
f.Seek(0, 0) // rewind after having read header above
io.Copy(w, f)
// ...log the successful request...
}
}
The UpdateHandler
is invoked thus in main.go
:
import (
"flag"
"net/http"
"github.com/gorilla/mux"
)
func main() {
var (
binaryPath = flag.String(
"binary-path",
"dist/new-binary",
"the path of the binary to serve via the update endpoint",
)
addr = flag.String(
"addr",
":8080",
"port to run the HTTP update server",
)
)
flag.Parse()
m := mux.NewRouter()
m.HandleFunc("/update", UpdateHandler(*binaryPath))
srv := http.Server{Addr: addr, Handler: m}
err := srv.ListenAndServe()
if err != http.ErrServerClosed {
// ...log the error...
}
}
WARNING: Allowing a binary to update itself requires a lot of trust, and the network isn’t secure enough to allow that trust without additional verification mechanisms at the application layer:
- The client shouldn’t naively trust that the binary it receives from the server is authentic.
- The server shouldn’t naively trust that the client requesting a binary is authorized to receive it.
If you use the naive implementation above in production, you may end up on the news.