| // Package component implements reusable bits for cloud service components. Each | 
 | // component is currently defined as being a standalone Go binary with its own | 
 | // internal gRPC listener. Subsequent listeners (eg. public gRPC or HTTP) can be | 
 | // defined by users of this library. | 
 | package component | 
 |  | 
 | import ( | 
 | 	"context" | 
 | 	"crypto/tls" | 
 | 	"crypto/x509" | 
 | 	"flag" | 
 | 	"net" | 
 | 	"net/http" | 
 | 	"os" | 
 | 	"path/filepath" | 
 |  | 
 | 	"github.com/adrg/xdg" | 
 | 	"github.com/prometheus/client_golang/prometheus" | 
 | 	"github.com/prometheus/client_golang/prometheus/collectors" | 
 | 	"github.com/prometheus/client_golang/prometheus/promhttp" | 
 | 	"google.golang.org/grpc" | 
 | 	"google.golang.org/grpc/credentials" | 
 | 	"k8s.io/klog/v2" | 
 | ) | 
 |  | 
 | // ComponentConfig is the common configuration of a component. It's | 
 | // supposed to be instantiated within a Configuration struct of a component. | 
 | // | 
 | // It can be configured by flags (via RegisterFlags) or manually (eg. in tests). | 
 | type ComponentConfig struct { | 
 | 	// GRPCKeyPath is the filesystem path of the x509 key used to serve internal | 
 | 	// gRPC traffic. | 
 | 	GRPCKeyPath string | 
 | 	// GRPCCertificatePath is the filesystem path of the x509 certificate used to | 
 | 	// serve internal gRPC traffic. | 
 | 	GRPCCertificatePath string | 
 | 	// GRPCCAPath is the filesystem path of of the x509 CA certificate used to | 
 | 	// verify incoming connections on internal gRPC traffic. | 
 | 	GRPCCAPath string | 
 | 	// GRPCListenAddress is the address on which the component should server | 
 | 	// internal gRPC traffic. | 
 | 	GRPCListenAddress string | 
 |  | 
 | 	// DevCerts, if enabled, automatically generates development CA and component | 
 | 	// certificates/keys at DevCertsPath, uses these to serve traffic. | 
 | 	DevCerts bool | 
 | 	// DevCertsPath sets the prefix in which DevCerts are generated. All components | 
 | 	// should have the same path set so that they reuse the CA certificate. | 
 | 	DevCertsPath string | 
 |  | 
 | 	// ComponentName is the name of this component, which should be [a-z0-9+]. It's | 
 | 	// used to prefix all flags set by the Configuration. | 
 | 	ComponentName string | 
 |  | 
 | 	// PrometheusListenAddress is the address on which the component should serve | 
 | 	// Prometheus metrics. | 
 | 	PrometheusListenAddress string | 
 | 	// PrometheusInsecure enables serving Prometheus metrics without any TLS, running | 
 | 	// a plain HTTP listener. If disabled, Prometheus metrics are served using the | 
 | 	// same PKI setup as the components' gRPC server. | 
 | 	PrometheusInsecure bool | 
 |  | 
 | 	prometheusRegistry *prometheus.Registry | 
 | } | 
 |  | 
 | // RegisterFlags registers the component configuration to be provided by flags. | 
 | // This must be called exactly once before then calling flags.Parse(). | 
 | func (c *ComponentConfig) RegisterFlags(componentName string) { | 
 | 	flag.StringVar(&c.GRPCKeyPath, componentName+"_grpc_key_path", "", "Path to gRPC server/client key for "+componentName) | 
 | 	flag.StringVar(&c.GRPCCertificatePath, componentName+"_grpc_certificate_path", "", "Path to gRPC server/client certificate for "+componentName) | 
 | 	flag.StringVar(&c.GRPCCAPath, componentName+"_grpc_ca_certificate_path", "", "Path to gRPC CA certificate for "+componentName) | 
 | 	flag.StringVar(&c.GRPCListenAddress, componentName+"_grpc_listen_address", ":4242", "Address to listen at for gRPC connections for "+componentName) | 
 | 	flag.StringVar(&c.PrometheusListenAddress, componentName+"_prometheus_listen_address", ":4243", "Address to listen at for Prometheus connections for "+componentName) | 
 | 	flag.BoolVar(&c.PrometheusInsecure, componentName+"_prometheus_insecure", false, "Serve plain HTTP prometheus without mTLS. If not set, main gRPC TLS credentials/certificates are used") | 
 |  | 
 | 	flag.BoolVar(&c.DevCerts, componentName+"_dev_certs", false, "Use developer certificates (autogenerated) for "+componentName) | 
 | 	flag.StringVar(&c.DevCertsPath, componentName+"_dev_certs_path", filepath.Join(xdg.ConfigHome, "monogon-dev-certs"), "Path for storing developer certificates") | 
 |  | 
 | 	c.ComponentName = componentName | 
 | } | 
 |  | 
 | func (c *ComponentConfig) getTLSConfig() *tls.Config { | 
 | 	var certPath, keyPath, caPath string | 
 | 	if c.DevCerts { | 
 | 		// Use devcerts if requested. | 
 | 		certPath, keyPath, caPath = c.GetDevCerts() | 
 | 	} else { | 
 | 		// Otherwise, use data from flags. | 
 | 		if c.GRPCKeyPath == "" { | 
 | 			klog.Exitf("-grpc_key_path must be set") | 
 | 		} | 
 | 		if c.GRPCCertificatePath == "" { | 
 | 			klog.Exitf("-grpc_certificate_path must be set") | 
 | 		} | 
 | 		if c.GRPCCAPath == "" { | 
 | 			klog.Exitf("-grpc_ca_certificate_path must be set") | 
 | 		} | 
 | 		keyPath = c.GRPCKeyPath | 
 | 		certPath = c.GRPCCertificatePath | 
 | 		caPath = c.GRPCCAPath | 
 | 	} | 
 |  | 
 | 	ca, err := os.ReadFile(caPath) | 
 | 	if err != nil { | 
 | 		klog.Exitf("Could not read GRPC CA: %v", err) | 
 | 	} | 
 | 	certPool := x509.NewCertPool() | 
 | 	if !certPool.AppendCertsFromPEM(ca) { | 
 | 		klog.Exitf("Could not load GRPC CA: %v", err) | 
 | 	} | 
 |  | 
 | 	pair, err := tls.LoadX509KeyPair(certPath, keyPath) | 
 | 	if err != nil { | 
 | 		klog.Exitf("Could not load GRPC TLS keypair: %v", err) | 
 | 	} | 
 | 	return &tls.Config{ | 
 | 		Certificates: []tls.Certificate{pair}, | 
 | 		ClientAuth:   tls.RequireAndVerifyClientCert, | 
 | 		ClientCAs:    certPool, | 
 | 	} | 
 | } | 
 |  | 
 | // PrometheusRegistry returns this component's singleton Prometheus registry, | 
 | // creating it as needed. This method is not goroutine-safe, and should only be | 
 | // called during the setup process of the Component. | 
 | func (c *ComponentConfig) PrometheusRegistry() *prometheus.Registry { | 
 | 	if c.prometheusRegistry == nil { | 
 | 		c.prometheusRegistry = prometheus.NewRegistry() | 
 | 		c.prometheusRegistry.Register(collectors.NewGoCollector()) | 
 | 		c.prometheusRegistry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})) | 
 | 	} | 
 | 	return c.prometheusRegistry | 
 | } | 
 |  | 
 | // StartPrometheus starts a Prometheus metrics server in a goroutine. It will | 
 | // serve any metrics that have been registered with the registry returned by | 
 | // PrometheusRegistry. | 
 | func (c *ComponentConfig) StartPrometheus(ctx context.Context) { | 
 | 	reg := c.PrometheusRegistry() | 
 |  | 
 | 	mux := http.NewServeMux() | 
 | 	mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) | 
 |  | 
 | 	var lis net.Listener | 
 | 	var err error | 
 |  | 
 | 	if c.PrometheusInsecure { | 
 | 		lis, err = net.Listen("tcp", c.PrometheusListenAddress) | 
 | 	} else { | 
 | 		lis, err = tls.Listen("tcp", c.PrometheusListenAddress, c.getTLSConfig()) | 
 | 	} | 
 | 	if err != nil { | 
 | 		klog.Exitf("Could not listen on prometheus address: %v", err) | 
 | 	} | 
 |  | 
 | 	srv := http.Server{ | 
 | 		Handler: mux, | 
 | 	} | 
 | 	go func() { | 
 | 		klog.Infof("Prometheus listening on %s", lis.Addr()) | 
 | 		if err := srv.Serve(lis); err != nil && ctx.Err() == nil { | 
 | 			klog.Exitf("Prometheus serve failed: %v", err) | 
 | 		} | 
 | 	}() | 
 | 	go func() { | 
 | 		<-ctx.Done() | 
 | 		srv.Close() | 
 | 	}() | 
 | } | 
 |  | 
 | // GRPCServerOptions returns pre-built grpc.ServerOptions that this component | 
 | // should use to serve internal gRPC. | 
 | func (c *ComponentConfig) GRPCServerOptions() []grpc.ServerOption { | 
 | 	return []grpc.ServerOption{ | 
 | 		grpc.Creds(credentials.NewTLS(c.getTLSConfig())), | 
 | 	} | 
 | } | 
 |  | 
 | // GRPCServerOptionsPublic returns pre-built grpc.ServerOptions that this | 
 | // component should use to serve public gRPC. Any client will be allowed to | 
 | // connect, and it's up to the server implementation to authenticate incoming | 
 | // requests. | 
 | func (c *ComponentConfig) GRPCServerOptionsPublic() []grpc.ServerOption { | 
 | 	var certPath, keyPath string | 
 | 	if c.DevCerts { | 
 | 		// Use devcerts if requested. | 
 | 		certPath, keyPath, _ = c.GetDevCerts() | 
 | 	} else { | 
 | 		// Otherwise, use data from flags. | 
 | 		if c.GRPCKeyPath == "" { | 
 | 			klog.Exitf("-grpc_key_path must be set") | 
 | 		} | 
 | 		if c.GRPCCertificatePath == "" { | 
 | 			klog.Exitf("-grpc_certificate_path must be set") | 
 | 		} | 
 | 		keyPath = c.GRPCKeyPath | 
 | 		certPath = c.GRPCCertificatePath | 
 | 	} | 
 |  | 
 | 	pair, err := tls.LoadX509KeyPair(certPath, keyPath) | 
 | 	if err != nil { | 
 | 		klog.Exitf("Could not load GRPC TLS keypair: %v", err) | 
 | 	} | 
 | 	tlsConf := &tls.Config{ | 
 | 		Certificates: []tls.Certificate{pair}, | 
 | 		ClientAuth:   tls.RequestClientCert, | 
 | 	} | 
 | 	return []grpc.ServerOption{ | 
 | 		grpc.Creds(credentials.NewTLS(tlsConf)), | 
 | 	} | 
 | } |