From db5bb20bf5d54fb139b54ec367177d847d643cc1 Mon Sep 17 00:00:00 2001 From: Aaron D Borden Date: Fri, 22 Jul 2022 17:17:11 -0700 Subject: [PATCH] Accpet HMAC key from file Fixes #15 --- README.md | 10 ++++++++++ main.go | 15 ++++++++++++++- server/server.go | 20 ++++++++++++-------- server/server_test.go | 19 ++++++++++++++++++- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 03ab7c7..6788893 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,13 @@ git config lfs.url http://localhost:9876/ # you can confirm the Endpoint that will be used by running git lfs env | grep Endpoint ``` + +When running multiple instances of lfscache, you must use a shared hmac key so +signatures from one instance can be verified by the others with a shared key. +This key is not used for storage, so it's safe to rotate the key and restart all +instances. + +``` +dd if=/dev/urandom bs=1 count=64 > hmac-key +$ ./lfscache --hmac-key-file hmac-key ... +``` diff --git a/main.go b/main.go index 1682dd9..a4965c7 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "errors" "flag" "fmt" + "io/ioutil" "net" "net/http" "net/url" @@ -30,6 +31,7 @@ func main() { tlsCert = flag.String("tls-cert", "", "HTTPS TLS certificate filepath") lfsServerURL = flag.String("url", "", "LFS server URL") directory = flag.String("directory", "./objects", "cache directory") + hmacKeyFile = flag.String("hmac-key-file", "", "file containing 64 byte HMAC key for request signing") printVersion = flag.Bool("v", false, "print version") ) @@ -56,7 +58,18 @@ func main() { os.Exit(1) } - s, err := server.New(logger, addr.String(), *directory) + var hmacKey []byte = nil + if *hmacKeyFile != "" { + hmac, err := ioutil.ReadFile(*hmacKeyFile) + if err != nil { + level.Error(logger).Log("err", err) + os.Exit(1) + } + hmacKey = hmac + } + + + s, err := server.New(logger, addr.String(), *directory, hmacKey) if err != nil { panic(err) } diff --git a/server/server.go b/server/server.go index 90f15bd..b2a68b3 100644 --- a/server/server.go +++ b/server/server.go @@ -98,16 +98,16 @@ type Server struct { } // New returns a new LFS proxy caching server. -func New(logger log.Logger, upstream, directory string) (*Server, error) { - return newServer(logger, upstream, directory, true) +func New(logger log.Logger, upstream, directory string, hmacKey []byte) (*Server, error) { + return newServer(logger, upstream, directory, true, hmacKey) } // NewNoCache returns a new LFS proxy server, with no caching. func NewNoCache(logger log.Logger, upstream string) (*Server, error) { - return newServer(logger, upstream, "", false) + return newServer(logger, upstream, "", false, nil) } -func newServer(logger log.Logger, upstream, directory string, cacheEnabled bool) (*Server, error) { +func newServer(logger log.Logger, upstream, directory string, cacheEnabled bool, hmacKey []byte) (*Server, error) { var fs *cache.FilesystemCache var err error if cacheEnabled { @@ -135,10 +135,14 @@ func newServer(logger log.Logger, upstream, directory string, cacheEnabled bool) ObjectBatchActionURLRewriter: DefaultObjectBatchActionURLRewriter, } - _, err = rand.Read(s.hmacKey[:]) - if err != nil { - return nil, err - } + if hmacKey != nil { + copy(s.hmacKey[:], hmacKey) + } else { + _, err = rand.Read(s.hmacKey[:]) + if err != nil { + return nil, err + } + } if s.upstream, err = url.Parse(upstream); err != nil { return nil, err diff --git a/server/server_test.go b/server/server_test.go index bed66bf..07c8a14 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1,6 +1,7 @@ package server import ( + "crypto/rand" "encoding/json" "fmt" "io/ioutil" @@ -47,11 +48,27 @@ func server() (*httptest.Server, *Server, string, error) { return ts, nil, dir, err } - s, err := New(log.NewNopLogger(), ts.URL, dir) + s, err := New(log.NewNopLogger(), ts.URL, dir, nil) return ts, s, dir, err } +func TestHmac(t *testing.T) { + var hmac [64]byte + _, err := rand.Read(hmac[:]) + require.NoError(t, err) + + s, err := New(log.NewNopLogger(), "http://example.com", "", hmac[:]) + require.NoError(t, err) + assert.Equal(t, s.hmacKey, hmac) +} + +func TestNoHmac(t *testing.T) { + s, err := New(log.NewNopLogger(), "http://example.com", "", nil) + require.NoError(t, err) + assert.NotEmpty(t, s.hmacKey) +} + func TestProxy(t *testing.T) { ts, s, dir, err := server() defer os.RemoveAll(dir)