Skip to content

Commit

Permalink
Merge branch 'rdpgw-cert-hot-reload' into 'main'
Browse files Browse the repository at this point in the history
feat(rdpgw): hot reload the tls certificate

See merge request isard/isardvdi!1627
  • Loading branch information
NefixEstrada committed Dec 12, 2022
2 parents c68c895 + add705a commit 8f79bf6
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 8 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.16
require (
github.com/bolkedebruin/rdpgw v1.0.4
github.com/crewjam/saml v0.4.6
github.com/fsnotify/fsnotify v1.6.0
github.com/go-ldap/ldap/v3 v3.4.1
github.com/golang-jwt/jwt v3.2.2+incompatible
// https://github.com/grafana/loki/issues/2826. This is the equivalent of 2.4.2
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,9 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
Expand Down Expand Up @@ -2508,8 +2509,9 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
Expand Down
6 changes: 3 additions & 3 deletions pkg/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ func New(name string, setDefaults func(), target interface{}) {

if err := viper.ReadInConfig(); err != nil {
if !errors.As(err, &viper.ConfigFileNotFoundError{}) {
log.Fatal().Err(err).Msg("read configuration")
log.Fatal().Str("service", name).Err(err).Msg("read configuration")
}

log.Warn().Msg("Configuration file not found, using environment variables and defaults")
log.Warn().Str("service", name).Msg("Configuration file not found, using environment variables and defaults")
}

if err := viper.Unmarshal(target); err != nil {
log.Fatal().Err(err).Msg("unmarshal configuration")
log.Fatal().Str("service", name).Err(err).Msg("unmarshal configuration")
}

}
Expand Down
109 changes: 109 additions & 0 deletions pkg/tls/reloader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package tls

// Copied between https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/certwatcher/certwatcher.go and https://stackoverflow.com/a/40883377

import (
"context"
"crypto/tls"
"fmt"
"sync"

"github.com/fsnotify/fsnotify"
"github.com/rs/zerolog"
)

type keypairReloader struct {
mux sync.RWMutex
watcher *fsnotify.Watcher

cert *tls.Certificate

certPath string
keyPath string
}

func NewKeyPairReloader(certPath, keyPath string) (*keypairReloader, error) {
kpr := &keypairReloader{
certPath: certPath,
keyPath: keyPath,
}

if err := kpr.ReadCertificate(); err != nil {
return nil, err
}

watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, fmt.Errorf("create certificate filesystem watcher: %w", err)
}

kpr.watcher = watcher

return kpr, nil
}

func (kpr *keypairReloader) Start(ctx context.Context, log *zerolog.Logger) error {
files := []string{kpr.certPath, kpr.keyPath}
for _, f := range files {
if err := kpr.watcher.Add(f); err != nil {
return fmt.Errorf("add '%s' to the filesystem watcher: %w", f, err)
}
}

go func() {
for {
select {
case event, ok := <-kpr.watcher.Events:
if !ok {
return
}

if event.Has(fsnotify.Remove) {
if err := kpr.watcher.Add(event.Name); err != nil {
log.Error().Err(err).Msg("rewatch certificate changes")
}

} else if !event.Has(fsnotify.Create) && !event.Has(fsnotify.Write) {
continue
}

if err := kpr.ReadCertificate(); err != nil {
log.Error().Err(err).Msg("reload certificate")
}

log.Info().Msg("tls certificate reloaded")

case err, ok := <-kpr.watcher.Errors:
if !ok {
return
}

log.Error().Err(err).Msg("certificate filesystem watch error")
}
}
}()

<-ctx.Done()

return kpr.watcher.Close()
}

func (kpr *keypairReloader) ReadCertificate() error {
kpr.mux.Lock()
defer kpr.mux.Unlock()

cert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath)
if err != nil {
return fmt.Errorf("read tls certificate: %w", err)
}
kpr.cert = &cert

return nil
}

func (kpr *keypairReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
kpr.mux.RLock()
defer kpr.mux.RUnlock()

return kpr.cert, nil
}
30 changes: 27 additions & 3 deletions rdpgw/transport/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@ package http

import (
"context"
"crypto/tls"
"net/http"
"sync"
"time"

tlsReloader "gitlab.com/isard/isardvdi/pkg/tls"

"github.com/bolkedebruin/rdpgw/common"
"github.com/bolkedebruin/rdpgw/protocol"
"github.com/rs/zerolog"
)

const (
certPath = "/portal-certs/chain.pem"
keyPath = "/portal-certs/chain.pem"
)

type RDPGwServer struct {
Addr string
Gateway *protocol.Gateway
Expand All @@ -23,13 +31,29 @@ func (r *RDPGwServer) Serve(ctx context.Context) {
m := http.NewServeMux()
m.Handle("/remoteDesktopGateway/", common.EnrichContext(http.HandlerFunc(r.Gateway.HandleGatewayProtocol)))

kpr, err := tlsReloader.NewKeyPairReloader(certPath, keyPath)
if err != nil {
r.Log.Fatal().Err(err).Msg("create tls certificate reloader")
}

tlsCfg := &tls.Config{
GetCertificate: kpr.GetCertificate,
}

s := http.Server{
Addr: r.Addr,
Handler: m,
Addr: r.Addr,
Handler: m,
TLSConfig: tlsCfg,
}

go func() {
if err := s.ListenAndServeTLS("/portal-certs/chain.pem", "/portal-certs/chain.pem"); err != nil {
if err := kpr.Start(ctx, r.Log); err != nil {
r.Log.Fatal().Err(err).Msg("start certificate reloader")
}
}()

go func() {
if err := s.ListenAndServeTLS("", ""); err != nil {
r.Log.Fatal().Err(err).Str("addr", r.Addr).Msg("serve http")
}
}()
Expand Down

0 comments on commit 8f79bf6

Please sign in to comment.