-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
configuration.go
453 lines (390 loc) · 15 KB
/
configuration.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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/*
Copyright 2021 The Dapr Authors
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 (
"context"
"encoding/json"
"fmt"
"os"
"sort"
"strings"
"time"
grpcRetry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
yaml "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/dapr/dapr/pkg/buildinfo"
operatorv1pb "github.com/dapr/dapr/pkg/proto/operator/v1"
"github.com/dapr/dapr/utils"
)
// Feature Flags section
type Feature string
const (
// Enable support for streaming in HTTP service invocation
ServiceInvocationStreaming Feature = "ServiceInvocationStreaming"
// Enables the app health check feature, allowing the use of the CLI flags
AppHealthCheck Feature = "AppHealthCheck"
)
// end feature flags section
const (
operatorCallTimeout = time.Second * 5
operatorMaxRetries = 100
AllowAccess = "allow"
DenyAccess = "deny"
DefaultTrustDomain = "public"
DefaultNamespace = "default"
ActionPolicyApp = "app"
ActionPolicyGlobal = "global"
SpiffeIDPrefix = "spiffe://"
HTTPProtocol = "http"
GRPCProtocol = "grpc"
)
// Configuration is an internal (and duplicate) representation of Dapr's Configuration CRD.
type Configuration struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
// See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
// See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
Spec ConfigurationSpec `json:"spec" yaml:"spec"`
// Internal fields
featuresEnabled map[Feature]struct{}
}
// AccessControlList is an in-memory access control list config for fast lookup.
type AccessControlList struct {
DefaultAction string
TrustDomain string
PolicySpec map[string]AccessControlListPolicySpec
}
// AccessControlListPolicySpec is an in-memory access control list config per app for fast lookup.
type AccessControlListPolicySpec struct {
AppName string
DefaultAction string
TrustDomain string
Namespace string
AppOperationActions *Trie
}
// AccessControlListOperationAction is an in-memory access control list config per operation for fast lookup.
type AccessControlListOperationAction struct {
VerbAction map[string]string
OperationName string
OperationAction string
}
type ConfigurationSpec struct {
HTTPPipelineSpec PipelineSpec `json:"httpPipeline,omitempty" yaml:"httpPipeline,omitempty"`
AppHTTPPipelineSpec PipelineSpec `json:"appHttpPipeline,omitempty" yaml:"appHttpPipeline,omitempty"`
TracingSpec TracingSpec `json:"tracing,omitempty" yaml:"tracing,omitempty"`
MTLSSpec MTLSSpec `json:"mtls,omitempty" yaml:"mtls,omitempty"`
MetricSpec MetricSpec `json:"metric,omitempty" yaml:"metric,omitempty"`
MetricsSpec MetricSpec `json:"metrics,omitempty" yaml:"metrics,omitempty"`
Secrets SecretsSpec `json:"secrets,omitempty" yaml:"secrets,omitempty"`
AccessControlSpec AccessControlSpec `json:"accessControl,omitempty" yaml:"accessControl,omitempty"`
NameResolutionSpec NameResolutionSpec `json:"nameResolution,omitempty" yaml:"nameResolution,omitempty"`
Features []FeatureSpec `json:"features,omitempty" yaml:"features,omitempty"`
APISpec APISpec `json:"api,omitempty" yaml:"api,omitempty"`
ComponentsSpec ComponentsSpec `json:"components,omitempty" yaml:"components,omitempty"`
LoggingSpec LoggingSpec `json:"logging,omitempty" yaml:"logging,omitempty"`
}
type SecretsSpec struct {
Scopes []SecretsScope `json:"scopes"`
}
// SecretsScope defines the scope for secrets.
type SecretsScope struct {
DefaultAccess string `json:"defaultAccess,omitempty" yaml:"defaultAccess,omitempty"`
StoreName string `json:"storeName" yaml:"storeName"`
AllowedSecrets []string `json:"allowedSecrets,omitempty" yaml:"allowedSecrets,omitempty"`
DeniedSecrets []string `json:"deniedSecrets,omitempty" yaml:"deniedSecrets,omitempty"`
}
type PipelineSpec struct {
Handlers []HandlerSpec `json:"handlers" yaml:"handlers"`
}
// APISpec describes the configuration for Dapr APIs.
type APISpec struct {
Allowed []APIAccessRule `json:"allowed,omitempty"`
}
// APIAccessRule describes an access rule for allowing a Dapr API to be enabled and accessible by an app.
type APIAccessRule struct {
Name string `json:"name"`
Version string `json:"version"`
Protocol string `json:"protocol"`
}
type HandlerSpec struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
Version string `json:"version" yaml:"version"`
SelectorSpec SelectorSpec `json:"selector,omitempty" yaml:"selector,omitempty"`
}
// LogName returns the name of the handler that can be used in logging.
func (h HandlerSpec) LogName() string {
return utils.ComponentLogName(h.Name, h.Type, h.Version)
}
type SelectorSpec struct {
Fields []SelectorField `json:"fields" yaml:"fields"`
}
type SelectorField struct {
Field string `json:"field" yaml:"field"`
Value string `json:"value" yaml:"value"`
}
type TracingSpec struct {
SamplingRate string `json:"samplingRate" yaml:"samplingRate"`
Stdout bool `json:"stdout" yaml:"stdout"`
Zipkin ZipkinSpec `json:"zipkin" yaml:"zipkin"`
Otel OtelSpec `json:"otel" yaml:"otel"`
}
// ZipkinSpec defines Zipkin exporter configurations.
type ZipkinSpec struct {
EndpointAddress string `json:"endpointAddress" yaml:"endpointAddress"`
}
// OtelSpec defines Otel exporter configurations.
type OtelSpec struct {
Protocol string `json:"protocol" yaml:"protocol"`
EndpointAddress string `json:"endpointAddress" yaml:"endpointAddress"`
IsSecure bool `json:"isSecure" yaml:"isSecure"`
}
// MetricSpec configuration for metrics.
type MetricSpec struct {
Enabled bool `json:"enabled" yaml:"enabled"`
Rules []MetricsRule `json:"rules" yaml:"rules"`
}
// MetricsRule defines configuration options for a metric.
type MetricsRule struct {
Name string `json:"name" yaml:"name"`
Labels []MetricLabel `json:"labels" yaml:"labels"`
}
// MetricsLabel defines an object that allows to set regex expressions for a label.
type MetricLabel struct {
Name string `json:"name" yaml:"name"`
Regex map[string]string `json:"regex" yaml:"regex"`
}
// AppPolicySpec defines the policy data structure for each app.
type AppPolicySpec struct {
AppName string `json:"appId" yaml:"appId"`
DefaultAction string `json:"defaultAction" yaml:"defaultAction"`
TrustDomain string `json:"trustDomain" yaml:"trustDomain"`
Namespace string `json:"namespace" yaml:"namespace"`
AppOperationActions []AppOperation `json:"operations" yaml:"operations"`
}
// AppOperation defines the data structure for each app operation.
type AppOperation struct {
Operation string `json:"name" yaml:"name"`
HTTPVerb []string `json:"httpVerb" yaml:"httpVerb"`
Action string `json:"action" yaml:"action"`
}
// AccessControlSpec is the spec object in ConfigurationSpec.
type AccessControlSpec struct {
DefaultAction string `json:"defaultAction" yaml:"defaultAction"`
TrustDomain string `json:"trustDomain" yaml:"trustDomain"`
AppPolicies []AppPolicySpec `json:"policies" yaml:"policies"`
}
type NameResolutionSpec struct {
Component string `json:"component" yaml:"component"`
Version string `json:"version" yaml:"version"`
Configuration interface{} `json:"configuration" yaml:"configuration"`
}
type MTLSSpec struct {
Enabled bool `json:"enabled" yaml:"enabled"`
WorkloadCertTTL string `json:"workloadCertTTL" yaml:"workloadCertTTL"`
AllowedClockSkew string `json:"allowedClockSkew" yaml:"allowedClockSkew"`
}
// SpiffeID represents the separated fields in a spiffe id.
type SpiffeID struct {
TrustDomain string
Namespace string
AppID string
}
// FeatureSpec defines which preview features are enabled.
type FeatureSpec struct {
Name Feature `json:"name" yaml:"name"`
Enabled bool `json:"enabled" yaml:"enabled"`
}
// ComponentsSpec describes the configuration for Dapr components
type ComponentsSpec struct {
// Denylist of component types that cannot be instantiated
Deny []string `json:"deny,omitempty" yaml:"deny,omitempty"`
}
// LoggingSpec defines the configuration for logging.
type LoggingSpec struct {
// Configure API logging.
APILogging APILoggingSpec `json:"apiLogging,omitempty" yaml:"apiLogging,omitempty"`
}
// APILoggingSpec defines the configuration for API logging.
type APILoggingSpec struct {
// Default value for enabling API logging. Sidecars can always override this by setting `--enable-api-logging` to true or false explicitly.
// The default value is false.
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
// When enabled, obfuscates the values of URLs in HTTP API logs, logging the route name rather than the full path being invoked, which could contain PII.
// Default: false.
// This option has no effect if API logging is disabled.
ObfuscateURLs bool `json:"obfuscateURLs" yaml:"obfuscateURLs"`
// If true, health checks are not reported in API logs. Default: false.
// This option has no effect if API logging is disabled.
OmitHealthChecks bool `json:"omitHealthChecks,omitempty" yaml:"omitHealthChecks,omitempty"`
}
// LoadDefaultConfiguration returns the default config.
func LoadDefaultConfiguration() *Configuration {
return &Configuration{
Spec: ConfigurationSpec{
TracingSpec: TracingSpec{
SamplingRate: "",
Otel: OtelSpec{
IsSecure: true,
},
},
MetricSpec: MetricSpec{
Enabled: true,
},
MetricsSpec: MetricSpec{
Enabled: true,
},
AccessControlSpec: AccessControlSpec{
DefaultAction: AllowAccess,
TrustDomain: "public",
},
},
}
}
// LoadStandaloneConfiguration gets the path to a config file and loads it into a configuration.
func LoadStandaloneConfiguration(config string) (*Configuration, string, error) {
_, err := os.Stat(config)
if err != nil {
return nil, "", err
}
b, err := os.ReadFile(config)
if err != nil {
return nil, "", err
}
// Parse environment variables from yaml
b = []byte(os.ExpandEnv(string(b)))
conf := LoadDefaultConfiguration()
err = yaml.Unmarshal(b, conf)
if err != nil {
return nil, string(b), err
}
err = sortAndValidateSecretsConfiguration(conf)
if err != nil {
return nil, string(b), err
}
sortMetricsSpec(conf)
return conf, string(b), nil
}
// LoadKubernetesConfiguration gets configuration from the Kubernetes operator with a given name.
func LoadKubernetesConfiguration(config, namespace string, podName string, operatorClient operatorv1pb.OperatorClient) (*Configuration, error) {
resp, err := operatorClient.GetConfiguration(context.Background(), &operatorv1pb.GetConfigurationRequest{
Name: config,
Namespace: namespace,
PodName: podName,
}, grpcRetry.WithMax(operatorMaxRetries), grpcRetry.WithPerRetryTimeout(operatorCallTimeout))
if err != nil {
return nil, err
}
if resp.GetConfiguration() == nil {
return nil, fmt.Errorf("configuration %s not found", config)
}
conf := LoadDefaultConfiguration()
err = json.Unmarshal(resp.GetConfiguration(), conf)
if err != nil {
return nil, err
}
err = sortAndValidateSecretsConfiguration(conf)
if err != nil {
return nil, err
}
sortMetricsSpec(conf)
return conf, nil
}
// Apply .metrics if set. If not, retain .metric.
func sortMetricsSpec(conf *Configuration) {
if !conf.Spec.MetricsSpec.Enabled {
conf.Spec.MetricSpec.Enabled = false
}
if len(conf.Spec.MetricsSpec.Rules) > 0 {
conf.Spec.MetricSpec.Rules = conf.Spec.MetricsSpec.Rules
}
}
// Validate the secrets configuration and sort to the allowed and denied lists if present.
func sortAndValidateSecretsConfiguration(conf *Configuration) error {
scopes := conf.Spec.Secrets.Scopes
set := sets.NewString()
for _, scope := range scopes {
// validate scope
if set.Has(scope.StoreName) {
return fmt.Errorf("%q storeName is repeated in secrets configuration", scope.StoreName)
}
if scope.DefaultAccess != "" &&
!strings.EqualFold(scope.DefaultAccess, AllowAccess) &&
!strings.EqualFold(scope.DefaultAccess, DenyAccess) {
return fmt.Errorf("defaultAccess %q can be either allow or deny", scope.DefaultAccess)
}
set.Insert(scope.StoreName)
// modify scope
sort.Strings(scope.AllowedSecrets)
sort.Strings(scope.DeniedSecrets)
}
return nil
}
// IsSecretAllowed Check if the secret is allowed to be accessed.
func (c SecretsScope) IsSecretAllowed(key string) bool {
// By default, set allow access for the secret store.
access := AllowAccess
// Check and set deny access.
if strings.EqualFold(c.DefaultAccess, DenyAccess) {
access = DenyAccess
}
// If the allowedSecrets list is not empty then check if the access is specifically allowed for this key.
if len(c.AllowedSecrets) != 0 {
return containsKey(c.AllowedSecrets, key)
}
// Check key in deny list if deny list is present for the secret store.
// If the specific key is denied, then alone deny access.
if deny := containsKey(c.DeniedSecrets, key); deny {
return !deny
}
// Check if defined default access is allow.
return access == AllowAccess
}
// Runs Binary Search on a sorted list of strings to find a key.
func containsKey(s []string, key string) bool {
index := sort.SearchStrings(s, key)
return index < len(s) && s[index] == key
}
// LoadFeatures loads the list of enabled features, from the Configuration spec and from the buildinfo.
func (c *Configuration) LoadFeatures() {
forced := buildinfo.Features()
c.featuresEnabled = make(map[Feature]struct{}, len(c.Spec.Features)+len(forced))
for _, feature := range c.Spec.Features {
if feature.Name == "" || !feature.Enabled {
continue
}
c.featuresEnabled[feature.Name] = struct{}{}
}
for _, v := range forced {
if v == "" {
continue
}
c.featuresEnabled[Feature(v)] = struct{}{}
}
}
// IsFeatureEnabled returns true if a Feature (such as a preview) is enabled.
func (c Configuration) IsFeatureEnabled(target Feature) (enabled bool) {
_, enabled = c.featuresEnabled[target]
return enabled
}
// EnabledFeatures returns the list of features that have been enabled.
func (c Configuration) EnabledFeatures() []string {
features := make([]string, len(c.featuresEnabled))
i := 0
for f := range c.featuresEnabled {
features[i] = string(f)
i++
}
return features[:i]
}