11package cvo
22
33import (
4- "bytes"
54 "context"
6- "crypto/sha256"
7- "crypto/tls"
8- "errors"
95 "fmt"
10- "io"
116 "net"
127 "net/http"
13- "os"
14- "path/filepath"
158 "time"
169
1710 "github.com/prometheus/client_golang/prometheus"
@@ -26,9 +19,6 @@ import (
2619 configv1 "github.com/openshift/api/config/v1"
2720 "github.com/openshift/cluster-version-operator/lib/resourcemerge"
2821 "github.com/openshift/cluster-version-operator/pkg/internal"
29- "github.com/openshift/library-go/pkg/crypto"
30-
31- "gopkg.in/fsnotify.v1"
3222)
3323
3424// RegisterMetrics initializes metrics and registers them with the
@@ -136,28 +126,18 @@ func createHttpServer() *http.Server {
136126 return server
137127}
138128
139- func shutdownHttpServer (parentCtx context.Context , svr * http.Server ) {
140- ctx , cancel := context .WithTimeout (parentCtx , 5 * time .Second )
141- defer cancel ()
142- klog .Info ("Shutting down metrics server so it can be recreated with updated TLS configuration." )
143- if err := svr .Shutdown (ctx ); err != nil {
144- klog .Errorf ("Failed to gracefully shut down metrics server during restart: %v" , err )
145- }
146- }
147-
148- func startListening (svr * http.Server , tlsConfig * tls.Config , lAddr string , resultChannel chan asyncResult ) {
149- tcpListener , err := net .Listen ("tcp" , lAddr )
129+ func startListening (svr * http.Server , lAddr string , resultChannel chan asyncResult ) {
130+ listener , err := net .Listen ("tcp" , lAddr )
150131 if err != nil {
151132 resultChannel <- asyncResult {
152- name : "HTTPS server" ,
133+ name : "HTTP server" ,
153134 error : fmt .Errorf ("failed to listen to the network address %s reserved for cluster-version-operator metrics: %w" , lAddr , err ),
154135 }
155136 return
156137 }
157- tlsListener := tls .NewListener (tcpListener , tlsConfig )
158- klog .Infof ("Metrics port listening for HTTPS on %v" , lAddr )
159- err = svr .Serve (tlsListener )
160- resultChannel <- asyncResult {name : "HTTPS server" , error : err }
138+ klog .Infof ("Metrics port listening for HTTP on %v" , lAddr )
139+ err = svr .Serve (listener )
140+ resultChannel <- asyncResult {name : "HTTP server" , error : err }
161141}
162142
163143func handleServerResult (result asyncResult , lastLoopError error ) error {
@@ -174,61 +154,19 @@ func handleServerResult(result asyncResult, lastLoopError error) error {
174154}
175155
176156// RunMetrics launches a server bound to listenAddress serving
177- // Prometheus metrics at /metrics over HTTPS . Continues serving
157+ // Prometheus metrics at /metrics over HTTP . Continues serving
178158// until runContext.Done() and then attempts a clean shutdown
179159// limited by shutdownContext.Done(). Assumes runContext.Done()
180160// occurs before or simultaneously with shutdownContext.Done().
181- // Also detects changes to metrics certificate files upon which
182- // the metrics HTTP server is shutdown and recreated with a new
183- // TLS configuration.
184- func RunMetrics (runContext context.Context , shutdownContext context.Context , listenAddress , certFile , keyFile string ) error {
185- var tlsConfig * tls.Config
186- if listenAddress != "" {
187- var err error
188- tlsConfig , err = makeTLSConfig (certFile , keyFile )
189- if err != nil {
190- return fmt .Errorf ("Failed to create TLS config: %w" , err )
191- }
192- } else {
193- return errors .New ("TLS configuration is required to serve metrics" )
194- }
161+ func RunMetrics (runContext context.Context , shutdownContext context.Context , listenAddress string ) error {
195162 server := createHttpServer ()
196163
197164 resultChannel := make (chan asyncResult , 1 )
198165 resultChannelCount := 1
199166
200- go startListening (server , tlsConfig , listenAddress , resultChannel )
201-
202- certDir := filepath .Dir (certFile )
203- keyDir := filepath .Dir (keyFile )
204-
205- origCertChecksum , err := checksumFile (certFile )
206- if err != nil {
207- return fmt .Errorf ("Failed to initialize certificate file checksum: %w" , err )
208- }
209- origKeyChecksum , err := checksumFile (keyFile )
210- if err != nil {
211- return fmt .Errorf ("Failed to initialize key file checksum: %w" , err )
212- }
213-
214- // Set up and start the file watcher.
215- watcher , err := fsnotify .NewWatcher ()
216- if watcher == nil || err != nil {
217- return fmt .Errorf ("Failed to create file watcher for certificate and key rotation: %w" , err )
218- } else {
219- defer watcher .Close ()
220- if err := watcher .Add (certDir ); err != nil {
221- return fmt .Errorf ("Failed to add %v to watcher: %w" , certDir , err )
222- }
223- if certDir != keyDir {
224- if err := watcher .Add (keyDir ); err != nil {
225- return fmt .Errorf ("Failed to add %v to watcher: %w" , keyDir , err )
226- }
227- }
228- }
167+ go startListening (server , listenAddress , resultChannel )
229168
230169 shutdown := false
231- restartServer := false
232170 var loopError error
233171 for resultChannelCount > 0 {
234172 if shutdown {
@@ -244,53 +182,8 @@ func RunMetrics(runContext context.Context, shutdownContext context.Context, lis
244182 select {
245183 case <- runContext .Done (): // clean shutdown
246184 case result := <- resultChannel : // crashed before a shutdown was requested or metrics server recreated
247- if restartServer {
248- klog .Info ("Creating metrics server with updated TLS configuration." )
249- server = createHttpServer ()
250- go startListening (server , tlsConfig , listenAddress , resultChannel )
251- restartServer = false
252- continue
253- }
254185 resultChannelCount --
255186 loopError = handleServerResult (result , loopError )
256- case event := <- watcher .Events :
257- if event .Op != fsnotify .Chmod && event .Op != fsnotify .Remove {
258- if changed , err := certsChanged (origCertChecksum , origKeyChecksum , certFile , keyFile ); changed {
259-
260- // Update file checksums with latest files.
261- //
262- if origCertChecksum , err = checksumFile (certFile ); err != nil {
263- klog .Errorf ("Failed to update certificate file checksum: %v" , err )
264- loopError = err
265- break
266- }
267- if origKeyChecksum , err = checksumFile (keyFile ); err != nil {
268- klog .Errorf ("Failed to update key file checksum: %v" , err )
269- loopError = err
270- break
271- }
272-
273- tlsConfig , err = makeTLSConfig (certFile , keyFile )
274- if err == nil {
275- restartServer = true
276- shutdownHttpServer (shutdownContext , server )
277- continue
278- } else {
279- klog .Errorf ("Failed to create TLS configuration with updated configuration: %v" , err )
280- loopError = err
281- }
282- } else if err != nil {
283- klog .Errorf ("%v" , err )
284- loopError = err
285- } else {
286- continue
287- }
288- } else {
289- continue
290- }
291- case err = <- watcher .Errors :
292- klog .Errorf ("Error from metrics server certificate file watcher: %v" , err )
293- loopError = err
294187 }
295188 shutdown = true
296189 shutdownError := server .Shutdown (shutdownContext )
@@ -637,92 +530,3 @@ func mostRecentTimestamp(cv *configv1.ClusterVersion) int64 {
637530 }
638531 return latest .Unix ()
639532}
640-
641- // Determine if the certificates have changed and need to be updated.
642- // If no errors occur, returns true if both files have changed and
643- // neither is an empty file. Otherwise returns false and any error.
644- func certsChanged (origCertChecksum []byte , origKeyChecksum []byte , certFile , keyFile string ) (bool , error ) {
645- // Check if both files exist.
646- certNotEmpty , err := fileExistsAndNotEmpty (certFile )
647- if err != nil {
648- return false , fmt .Errorf ("Error checking if changed TLS cert file empty/exists: %w" , err )
649- }
650- keyNotEmpty , err := fileExistsAndNotEmpty (keyFile )
651- if err != nil {
652- return false , fmt .Errorf ("Error checking if changed TLS key file empty/exists: %w" , err )
653- }
654- if ! certNotEmpty || ! keyNotEmpty {
655- // One of the files is missing despite some file event.
656- return false , fmt .Errorf ("Certificate or key is missing or empty, certificates will not be rotated." )
657- }
658-
659- currentCertChecksum , err := checksumFile (certFile )
660- if err != nil {
661- return false , fmt .Errorf ("Error checking certificate file checksum: %w" , err )
662- }
663-
664- currentKeyChecksum , err := checksumFile (keyFile )
665- if err != nil {
666- return false , fmt .Errorf ("Error checking key file checksum: %w" , err )
667- }
668-
669- // Check if the non-empty certificate/key files have actually changed.
670- if ! bytes .Equal (origCertChecksum , currentCertChecksum ) && ! bytes .Equal (origKeyChecksum , currentKeyChecksum ) {
671- klog .V (2 ).Info ("Certificate and key changed. Will recreate metrics server with updated TLS configuration." )
672- return true , nil
673- }
674-
675- return false , nil
676- }
677-
678- func makeTLSConfig (servingCertFile , servingKeyFile string ) (* tls.Config , error ) {
679- // Load the initial certificate contents.
680- certBytes , err := os .ReadFile (servingCertFile )
681- if err != nil {
682- return nil , err
683- }
684- keyBytes , err := os .ReadFile (servingKeyFile )
685- if err != nil {
686- return nil , err
687- }
688- certificate , err := tls .X509KeyPair (certBytes , keyBytes )
689- if err != nil {
690- return nil , err
691- }
692-
693- return crypto .SecureTLSConfig (& tls.Config {
694- GetCertificate : func (_ * tls.ClientHelloInfo ) (* tls.Certificate , error ) {
695- return & certificate , nil
696- },
697- }), nil
698- }
699-
700- // Compute the sha256 checksum for file 'fName' returning any error.
701- func checksumFile (fName string ) ([]byte , error ) {
702- file , err := os .Open (fName )
703- if err != nil {
704- return nil , fmt .Errorf ("Failed to open file %v for checksum: %w" , fName , err )
705- }
706- defer file .Close ()
707-
708- hash := sha256 .New ()
709-
710- if _ , err = io .Copy (hash , file ); err != nil {
711- return nil , fmt .Errorf ("Failed to compute checksum for file %v: %w" , fName , err )
712- }
713-
714- return hash .Sum (nil ), nil
715- }
716-
717- // Check if a file exists and has file.Size() not equal to 0.
718- // Returns any error returned by os.Stat other than os.ErrNotExist.
719- func fileExistsAndNotEmpty (fName string ) (bool , error ) {
720- if fi , err := os .Stat (fName ); err == nil {
721- return (fi .Size () != 0 ), nil
722- } else if errors .Is (err , os .ErrNotExist ) {
723- return false , nil
724- } else {
725- // Some other error, file may not exist.
726- return false , err
727- }
728- }
0 commit comments