Skip to content

Commit

Permalink
feat: require TLS client certificate for /metrics
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Pasquier <spasquie@redhat.com>
  • Loading branch information
simonpasquier committed Oct 30, 2024
1 parent a826c09 commit a11993f
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 6 deletions.
3 changes: 2 additions & 1 deletion cmd/operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,14 @@ func main() {
op, err := operator.New(
ctx,
operator.NewOperatorConfiguration(
operator.WithNamespace(namespace),
operator.WithMetricsAddr(metricsAddr),
operator.WithHealthProbeAddr(healthProbeAddr),
operator.WithPrometheusImage(imgMap["prometheus"]),
operator.WithAlertmanagerImage(imgMap["alertmanager"]),
operator.WithThanosSidecarImage(imgMap["thanos"]),
operator.WithThanosQuerierImage(imgMap["thanos"]),
operator.WithUIPlugins(namespace, imgMap),
operator.WithUIPluginImages(imgMap),
operator.WithFeatureGates(operator.FeatureGates{
OpenShift: operator.OpenShiftFeatureGates{
Enabled: openShiftEnabled,
Expand Down
32 changes: 27 additions & 5 deletions pkg/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"k8s.io/client-go/kubernetes"
eventsv1 "k8s.io/client-go/kubernetes/typed/events/v1"
"k8s.io/client-go/tools/events"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
Expand Down Expand Up @@ -51,6 +53,7 @@ type FeatureGates struct {
}

type OperatorConfiguration struct {
Namespace string
MetricsAddr string
HealthProbeAddr string
Prometheus stackctrl.PrometheusConfiguration
Expand All @@ -61,6 +64,13 @@ type OperatorConfiguration struct {
FeatureGates FeatureGates
}

func WithNamespace(ns string) func(*OperatorConfiguration) {
return func(oc *OperatorConfiguration) {
oc.Namespace = ns
oc.UIPlugins.ResourcesNamespace = ns
}
}

func WithPrometheusImage(image string) func(*OperatorConfiguration) {
return func(oc *OperatorConfiguration) {
oc.Prometheus.Image = image
Expand Down Expand Up @@ -97,10 +107,9 @@ func WithHealthProbeAddr(addr string) func(*OperatorConfiguration) {
}
}

func WithUIPlugins(namespace string, images map[string]string) func(*OperatorConfiguration) {
func WithUIPluginImages(images map[string]string) func(*OperatorConfiguration) {
return func(oc *OperatorConfiguration) {
oc.UIPlugins.Images = images
oc.UIPlugins.ResourcesNamespace = namespace
}
}

Expand All @@ -120,6 +129,7 @@ func NewOperatorConfiguration(opts ...func(*OperatorConfiguration)) *OperatorCon

func New(ctx context.Context, cfg *OperatorConfiguration) (*Operator, error) {
restConfig := ctrl.GetConfigOrDie()
scheme := NewScheme(cfg)

metricsOpts := metricsserver.Options{
BindAddress: cfg.MetricsAddr,
Expand Down Expand Up @@ -174,14 +184,26 @@ func New(ctx context.Context, cfg *OperatorConfiguration) (*Operator, error) {
return nil, fmt.Errorf("failed to initialize client CA controller: %w", err)
}

eventsClient, err := eventsv1.NewForConfig(restConfig)
if err != nil {
return nil, err
}

eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: eventsClient})
err = eventBroadcaster.StartRecordingToSinkWithContext(ctx)
if err != nil {
return nil, err
}
eventRecorder := eventBroadcaster.NewRecorder(scheme, "cluster-observability-operator")

servingCertController = dynamiccertificates.NewDynamicServingCertificateController(
&tls.Config{
ClientAuth: tls.NoClientCert,
ClientAuth: tls.RequireAndVerifyClientCert,
},
clientCAProvider,
certKeyProvider,
nil,
nil,
eventRecorder,
)
if err := servingCertController.RunOnce(); err != nil {
return nil, fmt.Errorf("failed to initialize serving certificate controller: %w", err)
Expand All @@ -198,7 +220,7 @@ func New(ctx context.Context, cfg *OperatorConfiguration) (*Operator, error) {
mgr, err := ctrl.NewManager(
restConfig,
ctrl.Options{
Scheme: NewScheme(cfg),
Scheme: scheme,
Metrics: metricsOpts,
HealthProbeBindAddress: cfg.HealthProbeAddr,
})
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/framework/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ func (f *Framework) getPodMetrics(ctx context.Context, pod *v1.Pod, opts ...func
tr.TLSClientConfig = &tls.Config{
ServerName: fmt.Sprintf("observability-operator.%s.svc", pod.Namespace),
RootCAs: f.RootCA,
GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return f.MetricsClientCert, nil
},
}

resp, err := (&http.Client{Transport: tr}).Do(req)
Expand Down
28 changes: 28 additions & 0 deletions test/e2e/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package framework
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
Expand Down Expand Up @@ -34,6 +35,7 @@ type Framework struct {
Retain bool
IsOpenshiftCluster bool
RootCA *x509.CertPool
MetricsClientCert *tls.Certificate
OperatorNamespace string
}

Expand Down Expand Up @@ -74,6 +76,32 @@ func (f *Framework) Setup() error {
}
f.RootCA = rootCA

// Load the prometheus-k8s TLS client certificate.
var s v1.Secret
key = client.ObjectKey{
Namespace: "openshift-monitoring",
Name: "metrics-client-certs",
}
if err := f.K8sClient.Get(context.Background(), key, &s); err != nil {
return err
}

cert, found := s.Data["tls.crt"]
if !found {
return errors.New("failed to find TLS client certificate")
}

k, found := s.Data["tls.key"]
if !found {
return errors.New("failed to find TLS client key")
}

c, err := tls.X509KeyPair(cert, k)
if err != nil {
return err
}
f.MetricsClientCert = &c

return nil
}

Expand Down

0 comments on commit a11993f

Please sign in to comment.