-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
configuration.go
332 lines (284 loc) · 10.6 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
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation and Dapr Contributors.
// Licensed under the MIT License.
// ------------------------------------------------------------
package config
import (
"context"
"encoding/json"
"io/ioutil"
"os"
"sort"
"strings"
"time"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
operatorv1pb "github.com/dapr/dapr/pkg/proto/operator/v1"
)
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"
ActorRentrancy Feature = "Actor.Reentrancy"
ActorTypeMetadata Feature = "Actor.TypeMetadata"
PubSubRouting Feature = "PubSub.Routing"
StateEncryption Feature = "State.Encryption"
)
type Feature string
// 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"`
}
// 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 map[string]AccessControlListOperationAction
}
// AccessControlListOperationAction is an in-memory access control list config per operation for fast lookup.
type AccessControlListOperationAction struct {
VerbAction map[string]string
OperationPostFix string
OperationAction string
}
type ConfigurationSpec struct {
HTTPPipelineSpec PipelineSpec `json:"httpPipeline,omitempty" yaml:"httpPipeline,omitempty"`
TracingSpec TracingSpec `json:"tracing,omitempty" yaml:"tracing,omitempty"`
MTLSSpec MTLSSpec `json:"mtls,omitempty"`
MetricSpec MetricSpec `json:"metric,omitempty" yaml:"metric,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"`
}
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"`
}
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"`
}
// ZipkinSpec defines Zipkin trace configurations.
type ZipkinSpec struct {
EndpointAddress string `json:"endpointAddress" yaml:"endpointAddress"`
}
// MetricSpec configuration for metrics.
type MetricSpec struct {
Enabled bool `json:"enabled" yaml:"enabled"`
}
// 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"`
WorkloadCertTTL string `json:"workloadCertTTL"`
AllowedClockSkew string `json:"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"`
}
// LoadDefaultConfiguration returns the default config.
func LoadDefaultConfiguration() *Configuration {
return &Configuration{
Spec: ConfigurationSpec{
TracingSpec: TracingSpec{
SamplingRate: "",
},
MetricSpec: 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 := ioutil.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
}
return conf, string(b), nil
}
// LoadKubernetesConfiguration gets configuration from the Kubernetes operator with a given name.
func LoadKubernetesConfiguration(config, namespace string, operatorClient operatorv1pb.OperatorClient) (*Configuration, error) {
resp, err := operatorClient.GetConfiguration(context.Background(), &operatorv1pb.GetConfigurationRequest{
Name: config,
Namespace: namespace,
}, grpc_retry.WithMax(operatorMaxRetries), grpc_retry.WithPerRetryTimeout(operatorCallTimeout))
if err != nil {
return nil, err
}
if resp.GetConfiguration() == nil {
return nil, errors.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
}
return conf, nil
}
// Validate the secrets configuration and sort the allow and deny 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 errors.Errorf("%q storeName is repeated in secrets configuration", scope.StoreName)
}
if scope.DefaultAccess != "" &&
!strings.EqualFold(scope.DefaultAccess, AllowAccess) &&
!strings.EqualFold(scope.DefaultAccess, DenyAccess) {
return errors.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
}
// 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.
var access string = 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
}
func IsFeatureEnabled(features []FeatureSpec, target Feature) bool {
for _, feature := range features {
if feature.Name == target {
return feature.Enabled
}
}
return false
}