|  | // 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)), | 
|  | } | 
|  | } |