Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

COO-480: feat: enable HTTPS in OpenShift clusters #595

Merged
merged 1 commit into from
Oct 29, 2024
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
Expand Up @@ -42,7 +42,7 @@ metadata:
categories: Monitoring
certified: "false"
containerImage: observability-operator:0.4.2
createdAt: "2024-10-29T09:23:51Z"
createdAt: "2024-10-29T11:40:05Z"
description: A Go based Kubernetes operator to setup and manage highly available
Monitoring Stack using Prometheus, Alertmanager and Thanos Querier.
operators.operatorframework.io/builder: operator-sdk-v1.37.0
Expand Down Expand Up @@ -753,6 +753,10 @@ spec:
capabilities:
drop:
- ALL
volumeMounts:
- mountPath: /etc/tls/private
name: observability-operator-tls
readOnly: true
securityContext:
runAsNonRoot: true
serviceAccountName: observability-operator-sa
Expand All @@ -761,6 +765,11 @@ spec:
- effect: NoSchedule
key: node-role.kubernetes.io/infra
operator: Exists
volumes:
- name: observability-operator-tls
secret:
optional: true
secretName: observability-operator-tls
strategy: deployment
installModes:
- supported: false
Expand Down
2 changes: 2 additions & 0 deletions bundle/manifests/observability-operator_v1_service.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.openshift.io/serving-cert-secret-name: observability-operator-tls
creationTimestamp: null
labels:
app.kubernetes.io/component: operator
Expand Down
4 changes: 3 additions & 1 deletion cmd/operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ func main() {
os.Exit(1)
}

ctx := ctrl.SetupSignalHandler()

