Skip to content

Commit 6132bc3

Browse files
committed
Bug 1809195: Send CVO metrics over https
CVO metrics are currently being sent insecurely over http. Change metrics configuration to use https and TLS encryption. CVO will continue to use the same metrics port and will continue to support http so a connection mux is setup on the existing port.
1 parent e318b86 commit 6132bc3

File tree

4 files changed

+97
-6
lines changed

4 files changed

+97
-6
lines changed

cmd/start.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@ func init() {
3030
cmd.PersistentFlags().BoolVar(&opts.EnableAutoUpdate, "enable-auto-update", opts.EnableAutoUpdate, "Enables the autoupdate controller.")
3131
cmd.PersistentFlags().BoolVar(&opts.EnableDefaultClusterVersion, "enable-default-cluster-version", opts.EnableDefaultClusterVersion, "Allows the operator to create a ClusterVersion object if one does not already exist.")
3232
cmd.PersistentFlags().StringVar(&opts.ReleaseImage, "release-image", opts.ReleaseImage, "The Openshift release image url.")
33+
cmd.PersistentFlags().StringVar(&opts.ServingCertFile, "serving-cert-file", opts.ServingCertFile, "The X.509 certificate file for serving metrics over HTTPS. You must set both --serving-cert-file and --serving-key-file, or neither.")
34+
cmd.PersistentFlags().StringVar(&opts.ServingKeyFile, "serving-key-file", opts.ServingKeyFile, "The X.509 key file for serving metrics over HTTPS. You must set both --serving-cert-file and --serving-key-file, or neither.")
3335
rootCmd.AddCommand(cmd)
3436
}

install/0000_00_cluster-version-operator_03_deployment.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ spec:
2626
- "--release-image={{.ReleaseImage}}"
2727
- "--enable-auto-update=false"
2828
- "--enable-default-cluster-version=true"
29+
- "--serving-cert-file=/etc/tls/serving-cert/tls.crt"
30+
- "--serving-key-file=/etc/tls/serving-cert/tls.key"
2931
- "--v=4"
3032
resources:
3133
requests:
@@ -39,6 +41,9 @@ spec:
3941
- mountPath: /etc/cvo/updatepayloads
4042
name: etc-cvo-updatepayloads
4143
readOnly: true
44+
- mountPath: /etc/tls/serving-cert
45+
name: serving-cert
46+
readOnly: true
4247
env:
4348
- name: KUBERNETES_SERVICE_PORT # allows CVO to communicate with apiserver directly on same host.
4449
value: "6443"
@@ -80,3 +85,6 @@ spec:
8085
- name: etc-cvo-updatepayloads
8186
hostPath:
8287
path: /etc/cvo/updatepayloads
88+
- name: serving-cert
89+
secret:
90+
secretName: cluster-version-operator-serving-cert

install/0000_90_cluster-version-operator_02_servicemonitor.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ metadata:
99
exclude.release.openshift.io/internal-openshift-hosted: "true"
1010
spec:
1111
endpoints:
12+
- bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
1213
- interval: 30s
1314
port: metrics
14-
scheme: http
15+
scheme: https
16+
tlsConfig:
17+
caFile: /etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt
18+
serverName: cluster-version-operator.openshift-cluster-version.svc
1519
namespaceSelector:
1620
matchNames:
1721
- openshift-cluster-version

pkg/start/start.go

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ package start
44

