From 0a67116a048ecb5d168c3bf5069321fe5886724c Mon Sep 17 00:00:00 2001 From: adel121 Date: Thu, 29 Aug 2024 15:04:18 +0200 Subject: [PATCH 1/3] expose kubernetes_resources_labels/annotations_as_tags --- apis/datadoghq/common/envvar.go | 2 + apis/datadoghq/v2alpha1/datadogagent_types.go | 14 ++++ .../v2alpha1/zz_generated.deepcopy.go | 36 +++++++++ .../bases/v1/datadoghq.com_datadogagents.yaml | 22 ++++++ .../component/clusteragent/utils.go | 5 ++ controllers/datadogagent/override/global.go | 38 +++++++++ controllers/datadogagent/override/rbac.go | 79 +++++++++++++++++++ .../datadogagent/override/rbac_test.go | 63 +++++++++++++++ controllers/testutils/agent.go | 6 ++ docs/configuration.v2alpha1.md | 2 + 10 files changed, 267 insertions(+) create mode 100644 controllers/datadogagent/override/rbac.go create mode 100644 controllers/datadogagent/override/rbac_test.go diff --git a/apis/datadoghq/common/envvar.go b/apis/datadoghq/common/envvar.go index 34db8bc3b..34b368a45 100644 --- a/apis/datadoghq/common/envvar.go +++ b/apis/datadoghq/common/envvar.go @@ -110,6 +110,8 @@ const ( DDNamespaceLabelsAsTags = "DD_KUBERNETES_NAMESPACE_LABELS_AS_TAGS" DDNamespaceAnnotationsAsTags = "DD_KUBERNETES_NAMESPACE_ANNOTATIONS_AS_TAGS" DDNodeLabelsAsTags = "DD_KUBERNETES_NODE_LABELS_AS_TAGS" + DDKubernetesResourcesLabelsAsTags = "DD_KUBERNETES_RESOURCES_LABELS_AS_TAGS" + DDKubernetesResourcesAnnotationsAsTags = "DD_KUBERNETES_RESOURCES_ANNOTATIONS_AS_TAGS" DDOrchestratorExplorerEnabled = "DD_ORCHESTRATOR_EXPLORER_ENABLED" DDOrchestratorExplorerExtraTags = "DD_ORCHESTRATOR_EXPLORER_EXTRA_TAGS" DDOrchestratorExplorerDDUrl = "DD_ORCHESTRATOR_EXPLORER_ORCHESTRATOR_DD_URL" diff --git a/apis/datadoghq/v2alpha1/datadogagent_types.go b/apis/datadoghq/v2alpha1/datadogagent_types.go index 9126ce325..bccf442df 100644 --- a/apis/datadoghq/v2alpha1/datadogagent_types.go +++ b/apis/datadoghq/v2alpha1/datadogagent_types.go @@ -1041,6 +1041,20 @@ type GlobalConfig struct { // +optional NamespaceAnnotationsAsTags map[string]string `json:"namespaceAnnotationsAsTags,omitempty"` + // Provide a mapping of Kubernetes Resource Groups to labels mapping to Datadog Tags. + // : + // : + // KUBERNETES_RESOURCE_GROUP should be in the form `{resource}.{group}` or `{resource}` (example: deployments.apps, pods) + // +optional + KubernetesResourcesLabelsAsTags map[string]map[string]string `json:"kubernetesResourcesLabelsAsTags,omitempty"` + + // Provide a mapping of Kubernetes Resource Groups to annotations mapping to Datadog Tags. + // : + // : + // KUBERNETES_RESOURCE_GROUP should be in the form `{resource}.{group}` or `{resource}` (example: deployments.apps, pods) + // +optional + KubernetesResourcesAnnotationsAsTags map[string]map[string]string `json:"kubernetesResourcesAnnotationsAsTags,omitempty"` + // NetworkPolicy contains the network configuration. // +optional NetworkPolicy *NetworkPolicyConfig `json:"networkPolicy,omitempty"` diff --git a/apis/datadoghq/v2alpha1/zz_generated.deepcopy.go b/apis/datadoghq/v2alpha1/zz_generated.deepcopy.go index 2098c4d96..d389220ee 100644 --- a/apis/datadoghq/v2alpha1/zz_generated.deepcopy.go +++ b/apis/datadoghq/v2alpha1/zz_generated.deepcopy.go @@ -1421,6 +1421,42 @@ func (in *GlobalConfig) DeepCopyInto(out *GlobalConfig) { (*out)[key] = val } } + if in.KubernetesResourcesLabelsAsTags != nil { + in, out := &in.KubernetesResourcesLabelsAsTags, &out.KubernetesResourcesLabelsAsTags + *out = make(map[string]map[string]string, len(*in)) + for key, val := range *in { + var outVal map[string]string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + (*out)[key] = outVal + } + } + if in.KubernetesResourcesAnnotationsAsTags != nil { + in, out := &in.KubernetesResourcesAnnotationsAsTags, &out.KubernetesResourcesAnnotationsAsTags + *out = make(map[string]map[string]string, len(*in)) + for key, val := range *in { + var outVal map[string]string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + (*out)[key] = outVal + } + } if in.NetworkPolicy != nil { in, out := &in.NetworkPolicy, &out.NetworkPolicy *out = new(NetworkPolicyConfig) diff --git a/config/crd/bases/v1/datadoghq.com_datadogagents.yaml b/config/crd/bases/v1/datadoghq.com_datadogagents.yaml index 596447a82..d95c105ea 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagents.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagents.yaml @@ -1817,6 +1817,28 @@ spec: Default: true type: boolean type: object + kubernetesResourcesAnnotationsAsTags: + additionalProperties: + additionalProperties: + type: string + type: object + description: |- + Provide a mapping of Kubernetes Resource Groups to annotations mapping to Datadog Tags. + : + : + KUBERNETES_RESOURCE_GROUP should be in the form `{resource}.{group}` or `{resource}` (example: deployments.apps, pods) + type: object + kubernetesResourcesLabelsAsTags: + additionalProperties: + additionalProperties: + type: string + type: object + description: |- + Provide a mapping of Kubernetes Resource Groups to labels mapping to Datadog Tags. + : + : + KUBERNETES_RESOURCE_GROUP should be in the form `{resource}.{group}` or `{resource}` (example: deployments.apps, pods) + type: object localService: description: LocalService contains configuration to customize the internal traffic policy service. properties: diff --git a/controllers/datadogagent/component/clusteragent/utils.go b/controllers/datadogagent/component/clusteragent/utils.go index 0fd348d87..d0ada8749 100644 --- a/controllers/datadogagent/component/clusteragent/utils.go +++ b/controllers/datadogagent/component/clusteragent/utils.go @@ -102,6 +102,11 @@ func GetExternalMetricsReaderClusterRoleName(dda metav1.Object, versionInfo *ver return fmt.Sprintf("%s-metrics-reader", GetClusterAgentRbacResourcesName(dda)) } +// GetResourceMetadataAsTagsClusterRoleName returns the name for the cluster role name used for kubernetes resource labels and annotations as tags +func GetResourceMetadataAsTagsClusterRoleName(dda metav1.Object) string { + return fmt.Sprintf("%s-kubernetes-metadata-as-tags", GetClusterAgentRbacResourcesName(dda)) +} + // GetApiserverAuthReaderRoleBindingName returns the name for the role binding to access the extension-apiserver-authentication cm func GetApiserverAuthReaderRoleBindingName(dda metav1.Object) string { return fmt.Sprintf("%s-apiserver", GetClusterAgentRbacResourcesName(dda)) diff --git a/controllers/datadogagent/override/global.go b/controllers/datadogagent/override/global.go index 2d1970a04..4b587dbf6 100644 --- a/controllers/datadogagent/override/global.go +++ b/controllers/datadogagent/override/global.go @@ -15,6 +15,7 @@ import ( "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" apiutils "github.com/DataDog/datadog-operator/apis/utils" "github.com/DataDog/datadog-operator/controllers/datadogagent/component" + componentdca "github.com/DataDog/datadog-operator/controllers/datadogagent/component/clusteragent" "github.com/DataDog/datadog-operator/controllers/datadogagent/feature" "github.com/DataDog/datadog-operator/controllers/datadogagent/object/volume" "github.com/DataDog/datadog-operator/pkg/defaulting" @@ -209,6 +210,43 @@ func applyGlobalSettings(logger logr.Logger, manager feature.PodTemplateManagers } } + // Provide a mapping of Kubernetes Resource Labels to Datadog Tags. + if config.KubernetesResourcesLabelsAsTags != nil { + kubernetesResourceLabelsAsTags, err := json.Marshal(config.KubernetesResourcesLabelsAsTags) + if err != nil { + logger.Error(err, "Failed to unmarshal json input") + } else { + manager.EnvVar().AddEnvVar(&corev1.EnvVar{ + Name: apicommon.DDKubernetesResourcesLabelsAsTags, + Value: string(kubernetesResourceLabelsAsTags), + }) + } + } + + // Provide a mapping of Kubernetes Resource Annotations to Datadog Tags. + if config.KubernetesResourcesLabelsAsTags != nil { + kubernetesResourceAnnotationsAsTags, err := json.Marshal(config.KubernetesResourcesAnnotationsAsTags) + if err != nil { + logger.Error(err, "Failed to unmarshal json input") + } else { + manager.EnvVar().AddEnvVar(&corev1.EnvVar{ + Name: apicommon.DDKubernetesResourcesAnnotationsAsTags, + Value: string(kubernetesResourceAnnotationsAsTags), + }) + } + } + + if componentName == v2alpha1.ClusterAgentComponentName { + if err := resourcesManager.RBACManager().AddClusterPolicyRules( + dda.Namespace, + componentdca.GetResourceMetadataAsTagsClusterRoleName(dda), + v2alpha1.GetClusterAgentServiceAccount(dda), + getKubernetesResourceMetadataAsTagsPolicyRules(config.KubernetesResourcesLabelsAsTags, config.KubernetesResourcesAnnotationsAsTags), + ); err != nil { + logger.Error(err, "error adding kubernetes resource metadata as tags clusterrole and clusterrolebinding to store") + } + } + if componentName == v2alpha1.NodeAgentComponentName { // Kubelet contains the kubelet configuration parameters. // The environment variable `DD_KUBERNETES_KUBELET_HOST` defaults to `status.hostIP` if not overriden. diff --git a/controllers/datadogagent/override/rbac.go b/controllers/datadogagent/override/rbac.go new file mode 100644 index 000000000..df9d77ca8 --- /dev/null +++ b/controllers/datadogagent/override/rbac.go @@ -0,0 +1,79 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package override + +import ( + "strings" + + "github.com/DataDog/datadog-operator/pkg/kubernetes/rbac" + rbacv1 "k8s.io/api/rbac/v1" +) + +func extractGroupAndResource(groupResource string) (group string, resource string, ok bool) { + parts := strings.Split(groupResource, ".") + + switch len(parts) { + case 1: + group = "" + resource = parts[0] + ok = true + case 2: + group = parts[1] + resource = parts[0] + ok = true + default: + ok = false + } + + return +} + +func appendGroupResource(groupResourceAccumulator map[string]map[string]struct{}, group string, resource string) map[string]map[string]struct{} { + if _, exists := groupResourceAccumulator[group]; !exists { + groupResourceAccumulator[group] = map[string]struct{}{resource: {}} + } else { + groupResourceAccumulator[group][resource] = struct{}{} + } + + return groupResourceAccumulator +} + +func getKubernetesResourceMetadataAsTagsPolicyRules(resourcesLabelsAsTags, resourcesAnnotationsAsTags map[string]map[string]string) []rbacv1.PolicyRule { + // maps group to resource set + // using map to avoid duplicates + groupResourceAccumulator := map[string]map[string]struct{}{} + + for groupResource := range resourcesLabelsAsTags { + if group, resource, ok := extractGroupAndResource(groupResource); ok { + groupResourceAccumulator = appendGroupResource(groupResourceAccumulator, group, resource) + } + } + + for groupResource := range resourcesAnnotationsAsTags { + if group, resource, ok := extractGroupAndResource(groupResource); ok { + groupResourceAccumulator = appendGroupResource(groupResourceAccumulator, group, resource) + } + } + + policyRules := make([]rbacv1.PolicyRule, 0) + + for group, resources := range groupResourceAccumulator { + for resource := range resources { + policyRules = append(policyRules, rbacv1.PolicyRule{ + APIGroups: []string{group}, + Resources: []string{resource}, + Verbs: []string{ + rbac.GetVerb, + rbac.ListVerb, + rbac.WatchVerb, + }, + }, + ) + } + } + + return policyRules +} diff --git a/controllers/datadogagent/override/rbac_test.go b/controllers/datadogagent/override/rbac_test.go new file mode 100644 index 000000000..7cd2058ae --- /dev/null +++ b/controllers/datadogagent/override/rbac_test.go @@ -0,0 +1,63 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package override + +import ( + "testing" + + "github.com/DataDog/datadog-operator/pkg/kubernetes/rbac" + assert "github.com/stretchr/testify/require" + rbacv1 "k8s.io/api/rbac/v1" +) + +func TestGetKubernetesResourceMetadataAsTagsPolicyRules(t *testing.T) { + labelsAsTags := map[string]map[string]string{ + "pods": { + "foo": "bar", + "bar": "bar", + }, + "deployments.apps": { + "foo": "baz", + "bar": "bar", + }, + } + + annotationsAsTags := map[string]map[string]string{ + "pods": { + "foo": "bar", + "bar": "bar", + }, + "deployments.apps": { + "foo": "baz", + "bar": "bar", + }, + } + + expectedRules := []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{ + rbac.GetVerb, + rbac.ListVerb, + rbac.WatchVerb, + }, + }, + { + APIGroups: []string{"apps"}, + Resources: []string{"deployments"}, + Verbs: []string{ + rbac.GetVerb, + rbac.ListVerb, + rbac.WatchVerb, + }, + }, + } + + rules := getKubernetesResourceMetadataAsTagsPolicyRules(labelsAsTags, annotationsAsTags) + + assert.ElementsMatch(t, expectedRules, rules) +} diff --git a/controllers/testutils/agent.go b/controllers/testutils/agent.go index 695e5b771..6c684814c 100644 --- a/controllers/testutils/agent.go +++ b/controllers/testutils/agent.go @@ -369,6 +369,12 @@ func NewDatadogAgentWithGlobalConfigSettings(namespace string, name string) v2al NodeLabelsAsTags: map[string]string{"some-label": "some-tag"}, NamespaceLabelsAsTags: map[string]string{"some-label": "some-tag"}, NamespaceAnnotationsAsTags: map[string]string{"some-annotation": "some-tag"}, + KubernetesResourcesLabelsAsTags: map[string]map[string]string{ + "some-group.some-resource": {"some-label": "some-tag"}, + }, + KubernetesResourcesAnnotationsAsTags: map[string]map[string]string{ + "some-group.some-resource": {"some-annotation": "some-tag"}, + }, NetworkPolicy: &v2alpha1.NetworkPolicyConfig{ Create: apiutils.NewBoolPointer(true), Flavor: v2alpha1.NetworkPolicyFlavorKubernetes, diff --git a/docs/configuration.v2alpha1.md b/docs/configuration.v2alpha1.md index 81f97a5be..08318b0e1 100644 --- a/docs/configuration.v2alpha1.md +++ b/docs/configuration.v2alpha1.md @@ -209,6 +209,8 @@ spec: | global.kubelet.host.secretKeyRef.optional | Specify whether the Secret or its key must be defined | | global.kubelet.hostCAPath | HostCAPath is the host path where the kubelet CA certificate is stored. | | global.kubelet.tlsVerify | TLSVerify toggles kubelet TLS verification. Default: true | +| global.kubernetesResourcesAnnotationsAsTags | Provide a mapping of Kubernetes Resource Groups to annotations mapping to Datadog Tags. : : KUBERNETES_RESOURCE_GROUP should be in the form `{resource}.{group}` or `{resource}` (example: deployments.apps, pods) | +| global.kubernetesResourcesLabelsAsTags | Provide a mapping of Kubernetes Resource Groups to labels mapping to Datadog Tags. : : KUBERNETES_RESOURCE_GROUP should be in the form `{resource}.{group}` or `{resource}` (example: deployments.apps, pods) | | global.localService.forceEnableLocalService | ForceEnableLocalService forces the creation of the internal traffic policy service to target the agent running on the local node. This parameter only applies to Kubernetes 1.21, where the feature is in alpha and is disabled by default. (On Kubernetes 1.22+, the feature entered beta and the internal traffic service is created by default, so this parameter is ignored.) Default: false | | global.localService.nameOverride | NameOverride defines the name of the internal traffic service to target the agent running on the local node. | | global.logLevel | LogLevel sets logging verbosity. This can be overridden by container. Valid log levels are: trace, debug, info, warn, error, critical, and off. Default: 'info' | From e6633ece5afa23501010d3c09feafbced3d1cb5d Mon Sep 17 00:00:00 2001 From: adel121 Date: Thu, 29 Aug 2024 15:27:25 +0200 Subject: [PATCH 2/3] fix lint --- controllers/datadogagent/override/rbac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/datadogagent/override/rbac.go b/controllers/datadogagent/override/rbac.go index df9d77ca8..d8d9fc519 100644 --- a/controllers/datadogagent/override/rbac.go +++ b/controllers/datadogagent/override/rbac.go @@ -28,7 +28,7 @@ func extractGroupAndResource(groupResource string) (group string, resource strin ok = false } - return + return group, resource, ok } func appendGroupResource(groupResourceAccumulator map[string]map[string]struct{}, group string, resource string) map[string]map[string]struct{} { From b6026732449af44ad90c52223e6c48c7482508c5 Mon Sep 17 00:00:00 2001 From: Fanny Jiang Date: Thu, 10 Oct 2024 16:25:41 -0400 Subject: [PATCH 3/3] rename clusterRole --- .../controller/datadogagent/component/clusteragent/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/datadogagent/component/clusteragent/utils.go b/internal/controller/datadogagent/component/clusteragent/utils.go index a48ce349f..32a040195 100644 --- a/internal/controller/datadogagent/component/clusteragent/utils.go +++ b/internal/controller/datadogagent/component/clusteragent/utils.go @@ -84,7 +84,7 @@ func GetExternalMetricsReaderClusterRoleName(dda metav1.Object, versionInfo *ver // GetResourceMetadataAsTagsClusterRoleName returns the name for the cluster role name used for kubernetes resource labels and annotations as tags func GetResourceMetadataAsTagsClusterRoleName(dda metav1.Object) string { - return fmt.Sprintf("%s-kubernetes-metadata-as-tags", GetClusterAgentRbacResourcesName(dda)) + return fmt.Sprintf("%s-annotations-and-labels-as-tags", GetClusterAgentRbacResourcesName(dda)) } // GetApiserverAuthReaderRoleBindingName returns the name for the role binding to access the extension-apiserver-authentication cm