13446: Enable TLS if given TLSCertificateFile and TLSKeyFile.
authorTom Clegg <tclegg@veritasgenetics.com>
Wed, 9 May 2018 19:56:20 +0000 (15:56 -0400)
committerTom Clegg <tclegg@veritasgenetics.com>
Wed, 9 May 2018 19:56:20 +0000 (15:56 -0400)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg@veritasgenetics.com>

services/keepstore/config.go
services/keepstore/keepstore.go
services/keepstore/server.go [new file with mode: 0644]
services/keepstore/server_test.go [new file with mode: 0644]
services/keepstore/usage.go

index 19dc7f69bed5077744c346567c1ee8a8de8f81d4..c9c9ae1158ec323f572524adb3e7586590d8f788 100644 (file)
@@ -43,6 +43,8 @@ type Config struct {
        PullWorkers         int
        TrashWorkers        int
        EmptyTrashWorkers   int
+       TLSCertificateFile  string
+       TLSKeyFile          string
 
        Volumes VolumeList
 
index c74275201753787189e34604227ba0719c398bf8..79e3017d55a8f7e108ee8d6b2d7effc3950a7260 100644 (file)
@@ -8,7 +8,6 @@ import (
        "flag"
        "fmt"
        "net"
-       "net/http"
        "os"
        "os/signal"
        "syscall"
@@ -203,7 +202,8 @@ func main() {
                log.Printf("Error notifying init daemon: %v", err)
        }
        log.Println("listening at", listener.Addr())
-       srv := &http.Server{Handler: router}
+       srv := &server{}
+       srv.Handler = router
        srv.Serve(listener)
 }
 
diff --git a/services/keepstore/server.go b/services/keepstore/server.go
new file mode 100644 (file)
index 0000000..3f67277
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package main
+
+import (
+       "crypto/tls"
+       "net"
+       "net/http"
+       "os"
+       "os/signal"
+       "syscall"
+)
+
+type server struct {
+       http.Server
+
+       // channel (size=1) with the current keypair
+       currentCert chan *tls.Certificate
+}
+
+func (srv *server) Serve(l net.Listener) error {
+       if theConfig.TLSCertificateFile == "" && theConfig.TLSKeyFile == "" {
+               return srv.Server.Serve(l)
+       }
+       // https://blog.gopheracademy.com/advent-2016/exposing-go-on-the-internet/
+       srv.TLSConfig = &tls.Config{
+               GetCertificate:           srv.getCertificate,
+               PreferServerCipherSuites: true,
+               CurvePreferences: []tls.CurveID{
+                       tls.CurveP256,
+                       tls.X25519,
+               },
+               MinVersion: tls.VersionTLS12,
+               CipherSuites: []uint16{
+                       tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+                       tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+                       tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
+                       tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
+                       tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+                       tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+               },
+       }
+       srv.currentCert = make(chan *tls.Certificate, 1)
+       go srv.refreshCertificate(theConfig.TLSCertificateFile, theConfig.TLSKeyFile)
+       return srv.Server.ServeTLS(l, "", "")
+}
+
+func (srv *server) refreshCertificate(certfile, keyfile string) {
+       cert, err := tls.LoadX509KeyPair(certfile, keyfile)
+       if err != nil {
+               log.WithError(err).Fatal("error loading X509 key pair")
+       }
+       srv.currentCert <- &cert
+
+       reload := make(chan os.Signal, 1)
+       signal.Notify(reload, syscall.SIGHUP)
+       for range reload {
+               cert, err := tls.LoadX509KeyPair(certfile, keyfile)
+               if err != nil {
+                       log.WithError(err).Warn("error loading X509 key pair")
+                       continue
+               }
+               // Throw away old cert and start using new one
+               <-srv.currentCert
+               srv.currentCert <- &cert
+       }
+}
+
+func (srv *server) getCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
+       if srv.currentCert == nil {
+               panic("srv.currentCert not initialized")
+       }
+       cert := <-srv.currentCert
+       srv.currentCert <- cert
+       return cert, nil
+}
diff --git a/services/keepstore/server_test.go b/services/keepstore/server_test.go
new file mode 100644 (file)
index 0000000..84adf36
--- /dev/null
@@ -0,0 +1,47 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package main
+
+import (
+       "bytes"
+       "context"
+       "crypto/tls"
+       "io/ioutil"
+       "net"
+       "net/http"
+       "testing"
+)
+
+func TestTLS(t *testing.T) {
+       defer func() {
+               theConfig.TLSKeyFile = ""
+               theConfig.TLSCertificateFile = ""
+       }()
+       theConfig.TLSKeyFile = "../api/tmp/self-signed.key"
+       theConfig.TLSCertificateFile = "../api/tmp/self-signed.pem"
+       srv := &server{}
+       srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               w.Write([]byte("OK"))
+       })
+       l, err := net.Listen("tcp", ":")
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer l.Close()
+       go srv.Serve(l)
+       defer srv.Shutdown(context.Background())
+       c := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
+       resp, err := c.Get("https://" + l.Addr().String() + "/")
+       if err != nil {
+               t.Fatal(err)
+       }
+       body, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               t.Error(err)
+       }
+       if !bytes.Equal(body, []byte("OK")) {
+               t.Errorf("expected OK, got %q", body)
+       }
+}
index 672d7cf1694e78df6e5269dfdab58a5cddbb8139..8e83f6ce5f02c33182f40f89d42c0d7e3d4b59b7 100644 (file)
@@ -133,6 +133,21 @@ PullWorkers:
     Maximum number of concurrent pull operations. Default is 1, i.e.,
     pull lists are processed serially.
 
+TLSCertificateFile:
+
+    Path to server certificate file in X509 format. Enables TLS mode.
+
+    Example: /var/lib/acme/live/keep0.example.com/fullchain
+
+TLSKeyFile:
+
+    Path to server key file in X509 format. Enables TLS mode.
+
+    The key pair is read from disk during startup, and whenever SIGHUP
+    is received.
+
+    Example: /var/lib/acme/live/keep0.example.com/privkey
+
 Volumes:
 
     List of storage volumes. If omitted or empty, the default is to