cluster/kube: deploy admitomatic

This doesn't yet enable a webhook, but deploys admitomatic itself.

Change-Id: Id177bc8841c873031f9c196b8ff3c12dd846ba8e
diff --git a/cluster/kube/k0.libsonnet b/cluster/kube/k0.libsonnet
index f393dbe..b5feb05 100644
--- a/cluster/kube/k0.libsonnet
+++ b/cluster/kube/k0.libsonnet
@@ -7,6 +7,7 @@
 
 local cluster = import "cluster.libsonnet";
 
+local admitomatic = import "lib/admitomatic.libsonnet";
 local cockroachdb = import "lib/cockroachdb.libsonnet";
 local registry = import "lib/registry.libsonnet";
 local rook = import "lib/rook.libsonnet";
@@ -308,5 +309,57 @@
             # TODO(implr): restricted policy with CAP_NET_ADMIN and tuntap, but no full root
             policies.AllowNamespaceInsecure("implr-vpn"),
         ],
+
+        # Admission controller that permits non-privileged users to manage
+        # their namespaces without danger of hijacking important URLs.
+        admitomatic: admitomatic.Environment {
+            cfg+: {
+                proto: {
+                    // Domains allowed in given namespaces. If a domain exists
+                    // anywhere, ingresses will only be permitted to be created
+                    // within namespaces in which it appears here. This works
+                    // the same way for wildcards, if a wildcard exists in this
+                    // list it blocks all unauthorized uses of that domain
+                    // elsewhere.
+                    //
+                    // See //cluster/admitomatic for more information.
+                    //
+                    // Or, tl;dr:
+                    //
+                    // If you do a wildcard CNAME onto the k0 ingress, you
+                    // should explicitly state *.your.name.com here.
+                    //
+                    // If you just want to protect your host from being
+                    // hijacked by other cluster users, you should also state
+                    // it here (either as a wildcard, or unary domains).
+                    allow_domain: [
+                        { namespace: "covid-formity", dns: "covid19.hackerspace.pl" },
+                        { namespace: "covid-formity", dns: "covid.hackerspace.pl" },
+                        { namespace: "covid-formity", dns: "www.covid.hackerspace.pl" },
+                        { namespace: "devtools-prod", dns: "hackdoc.hackerspace.pl" },
+                        { namespace: "devtools-prod", dns: "cs.hackerspace.pl" },
+                        { namespace: "engelsystem-prod", dns: "engelsystem.hackerspace.pl" },
+                        { namespace: "gerrit", dns: "gerrit.hackerspace.pl" },
+                        { namespace: "gitea-prod", dns: "gitea.hackerspace.pl" },
+                        { namespace: "hswaw-prod", dns: "*.hackerspace.pl" },
+                        { namespace: "internet", dns: "internet.hackerspace.pl" },
+                        { namespace: "matrix", dns: "matrix.hackerspace.pl" },
+                        { namespace: "onlyoffice-prod", dns: "office.hackerspace.pl" },
+                        { namespace: "redmine", dns: "issues.hackerspace.pl" },
+                        { namespace: "speedtest", dns: "speedtest.hackerspace.pl" },
+                        { namespace: "sso", dns: "sso.hackerspace.pl" },
+
+                        { namespace: "ceph-waw3", dns: "ceph-waw3.hswaw.net" },
+                        { namespace: "ceph-waw3", dns: "object.ceph-waw3.hswaw.net" },
+                        { namespace: "monitoring-global-k0", dns: "*.hswaw.net" },
+                        { namespace: "registry", dns: "*.hswaw.net" },
+
+                        // q3k's legacy namespace (pre-prodvider)
+                        { namespace: "q3k", dns: "*.q3k.org" },
+                        { namespace: "personal-q3k", dns: "*.q3k.org" },
+                    ],
+                },
+            },
+        },
     },
 }
diff --git a/cluster/kube/lib/admitomatic.libsonnet b/cluster/kube/lib/admitomatic.libsonnet
new file mode 100644
index 0000000..36ea5ef
--- /dev/null
+++ b/cluster/kube/lib/admitomatic.libsonnet
@@ -0,0 +1,94 @@
+// Deploys admitomatic, a validating admission webhook. It is used in
+// conjunction with Kubernetes' RBAC to provide a level of multitenancy to the
+// cluster, adding extra restrictions to resources created by non-administrative
+// users.
+//
+// For more information about admitomatic , see //cluster/admitomatic .
+//
+// As with every Kubernetes admission webhook, the Kubernetes control plane
+// (ie. apiserver) needs to be able to dial the deployed admitomatic service.
+// The authentication story for this is unfortunately quite sad and requires
+// the use of a pre-generated one-shot CA and certificate.
+//
+//         .---- self-signed -.
+//         v                  |
+//   Admitomatic CA ----------'  <-- caBundle used by apiserver,
+//         |                         set in ValidatingWebhookConfiguration
+//         v
+//   Admitomatic Cert            <-- admitomatic_tls_cert used by admitomatic
+//
+// This CA needs to be provisioned ahead of time by ourselves. In order to keep
+// things simple (as admitomatic being an admission webhook becomes a core
+// component of the k8s control plane), we generate this CA as plain text
+// secrets, and store them with secretstore in git. This is done via clustercfg.
+
+local kube = import "../../../kube/kube.libsonnet";
+local prototext = import "../../../kube/prototext.libsonnet";
+
+{
+    Environment: {
+        local env = self,
+        local cfg = env.cfg,
+
+        cfg:: {
+            namespace: "admitomatic",
+            image: "registry.k0.hswaw.net/q3k/admitomatic:1612618063-0b68e233116f733fb3ec9016c9d3b7decb86f192",
+
+            proto: {},
+        },
+
+        namespace: kube.Namespace(cfg.namespace),
+        local ns = self.namespace,
+
+        config: ns.Contain(kube.ConfigMap("admitomatic")) {
+            data: {
+                "config.pb.text": prototext.manifestProtoText(cfg.proto),
+            },
+        },
+
+        secret: ns.Contain(kube.Secret("admitomatic")) {
+            data_: {
+                "webhook.key": importstr "../../secrets/plain/admitomatic-webhook.key",
+                "webhook.crt": importstr "../../certs/admitomatic-webhook.cert",
+            },
+        },
+
+        daemonset: ns.Contain(kube.DaemonSet("admitomatic")) {
+            spec+: {
+                template+: {
+                    spec+: {
+                        containers_: {
+                            default: kube.Container("default") {
+                                image: cfg.image,
+                                args: [
+                                    "/cluster/admitomatic/admitomatic",
+                                    "-admitomatic_config", "/admitomatic/config/config.pb.text",
+                                    "-admitomatic_listen", "0.0.0.0:8443",
+                                    "-admitomatic_tls_cert", "/admitomatic/secret/webhook.crt",
+                                    "-admitomatic_tls_key", "/admitomatic/secret/webhook.key",
+                                    // doesn't serve anything over gRPC.
+                                    "-hspki_disable"
+                                ],
+                                volumeMounts_: {
+                                    config: { mountPath: "/admitomatic/config" },
+                                    secret: { mountPath: "/admitomatic/secret" },
+                                },
+                                ports_: {
+                                    public: { containerPort: 8443 },
+                                },
+                            },
+                        },
+                        volumes_: {
+                            config: kube.ConfigMapVolume(env.config),
+                            secret: kube.SecretVolume(env.secret),
+                        },
+                    },
+                },
+            },
+        },
+
+        svc: ns.Contain(kube.Service("admitomatic")) {
+            target_pod:: env.daemonset.spec.template,
+        },
+    },
+}