smsgw: productionize, implement kube/mirko

This productionizes smsgw.

We also add some jsonnet machinery to provide a unified service for Go
micro/mirkoservices.

This machinery provides all the nice stuff:
 - a deployment
 - a service for all your types of pots
 - TLS certificates for HSPKI

We also update and test hspki for a new name scheme.

Change-Id: I292d00f858144903cbc8fe0c1c26eb1180d636bc
diff --git a/go/pki/BUILD.bazel b/go/pki/BUILD.bazel
index 0d3544f..5bc7522 100644
--- a/go/pki/BUILD.bazel
+++ b/go/pki/BUILD.bazel
@@ -1,4 +1,4 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
@@ -15,3 +15,10 @@
         "@org_golang_x_net//trace:go_default_library",
     ],
 )
+
+go_test(
+    name = "go_default_test",
+    srcs = ["grpc_test.go"],
+    embed = [":go_default_library"],
+    deps = ["@com_github_go_test_deep//:go_default_library"],
+)
diff --git a/go/pki/README.md b/go/pki/README.md
index b84c32d..f44a970 100644
--- a/go/pki/README.md
+++ b/go/pki/README.md
@@ -24,36 +24,49 @@
 
 All certs for mutual auth have the following CN/SAN format:
 
-    <job>.<principal>.<realm>
+    <job>.<principal>.svc.<cluster-short>.<realm>
+    or
+    <principal>.person.<realm>
+    or
+    <principal>.external.<realm>
 
-For example, if principal maps into a 'group' and job into a 'user':
+Where in adition we define `<cluster>` as being `<realm>` plus its next left-side member.
 
-    arista-proxy-dcr01u23.cluster-management-prod.c.example.com
+For example, for kubernetes jobs:
 
-    job = arista-proxy-dcr01u23
-    principal = cluster-management-prod
-    realm = c.example.com
+    foo.bar.svc.k0.hswaw.net
+
+    job = foo
+    principal = bar.svc
+    cluster = k0.hswaw.net
+    realm = hswaw.net
+
+Where foo is the name of a kubernets service, bar is the name of the namespace its in, and
+k0.hswaw.net is the cluster running them.
+
+For people and external services:
+
+    q3k.person.hswaw.net
+
+    job =
+    principal = q3k
+    cluster = person.hswaw.net
+    realm = hswaw.net
 
 The Realm is a DNS name that is global to all jobs that need mutual authentication.
 
-The Principal is any name that carries significance for logical grouping of jobs.
-It can, but doesn't need to, group jobs by similar permissions.
+The Principal is any name that carries significance for an authentication principal,
+ie. a unit that gives information about an identity of an element. In case of kubernetes
+it's a namespace (as we split authentication/authorization into namespaces). In the case of external
+services and people it's the name of the service or person.
 
-The Job is any name that identifies uniquely (within the principal) a security
-endpoint that describes a single security policy for a gRPC endpoint.
+The Job is a name that makes the Principal more specific, if possible. If set, the Principal
+can be treated as a group of Jobs.
 
 The entire CN should be DNS resolvable into an IP address that would respond to
 gRPC requests on port 42000 (with a server TLS certificate that represents this CN) if the
 job represents a service.
 
-This maps nicely to the Kubernetes Cluster DNS format if you set `realm` to `svc.cluster.local`.
-Then, `principal` maps to a Kubernetes namespace, and `job` maps into a Kubernetes service.
-
-    arista-proxy-dcr01u23.infrastructure-prod.svc.cluster.local
-
-    job/service = arista-proxy-dcr01u23
-    principal/namespace = infrastructure-prod
-    realm = svc.cluster.local
 
 ACL, or How do I restrict access to my service?
 -----------------------------------------------
@@ -84,8 +97,10 @@
 
 Once linked into your program, the following flags will be automatically present:
 
-    -hspki_realm string
+    -hspki_cluster string
         PKI realm (default "svc.cluster.local")
+    -hspki_realm string
+        PKI realm (default "cluster.local")
     -hspki_tls_ca_path string
         Path to PKI CA certificate (default "pki/ca.pem")
     -hspki_tls_certificate_path string
diff --git a/go/pki/grpc.go b/go/pki/grpc.go
index 6d8f173..1720ad8 100644
--- a/go/pki/grpc.go
+++ b/go/pki/grpc.go
@@ -36,6 +36,7 @@
 	flagCAPath          string
 	flagCertificatePath string
 	flagKeyPath         string
+	flagPKICluster      string
 	flagPKIRealm        string
 	flagPKIDisable      bool
 
@@ -53,7 +54,8 @@
 	flag.StringVar(&flagCAPath, "hspki_tls_ca_path", "pki/ca.pem", "Path to PKI CA certificate")
 	flag.StringVar(&flagCertificatePath, "hspki_tls_certificate_path", "pki/service.pem", "Path to PKI service certificate")
 	flag.StringVar(&flagKeyPath, "hspki_tls_key_path", "pki/service-key.pem", "Path to PKI service private key")
-	flag.StringVar(&flagPKIRealm, "hspki_realm", "svc.cluster.local", "PKI realm")
+	flag.StringVar(&flagPKICluster, "hspki_cluster", "local.hswaw.net", "FQDN of cluster on which this service runs")
+	flag.StringVar(&flagPKIRealm, "hspki_realm", "hswaw.net", "Cluster realm (top level from which we accept foreign cluster certs)")
 	flag.BoolVar(&flagPKIDisable, "hspki_disable", false, "Disable PKI entirely (insecure!)")
 }
 
