Permalink
Browse files

TLS support (#16)

  • Loading branch information...
hoffie committed Jan 17, 2015
1 parent f75b87f commit 752cb04eef7a24b6394864ad6deeae6f14e10dc0
View
@@ -1,3 +1,4 @@
larasync
.idea
*.gcfg
*.crt
*.key
View
@@ -9,11 +9,13 @@ import (
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"time"
. "gopkg.in/check.v1"
edhelpers "github.com/hoffie/larasync/helpers/ed25519"
"github.com/hoffie/larasync/helpers/x509"
"github.com/hoffie/larasync/repository"
)
@@ -28,18 +30,17 @@ type BaseTests struct {
httpMethod string
getURL func() string
urlParams url.Values
certFile string
keyFile string
}
func (t *BaseTests) SetUpTest(c *C) {
t.repos = c.MkDir()
t.createRepoManager(c)
t.createServer(c)
t.httpMethod = "GET"
t.repositoryName = "test"
rm, err := repository.NewManager(t.repos)
c.Assert(err, IsNil)
t.server = New(adminPubkey, time.Minute, rm)
c.Assert(rm.Exists(t.repositoryName), Equals, false)
t.rm = rm
c.Assert(t.rm.Exists(t.repositoryName), Equals, false)
t.getURL = func() string {
return fmt.Sprintf(
"http://example.org/repositories/%s",
@@ -50,25 +51,37 @@ func (t *BaseTests) SetUpTest(c *C) {
t.urlParams = url.Values{}
}
func (t *BaseTests) createRepoManager(c *C) {
t.repos = c.MkDir()
rm, err := repository.NewManager(t.repos)
c.Assert(err, IsNil)
t.rm = rm
}
func (t *BaseTests) createServer(c *C) {
t.server = New(adminPubkey, time.Minute, t.rm, t.certFile, t.keyFile)
}
func (t *BaseTests) SetUpSuite(c *C) {
byteArray := make([]byte, PrivateKeySize)
_, err := rand.Read(byteArray)
c.Assert(err, IsNil)
t.privateKey, err = passphraseToKey(byteArray)
c.Assert(err, IsNil)
t.pubKey = edhelpers.GetPublicKeyFromPrivate(t.privateKey)
t.createServerCert(c)
}
func (t *BaseTests) TearDownTest(c *C) {
os.RemoveAll(t.repos)
}
func (t *BaseTests) getServer() *Server {
return t.server
func (t *BaseTests) createServerCert(c *C) {
dir := c.MkDir()
t.certFile = filepath.Join(dir, "server.crt")
t.keyFile = filepath.Join(dir, "server.key")
err := x509.GenerateServerCertFiles(t.certFile, t.keyFile)
c.Assert(err, IsNil)
}
func (t *BaseTests) setServer(server *Server) {
t.server = server
func (t *BaseTests) TearDownTest(c *C) {
os.RemoveAll(t.repos)
}
func (t *BaseTests) getResponse(req *http.Request) *httptest.ResponseRecorder {
View
@@ -1,6 +1,7 @@
package api
import (
"crypto/tls"
"net/http"
)
@@ -17,13 +18,16 @@ type Client struct {
// NetlocToURL returns the URL matching the given netloc
func NetlocToURL(netloc, repoName string) string {
// IMPROVEMENT: use mux router to generate URLs
return "http://" + netloc + "/repositories/" + repoName
return "https://" + netloc + "/repositories/" + repoName
}
// NewClient returns a new Client instance.
func NewClient(url string) *Client {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
return &Client{
http: &http.Client{},
http: &http.Client{Transport: tr},
BaseURL: url,
}
}
@@ -6,17 +6,13 @@ import (
"os"
"path/filepath"
"strings"
"time"
. "gopkg.in/check.v1"
"github.com/hoffie/larasync/repository"
)
type RepoListTests struct {
server *Server
req *http.Request
repos string
BaseTests
req *http.Request
}
var _ = Suite(&RepoListTests{})
@@ -25,13 +21,9 @@ func (t *RepoListTests) SetUpTest(c *C) {
req, err := http.NewRequest("GET", "http://example.org/repositories", nil)
c.Assert(err, IsNil)
t.req = req
}
func (t *RepoListTests) SetUpSuite(c *C) {
t.repos = c.MkDir()
rm, err := repository.NewManager(t.repos)
c.Assert(err, IsNil)
t.server = New(adminPubkey, time.Minute, rm)
t.createRepoManager(c)
t.createServer(c)
t.createRepository(c)
}
func (t *RepoListTests) getResponse(req *http.Request) *httptest.ResponseRecorder {
@@ -9,20 +9,14 @@ import (
"net/http/httptest"
"os"
"strings"
"time"
. "gopkg.in/check.v1"
"github.com/hoffie/larasync/repository"
)
type RepoListCreateTests struct {
server *Server
rm *repository.Manager
req *http.Request
repos string
repositoryName string
pubKey []byte
BaseTests
req *http.Request
pubKey []byte
}
var _ = Suite(&RepoListCreateTests{})
@@ -53,18 +47,15 @@ func (t *RepoListCreateTests) requestWithReader(c *C, httpBody io.Reader) *http.
}
func (t *RepoListCreateTests) SetUpTest(c *C) {
t.repos = c.MkDir()
t.repositoryName = "test"
rm, err := repository.NewManager(t.repos)
c.Assert(err, IsNil)
t.server = New(adminPubkey, time.Minute, rm)
c.Assert(rm.Exists(t.repositoryName), Equals, false)
t.rm = rm
t.createRepoManager(c)
t.createServer(c)
t.req = t.requestWithBytes(c, nil)
}
func (t *RepoListCreateTests) SetUpSuite(c *C) {
t.pubKey = make([]byte, PublicKeySize)
t.createServerCert(c)
}
func (t *RepoListCreateTests) TearDownTest(c *C) {
View
@@ -1,6 +1,7 @@
package api
import (
"crypto/tls"
"fmt"
"net"
"net/http"
@@ -19,6 +20,8 @@ type Server struct {
router *mux.Router
maxRequestAge time.Duration
http *http.Server
certFile string
keyFile string
rm *repository.Manager
}
@@ -28,7 +31,7 @@ const (
)
// New returns a new Server.
func New(adminPubkey [PublicKeySize]byte, maxRequestAge time.Duration, rm *repository.Manager) *Server {
func New(adminPubkey [PublicKeySize]byte, maxRequestAge time.Duration, rm *repository.Manager, certFile, keyFile string) *Server {
serveMux := http.NewServeMux()
s := Server{
adminPubkey: adminPubkey,
@@ -39,6 +42,8 @@ func New(adminPubkey [PublicKeySize]byte, maxRequestAge time.Duration, rm *repos
Addr: fmt.Sprintf(":%d", DefaultPort),
Handler: serveMux,
},
certFile: certFile,
keyFile: keyFile,
}
s.setupRoutes()
serveMux.Handle("/", s.router)
@@ -203,13 +208,26 @@ func attachCurrentTransactionHeader(r *repository.Repository, rw http.ResponseWr
}
}
// ListenAndServe starts serving requests on the default port.
// ListenAndServe starts serving requests on the default port using TLS
func (s *Server) ListenAndServe() error {
return s.http.ListenAndServe()
listener, err := net.Listen("tcp", s.http.Addr)
if err != nil {
return err
}
return s.Serve(listener)
}
// Serve proxies net/http.Server.Serve; exposue of this function is required
// for test server setup
// Serve serves TLS-enabled requests on the given listener.
func (s *Server) Serve(l net.Listener) error {
return s.http.Serve(l)
config := &tls.Config{}
config.NextProtos = []string{"http/1.1"}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(s.certFile, s.keyFile)
if err != nil {
return err
}
tlsListener := tls.NewListener(tcpKeepAliveListener{l.(*net.TCPListener)}, config)
return s.http.Serve(tlsListener)
}
View
@@ -0,0 +1,26 @@
package api
import (
"net"
"time"
)
// from net/http; sadly it's private...
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
@@ -25,7 +25,7 @@ func (t *AuthorizationURLTests) SetUpTest(c *C) {
t.pubKey = *pubKey
t.signKey = *signKey
rand.Read(t.encKey[:])
t.baseURL = "http://example.org/repo"
t.baseURL = "https://example.org/repo"
}
func (t *AuthorizationURLTests) getAuthURL() *AuthorizationURL {
View
@@ -42,7 +42,7 @@ func (d *Dispatcher) cloneAction() int {
fmt.Fprintf(d.stderr, "unable to load state config (%s)\n", err)
return 1
}
sc.DefaultServer = "http://" + u.Host + path.Dir(path.Dir(u.Path))
sc.DefaultServer = "https://" + u.Host + path.Dir(path.Dir(u.Path))
err = sc.Save()
if err != nil {
fmt.Fprintf(d.stderr, "unable to save state config (%s)\n", err)
@@ -49,7 +49,7 @@ func (t *RegisterTests) TestRegisterNoArgs(c *C) {
}
func (t *RegisterTests) TestRegisterOnlyURL(c *C) {
url := "http://127.0.0.1:14124"
url := "https://127.0.0.1:14124"
c.Assert(t.d.run([]string{"register", url}), Equals, 1)
}
View
@@ -1,14 +1,21 @@
package main
import (
"os"
"path/filepath"
"github.com/inconshreveable/log15"
"github.com/hoffie/larasync/api"
"github.com/hoffie/larasync/helpers/x509"
"github.com/hoffie/larasync/repository"
)
const (
certFileName = "larasync-server.crt"
keyFileName = "larasync-server.key"
)
// serverAction starts the server process.
func (d *Dispatcher) serverAction() int {
d.setupLogging()
@@ -27,13 +34,48 @@ func (d *Dispatcher) serverAction() int {
log.Error("repository.Manager creation failure", log15.Ctx{"error": err})
return 1
}
cfgDir := filepath.Dir(cfgPath)
certFile := filepath.Join(cfgDir, certFileName)
keyFile := filepath.Join(cfgDir, keyFileName)
err = d.needServerCert(certFile, keyFile)
if err != nil {
log.Err("unable to load/generate keys", log15.Ctx{"error": err})
return 1
}
s := api.New(*cfg.Signatures.AdminPubkeyBinary,
cfg.Signatures.MaxAge, rm)
cfg.Signatures.MaxAge, rm, certFile, keyFile)
log.Info("Listening", log15.Ctx{"address": cfg.Server.Listen})
log.Error("Error", log15.Ctx{"code": s.ListenAndServe()})
return 1
}
// needServerCert checks whether both required certificate files exist;
// if they don't, an appropriate certificate is generated
func (d *Dispatcher) needServerCert(certFile, keyFile string) error {
haveKeys, err := d.haveServerCert(certFile, keyFile)
if err != nil {
return err
}
if haveKeys {
return nil
}
return x509.GenerateServerCertFiles(certFile, keyFile)
}
// haveServerCert returns whether both required files are present.
func (d *Dispatcher) haveServerCert(certFile, keyFile string) (bool, error) {
for _, file := range []string{certFile, keyFile} {
_, err := os.Stat(file)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, err
}
}
return true, nil
}
// getServerConfigPath returns the absolute path of the server config file
func (d *Dispatcher) getServerConfigPath() (string, error) {
path := d.context.String("config")
Oops, something went wrong.

0 comments on commit 752cb04

Please sign in to comment.