Serge Bazanski | bee272f | 2022-09-13 13:52:42 +0200 | [diff] [blame^] | 1 | // Package component implements reusable bits for cloud service components. Each |
| 2 | // component is currently defined as being a standalone Go binary with its own |
| 3 | // internal gRPC listener. Subsequent listeners (eg. public gRPC or HTTP) can be |
| 4 | // defined by users of this library. |
| 5 | package component |
| 6 | |
| 7 | import ( |
| 8 | "crypto/tls" |
| 9 | "crypto/x509" |
| 10 | "flag" |
| 11 | "os" |
| 12 | "path/filepath" |
| 13 | |
| 14 | "github.com/adrg/xdg" |
| 15 | "google.golang.org/grpc" |
| 16 | "google.golang.org/grpc/credentials" |
| 17 | "k8s.io/klog/v2" |
| 18 | ) |
| 19 | |
| 20 | // Configuration is the common configuration of a component. |
| 21 | type Configuration struct { |
| 22 | // GRPCKeyPath is the filesystem path of the x509 key used to serve internal |
| 23 | // gRPC traffic. |
| 24 | GRPCKeyPath string |
| 25 | // GRPCCertificatePath is the filesystem path of the x509 certificate used to |
| 26 | // serve internal gRPC traffic. |
| 27 | GRPCCertificatePath string |
| 28 | // GRPCCAPath is the filesystem path of of the x509 CA certificate used to |
| 29 | // verify incoming connections on internal gRPC traffic. |
| 30 | GRPCCAPath string |
| 31 | // GRPCListenAddress is the address on which the component should server |
| 32 | // internal gRPC traffic. |
| 33 | GRPCListenAddress string |
| 34 | |
| 35 | // DevCerts, if enabled, automatically generates development CA and component |
| 36 | // certificates/keys at DevCertsPath, uses these to serve traffic. |
| 37 | DevCerts bool |
| 38 | // DevCertsPath sets the prefix in which DevCerts are generated. All components |
| 39 | // should have the same path set so that they reuse the CA certificate. |
| 40 | DevCertsPath string |
| 41 | |
| 42 | // ComponentName is the name of this component, which should be [a-z0-9+]. It's |
| 43 | // used to prefix all flags set by the Configuration. |
| 44 | ComponentName string |
| 45 | } |
| 46 | |
| 47 | // RegisterFlags registers the component configuration to be provided by flags. |
| 48 | // This must be called exactly once before then calling flags.Parse(). |
| 49 | func (c *Configuration) RegisterFlags(componentName string) { |
| 50 | flag.StringVar(&c.GRPCKeyPath, componentName+"_grpc_key_path", "", "Path to gRPC server/client key for "+componentName) |
| 51 | flag.StringVar(&c.GRPCCertificatePath, componentName+"_grpc_certificate_path", "", "Path to gRPC server/client certificate for "+componentName) |
| 52 | flag.StringVar(&c.GRPCCAPath, componentName+"_grpc_ca_certificate_path", "", "Path to gRPC CA certificate for "+componentName) |
| 53 | flag.StringVar(&c.GRPCListenAddress, componentName+"_grpc_listen_address", ":4242", "Address to listen at for gRPC connections for "+componentName) |
| 54 | |
| 55 | flag.BoolVar(&c.DevCerts, componentName+"_dev_certs", false, "Use developer certificates (autogenerated) for "+componentName) |
| 56 | flag.StringVar(&c.DevCertsPath, componentName+"_dev_certs_path", filepath.Join(xdg.ConfigHome, "monogon-dev-certs"), "Path for storing developer certificates") |
| 57 | |
| 58 | c.ComponentName = componentName |
| 59 | } |
| 60 | |
| 61 | // GRPCServerOptions returns pre-built grpc.ServerOptions that this component |
| 62 | // should use to serve internal gRPC. |
| 63 | func (c *Configuration) GRPCServerOptions() []grpc.ServerOption { |
| 64 | var certPath, keyPath, caPath string |
| 65 | if c.DevCerts { |
| 66 | // Use devcerts if requested. |
| 67 | certPath, keyPath, caPath = c.GetDevCerts() |
| 68 | } else { |
| 69 | // Otherwise, use data from flags. |
| 70 | if c.GRPCKeyPath == "" { |
| 71 | klog.Exitf("-grpc_key_path must be set") |
| 72 | } |
| 73 | if c.GRPCCertificatePath == "" { |
| 74 | klog.Exitf("-grpc_certificate_path must be set") |
| 75 | } |
| 76 | if c.GRPCCAPath == "" { |
| 77 | klog.Exitf("-grpc_ca_certificate_path must be set") |
| 78 | } |
| 79 | keyPath = c.GRPCKeyPath |
| 80 | certPath = c.GRPCCertificatePath |
| 81 | caPath = c.GRPCCAPath |
| 82 | } |
| 83 | |
| 84 | ca, err := os.ReadFile(caPath) |
| 85 | if err != nil { |
| 86 | klog.Exitf("Could not read GRPC CA: %v", err) |
| 87 | } |
| 88 | certPool := x509.NewCertPool() |
| 89 | if !certPool.AppendCertsFromPEM(ca) { |
| 90 | klog.Exitf("Could not load GRPC CA: %v", err) |
| 91 | } |
| 92 | |
| 93 | pair, err := tls.LoadX509KeyPair(certPath, keyPath) |
| 94 | if err != nil { |
| 95 | klog.Exitf("Could not load GRPC TLS keypair: %v", err) |
| 96 | } |
| 97 | tlsConf := &tls.Config{ |
| 98 | Certificates: []tls.Certificate{pair}, |
| 99 | ClientAuth: tls.RequireAndVerifyClientCert, |
| 100 | RootCAs: certPool, |
| 101 | } |
| 102 | return []grpc.ServerOption{ |
| 103 | grpc.Creds(credentials.NewTLS(tlsConf)), |
| 104 | } |
| 105 | } |