Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
project: charts/operator
kind: Removed
body: |
`gcr.io/kubebuilder/kube-rbac-proxy` container is deprecated and has been removed from the Redpanda
operator helm chart. The same ports will continue to serve metrics using kubebuilder's built in RBAC.

Any existing prometheus rules don't need to be adjusted.

For more details see: https://github.com/kubernetes-sigs/kubebuilder/discussions/3907
time: 2025-02-26T18:20:37.65853+01:00
10 changes: 10 additions & 0 deletions .changes/unreleased/operator-Removed-20250226-182037.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
project: operator
kind: Removed
body: |
`gcr.io/kubebuilder/kube-rbac-proxy` container is deprecated and has been removed from the Redpanda
operator helm chart. The same ports will continue to serve metrics using kubebuilder's built in RBAC.

Any existing prometheus rules don't need to be adjusted.

For more details see: https://github.com/kubernetes-sigs/kubebuilder/discussions/3907
time: 2025-02-26T18:20:37.658528+01:00
26 changes: 26 additions & 0 deletions acceptance/features/operator.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Feature: Metrics endpoint has authentication and authorization

@skip:gke @skip:aks @skip:eks
Scenario: Reject request without TLS
Given the operator is running
Then its metrics endpoint should reject http request with status code "400"

@skip:gke @skip:aks @skip:eks
Scenario: Reject unauthenticated token
Given the operator is running
Then its metrics endpoint should reject authorization random token request with status code "500"

@skip:gke @skip:aks @skip:eks
Scenario: Accept request
Given the operator is running
When I apply Kubernetes manifest:
"""
apiVersion: v1
kind: ServiceAccount
metadata:
name: testing
"""
And "testing" service account has bounded "redpanda-operator-metrics-reader" cluster role
Then its metrics endpoint should accept https request with "testing" service account token


4 changes: 2 additions & 2 deletions acceptance/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.23.2
require (
github.com/cucumber/godog v0.14.1
github.com/go-logr/logr v1.4.2
github.com/prometheus/common v0.55.0
github.com/quasilyte/go-ruleguard/dsl v0.3.22
github.com/redpanda-data/common-go/rpadmin v0.1.13-0.20250109154132-12ac78a58f95
github.com/redpanda-data/redpanda-operator/harpoon v0.0.0-00010101000000-000000000000
Expand All @@ -16,6 +17,7 @@ require (
github.com/twmb/franz-go/pkg/sr v1.2.0
k8s.io/api v0.30.3
k8s.io/apimachinery v0.30.3
k8s.io/client-go v0.30.3
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/controller-runtime v0.18.5
)
Expand Down Expand Up @@ -166,7 +168,6 @@ require (
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.76.2 // indirect
github.com/prometheus/client_golang v1.20.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/redpanda-data/common-go/net v0.1.0 // indirect
github.com/redpanda-data/console/backend v0.0.0-20240303221210-05d5d9e85f20 // indirect
Expand Down Expand Up @@ -227,7 +228,6 @@ require (
k8s.io/apiextensions-apiserver v0.30.3 // indirect
k8s.io/apiserver v0.30.3 // indirect
k8s.io/cli-runtime v0.30.3 // indirect
k8s.io/client-go v0.30.3 // indirect
k8s.io/component-base v0.30.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f // indirect
Expand Down
107 changes: 107 additions & 0 deletions acceptance/steps/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,29 @@ package steps

import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"time"

"github.com/cucumber/godog"
"github.com/prometheus/common/expfmt"
"github.com/redpanda-data/common-go/rpadmin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/twmb/franz-go/pkg/kadm"
"github.com/twmb/franz-go/pkg/kgo"
"github.com/twmb/franz-go/pkg/sr"
appsv1 "k8s.io/api/apps/v1"
authenticationv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"k8s.io/utils/ptr"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"

Expand Down Expand Up @@ -354,3 +362,102 @@ func checkStableResource(ctx context.Context, t framework.TestingT, o runtimecli
}, 30*time.Second, 1*time.Second, "Resource never stabilized")
t.Logf("Resource %q has been stable for 5 seconds", key.String())
}

type operatorClients struct {
client http.Client
operatorPodName string
namespace string
schema string
token string
expectedStatusCode int
}

func (c *operatorClients) ExpectRequestRejected(ctx context.Context) {
t := framework.T(ctx)

url := fmt.Sprintf("%s://%s.%s:8443/metrics", c.schema, c.operatorPodName, c.namespace)

t.Logf("Request %s to operator", url)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
require.NoError(t, err)

req.Header.Set("Authorization", "Bearer "+c.token)
resp, err := c.client.Do(req)
require.NoError(t, err)

require.Equal(t, c.expectedStatusCode, resp.StatusCode)

defer resp.Body.Close()
}

func (c *operatorClients) ExpectCorrectMetricsResponse(ctx context.Context) {
t := framework.T(ctx)

url := fmt.Sprintf("%s://%s.%s:8443/metrics", c.schema, c.operatorPodName, c.namespace)

t.Logf("Request %s to operator", url)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
require.NoError(t, err)

req.Header.Set("Authorization", "Bearer "+c.token)
resp, err := c.client.Do(req)
require.NoError(t, err)

require.Equal(t, http.StatusOK, resp.StatusCode)

defer resp.Body.Close()

var parser expfmt.TextParser
_, err = parser.TextToMetricFamilies(resp.Body)
require.NoError(t, err)
}

func clientsForOperator(ctx context.Context, includeTLS bool, serviceAccountName, expectedStatusCode string) *operatorClients {
t := framework.T(ctx)

var dep appsv1.Deployment
require.NoError(t, t.Get(ctx, t.ResourceKey("redpanda-operator"), &dep))

var podList corev1.PodList

require.NoError(t, t.List(ctx, &podList, &runtimeclient.ListOptions{
LabelSelector: labels.SelectorFromSet(dep.Spec.Selector.MatchLabels),
}))

require.Len(t, podList.Items, 1, "expected 1 pod, got %d", len(podList.Items))

var tlsCfg tls.Config
schema := "http"
if includeTLS {
tlsCfg = tls.Config{InsecureSkipVerify: includeTLS} // nolint:gosec
schema = "https"
}

token := "non-existing-token"
if serviceAccountName != "" {
cs, err := kubernetes.NewForConfig(t.RestConfig())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, can you not use the client.Client for this? These resources are always a bit weird... Is it a subresource?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only client.Client have typed client that implements CreateToken function.

require.NoError(t, err)
tokenResponse, err := cs.CoreV1().ServiceAccounts(t.Namespace()).CreateToken(ctx, serviceAccountName, &authenticationv1.TokenRequest{}, metav1.CreateOptions{})
require.NoError(t, err)
token = tokenResponse.Status.Token
}

statusCode := http.StatusOK
if expectedStatusCode != "" {
var err error
statusCode, err = strconv.Atoi(expectedStatusCode)
require.NoError(t, err)
}

return &operatorClients{
expectedStatusCode: statusCode,
token: token,
schema: schema,
namespace: t.Namespace(),
operatorPodName: podList.Items[0].Name,
client: http.Client{Transport: &http.Transport{
TLSClientConfig: &tlsCfg,
DialContext: kube.NewPodDialer(t.RestConfig()).DialContext,
}},
}
}
78 changes: 78 additions & 0 deletions acceptance/steps/operator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2025 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

package steps

import (
"context"

"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

framework "github.com/redpanda-data/redpanda-operator/harpoon"
)

func operatorIsRunning(ctx context.Context, t framework.TestingT) {
var dep appsv1.Deployment
require.NoError(t, t.Get(ctx, t.ResourceKey("redpanda-operator"), &dep))

// make sure the resource is stable
checkStableResource(ctx, t, &dep)

require.Equal(t, dep.Status.AvailableReplicas, int32(1))
require.Equal(t, dep.Status.Replicas, int32(1))
require.Equal(t, dep.Status.ReadyReplicas, int32(1))
require.Equal(t, dep.Status.UnavailableReplicas, int32(0))
}

func requestMetricsEndpointPlainHTTP(ctx context.Context, statusCode string) {
clientsForOperator(ctx, false, "", statusCode).ExpectRequestRejected(ctx)
}

func requestMetricsEndpointWithTLSAndRandomToken(ctx context.Context, statusCode string) {
clientsForOperator(ctx, true, "", statusCode).ExpectRequestRejected(ctx)
}

func acceptServiceAccountMetricsRequest(ctx context.Context, serviceAccountName string) {
clientsForOperator(ctx, true, serviceAccountName, "").ExpectCorrectMetricsResponse(ctx)
}

func createClusterRoleBinding(ctx context.Context, serviceAccountName, clusterRoleName string) {
t := framework.T(ctx)

require.NoError(t, t.Create(ctx, &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName,
Namespace: t.Namespace(),
},
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: clusterRoleName,
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: serviceAccountName,
Namespace: t.Namespace(),
},
},
}))

