Add Kubernetes DNS with CoreDNS

This adds Kubernetes DNS with a CoreDNS instance running on the host. This has some distinct advantages over
running it inside a container, like a simplified lifecycle (no state reconciliation) and the possibility of redirecting
all host DNS requests over this instance for observability or central DNSSEC enforcement.

Test Plan: Manually tested (`host kubernetes` in an Alpine container), will be covered by CTS.

X-Origin-Diff: phab/D616
GitOrigin-RevId: 281f5f384f4ef7eba2c3c3190be8e6a89772295c
diff --git a/core/internal/kubernetes/dns/BUILD.bazel b/core/internal/kubernetes/dns/BUILD.bazel
new file mode 100644
index 0000000..173360d
--- /dev/null
+++ b/core/internal/kubernetes/dns/BUILD.bazel
@@ -0,0 +1,12 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["coredns.go"],
+    importpath = "git.monogon.dev/source/nexantic.git/core/internal/kubernetes/dns",
+    visibility = ["//core:__subpackages__"],
+    deps = [
+        "//core/internal/common/supervisor:go_default_library",
+        "//core/pkg/fileargs:go_default_library",
+    ],
+)
diff --git a/core/internal/kubernetes/dns/coredns.go b/core/internal/kubernetes/dns/coredns.go
new file mode 100644
index 0000000..d020ec5
--- /dev/null
+++ b/core/internal/kubernetes/dns/coredns.go
@@ -0,0 +1,91 @@
+// 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 DNS provides a Kubernetes DNS server using CoreDNS.
+package dns
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io"
+	"os/exec"
+	"text/template"
+
+	"git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
+	"git.monogon.dev/source/nexantic.git/core/pkg/fileargs"
+)
+
+type corefileSpec struct {
+	KubeconfigPath string
+	ClusterDomain  string
+}
+
+var corefileTemplate = template.Must(template.New("corefile").Parse(`
+.:53 {
+    errors
+    health {
+        lameduck 5s
+    }
+    kubernetes {{.ClusterDomain}} in-addr.arpa ip6.arpa {
+		kubeconfig {{.KubeconfigPath}} default
+        pods insecure
+        fallthrough in-addr.arpa ip6.arpa
+        ttl 30
+    }
+    forward . /etc/resolv.conf
+    cache 30
+    loadbalance
+}
+`))
+
+type Service struct {
+	Output        io.Writer
+	Kubeconfig    []byte
+	ClusterDomain string
+}
+
+func (s *Service) Run(ctx context.Context) error {
+	args, err := fileargs.New()
+	if err != nil {
+		return fmt.Errorf("failed to create fileargs: %w", err)
+	}
+	defer args.Close()
+
+	var corefile bytes.Buffer
+	if err := corefileTemplate.Execute(&corefile, &corefileSpec{
+		KubeconfigPath: args.ArgPath("kubeconfig", s.Kubeconfig),
+		ClusterDomain:  s.ClusterDomain,
+	}); err != nil {
+		return fmt.Errorf("failed to execute Corefile template: %w", err)
+	}
+
+	cmd := exec.CommandContext(ctx, "/kubernetes/bin/coredns",
+		args.FileOpt("-conf", "Corefile", corefile.Bytes()),
+	)
+
+	if args.Error() != nil {
+		return fmt.Errorf("failed to use fileargs: %w", err)
+	}
+
+	cmd.Stdout = s.Output
+	cmd.Stderr = s.Output
+
+	supervisor.Signal(ctx, supervisor.SignalHealthy)
+	err = cmd.Run()
+	fmt.Fprintf(s.Output, "coredns stopped: %v\n", err)
+	return err
+}