/
certificate.go
128 lines (114 loc) · 3 KB
/
certificate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package secretloader
import (
"context"
"crypto/tls"
"fmt"
"log/slog"
"time"
"github.com/liquidgecka/blobby/internal/sloghelper"
)
// A Generic interface around certificate loading.
type Certificate struct {
// The Loader that will fetch the bytes needed for the public portion of
// the certificate.
Certificate Loader
// The loader that will fetch the bytes needed for the private portion
// of the certificate.
Private Loader
// All logging for the certificate manager will be done via this Logger
// object.
Logger *slog.Logger
// A cache of the certificate that was generated via the prior Load()
// call.
cert *tls.Certificate
}
// Returns the certificate loaded via the Load() call.
func (c *Certificate) Cert(ctx context.Context) (*tls.Certificate, error) {
if c.Certificate.IsStale(ctx) || c.Private.IsStale(ctx) {
if err := c.load(ctx); err != nil {
return nil, err
}
}
return c.cert, nil
}
// Returns true if this secret is expected to be pre-loaded at startup.
func (c *Certificate) PreLoad(ctx context.Context) error {
if c == nil {
return nil
} else if !c.Certificate.PreLoad(ctx) && !c.Private.PreLoad(ctx) {
return nil
}
return c.load(ctx)
}
// Starts a goroutine that will periodically refresh the data in the secret
// if configured to do so. This routine will stop processing if the passed
// in context is canceled.
func (c *Certificate) StartRefresher(ctx context.Context) {
switch {
case c == nil:
case c.Certificate != nil && c.Certificate.Stale(ctx):
case c.Private != nil && c.Private.Stale(ctx):
default:
dur := c.Certificate.CacheDuration()
if dur2 := c.Private.CacheDuration(); dur2 < dur {
dur = dur2
}
if dur > 0 {
go c.refresher(dur, ctx)
}
}
}
// Reloads the secret on an interval until the context is canceled. This
// is expected to be run as a goroutine.
func (c *Certificate) refresher(dur time.Duration, ctx context.Context) {
timer := time.NewTimer(dur)
defer func() {
if !timer.Stop() {
<-timer.C
}
}()
for {
select {
case <-ctx.Done():
return
case <-timer.C:
}
c.Logger.LogAttrs(
ctx,
slog.LevelDebug,
"Refreshing the certificate data.")
if err := c.load(ctx); err != nil {
c.Logger.LogAttrs(
ctx,
slog.LevelError,
"Error refreshing the certificate data.",
sloghelper.Error("error", err))
}
}
}
// Loads the certificate from the loaders and parses it. If this returns
// an error then the existing certificate will not be changed.
func (c *Certificate) load(ctx context.Context) error {
// Get the raw certificate bytes.
certRaw, err := c.Certificate.Fetch(ctx)
if err != nil {
return err
}
// Get the raw private key bytes.
keyRaw, err := c.Private.Fetch(ctx)
if err != nil {
return err
}
// Parse the bytes into a certificate.
if cert, err := tls.X509KeyPair(certRaw, keyRaw); err != nil {
return fmt.Errorf(
"Error loading certificate from '%s'/'%s': %s'",
c.Certificate.URL(ctx),
c.Private.URL(ctx),
err.Error())
} else {
c.cert = &cert
}
// Success
return nil
}