Skip to content

Commit

Permalink
Wait for certificate before starting HTTP listener
Browse files Browse the repository at this point in the history
When starting up, the HTTP server can start accepting requests before
the TLS config has loaded certificates from the certificate generator.

We add in a wait (assuming we don't have certificates already given to
us via flags or environment variables) for that certificate to be
generated before starting the server to prevent confusing, though
harmless, errors in logs.

* Fixes #318
* Fixes #320
  • Loading branch information
Christopher Swenson committed May 24, 2022
1 parent 676ee9f commit b6485fe
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 8 deletions.
21 changes: 16 additions & 5 deletions helper/cert/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ package cert

import (
"context"
"sync"
"time"

"github.com/hashicorp/go-hclog"
)

func NewNotify(ctx context.Context, newBundle chan<- Bundle, source Source, logger hclog.Logger) *Notify {
func NewNotify(ctx context.Context, newBundle chan<- Bundle, notifyOnce chan<- bool, source Source, logger hclog.Logger) *Notify {
return &Notify{
ctx: ctx,
ch: newBundle,
source: source,
logger: logger,
ctx: ctx,
ch: newBundle,
notifyOnce: notifyOnce,
source: source,
logger: logger,
}
}

Expand All @@ -26,6 +28,8 @@ type Notify struct {
// blocks then the notify loop will also be blocked, so downstream
// users should process this channel in a timely manner.
ch chan<- Bundle
// used to notify the first time it is run
notifyOnce chan<- bool

// source is the source of certificates.
source Source
Expand All @@ -39,6 +43,7 @@ func (n *Notify) Run() {
retryTicker := time.NewTicker(5 * time.Second)
defer retryTicker.Stop()
first := true
once := sync.Once{}
for {
if first {
// On the first pass we want to check for the cert immediately.
Expand All @@ -58,6 +63,12 @@ func (n *Notify) Run() {
continue
}

once.Do(func() {
go func() {
n.notifyOnce <- true
}()
})

// If the returned bundles are equal, then ignore it.
if last.Equal(&next) {
continue
Expand Down
20 changes: 18 additions & 2 deletions helper/cert/notify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ func TestNotify(t *testing.T) {

// Create notifier
ch := make(chan Bundle)
n := NewNotify(context.Background(), ch, source, hclog.NewNullLogger())
notifyOnce := make(chan bool)
n := NewNotify(context.Background(), ch, notifyOnce, source, hclog.NewNullLogger())
go n.Run()

// We should receive an update almost immediately
Expand All @@ -37,6 +38,19 @@ func TestNotify(t *testing.T) {
t.Fatal("should not receive update")
}

// we should receive the once notification
select {
case <-time.After(250 * time.Millisecond):
t.Fatal("should've received once notification")
case <-notifyOnce:
}
// ... just once
select {
case <-time.After(250 * time.Millisecond):
case <-notifyOnce:
t.Fatal("should not receive second notification on notifyOnce channel")
}

b := <-ch
testBundleVerify(t, &b)
}
Expand All @@ -54,6 +68,8 @@ func TestNotifyRace(t *testing.T) {
// giving races a chance to be detected.
done := make(chan interface{})

notifyOnce := make(chan bool)

for i := 0; i < numParallel; i++ {

// Use one background context for everything since that could happen
Expand All @@ -68,7 +84,7 @@ func TestNotifyRace(t *testing.T) {
Hosts: []string{"some", "hosts"},
Log: hclog.Default(),
}
n := NewNotify(ctx, certCh, certSource, hclog.NewNullLogger())
n := NewNotify(ctx, certCh, notifyOnce, certSource, hclog.NewNullLogger())

go func() {
<-start
Expand Down
15 changes: 14 additions & 1 deletion subcommand/injector/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ func (c *Command) Run(args []string) int {
// Create the certificate notifier so we can update for certificates,
// then start all the background routines for updating certificates.
certCh := make(chan cert.Bundle)
certNotify := cert.NewNotify(ctx, certCh, certSource, logger.Named("notify"))
certReady := make(chan bool)
certNotify := cert.NewNotify(ctx, certCh, certReady, certSource, logger.Named("notify"))
go certNotify.Run()
go c.certWatcher(ctx, certCh, clientset, leaderElector, logger.Named("certwatcher"))

Expand Down Expand Up @@ -222,6 +223,18 @@ func (c *Command) Run(args []string) int {
c.UI.Error(fmt.Sprintf("Failed to configure TLS: %s", err))
return 1
}

// if we don't have a certificate given to us, wait for cert to generate to prevent
// TLS errors on startup
if c.flagCertFile == "" && c.flagKeyFile == "" {
<-certReady
} else {
// otherwise drain async just to clean up the sending goroutine
go func() {
<-certReady
}()
}

server := &http.Server{
Addr: c.flagListen,
Handler: handler,
Expand Down

0 comments on commit b6485fe

Please sign in to comment.