blob: dee4703d7eef3ff65492fd78af37b6d35f8b7029 [file] [log] [blame]
// 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 (
"crypto/tls"
"crypto/x509"
"flag"
"os"
"path/filepath"
"github.com/adrg/xdg"
"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
}
// 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.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
}
// GRPCServerOptions returns pre-built grpc.ServerOptions that this component
// should use to serve internal gRPC.
func (c *ComponentConfig) GRPCServerOptions() []grpc.ServerOption {
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)
}
tlsConf := &tls.Config{
Certificates: []tls.Certificate{pair},
ClientAuth: tls.RequireAndVerifyClientCert,
RootCAs: certPool,
}
return []grpc.ServerOption{
grpc.Creds(credentials.NewTLS(tlsConf)),
}
}
// 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)),
}
}