From 0c9f570f2962c8ac612daef04fd1ae471f8a84d2 Mon Sep 17 00:00:00 2001 From: Celene Date: Fri, 1 Jul 2022 10:42:41 -0400 Subject: [PATCH] add dependencies override and ability to rm rbacs by component (#527) * add dependencies override and ability to rm rbacs by component --- .../datadogagent/controller_reconcile_v2.go | 12 ++- .../datadogagent/dependencies/store.go | 17 ++++ .../feature/enabledefault/feature.go | 28 +++--- controllers/datadogagent/merger/rbac.go | 93 ++++++++++++++++++- .../datadogagent/override/dependencies.go | 33 +++++++ 5 files changed, 166 insertions(+), 17 deletions(-) create mode 100644 controllers/datadogagent/override/dependencies.go diff --git a/controllers/datadogagent/controller_reconcile_v2.go b/controllers/datadogagent/controller_reconcile_v2.go index 944a21865..f787dbb04 100644 --- a/controllers/datadogagent/controller_reconcile_v2.go +++ b/controllers/datadogagent/controller_reconcile_v2.go @@ -22,6 +22,7 @@ import ( datadoghqv2alpha1 "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" "github.com/DataDog/datadog-operator/controllers/datadogagent/dependencies" "github.com/DataDog/datadog-operator/controllers/datadogagent/feature" + "github.com/DataDog/datadog-operator/controllers/datadogagent/override" "github.com/DataDog/datadog-operator/pkg/controller/utils" ) @@ -97,14 +98,21 @@ func (r *Reconciler) reconcileInstanceV2(ctx context.Context, logger logr.Logger Scheme: r.scheme, } depsStore := dependencies.NewStore(instance, storeOptions) - resourcesManager := feature.NewResourceManagers(depsStore) + resourceManagers := feature.NewResourceManagers(depsStore) + var errs []error + + // Set up dependencies required by enabled features for id, feat := range features { logger.Info("Dependency ManageDependencies", "featureID", id) - if featErr := feat.ManageDependencies(resourcesManager, requiredComponents); err != nil { + if featErr := feat.ManageDependencies(resourceManagers, requiredComponents); err != nil { errs = append(errs, featErr) } } + + // Examine user configuration to override any external dependencies (e.g. RBACs) + errs = append(errs, override.Dependencies(resourceManagers, instance.Spec.Override, instance.Namespace)...) + // Now create/update dependencies errs = append(errs, depsStore.Apply(ctx, r.client)...) if len(errs) > 0 { diff --git a/controllers/datadogagent/dependencies/store.go b/controllers/datadogagent/dependencies/store.go index 3ed7be0b9..4b59411c7 100644 --- a/controllers/datadogagent/dependencies/store.go +++ b/controllers/datadogagent/dependencies/store.go @@ -37,6 +37,7 @@ type StoreClient interface { AddOrUpdate(kind kubernetes.ObjectKind, obj client.Object) error Get(kind kubernetes.ObjectKind, namespace, name string) (client.Object, bool) GetOrCreate(kind kubernetes.ObjectKind, namespace, name string) (client.Object, bool) + Delete(kind kubernetes.ObjectKind, namespace string, name string) bool } // NewStore returns a new Store instance @@ -166,6 +167,22 @@ func (ds *Store) GetOrCreate(kind kubernetes.ObjectKind, namespace, name string) return obj, found } +// Delete deletes an item from the store by kind, namespace and name. +func (ds *Store) Delete(kind kubernetes.ObjectKind, namespace string, name string) bool { + ds.mutex.RLock() + defer ds.mutex.RUnlock() + + if _, found := ds.deps[kind]; !found { + return false + } + id := buildID(namespace, name) + if _, found := ds.deps[kind][id]; found { + delete(ds.deps[kind], id) + return true + } + return false +} + // Apply use to create/update resources in the api-server func (ds *Store) Apply(ctx context.Context, k8sClient client.Client) []error { ds.mutex.RLock() diff --git a/controllers/datadogagent/feature/enabledefault/feature.go b/controllers/datadogagent/feature/enabledefault/feature.go index 1784faddc..2d5d1d66b 100644 --- a/controllers/datadogagent/feature/enabledefault/feature.go +++ b/controllers/datadogagent/feature/enabledefault/feature.go @@ -47,11 +47,11 @@ type defaultFeature struct { namespace string owner metav1.Object - credentialsInfo credentialsInfo - dcaTokenInfo dcaTokenInfo - clusterAgent clusterAgentConfig - agent agentConfig - clusterCheckRunner clusterCheckRunnerConfig + credentialsInfo credentialsInfo + dcaTokenInfo dcaTokenInfo + clusterAgent clusterAgentConfig + agent agentConfig + clusterChecksRunner clusterChecksRunnerConfig } type credentialsInfo struct { @@ -84,7 +84,7 @@ type agentConfig struct { serviceAccountName string } -type clusterCheckRunnerConfig struct { +type clusterChecksRunnerConfig struct { serviceAccountName string } @@ -95,7 +95,7 @@ func (f *defaultFeature) Configure(dda *v2alpha1.DatadogAgent) feature.RequiredC f.clusterAgent.serviceAccountName = v2alpha1.GetClusterAgentServiceAccount(dda) f.agent.serviceAccountName = v2alpha1.GetAgentServiceAccount(dda) - f.clusterCheckRunner.serviceAccountName = v2alpha1.GetClusterChecksRunnerServiceAccount(dda) + f.clusterChecksRunner.serviceAccountName = v2alpha1.GetClusterChecksRunnerServiceAccount(dda) if dda.Spec.Global != nil { if dda.Spec.Global.Credentials != nil { @@ -162,7 +162,7 @@ func (f *defaultFeature) ConfigureV1(dda *v1alpha1.DatadogAgent) feature.Require f.clusterAgent.serviceAccountName = v1alpha1.GetClusterAgentServiceAccount(dda) f.agent.serviceAccountName = v1alpha1.GetAgentServiceAccount(dda) - f.clusterCheckRunner.serviceAccountName = v1alpha1.GetClusterChecksRunnerServiceAccount(dda) + f.clusterChecksRunner.serviceAccountName = v1alpha1.GetClusterChecksRunnerServiceAccount(dda) // get info about credential // If API key, app key _and_ token don't need a new secret, then don't create one. @@ -255,7 +255,7 @@ func (f *defaultFeature) agentDependencies(managers feature.ResourceManagers, co var errs []error // serviceAccount if f.agent.serviceAccountName != "" { - if err := managers.RBACManager().AddServiceAccount(f.namespace, f.agent.serviceAccountName); err != nil { + if err := managers.RBACManager().AddServiceAccountByComponent(f.namespace, f.agent.serviceAccountName, string(v2alpha1.NodeAgentComponentName)); err != nil { errs = append(errs, err) } } @@ -274,17 +274,17 @@ func (f *defaultFeature) clusterAgentDependencies(managers feature.ResourceManag // serviceAccount if f.clusterAgent.serviceAccountName != "" { // Service Account creation - if err := managers.RBACManager().AddServiceAccount(f.namespace, f.clusterAgent.serviceAccountName); err != nil { + if err := managers.RBACManager().AddServiceAccountByComponent(f.namespace, f.clusterAgent.serviceAccountName, string(v2alpha1.ClusterAgentComponentName)); err != nil { errs = append(errs, err) } // Role Creation - if err := managers.RBACManager().AddPolicyRules(f.namespace, componentdca.GetClusterAgentRbacResourcesName(f.owner), f.clusterAgent.serviceAccountName, componentdca.GetDefaultClusterAgentRolePolicyRules(f.owner)); err != nil { + if err := managers.RBACManager().AddPolicyRulesByComponent(f.namespace, componentdca.GetClusterAgentRbacResourcesName(f.owner), f.clusterAgent.serviceAccountName, componentdca.GetDefaultClusterAgentRolePolicyRules(f.owner), string(v2alpha1.ClusterAgentComponentName)); err != nil { errs = append(errs, err) } // ClusterRole creation - if err := managers.RBACManager().AddClusterPolicyRules(f.namespace, componentdca.GetClusterAgentRbacResourcesName(f.owner), f.clusterAgent.serviceAccountName, componentdca.GetDefaultClusterAgentClusterRolePolicyRules(f.owner)); err != nil { + if err := managers.RBACManager().AddClusterPolicyRulesByComponent(f.namespace, componentdca.GetClusterAgentRbacResourcesName(f.owner), f.clusterAgent.serviceAccountName, componentdca.GetDefaultClusterAgentClusterRolePolicyRules(f.owner), string(v2alpha1.ClusterAgentComponentName)); err != nil { errs = append(errs, err) } } @@ -301,8 +301,8 @@ func (f *defaultFeature) clusterChecksRunnerDependencies(managers feature.Resour _ = component var errs []error // serviceAccount - if f.clusterCheckRunner.serviceAccountName != "" { - if err := managers.RBACManager().AddServiceAccount(f.namespace, f.clusterCheckRunner.serviceAccountName); err != nil { + if f.clusterChecksRunner.serviceAccountName != "" { + if err := managers.RBACManager().AddServiceAccountByComponent(f.namespace, f.clusterChecksRunner.serviceAccountName, string(v2alpha1.ClusterChecksRunnerComponentName)); err != nil { errs = append(errs, err) } } diff --git a/controllers/datadogagent/merger/rbac.go b/controllers/datadogagent/merger/rbac.go index 8af12158e..3d832ab3b 100644 --- a/controllers/datadogagent/merger/rbac.go +++ b/controllers/datadogagent/merger/rbac.go @@ -14,13 +14,20 @@ import ( corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/util/errors" ) // RBACManager use to manage RBAC resources. type RBACManager interface { AddServiceAccount(namespace string, name string) error + AddServiceAccountByComponent(namespace, name, component string) error AddPolicyRules(namespace string, roleName string, saName string, policies []rbacv1.PolicyRule) error + AddPolicyRulesByComponent(namespace string, roleName string, saName string, policies []rbacv1.PolicyRule, component string) error AddClusterPolicyRules(namespace string, roleName string, saName string, policies []rbacv1.PolicyRule) error + AddClusterPolicyRulesByComponent(namespace string, roleName string, saName string, policies []rbacv1.PolicyRule, component string) error + DeleteServiceAccountByComponent(component, namespace string) error + DeleteRoleByComponent(component, namespace string) error + DeleteClusterRoleByComponent(component, namespace string) error } // NewRBACManager return new RBACManager instance @@ -34,6 +41,10 @@ func NewRBACManager(store dependencies.StoreClient) RBACManager { // rbacManagerImpl use to manage RBAC resources. type rbacManagerImpl struct { store dependencies.StoreClient + + serviceAccountByComponent map[string][]string + roleByComponent map[string][]string + clusterRoleByComponent map[string][]string } // AddServiceAccount use to create a ServiceAccount @@ -47,7 +58,30 @@ func (m *rbacManagerImpl) AddServiceAccount(namespace string, name string) error return m.store.AddOrUpdate(kubernetes.ServiceAccountsKind, sa) } -// AddPolicyRules use to add PolicyRules to a Role. It also create the RoleBinding. +// AddServiceAccountByComponent is used to create a ServiceAccount and associate it with a component +func (m *rbacManagerImpl) AddServiceAccountByComponent(namespace, name, component string) error { + m.serviceAccountByComponent[component] = append(m.serviceAccountByComponent[component], name) + return m.AddServiceAccount(namespace, name) +} + +// DeleteServiceAccount use to remove a ServiceAccount from the store +func (m *rbacManagerImpl) DeleteServiceAccount(namespace string, name string) error { + found := m.store.Delete(kubernetes.ServiceAccountsKind, namespace, name) + if !found { + return fmt.Errorf("unable to delete ServiceAccount from the store because it was not found: %s/%s", namespace, name) + } + return nil +} + +func (m *rbacManagerImpl) DeleteServiceAccountByComponent(component, namespace string) error { + errs := make([]error, 0, len(m.serviceAccountByComponent[component])) + for _, name := range m.serviceAccountByComponent[component] { + errs = append(errs, m.DeleteServiceAccount(namespace, name)) + } + return errors.NewAggregate(errs) +} + +// AddPolicyRules is used to add PolicyRules to a Role. It also creates the RoleBinding. func (m *rbacManagerImpl) AddPolicyRules(namespace string, roleName string, saName string, policies []rbacv1.PolicyRule) error { obj, _ := m.store.GetOrCreate(kubernetes.RolesKind, namespace, roleName) role, ok := obj.(*rbacv1.Role) @@ -93,6 +127,34 @@ func (m *rbacManagerImpl) AddPolicyRules(namespace string, roleName string, saNa return nil } +// AddPolicyRulesByComponent is used to add PolicyRules to a Role, create a RoleBinding, and associate them with a component +func (m *rbacManagerImpl) AddPolicyRulesByComponent(namespace string, roleName string, saName string, policies []rbacv1.PolicyRule, component string) error { + m.roleByComponent[component] = append(m.roleByComponent[component], roleName) + return m.AddPolicyRules(namespace, roleName, saName, policies) +} + +// DeleteRole is used to delete a Role and RoleBinding. +func (m *rbacManagerImpl) DeleteRole(namespace string, roleName string) error { + found := m.store.Delete(kubernetes.RolesKind, namespace, roleName) + if !found { + return fmt.Errorf("unable to delete Role from the store because it was not found: %s/%s", namespace, roleName) + } + + found = m.store.Delete(kubernetes.RoleBindingKind, namespace, roleName) + if !found { + return fmt.Errorf("unable to delete RoleBinding from the store because it was not found: %s/%s", namespace, roleName) + } + return nil +} + +func (m *rbacManagerImpl) DeleteRoleByComponent(component, namespace string) error { + errs := make([]error, 0, len(m.roleByComponent[component])) + for _, name := range m.roleByComponent[component] { + errs = append(errs, m.DeleteRole(namespace, name)) + } + return errors.NewAggregate(errs) +} + // AddClusterPolicyRules use to add PolicyRules to a ClusterRole. It also create the ClusterRoleBinding. func (m *rbacManagerImpl) AddClusterPolicyRules(namespace string, roleName string, saName string, policies []rbacv1.PolicyRule) error { obj, _ := m.store.GetOrCreate(kubernetes.ClusterRolesKind, "", roleName) @@ -138,3 +200,32 @@ func (m *rbacManagerImpl) AddClusterPolicyRules(namespace string, roleName strin return nil } + +// AddClusterPolicyRulesByComponent use to add PolicyRules to a ClusterRole. It also create the ClusterRoleBinding. +func (m *rbacManagerImpl) AddClusterPolicyRulesByComponent(namespace string, roleName string, saName string, policies []rbacv1.PolicyRule, component string) error { + m.clusterRoleByComponent[component] = append(m.clusterRoleByComponent[component], roleName) + return m.AddClusterPolicyRules(namespace, roleName, saName, policies) +} + +// DeleteClusterRole is used to delete a ClusterRole and ClusterRoleBinding. +func (m *rbacManagerImpl) DeleteClusterRole(namespace string, roleName string) error { + found := m.store.Delete(kubernetes.ClusterRolesKind, namespace, roleName) + if !found { + return fmt.Errorf("unable to delete ClusterRole from the store because it was not found: %s/%s", namespace, roleName) + } + + found = m.store.Delete(kubernetes.ClusterRoleBindingKind, namespace, roleName) + if !found { + return fmt.Errorf("unable to delete ClusterRoleBinding from the store because it was not found: %s/%s", namespace, roleName) + } + + return nil +} + +func (m *rbacManagerImpl) DeleteClusterRoleByComponent(component, namespace string) error { + errs := make([]error, 0, len(m.clusterRoleByComponent[component])) + for _, name := range m.clusterRoleByComponent[component] { + errs = append(errs, m.DeleteClusterRole(namespace, name)) + } + return errors.NewAggregate(errs) +} diff --git a/controllers/datadogagent/override/dependencies.go b/controllers/datadogagent/override/dependencies.go new file mode 100644 index 000000000..942a2cc0d --- /dev/null +++ b/controllers/datadogagent/override/dependencies.go @@ -0,0 +1,33 @@ +// 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 ( + "k8s.io/apimachinery/pkg/util/errors" + + "github.com/DataDog/datadog-operator/apis/datadoghq/v2alpha1" + apiutils "github.com/DataDog/datadog-operator/apis/utils" + "github.com/DataDog/datadog-operator/controllers/datadogagent/feature" +) + +// Dependencies is used to override any resource/dependency settings with a v2alpha1.DatadogAgentComponentOverride. +func Dependencies(manager feature.ResourceManagers, overrides map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride, namespace string) (errs []error) { + for component, override := range overrides { + errs = append(errs, overrideComponentDependencies(manager, override, component, namespace)) + } + return errs +} + +func overrideComponentDependencies(manager feature.ResourceManagers, override *v2alpha1.DatadogAgentComponentOverride, component v2alpha1.ComponentName, namespace string) error { + var errs []error + if !apiutils.BoolValue(override.CreateRbac) { + rbacManager := manager.RBACManager() + errs = append(errs, rbacManager.DeleteServiceAccountByComponent(string(component), namespace)) + errs = append(errs, rbacManager.DeleteRoleByComponent(string(component), namespace)) + errs = append(errs, rbacManager.DeleteClusterRoleByComponent(string(component), namespace)) + } + return errors.NewAggregate(errs) +}