mirror of https://github.com/dapr/dapr.git
[1.12] Change injector and sentry to GET daprsystem Configuration (#7116)
* Change injector and sentry to GET daprsystem Configuration In Kubernetes mode, when the Injector is patching a Pod to determine whether mTLS is enabled and Sentry on startup] fetches the global daprsystem Configuration, they do so by listing all Configurations in all namespaces and then match on the first Configuration with the name daprsystem. As Namespaces are sorted alphabetically when listed, the Configuration chosen by these services may not be the one located in the Dapr System namespace. This means that a malicious actor, or by accident a user of a Dapr enabled Kubernetes cluster, with write permissions to Configurations in a namespace which is alphabetically higher than the Dapr system namespace is able to override the global config for Sentry and Injector. We can expect that users of Dapr in Kubernertes would be able to have permissions to Configurations in order for them to control their Dapr deployment configuration. PR updates the injector and sentry services to GET the daprsystem Configuration in the Dapr control plane namespace. Signed-off-by: joshvanl <me@joshvanl.dev> * Fix RBAC for new daprsystem config get Signed-off-by: joshvanl <me@joshvanl.dev> * Return error if NAMESPACE is set but empty in `CurrentNamespaceOrError` Signed-off-by: joshvanl <me@joshvanl.dev> --------- Signed-off-by: joshvanl <me@joshvanl.dev>
This commit is contained in:
parent
63fb8d27a3
commit
6af5d4367d
|
@ -26,7 +26,7 @@ rules:
|
|||
resourceNames: ["dapr-sidecar-injector"]
|
||||
{{- if not .Values.global.rbac.namespaced }}
|
||||
- apiGroups: ["dapr.io"]
|
||||
resources: ["configurations", "components"]
|
||||
resources: ["components"]
|
||||
verbs: [ "get", "list"]
|
||||
{{- end }}
|
||||
---
|
||||
|
@ -63,9 +63,12 @@ rules:
|
|||
resourceNames: ["dapr-trust-bundle"]
|
||||
{{- if eq .Values.global.rbac.namespaced true }}
|
||||
- apiGroups: ["dapr.io"]
|
||||
resources: ["configurations", "components"]
|
||||
resources: ["components"]
|
||||
verbs: [ "get", "list"]
|
||||
{{- end }}
|
||||
- apiGroups: ["dapr.io"]
|
||||
resources: ["configurations"]
|
||||
verbs: [ "get" ]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
|
|
@ -64,11 +64,9 @@ rules:
|
|||
resources: ["configmaps"]
|
||||
verbs: ["get", "update", "watch", "list"]
|
||||
resourceNames: ["dapr-trust-bundle"]
|
||||
{{- if eq .Values.global.rbac.namespaced true }}
|
||||
- apiGroups: ["dapr.io"]
|
||||
resources: ["configurations"]
|
||||
verbs: ["list"]
|
||||
{{- end }}
|
||||
verbs: ["list", "get", "watch"]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
|
|
@ -82,10 +82,15 @@ func main() {
|
|||
log.Fatalf("Failed to get authentication uids from services accounts: %s", err)
|
||||
}
|
||||
|
||||
namespace, err := security.CurrentNamespaceOrError()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get current namespace: %s", err)
|
||||
}
|
||||
|
||||
secProvider, err := security.New(ctx, security.Options{
|
||||
SentryAddress: cfg.SentryAddress,
|
||||
ControlPlaneTrustDomain: cfg.ControlPlaneTrustDomain,
|
||||
ControlPlaneNamespace: security.CurrentNamespace(),
|
||||
ControlPlaneNamespace: namespace,
|
||||
TrustAnchorsFile: cfg.TrustAnchorsFile,
|
||||
AppID: "dapr-injector",
|
||||
MTLSEnabled: true,
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
scheme "github.com/dapr/dapr/pkg/client/clientset/versioned"
|
||||
|
@ -65,7 +66,7 @@ func (i *injector) getPodPatchOperations(ctx context.Context, ar *admissionv1.Ad
|
|||
sidecar.GetInjectedComponentContainers = i.getInjectedComponentContainers
|
||||
sidecar.Mode = injectorConsts.ModeKubernetes
|
||||
sidecar.Namespace = ar.Request.Namespace
|
||||
sidecar.MTLSEnabled = mTLSEnabled(i.daprClient)
|
||||
sidecar.MTLSEnabled = mTLSEnabled(i.controlPlaneNamespace, i.daprClient)
|
||||
sidecar.Identity = ar.Request.Namespace + ":" + pod.Spec.ServiceAccountName
|
||||
sidecar.IgnoreEntrypointTolerations = i.config.GetIgnoreEntrypointTolerations()
|
||||
sidecar.ImagePullPolicy = i.config.GetPullPolicy()
|
||||
|
@ -103,20 +104,19 @@ func (i *injector) getPodPatchOperations(ctx context.Context, ar *admissionv1.Ad
|
|||
return patch, nil
|
||||
}
|
||||
|
||||
func mTLSEnabled(daprClient scheme.Interface) bool {
|
||||
func mTLSEnabled(controlPlaneNamespace string, daprClient scheme.Interface) bool {
|
||||
resp, err := daprClient.ConfigurationV1alpha1().
|
||||
Configurations(metav1.NamespaceAll).
|
||||
List(metav1.ListOptions{})
|
||||
Configurations(controlPlaneNamespace).
|
||||
Get(defaultConfig, metav1.GetOptions{})
|
||||
if !apierrors.IsNotFound(err) {
|
||||
log.Infof("Dapr system configuration '%s' does not exist; using default value %t for mTLSEnabled", defaultConfig, defaultMtlsEnabled)
|
||||
return defaultMtlsEnabled
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Failed to load dapr configuration from k8s, use default value %t for mTLSEnabled: %s", defaultMtlsEnabled, err)
|
||||
return defaultMtlsEnabled
|
||||
}
|
||||
|
||||
for _, c := range resp.Items {
|
||||
if c.GetName() == defaultConfig {
|
||||
return c.Spec.MTLSSpec.GetEnabled()
|
||||
}
|
||||
}
|
||||
log.Infof("Dapr system configuration '%s' does not exist; using default value %t for mTLSEnabled", defaultConfig, defaultMtlsEnabled)
|
||||
return defaultMtlsEnabled
|
||||
return resp.Spec.MTLSSpec.GetEnabled()
|
||||
}
|
||||
|
|
|
@ -31,9 +31,14 @@ func LoadConfiguration(ctx context.Context, name string, restConfig *rest.Config
|
|||
return nil, fmt.Errorf("could not get Kubernetes API client: %w", err)
|
||||
}
|
||||
|
||||
namespace, err := security.CurrentNamespaceOrError()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var conf v1alpha1.Configuration
|
||||
key := types.NamespacedName{
|
||||
Namespace: security.CurrentNamespace(),
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
}
|
||||
if err := client.Get(ctx, key, &conf); err != nil {
|
||||
|
|
|
@ -408,6 +408,21 @@ func CurrentNamespace() string {
|
|||
return namespace
|
||||
}
|
||||
|
||||
// CurrentNamespaceOrError returns the namespace of this workload. If current
|
||||
// Namespace is not found, error.
|
||||
func CurrentNamespaceOrError() (string, error) {
|
||||
namespace, ok := os.LookupEnv("NAMESPACE")
|
||||
if !ok {
|
||||
return "", errors.New("'NAMESPACE' environment variable not set")
|
||||
}
|
||||
|
||||
if len(namespace) == 0 {
|
||||
return "", errors.New("'NAMESPACE' environment variable is empty")
|
||||
}
|
||||
|
||||
return namespace, nil
|
||||
}
|
||||
|
||||
// SentryID returns the SPIFFE ID of the sentry server.
|
||||
func SentryID(sentryTrustDomain spiffeid.TrustDomain, sentryNamespace string) (spiffeid.ID, error) {
|
||||
sentryID, err := spiffeid.FromSegments(sentryTrustDomain, "ns", sentryNamespace, "dapr-sentry")
|
||||
|
|
|
@ -169,3 +169,32 @@ func Test_Start(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCurrentNamespace(t *testing.T) {
|
||||
t.Run("error is namespace is not set", func(t *testing.T) {
|
||||
osns, ok := os.LookupEnv("NAMESPACE")
|
||||
os.Unsetenv("NAMESPACE")
|
||||
t.Cleanup(func() {
|
||||
if ok {
|
||||
os.Setenv("NAMESPACE", osns)
|
||||
}
|
||||
})
|
||||
ns, err := CurrentNamespaceOrError()
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, ns)
|
||||
})
|
||||
|
||||
t.Run("error if namespace is set but empty", func(t *testing.T) {
|
||||
t.Setenv("NAMESPACE", "")
|
||||
ns, err := CurrentNamespaceOrError()
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, ns)
|
||||
})
|
||||
|
||||
t.Run("returns namespace if set", func(t *testing.T) {
|
||||
t.Setenv("NAMESPACE", "foo")
|
||||
ns, err := CurrentNamespaceOrError()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "foo", ns)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -21,11 +21,13 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
scheme "github.com/dapr/dapr/pkg/client/clientset/versioned"
|
||||
daprGlobalConfig "github.com/dapr/dapr/pkg/config"
|
||||
sentryv1pb "github.com/dapr/dapr/pkg/proto/sentry/v1"
|
||||
"github.com/dapr/dapr/pkg/security"
|
||||
"github.com/dapr/dapr/utils"
|
||||
)
|
||||
|
||||
|
@ -106,7 +108,7 @@ func getKubernetesConfig(configName string) (Config, error) {
|
|||
return defaultConfig, err
|
||||
}
|
||||
|
||||
list, err := daprClient.ConfigurationV1alpha1().Configurations(metaV1.NamespaceAll).List(metaV1.ListOptions{})
|
||||
namespace, err := security.CurrentNamespaceOrError()
|
||||
if err != nil {
|
||||
return defaultConfig, err
|
||||
}
|
||||
|
@ -115,20 +117,28 @@ func getKubernetesConfig(configName string) (Config, error) {
|
|||
configName = defaultDaprSystemConfigName
|
||||
}
|
||||
|
||||
for _, i := range list.Items {
|
||||
if i.GetName() == configName {
|
||||
spec, _ := json.Marshal(i.Spec)
|
||||
|
||||
var configSpec daprGlobalConfig.ConfigurationSpec
|
||||
json.Unmarshal(spec, &configSpec)
|
||||
|
||||
conf := daprGlobalConfig.Configuration{
|
||||
Spec: configSpec,
|
||||
}
|
||||
return parseConfiguration(defaultConfig, &conf)
|
||||
}
|
||||
cfg, err := daprClient.ConfigurationV1alpha1().Configurations(namespace).Get(configName, metaV1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return defaultConfig, errors.New("config CRD not found")
|
||||
}
|
||||
return defaultConfig, errors.New("config CRD not found")
|
||||
|
||||
if err != nil {
|
||||
return defaultConfig, err
|
||||
}
|
||||
|
||||
spec, err := json.Marshal(cfg.Spec)
|
||||
if err != nil {
|
||||
return defaultConfig, err
|
||||
}
|
||||
|
||||
var configSpec daprGlobalConfig.ConfigurationSpec
|
||||
if err := json.Unmarshal(spec, &configSpec); err != nil {
|
||||
return defaultConfig, err
|
||||
}
|
||||
|
||||
return parseConfiguration(defaultConfig, &daprGlobalConfig.Configuration{
|
||||
Spec: configSpec,
|
||||
})
|
||||
}
|
||||
|
||||
func getSelfhostedConfig(configName string) (Config, error) {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: aa
|
||||
---
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Configuration
|
||||
metadata:
|
||||
name: daprsystem
|
||||
namespace: aa
|
||||
spec:
|
||||
metric:
|
||||
enabled: true
|
||||
metrics:
|
||||
enabled: true
|
||||
mtls:
|
||||
allowedClockSkew: 0m
|
||||
controlPlaneTrustDomain: cluster.local
|
||||
enabled: false
|
||||
sentryAddress: bad-address:1234
|
||||
workloadCertTTL: 1ms
|
|
@ -253,7 +253,7 @@ create-test-namespace:
|
|||
kubectl create namespace $(DAPR_TEST_NAMESPACE)
|
||||
|
||||
delete-test-namespace:
|
||||
kubectl delete namespace $(DAPR_TEST_NAMESPACE)
|
||||
kubectl delete namespace $(DAPR_TEST_NAMESPACE) aa
|
||||
|
||||
setup-3rd-party: setup-helm-init setup-test-env-redis setup-test-env-kafka setup-test-env-zipkin setup-test-env-postgres
|
||||
|
||||
|
@ -584,6 +584,8 @@ setup-test-components: setup-app-configurations
|
|||
$(KUBECTL) apply -f ./tests/config/externalinvocationcrd.yaml --namespace $(DAPR_TEST_NAMESPACE)
|
||||
$(KUBECTL) apply -f ./tests/config/omithealthchecks_config.yaml --namespace $(DAPR_TEST_NAMESPACE)
|
||||
$(KUBECTL) apply -f ./tests/config/external_invocation_http_endpoint_tls.yaml --namespace $(DAPR_TEST_NAMESPACE)
|
||||
# Don't set namespace as Namespace is defind in the yaml.
|
||||
$(KUBECTL) apply -f ./tests/config/ignore_daprsystem_config.yaml
|
||||
|
||||
# Show the installed components
|
||||
$(KUBECTL) get components --namespace $(DAPR_TEST_NAMESPACE)
|
||||
|
|
|
@ -80,6 +80,20 @@ func WithDaprConfigurationGet(t *testing.T, ns, name string, config *configapi.C
|
|||
return handleGetResource(t, "/apis/dapr.io/v1alpha1", "configurations", ns, name, config)
|
||||
}
|
||||
|
||||
func WithDaprConfigurationGet(t *testing.T, config *configapi.Configuration) Option {
|
||||
return func(o *options) {
|
||||
obj, err := json.Marshal(config)
|
||||
require.NoError(t, err)
|
||||
o.handlers = append(o.handlers, handleRoute{
|
||||
path: "/apis/dapr.io/v1alpha1/namespaces/" + config.Namespace + "/configurations/" + config.Name,
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.Write(obj)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WithSecretGet(t *testing.T, ns, name string, secret *corev1.Secret) Option {
|
||||
return handleGetResource(t, "/api/v1", "secrets", ns, name, secret)
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func kubeAPI(t *testing.T, bundle ca.Bundle, namespace, serviceaccount string) *
|
|||
MTLSSpec: &configapi.MTLSSpec{ControlPlaneTrustDomain: "integration.test.dapr.io"},
|
||||
},
|
||||
},
|
||||
}}),
|
||||
}),
|
||||
prockube.WithSecretGet(t, "sentrynamespace", "dapr-trust-bundle", &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "dapr-trust-bundle"},
|
||||
|
|
Loading…
Reference in New Issue