-
Notifications
You must be signed in to change notification settings - Fork 39
/
config.go
352 lines (313 loc) · 10.4 KB
/
config.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
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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
//
// Copyright (c) SAS Institute Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package config
import (
"crypto/x509"
"errors"
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
"github.com/sassoftware/relic/v8/lib/certloader"
)
const (
defaultSigXchg = "relic.signatures"
sigKey = "relic.signatures"
)
var (
// these are set by main
Version = "unknown"
Commit = "unknown"
Author = "SAS Institute Inc."
UserAgent = "relic/" + Version
)
type TokenConfig struct {
Type string // Provider type: file or pkcs11 (default)
Provider string // Path to PKCS#11 provider module (required)
Label string // Select a token by label
Serial string // Select a token by serial number
Pin *string // PIN to use, otherwise will be prompted. Can be empty. (optional)
Timeout int // (server) Terminate command after N seconds (default 60)
Retries int // (server) Retry failed commands N times (default 5)
RateLimit float64 // (server) limit token operations per second
RateBurst int // (server) allow burst of operations before limit kicks in
User *uint // User argument for PKCS#11 login (optional)
UseKeyring bool // Read PIN from system keyring
name string
}
type KeyConfig struct {
Token string // Token section to use for this key (linux)
Alias string // This is an alias for another key
Label string // Select a key by label
ID string // Select a key by ID (hex notation)
PgpCertificate string // Path to PGP certificate associated with this key
X509Certificate string // Path to X.509 certificate associated with this key
KeyFile string // For "file" tokens, path to the private key
IsPkcs12 bool // If true, key file contains PKCS#12 key and certificate chain
Roles []string // List of user roles that can use this key
Timestamp bool // If true, attach a timestamped countersignature when possible
Hide bool // If true, then omit this key from 'remote list-keys'
name string
token *TokenConfig
}
type ServerConfig struct {
Listen string // Port to listen for TLS connections
ListenHTTP string // Port to listen for plaintext connections
KeyFile string // Path to TLS key file
CertFile string // Path to TLS certificate chain
LogFile string // Optional error log
LogLevel string // Optional log level
PolicyURL string // Optional open-policy-agent endpoint
Disabled bool // Always return 503 Service Unavailable
ListenDebug bool // Serve debug info on an alternate port
ListenMetrics string // Port to listen for plaintext metrics
NumWorkers int // Number of worker subprocesses per configured token
TokenCheckInterval int
TokenCheckFailures int
TokenCheckTimeout int
TokenCacheSeconds int
ReadHeaderTimeout int
ReadTimeout int
WriteTimeout int
// URLs to all servers in the cluster. If a client uses DirectoryURL to
// point to this server (or a load balancer), then we will give them these
// URLs as a means to distribute load without needing a middle-box.
Siblings []string
// IP networks of trusted reverse proxies that can front this service
TrustedProxies []string
AzureAD *ServerAzureConfig
}
type ServerAzureConfig struct {
Authority string
ClientID string
Scopes []string
}
type ClientConfig struct {
Nickname string // Name that appears in audit log entries
Roles []string // List of roles that this client possesses
Certificate string // Optional CA certificate(s) that sign client certs instead of using fingerprint-based auth
certs *x509.CertPool
}
type RemoteConfig struct {
URL string `yaml:",omitempty"` // URL of remote server
DirectoryURL string `yaml:",omitempty"` // URL of directory server
KeyFile string `yaml:",omitempty"` // Path to TLS client key file
CertFile string `yaml:",omitempty"` // Path to TLS client certificate or embedded certificate
CaCert string `yaml:",omitempty"` // Path to CA certificate or embedded certificate
ConnectTimeout int `yaml:",omitempty"` // Connection timeout in seconds
Retries int `yaml:",omitempty"` // Attempt an operation (at least) N times
AccessToken string `yaml:"-"`
Interactive bool
}
type TimestampConfig struct {
URLs []string // List of timestamp server URLs
MsURLs []string // List of microsoft-style URLs
Timeout int // Connect timeout in seconds
CaCert string // Path to CA certificate
Memcache []string // host:port of memcached to use for caching timestamps
RateLimit float64 // limit timestamp requests per second
RateBurst int // allow burst of requests before limit kicks in
}
type AmqpConfig struct {
URL string // AMQP URL to report signatures to i.e. amqp://user:password@host
CaCert string
KeyFile string
CertFile string
SigsXchg string // Name of exchange to send to (default relic.signatures)
}
type Config struct {
Tokens map[string]*TokenConfig `yaml:",omitempty"`
Keys map[string]*KeyConfig `yaml:",omitempty"`
Server *ServerConfig `yaml:",omitempty"`
Clients map[string]*ClientConfig `yaml:",omitempty"`
Remote *RemoteConfig `yaml:",omitempty"`
Timestamp *TimestampConfig `yaml:",omitempty"`
Amqp *AmqpConfig `yaml:",omitempty"`
AuditFile string `yaml:",omitempty"` // Optional log file for signatures
PinFile string `yaml:",omitempty"` // Optional YAML file with additional token PINs
path string
}
func ReadFile(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
config := new(Config)
err = yaml.Unmarshal(data, config)
if err != nil {
return nil, err
}
return config, config.Normalize(path)
}
func (config *Config) Normalize(path string) error {
config.path = path
normalized := make(map[string]*ClientConfig)
for fingerprint, client := range config.Clients {
if client.Certificate != "" {
certs, err := certloader.ParseX509Certificates([]byte(client.Certificate))
if err != nil {
return fmt.Errorf("invalid certificate for client %s: %w", fingerprint, err)
}
client.certs = x509.NewCertPool()
for _, cert := range certs {
client.certs.AddCert(cert)
}
} else if len(fingerprint) != 64 {
return errors.New("Client keys must be hex-encoded SHA256 digests of the public key")
}
lower := strings.ToLower(fingerprint)
normalized[lower] = client
}
config.Clients = normalized
if config.PinFile != "" {
contents, err := os.ReadFile(config.PinFile)
if err != nil {
return fmt.Errorf("reading PinFile: %w", err)
}
pinMap := make(map[string]string)
if err := yaml.Unmarshal(contents, pinMap); err != nil {
return fmt.Errorf("reading PinFile: %w", err)
}
for token, pin := range pinMap {
tokenConf := config.Tokens[token]
if tokenConf != nil {
ppin := pin
tokenConf.Pin = &ppin
}
}
}
for tokenName, tokenConf := range config.Tokens {
tokenConf.name = tokenName
if tokenConf.Type == "" {
tokenConf.Type = "pkcs11"
}
}
for keyName, keyConf := range config.Keys {
keyConf.name = keyName
if keyConf.Token != "" {
keyConf.token = config.Tokens[keyConf.Token]
}
}
if s := config.Server; s != nil {
if s.TokenCheckInterval == 0 {
s.TokenCheckInterval = 60
}
if s.TokenCheckTimeout == 0 {
s.TokenCheckTimeout = 60
}
if s.TokenCheckFailures == 0 {
s.TokenCheckFailures = 3
}
if s.TokenCacheSeconds == 0 {
s.TokenCacheSeconds = 600
}
if s.ReadHeaderTimeout == 0 {
s.ReadHeaderTimeout = 10
}
if s.ReadTimeout == 0 {
s.ReadTimeout = 600
}
if s.WriteTimeout == 0 {
s.WriteTimeout = 600
}
}
if r := config.Remote; r != nil {
if r.ConnectTimeout == 0 {
r.ConnectTimeout = 15
}
if r.Retries == 0 {
r.Retries = 3
}
}
return nil
}
func (config *Config) GetToken(tokenName string) (*TokenConfig, error) {
if config.Tokens == nil {
return nil, errors.New("No tokens defined in configuration")
}
tokenConf, ok := config.Tokens[tokenName]
if !ok {
return nil, fmt.Errorf("Token \"%s\" not found in configuration", tokenName)
}
return tokenConf, nil
}
func (config *Config) NewToken(name string) *TokenConfig {
if config.Tokens == nil {
config.Tokens = make(map[string]*TokenConfig)
}
tok := &TokenConfig{name: name}
config.Tokens[name] = tok
return tok
}
func (config *Config) GetKey(keyName string) (*KeyConfig, error) {
keyConf, ok := config.Keys[keyName]
if !ok {
return nil, fmt.Errorf("Key \"%s\" not found in configuration", keyName)
} else if keyConf.Alias != "" {
keyConf, ok = config.Keys[keyConf.Alias]
if !ok {
return nil, fmt.Errorf("Alias \"%s\" points to undefined key \"%s\"", keyName, keyConf.Alias)
}
}
if keyConf.Token == "" {
return nil, fmt.Errorf("Key \"%s\" does not specify required value 'token'", keyName)
}
return keyConf, nil
}
func (config *Config) NewKey(name string) *KeyConfig {
if config.Keys == nil {
config.Keys = make(map[string]*KeyConfig)
}
key := &KeyConfig{name: name}
config.Keys[name] = key
return key
}
func (config *Config) Path() string {
return config.path
}
func (config *Config) GetTimestampConfig() (*TimestampConfig, error) {
tconf := config.Timestamp
if tconf == nil {
return nil, errors.New("No timestamp section exists in the configuration")
}
return tconf, nil
}
// ListServedTokens returns a list of token names that are accessible by at least one role
func (config *Config) ListServedTokens() []string {
names := make(map[string]bool)
for _, key := range config.Keys {
if len(key.Roles) != 0 {
names[key.Token] = true
}
}
ret := make([]string, 0, len(names))
for name := range names {
ret = append(ret, name)
}
return ret
}
func (tconf *TokenConfig) Name() string {
return tconf.name
}
func (aconf *AmqpConfig) ExchangeName() string {
if aconf.SigsXchg != "" {
return aconf.SigsXchg
}
return defaultSigXchg
}
func (aconf *AmqpConfig) RoutingKey() string {
return sigKey
}