cloud: init with apigw

This adds a first component to the cloud project, the apigw (API
Gateway), which listens on a public gRPC-Web socket.

It's not truly a gateway - it will actually contain most of the
IAM/Project logic for the cloud system. A better name should be picked
later.

We implement a minimum internal/public gRPC(-Web) listener and some
boilerplate for the parts that are gonna pop up again. Notably, we add
some automation around generating developer TLS certificates for the
internal gRPC listener.

Currently the apigw serves a single, demo RPC which returns
'unimplemented'.

Change-Id: I9164ddbd9a20172154ae5a3ffad676de5fe4927d
Reviewed-on: https://review.monogon.dev/c/monogon/+/906
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Jenkins CI
diff --git a/cloud/lib/component/component.go b/cloud/lib/component/component.go
new file mode 100644
index 0000000..4353bdf
--- /dev/null
+++ b/cloud/lib/component/component.go
@@ -0,0 +1,105 @@
+// 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"
+)
+
+// Configuration is the common configuration of a component.
+type Configuration 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 *Configuration) 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 *Configuration) 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)),
+	}
+}