t.Cleanup(func(ctx context.Context) {
require.NoError(t, t.Delete(ctx, &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName,
Namespace: t.Namespace(),
},
}))
})
}
7 changes: 7 additions & 0 deletions acceptance/steps/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ func init() {
framework.RegisterStep(`^"([^"]*)" should exist and be able to authenticate to the "([^"]*)" cluster$`, shouldExistAndBeAbleToAuthenticateToTheCluster)
framework.RegisterStep(`^"([^"]*)" should be able to authenticate to the "([^"]*)" cluster with password "([^"]*)" and mechanism "([^"]*)"$`, shouldBeAbleToAuthenticateToTheClusterWithPasswordAndMechanism)
framework.RegisterStep(`^there should be ACLs in the cluster "([^"]*)" for user "([^"]*)"$`, thereShouldBeACLsInTheClusterForUser)

// Operator scenario steps
framework.RegisterStep(`^the operator is running$`, operatorIsRunning)
framework.RegisterStep(`^its metrics endpoint should reject http request with status code "([^"]*)"$`, requestMetricsEndpointPlainHTTP)
framework.RegisterStep(`^its metrics endpoint should reject authorization random token request with status code "([^"]*)"$`, requestMetricsEndpointWithTLSAndRandomToken)
framework.RegisterStep(`^"([^"]*)" service account has bounded "([^"]*)" cluster role$`, createClusterRoleBinding)
framework.RegisterStep(`^its metrics endpoint should accept https request with "([^"]*)" service account token$`, acceptServiceAccountMetricsRequest)
}
10 changes: 10 additions & 0 deletions charts/operator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and is generated by [Changie](https://github.com/miniscruff/changie).


## Unreleased
### Removed
* `gcr.io/kubebuilder/kube-rbac-proxy` container is deprecated and has been removed from the Redpanda
operator helm chart. The same ports will continue to serve metrics using kubebuilder's built in RBAC.

Any existing prometheus rules don't need to be adjusted.

For more details see: https://github.com/kubernetes-sigs/kubebuilder/discussions/3907


## v0.4.39 - 2025-01-17
### Deprecated
* `configurator`, `configurator.tag`, `configurator.pullPolicy`, and `configurator.repository` are no
Expand Down
Loading