diff --git a/.chloggen/2833-fix-detector-resourcedetectionprocessor.yaml b/.chloggen/2833-fix-detector-resourcedetectionprocessor.yaml new file mode 100755 index 0000000000..effa51536f --- /dev/null +++ b/.chloggen/2833-fix-detector-resourcedetectionprocessor.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: bug_fix + +# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action) +component: collector + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: "Use the k8snode detector instead of kubernetes for the automatic RBAC creation for the resourcedetector" + +# One or more tracking issues related to the change +issues: [2833] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/.chloggen/2862-fix-clusterrolebinding-names.yaml b/.chloggen/2862-fix-clusterrolebinding-names.yaml new file mode 100755 index 0000000000..44307f7670 --- /dev/null +++ b/.chloggen/2862-fix-clusterrolebinding-names.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: bug_fix + +# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action) +component: collector + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: "When two Collectors are created with the same name but different namespaces, the ClusterRoleBinding created by the first will be overriden by the second one." + +# One or more tracking issues related to the change +issues: [2862] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/.chloggen/replace-create-rbac-permissions-by-checking-the-sa-permissions.yaml b/.chloggen/replace-create-rbac-permissions-by-checking-the-sa-permissions.yaml new file mode 100755 index 0000000000..ab5895bb16 --- /dev/null +++ b/.chloggen/replace-create-rbac-permissions-by-checking-the-sa-permissions.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action) +component: operator + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Automatically enable RBAC creation if operator SA can create clusterroles and bindings. --create-rbac-permissions flag is noop and deprecated now. + +# One or more tracking issues related to the change +issues: [2588] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: \ No newline at end of file diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index d8bf4d629a..5a413b5535 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -24,6 +24,7 @@ jobs: - "1.29" group: - e2e + - e2e-automatic-rbac - e2e-autoscale - e2e-instrumentation - e2e-opampbridge @@ -40,7 +41,8 @@ jobs: setup: "add-multi-instrumentation-params prepare-e2e" - group: e2e-metadata-filters setup: "add-operator-arg OPERATOR_ARG='--annotations-filter=*filter.out --labels=*filter.out' prepare-e2e" - + - group: e2e-automatic-rbac + setup: "add-rbac-permissions-to-operator prepare-e2e" steps: - name: Check out code into the Go module directory uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 88abe862c0..1438657894 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ config/manager/kustomization.yaml # test resources kubeconfig tests/_build/ +config/rbac/extra-permissions-operator/ # autoinstrumentation artifacts build diff --git a/Makefile b/Makefile index d169d3c38b..c5a601fe0a 100644 --- a/Makefile +++ b/Makefile @@ -157,9 +157,15 @@ add-multi-instrumentation-params: add-image-opampbridge: @$(MAKE) add-operator-arg OPERATOR_ARG=--operator-opamp-bridge-image=$(OPERATOROPAMPBRIDGE_IMG) -.PHONY: enable-operator-featuregates -enable-operator-featuregates: OPERATOR_ARG = --feature-gates=$(FEATUREGATES) -enable-operator-featuregates: add-operator-arg +.PHONY: add-rbac-permissions-to-operator +add-rbac-permissions-to-operator: manifests kustomize + # Kustomize only allows patches in the folder where the kustomization is located + # This folder is ignored by .gitignore + cp -r tests/e2e-automatic-rbac/extra-permissions-operator/ config/rbac/extra-permissions-operator + cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/namespaces.yaml + cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/nodes.yaml + cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/rbac.yaml + cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/replicaset.yaml # Deploy controller in the current Kubernetes context, configured in ~/.kube/config .PHONY: deploy @@ -217,6 +223,11 @@ generate: controller-gen e2e: chainsaw $(CHAINSAW) test --test-dir ./tests/e2e +# end-to-end-test for testing automatic RBAC creation +.PHONY: e2e-automatic-rbac +e2e-automatic-rbac: chainsaw + $(CHAINSAW) test --test-dir ./tests/e2e-automatic-rbac + # end-to-end-test for testing autoscale .PHONY: e2e-autoscale e2e-autoscale: chainsaw @@ -272,9 +283,6 @@ e2e-upgrade: undeploy chainsaw .PHONY: prepare-e2e prepare-e2e: chainsaw set-image-controller add-image-targetallocator add-image-opampbridge container container-target-allocator container-operator-opamp-bridge start-kind cert-manager install-metrics-server install-targetallocator-prometheus-crds load-image-all deploy -.PHONY: prepare-e2e-with-featuregates -prepare-e2e-with-featuregates: chainsaw enable-operator-featuregates prepare-e2e - .PHONY: scorecard-tests scorecard-tests: operator-sdk $(OPERATOR_SDK) scorecard -w=5m bundle || (echo "scorecard test failed" && exit 1) @@ -320,10 +328,6 @@ endif install-metrics-server: ./hack/install-metrics-server.sh -.PHONY: install-prometheus-operator -install-prometheus-operator: - ./hack/install-prometheus-operator.sh - # This only installs the CRDs Target Allocator supports .PHONY: install-targetallocator-prometheus-crds install-targetallocator-prometheus-crds: diff --git a/apis/v1beta1/collector_webhook.go b/apis/v1beta1/collector_webhook.go index e26f4e232c..99fefdaee8 100644 --- a/apis/v1beta1/collector_webhook.go +++ b/apis/v1beta1/collector_webhook.go @@ -20,7 +20,6 @@ import ( "strings" "github.com/go-logr/logr" - authorizationv1 "k8s.io/api/authorization/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" @@ -359,7 +358,7 @@ func (c CollectorWebhook) validateTargetAllocatorConfig(ctx context.Context, r * if subjectAccessReviews, err := c.reviewer.CheckPolicyRules(ctx, r.GetNamespace(), r.Spec.TargetAllocator.ServiceAccount, targetAllocatorCRPolicyRules...); err != nil { return nil, fmt.Errorf("unable to check rbac rules %w", err) } else if allowed, deniedReviews := rbac.AllSubjectAccessReviewsAllowed(subjectAccessReviews); !allowed { - return warningsGroupedByResource(deniedReviews), nil + return rbac.WarningsGroupedByResource(deniedReviews), nil } } @@ -407,28 +406,6 @@ func checkAutoscalerSpec(autoscaler *AutoscalerSpec) error { return nil } -// warningsGroupedByResource is a helper to take the missing permissions and format them as warnings. -func warningsGroupedByResource(reviews []*authorizationv1.SubjectAccessReview) []string { - fullResourceToVerbs := make(map[string][]string) - for _, review := range reviews { - if review.Spec.ResourceAttributes != nil { - key := fmt.Sprintf("%s/%s", review.Spec.ResourceAttributes.Group, review.Spec.ResourceAttributes.Resource) - if len(review.Spec.ResourceAttributes.Group) == 0 { - key = review.Spec.ResourceAttributes.Resource - } - fullResourceToVerbs[key] = append(fullResourceToVerbs[key], review.Spec.ResourceAttributes.Verb) - } else if review.Spec.NonResourceAttributes != nil { - key := fmt.Sprintf("nonResourceURL: %s", review.Spec.NonResourceAttributes.Path) - fullResourceToVerbs[key] = append(fullResourceToVerbs[key], review.Spec.NonResourceAttributes.Verb) - } - } - var warnings []string - for fullResource, verbs := range fullResourceToVerbs { - warnings = append(warnings, fmt.Sprintf("missing the following rules for %s: [%s]", fullResource, strings.Join(verbs, ","))) - } - return warnings -} - func SetupCollectorWebhook(mgr ctrl.Manager, cfg config.Config, reviewer *rbac.Reviewer) error { cvw := &CollectorWebhook{ reviewer: reviewer, diff --git a/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml index a200bdd9a0..c14ddf4e76 100644 --- a/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml +++ b/bundle/manifests/opentelemetry-operator.clusterserviceversion.yaml @@ -99,7 +99,7 @@ metadata: categories: Logging & Tracing,Monitoring certified: "false" containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator - createdAt: "2024-04-30T12:37:39Z" + createdAt: "2024-05-03T15:21:44Z" description: Provides the OpenTelemetry components, including the Collector operators.operatorframework.io/builder: operator-sdk-v1.29.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 @@ -499,6 +499,11 @@ spec: - --zap-log-level=info - --zap-time-encoding=rfc3339nano - --enable-nginx-instrumentation=true + env: + - name: SERVICE_ACCOUNT_NAME + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName image: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator:0.99.0 livenessProbe: httpGet: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index a329ad65ab..b15e4abfd6 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -51,5 +51,10 @@ spec: requests: cpu: 100m memory: 64Mi + env: + - name: SERVICE_ACCOUNT_NAME + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName serviceAccountName: controller-manager terminationGracePeriodSeconds: 10 diff --git a/controllers/opampbridge_controller_test.go b/controllers/opampbridge_controller_test.go index 269a43c745..99f4f43a78 100644 --- a/controllers/opampbridge_controller_test.go +++ b/controllers/opampbridge_controller_test.go @@ -37,6 +37,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/controllers" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" "github.com/open-telemetry/opentelemetry-operator/internal/config" ) @@ -48,6 +49,9 @@ var opampBridgeMockAutoDetector = &mockAutoDetect{ PrometheusCRsAvailabilityFunc: func() (prometheus.Availability, error) { return prometheus.Available, nil }, + RBACPermissionsFunc: func(ctx context.Context) (rbac.Availability, error) { + return rbac.Available, nil + }, } func TestNewObjectsOnReconciliation_OpAMPBridge(t *testing.T) { diff --git a/controllers/opentelemetrycollector_controller.go b/controllers/opentelemetrycollector_controller.go index 258c5fadfc..a713e15dfd 100644 --- a/controllers/opentelemetrycollector_controller.go +++ b/controllers/opentelemetrycollector_controller.go @@ -39,6 +39,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" "github.com/open-telemetry/opentelemetry-operator/internal/config" "github.com/open-telemetry/opentelemetry-operator/internal/manifests" "github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector" @@ -239,7 +240,7 @@ func (r *OpenTelemetryCollectorReconciler) SetupWithManager(mgr ctrl.Manager) er Owns(&autoscalingv2.HorizontalPodAutoscaler{}). Owns(&policyV1.PodDisruptionBudget{}) - if r.config.CreateRBACPermissions() { + if r.config.CreateRBACPermissions() == rbac.Available { builder.Owns(&rbacv1.ClusterRoleBinding{}) builder.Owns(&rbacv1.ClusterRole{}) } diff --git a/controllers/suite_test.go b/controllers/suite_test.go index d20e7be8b2..a9d82248e9 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -56,6 +56,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" + autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" "github.com/open-telemetry/opentelemetry-operator/internal/config" "github.com/open-telemetry/opentelemetry-operator/internal/manifests" "github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/testdata" @@ -95,6 +96,7 @@ var _ autodetect.AutoDetect = (*mockAutoDetect)(nil) type mockAutoDetect struct { OpenShiftRoutesAvailabilityFunc func() (openshift.RoutesAvailability, error) PrometheusCRsAvailabilityFunc func() (prometheus.Availability, error) + RBACPermissionsFunc func(ctx context.Context) (autoRBAC.Availability, error) } func (m *mockAutoDetect) PrometheusCRsAvailability() (prometheus.Availability, error) { @@ -111,6 +113,13 @@ func (m *mockAutoDetect) OpenShiftRoutesAvailability() (openshift.RoutesAvailabi return openshift.RoutesNotAvailable, nil } +func (m *mockAutoDetect) RBACPermissions(ctx context.Context) (autoRBAC.Availability, error) { + if m.RBACPermissionsFunc != nil { + return m.RBACPermissionsFunc(ctx) + } + return autoRBAC.NotAvailable, nil +} + func TestMain(m *testing.M) { ctx, cancel = context.WithCancel(context.TODO()) defer cancel() diff --git a/internal/autodetect/main.go b/internal/autodetect/main.go index e0dec72245..359843dcd0 100644 --- a/internal/autodetect/main.go +++ b/internal/autodetect/main.go @@ -16,11 +16,16 @@ package autodetect import ( + "context" + "fmt" + "k8s.io/client-go/discovery" "k8s.io/client-go/rest" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" + autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" + "github.com/open-telemetry/opentelemetry-operator/internal/rbac" ) var _ AutoDetect = (*autoDetect)(nil) @@ -29,14 +34,16 @@ var _ AutoDetect = (*autoDetect)(nil) type AutoDetect interface { OpenShiftRoutesAvailability() (openshift.RoutesAvailability, error) PrometheusCRsAvailability() (prometheus.Availability, error) + RBACPermissions(ctx context.Context) (autoRBAC.Availability, error) } type autoDetect struct { - dcl discovery.DiscoveryInterface + dcl discovery.DiscoveryInterface + reviewer *rbac.Reviewer } // New creates a new auto-detection worker, using the given client when talking to the current cluster. -func New(restConfig *rest.Config) (AutoDetect, error) { +func New(restConfig *rest.Config, reviewer *rbac.Reviewer) (AutoDetect, error) { dcl, err := discovery.NewDiscoveryClientForConfig(restConfig) if err != nil { // it's pretty much impossible to get into this problem, as most of the @@ -46,7 +53,8 @@ func New(restConfig *rest.Config) (AutoDetect, error) { } return &autoDetect{ - dcl: dcl, + dcl: dcl, + reviewer: reviewer, }, nil } @@ -83,3 +91,15 @@ func (a *autoDetect) OpenShiftRoutesAvailability() (openshift.RoutesAvailability return openshift.RoutesNotAvailable, nil } + +func (a *autoDetect) RBACPermissions(ctx context.Context) (autoRBAC.Availability, error) { + w, err := autoRBAC.CheckRBACPermissions(ctx, a.reviewer) + if err != nil { + return autoRBAC.NotAvailable, err + } + if w != nil { + return autoRBAC.NotAvailable, fmt.Errorf("missing permissions: %s", w) + } + + return autoRBAC.Available, nil +} diff --git a/internal/autodetect/main_test.go b/internal/autodetect/main_test.go index 6b1e22369a..d5dbbc707e 100644 --- a/internal/autodetect/main_test.go +++ b/internal/autodetect/main_test.go @@ -15,19 +15,28 @@ package autodetect_test import ( + "context" "encoding/json" + "fmt" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/api/authorization/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/rest" + kubeTesting "k8s.io/client-go/testing" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" + autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" + "github.com/open-telemetry/opentelemetry-operator/internal/rbac" ) func TestDetectPlatformBasedOnAvailableAPIGroups(t *testing.T) { @@ -61,7 +70,7 @@ func TestDetectPlatformBasedOnAvailableAPIGroups(t *testing.T) { })) defer server.Close() - autoDetect, err := autodetect.New(&rest.Config{Host: server.URL}) + autoDetect, err := autodetect.New(&rest.Config{Host: server.URL}, nil) require.NoError(t, err) // test @@ -104,7 +113,7 @@ func TestDetectPlatformBasedOnAvailableAPIGroupsPrometheus(t *testing.T) { })) defer server.Close() - autoDetect, err := autodetect.New(&rest.Config{Host: server.URL}) + autoDetect, err := autodetect.New(&rest.Config{Host: server.URL}, nil) require.NoError(t, err) // test @@ -115,3 +124,106 @@ func TestDetectPlatformBasedOnAvailableAPIGroupsPrometheus(t *testing.T) { assert.Equal(t, tt.expected, ora) } } + +type fakeClientGenerator func() kubernetes.Interface + +const ( + createVerb = "create" + sarResource = "subjectaccessreviews" +) + +func reactorFactory(status v1.SubjectAccessReviewStatus) fakeClientGenerator { + return func() kubernetes.Interface { + c := fake.NewSimpleClientset() + c.PrependReactor(createVerb, sarResource, func(action kubeTesting.Action) (handled bool, ret runtime.Object, err error) { + // check our expectation here + if !action.Matches(createVerb, sarResource) { + return false, nil, fmt.Errorf("must be a create for a SAR") + } + sar, ok := action.(kubeTesting.CreateAction).GetObject().DeepCopyObject().(*v1.SubjectAccessReview) + if !ok || sar == nil { + return false, nil, fmt.Errorf("bad object") + } + sar.Status = status + return true, sar, nil + }) + return c + } +} + +func TestDetectRBACPermissionsBasedOnAvailableClusterRoles(t *testing.T) { + + for _, tt := range []struct { + description string + expectedAvailability autoRBAC.Availability + shouldError bool + namespace string + serviceAccount string + clientGenerator fakeClientGenerator + }{ + { + description: "Not possible to read the namespace", + namespace: "default", + shouldError: true, + expectedAvailability: autoRBAC.NotAvailable, + clientGenerator: reactorFactory(v1.SubjectAccessReviewStatus{ + Allowed: true, + }), + }, + { + description: "Not possible to read the service account", + serviceAccount: "default", + shouldError: true, + clientGenerator: reactorFactory(v1.SubjectAccessReviewStatus{ + Allowed: true, + }), + }, + { + description: "RBAC resources are NOT there", + + shouldError: true, + namespace: "default", + serviceAccount: "defaultSA", + clientGenerator: reactorFactory(v1.SubjectAccessReviewStatus{ + Allowed: false, + }), + expectedAvailability: autoRBAC.NotAvailable, + }, + { + description: "RBAC resources are there", + + shouldError: false, + namespace: "default", + serviceAccount: "defaultSA", + clientGenerator: reactorFactory(v1.SubjectAccessReviewStatus{ + Allowed: true, + }), + expectedAvailability: autoRBAC.Available, + }, + } { + t.Run(tt.description, func(t *testing.T) { + // These settings can be get from env vars + t.Setenv(autoRBAC.NAMESPACE_ENV_VAR, tt.namespace) + t.Setenv(autoRBAC.SA_ENV_VAR, tt.serviceAccount) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {})) + defer server.Close() + + r := rbac.NewReviewer(tt.clientGenerator()) + + aD, err := autodetect.New(&rest.Config{Host: server.URL}, r) + require.NoError(t, err) + + // test + rAuto, err := aD.RBACPermissions(context.Background()) + + // verify + assert.Equal(t, tt.expectedAvailability, rAuto) + if tt.shouldError { + require.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/internal/autodetect/rbac/check.go b/internal/autodetect/rbac/check.go new file mode 100644 index 0000000000..1e133ebf49 --- /dev/null +++ b/internal/autodetect/rbac/check.go @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry 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 rbac + +import ( + "context" + "fmt" + "os" + + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/open-telemetry/opentelemetry-operator/internal/rbac" +) + +const ( + SA_ENV_VAR = "SERVICE_ACCOUNT_NAME" + NAMESPACE_ENV_VAR = "NAMESPACE" + NAMESPACE_FILE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" +) + +func getOperatorNamespace() (string, error) { + namespace := os.Getenv(NAMESPACE_ENV_VAR) + if namespace != "" { + return namespace, nil + } + + nsBytes, err := os.ReadFile(NAMESPACE_FILE_PATH) + if err != nil { + return "", err + } + return string(nsBytes), nil +} + +func getOperatorServiceAccount() (string, error) { + sa := os.Getenv(SA_ENV_VAR) + if sa == "" { + return sa, fmt.Errorf("%s env variable not found", SA_ENV_VAR) + } + return sa, nil +} + +// CheckRBACPermissions checks if the operator has the needed permissions to create RBAC resources automatically. +// If the RBAC is there, no errors nor warnings are returned. +func CheckRBACPermissions(ctx context.Context, reviewer *rbac.Reviewer) (admission.Warnings, error) { + namespace, err := getOperatorNamespace() + if err != nil { + return nil, fmt.Errorf("%s: %w", "not possible to check RBAC rules", err) + } + + serviceAccount, err := getOperatorServiceAccount() + if err != nil { + return nil, fmt.Errorf("%s: %w", "not possible to check RBAC rules", err) + } + + rules := []*rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterrolebindings", "clusterroles"}, + Verbs: []string{"create", "delete", "get", "list", "patch", "update"}, + }, + } + + if subjectAccessReviews, err := reviewer.CheckPolicyRules(ctx, serviceAccount, namespace, rules...); err != nil { + return nil, fmt.Errorf("%s: %w", "unable to check rbac rules", err) + } else if allowed, deniedReviews := rbac.AllSubjectAccessReviewsAllowed(subjectAccessReviews); !allowed { + return rbac.WarningsGroupedByResource(deniedReviews), nil + } + return nil, nil +} diff --git a/internal/autodetect/rbac/operator.go b/internal/autodetect/rbac/operator.go new file mode 100644 index 0000000000..03d0b892ea --- /dev/null +++ b/internal/autodetect/rbac/operator.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry 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 rbac + +// Availability represents that the opeerator service account has permissions to create RBAC resources. +type Availability int + +const ( + // NotAvailable RBAC permissions are not available. + NotAvailable Availability = iota + + // Available NotAvailable RBAC permissions are available. + Available +) + +func (p Availability) String() string { + return [...]string{"NotAvailable", "Available"}[p] +} diff --git a/internal/config/main.go b/internal/config/main.go index 4430f3d618..a0dbe533b9 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -16,6 +16,7 @@ package config import ( + "context" "time" "github.com/go-logr/logr" @@ -24,6 +25,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" + autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" "github.com/open-telemetry/opentelemetry-operator/internal/version" ) @@ -43,7 +45,7 @@ type Config struct { autoInstrumentationPythonImage string collectorImage string collectorConfigMapEntry string - createRBACPermissions bool + createRBACPermissions autoRBAC.Availability enableMultiInstrumentation bool enableApacheHttpdInstrumentation bool enableDotNetInstrumentation bool @@ -60,10 +62,11 @@ type Config struct { operatorOpAMPBridgeConfigMapEntry string autoInstrumentationNodeJSImage string autoInstrumentationJavaImage string - openshiftRoutesAvailability openshift.RoutesAvailability - prometheusCRAvailability prometheus.Availability - labelsFilter []string - annotationsFilter []string + + openshiftRoutesAvailability openshift.RoutesAvailability + prometheusCRAvailability prometheus.Availability + labelsFilter []string + annotationsFilter []string } // New constructs a new configuration based on the given options. @@ -72,6 +75,7 @@ func New(opts ...Option) Config { o := options{ prometheusCRAvailability: prometheus.NotAvailable, openshiftRoutesAvailability: openshift.RoutesNotAvailable, + createRBACPermissions: autoRBAC.NotAvailable, collectorConfigMapEntry: defaultCollectorConfigMapEntry, targetAllocatorConfigMapEntry: defaultTargetAllocatorConfigMapEntry, operatorOpAMPBridgeConfigMapEntry: defaultOperatorOpAMPBridgeConfigMapEntry, @@ -80,6 +84,7 @@ func New(opts ...Option) Config { enableJavaInstrumentation: true, annotationsFilter: []string{"kubectl.kubernetes.io/last-applied-configuration"}, } + for _, opt := range opts { opt(&o) } @@ -88,7 +93,6 @@ func New(opts ...Option) Config { autoDetect: o.autoDetect, collectorImage: o.collectorImage, collectorConfigMapEntry: o.collectorConfigMapEntry, - createRBACPermissions: o.createRBACPermissions, enableMultiInstrumentation: o.enableMultiInstrumentation, enableApacheHttpdInstrumentation: o.enableApacheHttpdInstrumentation, enableDotNetInstrumentation: o.enableDotNetInstrumentation, @@ -125,12 +129,22 @@ func (c *Config) AutoDetect() error { return err } c.openshiftRoutesAvailability = ora + c.logger.V(2).Info("openshift routes detected", "availability", ora) pcrd, err := c.autoDetect.PrometheusCRsAvailability() if err != nil { return err } c.prometheusCRAvailability = pcrd + c.logger.V(2).Info("prometheus cr detected", "availability", pcrd) + + rAuto, err := c.autoDetect.RBACPermissions(context.Background()) + if err != nil { + c.logger.V(2).Info("the rbac permissions are not set for the operator", "reason", err) + } + c.createRBACPermissions = rAuto + c.logger.V(2).Info("create rbac permissions detected", "availability", rAuto) + return nil } @@ -185,7 +199,7 @@ func (c *Config) CollectorConfigMapEntry() string { } // CreateRBACPermissions is true when the operator can create RBAC permissions for SAs running a collector instance. Immutable. -func (c *Config) CreateRBACPermissions() bool { +func (c *Config) CreateRBACPermissions() autoRBAC.Availability { return c.createRBACPermissions } diff --git a/internal/config/main_test.go b/internal/config/main_test.go index 7c22e65687..1f3886f776 100644 --- a/internal/config/main_test.go +++ b/internal/config/main_test.go @@ -15,6 +15,7 @@ package config_test import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -23,6 +24,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" "github.com/open-telemetry/opentelemetry-operator/internal/config" ) @@ -51,6 +53,9 @@ func TestConfigChangesOnAutoDetect(t *testing.T) { PrometheusCRsAvailabilityFunc: func() (prometheus.Availability, error) { return prometheus.Available, nil }, + RBACPermissionsFunc: func(ctx context.Context) (rbac.Availability, error) { + return rbac.Available, nil + }, } cfg := config.New( config.WithAutoDetect(mock), @@ -74,6 +79,7 @@ var _ autodetect.AutoDetect = (*mockAutoDetect)(nil) type mockAutoDetect struct { OpenShiftRoutesAvailabilityFunc func() (openshift.RoutesAvailability, error) PrometheusCRsAvailabilityFunc func() (prometheus.Availability, error) + RBACPermissionsFunc func(ctx context.Context) (rbac.Availability, error) } func (m *mockAutoDetect) OpenShiftRoutesAvailability() (openshift.RoutesAvailability, error) { @@ -89,3 +95,10 @@ func (m *mockAutoDetect) PrometheusCRsAvailability() (prometheus.Availability, e } return prometheus.NotAvailable, nil } + +func (m *mockAutoDetect) RBACPermissions(ctx context.Context) (rbac.Availability, error) { + if m.RBACPermissionsFunc != nil { + return m.RBACPermissionsFunc(ctx) + } + return rbac.NotAvailable, nil +} diff --git a/internal/config/options.go b/internal/config/options.go index 40ce1dade9..7635059413 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -23,6 +23,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" + autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" "github.com/open-telemetry/opentelemetry-operator/internal/version" ) @@ -42,7 +43,7 @@ type options struct { autoInstrumentationNginxImage string collectorImage string collectorConfigMapEntry string - createRBACPermissions bool + createRBACPermissions autoRBAC.Availability enableMultiInstrumentation bool enableApacheHttpdInstrumentation bool enableDotNetInstrumentation bool @@ -86,11 +87,6 @@ func WithCollectorConfigMapEntry(s string) Option { o.collectorConfigMapEntry = s } } -func WithCreateRBACPermissions(s bool) Option { - return func(o *options) { - o.createRBACPermissions = s - } -} func WithEnableMultiInstrumentation(s bool) Option { return func(o *options) { o.enableMultiInstrumentation = s @@ -206,6 +202,12 @@ func WithPrometheusCRAvailability(pcrd prometheus.Availability) Option { } } +func WithRBACPermissions(rAuto autoRBAC.Availability) Option { + return func(o *options) { + o.createRBACPermissions = rAuto + } +} + func WithLabelFilters(labelFilters []string) Option { return func(o *options) { diff --git a/internal/manifests/collector/adapters/config_to_rbac_test.go b/internal/manifests/collector/adapters/config_to_rbac_test.go index 28774ca846..8260d23648 100644 --- a/internal/manifests/collector/adapters/config_to_rbac_test.go +++ b/internal/manifests/collector/adapters/config_to_rbac_test.go @@ -51,7 +51,7 @@ service: desc: "resourcedetection-processor k8s", config: `processors: resourcedetection: - detectors: [kubernetes] + detectors: [k8snode] service: pipelines: traces: diff --git a/internal/manifests/collector/collector.go b/internal/manifests/collector/collector.go index 3b984d6918..9cb2302bba 100644 --- a/internal/manifests/collector/collector.go +++ b/internal/manifests/collector/collector.go @@ -18,6 +18,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" "github.com/open-telemetry/opentelemetry-operator/internal/manifests" "github.com/open-telemetry/opentelemetry-operator/pkg/featuregate" ) @@ -60,7 +61,7 @@ func Build(params manifests.Params) ([]client.Object, error) { } } - if params.Config.CreateRBACPermissions() { + if params.Config.CreateRBACPermissions() == rbac.Available { manifestFactories = append(manifestFactories, manifests.Factory(ClusterRole), manifests.Factory(ClusterRoleBinding), diff --git a/internal/manifests/collector/parser/processor/processor_k8sattributes.go b/internal/manifests/collector/parser/processor/processor_k8sattributes.go index 293411acbf..76fb45e08f 100644 --- a/internal/manifests/collector/parser/processor/processor_k8sattributes.go +++ b/internal/manifests/collector/parser/processor/processor_k8sattributes.go @@ -57,15 +57,19 @@ func (o *K8sAttributesParser) GetRBACRules() []rbacv1.PolicyRule { Resources: []string{"pods", "namespaces"}, Verbs: []string{"get", "watch", "list"}, }, - { - APIGroups: []string{"apps"}, - Resources: []string{"replicasets"}, - Verbs: []string{"get", "watch", "list"}, - }, + } + + replicasetPolicy := rbacv1.PolicyRule{ + APIGroups: []string{"apps"}, + Resources: []string{"replicasets"}, + Verbs: []string{"get", "watch", "list"}, } extractCfg, ok := o.config["extract"] if !ok { + // k8s.deployment.name is enabled by default so, replicasets permissions are needed + // https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/32248#discussion_r1560077826 + prs = append(prs, replicasetPolicy) return prs } @@ -81,7 +85,9 @@ func (o *K8sAttributesParser) GetRBACRules() []rbacv1.PolicyRule { for _, m := range metadata { metadataField := fmt.Sprint(m) - if strings.Contains(metadataField, "k8s.node") { + if metadataField == "k8s.deployment.uid" || metadataField == "k8s.deployment.name" { + prs = append(prs, replicasetPolicy) + } else if strings.Contains(metadataField, "k8s.node") { prs = append(prs, rbacv1.PolicyRule{ APIGroups: []string{""}, diff --git a/internal/manifests/collector/parser/processor/processor_k8sattributes_test.go b/internal/manifests/collector/parser/processor/processor_k8sattributes_test.go index c6328cc51f..7274ffaa3d 100644 --- a/internal/manifests/collector/parser/processor/processor_k8sattributes_test.go +++ b/internal/manifests/collector/parser/processor/processor_k8sattributes_test.go @@ -48,11 +48,11 @@ func TestK8sAttributesRBAC(t *testing.T) { }, }, { - name: "extract k8s.node", + name: "extract k8s.deployment.name", config: map[interface{}]interface{}{ "extract": map[interface{}]interface{}{ "metadata": []interface{}{ - "k8s.node", + "k8s.deployment.name", }, }, }, @@ -67,6 +67,62 @@ func TestK8sAttributesRBAC(t *testing.T) { Resources: []string{"replicasets"}, Verbs: []string{"get", "watch", "list"}, }, + }, + }, + { + name: "extract k8s.deployment.uid", + config: map[interface{}]interface{}{ + "extract": map[interface{}]interface{}{ + "metadata": []interface{}{ + "k8s.deployment.uid", + }, + }, + }, + expectedRules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"pods", "namespaces"}, + Verbs: []string{"get", "watch", "list"}, + }, + { + APIGroups: []string{"apps"}, + Resources: []string{"replicasets"}, + Verbs: []string{"get", "watch", "list"}, + }, + }, + }, + { + name: "extract k8s.pod.name", + config: map[interface{}]interface{}{ + "extract": map[interface{}]interface{}{ + "metadata": []interface{}{ + "k8s.pod.name", + }, + }, + }, + expectedRules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"pods", "namespaces"}, + Verbs: []string{"get", "watch", "list"}, + }, + }, + }, + { + name: "extract k8s.node", + config: map[interface{}]interface{}{ + "extract": map[interface{}]interface{}{ + "metadata": []interface{}{ + "k8s.node", + }, + }, + }, + expectedRules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"pods", "namespaces"}, + Verbs: []string{"get", "watch", "list"}, + }, { APIGroups: []string{""}, Resources: []string{"nodes"}, diff --git a/internal/manifests/collector/parser/processor/processor_resourcedetection.go b/internal/manifests/collector/parser/processor/processor_resourcedetection.go index b9038733c5..4145f6baa6 100644 --- a/internal/manifests/collector/parser/processor/processor_resourcedetection.go +++ b/internal/manifests/collector/parser/processor/processor_resourcedetection.go @@ -63,7 +63,7 @@ func (o *ResourceDetectionParser) GetRBACRules() []rbacv1.PolicyRule { for _, d := range detectors { detectorName := fmt.Sprint(d) switch detectorName { - case "kubernetes": + case "k8snode": policy := rbacv1.PolicyRule{ APIGroups: []string{""}, Resources: []string{"nodes"}, diff --git a/internal/manifests/collector/rbac.go b/internal/manifests/collector/rbac.go index 4af9223996..25bad9dcb1 100644 --- a/internal/manifests/collector/rbac.go +++ b/internal/manifests/collector/rbac.go @@ -70,7 +70,7 @@ func ClusterRoleBinding(params manifests.Params) (*rbacv1.ClusterRoleBinding, er return nil, nil } - name := naming.ClusterRoleBinding(params.OtelCol.Name) + name := naming.ClusterRoleBinding(params.OtelCol.Name, params.OtelCol.Namespace) labels := manifestutils.Labels(params.OtelCol.ObjectMeta, name, params.OtelCol.Spec.Image, ComponentOpenTelemetryCollector, params.Config.LabelsFilter()) return &rbacv1.ClusterRoleBinding{ diff --git a/internal/manifests/collector/testdata/rbac_resourcedetectionprocessor_k8s.yaml b/internal/manifests/collector/testdata/rbac_resourcedetectionprocessor_k8s.yaml index 9cd7c5790d..4027b74a15 100644 --- a/internal/manifests/collector/testdata/rbac_resourcedetectionprocessor_k8s.yaml +++ b/internal/manifests/collector/testdata/rbac_resourcedetectionprocessor_k8s.yaml @@ -4,7 +4,7 @@ receivers: grpc: processors: resourcedetection: - detectors: [kubernetes] + detectors: [k8snode] exporters: otlp: endpoint: "otlp:4317" diff --git a/internal/naming/main.go b/internal/naming/main.go index 8b801ce75e..def5adbf2a 100644 --- a/internal/naming/main.go +++ b/internal/naming/main.go @@ -136,8 +136,8 @@ func ClusterRole(otelcol string, namespace string) string { } // ClusterRoleBinding builds the cluster role binding name based on the instance. -func ClusterRoleBinding(otelcol string) string { - return DNSName(Truncate("%s-collector", 63, otelcol)) +func ClusterRoleBinding(otelcol, namespace string) string { + return DNSName(Truncate("%s-%s-collector", 63, otelcol, namespace)) } // TAService returns the name to use for the TargetAllocator service. diff --git a/internal/rbac/format.go b/internal/rbac/format.go new file mode 100644 index 0000000000..784bcc39c2 --- /dev/null +++ b/internal/rbac/format.go @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry 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 rbac + +import ( + "fmt" + "strings" + + v1 "k8s.io/api/authorization/v1" +) + +// WarningsGroupedByResource is a helper to take the missing permissions and format them as warnings. +func WarningsGroupedByResource(reviews []*v1.SubjectAccessReview) []string { + fullResourceToVerbs := make(map[string][]string) + for _, review := range reviews { + if review.Spec.ResourceAttributes != nil { + key := fmt.Sprintf("%s/%s", review.Spec.ResourceAttributes.Group, review.Spec.ResourceAttributes.Resource) + if len(review.Spec.ResourceAttributes.Group) == 0 { + key = review.Spec.ResourceAttributes.Resource + } + fullResourceToVerbs[key] = append(fullResourceToVerbs[key], review.Spec.ResourceAttributes.Verb) + } else if review.Spec.NonResourceAttributes != nil { + key := fmt.Sprintf("nonResourceURL: %s", review.Spec.NonResourceAttributes.Path) + fullResourceToVerbs[key] = append(fullResourceToVerbs[key], review.Spec.NonResourceAttributes.Verb) + } + } + var warnings []string + for fullResource, verbs := range fullResourceToVerbs { + warnings = append(warnings, fmt.Sprintf("missing the following rules for %s: [%s]", fullResource, strings.Join(verbs, ","))) + } + return warnings +} diff --git a/internal/rbac/format_test.go b/internal/rbac/format_test.go new file mode 100644 index 0000000000..82f97d25fe --- /dev/null +++ b/internal/rbac/format_test.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry 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 rbac + +import ( + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/authorization/v1" +) + +func TestWarningsGroupedByResource(t *testing.T) { + tests := []struct { + desc string + reviews []*v1.SubjectAccessReview + expected []string + }{ + { + desc: "No access reviews", + reviews: nil, + expected: nil, + }, + { + desc: "One access review with resource attributes", + reviews: []*v1.SubjectAccessReview{ + { + Spec: v1.SubjectAccessReviewSpec{ + ResourceAttributes: &v1.ResourceAttributes{ + Verb: "get", + Group: "", + Resource: "namespaces", + }, + }, + }, + }, + expected: []string{"missing the following rules for namespaces: [get]"}, + }, + { + desc: "One access review with non resource attributes", + reviews: []*v1.SubjectAccessReview{ + { + Spec: v1.SubjectAccessReviewSpec{ + ResourceAttributes: &v1.ResourceAttributes{ + Verb: "watch", + Group: "apps", + Resource: "replicasets", + }, + }, + }, + }, + expected: []string{"missing the following rules for apps/replicasets: [watch]"}, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + w := WarningsGroupedByResource(tt.reviews) + assert.Equal(t, tt.expected, w) + }) + } + +} diff --git a/main.go b/main.go index fb12b5eccf..dbf9be361f 100644 --- a/main.go +++ b/main.go @@ -139,7 +139,7 @@ func main() { pflag.BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") - pflag.BoolVar(&createRBACPermissions, "create-rbac-permissions", false, "Automatically create RBAC permissions needed by the processors") + pflag.BoolVar(&createRBACPermissions, "create-rbac-permissions", false, "Automatically create RBAC permissions needed by the processors (deprecated)") pflag.BoolVar(&enableMultiInstrumentation, "enable-multi-instrumentation", false, "Controls whether the operator supports multi instrumentation") pflag.BoolVar(&enableApacheHttpdInstrumentation, constants.FlagApacheHttpd, true, "Controls whether the operator supports Apache HTTPD auto-instrumentation") pflag.BoolVar(&enableDotNetInstrumentation, constants.FlagDotNet, true, "Controls whether the operator supports dotnet auto-instrumentation") @@ -199,57 +199,6 @@ func main() { restConfig := ctrl.GetConfigOrDie() - // builds the operator's configuration - ad, err := autodetect.New(restConfig) - if err != nil { - setupLog.Error(err, "failed to setup auto-detect routine") - os.Exit(1) - } - - cfg := config.New( - config.WithLogger(ctrl.Log.WithName("config")), - config.WithVersion(v), - config.WithCollectorImage(collectorImage), - config.WithCreateRBACPermissions(createRBACPermissions), - config.WithEnableMultiInstrumentation(enableMultiInstrumentation), - config.WithEnableApacheHttpdInstrumentation(enableApacheHttpdInstrumentation), - config.WithEnableDotNetInstrumentation(enableDotNetInstrumentation), - config.WithEnableGoInstrumentation(enableGoInstrumentation), - config.WithEnableNginxInstrumentation(enableNginxInstrumentation), - config.WithEnablePythonInstrumentation(enablePythonInstrumentation), - config.WithEnableNodeJSInstrumentation(enableNodeJSInstrumentation), - config.WithEnableJavaInstrumentation(enableJavaInstrumentation), - config.WithTargetAllocatorImage(targetAllocatorImage), - config.WithOperatorOpAMPBridgeImage(operatorOpAMPBridgeImage), - config.WithAutoInstrumentationJavaImage(autoInstrumentationJava), - config.WithAutoInstrumentationNodeJSImage(autoInstrumentationNodeJS), - config.WithAutoInstrumentationPythonImage(autoInstrumentationPython), - config.WithAutoInstrumentationDotNetImage(autoInstrumentationDotNet), - config.WithAutoInstrumentationGoImage(autoInstrumentationGo), - config.WithAutoInstrumentationApacheHttpdImage(autoInstrumentationApacheHttpd), - config.WithAutoInstrumentationNginxImage(autoInstrumentationNginx), - config.WithAutoDetect(ad), - config.WithLabelFilters(labelsFilter), - config.WithAnnotationFilters(annotationsFilter), - ) - err = cfg.AutoDetect() - if err != nil { - setupLog.Error(err, "failed to autodetect config variables") - } - // Only add these to the scheme if they are available - if cfg.PrometheusCRAvailability() == prometheus.Available { - setupLog.Info("Prometheus CRDs are installed, adding to scheme.") - utilruntime.Must(monitoringv1.AddToScheme(scheme)) - } else { - setupLog.Info("Prometheus CRDs are not installed, skipping adding to scheme.") - } - if cfg.OpenShiftRoutesAvailability() == openshift.RoutesAvailable { - setupLog.Info("Openshift CRDs are installed, adding to scheme.") - utilruntime.Must(routev1.Install(scheme)) - } else { - setupLog.Info("Openshift CRDs are not installed, skipping adding to scheme.") - } - var namespaces map[string]cache.Config watchNamespace, found := os.LookupEnv("WATCH_NAMESPACE") if found { @@ -298,17 +247,69 @@ func main() { os.Exit(1) } + clientset, clientErr := kubernetes.NewForConfig(mgr.GetConfig()) + if err != nil { + setupLog.Error(clientErr, "failed to create kubernetes clientset") + } + + reviewer := rbac.NewReviewer(clientset) ctx := ctrl.SetupSignalHandler() - err = addDependencies(ctx, mgr, cfg, v) + + // builds the operator's configuration + ad, err := autodetect.New(restConfig, reviewer) if err != nil { - setupLog.Error(err, "failed to add/run bootstrap dependencies to the controller manager") + setupLog.Error(err, "failed to setup auto-detect routine") os.Exit(1) } - clientset, clientErr := kubernetes.NewForConfig(mgr.GetConfig()) + + cfg := config.New( + config.WithLogger(ctrl.Log.WithName("config")), + config.WithVersion(v), + config.WithCollectorImage(collectorImage), + config.WithEnableMultiInstrumentation(enableMultiInstrumentation), + config.WithEnableApacheHttpdInstrumentation(enableApacheHttpdInstrumentation), + config.WithEnableDotNetInstrumentation(enableDotNetInstrumentation), + config.WithEnableGoInstrumentation(enableGoInstrumentation), + config.WithEnableNginxInstrumentation(enableNginxInstrumentation), + config.WithEnablePythonInstrumentation(enablePythonInstrumentation), + config.WithEnableNodeJSInstrumentation(enableNodeJSInstrumentation), + config.WithEnableJavaInstrumentation(enableJavaInstrumentation), + config.WithTargetAllocatorImage(targetAllocatorImage), + config.WithOperatorOpAMPBridgeImage(operatorOpAMPBridgeImage), + config.WithAutoInstrumentationJavaImage(autoInstrumentationJava), + config.WithAutoInstrumentationNodeJSImage(autoInstrumentationNodeJS), + config.WithAutoInstrumentationPythonImage(autoInstrumentationPython), + config.WithAutoInstrumentationDotNetImage(autoInstrumentationDotNet), + config.WithAutoInstrumentationGoImage(autoInstrumentationGo), + config.WithAutoInstrumentationApacheHttpdImage(autoInstrumentationApacheHttpd), + config.WithAutoInstrumentationNginxImage(autoInstrumentationNginx), + config.WithAutoDetect(ad), + config.WithLabelFilters(labelsFilter), + config.WithAnnotationFilters(annotationsFilter), + ) + err = cfg.AutoDetect() if err != nil { - setupLog.Error(clientErr, "failed to create kubernetes clientset") + setupLog.Error(err, "failed to autodetect config variables") + } + // Only add these to the scheme if they are available + if cfg.PrometheusCRAvailability() == prometheus.Available { + setupLog.Info("Prometheus CRDs are installed, adding to scheme.") + utilruntime.Must(monitoringv1.AddToScheme(scheme)) + } else { + setupLog.Info("Prometheus CRDs are not installed, skipping adding to scheme.") + } + if cfg.OpenShiftRoutesAvailability() == openshift.RoutesAvailable { + setupLog.Info("Openshift CRDs are installed, adding to scheme.") + utilruntime.Must(routev1.Install(scheme)) + } else { + setupLog.Info("Openshift CRDs are not installed, skipping adding to scheme.") + } + + err = addDependencies(ctx, mgr, cfg, v) + if err != nil { + setupLog.Error(err, "failed to add/run bootstrap dependencies to the controller manager") + os.Exit(1) } - reviewer := rbac.NewReviewer(clientset) if err = controllers.NewReconciler(controllers.Params{ Client: mgr.GetClient(), diff --git a/tests/e2e-automatic-rbac/extra-permissions-operator/namespaces.yaml b/tests/e2e-automatic-rbac/extra-permissions-operator/namespaces.yaml new file mode 100644 index 0000000000..99a57e7386 --- /dev/null +++ b/tests/e2e-automatic-rbac/extra-permissions-operator/namespaces.yaml @@ -0,0 +1,15 @@ +- op: add + path: /rules/- + value: + apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/tests/e2e-automatic-rbac/extra-permissions-operator/nodes.yaml b/tests/e2e-automatic-rbac/extra-permissions-operator/nodes.yaml new file mode 100644 index 0000000000..3971ded1a4 --- /dev/null +++ b/tests/e2e-automatic-rbac/extra-permissions-operator/nodes.yaml @@ -0,0 +1,12 @@ +--- +- op: add + path: /rules/- + value: + apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch diff --git a/tests/e2e-automatic-rbac/extra-permissions-operator/rbac.yaml b/tests/e2e-automatic-rbac/extra-permissions-operator/rbac.yaml new file mode 100644 index 0000000000..c36e0c2213 --- /dev/null +++ b/tests/e2e-automatic-rbac/extra-permissions-operator/rbac.yaml @@ -0,0 +1,16 @@ +- op: add + path: /rules/- + value: + apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/tests/e2e-automatic-rbac/extra-permissions-operator/replicaset.yaml b/tests/e2e-automatic-rbac/extra-permissions-operator/replicaset.yaml new file mode 100644 index 0000000000..887c17da54 --- /dev/null +++ b/tests/e2e-automatic-rbac/extra-permissions-operator/replicaset.yaml @@ -0,0 +1,11 @@ +- op: add + path: /rules/- + value: + apiGroups: + - apps + resources: + - replicasets + verbs: + - get + - list + - watch diff --git a/tests/e2e-automatic-rbac/processor-k8sattributes/00-install.yaml b/tests/e2e-automatic-rbac/processor-k8sattributes/00-install.yaml new file mode 100644 index 0000000000..274fa212c1 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-k8sattributes/00-install.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: chainsaw-k8sattributes diff --git a/tests/e2e-automatic-rbac/processor-k8sattributes/01-assert.yaml b/tests/e2e-automatic-rbac/processor-k8sattributes/01-assert.yaml new file mode 100644 index 0000000000..ae72093f6f --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-k8sattributes/01-assert.yaml @@ -0,0 +1,40 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: simplest-chainsaw-k8sattributes-cluster-role +rules: +- apiGroups: + - "" + resources: + - pods + - namespaces + verbs: + - get + - watch + - list +- apiGroups: + - "apps" + resources: + - replicasets + verbs: + - get + - watch + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/instance: chainsaw-k8sattributes.simplest + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: simplest-chainsaw-k8sattributes-collector + name: simplest-chainsaw-k8sattributes-collector +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: simplest-chainsaw-k8sattributes-cluster-role +subjects: +- kind: ServiceAccount + name: simplest-collector + namespace: chainsaw-k8sattributes \ No newline at end of file diff --git a/tests/e2e-automatic-rbac/processor-k8sattributes/01-install.yaml b/tests/e2e-automatic-rbac/processor-k8sattributes/01-install.yaml new file mode 100644 index 0000000000..f498429a54 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-k8sattributes/01-install.yaml @@ -0,0 +1,22 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: simplest + namespace: chainsaw-k8sattributes +spec: + config: | + receivers: + otlp: + protocols: + grpc: + http: + processors: + k8sattributes: + exporters: + debug: + service: + pipelines: + traces: + receivers: [otlp] + processors: [k8sattributes] + exporters: [debug] diff --git a/tests/e2e-automatic-rbac/processor-k8sattributes/02-assert.yaml b/tests/e2e-automatic-rbac/processor-k8sattributes/02-assert.yaml new file mode 100644 index 0000000000..b1a5b4a58d --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-k8sattributes/02-assert.yaml @@ -0,0 +1,40 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: simplest-chainsaw-k8sattributes-cluster-role +rules: +- apiGroups: + - "" + resources: + - pods + - namespaces + verbs: + - get + - watch + - list +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - watch + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/instance: chainsaw-k8sattributes.simplest + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: simplest-chainsaw-k8sattributes-collector + name: simplest-chainsaw-k8sattributes-collector +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: simplest-chainsaw-k8sattributes-cluster-role +subjects: +- kind: ServiceAccount + name: simplest-collector + namespace: chainsaw-k8sattributes diff --git a/tests/e2e-automatic-rbac/processor-k8sattributes/02-install.yaml b/tests/e2e-automatic-rbac/processor-k8sattributes/02-install.yaml new file mode 100644 index 0000000000..612e9676b3 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-k8sattributes/02-install.yaml @@ -0,0 +1,25 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: simplest + namespace: chainsaw-k8sattributes +spec: + config: | + receivers: + otlp: + protocols: + grpc: + http: + processors: + k8sattributes: + extract: + metadata: + - k8s.node.name + exporters: + debug: + service: + pipelines: + traces: + receivers: [otlp] + processors: [k8sattributes] + exporters: [debug] diff --git a/tests/e2e-automatic-rbac/processor-k8sattributes/chainsaw-test.yaml b/tests/e2e-automatic-rbac/processor-k8sattributes/chainsaw-test.yaml new file mode 100644 index 0000000000..efceb94533 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-k8sattributes/chainsaw-test.yaml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: processor-k8sattributes +spec: + steps: + - name: create-namespace + try: + - apply: + file: 00-install.yaml + - name: default-conf + try: + - apply: + file: 01-install.yaml + - assert: + file: 01-assert.yaml + - name: k8s.node + try: + - apply: + file: 02-install.yaml + - assert: + file: 02-assert.yaml diff --git a/tests/e2e-automatic-rbac/processor-resourcedetection/00-install.yaml b/tests/e2e-automatic-rbac/processor-resourcedetection/00-install.yaml new file mode 100644 index 0000000000..412a68b477 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-resourcedetection/00-install.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: chainsaw-resourcedetection \ No newline at end of file diff --git a/tests/e2e-automatic-rbac/processor-resourcedetection/01-assert.yaml b/tests/e2e-automatic-rbac/processor-resourcedetection/01-assert.yaml new file mode 100644 index 0000000000..077ce1a5e3 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-resourcedetection/01-assert.yaml @@ -0,0 +1,33 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: simplest-chainsaw-resourcedetection-cluster-role +rules: +- apiGroups: + - config.openshift.io + resources: + - infrastructures + - infrastructures/status + verbs: + - get + - watch + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/instance: chainsaw-resourcedetection.simplest + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: simplest-chainsaw-resourcedetection-collector + app.kubernetes.io/part-of: opentelemetry + name: simplest-chainsaw-resourcedetection-collector +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: simplest-chainsaw-resourcedetection-cluster-role +subjects: +- kind: ServiceAccount + name: simplest-collector + namespace: chainsaw-resourcedetection diff --git a/tests/e2e-automatic-rbac/processor-resourcedetection/01-install.yaml b/tests/e2e-automatic-rbac/processor-resourcedetection/01-install.yaml new file mode 100644 index 0000000000..139126ad34 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-resourcedetection/01-install.yaml @@ -0,0 +1,25 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: simplest + namespace: chainsaw-resourcedetection +spec: + config: | + receivers: + otlp: + protocols: + grpc: + http: + processors: + resourcedetection: + detectors: [openshift] + timeout: 2s + override: false + exporters: + debug: + service: + pipelines: + traces: + receivers: [otlp] + processors: [resourcedetection] + exporters: [debug] diff --git a/tests/e2e-automatic-rbac/processor-resourcedetection/02-assert.yaml b/tests/e2e-automatic-rbac/processor-resourcedetection/02-assert.yaml new file mode 100644 index 0000000000..ecdea43a56 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-resourcedetection/02-assert.yaml @@ -0,0 +1,31 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: simplest-chainsaw-resourcedetection-cluster-role +rules: +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/instance: chainsaw-resourcedetection.simplest + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: simplest-chainsaw-resourcedetection-collector + app.kubernetes.io/part-of: opentelemetry + name: simplest-chainsaw-resourcedetection-collector +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: simplest-chainsaw-resourcedetection-cluster-role +subjects: +- kind: ServiceAccount + name: simplest-collector + namespace: chainsaw-resourcedetection diff --git a/tests/e2e-automatic-rbac/processor-resourcedetection/02-install.yaml b/tests/e2e-automatic-rbac/processor-resourcedetection/02-install.yaml new file mode 100644 index 0000000000..f155149391 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-resourcedetection/02-install.yaml @@ -0,0 +1,25 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: simplest + namespace: chainsaw-resourcedetection +spec: + config: | + receivers: + otlp: + protocols: + grpc: + http: + processors: + resourcedetection: + detectors: [k8snode] + timeout: 2s + override: false + exporters: + debug: + service: + pipelines: + traces: + receivers: [otlp] + processors: [resourcedetection] + exporters: [debug] diff --git a/tests/e2e-automatic-rbac/processor-resourcedetection/chainsaw-test.yaml b/tests/e2e-automatic-rbac/processor-resourcedetection/chainsaw-test.yaml new file mode 100644 index 0000000000..8d0e7ccbd9 --- /dev/null +++ b/tests/e2e-automatic-rbac/processor-resourcedetection/chainsaw-test.yaml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: processor-resourcedetection +spec: + steps: + - name: create-namespace + try: + - apply: + file: 00-install.yaml + - name: openshift-detector + try: + - apply: + file: 01-install.yaml + - assert: + file: 01-assert.yaml + - name: k8snode-detector + try: + - apply: + file: 02-install.yaml + - assert: + file: 02-assert.yaml