diff --git a/.gitignore b/.gitignore index f74a8d23353e1f6926c66283cac81042cb3c05cb..1239d67d2caf9c47013e55c21bf47047c76563e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /dist .idea **/.DS_Store +config/crd/bases github.com/ diff --git a/charts/dapr/crds/components.yaml b/charts/dapr/crds/components.yaml index e959dbb2fbb472dd7b24a72f1a3979c46ce727b8..c6c23a292f0ebe4988665283ef16f7e59cb1b985 100644 --- a/charts/dapr/crds/components.yaml +++ b/charts/dapr/crds/components.yaml @@ -12,7 +12,7 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Component describes an Dapr component type + description: Component describes an Dapr component type. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -20,7 +20,7 @@ spec: internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string auth: - description: Auth represents authentication details for the component + description: Auth represents authentication details for the component. properties: secretStore: type: string @@ -39,32 +39,38 @@ spec: type: string type: array spec: - description: ComponentSpec is the spec for a component + description: ComponentSpec is the spec for a component. properties: - initTimeout: - type: string ignoreErrors: type: boolean + initTimeout: + type: string metadata: items: - description: MetadataItem is a name/value pair for a metadata + description: NameValuePair is a name/value pair. properties: + envRef: + description: EnvRef is the name of an environmental variable + to read the value from. + type: string name: + description: Name of the property. type: string secretKeyRef: - description: SecretKeyRef is a reference to a secret holding - the value for the metadata item. Name is the secret name, - and key is the field in the secret. + description: SecretKeyRef is the reference of a value in a secret + store component. properties: key: + description: Field in the secret. type: string name: + description: Secret name. type: string required: - - key - name type: object value: + description: Value of the property, in plaintext. x-kubernetes-preserve-unknown-fields: true required: - name diff --git a/charts/dapr/crds/httpendpoints.yaml b/charts/dapr/crds/httpendpoints.yaml index 22667592957f8bb7c6b670c916b9412503364cad..95c2d34ff1c366201fa235606479f5e74fcf2c4b 100644 --- a/charts/dapr/crds/httpendpoints.yaml +++ b/charts/dapr/crds/httpendpoints.yaml @@ -18,10 +18,14 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: HTTPEndpoint describes a Dapr HTTPEndpoint type for external service invocation. This endpoint can be external to Dapr, or external to the environment. + description: HTTPEndpoint describes a Dapr HTTPEndpoint type for external + service invocation. This endpoint can be external to Dapr, or external to + the environment. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string auth: description: Auth represents authentication details for the component. @@ -32,7 +36,9 @@ spec: - secretStore type: object kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -41,28 +47,37 @@ spec: type: string type: array spec: - description: HTTPEndpointSpec describes an access specification for allowing external service invocations. + description: HTTPEndpointSpec describes an access specification for allowing + external service invocations. properties: baseUrl: type: string headers: items: - description: Header is the name/value pair for a header specification. + description: NameValuePair is a name/value pair. properties: + envRef: + description: EnvRef is the name of an environmental variable + to read the value from. + type: string name: + description: Name of the property. type: string secretKeyRef: - description: SecretKeyRef is a reference to a secret holding the value for the metadata item. Name is the secret name, and key is the field in the secret. + description: SecretKeyRef is the reference of a value in a secret + store component. properties: key: + description: Field in the secret. type: string name: + description: Secret name. type: string required: - - key - name type: object value: + description: Value of the property, in plaintext. x-kubernetes-preserve-unknown-fields: true required: - name diff --git a/pkg/apis/common/namevalue.go b/pkg/apis/common/namevalue.go new file mode 100644 index 0000000000000000000000000000000000000000..129ad4fe4a3cbd675f09c75ed83d0b6b853836a2 --- /dev/null +++ b/pkg/apis/common/namevalue.go @@ -0,0 +1,78 @@ +/* +Copyright 2023 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 common + +// +kubebuilder:object:generate=true + +import ( + "strconv" + + apiextensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" +) + +// NameValuePair is a name/value pair. +type NameValuePair struct { + // Name of the property. + Name string `json:"name"` + // Value of the property, in plaintext. + //+optional + Value DynamicValue `json:"value,omitempty"` + // SecretKeyRef is the reference of a value in a secret store component. + //+optional + SecretKeyRef SecretKeyRef `json:"secretKeyRef,omitempty"` + // EnvRef is the name of an environmental variable to read the value from. + //+optional + EnvRef string `json:"envRef,omitempty"` +} + +// HasValue returns true if the NameValuePair has a non-empty value. +func (nvp NameValuePair) HasValue() bool { + return len(nvp.Value.JSON.Raw) > 0 +} + +// SetValue sets the value. +func (nvp *NameValuePair) SetValue(val []byte) { + nvp.Value = DynamicValue{ + JSON: apiextensionsV1.JSON{ + Raw: val, + }, + } +} + +// SecretKeyRef is a reference to a secret holding the value for the name/value item. +type SecretKeyRef struct { + // Secret name. + Name string `json:"name"` + // Field in the secret. + //+optional + Key string `json:"key"` +} + +// DynamicValue is a dynamic value struct for the component.metadata pair value. +// +kubebuilder:validation:Type="" +// +kubebuilder:validation:Schemaless +type DynamicValue struct { + apiextensionsV1.JSON `json:",inline"` +} + +// String returns the string representation of the raw value. +// If the value is a string, it will be unquoted as the string is guaranteed to be a JSON serialized string. +func (d *DynamicValue) String() string { + s := string(d.Raw) + c, err := strconv.Unquote(s) + if err == nil { + s = c + } + return s +} diff --git a/pkg/apis/common/scoped.go b/pkg/apis/common/scoped.go new file mode 100644 index 0000000000000000000000000000000000000000..164d29f22c63b73e9f67ae658e90d07b0fc063d7 --- /dev/null +++ b/pkg/apis/common/scoped.go @@ -0,0 +1,38 @@ +/* +Copyright 2023 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 common + +// +kubebuilder:object:generate=true + +// Scoped is a base struct for components and other resources that can be scoped to apps. +type Scoped struct { + //+optional + Scopes []string `json:"scopes,omitempty"` +} + +// IsAppScoped returns true if the appID is allowed in the scopes for the resource. +func (s Scoped) IsAppScoped(appID string) bool { + if len(s.Scopes) == 0 { + // If there are no scopes, then every app is allowed + return true + } + + for _, s := range s.Scopes { + if s == appID { + return true + } + } + + return false +} diff --git a/pkg/apis/common/zz_generated.deepcopy.go b/pkg/apis/common/zz_generated.deepcopy.go new file mode 100644 index 0000000000000000000000000000000000000000..058dc6b1c2c34395db25e77e7586ffcd86dad1c6 --- /dev/null +++ b/pkg/apis/common/zz_generated.deepcopy.go @@ -0,0 +1,92 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package common + +import () + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicValue) DeepCopyInto(out *DynamicValue) { + *out = *in + in.JSON.DeepCopyInto(&out.JSON) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicValue. +func (in *DynamicValue) DeepCopy() *DynamicValue { + if in == nil { + return nil + } + out := new(DynamicValue) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NameValuePair) DeepCopyInto(out *NameValuePair) { + *out = *in + in.Value.DeepCopyInto(&out.Value) + out.SecretKeyRef = in.SecretKeyRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NameValuePair. +func (in *NameValuePair) DeepCopy() *NameValuePair { + if in == nil { + return nil + } + out := new(NameValuePair) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Scoped) DeepCopyInto(out *Scoped) { + *out = *in + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Scoped. +func (in *Scoped) DeepCopy() *Scoped { + if in == nil { + return nil + } + out := new(Scoped) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretKeyRef) DeepCopyInto(out *SecretKeyRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeyRef. +func (in *SecretKeyRef) DeepCopy() *SecretKeyRef { + if in == nil { + return nil + } + out := new(SecretKeyRef) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/components/v1alpha1/types.go b/pkg/apis/components/v1alpha1/types.go index 3f842ce0758962db8fedde97d33248a9a76cd805..1a63a5701697772b04fff41e704e0650a4912ab6 100644 --- a/pkg/apis/components/v1alpha1/types.go +++ b/pkg/apis/components/v1alpha1/types.go @@ -14,11 +14,9 @@ limitations under the License. package v1alpha1 import ( - "strconv" - - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/dapr/dapr/pkg/apis/common" "github.com/dapr/dapr/utils" ) @@ -34,9 +32,8 @@ type Component struct { //+optional Spec ComponentSpec `json:"spec,omitempty"` //+optional - Auth `json:"auth,omitempty"` - //+optional - Scopes []string `json:"scopes,omitempty"` + Auth `json:"auth,omitempty"` + common.Scoped `json:",inline"` } // Kind returns the component kind. @@ -49,20 +46,14 @@ func (c Component) LogName() string { return utils.ComponentLogName(c.ObjectMeta.Name, c.Spec.Type, c.Spec.Version) } -// IsAppScoped returns true if the appID is allowed in the scopes for the component. -func (c Component) IsAppScoped(appID string) bool { - if len(c.Scopes) == 0 { - // If there are no scopes, then every app is allowed - return true - } - - for _, s := range c.Scopes { - if s == appID { - return true - } - } +// GetSecretStore returns the name of the secret store. +func (c Component) GetSecretStore() string { + return c.Auth.SecretStore +} - return false +// NameValuePairs returns the component's metadata as name/value pairs +func (c Component) NameValuePairs() []common.NameValuePair { + return c.Spec.Metadata } // ComponentSpec is the spec for a component. @@ -70,27 +61,12 @@ type ComponentSpec struct { Type string `json:"type"` Version string `json:"version"` //+optional - IgnoreErrors bool `json:"ignoreErrors"` - Metadata []MetadataItem `json:"metadata"` + IgnoreErrors bool `json:"ignoreErrors"` + Metadata []common.NameValuePair `json:"metadata"` //+optional InitTimeout string `json:"initTimeout"` } -// MetadataItem is a name/value pair for a metadata. -type MetadataItem struct { - Name string `json:"name"` - //+optional - Value DynamicValue `json:"value,omitempty"` - //+optional - SecretKeyRef SecretKeyRef `json:"secretKeyRef,omitempty"` -} - -// SecretKeyRef is a reference to a secret holding the value for the metadata item. Name is the secret name, and key is the field in the secret. -type SecretKeyRef struct { - Name string `json:"name"` - Key string `json:"key"` -} - // Auth represents authentication details for the component. type Auth struct { SecretStore string `json:"secretStore"` @@ -105,19 +81,3 @@ type ComponentList struct { Items []Component `json:"items"` } - -// DynamicValue is a dynamic value struct for the component.metadata pair value. -type DynamicValue struct { - v1.JSON `json:",inline"` -} - -// String returns the string representation of the raw value. -// If the value is a string, it will be unquoted as the string is guaranteed to be a JSON serialized string. -func (d *DynamicValue) String() string { - s := string(d.Raw) - c, err := strconv.Unquote(s) - if err == nil { - s = c - } - return s -} diff --git a/pkg/apis/components/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/components/v1alpha1/zz_generated.deepcopy.go index 04dc8c7b9f974a23a935fbbd59ca1eeebfa51e42..974d02bd84ab79bc5c8f0991b9b11276f1d35260 100644 --- a/pkg/apis/components/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/components/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ limitations under the License. package v1alpha1 import ( + "github.com/dapr/dapr/pkg/apis/common" "k8s.io/apimachinery/pkg/runtime" ) @@ -47,11 +48,7 @@ func (in *Component) DeepCopyInto(out *Component) { in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) out.Auth = in.Auth - if in.Scopes != nil { - in, out := &in.Scopes, &out.Scopes - *out = make([]string, len(*in)) - copy(*out, *in) - } + in.Scoped.DeepCopyInto(&out.Scoped) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Component. @@ -109,7 +106,7 @@ func (in *ComponentSpec) DeepCopyInto(out *ComponentSpec) { *out = *in if in.Metadata != nil { in, out := &in.Metadata, &out.Metadata - *out = make([]MetadataItem, len(*in)) + *out = make([]common.NameValuePair, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -125,51 +122,3 @@ func (in *ComponentSpec) DeepCopy() *ComponentSpec { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DynamicValue) DeepCopyInto(out *DynamicValue) { - *out = *in - in.JSON.DeepCopyInto(&out.JSON) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicValue. -func (in *DynamicValue) DeepCopy() *DynamicValue { - if in == nil { - return nil - } - out := new(DynamicValue) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetadataItem) DeepCopyInto(out *MetadataItem) { - *out = *in - in.Value.DeepCopyInto(&out.Value) - out.SecretKeyRef = in.SecretKeyRef -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataItem. -func (in *MetadataItem) DeepCopy() *MetadataItem { - if in == nil { - return nil - } - out := new(MetadataItem) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretKeyRef) DeepCopyInto(out *SecretKeyRef) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeyRef. -func (in *SecretKeyRef) DeepCopy() *SecretKeyRef { - if in == nil { - return nil - } - out := new(SecretKeyRef) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/apis/httpEndpoint/v1alpha1/types.go b/pkg/apis/httpEndpoint/v1alpha1/types.go index 9155e047e6a97fbeb6fac3c33b5b410108e93852..9627f4351655be91b8cfd3e43bc59d3877d77aad 100644 --- a/pkg/apis/httpEndpoint/v1alpha1/types.go +++ b/pkg/apis/httpEndpoint/v1alpha1/types.go @@ -14,10 +14,9 @@ limitations under the License. package v1alpha1 import ( - "strconv" - - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/dapr/dapr/pkg/apis/common" ) //+genclient @@ -33,9 +32,8 @@ type HTTPEndpoint struct { //+optional Spec HTTPEndpointSpec `json:"spec,omitempty"` //+optional - Auth `json:"auth,omitempty"` - //+optional - Scopes []string `json:"scopes,omitempty"` + Auth `json:"auth,omitempty"` + common.Scoped `json:",inline"` } // Kind returns the component kind. @@ -43,42 +41,21 @@ func (HTTPEndpoint) Kind() string { return "HTTPEndpoint" } -// IsAppScoped returns true if the appID is allowed in the scopes for the http endpoint. -func (e HTTPEndpoint) IsAppScoped(appID string) bool { - if len(e.Scopes) == 0 { - // If there are no scopes, then every app is allowed - return true - } - - for _, s := range e.Scopes { - if s == appID { - return true - } - } - - return false +// GetSecretStore returns the name of the secret store. +func (h HTTPEndpoint) GetSecretStore() string { + return h.Auth.SecretStore } -// Header is the name/value pair for a header specification. -type Header struct { - Name string `json:"name"` - //+optional - Value DynamicValue `json:"value"` - //+optional - SecretKeyRef SecretKeyRef `json:"secretKeyRef,omitempty"` +// NameValuePairs returns the component's headers as name/value pairs +func (h HTTPEndpoint) NameValuePairs() []common.NameValuePair { + return h.Spec.Headers } // HTTPEndpointSpec describes an access specification for allowing external service invocations. type HTTPEndpointSpec struct { BaseURL string `json:"baseUrl" validate:"required"` //+optional - Headers []Header `json:"headers"` -} - -// SecretKeyRef is a reference to a secret holding the value for the metadata item. Name is the secret name, and key is the field in the secret. -type SecretKeyRef struct { - Name string `json:"name" validate:"required"` - Key string `json:"key" validate:"required"` + Headers []common.NameValuePair `json:"headers"` } // Auth represents authentication details for the component. @@ -95,20 +72,3 @@ type HTTPEndpointList struct { Items []HTTPEndpoint `json:"items"` } - -// DynamicValue is a dynamic value struct for the header.value. -type DynamicValue struct { - v1.JSON `json:",inline"` -} - -// String returns the string representation of the raw value. -// If the value is a string, it will be unquoted as the string is guaranteed to be a JSON serialized string. -func (d *DynamicValue) String() string { - s := string(d.Raw) - c, err := strconv.Unquote(s) - if err == nil { - s = c - } - - return s -} diff --git a/pkg/apis/httpEndpoint/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/httpEndpoint/v1alpha1/zz_generated.deepcopy.go index e54922c1969ac876c80a935d8744a968e07d9da0..6143256d20287b00b67f933d38db89db98f03d3d 100644 --- a/pkg/apis/httpEndpoint/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/httpEndpoint/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ limitations under the License. package v1alpha1 import ( + "github.com/dapr/dapr/pkg/apis/common" "k8s.io/apimachinery/pkg/runtime" ) @@ -40,22 +41,6 @@ func (in *Auth) DeepCopy() *Auth { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DynamicValue) DeepCopyInto(out *DynamicValue) { - *out = *in - in.JSON.DeepCopyInto(&out.JSON) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicValue. -func (in *DynamicValue) DeepCopy() *DynamicValue { - if in == nil { - return nil - } - out := new(DynamicValue) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPEndpoint) DeepCopyInto(out *HTTPEndpoint) { *out = *in @@ -63,11 +48,7 @@ func (in *HTTPEndpoint) DeepCopyInto(out *HTTPEndpoint) { in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) out.Auth = in.Auth - if in.Scopes != nil { - in, out := &in.Scopes, &out.Scopes - *out = make([]string, len(*in)) - copy(*out, *in) - } + in.Scoped.DeepCopyInto(&out.Scoped) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPEndpoint. @@ -125,7 +106,7 @@ func (in *HTTPEndpointSpec) DeepCopyInto(out *HTTPEndpointSpec) { *out = *in if in.Headers != nil { in, out := &in.Headers, &out.Headers - *out = make([]Header, len(*in)) + *out = make([]common.NameValuePair, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -141,35 +122,3 @@ func (in *HTTPEndpointSpec) DeepCopy() *HTTPEndpointSpec { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Header) DeepCopyInto(out *Header) { - *out = *in - in.Value.DeepCopyInto(&out.Value) - out.SecretKeyRef = in.SecretKeyRef -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Header. -func (in *Header) DeepCopy() *Header { - if in == nil { - return nil - } - out := new(Header) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretKeyRef) DeepCopyInto(out *SecretKeyRef) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeyRef. -func (in *SecretKeyRef) DeepCopy() *SecretKeyRef { - if in == nil { - return nil - } - out := new(SecretKeyRef) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/channel/http/http_channel.go b/pkg/channel/http/http_channel.go index 16a086535c4938e48f016a77b18b52ba8139e74e..f5565c9456b54df3656c4d8d8b91df21eb14ef51 100644 --- a/pkg/channel/http/http_channel.go +++ b/pkg/channel/http/http_channel.go @@ -27,7 +27,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - endpointV1alpha1 "github.com/dapr/dapr/pkg/apis/httpEndpoint/v1alpha1" + commonapi "github.com/dapr/dapr/pkg/apis/common" "github.com/dapr/dapr/pkg/apphealth" "github.com/dapr/dapr/pkg/channel" "github.com/dapr/dapr/pkg/config" @@ -283,7 +283,7 @@ func (h *Channel) constructRequest(ctx context.Context, req *invokev1.InvokeMeth msg := req.Message() verb := msg.HttpExtension.Verb.String() method := msg.Method - var headers []endpointV1alpha1.Header + var headers []commonapi.NameValuePair uri := strings.Builder{} diff --git a/pkg/encryption/encryption.go b/pkg/encryption/encryption.go index c01ea04313363dac6c0a9746d1d70bdadd833b98..ff64ea9e87c3c0d16c9d8e1e22dc95a331963eb1 100644 --- a/pkg/encryption/encryption.go +++ b/pkg/encryption/encryption.go @@ -25,6 +25,7 @@ import ( "io" "github.com/dapr/components-contrib/secretstores" + commonapi "github.com/dapr/dapr/pkg/apis/common" "github.com/dapr/dapr/pkg/apis/components/v1alpha1" ) @@ -123,7 +124,7 @@ func ComponentEncryptionKey(component v1alpha1.Component, secretStore secretstor return cek, nil } -func tryGetEncryptionKeyFromMetadataItem(namespace string, item v1alpha1.MetadataItem, secretStore secretstores.SecretStore) (Key, error) { +func tryGetEncryptionKeyFromMetadataItem(namespace string, item commonapi.NameValuePair, secretStore secretstores.SecretStore) (Key, error) { if item.SecretKeyRef.Name == "" { return Key{}, fmt.Errorf("%s: secretKeyRef cannot be empty", errPrefix) } diff --git a/pkg/encryption/encryption_test.go b/pkg/encryption/encryption_test.go index 9f1f73aeca44ca88913dd11db8e5b0349b79ea8f..28e8eb384f419070dd5c8eca5bba912fe57ed419 100644 --- a/pkg/encryption/encryption_test.go +++ b/pkg/encryption/encryption_test.go @@ -21,6 +21,7 @@ import ( "github.com/dapr/components-contrib/metadata" "github.com/dapr/components-contrib/secretstores" + commonapi "github.com/dapr/dapr/pkg/apis/common" "github.com/dapr/dapr/pkg/apis/components/v1alpha1" "github.com/stretchr/testify/assert" @@ -65,16 +66,16 @@ func TestComponentEncryptionKey(t *testing.T) { Name: "statestore", }, Spec: v1alpha1.ComponentSpec{ - Metadata: []v1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: primaryEncryptionKey, - SecretKeyRef: v1alpha1.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: "primaryKey", }, }, { Name: secondaryEncryptionKey, - SecretKeyRef: v1alpha1.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: "secondaryKey", }, }, @@ -111,16 +112,16 @@ func TestComponentEncryptionKey(t *testing.T) { Name: "statestore", }, Spec: v1alpha1.ComponentSpec{ - Metadata: []v1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: primaryEncryptionKey, - SecretKeyRef: v1alpha1.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: "primaryKey", }, }, { Name: secondaryEncryptionKey, - SecretKeyRef: v1alpha1.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: "secondaryKey", }, }, @@ -140,7 +141,7 @@ func TestComponentEncryptionKey(t *testing.T) { Name: "statestore", }, Spec: v1alpha1.ComponentSpec{ - Metadata: []v1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "something", }, @@ -163,7 +164,7 @@ func TestTryGetEncryptionKeyFromMetadataItem(t *testing.T) { }, }}) - _, err := tryGetEncryptionKeyFromMetadataItem("", v1alpha1.MetadataItem{}, secretStore) + _, err := tryGetEncryptionKeyFromMetadataItem("", commonapi.NameValuePair{}, secretStore) assert.Error(t, err) }) } diff --git a/pkg/grpc/api_test.go b/pkg/grpc/api_test.go index adec0238e79a47be862faa3673de6f29b3a7fc5e..1b8f447fbfb2ebda836111f61492e102f9d3d4e5 100644 --- a/pkg/grpc/api_test.go +++ b/pkg/grpc/api_test.go @@ -51,6 +51,7 @@ import ( "github.com/dapr/components-contrib/secretstores" "github.com/dapr/components-contrib/state" "github.com/dapr/dapr/pkg/actors" + commonapi "github.com/dapr/dapr/pkg/apis/common" componentsV1alpha1 "github.com/dapr/dapr/pkg/apis/components/v1alpha1" httpEndpointsV1alpha1 "github.com/dapr/dapr/pkg/apis/httpEndpoint/v1alpha1" "github.com/dapr/dapr/pkg/apis/resiliency/v1alpha1" @@ -4215,10 +4216,10 @@ func TestMetadata(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "mock.component1Type", Version: "v1.0", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "actorMockComponent1", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("true")}, }, }, @@ -4232,10 +4233,10 @@ func TestMetadata(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "mock.component2Type", Version: "v1.0", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "actorMockComponent2", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("true")}, }, }, @@ -4262,10 +4263,10 @@ func TestMetadata(t *testing.T) { }, Spec: httpEndpointsV1alpha1.HTTPEndpointSpec{ BaseURL: "api.test.com", - Headers: []httpEndpointsV1alpha1.Header{ + Headers: []commonapi.NameValuePair{ { Name: "Accept-Language", - Value: httpEndpointsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("en-US")}, }, }, diff --git a/pkg/http/api_test.go b/pkg/http/api_test.go index cde2a5dfa5b48d9e62ce296f52880817aeae1e18..143c5cae9c442d6d7bfe77d773e481f7529f83d0 100644 --- a/pkg/http/api_test.go +++ b/pkg/http/api_test.go @@ -39,7 +39,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/anypb" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/dapr/components-contrib/bindings" @@ -52,6 +52,7 @@ import ( workflowContrib "github.com/dapr/components-contrib/workflows" "github.com/dapr/dapr/pkg/actors" "github.com/dapr/dapr/pkg/actors/reminders" + commonapi "github.com/dapr/dapr/pkg/apis/common" componentsV1alpha1 "github.com/dapr/dapr/pkg/apis/components/v1alpha1" httpEndpointsV1alpha1 "github.com/dapr/dapr/pkg/apis/httpEndpoint/v1alpha1" "github.com/dapr/dapr/pkg/apis/resiliency/v1alpha1" @@ -2597,11 +2598,11 @@ func TestV1MetadataEndpoint(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "mock.component1Type", Version: "v1.0", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "actorMockComponent1", - Value: componentsV1alpha1.DynamicValue{ - JSON: v1.JSON{Raw: []byte("true")}, + Value: commonapi.DynamicValue{ + JSON: apiextensionsV1.JSON{Raw: []byte("true")}, }, }, }, @@ -2614,11 +2615,11 @@ func TestV1MetadataEndpoint(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "mock.component2Type", Version: "v1.0", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "actorMockComponent2", - Value: componentsV1alpha1.DynamicValue{ - JSON: v1.JSON{Raw: []byte("true")}, + Value: commonapi.DynamicValue{ + JSON: apiextensionsV1.JSON{Raw: []byte("true")}, }, }, }, @@ -2644,11 +2645,11 @@ func TestV1MetadataEndpoint(t *testing.T) { }, Spec: httpEndpointsV1alpha1.HTTPEndpointSpec{ BaseURL: "api.test.com", - Headers: []httpEndpointsV1alpha1.Header{ + Headers: []commonapi.NameValuePair{ { Name: "Accept-Language", - Value: httpEndpointsV1alpha1.DynamicValue{ - JSON: v1.JSON{Raw: []byte("en-US")}, + Value: commonapi.DynamicValue{ + JSON: apiextensionsV1.JSON{Raw: []byte("en-US")}, }, }, }, diff --git a/pkg/injector/components/components_test.go b/pkg/injector/components/components_test.go index 7caf411fea5e120cab76563175b75b611027b60b..83be1f8788bbb6c6f1d1c8a751a67f6f088cb885 100644 --- a/pkg/injector/components/components_test.go +++ b/pkg/injector/components/components_test.go @@ -17,6 +17,7 @@ import ( "fmt" "testing" + commonapi "github.com/dapr/dapr/pkg/apis/common" componentsapi "github.com/dapr/dapr/pkg/apis/components/v1alpha1" "github.com/dapr/dapr/pkg/injector/annotations" "github.com/dapr/dapr/pkg/injector/patcher" @@ -97,9 +98,11 @@ func TestComponentsPatch(t *testing.T) { { "patch should not create injectable containers operations when app is scopped but has no annotations", appName, - []componentsapi.Component{{ - Scopes: []string{appName}, - }}, + []componentsapi.Component{ + { + Scoped: commonapi.Scoped{Scopes: []string{appName}}, + }, + }, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ @@ -148,7 +151,7 @@ func TestComponentsPatch(t *testing.T) { }`, componentImage), }, }, - Scopes: []string{appName}, + Scoped: commonapi.Scoped{Scopes: []string{appName}}, }}, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/injector/sidecar/container.go b/pkg/injector/sidecar/container.go index a03502642dac6ddf32dc21900ada043af45b943a..220bdab6ea0fd4fc7f7d82851d9689d79232ae33 100644 --- a/pkg/injector/sidecar/container.go +++ b/pkg/injector/sidecar/container.go @@ -267,9 +267,13 @@ func GetSidecarContainer(cfg ContainerConfig) (*corev1.Container, error) { container.Args = append(container.Args, args...) } - containerEnv := ParseEnvString(cfg.Annotations[annotations.KeyEnv]) + containerEnvKeys, containerEnv := ParseEnvString(cfg.Annotations[annotations.KeyEnv]) if len(containerEnv) > 0 { container.Env = append(container.Env, containerEnv...) + container.Env = append(container.Env, corev1.EnvVar{ + Name: authConsts.EnvKeysEnvVar, + Value: strings.Join(containerEnvKeys, " "), + }) } // This is a special case that requires administrator privileges in Windows containers diff --git a/pkg/injector/sidecar/container_test.go b/pkg/injector/sidecar/container_test.go index 65d0ef849d69c89f1fe73f464537ac73a24e6c9b..a0405cd3fcf4f22bc7a4f9ce6cc86296421d7038 100644 --- a/pkg/injector/sidecar/container_test.go +++ b/pkg/injector/sidecar/container_test.go @@ -22,6 +22,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "github.com/dapr/dapr/pkg/injector/annotations" + authConsts "github.com/dapr/dapr/pkg/runtime/security/consts" ) const ( @@ -847,6 +848,32 @@ func TestGetSidecarContainer(t *testing.T) { } }) + t.Run("sidecar container should have env vars injected", func(t *testing.T) { + an := map[string]string{ + annotations.KeyEnv: `HELLO=world, CIAO=mondo, BONJOUR=monde`, + } + container, _ := GetSidecarContainer(ContainerConfig{ + Annotations: an, + }) + + expect := map[string]string{ + "HELLO": "world", + "CIAO": "mondo", + "BONJOUR": "monde", + authConsts.EnvKeysEnvVar: "HELLO CIAO BONJOUR", + } + + found := map[string]string{} + for _, env := range container.Env { + switch env.Name { + case "HELLO", "CIAO", "BONJOUR", authConsts.EnvKeysEnvVar: + found[env.Name] = env.Value + } + } + + assert.Equal(t, expect, found) + }) + t.Run("sidecar container should specify commands only when ignoreEntrypointTolerations match with the pod", func(t *testing.T) { testCases := []struct { name string diff --git a/pkg/injector/sidecar/utils.go b/pkg/injector/sidecar/utils.go index c8d85c8ea9bca766ccd1708de5be6dedefd8d160..d051d38ec323f4f65c738b2fea07da2997f1f9ed 100644 --- a/pkg/injector/sidecar/utils.go +++ b/pkg/injector/sidecar/utils.go @@ -36,7 +36,7 @@ func GetMetricsEnabled(pod metaV1.ObjectMeta) bool { } // add env-vars from annotations. -func ParseEnvString(envStr string) []coreV1.EnvVar { +func ParseEnvString(envStr string) ([]string, []coreV1.EnvVar) { indexes := envRegexp.FindAllStringIndex(envStr, -1) lastEnd := len(envStr) parts := make([]string, len(indexes)+1) @@ -46,19 +46,21 @@ func ParseEnvString(envStr string) []coreV1.EnvVar { } parts[0] = envStr[0:lastEnd] + envKeys := make([]string, 0) envVars := make([]coreV1.EnvVar, 0) for _, s := range parts { pairs := strings.Split(strings.TrimSpace(s), "=") if len(pairs) != 2 { continue } + envKeys = append(envKeys, pairs[0]) envVars = append(envVars, coreV1.EnvVar{ Name: pairs[0], Value: pairs[1], }) } - return envVars + return envKeys, envVars } // ParseVolumeMountsString parses the annotation and returns volume mounts. diff --git a/pkg/injector/sidecar/utils_test.go b/pkg/injector/sidecar/utils_test.go index ae88587ca42b8d82cea24b6ecb0c517fe47a9b90..20f965ff210bdf917b13484bdb62e1cf690d3b8d 100644 --- a/pkg/injector/sidecar/utils_test.go +++ b/pkg/injector/sidecar/utils_test.go @@ -14,7 +14,6 @@ limitations under the License. package sidecar import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -42,21 +41,24 @@ func TestGetAppID(t *testing.T) { func TestParseEnvString(t *testing.T) { testCases := []struct { - testName string - envStr string - expEnvLen int - expEnv []coreV1.EnvVar + testName string + envStr string + expLen int + expKeys []string + expEnv []coreV1.EnvVar }{ { - testName: "empty environment string.", - envStr: "", - expEnvLen: 0, - expEnv: []coreV1.EnvVar{}, + testName: "empty environment string", + envStr: "", + expLen: 0, + expKeys: []string{}, + expEnv: []coreV1.EnvVar{}, }, { - testName: "common valid environment string.", - envStr: "ENV1=value1,ENV2=value2, ENV3=value3", - expEnvLen: 3, + testName: "common valid environment string", + envStr: "ENV1=value1,ENV2=value2, ENV3=value3", + expLen: 3, + expKeys: []string{"ENV1", "ENV2", "ENV3"}, expEnv: []coreV1.EnvVar{ { Name: "ENV1", @@ -73,9 +75,10 @@ func TestParseEnvString(t *testing.T) { }, }, { - testName: "special valid environment string.", - envStr: `HTTP_PROXY=http://myproxy.com, NO_PROXY="localhost,127.0.0.1,.amazonaws.com"`, - expEnvLen: 2, + testName: "environment string with quotes", + envStr: `HTTP_PROXY=http://myproxy.com, NO_PROXY="localhost,127.0.0.1,.amazonaws.com"`, + expLen: 2, + expKeys: []string{"HTTP_PROXY", "NO_PROXY"}, expEnv: []coreV1.EnvVar{ { Name: "HTTP_PROXY", @@ -92,9 +95,9 @@ func TestParseEnvString(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { - envVars := ParseEnvString(tc.envStr) - fmt.Println(tc.testName) //nolint:forbidigo - assert.Equal(t, tc.expEnvLen, len(envVars)) + envKeys, envVars := ParseEnvString(tc.envStr) + assert.Equal(t, tc.expLen, len(envVars)) + assert.Equal(t, tc.expKeys, envKeys) assert.Equal(t, tc.expEnv, envVars) }) } diff --git a/pkg/operator/api/api.go b/pkg/operator/api/api.go index 1492c1128b0cbf2896bcfa7fc26109887ae2f4c5..75beb95cc85a12e4e482c562bba2affc300fdbf1 100644 --- a/pkg/operator/api/api.go +++ b/pkg/operator/api/api.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + commonapi "github.com/dapr/dapr/pkg/apis/common" componentsapi "github.com/dapr/dapr/pkg/apis/components/v1alpha1" configurationapi "github.com/dapr/dapr/pkg/apis/configuration/v1alpha1" httpendpointsapi "github.com/dapr/dapr/pkg/apis/httpEndpoint/v1alpha1" @@ -223,7 +224,7 @@ func processComponentSecrets(ctx context.Context, component *componentsapi.Compo if err != nil { return err } - component.Spec.Metadata[i].Value = componentsapi.DynamicValue{ + component.Spec.Metadata[i].Value = commonapi.DynamicValue{ JSON: v1.JSON{ Raw: jsonEnc, }, @@ -260,7 +261,7 @@ func processHTTPEndpointSecrets(ctx context.Context, endpoint *httpendpointsapi. if err != nil { return err } - endpoint.Spec.Headers[i].Value = httpendpointsapi.DynamicValue{ + endpoint.Spec.Headers[i].Value = commonapi.DynamicValue{ JSON: v1.JSON{ Raw: jsonEnc, }, diff --git a/pkg/operator/api/api_test.go b/pkg/operator/api/api_test.go index 3ad408ccb6a7260130339ffe23279a9ae4e6e737..5633f5861322363378cbe52a4d37d541928c5c8d 100644 --- a/pkg/operator/api/api_test.go +++ b/pkg/operator/api/api_test.go @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/yaml" + commonapi "github.com/dapr/dapr/pkg/apis/common" componentsapi "github.com/dapr/dapr/pkg/apis/components/v1alpha1" httpendpointapi "github.com/dapr/dapr/pkg/apis/httpEndpoint/v1alpha1" resiliencyapi "github.com/dapr/dapr/pkg/apis/resiliency/v1alpha1" @@ -69,10 +70,10 @@ func TestProcessComponentSecrets(t *testing.T) { t.Run("secret ref exists, not kubernetes secret store, no error", func(t *testing.T) { c := componentsapi.Component{ Spec: componentsapi.ComponentSpec{ - Metadata: []componentsapi.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "test1", - SecretKeyRef: componentsapi.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: "secret1", Key: "key1", }, @@ -91,10 +92,10 @@ func TestProcessComponentSecrets(t *testing.T) { t.Run("secret ref exists, kubernetes secret store, secret extracted", func(t *testing.T) { c := componentsapi.Component{ Spec: componentsapi.ComponentSpec{ - Metadata: []componentsapi.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "test1", - SecretKeyRef: componentsapi.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: "secret1", Key: "key1", }, @@ -138,10 +139,10 @@ func TestProcessComponentSecrets(t *testing.T) { t.Run("secret ref exists, default kubernetes secret store, secret extracted", func(t *testing.T) { c := componentsapi.Component{ Spec: componentsapi.ComponentSpec{ - Metadata: []componentsapi.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "test1", - SecretKeyRef: componentsapi.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: "secret1", Key: "key1", }, @@ -559,10 +560,10 @@ func TestProcessHTTPEndpointSecrets(t *testing.T) { e := httpendpointapi.HTTPEndpoint{ Spec: httpendpointapi.HTTPEndpointSpec{ BaseURL: "http://test.com/", - Headers: []httpendpointapi.Header{ + Headers: []commonapi.NameValuePair{ { Name: "test1", - SecretKeyRef: httpendpointapi.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: "secret1", Key: "key1", }, diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index c758f2afc233bd4d03d7c870b131865db527e3e3..d96cc110423842a005d4993adcde47f38dc73f44 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -52,11 +52,11 @@ import ( "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/structpb" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "github.com/dapr/dapr/pkg/actors" + commonapi "github.com/dapr/dapr/pkg/apis/common" componentsV1alpha1 "github.com/dapr/dapr/pkg/apis/components/v1alpha1" httpEndpointV1alpha1 "github.com/dapr/dapr/pkg/apis/httpEndpoint/v1alpha1" "github.com/dapr/dapr/pkg/apphealth" @@ -79,6 +79,7 @@ import ( "github.com/dapr/dapr/pkg/resiliency" runtimePubsub "github.com/dapr/dapr/pkg/runtime/pubsub" "github.com/dapr/dapr/pkg/runtime/security" + authConsts "github.com/dapr/dapr/pkg/runtime/security/consts" "github.com/dapr/dapr/pkg/runtime/wfengine" "github.com/dapr/dapr/pkg/scopes" "github.com/dapr/dapr/utils" @@ -1132,9 +1133,9 @@ func (a *DaprRuntime) beginComponentsUpdates() error { func (a *DaprRuntime) onComponentUpdated(component componentsV1alpha1.Component) bool { oldComp, exists := a.compStore.GetComponent(component.Spec.Type, component.Name) - newComp, _ := a.processComponentSecrets(component) + _, _ = a.processResourceSecrets(&component) - if exists && reflect.DeepEqual(oldComp.Spec, newComp.Spec) { + if exists && reflect.DeepEqual(oldComp.Spec, component.Spec) { return false } @@ -1229,16 +1230,13 @@ func (a *DaprRuntime) beginHTTPEndpointsUpdates() error { func (a *DaprRuntime) onHTTPEndpointUpdated(endpoint httpEndpointV1alpha1.HTTPEndpoint) bool { oldEndpoint, exists := a.compStore.GetHTTPEndpoint(endpoint.Name) - newEndpoint, _ := a.processHTTPEndpointSecrets(endpoint) + _, _ = a.processResourceSecrets(&endpoint) - if exists && reflect.DeepEqual(oldEndpoint.Spec, newEndpoint.Spec) { + if exists && reflect.DeepEqual(oldEndpoint.Spec, endpoint.Spec) { return false } a.pendingHTTPEndpoints <- endpoint - - log.Infof("http endpoint updated for http endpoint named: %s", endpoint.Name) - return true } @@ -1706,7 +1704,7 @@ func (a *DaprRuntime) isAppSubscribedToBinding(binding string) (bool, error) { return false, nil } -func isBindingOfDirection(direction string, metadata []componentsV1alpha1.MetadataItem) bool { +func isBindingOfDirection(direction string, metadata []commonapi.NameValuePair) bool { directionFound := false for _, m := range metadata { @@ -1870,7 +1868,7 @@ func (a *DaprRuntime) initState(s componentsV1alpha1.Component) error { return rterrors.NewInit(rterrors.CreateComponentFailure, fName, err) } if store != nil { - secretStoreName := a.authSecretStoreOrDefault(s) + secretStoreName := a.authSecretStoreOrDefault(&s) secretStore, _ := a.compStore.GetSecretStore(secretStoreName) encKeys, encErr := encryption.ComponentEncryptionKey(s, secretStore) @@ -2150,7 +2148,7 @@ func (a *DaprRuntime) BulkPublish(req *pubsub.BulkPublishRequest) (pubsub.BulkPu return runtimePubsub.ApplyBulkPublishResiliency(context.TODO(), req, policyDef, defaultBulkPublisher) } -func metadataContainsNamespace(items []componentsV1alpha1.MetadataItem) bool { +func metadataContainsNamespace(items []commonapi.NameValuePair) bool { for _, c := range items { val := c.Value.String() if strings.Contains(val, "{namespace}") { @@ -2695,8 +2693,8 @@ func (a *DaprRuntime) processHTTPEndpoints() { if endpoint.Name == "" { continue } - newEndpoint, _ := a.processHTTPEndpointSecrets(endpoint) - a.compStore.AddHTTPEndpoint(newEndpoint) + _, _ = a.processResourceSecrets(&endpoint) + a.compStore.AddHTTPEndpoint(endpoint) } } @@ -2792,8 +2790,7 @@ func (a *DaprRuntime) doProcessOneComponent(category components.Category, comp c } func (a *DaprRuntime) preprocessOneComponent(comp *componentsV1alpha1.Component) componentPreprocessRes { - var unreadySecretsStore string - *comp, unreadySecretsStore = a.processComponentSecrets(*comp) + _, unreadySecretsStore := a.processResourceSecrets(comp) if unreadySecretsStore != "" { return componentPreprocessRes{ unreadyDependency: componentDependency(components.CategorySecretStore, unreadySecretsStore), @@ -2983,17 +2980,69 @@ func (a *DaprRuntime) WaitUntilShutdown() error { return <-a.shutdownC } -// Returns the component updated with the secrets applied. -// If the component references a secret store that hasn't been loaded yet, it returns the name of the secret store component as second returned value. -func (a *DaprRuntime) processComponentSecrets(component componentsV1alpha1.Component) (componentsV1alpha1.Component, string) { +// Interface that applies to both Component and HTTPEndpoint resources. +type resourceWithMetadata interface { + Kind() string + GetName() string + GetNamespace() string + GetSecretStore() string + NameValuePairs() []commonapi.NameValuePair +} + +func isEnvVarAllowed(key string) bool { + // First, apply a denylist that blocks access to sensitive env vars + key = strings.ToUpper(key) + switch { + case key == "": + return false + case key == "APP_API_TOKEN": + return false + case strings.HasPrefix(key, "DAPR_"): + return false + case strings.Contains(key, " "): + return false + } + + // If we have a `DAPR_ENV_KEYS` env var (which is added by the Dapr Injector in Kubernetes mode), use that as allowlist too + allowlist := os.Getenv(authConsts.EnvKeysEnvVar) + if allowlist == "" { + return true + } + + // Need to check for the full var, so there must be a space after OR it must be the end of the string, and there must be a space before OR it must be at the beginning of the string + idx := strings.Index(allowlist, key) + if idx >= 0 && + (idx+len(key) == len(allowlist) || allowlist[idx+len(key)] == ' ') && + (idx == 0 || allowlist[idx-1] == ' ') { + return true + } + return false +} + +// Returns the component or HTTP endpoint updated with the secrets applied. +// If the resource references a secret store that hasn't been loaded yet, it returns the name of the secret store component as second returned value. +func (a *DaprRuntime) processResourceSecrets(resource resourceWithMetadata) (updated bool, secretStoreName string) { cache := map[string]secretstores.GetSecretResponse{} - for i, m := range component.Spec.Metadata { - if m.SecretKeyRef.Name == "" { + secretStoreName = a.authSecretStoreOrDefault(resource) + + metadata := resource.NameValuePairs() + for i, m := range metadata { + // If there's an env var and no value, use that + if !m.HasValue() && m.EnvRef != "" { + if isEnvVarAllowed(m.EnvRef) { + metadata[i].SetValue([]byte(os.Getenv(m.EnvRef))) + } else { + log.Warnf("%s %s references an env variable that isn't allowed: %s", resource.Kind(), resource.GetName(), m.EnvRef) + } + metadata[i].EnvRef = "" + updated = true continue } - secretStoreName := a.authSecretStoreOrDefault(component) + if m.SecretKeyRef.Name == "" { + continue + } // If running in Kubernetes and have an operator client, do not fetch secrets from the Kubernetes secret store as they will be populated by the operator. // Instead, base64 decode the secret values into their real self. @@ -3011,20 +3060,16 @@ func (a *DaprRuntime) processComponentSecrets(component componentsV1alpha1.Compo continue } - m.Value = componentsV1alpha1.DynamicValue{ - JSON: v1.JSON{ - Raw: dec, - }, - } - - component.Spec.Metadata[i] = m + metadata[i].SetValue(dec) + metadata[i].SecretKeyRef = commonapi.SecretKeyRef{} + updated = true continue } secretStore, ok := a.compStore.GetSecretStore(secretStoreName) if !ok { - log.Warnf("Component %s references a secret store that isn't loaded: %s", component.Name, secretStoreName) - return component, secretStoreName + log.Warnf("%s %s references a secret store that isn't loaded: %s", resource.Kind(), resource.GetName(), secretStoreName) + return updated, secretStoreName } resp, ok := cache[m.SecretKeyRef.Name] @@ -3033,7 +3078,7 @@ func (a *DaprRuntime) processComponentSecrets(component componentsV1alpha1.Compo r, err := secretStore.GetSecret(context.TODO(), secretstores.GetSecretRequest{ Name: m.SecretKeyRef.Name, Metadata: map[string]string{ - "namespace": component.ObjectMeta.Namespace, + "namespace": resource.GetNamespace(), }, }) if err != nil { @@ -3051,109 +3096,18 @@ func (a *DaprRuntime) processComponentSecrets(component componentsV1alpha1.Compo val, ok := resp.Data[secretKeyName] if ok && val != "" { - component.Spec.Metadata[i].Value = componentsV1alpha1.DynamicValue{ - JSON: v1.JSON{ - Raw: []byte(val), - }, - } + metadata[i].SetValue([]byte(val)) + metadata[i].SecretKeyRef = commonapi.SecretKeyRef{} + updated = true } cache[m.SecretKeyRef.Name] = resp } - return component, "" + return updated, "" } -// Returns the http endpoint updated with the secrets applied. -// If the http endpoint references a secret store that hasn't been loaded yet, it returns the name of the secret store component as second returned value. -func (a *DaprRuntime) processHTTPEndpointSecrets(endpoint httpEndpointV1alpha1.HTTPEndpoint) (httpEndpointV1alpha1.HTTPEndpoint, string) { - cache := map[string]secretstores.GetSecretResponse{} - - for i, header := range endpoint.Spec.Headers { - if header.SecretKeyRef.Name == "" { - continue - } - - secretStoreName := a.authSecretStoreOrDefault(endpoint) - - // If running in Kubernetes and have an operator client, do not fetch secrets from the Kubernetes secret store as they will be populated by the operator. - // Instead, base64 decode the secret values into their real self. - if a.operatorClient != nil && secretStoreName == secretstoresLoader.BuiltinKubernetesSecretStore { - var jsonVal string - err := json.Unmarshal(header.Value.Raw, &jsonVal) - if err != nil { - log.Errorf("Error decoding secret: %v", err) - continue - } - - dec, err := base64.StdEncoding.DecodeString(jsonVal) - if err != nil { - log.Errorf("Error decoding secret: %v", err) - continue - } - - header.Value = httpEndpointV1alpha1.DynamicValue{ - JSON: v1.JSON{ - Raw: dec, - }, - } - - endpoint.Spec.Headers[i] = header - continue - } - - secretStore, ok := a.compStore.GetSecretStore(secretStoreName) - if !ok { - log.Warnf("HTTP Endpoint %s references a secret store that isn't loaded: %s", endpoint.Name, secretStoreName) - return endpoint, secretStoreName - } - - resp, ok := cache[header.SecretKeyRef.Name] - if !ok { - // TODO: cascade context. - r, err := secretStore.GetSecret(context.TODO(), secretstores.GetSecretRequest{ - Name: header.SecretKeyRef.Name, - Metadata: map[string]string{ - "namespace": endpoint.ObjectMeta.Namespace, - }, - }) - if err != nil { - log.Errorf("Error getting secret: %v", err) - continue - } - resp = r - } - - // Use the SecretKeyRef.Name key if SecretKeyRef.Key is not given - secretKeyName := header.SecretKeyRef.Key - if secretKeyName == "" { - secretKeyName = header.SecretKeyRef.Name - } - - val, ok := resp.Data[secretKeyName] - if ok { - endpoint.Spec.Headers[i].Value = httpEndpointV1alpha1.DynamicValue{ - JSON: v1.JSON{ - Raw: []byte(val), - }, - } - } - - cache[header.SecretKeyRef.Name] = resp - } - return endpoint, "" -} - -func (a *DaprRuntime) authSecretStoreOrDefault(object interface{}) string { - var secretStore string - switch obj := object.(type) { - case componentsV1alpha1.Component: - secretStore = obj.SecretStore - case httpEndpointV1alpha1.HTTPEndpoint: - secretStore = obj.SecretStore - default: - // Handle unsupported types - return "" - } +func (a *DaprRuntime) authSecretStoreOrDefault(resource resourceWithMetadata) string { + secretStore := resource.GetSecretStore() if secretStore == "" { switch a.runtimeConfig.Mode { case modes.KubernetesMode: @@ -3387,7 +3341,7 @@ func (a *DaprRuntime) initSecretStore(c componentsV1alpha1.Component) error { return nil } -func (a *DaprRuntime) convertMetadataItemsToProperties(items []componentsV1alpha1.MetadataItem) map[string]string { +func (a *DaprRuntime) convertMetadataItemsToProperties(items []commonapi.NameValuePair) map[string]string { properties := map[string]string{} for _, c := range items { val := c.Value.String() diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index df8ace75fcf1bf4b9abf114bfe4785f1e15001ac..028ec546f0883f4507059cfadbd31b4e0a762f71 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -64,6 +64,7 @@ import ( "github.com/dapr/components-contrib/pubsub" "github.com/dapr/components-contrib/secretstores" "github.com/dapr/components-contrib/state" + commonapi "github.com/dapr/dapr/pkg/apis/common" componentsV1alpha1 "github.com/dapr/dapr/pkg/apis/components/v1alpha1" httpEndpointV1alpha1 "github.com/dapr/dapr/pkg/apis/httpEndpoint/v1alpha1" "github.com/dapr/dapr/pkg/apis/resiliency/v1alpha1" @@ -96,6 +97,7 @@ import ( rterrors "github.com/dapr/dapr/pkg/runtime/errors" runtimePubsub "github.com/dapr/dapr/pkg/runtime/pubsub" "github.com/dapr/dapr/pkg/runtime/security" + authConsts "github.com/dapr/dapr/pkg/runtime/security/consts" "github.com/dapr/dapr/pkg/scopes" sentryConsts "github.com/dapr/dapr/pkg/sentry/consts" daprt "github.com/dapr/dapr/pkg/testing" @@ -407,10 +409,10 @@ func TestDoProcessComponent(t *testing.T) { ) lockComponentWithWrongStrategy := lockComponent - lockComponentWithWrongStrategy.Spec.Metadata = []componentsV1alpha1.MetadataItem{ + lockComponentWithWrongStrategy.Spec.Metadata = []commonapi.NameValuePair{ { Name: "keyPrefix", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("||")}, }, }, @@ -824,16 +826,16 @@ func TestInitState(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "state.mockState", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: actorStateStore, - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("true")}, }, }, { Name: "primaryEncryptionKey", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte(primaryKey)}, }, }, @@ -1113,16 +1115,16 @@ func TestMetadataUUID(t *testing.T) { pubsubComponent.Spec.Metadata = append( pubsubComponent.Spec.Metadata, - componentsV1alpha1.MetadataItem{ + commonapi.NameValuePair{ Name: "consumerID", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("{uuid}"), }, }, - }, componentsV1alpha1.MetadataItem{ + }, commonapi.NameValuePair{ Name: "twoUUIDs", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("{uuid} {uuid}"), }, @@ -1176,9 +1178,9 @@ func TestMetadataPodName(t *testing.T) { pubsubComponent.Spec.Metadata = append( pubsubComponent.Spec.Metadata, - componentsV1alpha1.MetadataItem{ + commonapi.NameValuePair{ Name: "consumerID", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("{podName}"), }, @@ -1222,9 +1224,9 @@ func TestMetadataNamespace(t *testing.T) { pubsubComponent.Spec.Metadata = append( pubsubComponent.Spec.Metadata, - componentsV1alpha1.MetadataItem{ + commonapi.NameValuePair{ Name: "consumerID", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("{namespace}"), }, @@ -1269,9 +1271,9 @@ func TestMetadataAppID(t *testing.T) { pubsubComponent.Spec.Metadata = append( pubsubComponent.Spec.Metadata, - componentsV1alpha1.MetadataItem{ + commonapi.NameValuePair{ Name: "clientID", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("{appID} {appID}"), }, @@ -1313,10 +1315,10 @@ func TestOnComponentUpdated(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "pubsub.mockPubSub", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "name1", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("value1"), }, @@ -1337,10 +1339,10 @@ func TestOnComponentUpdated(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "pubsub.mockPubSub", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "name1", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("value2"), }, @@ -1362,10 +1364,10 @@ func TestOnComponentUpdated(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "pubsub.mockPubSub", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "name1", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("value1"), }, @@ -1386,10 +1388,10 @@ func TestOnComponentUpdated(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "pubsub.mockPubSub", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "name1", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("value1"), }, @@ -1404,10 +1406,10 @@ func TestOnComponentUpdated(t *testing.T) { } func TestConsumerID(t *testing.T) { - metadata := []componentsV1alpha1.MetadataItem{ + metadata := []commonapi.NameValuePair{ { Name: "host", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("localhost"), }, @@ -1415,7 +1417,7 @@ func TestConsumerID(t *testing.T) { }, { Name: "password", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("fakePassword"), }, @@ -2303,8 +2305,8 @@ func TestMiddlewareBuildPipeline(t *testing.T) { Type: "middleware.http.fakemw", Version: "v1", IgnoreErrors: ignoreErrors, - Metadata: []componentsV1alpha1.MetadataItem{ - {Name: "fail", Value: componentsV1alpha1.DynamicValue{JSON: v1.JSON{Raw: []byte("true")}}}, + Metadata: []commonapi.NameValuePair{ + {Name: "fail", Value: commonapi.DynamicValue{JSON: v1.JSON{Raw: []byte("true")}}}, }, }, }) @@ -2371,10 +2373,10 @@ func TestMetadataItemsToPropertiesConversion(t *testing.T) { t.Run("string", func(t *testing.T) { rt := NewTestDaprRuntime(modes.StandaloneMode) defer stopRuntime(t, rt) - items := []componentsV1alpha1.MetadataItem{ + items := []commonapi.NameValuePair{ { Name: "a", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("b")}, }, }, @@ -2387,10 +2389,10 @@ func TestMetadataItemsToPropertiesConversion(t *testing.T) { t.Run("int", func(t *testing.T) { rt := NewTestDaprRuntime(modes.StandaloneMode) defer stopRuntime(t, rt) - items := []componentsV1alpha1.MetadataItem{ + items := []commonapi.NameValuePair{ { Name: "a", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte(strconv.Itoa(6))}, }, }, @@ -2403,10 +2405,10 @@ func TestMetadataItemsToPropertiesConversion(t *testing.T) { t.Run("bool", func(t *testing.T) { rt := NewTestDaprRuntime(modes.StandaloneMode) defer stopRuntime(t, rt) - items := []componentsV1alpha1.MetadataItem{ + items := []commonapi.NameValuePair{ { Name: "a", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("true")}, }, }, @@ -2419,10 +2421,10 @@ func TestMetadataItemsToPropertiesConversion(t *testing.T) { t.Run("float", func(t *testing.T) { rt := NewTestDaprRuntime(modes.StandaloneMode) defer stopRuntime(t, rt) - items := []componentsV1alpha1.MetadataItem{ + items := []commonapi.NameValuePair{ { Name: "a", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("5.5")}, }, }, @@ -2435,10 +2437,10 @@ func TestMetadataItemsToPropertiesConversion(t *testing.T) { t.Run("JSON string", func(t *testing.T) { rt := NewTestDaprRuntime(modes.StandaloneMode) defer stopRuntime(t, rt) - items := []componentsV1alpha1.MetadataItem{ + items := []commonapi.NameValuePair{ { Name: "a", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte(`"hello there"`)}, }, }, @@ -2473,43 +2475,30 @@ func TestPopulateSecretsConfiguration(t *testing.T) { }) } -func TestProcessComponentSecrets(t *testing.T) { - mockBinding := componentsV1alpha1.Component{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "mockBinding", - }, - Spec: componentsV1alpha1.ComponentSpec{ - Type: "bindings.mock", - Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ - { - Name: "a", - SecretKeyRef: componentsV1alpha1.SecretKeyRef{ - Key: "key1", - Name: "name1", - }, - }, - { - Name: "b", - Value: componentsV1alpha1.DynamicValue{ - JSON: v1.JSON{Raw: []byte("value2")}, - }, - }, +func TestProcessResourceSecrets(t *testing.T) { + createMockBinding := func() *componentsV1alpha1.Component { + return &componentsV1alpha1.Component{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "mockBinding", }, - }, - Auth: componentsV1alpha1.Auth{ - SecretStore: secretstoresLoader.BuiltinKubernetesSecretStore, - }, + Spec: componentsV1alpha1.ComponentSpec{ + Type: "bindings.mock", + Version: "v1", + Metadata: []commonapi.NameValuePair{}, + }, + } } t.Run("Standalone Mode", func(t *testing.T) { - mockBinding.Spec.Metadata[0].Value = componentsV1alpha1.DynamicValue{ - JSON: v1.JSON{Raw: []byte("")}, - } - mockBinding.Spec.Metadata[0].SecretKeyRef = componentsV1alpha1.SecretKeyRef{ - Key: "key1", - Name: "name1", - } + mockBinding := createMockBinding() + mockBinding.Spec.Metadata = append(mockBinding.Spec.Metadata, commonapi.NameValuePair{ + Name: "a", + SecretKeyRef: commonapi.SecretKeyRef{ + Key: "key1", + Name: "name1", + }, + }) + mockBinding.Auth.SecretStore = secretstoresLoader.BuiltinKubernetesSecretStore rt := NewTestDaprRuntime(modes.StandaloneMode) defer stopRuntime(t, rt) @@ -2532,18 +2521,20 @@ func TestProcessComponentSecrets(t *testing.T) { }, }) - mod, unready := rt.processComponentSecrets(mockBinding) - assert.Equal(t, "value1", mod.Spec.Metadata[0].Value.String()) + updated, unready := rt.processResourceSecrets(mockBinding) + assert.True(t, updated) + assert.Equal(t, "value1", mockBinding.Spec.Metadata[0].Value.String()) assert.Empty(t, unready) }) t.Run("Look up name only", func(t *testing.T) { - mockBinding.Spec.Metadata[0].Value = componentsV1alpha1.DynamicValue{ - JSON: v1.JSON{Raw: []byte("")}, - } - mockBinding.Spec.Metadata[0].SecretKeyRef = componentsV1alpha1.SecretKeyRef{ - Name: "name1", - } + mockBinding := createMockBinding() + mockBinding.Spec.Metadata = append(mockBinding.Spec.Metadata, commonapi.NameValuePair{ + Name: "a", + SecretKeyRef: commonapi.SecretKeyRef{ + Name: "name1", + }, + }) mockBinding.Auth.SecretStore = "mock" rt := NewTestDaprRuntime(modes.KubernetesMode) @@ -2568,8 +2559,53 @@ func TestProcessComponentSecrets(t *testing.T) { }) assert.NoError(t, err) - mod, unready := rt.processComponentSecrets(mockBinding) - assert.Equal(t, "value1", mod.Spec.Metadata[0].Value.String()) + updated, unready := rt.processResourceSecrets(mockBinding) + assert.True(t, updated) + assert.Equal(t, "value1", mockBinding.Spec.Metadata[0].Value.String()) + assert.Empty(t, unready) + }) + + t.Run("Secret from env", func(t *testing.T) { + t.Setenv("MY_ENV_VAR", "ciao mondo") + + mockBinding := createMockBinding() + mockBinding.Spec.Metadata = append(mockBinding.Spec.Metadata, commonapi.NameValuePair{ + Name: "a", + EnvRef: "MY_ENV_VAR", + }) + + rt := NewTestDaprRuntime(modes.StandaloneMode) + defer stopRuntime(t, rt) + + updated, unready := rt.processResourceSecrets(mockBinding) + assert.True(t, updated) + assert.Equal(t, "ciao mondo", mockBinding.Spec.Metadata[0].Value.String()) + assert.Empty(t, unready) + }) + + t.Run("Disallowed env var", func(t *testing.T) { + t.Setenv("APP_API_TOKEN", "test") + t.Setenv("DAPR_KEY", "test") + + mockBinding := createMockBinding() + mockBinding.Spec.Metadata = append(mockBinding.Spec.Metadata, + commonapi.NameValuePair{ + Name: "a", + EnvRef: "DAPR_KEY", + }, + commonapi.NameValuePair{ + Name: "b", + EnvRef: "APP_API_TOKEN", + }, + ) + + rt := NewTestDaprRuntime(modes.StandaloneMode) + defer stopRuntime(t, rt) + + updated, unready := rt.processResourceSecrets(mockBinding) + assert.True(t, updated) + assert.Equal(t, "", mockBinding.Spec.Metadata[0].Value.String()) + assert.Equal(t, "", mockBinding.Spec.Metadata[1].Value.String()) assert.Empty(t, unready) }) } @@ -2702,10 +2738,10 @@ func TestFlushOutstandingComponent(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "secretstores.kubernetesMockGrandChild", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "a", - SecretKeyRef: componentsV1alpha1.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Key: "key1", Name: "name1", }, @@ -2723,10 +2759,10 @@ func TestFlushOutstandingComponent(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "secretstores.kubernetesMockChild", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "a", - SecretKeyRef: componentsV1alpha1.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Key: "key1", Name: "name1", }, @@ -3536,7 +3572,7 @@ func TestPubsubLifecycle(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "pubsub.mockPubSubAlpha", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{}, + Metadata: []commonapi.NameValuePair{}, }, } comp2 := componentsV1alpha1.Component{ @@ -3546,7 +3582,7 @@ func TestPubsubLifecycle(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "pubsub.mockPubSubBeta", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{}, + Metadata: []commonapi.NameValuePair{}, }, } comp3 := componentsV1alpha1.Component{ @@ -3556,7 +3592,7 @@ func TestPubsubLifecycle(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "pubsub.mockPubSubBeta", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{}, + Metadata: []commonapi.NameValuePair{}, }, } @@ -4246,11 +4282,11 @@ func getFakeProperties() map[string]string { } } -func getFakeMetadataItems() []componentsV1alpha1.MetadataItem { - return []componentsV1alpha1.MetadataItem{ +func getFakeMetadataItems() []commonapi.NameValuePair { + return []commonapi.NameValuePair{ { Name: "host", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("localhost"), }, @@ -4258,7 +4294,7 @@ func getFakeMetadataItems() []componentsV1alpha1.MetadataItem { }, { Name: "password", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("fakePassword"), }, @@ -4266,7 +4302,7 @@ func getFakeMetadataItems() []componentsV1alpha1.MetadataItem { }, { Name: "consumerID", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte(TestRuntimeConfigID), }, @@ -4274,7 +4310,7 @@ func getFakeMetadataItems() []componentsV1alpha1.MetadataItem { }, { Name: scopes.SubscriptionScopes, - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte(fmt.Sprintf("%s=topic0,topic1", TestRuntimeConfigID)), }, @@ -4282,7 +4318,7 @@ func getFakeMetadataItems() []componentsV1alpha1.MetadataItem { }, { Name: scopes.PublishingScopes, - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte(fmt.Sprintf("%s=topic0,topic1", TestRuntimeConfigID)), }, @@ -5415,10 +5451,10 @@ func TestStopWithErrors(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "bindings.output", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "output", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{}, }, }, @@ -5432,10 +5468,10 @@ func TestStopWithErrors(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "pubsub.pubsub", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "pubsub", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{}, }, }, @@ -5449,10 +5485,10 @@ func TestStopWithErrors(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "state.statestore", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "statestore", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{}, }, }, @@ -5466,10 +5502,10 @@ func TestStopWithErrors(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "secretstores.secretstore", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: "secretstore", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{}, }, }, @@ -5796,9 +5832,9 @@ func matchDaprRequestMethod(method string) any { func TestMetadataContainsNamespace(t *testing.T) { t.Run("namespace field present", func(t *testing.T) { r := metadataContainsNamespace( - []componentsV1alpha1.MetadataItem{ + []commonapi.NameValuePair{ { - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("{namespace}")}, }, }, @@ -5810,7 +5846,7 @@ func TestMetadataContainsNamespace(t *testing.T) { t.Run("namespace field not present", func(t *testing.T) { r := metadataContainsNamespace( - []componentsV1alpha1.MetadataItem{ + []commonapi.NameValuePair{ {}, }, ) @@ -5938,16 +5974,16 @@ func TestGracefulShutdownActors(t *testing.T) { Spec: componentsV1alpha1.ComponentSpec{ Type: "state.mockState", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{ + Metadata: []commonapi.NameValuePair{ { Name: strings.ToUpper(actorStateStore), - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte("true")}, }, }, { Name: "primaryEncryptionKey", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{Raw: []byte(encryptKey)}, }, }, @@ -6139,26 +6175,77 @@ func TestHTTPEndpointsUpdate(t *testing.T) { assert.True(t, exists, fmt.Sprintf("expect http endpoint with name: %s", endpoint3.Name)) } +func TestIsEnvVarAllowed(t *testing.T) { + t.Run("no allowlist", func(t *testing.T) { + tests := []struct { + name string + key string + want bool + }{ + {name: "empty string is not allowed", key: "", want: false}, + {name: "key is allowed", key: "FOO", want: true}, + {name: "keys starting with DAPR_ are denied", key: "DAPR_TEST", want: false}, + {name: "APP_API_TOKEN is denied", key: "APP_API_TOKEN", want: false}, + {name: "keys with a space are denied", key: "FOO BAR", want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isEnvVarAllowed(tt.key); got != tt.want { + t.Errorf("isEnvVarAllowed(%q) = %v, want %v", tt.key, got, tt.want) + } + }) + } + }) + + t.Run("with allowlist", func(t *testing.T) { + t.Setenv(authConsts.EnvKeysEnvVar, "FOO BAR TEST") + + tests := []struct { + name string + key string + want bool + }{ + {name: "FOO is allowed", key: "FOO", want: true}, + {name: "BAR is allowed", key: "BAR", want: true}, + {name: "TEST is allowed", key: "TEST", want: true}, + {name: "FO is not allowed", key: "FO", want: false}, + {name: "EST is not allowed", key: "EST", want: false}, + {name: "BA is not allowed", key: "BA", want: false}, + {name: "AR is not allowed", key: "AR", want: false}, + {name: "keys starting with DAPR_ are denied", key: "DAPR_TEST", want: false}, + {name: "APP_API_TOKEN is denied", key: "APP_API_TOKEN", want: false}, + {name: "keys with a space are denied", key: "FOO BAR", want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isEnvVarAllowed(tt.key); got != tt.want { + t.Errorf("isEnvVarAllowed(%q) = %v, want %v", tt.key, got, tt.want) + } + }) + } + }) +} + func TestIsBindingOfDirection(t *testing.T) { t.Run("no direction in metadata for input binding", func(t *testing.T) { - m := []componentsV1alpha1.MetadataItem{} + m := []commonapi.NameValuePair{} r := isBindingOfDirection("input", m) assert.True(t, r) }) t.Run("no direction in metadata for output binding", func(t *testing.T) { - m := []componentsV1alpha1.MetadataItem{} + m := []commonapi.NameValuePair{} r := isBindingOfDirection("output", m) assert.True(t, r) }) t.Run("input direction in metadata", func(t *testing.T) { - m := []componentsV1alpha1.MetadataItem{ + m := []commonapi.NameValuePair{ { Name: "direction", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("input"), }, @@ -6173,10 +6260,10 @@ func TestIsBindingOfDirection(t *testing.T) { }) t.Run("output direction in metadata", func(t *testing.T) { - m := []componentsV1alpha1.MetadataItem{ + m := []commonapi.NameValuePair{ { Name: "direction", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("output"), }, @@ -6191,10 +6278,10 @@ func TestIsBindingOfDirection(t *testing.T) { }) t.Run("input and output direction in metadata", func(t *testing.T) { - m := []componentsV1alpha1.MetadataItem{ + m := []commonapi.NameValuePair{ { Name: "direction", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("input, output"), }, @@ -6209,10 +6296,10 @@ func TestIsBindingOfDirection(t *testing.T) { }) t.Run("invalid direction for input binding", func(t *testing.T) { - m := []componentsV1alpha1.MetadataItem{ + m := []commonapi.NameValuePair{ { Name: "direction", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("aaa"), }, @@ -6224,10 +6311,10 @@ func TestIsBindingOfDirection(t *testing.T) { }) t.Run("invalid direction for output binding", func(t *testing.T) { - m := []componentsV1alpha1.MetadataItem{ + m := []commonapi.NameValuePair{ { Name: "direction", - Value: componentsV1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte("aaa"), }, diff --git a/pkg/runtime/security/consts/consts.go b/pkg/runtime/security/consts/consts.go index b6e77223bab9dbec0c6a8edc322eb93b7ab7c642..8052a3ce878d6087db0e1056a82748493ecce430 100644 --- a/pkg/runtime/security/consts/consts.go +++ b/pkg/runtime/security/consts/consts.go @@ -15,9 +15,11 @@ package consts /* #nosec. */ const ( - // APITokenEnvVar is the environment variable for the api token. + // Env var for the API token. APITokenEnvVar = "DAPR_API_TOKEN" AppAPITokenEnvVar = "APP_API_TOKEN" - // APITokenHeader is header name for http/gRPC calls to hold the token. + // Header name for HTTP/gRPC calls to hold the token. APITokenHeader = "dapr-api-token" + // Name of the variable injected in the daprd container with the list of injected env vars. + EnvKeysEnvVar = "DAPR_ENV_KEYS" ) diff --git a/pkg/runtime/wfengine/component.go b/pkg/runtime/wfengine/component.go index 01a7e483adc21a9a29b8707e2b0c0bde35100f53..98538dad9aa37bf741c0f1c180aa77b3ca79c4ec 100644 --- a/pkg/runtime/wfengine/component.go +++ b/pkg/runtime/wfengine/component.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/dapr/components-contrib/workflows" + commonapi "github.com/dapr/dapr/pkg/apis/common" componentsV1alpha1 "github.com/dapr/dapr/pkg/apis/components/v1alpha1" // This will be removed "github.com/dapr/kit/logger" ) @@ -39,7 +40,7 @@ var ComponentDefinition = componentsV1alpha1.Component{ Spec: componentsV1alpha1.ComponentSpec{ Type: "workflow.dapr", Version: "v1", - Metadata: []componentsV1alpha1.MetadataItem{}, + Metadata: []commonapi.NameValuePair{}, }, } diff --git a/tests/config/dapr_cron_binding.yaml b/tests/config/dapr_cron_binding.yaml index 2fcb5f2c6315b1e86819d98d1dce5bc10451e5a4..a4e6fb32ac9b021ca312df42f129dfc1b6c96dbb 100644 --- a/tests/config/dapr_cron_binding.yaml +++ b/tests/config/dapr_cron_binding.yaml @@ -19,7 +19,11 @@ spec: type: bindings.cron version: v1 metadata: - - name: schedule - value: "@every 1s" - - name: direction - value: input + - name: schedule + envRef: "CRON_SCHEDULE" + - name: direction + value: input +scopes: + - healthapp-http + - healthapp-grpc + - healthapp-h2c diff --git a/tests/e2e/healthcheck/healthcheck_test.go b/tests/e2e/healthcheck/healthcheck_test.go index 85eccc4b2770a45e18ae44435530eb5dc6fd5426..5126e06eda2e2d50a63976c6b22cd0977579a524 100644 --- a/tests/e2e/healthcheck/healthcheck_test.go +++ b/tests/e2e/healthcheck/healthcheck_test.go @@ -64,6 +64,7 @@ func TestMain(m *testing.M) { AppHealthProbeInterval: 3, AppHealthProbeTimeout: 200, AppHealthThreshold: 3, + DaprEnv: `CRON_SCHEDULE="@every 1s"`, // Test envRef for components AppEnv: map[string]string{ "APP_PORT": "4000", "CONTROL_PORT": "3000", @@ -85,6 +86,7 @@ func TestMain(m *testing.M) { AppHealthProbeInterval: 3, AppHealthProbeTimeout: 200, AppHealthThreshold: 3, + DaprEnv: `CRON_SCHEDULE="@every 1s"`, AppEnv: map[string]string{ "APP_PORT": "4000", "CONTROL_PORT": "3000", @@ -106,6 +108,7 @@ func TestMain(m *testing.M) { AppHealthProbeInterval: 3, AppHealthProbeTimeout: 200, AppHealthThreshold: 3, + DaprEnv: `CRON_SCHEDULE="@every 1s"`, AppEnv: map[string]string{ "APP_PORT": "4000", "CONTROL_PORT": "3000", diff --git a/tests/platforms/kubernetes/daprcomponent.go b/tests/platforms/kubernetes/daprcomponent.go index b1eafd884da199a5eb3b09c2f3eac3fb7cacb840..a630c0e9623d8241d3df62e1f15bf62adf123f6d 100644 --- a/tests/platforms/kubernetes/daprcomponent.go +++ b/tests/platforms/kubernetes/daprcomponent.go @@ -19,6 +19,7 @@ import ( v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + commonapi "github.com/dapr/dapr/pkg/apis/common" v1alpha1 "github.com/dapr/dapr/pkg/apis/components/v1alpha1" ) @@ -40,24 +41,24 @@ func NewDaprComponent(client *KubeClient, ns string, comp ComponentDescription) // toComponentSpec builds the componentSpec for the given ComponentDescription func (do *DaprComponent) toComponentSpec() *v1alpha1.Component { - metadata := []v1alpha1.MetadataItem{} + metadata := []commonapi.NameValuePair{} for k, v := range do.component.MetaData { - var item v1alpha1.MetadataItem + var item commonapi.NameValuePair if v.FromSecretRef == nil { - item = v1alpha1.MetadataItem{ + item = commonapi.NameValuePair{ Name: k, - Value: v1alpha1.DynamicValue{ + Value: commonapi.DynamicValue{ JSON: v1.JSON{ Raw: []byte(v.Raw), }, }, } } else { - item = v1alpha1.MetadataItem{ + item = commonapi.NameValuePair{ Name: k, - SecretKeyRef: v1alpha1.SecretKeyRef{ + SecretKeyRef: commonapi.SecretKeyRef{ Name: v.FromSecretRef.Name, Key: v.FromSecretRef.Key, }, diff --git a/tests/platforms/kubernetes/kubeobj.go b/tests/platforms/kubernetes/kubeobj.go index 44d9bd7db09949d9ceb08c72d29986d7f3b66388..1608d5f6bee5690b7a463820584436ab87e4cf19 100644 --- a/tests/platforms/kubernetes/kubeobj.go +++ b/tests/platforms/kubernetes/kubeobj.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + commonapi "github.com/dapr/dapr/pkg/apis/common" v1alpha1 "github.com/dapr/dapr/pkg/apis/components/v1alpha1" "github.com/dapr/dapr/utils" ) @@ -340,7 +341,7 @@ func buildServiceObject(namespace string, appDesc AppDescription) *apiv1.Service } // buildDaprComponentObject creates dapr component object. -func buildDaprComponentObject(componentName string, typeName string, scopes []string, annotations map[string]string, metaData []v1alpha1.MetadataItem) *v1alpha1.Component { +func buildDaprComponentObject(componentName string, typeName string, scopes []string, annotations map[string]string, metaData []commonapi.NameValuePair) *v1alpha1.Component { return &v1alpha1.Component{ TypeMeta: metav1.TypeMeta{ Kind: DaprComponentsKind, @@ -353,7 +354,7 @@ func buildDaprComponentObject(componentName string, typeName string, scopes []st Type: typeName, Metadata: metaData, }, - Scopes: scopes, + Scoped: commonapi.Scoped{Scopes: scopes}, } } diff --git a/tools/codegen.mk b/tools/codegen.mk index b51caff42091691888e6227e0c899dd4cbbc1643..ad15a7d63c321004439670e894825206ad9a14f3 100644 --- a/tools/codegen.mk +++ b/tools/codegen.mk @@ -9,7 +9,9 @@ endif # Generate code code-generate: controller-gen $(CONTROLLER_GEN) object:headerFile="./tools/boilerplate.go.txt" \ - crd:trivialVersions=true crd:crdVersions=v1 paths="./pkg/apis/..." output:crd:artifacts:config=config/crd/bases + crd:crdVersions=v1 paths="./pkg/apis/..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) object:headerFile="./tools/boilerplate.go.txt" \ + paths="./pkg/apis/..." # find or download controller-gen # download controller-gen if necessary