55
import (
66
"context"
7+
"crypto/tls"
78
"fmt"
9+
"io/ioutil"
810
"math/rand"
11+
"net"
912
"net/http"
1013
"os"
1114
"os/signal"
1215
"sync"
1316
"syscall"
1417
"time"
1518

19+
"github.com/cockroachdb/cmux"
20+
1621
"github.com/google/uuid"
1722
"github.com/prometheus/client_golang/prometheus/promhttp"
1823
v1 "k8s.io/api/core/v1"
@@ -34,6 +39,7 @@ import (
3439
"github.com/openshift/cluster-version-operator/pkg/autoupdate"
3540
"github.com/openshift/cluster-version-operator/pkg/cvo"
3641
"github.com/openshift/cluster-version-operator/pkg/internal"
42+
"github.com/openshift/library-go/pkg/crypto"
3743
)
3844

3945
const (
@@ -49,7 +55,9 @@ const (
4955

5056
// Options are the valid inputs to starting the CVO.
5157
type Options struct {
52-
ReleaseImage string
58+
ReleaseImage string
59+
ServingCertFile string
60+
ServingKeyFile string
5361

5462
Kubeconfig string
5563
NodeName string
@@ -103,6 +111,12 @@ func (o *Options) Run() error {
103111
if o.ReleaseImage == "" {
104112
return fmt.Errorf("missing --release-image flag, it is required")
105113
}
114+
if o.ServingCertFile == "" && o.ServingKeyFile != "" {
115+
return fmt.Errorf("--serving-key-file was set, so --serving-cert-file must also be set")
116+
}
117+
if o.ServingKeyFile == "" && o.ServingCertFile != "" {
118+
return fmt.Errorf("--serving-cert-file was set, so --serving-key-file must also be set")
119+
}
106120
if len(o.PayloadOverride) > 0 {
107121
klog.Warningf("Using an override payload directory for testing only: %s", o.PayloadOverride)
108122
}
@@ -153,16 +167,79 @@ func (o *Options) Run() error {
153167
return nil
154168
}
155169

170+
func (o *Options) makeTLSConfig() (*tls.Config, error) {
171+
// Load the initial certificate contents.
172+
certBytes, err := ioutil.ReadFile(o.ServingCertFile)
173+
if err != nil {
174+
return nil, err
175+
}
176+
keyBytes, err := ioutil.ReadFile(o.ServingKeyFile)
177+
if err != nil {
178+
return nil, err
179+
}
180+
certificate, err := tls.X509KeyPair(certBytes, keyBytes)
181+
if err != nil {
182+
return nil, err
183+
}
184+
185+
return crypto.SecureTLSConfig(&tls.Config{
186+
GetCertificate: func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
187+
return &certificate, nil
188+
},
189+
}), nil
190+
}
191+
156192
func (o *Options) run(ctx context.Context, controllerCtx *Context, lock *resourcelock.ConfigMapLock) {
157193
// listen on metrics
158194
if len(o.ListenAddr) > 0 {
159-
mux := http.NewServeMux()
160-
mux.Handle("/metrics", promhttp.Handler())
195+
handler := http.NewServeMux()
196+
handler.Handle("/metrics", promhttp.Handler())
197+
tcpl, err := net.Listen("tcp", o.ListenAddr)
198+
if err != nil {
199+
klog.Fatalf("Listen error: %v", err)
200+
}
201+
202+
// if a TLS connection was requested, set up a connection mux that will send TLS requests to
203+
// the TLS server but send HTTP requests to the HTTP server. Preserves the ability for legacy
204+
// HTTP, needed during upgrade, while still allowing TLS certs and end to end metrics protection.
205+
m := cmux.New(tcpl)
206+
207+
// match HTTP first
208+
httpl := m.Match(cmux.HTTP1())
161209
go func() {
162-
if err := http.ListenAndServe(o.ListenAddr, mux); err != nil {
163-
klog.Fatalf("Unable to start metrics server: %v", err)
210+
s := &http.Server{
211+
Handler: handler,
212+
}
213+
if err := s.Serve(httpl); err != cmux.ErrListenerClosed {
214+
klog.Fatalf("HTTP serve error: %v", err)
164215
}
165216
}()
217+
218+
if o.ServingCertFile != "" || o.ServingKeyFile != "" {
219+
tlsConfig, err := o.makeTLSConfig()
220+
if err != nil {
221+
klog.Fatalf("Failed to create TLS config: %v", err)
222+
}
223+
224+
tlsListener := tls.NewListener(m.Match(cmux.Any()), tlsConfig)
225+
klog.Infof("Metrics port listening for HTTP and HTTPS on %v", o.ListenAddr)
226+
go func() {
227+
s := &http.Server{
228+
Handler: handler,
229+
}
230+
if err := s.Serve(tlsListener); err != cmux.ErrListenerClosed {
231+
klog.Fatalf("HTTPS serve error: %v", err)
232+
}
233+
}()
234+
235+
go func() {
236+
if err := m.Serve(); err != nil {
237+
klog.Errorf("CMUX serve error: %v", err)
238+
}
239+
}()
240+
} else {
241+
klog.Infof("Metrics port listening for HTTP on %v", o.ListenAddr)
242+
}
166243
}
167244

168245
exit := make(chan struct{})

0 commit comments

Comments
 (0)