diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 6f32eed8..00920faf 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -6,6 +6,9 @@ metadata: rules: - nonResourceURLs: - /healthz + - /metrics + - /metrics/cadvisor + - /stats/summary - /version verbs: - get @@ -146,6 +149,12 @@ rules: - get - list - watch +- apiGroups: + - policy + resources: + - podsecuritypolicies + verbs: + - use - apiGroups: - policy resourceNames: diff --git a/controllers/apply.go b/controllers/apply.go index 5682458b..e07c9dbf 100644 --- a/controllers/apply.go +++ b/controllers/apply.go @@ -23,6 +23,7 @@ import ( instanav1 "github.com/instana/instana-agent-operator/api/v1" agentdaemonset "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/daemonset" headlessservice "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/headless-service" + agentrbac "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/rbac" agentsecrets "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/secrets" keyssecret "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/secrets/keys-secret" tlssecret "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/secrets/tls-secret" @@ -98,6 +99,8 @@ func (r *InstanaAgentReconciler) applyResources( agentsecrets.NewContainerBuilder(agent, keysSecret), tlssecret.NewSecretBuilder(agent), service.NewServiceBuilder(agent), + agentrbac.NewClusterRoleBuilder(agent), + agentrbac.NewClusterRoleBindingBuilder(agent), agentserviceaccount.NewServiceAccountBuilder(agent), k8ssensorpoddisruptionbudget.NewPodDisruptionBudgetBuilder(agent), k8ssensorrbac.NewClusterRoleBuilder(agent), diff --git a/controllers/instanaagent_controller.go b/controllers/instanaagent_controller.go index f6139c2c..e3f23ca2 100644 --- a/controllers/instanaagent_controller.go +++ b/controllers/instanaagent_controller.go @@ -156,9 +156,10 @@ func (r *InstanaAgentReconciler) reconcile( // +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch // +kubebuilder:rbac:groups=instana.io,resources=agents/status,verbs=get;update;patch // +kubebuilder:rbac:groups=instana.io,resources=agents/finalizers,verbs=update +// +kubebuilder:rbac:groups=policy,resources=podsecuritypolicies,verbs=use // adding role property required to manage instana-agent-k8sensor ClusterRole -// +kubebuilder:rbac:urls=/version;/healthz,verbs=get +// +kubebuilder:rbac:urls=/version;/healthz;/metrics;/metrics/cadvisor;/stats/summary,verbs=get // +kubebuilder:rbac:groups=extensions,resources=deployments;replicasets;ingresses,verbs=get;list;watch // +kubebuilder:rbac:groups=core,resources=configmaps;events;services;endpoints;namespaces;nodes;pods;pods/log;replicationcontrollers;resourcequotas;persistentvolumes;persistentvolumeclaims;nodes/metrics;nodes/stats,verbs=get;list;watch // +kubebuilder:rbac:groups=apps,resources=daemonsets;deployments;replicasets;statefulsets,verbs=get;list;watch diff --git a/pkg/k8s/object/builders/agent/rbac/clusterrole.go b/pkg/k8s/object/builders/agent/rbac/clusterrole.go new file mode 100644 index 00000000..1a0fcae9 --- /dev/null +++ b/pkg/k8s/object/builders/agent/rbac/clusterrole.go @@ -0,0 +1,104 @@ +/* +(c) Copyright IBM Corp. 2025 +(c) Copyright Instana Inc. + +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 rbac + +import ( + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" +) + +type clusterRoleBuilder struct { + *instanav1.InstanaAgent + helpers.Helpers +} + +func (c *clusterRoleBuilder) ComponentName() string { + return constants.ComponentInstanaAgent +} + +func (c *clusterRoleBuilder) IsNamespaced() bool { + return false +} + +func (c *clusterRoleBuilder) Build() optional.Optional[client.Object] { + return optional.Of[client.Object]( + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + APIVersion: rbacApiVersion, + Kind: roleKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: c.ServiceAccountName(), + }, + Rules: []rbacv1.PolicyRule{ + { + NonResourceURLs: []string{ + "/version", + "/healthz", + "/metrics", + "/stats/summary", + "/metrics/cadvisor", + }, + Verbs: []string{"get"}, + APIGroups: []string{}, + Resources: []string{}, + }, + { + APIGroups: []string{""}, + Resources: []string{ + "nodes", + "nodes/stats", + "nodes/metrics", + "pods", + }, + Verbs: constants.ReaderVerbs(), + }, + { + APIGroups: []string{"security.openshift.io"}, + ResourceNames: []string{"privileged"}, + Resources: []string{ + "securitycontextconstraints", + }, + Verbs: []string{"use"}, + }, + { + APIGroups: []string{"policy"}, + Resources: []string{ + "podsecuritypolicies", + }, + Verbs: []string{"use"}, + }, + }, + }, + ) +} + +func NewClusterRoleBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &clusterRoleBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/agent/rbac/clusterrole_test.go b/pkg/k8s/object/builders/agent/rbac/clusterrole_test.go new file mode 100644 index 00000000..97561ef7 --- /dev/null +++ b/pkg/k8s/object/builders/agent/rbac/clusterrole_test.go @@ -0,0 +1,111 @@ +/* +(c) Copyright IBM Corp. 2025 +(c) Copyright Instana Inc. + +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 rbac + +import ( + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/instana/instana-agent-operator/mocks" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestClusterRoleBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + cb := NewClusterRoleBuilder(nil) + + assertions.False(cb.IsNamespaced()) + assertions.Equal(constants.ComponentInstanaAgent, cb.ComponentName()) +} + +func TestClusterRoleBuilder_Build(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + sensorResourcesName := rand.String(10) + + expected := optional.Of[client.Object]( + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + APIVersion: rbacApiVersion, + Kind: roleKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: sensorResourcesName, + }, + Rules: []rbacv1.PolicyRule{ + { + NonResourceURLs: []string{ + "/version", + "/healthz", + "/metrics", + "/stats/summary", + "/metrics/cadvisor", + }, + Verbs: []string{"get"}, + APIGroups: []string{}, + Resources: []string{}, + }, + { + APIGroups: []string{""}, + Resources: []string{ + "nodes", + "nodes/stats", + "nodes/metrics", + "pods", + }, + Verbs: constants.ReaderVerbs(), + }, + { + APIGroups: []string{"security.openshift.io"}, + ResourceNames: []string{"privileged"}, + Resources: []string{ + "securitycontextconstraints", + }, + Verbs: []string{"use"}, + }, + { + APIGroups: []string{"policy"}, + Resources: []string{ + "podsecuritypolicies", + }, + Verbs: []string{"use"}, + }, + }, + }, + ) + + helpers := mocks.NewMockHelpers(ctrl) + helpers.EXPECT().ServiceAccountName().Times(1).Return(sensorResourcesName) + + cb := &clusterRoleBuilder{ + Helpers: helpers, + } + + actual := cb.Build() + + assertions.Equal(expected, actual) +} diff --git a/pkg/k8s/object/builders/agent/rbac/clusterrolebinding.go b/pkg/k8s/object/builders/agent/rbac/clusterrolebinding.go new file mode 100644 index 00000000..b67ace5d --- /dev/null +++ b/pkg/k8s/object/builders/agent/rbac/clusterrolebinding.go @@ -0,0 +1,77 @@ +/* +(c) Copyright IBM Corp. 2025 +(c) Copyright Instana Inc. + +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 rbac + +import ( + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" +) + +type clusterRoleBindingBuilder struct { + *instanav1.InstanaAgent + helpers.Helpers +} + +func (c *clusterRoleBindingBuilder) IsNamespaced() bool { + return false +} + +func (c *clusterRoleBindingBuilder) ComponentName() string { + return constants.ComponentInstanaAgent +} + +func (c *clusterRoleBindingBuilder) Build() optional.Optional[client.Object] { + return optional.Of[client.Object]( + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + APIVersion: rbacApiVersion, + Kind: "ClusterRoleBinding", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: c.ServiceAccountName(), + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacApiGroup, + Kind: roleKind, + Name: c.ServiceAccountName(), + }, + Subjects: []rbacv1.Subject{ + { + Kind: subjectKind, + Name: c.ServiceAccountName(), + Namespace: c.Namespace, + }, + }, + }, + ) +} + +func NewClusterRoleBindingBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &clusterRoleBindingBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/agent/rbac/clusterrolebinding_test.go b/pkg/k8s/object/builders/agent/rbac/clusterrolebinding_test.go new file mode 100644 index 00000000..1281af8e --- /dev/null +++ b/pkg/k8s/object/builders/agent/rbac/clusterrolebinding_test.go @@ -0,0 +1,93 @@ +/* +(c) Copyright IBM Corp. 2025 +(c) Copyright Instana Inc. + +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 rbac + +import ( + "testing" + + "github.com/instana/instana-agent-operator/mocks" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" +) + +func TestClusterRoleBindingBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + crb := NewClusterRoleBindingBuilder(nil) + + assertions.False(crb.IsNamespaced()) + assertions.Equal(constants.ComponentInstanaAgent, crb.ComponentName()) +} + +func TestClusterRoleBindingBuilder_Build(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + sensorResourcesName := rand.String(10) + namespace := rand.String(10) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + } + + expected := optional.Of[client.Object]( + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "ClusterRoleBinding", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: sensorResourcesName, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: sensorResourcesName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: sensorResourcesName, + Namespace: namespace, + }, + }, + }, + ) + + helpers := mocks.NewMockHelpers(ctrl) + helpers.EXPECT().ServiceAccountName().Times(3).Return(sensorResourcesName) + + crb := &clusterRoleBindingBuilder{ + InstanaAgent: agent, + Helpers: helpers, + } + + actual := crb.Build() + + assertions.Equal(expected, actual) +} diff --git a/pkg/k8s/object/builders/agent/rbac/constants.go b/pkg/k8s/object/builders/agent/rbac/constants.go new file mode 100644 index 00000000..df400ef7 --- /dev/null +++ b/pkg/k8s/object/builders/agent/rbac/constants.go @@ -0,0 +1,25 @@ +/* +(c) Copyright IBM Corp. 2025 +(c) Copyright Instana Inc. + +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 rbac + +const ( + rbacApiGroup = "rbac.authorization.k8s.io" + rbacApiVersion = rbacApiGroup + "/v1" + roleKind = "ClusterRole" + subjectKind = "ServiceAccount" +) diff --git a/pkg/k8s/object/builders/common/constants/constants.go b/pkg/k8s/object/builders/common/constants/constants.go index 03f69e65..d3616a50 100644 --- a/pkg/k8s/object/builders/common/constants/constants.go +++ b/pkg/k8s/object/builders/common/constants/constants.go @@ -34,3 +34,8 @@ const ( DownloadKey = "downloadKey" BackendKey = "backend" ) + +// ReaderVerbs are the list RBAC Verbs used for being able to read resources for a specific api group as specified in a PolicyRule, i.e: "get", "list", "watch" +func ReaderVerbs() []string { + return []string{"get", "list", "watch"} +} diff --git a/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole.go b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole.go index eaf1c42c..c35ac928 100644 --- a/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole.go +++ b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole.go @@ -1,3 +1,20 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +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 rbac import ( @@ -12,10 +29,6 @@ import ( "github.com/instana/instana-agent-operator/pkg/optional" ) -func readerVerbs() []string { - return []string{"get", "list", "watch"} -} - type clusterRoleBuilder struct { *instanav1.InstanaAgent helpers.Helpers @@ -49,7 +62,7 @@ func (c *clusterRoleBuilder) Build() optional.Optional[client.Object] { { APIGroups: []string{"extensions"}, Resources: []string{"deployments", "replicasets", "ingresses"}, - Verbs: readerVerbs(), + Verbs: constants.ReaderVerbs(), }, { APIGroups: []string{""}, @@ -69,32 +82,32 @@ func (c *clusterRoleBuilder) Build() optional.Optional[client.Object] { "persistentvolumes", "persistentvolumeclaims", }, - Verbs: readerVerbs(), + Verbs: constants.ReaderVerbs(), }, { APIGroups: []string{"apps"}, Resources: []string{"daemonsets", "deployments", "replicasets", "statefulsets"}, - Verbs: readerVerbs(), + Verbs: constants.ReaderVerbs(), }, { APIGroups: []string{"batch"}, Resources: []string{"cronjobs", "jobs"}, - Verbs: readerVerbs(), + Verbs: constants.ReaderVerbs(), }, { APIGroups: []string{"networking.k8s.io"}, Resources: []string{"ingresses"}, - Verbs: readerVerbs(), + Verbs: constants.ReaderVerbs(), }, { APIGroups: []string{"autoscaling"}, Resources: []string{"horizontalpodautoscalers"}, - Verbs: readerVerbs(), + Verbs: constants.ReaderVerbs(), }, { APIGroups: []string{"apps.openshift.io"}, Resources: []string{"deploymentconfigs"}, - Verbs: readerVerbs(), + Verbs: constants.ReaderVerbs(), }, { APIGroups: []string{"security.openshift.io"},