@@ -81,14 +83,39 @@
 	if !strings.HasSuffix(name, "."+flagPKIRealm) {
 		return nil, fmt.Errorf("invalid realm")
 	}
-	service := strings.TrimSuffix(name, "."+flagPKIRealm)
-	parts := strings.Split(service, ".")
-	if len(parts) != 2 {
-		return nil, fmt.Errorf("invalid job/principal format")
+
+	inRealm := strings.TrimSuffix(name, "."+flagPKIRealm)
+
+	special := []string{"person", "external"}
+
+	for _, s := range special {
+		// Special case for people running jobs from workstations, or for non-cluster services.
+		if strings.HasSuffix(inRealm, "."+s) {
+			asPerson := strings.TrimSuffix(inRealm, "."+s)
+			parts := strings.Split(asPerson, ".")
+			if len(parts) != 1 {
+				return nil, fmt.Errorf("invalid person fqdn")
+			}
+			return &ClientInfo{
+				Cluster:   fmt.Sprintf("%s.%s", s, flagPKIRealm),
+				Principal: parts[0],
+				Job:       "",
+			}, nil
+		}
 	}
+
+	parts := strings.Split(inRealm, ".")
+	if len(parts) != 4 {
+		return nil, fmt.Errorf("invalid job/principal format for in-cluster")
+	}
+	if parts[2] != "svc" {
+		return nil, fmt.Errorf("can only refer to services within cluster")
+	}
+	clusterShort := parts[3]
+
 	return &ClientInfo{
-		Realm:     flagPKIRealm,
-		Principal: parts[1],
+		Cluster:   fmt.Sprintf("%s.%s", clusterShort, flagPKIRealm),
+		Principal: fmt.Sprintf("%s.svc", parts[1]),
 		Job:       parts[0],
 	}, nil
 }
@@ -137,15 +164,24 @@
 // ClientInfo contains information about the HSPKI authentication data of the
 // gRPC client that has made the request.
 type ClientInfo struct {
-	Realm     string
+	Cluster   string
 	Principal string
 	Job       string
 }
 
 // String returns a human-readable representation of the ClientInfo in the
-// form "job=foo, principal=bar, realm=baz".
+// form "job=foo, principal=bar.svc, cluster=baz.hswaw.net".
 func (c *ClientInfo) String() string {
-	return fmt.Sprintf("job=%q, principal=%q, realm=%q", c.Job, c.Principal, c.Realm)
+	return fmt.Sprintf("job=%q, principal=%q, cluster=%q", c.Job, c.Principal, c.Cluster)
+}
+
+// Person returns a reference to a person's ID if the ClientInfo describes a person.
+// Otherwise, it returns an empty string.
+func (c *ClientInfo) Person() string {
+	if c.Cluster != fmt.Sprintf("person.%s", flagPKIRealm) {
+		return ""
+	}
+	return c.Principal
 }
 
 // ClientInfoFromContext returns ClientInfo from a gRPC service context.
diff --git a/go/pki/grpc_test.go b/go/pki/grpc_test.go
new file mode 100644
index 0000000..9ce2a8c
--- /dev/null
+++ b/go/pki/grpc_test.go
@@ -0,0 +1,69 @@
+package pki
+
+import (
+	"testing"
+
+	"github.com/go-test/deep"
+)
+
+func TestParseClient(t *testing.T) {
+	flagPKIRealm = "hswaw.net"
+
+	tests := []struct {
+		name string
+		want *ClientInfo
+	}{
+		// Local cluster
+		{"foo.bar.svc.k0.hswaw.net", &ClientInfo{Cluster: "k0.hswaw.net", Principal: "bar.svc", Job: "foo"}},
+		{"foo.bar.k0.hswaw.net", nil},
+
+		// Foreign cluster
+		{"foo.bar.svc.k1.hswaw.net", &ClientInfo{Cluster: "k1.hswaw.net", Principal: "bar.svc", Job: "foo"}},
+		{"foo.bar.k1.hswaw.net", nil},
+
+		// Human admins (admins, as we know, don't have a real job)
+		{"q3k.person.hswaw.net", &ClientInfo{Cluster: "person.hswaw.net", Principal: "q3k", Job: ""}},
+
+		// External services
+		{"kasownik.external.hswaw.net", &ClientInfo{Cluster: "external.hswaw.net", Principal: "kasownik", Job: ""}},
+
+		// Broken.
+		{"foo.hswaw.net", nil},
+		{"ldap.hackerspace.pl", nil},
+		{"", nil},
+		{"..what..plz...don.t.hack.me.hswaw.net", nil},
+	}
+
+	for i, te := range tests {
+		res, err := parseClientName(te.name)
+		if err != nil {
+			if te.want != nil {
+				t.Errorf("#%d: wanted result, got err %v", i, err)
+			}
+			continue
+		}
+
+		if te.want == nil {
+			t.Errorf("#%d: wanted err, got %+v", i, res)
+			continue
+		}
+
+		if diff := deep.Equal(*te.want, *res); diff != nil {
+			t.Errorf("#%d: res diff: %v", i, diff)
+			continue
+		}
+	}
+}
+
+func TestCheckPerson(t *testing.T) {
+	flagPKIRealm = "hswaw.net"
+
+	res, err := parseClientName("q3k.person.hswaw.net")
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	if want, got := "q3k", res.Person(); want != got {
+		t.Fatalf("wanted %q, got %q", want, got)
+	}
+}