core -> metropolis

Smalltown is now called Metropolis!

This is the first commit in a series of cleanup commits that prepare us
for an open source release. This one just some Bazel packages around to
follow a stricter directory layout.

All of Metropolis now lives in `//metropolis`.

All of Metropolis Node code now lives in `//metropolis/node`.

All of the main /init now lives in `//m/n/core`.

All of the Kubernetes functionality/glue now lives in `//m/n/kubernetes`.

Next steps:
     - hunt down all references to Smalltown and replace them appropriately
     - narrow down visibility rules
     - document new code organization
     - move `//build/toolchain` to `//monogon/build/toolchain`
     - do another cleanup pass between `//golibs` and
       `//monogon/node/{core,common}`.
     - remove `//delta` and `//anubis`

Fixes T799.

Test Plan: Just a very large refactor. CI should help us out here.

Bug: T799

X-Origin-Diff: phab/D667
GitOrigin-RevId: 6029b8d4edc42325d50042596b639e8b122d0ded
diff --git a/metropolis/node/kubernetes/apiserver.go b/metropolis/node/kubernetes/apiserver.go
new file mode 100644
index 0000000..583e268
--- /dev/null
+++ b/metropolis/node/kubernetes/apiserver.go
@@ -0,0 +1,137 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package kubernetes
+
+import (
+	"context"
+	"encoding/pem"
+	"fmt"
+	"io"
+	"net"
+	"os/exec"
+
+	common "git.monogon.dev/source/nexantic.git/metropolis/node"
+	"git.monogon.dev/source/nexantic.git/metropolis/node/common/fileargs"
+	"git.monogon.dev/source/nexantic.git/metropolis/node/common/supervisor"
+	"git.monogon.dev/source/nexantic.git/metropolis/node/core/localstorage"
+	"git.monogon.dev/source/nexantic.git/metropolis/node/kubernetes/pki"
+)
+
+type apiserverService struct {
+	KPKI                        *pki.KubernetesPKI
+	AdvertiseAddress            net.IP
+	ServiceIPRange              net.IPNet
+	Output                      io.Writer
+	EphemeralConsensusDirectory *localstorage.EphemeralConsensusDirectory
+
+	// All PKI-related things are in DER
+	idCA                  []byte
+	kubeletClientCert     []byte
+	kubeletClientKey      []byte
+	aggregationCA         []byte
+	aggregationClientCert []byte
+	aggregationClientKey  []byte
+	serviceAccountPrivKey []byte // In PKIX form
+	serverCert            []byte
+	serverKey             []byte
+}
+
+func (s *apiserverService) loadPKI(ctx context.Context) error {
+	for _, el := range []struct {
+		targetCert *[]byte
+		targetKey  *[]byte
+		name       pki.KubeCertificateName
+	}{
+		{&s.idCA, nil, pki.IdCA},
+		{&s.kubeletClientCert, &s.kubeletClientKey, pki.KubeletClient},
+		{&s.aggregationCA, nil, pki.AggregationCA},
+		{&s.aggregationClientCert, &s.aggregationClientKey, pki.FrontProxyClient},
+		{&s.serverCert, &s.serverKey, pki.APIServer},
+	} {
+		cert, key, err := s.KPKI.Certificate(ctx, el.name)
+		if err != nil {
+			return fmt.Errorf("could not load certificate %q from PKI: %w", el.name, err)
+		}
+		if el.targetCert != nil {
+			*el.targetCert = cert
+		}
+		if el.targetKey != nil {
+			*el.targetKey = key
+		}
+	}
+
+	var err error
+	s.serviceAccountPrivKey, err = s.KPKI.ServiceAccountKey(ctx)
+	if err != nil {
+		return fmt.Errorf("could not load serviceaccount privkey: %w", err)
+	}
+	return nil
+}
+
+func (s *apiserverService) Run(ctx context.Context) error {
+	if err := s.loadPKI(ctx); err != nil {
+		return fmt.Errorf("loading PKI data failed: %w", err)
+	}
+	args, err := fileargs.New()
+	if err != nil {
+		panic(err) // If this fails, something is very wrong. Just crash.
+	}
+	defer args.Close()
+
+	cmd := exec.CommandContext(ctx, "/kubernetes/bin/kube", "kube-apiserver",
+		fmt.Sprintf("--advertise-address=%v", s.AdvertiseAddress.String()),
+		"--authorization-mode=Node,RBAC",
+		args.FileOpt("--client-ca-file", "client-ca.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.idCA})),
+		"--enable-admission-plugins=NodeRestriction,PodSecurityPolicy",
+		"--enable-aggregator-routing=true",
+		"--insecure-port=0",
+		fmt.Sprintf("--secure-port=%v", common.KubernetesAPIPort),
+		fmt.Sprintf("--etcd-servers=unix:///%s:0", s.EphemeralConsensusDirectory.ClientSocket.FullPath()),
+		args.FileOpt("--kubelet-client-certificate", "kubelet-client-cert.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.kubeletClientCert})),
+		args.FileOpt("--kubelet-client-key", "kubelet-client-key.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.kubeletClientKey})),
+		"--kubelet-preferred-address-types=InternalIP",
+		args.FileOpt("--proxy-client-cert-file", "aggregation-client-cert.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.aggregationClientCert})),
+		args.FileOpt("--proxy-client-key-file", "aggregation-client-key.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.aggregationClientKey})),
+		"--requestheader-allowed-names=front-proxy-client",
+		args.FileOpt("--requestheader-client-ca-file", "aggregation-ca.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.aggregationCA})),
+		"--requestheader-extra-headers-prefix=X-Remote-Extra-",
+		"--requestheader-group-headers=X-Remote-Group",
+		"--requestheader-username-headers=X-Remote-User",
+		args.FileOpt("--service-account-key-file", "service-account-pubkey.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.serviceAccountPrivKey})),
+		fmt.Sprintf("--service-cluster-ip-range=%v", s.ServiceIPRange.String()),
+		args.FileOpt("--tls-cert-file", "server-cert.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.serverCert})),
+		args.FileOpt("--tls-private-key-file", "server-key.pem",
+			pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.serverKey})),
+	)
+	if args.Error() != nil {
+		return err
+	}
+	cmd.Stdout = s.Output
+	cmd.Stderr = s.Output
+	supervisor.Signal(ctx, supervisor.SignalHealthy)
+	err = cmd.Run()
+	fmt.Fprintf(s.Output, "apiserver stopped: %v\n", err)
+	return err
+}