Skip to content

Commit

Permalink
Implement server TLS support
Browse files Browse the repository at this point in the history
  • Loading branch information
mperham committed Feb 15, 2024
1 parent f2a00b4 commit 98268a3
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 11 deletions.
5 changes: 4 additions & 1 deletion Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

Changelog: Faktory || [Faktory Enterprise](https://github.com/contribsys/faktory/blob/master/Ent-Changes.md)

## HEAD
## 1.9.0

- Implement native TLS support [#469]
Put `public.cert.pem` and `private.key.pem` in your config directory
and Faktory will automatically enable TLS on port 7419 and 7420.
- Unwrap and display ActiveJob class names [#460, ibrahima]
- Add Go client API so batches can push jobs in bulk [#437, tylerian]

Expand Down
2 changes: 1 addition & 1 deletion client/faktory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ package client

var (
Name = "Faktory"
Version = "1.8.1"
Version = "1.9.0"
)
77 changes: 71 additions & 6 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import (
"context"
"crypto/sha256"
"crypto/subtle"
"crypto/tls"
"errors"
"fmt"
"io"
"math/rand"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
Expand All @@ -34,6 +37,10 @@ type Server struct {
Stats *RuntimeStats
Subsystems []Subsystem

TLSPublicCert string
TLSPrivateKey string

tlsConfig *tls.Config
listener net.Listener
store storage.Store
manager manager.Manager
Expand All @@ -44,6 +51,41 @@ type Server struct {
closed bool
}

func (s *Server) useTLS() error {
privateKey := filepath.Join(s.Options.ConfigDirectory, "private.key.pem")
publicCert := filepath.Join(s.Options.ConfigDirectory, "public.cert.pem")

if _, err := os.Stat(privateKey); errors.Is(err, os.ErrNotExist) {
return nil
}
if _, err := os.Stat(publicCert); errors.Is(err, os.ErrNotExist) {
return nil
}
x, err := os.Open(privateKey)
if err != nil {
return fmt.Errorf("Unable to open private key: %w", err)
}
defer x.Close()
y, err := os.Open(publicCert)
if err != nil {
return fmt.Errorf("Unable to open public cert: %w", err)
}
defer y.Close()
cert, err := tls.LoadX509KeyPair(publicCert, privateKey)
if err != nil {
return fmt.Errorf("Unable to initialize TLS certificate: %w", err)
}
util.Infof("TLS activated with %s", publicCert)

s.TLSPublicCert = publicCert
s.TLSPrivateKey = privateKey
s.tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}
return nil
}

func NewServer(opts *ServerOptions) (*Server, error) {
if opts.Binding == "" {
opts.Binding = "localhost:7419"
Expand Down Expand Up @@ -95,7 +137,17 @@ func (s *Server) Boot() error {
return fmt.Errorf("cannot open redis database: %w", err)
}

listener, err := net.Listen("tcp", s.Options.Binding)
err = s.useTLS()
if err != nil {
return err
}

var listener net.Listener
if s.tlsConfig != nil {
listener, err = tls.Listen("tcp", s.Options.Binding, s.tlsConfig)
} else {
listener, err = net.Listen("tcp", s.Options.Binding)
}
if err != nil {
store.Close()
return fmt.Errorf("cannot listen on %s: %w", s.Options.Binding, err)
Expand Down Expand Up @@ -221,19 +273,32 @@ func startConnection(conn net.Conn, s *Server) *Connection {

line, err := buf.ReadString('\n')
if err != nil {
defer conn.Close()
// TCP probes on the socket will close connection
// immediately and lead to EOF. Don't flood logs with them.
if err != io.EOF {
util.Error("Bad connection", err)
if errors.Is(err, io.EOF) {
return nil
}
conn.Close()

opErr, ok := err.(net.Error)
if ok && opErr.Timeout() {
// util.Debugf("Error establishing connection with client %v", err)
return nil
}
_, ok = err.(tls.RecordHeaderError)
if ok {
// util.Debugf("Client not using TLS %v", err)
return nil

}
util.Infof("Bad connection %T: %v", err, err)
return nil
}

valid := strings.HasPrefix(line, "HELLO {")
if !valid {
util.Infof("Invalid preamble: %s", line)
util.Info("Need a valid HELLO")
util.Debugf("Invalid preamble: %s", line)
util.Debug("Need a valid HELLO, is client using TLS?")
conn.Close()
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func runServer(binding string, runner func()) {
Binding: binding,
StorageDirectory: dir,
RedisSock: sock,
ConfigDirectory: os.ExpandEnv("$HOME/.faktory"),
ConfigDirectory: os.ExpandEnv("test/.faktory"),
}
s, err := NewServer(opts)
if err != nil {
Expand Down
10 changes: 8 additions & 2 deletions webui/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,18 @@ func (ui *WebUI) Run() (func(), error) {
}

go func() {
err := s.ListenAndServe()
var err error
if ui.Server.TLSPrivateKey != "" {
util.Infof("Web server now listening via TLS at %s", ui.Options.Binding)
err = s.ListenAndServeTLS(ui.Server.TLSPublicCert, ui.Server.TLSPrivateKey)
} else {
util.Infof("Web server now listening at %s", ui.Options.Binding)
err = s.ListenAndServe()
}
if err != http.ErrServerClosed {
util.Error(fmt.Sprintf("%s server crashed", ui.Options.Binding), err)
}
}()
util.Infof("Web server now listening at %s", ui.Options.Binding)
return func() { _ = s.Shutdown(context.Background()) }, nil
}

Expand Down

0 comments on commit 98268a3

Please sign in to comment.