op, err := operator.New(
ctx,
operator.NewOperatorConfiguration(
operator.WithMetricsAddr(metricsAddr),
operator.WithHealthProbeAddr(healthProbeAddr),
Expand All @@ -135,7 +138,6 @@ func main() {
os.Exit(1)
}

ctx := ctrl.SetupSignalHandler()
setupLog.Info("starting manager")
if err := op.Start(ctx); err != nil {
setupLog.Error(err, "terminating")
Expand Down
9 changes: 9 additions & 0 deletions deploy/operator/observability-operator-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,14 @@ spec:
httpGet:
path: /healthz
port: 8081
volumeMounts:
- mountPath: /etc/tls/private
name: observability-operator-tls
readOnly: true
serviceAccountName: observability-operator-sa
volumes:
- name: observability-operator-tls
secret:
secretName: observability-operator-tls
optional: true
terminationGracePeriodSeconds: 30
2 changes: 2 additions & 0 deletions deploy/operator/observability-operator-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ metadata:
app.kubernetes.io/component: operator
app.kubernetes.io/name: observability-operator
app.kubernetes.io/part-of: observability-operator
annotations:
service.beta.openshift.io/serving-cert-secret-name: observability-operator-tls
spec:
selector:
app.kubernetes.io/name: observability-operator
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ require (
k8s.io/api v0.31.2
k8s.io/apiextensions-apiserver v0.31.1
k8s.io/apimachinery v0.31.2
k8s.io/client-go v0.31.1
k8s.io/component-base v0.31.1
k8s.io/apiserver v0.31.2
k8s.io/client-go v0.31.2
k8s.io/component-base v0.31.2
k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3
sigs.k8s.io/controller-runtime v0.19.0
)
Expand Down
15 changes: 9 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ github.com/efficientgo/core v1.0.0-rc.2 h1:7j62qHLnrZqO3V3UA0AqOGd5d5aXV3AX6m/NZ
github.com/efficientgo/core v1.0.0-rc.2/go.mod h1:FfGdkzWarkuzOlY04VY+bGfb1lWrjaL6x/GLcQ4vJps=
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
Expand Down Expand Up @@ -91,6 +91,7 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
Expand Down Expand Up @@ -359,10 +360,12 @@ k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/
k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ=
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8=
k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w=
k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4=
k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE=
k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc=
k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs=
k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA=
k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo=
Expand Down
129 changes: 113 additions & 16 deletions pkg/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ package operator

import (
"context"
"crypto/tls"
"fmt"

"os"
"path/filepath"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
Expand All @@ -15,15 +23,23 @@ import (
uictrl "github.com/rhobs/observability-operator/pkg/controllers/uiplugin"
)

// NOTE: The instance selector label is hardcoded in static assets.
// Any change to that must be reflected here as well
const instanceSelector = "app.kubernetes.io/managed-by=observability-operator"
const (
// NOTE: The instance selector label is hardcoded in static assets.
// Any change to that must be reflected here as well
instanceSelector = "app.kubernetes.io/managed-by=observability-operator"

ObservabilityOperatorName = "observability-operator"

const ObservabilityOperatorName = "observability-operator"
// The mount path for the serving certificate seret is hardcoded in the
// static assets.
tlsMountPath = "/etc/tls/private"
)

// Operator embedds manager and exposes only the minimal set of functions
// Operator embeds a manager and a serving certificate controller (for
// OpenShift installations).
type Operator struct {
manager manager.Manager
manager manager.Manager
servingCertController *dynamiccertificates.DynamicServingCertificateController
}

type OpenShiftFeatureGates struct {
Expand Down Expand Up @@ -102,14 +118,90 @@ func NewOperatorConfiguration(opts ...func(*OperatorConfiguration)) *OperatorCon
return cfg
}

func New(cfg *OperatorConfiguration) (*Operator, error) {
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: NewScheme(cfg),
Metrics: metricsserver.Options{
BindAddress: cfg.MetricsAddr,
},
HealthProbeBindAddress: cfg.HealthProbeAddr,
})
func New(ctx context.Context, cfg *OperatorConfiguration) (*Operator, error) {
restConfig := ctrl.GetConfigOrDie()

metricsOpts := metricsserver.Options{
BindAddress: cfg.MetricsAddr,
}

var servingCertController *dynamiccertificates.DynamicServingCertificateController
if cfg.FeatureGates.OpenShift.Enabled {
// When running in OpenShift, the server uses HTTPS thanks to the
// service CA operator.
certFile := filepath.Join(tlsMountPath, "tls.crt")
keyFile := filepath.Join(tlsMountPath, "tls.key")

// Wait for the files to be mounted into the container.
var pollErr error
err := wait.PollUntilContextTimeout(ctx, time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
for _, f := range []string{certFile, keyFile} {
if _, err := os.Stat(f); err != nil {
pollErr = err
return false, nil
}
}

return true, nil
})
if err != nil {
return nil, fmt.Errorf("%w: %w", err, pollErr)
}

// DynamicCertKeyPairContent automatically reloads the certificate and key from disk.
certKeyProvider, err := dynamiccertificates.NewDynamicServingContentFromFiles("serving-cert", certFile, keyFile)
if err != nil {
return nil, err
}
if err := certKeyProvider.RunOnce(ctx); err != nil {
return nil, fmt.Errorf("failed to initialize cert/key content: %w", err)
}

kubeClient, err := kubernetes.NewForConfig(restConfig)
if err != nil {
return nil, err
}

// ConfigMapCAController automatically reloads the client CA.
clientCAProvider, err := dynamiccertificates.NewDynamicCAFromConfigMapController(
"client-ca",
metav1.NamespaceSystem,
"extension-apiserver-authentication",
"client-ca-file",
kubeClient,
)
if err != nil {
return nil, fmt.Errorf("failed to initialize client CA controller: %w", err)
}

servingCertController = dynamiccertificates.NewDynamicServingCertificateController(
&tls.Config{
ClientAuth: tls.NoClientCert,
},
clientCAProvider,
certKeyProvider,
nil,
nil,
)
if err := servingCertController.RunOnce(); err != nil {
return nil, fmt.Errorf("failed to initialize serving certificate controller: %w", err)
}

metricsOpts.SecureServing = true
metricsOpts.TLSOpts = []func(*tls.Config){
func(c *tls.Config) {
c.GetConfigForClient = servingCertController.GetConfigForClient
},
}
}

mgr, err := ctrl.NewManager(
restConfig,
ctrl.Options{
Scheme: NewScheme(cfg),
Metrics: metricsOpts,
HealthProbeBindAddress: cfg.HealthProbeAddr,
})
if err != nil {
return nil, fmt.Errorf("unable to create manager: %w", err)
}
Expand Down Expand Up @@ -141,11 +233,16 @@ func New(cfg *OperatorConfiguration) (*Operator, error) {
}

return &Operator{
manager: mgr,
manager: mgr,
servingCertController: servingCertController,
}, nil
}

func (o *Operator) Start(ctx context.Context) error {
if o.servingCertController != nil {
go o.servingCertController.Run(1, ctx.Done())
}

if err := o.manager.Start(ctx); err != nil {
return fmt.Errorf("unable to start manager: %w", err)
}
Expand Down
Loading