-
Notifications
You must be signed in to change notification settings - Fork 124
/
conf.go
231 lines (199 loc) · 8.68 KB
/
conf.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
// Copyright 2021-2023 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0
package server
import (
"encoding/base64"
"errors"
"fmt"
"strconv"
"time"
"go.uber.org/multierr"
"golang.org/x/crypto/bcrypt"
"github.com/cerbos/cerbos/internal/config"
"github.com/cerbos/cerbos/internal/util"
)
const (
confKey = "server"
defaultAdminPassword = "cerbosAdmin"
defaultAdminUsername = "cerbos"
defaultGRPCConnectionTimeout = 60 * time.Second
defaultGRPCListenAddr = ":3593"
defaultGRPCMaxConnectionAge = 10 * time.Minute
defaultGRPCMaxRecvMsgSizeBytes = 4 * 1024 * 1024 // 4MiB
defaultHTTPIdleTimeout = 120 * time.Second
defaultHTTPListenAddr = ":3592"
defaultHTTPReadHeaderTimeout = 15 * time.Second
defaultHTTPReadTimeout = 30 * time.Second
defaultHTTPWriteTimeout = 30 * time.Second
defaultMaxActionsPerResource = 50
defaultMaxResourcesPerRequest = 50
defaultRawAdminPasswordHash = "$2y$10$VlPwcwpgcGZ5KjTaN1Pzk.vpFiQVG6F2cSWzQa9RtrNo3IacbzsEi" //nolint:gosec
defaultUDSFileMode = "0o766"
requestItemsMax = 500
)
var (
defaultAdminPasswordHash = base64.StdEncoding.EncodeToString([]byte(defaultRawAdminPasswordHash))
errAdminCredsUndefined = errors.New("admin credentials not defined")
)
// Conf is required configuration for the server.
type Conf struct {
// TLS defines the TLS configuration for the server.
TLS *TLSConf `yaml:"tls"`
// AdminAPI defines the admin API configuration.
AdminAPI AdminAPIConf `yaml:"adminAPI"`
// HTTPListenAddr is the dedicated HTTP address.
HTTPListenAddr string `yaml:"httpListenAddr" conf:"required,example=\":3592\""`
// GRPCListenAddr is the dedicated GRPC address.
GRPCListenAddr string `yaml:"grpcListenAddr" conf:"required,example=\":3593\""`
// UDSFileMode sets the file mode of the unix domain sockets created by the server.
UDSFileMode string `yaml:"udsFileMode" conf:",example=0o766"`
// CORS defines the CORS configuration for the server.
CORS CORSConf `yaml:"cors"`
// RequestLimits defines the limits for requests.
RequestLimits RequestLimitsConf `yaml:"requestLimits"`
// MetricsEnabled defines whether the metrics endpoint is enabled.
MetricsEnabled bool `yaml:"metricsEnabled" conf:",example=true"`
// LogRequestPayloads defines whether the request payloads should be logged.
LogRequestPayloads bool `yaml:"logRequestPayloads" conf:",example=false"`
// PlaygroundEnabled defines whether the playground API is enabled.
PlaygroundEnabled bool `yaml:"playgroundEnabled" conf:",example=false"`
// Advanced server settings.
Advanced AdvancedConf `yaml:"advanced"`
}
// TLSConf holds TLS configuration.
type TLSConf struct {
// Cert is the path to the TLS certificate file.
Cert string `yaml:"cert" conf:",example=/path/to/certificate"`
// Key is the path to the TLS private key file.
Key string `yaml:"key" conf:",example=/path/to/private_key"`
// CACert is the path to the optional CA certificate for verifying client requests.
CACert string `yaml:"caCert" conf:",example=/path/to/CA_certificate"`
}
type CORSConf struct {
// AllowedOrigins is the contents of the allowed-origins header.
AllowedOrigins []string `yaml:"allowedOrigins" conf:",example=['*']"`
// AllowedHeaders is the contents of the allowed-headers header.
AllowedHeaders []string `yaml:"allowedHeaders" conf:",example=['content-type']"`
// Disabled sets whether CORS is disabled.
Disabled bool `yaml:"disabled" conf:",example=false"`
// MaxAge is the max age of the CORS preflight check.
MaxAge time.Duration `yaml:"maxAge" conf:",example=10s"`
}
type AdminAPIConf struct {
// AdminCredentials defines the admin user credentials.
AdminCredentials *AdminCredentialsConf `yaml:"adminCredentials"`
// Enabled defines whether the admin API is enabled.
Enabled bool `yaml:"enabled" conf:",example=true"`
}
type AdminCredentialsConf struct {
// Username is the hardcoded username to use for authentication.
Username string `yaml:"username" conf:",example=cerbos"`
// PasswordHash is the base64-encoded bcrypt hash of the password to use for authentication.
PasswordHash string `yaml:"passwordHash" conf:",example=JDJ5JDEwJEdEOVFzZDE2VVhoVkR0N2VkUFBVM09nalc0QnNZaC9xc2E4bS9mcUJJcEZXenp5OUpjMi91Cgo="`
}
func (a *AdminCredentialsConf) usernameAndPasswordHash() (string, []byte, error) {
if a == nil {
return "", nil, errAdminCredsUndefined
}
passwordHashBytes, err := base64.StdEncoding.DecodeString(a.PasswordHash)
if err != nil {
return "", nil, fmt.Errorf("failed to base64 decode admin passwordHash: %w", err)
}
return a.Username, passwordHashBytes, nil
}
func adminCredentialsAreUnsafe(passwordHash []byte) (bool, error) {
err := bcrypt.CompareHashAndPassword(passwordHash, []byte(defaultAdminPassword))
if err == nil {
return true, nil
} else if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return false, nil
}
return false, err
}
type RequestLimitsConf struct {
// MaxActionsPerResource sets the maximum number of actions that could be checked for a resource in a single request.
MaxActionsPerResource uint `yaml:"maxActionsPerResource" conf:",example=50"`
// MaxResourcesPerBatch sets the maximum number of resources that could be sent in a single request.
MaxResourcesPerRequest uint `yaml:"maxResourcesPerRequest" conf:",example=50"`
}
type AdvancedConf struct {
// HTTP server settings.
HTTP AdvancedHTTPConf `yaml:"http"`
// GRPC server settings.
GRPC AdvancedGRPCConf `yaml:"grpc"`
}
type AdvancedHTTPConf struct {
// ReadTimeout sets the timeout for reading a request.
ReadTimeout time.Duration `yaml:"readTimeout" conf:",example=30s"`
// ReadHeaderTimeout sets the timeout for reading request headers.
ReadHeaderTimeout time.Duration `yaml:"readHeaderTimeout" conf:",example=15s"`
// WriteTimeout sets the timeout for writing a response.
WriteTimeout time.Duration `yaml:"writeTimeout" conf:",example=30s"`
// IdleTimeout sets the keepalive timeout.
IdleTimeout time.Duration `yaml:"idleTimeout" conf:",example=120s"`
}
type AdvancedGRPCConf struct {
// MaxRecvMsgSizeBytes sets the maximum size of a single request message. Defaults to 4MiB. Affects performance and resource utilisation.
MaxRecvMsgSizeBytes uint `yaml:"maxRecvMsgSizeBytes" conf:",example=4194304"`
// MaxConnectionAge sets the maximum age of a connection.
MaxConnectionAge time.Duration `yaml:"maxConnectionAge" conf:",example=600s"`
// ConnectionTimeout sets the timeout for establishing a new connection.
ConnectionTimeout time.Duration `yaml:"connectionTimeout" conf:",example=60s"`
}
func (c *Conf) Key() string {
return confKey
}
func (c *Conf) SetDefaults() {
c.HTTPListenAddr = defaultHTTPListenAddr
c.GRPCListenAddr = defaultGRPCListenAddr
c.MetricsEnabled = true
c.UDSFileMode = defaultUDSFileMode
c.RequestLimits = RequestLimitsConf{
MaxActionsPerResource: defaultMaxActionsPerResource,
MaxResourcesPerRequest: defaultMaxResourcesPerRequest,
}
if c.AdminAPI.AdminCredentials == nil {
c.AdminAPI.AdminCredentials = &AdminCredentialsConf{
Username: defaultAdminUsername,
PasswordHash: defaultAdminPasswordHash,
}
}
c.Advanced = AdvancedConf{
HTTP: AdvancedHTTPConf{
ReadTimeout: defaultHTTPReadTimeout,
ReadHeaderTimeout: defaultHTTPReadHeaderTimeout,
WriteTimeout: defaultHTTPWriteTimeout,
IdleTimeout: defaultHTTPIdleTimeout,
},
GRPC: AdvancedGRPCConf{
MaxRecvMsgSizeBytes: defaultGRPCMaxRecvMsgSizeBytes,
MaxConnectionAge: defaultGRPCMaxConnectionAge,
ConnectionTimeout: defaultGRPCConnectionTimeout,
},
}
}
func (c *Conf) Validate() (errs error) {
if _, _, err := util.ParseListenAddress(c.HTTPListenAddr); err != nil {
errs = multierr.Append(errs, fmt.Errorf("invalid httpListenAddr '%s': %w", c.HTTPListenAddr, err))
}
if _, _, err := util.ParseListenAddress(c.GRPCListenAddr); err != nil {
errs = multierr.Append(errs, fmt.Errorf("invalid grpcListenAddr '%s': %w", c.GRPCListenAddr, err))
}
if mode, err := strconv.ParseInt(c.UDSFileMode, 0, 32); err != nil {
errs = multierr.Append(errs, fmt.Errorf("invalid udsFileMode %q: %w", c.UDSFileMode, err))
} else if mode <= 0 {
errs = multierr.Append(errs, fmt.Errorf("invalid udsFileMode %q", c.UDSFileMode))
}
if c.RequestLimits.MaxActionsPerResource < 1 || c.RequestLimits.MaxActionsPerResource > requestItemsMax {
errs = multierr.Append(errs, fmt.Errorf("maxActionsPerResource must be between 1 and %d", requestItemsMax))
}
if c.RequestLimits.MaxResourcesPerRequest < 1 || c.RequestLimits.MaxResourcesPerRequest > requestItemsMax {
errs = multierr.Append(errs, fmt.Errorf("maxResourcesPerRequest must be between 1 and %d", requestItemsMax))
}
return errs
}
func GetConf() (*Conf, error) {
conf := &Conf{}
err := config.GetSection(conf)
return conf, err
}