forked from adamlaska/boulder
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.go
More file actions
333 lines (299 loc) · 10.3 KB
/
config.go
File metadata and controls
333 lines (299 loc) · 10.3 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package cmd
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"hash/fnv"
"io/ioutil"
"math"
"os"
"path"
"strings"
"time"
"github.com/go-sql-driver/mysql"
"github.com/honeycombio/beeline-go"
"github.com/letsencrypt/boulder/core"
)
// PasswordConfig contains a path to a file containing a password.
type PasswordConfig struct {
PasswordFile string
}
// Pass returns a password, extracted from the PasswordConfig's PasswordFile
func (pc *PasswordConfig) Pass() (string, error) {
// Make PasswordConfigs optional, for backwards compatibility.
if pc.PasswordFile == "" {
return "", nil
}
contents, err := ioutil.ReadFile(pc.PasswordFile)
if err != nil {
return "", err
}
return strings.TrimRight(string(contents), "\n"), nil
}
// ServiceConfig contains config items that are common to all our services, to
// be embedded in other config structs.
type ServiceConfig struct {
// DebugAddr is the address to run the /debug handlers on.
DebugAddr string
GRPC *GRPCServerConfig
TLS TLSConfig
}
// DBConfig defines how to connect to a database. The connect string may be
// stored in a file separate from the config, because it can contain a password,
// which we want to keep out of configs.
type DBConfig struct {
DBConnect string
// A file containing a connect URL for the DB.
DBConnectFile string
// MaxOpenConns sets the maximum number of open connections to the
// database. If MaxIdleConns is greater than 0 and MaxOpenConns is
// less than MaxIdleConns, then MaxIdleConns will be reduced to
// match the new MaxOpenConns limit. If n < 0, then there is no
// limit on the number of open connections.
MaxOpenConns int
// MaxIdleConns sets the maximum number of connections in the idle
// connection pool. If MaxOpenConns is greater than 0 but less than
// MaxIdleConns, then MaxIdleConns will be reduced to match the
// MaxOpenConns limit. If n < 0, no idle connections are retained.
MaxIdleConns int
// ConnMaxLifetime sets the maximum amount of time a connection may
// be reused. Expired connections may be closed lazily before reuse.
// If d < 0, connections are not closed due to a connection's age.
ConnMaxLifetime ConfigDuration
// ConnMaxIdleTime sets the maximum amount of time a connection may
// be idle. Expired connections may be closed lazily before reuse.
// If d < 0, connections are not closed due to a connection's idle
// time.
ConnMaxIdleTime ConfigDuration
}
// URL returns the DBConnect URL represented by this DBConfig object, either
// loading it from disk or returning a default value. Leading and trailing
// whitespace is stripped.
func (d *DBConfig) URL() (string, error) {
if d.DBConnectFile != "" {
url, err := ioutil.ReadFile(d.DBConnectFile)
return strings.TrimSpace(string(url)), err
}
return d.DBConnect, nil
}
// DSNAddressAndUser returns the Address and User of the DBConnect DSN from
// this object.
func (d *DBConfig) DSNAddressAndUser() (string, string, error) {
dsnStr, err := d.URL()
if err != nil {
return "", "", fmt.Errorf("failed to load DBConnect URL: %s", err)
}
config, err := mysql.ParseDSN(dsnStr)
if err != nil {
return "", "", fmt.Errorf("failed to parse DSN from the DBConnect URL: %s", err)
}
return config.Addr, config.User, nil
}
type SMTPConfig struct {
PasswordConfig
Server string
Port string
Username string
}
// PAConfig specifies how a policy authority should connect to its
// database, what policies it should enforce, and what challenges
// it should offer.
type PAConfig struct {
DBConfig
Challenges map[core.AcmeChallenge]bool
}
// CheckChallenges checks whether the list of challenges in the PA config
// actually contains valid challenge names
func (pc PAConfig) CheckChallenges() error {
if len(pc.Challenges) == 0 {
return errors.New("empty challenges map in the Policy Authority config is not allowed")
}
for c := range pc.Challenges {
if !c.IsValid() {
return fmt.Errorf("Invalid challenge in PA config: %s", c)
}
}
return nil
}
// HostnamePolicyConfig specifies a file from which to load a policy regarding
// what hostnames to issue for.
type HostnamePolicyConfig struct {
HostnamePolicyFile string
}
// TLSConfig represents certificates and a key for authenticated TLS.
type TLSConfig struct {
CertFile *string
KeyFile *string
CACertFile *string
}
// Load reads and parses the certificates and key listed in the TLSConfig, and
// returns a *tls.Config suitable for either client or server use.
func (t *TLSConfig) Load() (*tls.Config, error) {
if t == nil {
return nil, fmt.Errorf("nil TLS section in config")
}
if t.CertFile == nil {
return nil, fmt.Errorf("nil CertFile in TLSConfig")
}
if t.KeyFile == nil {
return nil, fmt.Errorf("nil KeyFile in TLSConfig")
}
if t.CACertFile == nil {
return nil, fmt.Errorf("nil CACertFile in TLSConfig")
}
caCertBytes, err := ioutil.ReadFile(*t.CACertFile)
if err != nil {
return nil, fmt.Errorf("reading CA cert from %q: %s", *t.CACertFile, err)
}
rootCAs := x509.NewCertPool()
if ok := rootCAs.AppendCertsFromPEM(caCertBytes); !ok {
return nil, fmt.Errorf("parsing CA certs from %s failed", *t.CACertFile)
}
cert, err := tls.LoadX509KeyPair(*t.CertFile, *t.KeyFile)
if err != nil {
return nil, fmt.Errorf("loading key pair from %q and %q: %s",
*t.CertFile, *t.KeyFile, err)
}
return &tls.Config{
RootCAs: rootCAs,
ClientCAs: rootCAs,
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{cert},
// Set the only acceptable TLS version to 1.2 and the only acceptable cipher suite
// to ECDHE-RSA-CHACHA20-POLY1305.
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS12,
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
}, nil
}
// SyslogConfig defines the config for syslogging.
type SyslogConfig struct {
StdoutLevel int
SyslogLevel int
}
// ConfigDuration is just an alias for time.Duration that allows
// serialization to YAML as well as JSON.
type ConfigDuration struct {
time.Duration
}
// ErrDurationMustBeString is returned when a non-string value is
// presented to be deserialized as a ConfigDuration
var ErrDurationMustBeString = errors.New("cannot JSON unmarshal something other than a string into a ConfigDuration")
// UnmarshalJSON parses a string into a ConfigDuration using
// time.ParseDuration. If the input does not unmarshal as a
// string, then UnmarshalJSON returns ErrDurationMustBeString.
func (d *ConfigDuration) UnmarshalJSON(b []byte) error {
s := ""
err := json.Unmarshal(b, &s)
if err != nil {
var jsonUnmarshalTypeErr *json.UnmarshalTypeError
if errors.As(err, &jsonUnmarshalTypeErr) {
return ErrDurationMustBeString
}
return err
}
dd, err := time.ParseDuration(s)
d.Duration = dd
return err
}
// MarshalJSON returns the string form of the duration, as a byte array.
func (d ConfigDuration) MarshalJSON() ([]byte, error) {
return []byte(d.Duration.String()), nil
}
// UnmarshalYAML uses the same format as JSON, but is called by the YAML
// parser (vs. the JSON parser).
func (d *ConfigDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
err := unmarshal(&s)
if err != nil {
return err
}
dur, err := time.ParseDuration(s)
if err != nil {
return err
}
d.Duration = dur
return nil
}
// GRPCClientConfig contains the information needed to talk to the gRPC service
type GRPCClientConfig struct {
ServerAddress string
Timeout ConfigDuration
}
// GRPCServerConfig contains the information needed to run a gRPC service
type GRPCServerConfig struct {
Address string `json:"address"`
// ClientNames is a list of allowed client certificate subject alternate names
// (SANs). The server will reject clients that do not present a certificate
// with a SAN present on the `ClientNames` list.
ClientNames []string `json:"clientNames"`
// MaxConnectionAge specifies how long a connection may live before the server sends a GoAway to the
// client. Because gRPC connections re-resolve DNS after a connection close,
// this controls how long it takes before a client learns about changes to its
// backends.
// https://pkg.go.dev/google.golang.org/grpc/keepalive#ServerParameters
MaxConnectionAge ConfigDuration
}
// PortConfig specifies what ports the VA should call to on the remote
// host when performing its checks.
type PortConfig struct {
HTTPPort int
HTTPSPort int
TLSPort int
}
// BeelineConfig provides config options for the Honeycomb beeline-go library,
// which are passed to its beeline.Init() method.
type BeelineConfig struct {
// WriteKey is the API key needed to send data Honeycomb. This can be given
// directly in the JSON config for local development, or as a path to a
// separate file for production deployment.
WriteKey PasswordConfig
// Dataset is the event collection, e.g. Staging or Prod.
Dataset string
// SampleRate is the (positive integer) denominator of the sample rate.
// Default: 1 (meaning all traces are sent). Set higher to send fewer traces.
SampleRate uint32
// Mute disables honeycomb entirely; useful in test environments.
Mute bool
// Many other fields of beeline.Config are omitted as they are not yet used.
}
// makeSampler constructs a SamplerHook which will deterministically decide if
// any given span should be sampled based on its TraceID, which is shared by all
// spans within a trace. If a trace_id can't be found, the span will be sampled.
// A sample rate of 0 defaults to a sample rate of 1 (i.e. all events are sent).
func makeSampler(rate uint32) func(fields map[string]interface{}) (bool, int) {
if rate == 0 {
rate = 1
}
upperBound := math.MaxUint32 / rate
return func(fields map[string]interface{}) (bool, int) {
id, ok := fields["trace.trace_id"].(string)
if !ok {
return true, 1
}
h := fnv.New32()
h.Write([]byte(id))
return h.Sum32() < upperBound, int(rate)
}
}
// Load converts a BeelineConfig to a beeline.Config, loading the api WriteKey
// and setting the ServiceName automatically.
func (bc *BeelineConfig) Load() (beeline.Config, error) {
exec, err := os.Executable()
if err != nil {
return beeline.Config{}, fmt.Errorf("failed to get executable name: %w", err)
}
writekey, err := bc.WriteKey.Pass()
if err != nil {
return beeline.Config{}, fmt.Errorf("failed to get write key: %w", err)
}
return beeline.Config{
WriteKey: writekey,
Dataset: bc.Dataset,
ServiceName: path.Base(exec),
SamplerHook: makeSampler(bc.SampleRate),
Mute: bc.Mute,
}, nil
}