*: developer machine HSPKI credentials
In addition to k8s certificates, prodaccess now issues HSPKI
certificates, with DN=$username.sso.hswaw.net. These are installed into
XDG_CONFIG_HOME (or os equiv).
//go/pki will now automatically attempt to load these certificates. This
means you can now run any pki-dependant tool with -hspki_disable, and
with automatic mTLS!
Change-Id: I5b28e193e7c968d621bab0d42aabd6f0510fed6d
diff --git a/cluster/kube/lib/prodvider.libsonnet b/cluster/kube/lib/prodvider.libsonnet
index 5805993..3890f5a 100644
--- a/cluster/kube/lib/prodvider.libsonnet
+++ b/cluster/kube/lib/prodvider.libsonnet
@@ -9,7 +9,7 @@
cfg:: {
namespace: "prodvider",
- image: "registry.k0.hswaw.net/cluster/prodvider:1567256363-71a21c769369d013972d8dd0a71b83bee3e6848e",
+ image: "registry.k0.hswaw.net/q3k/prodvider:1596294699-1e1a4ddfc88008465aa38e4c037d2ba5d6ec8130",
apiEndpoint: error "API endpoint must be set",
diff --git a/cluster/prodaccess/BUILD.bazel b/cluster/prodaccess/BUILD.bazel
index 5124ffc..6c72082 100644
--- a/cluster/prodaccess/BUILD.bazel
+++ b/cluster/prodaccess/BUILD.bazel
@@ -3,6 +3,7 @@
go_library(
name = "go_default_library",
srcs = [
+ "hspki.go",
"kubernetes.go",
"prodaccess.go",
],
@@ -11,6 +12,7 @@
deps = [
"//cluster/certs:go_default_library",
"//cluster/prodvider/proto:go_default_library",
+ "//go/pki:go_default_library",
"@com_github_golang_glog//:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//credentials:go_default_library",
diff --git a/cluster/prodaccess/hspki.go b/cluster/prodaccess/hspki.go
new file mode 100644
index 0000000..2fcfaf0
--- /dev/null
+++ b/cluster/prodaccess/hspki.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+ "io/ioutil"
+ "os"
+
+ "github.com/golang/glog"
+
+ pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
+ "code.hackerspace.pl/hscloud/go/pki"
+)
+
+func useHSPKIKeys(keys *pb.HSPKIKeys) {
+ path, err := pki.DeveloperCredentialsLocation()
+ err = os.MkdirAll(path, 0700)
+ if err != nil {
+ glog.Exitf("mkdir %q: %v", path, err)
+ }
+
+ for _, el := range []struct {
+ target string
+ data []byte
+ }{
+ {path + "/ca.crt", keys.Ca},
+ {path + "/tls.crt", keys.Cert},
+ {path + "/tls.key", keys.Key},
+ } {
+ err := ioutil.WriteFile(el.target, el.data, 400)
+ if err != nil {
+ glog.Exitf("Failed to write %q: %v", el.target, err)
+ }
+ }
+}
diff --git a/cluster/prodaccess/prodaccess.go b/cluster/prodaccess/prodaccess.go
index e0e8ec2..1153bab 100644
--- a/cluster/prodaccess/prodaccess.go
+++ b/cluster/prodaccess/prodaccess.go
@@ -99,6 +99,9 @@
}
useKubernetesKeys(res.KubernetesKeys)
+ fmt.Printf("-> Kubernetes credentials installed\n")
+ useHSPKIKeys(res.HspkiKeys)
+ fmt.Printf("-> HSPKI credentials installed\n")
return true
}
diff --git a/cluster/prodvider/BUILD.bazel b/cluster/prodvider/BUILD.bazel
index 14690b7..c15ab66 100644
--- a/cluster/prodvider/BUILD.bazel
+++ b/cluster/prodvider/BUILD.bazel
@@ -5,6 +5,7 @@
name = "go_default_library",
srcs = [
"certs.go",
+ "hspki.go",
"kubernetes.go",
"main.go",
"service.go",
@@ -15,6 +16,7 @@
"//cluster/prodvider/proto:go_default_library",
"@com_github_cloudflare_cfssl//config:go_default_library",
"@com_github_cloudflare_cfssl//csr:go_default_library",
+ "@com_github_cloudflare_cfssl//helpers:go_default_library",
"@com_github_cloudflare_cfssl//signer:go_default_library",
"@com_github_cloudflare_cfssl//signer/local:go_default_library",
"@com_github_golang_glog//:go_default_library",
@@ -59,6 +61,6 @@
image = ":runtime",
format = "Docker",
registry = "registry.k0.hswaw.net",
- repository = "cluster/prodvider",
+ repository = "q3k/prodvider",
tag = "{BUILD_TIMESTAMP}-{STABLE_GIT_COMMIT}",
)
diff --git a/cluster/prodvider/hspki.go b/cluster/prodvider/hspki.go
new file mode 100644
index 0000000..243a424
--- /dev/null
+++ b/cluster/prodvider/hspki.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "encoding/pem"
+ "fmt"
+ "time"
+
+ "github.com/cloudflare/cfssl/config"
+ "github.com/cloudflare/cfssl/csr"
+ "github.com/cloudflare/cfssl/helpers"
+ "github.com/cloudflare/cfssl/signer"
+ "github.com/cloudflare/cfssl/signer/local"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
+)
+
+func (p *prodvider) hspkiSigner() (*local.Signer, error) {
+ policy := &config.Signing{
+ Profiles: map[string]*config.SigningProfile{
+ "client": &config.SigningProfile{
+ Usage: []string{"signing", "key encipherment", "client auth"},
+ ExpiryString: "30d",
+ },
+ },
+ Default: config.DefaultConfig(),
+ }
+
+ secret, err := p.k8s.CoreV1().Secrets("cert-manager").Get("pki-selfsigned-cert", metav1.GetOptions{})
+ if err != nil {
+ return nil, fmt.Errorf("hspki secret get failed: %w", err)
+ }
+
+ parsedCa, err := helpers.ParseCertificatePEM(secret.Data["tls.crt"])
+ if err != nil {
+ return nil, fmt.Errorf("when parsing tls.crt: %w", err)
+ }
+
+ priv, err := helpers.ParsePrivateKeyPEMWithPassword(secret.Data["tls.key"], nil)
+ if err != nil {
+ return nil, fmt.Errorf("when parsing tls.key: %w", err)
+ }
+
+ return local.NewSigner(priv, parsedCa, signer.DefaultSigAlgo(priv), policy)
+}
+
+func (p *prodvider) hspkiCreds(username string) (*pb.HSPKIKeys, error) {
+ principal := fmt.Sprintf("%s.sso.hswaw.net", username)
+
+ s, err := p.hspkiSigner()
+ if err != nil {
+ return nil, fmt.Errorf("hspkiSigner: %w", err)
+ }
+
+ signerCert, _ := s.Certificate("", "")
+ req := &csr.CertificateRequest{
+ CN: principal,
+ KeyRequest: &csr.BasicKeyRequest{
+ A: "rsa",
+ S: 4096,
+ },
+ Names: []csr.Name{
+ {
+ O: "prodvider",
+ OU: fmt.Sprintf("Prodvider HSPKI Cert for %s", username),
+ },
+ },
+ }
+
+ g := &csr.Generator{
+ Validator: func(req *csr.CertificateRequest) error { return nil },
+ }
+
+ csrPEM, keyPEM, err := g.ProcessRequest(req)
+ if err != nil {
+ return nil, fmt.Errorf("when making CSR: %w", err)
+ }
+
+ signReq := signer.SignRequest{
+ Hosts: []string{},
+ Request: string(csrPEM),
+ Profile: "client",
+ NotAfter: time.Now().Add(9 * time.Hour),
+ }
+
+ certPEM, err := s.Sign(signReq)
+ if err != nil {
+ return nil, fmt.Errorf("when issuing certificate: %w", err)
+ }
+
+ caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: signerCert.Raw})
+
+ return &pb.HSPKIKeys{
+ Ca: caPEM,
+ Cert: certPEM,
+ Key: keyPEM,
+ Principal: principal,
+ }, nil
+}
diff --git a/cluster/prodvider/proto/BUILD.bazel b/cluster/prodvider/proto/BUILD.bazel
index 2efd457..0817dfb 100644
--- a/cluster/prodvider/proto/BUILD.bazel
+++ b/cluster/prodvider/proto/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
diff --git a/cluster/prodvider/proto/prodvider.proto b/cluster/prodvider/proto/prodvider.proto
index 1ae2798..ba5bf9d 100644
--- a/cluster/prodvider/proto/prodvider.proto
+++ b/cluster/prodvider/proto/prodvider.proto
@@ -15,6 +15,7 @@
}
Result result = 1;
KubernetesKeys kubernetes_keys = 2;
+ HSPKIKeys hspki_keys = 3;
}
message KubernetesKeys {
@@ -24,6 +25,13 @@
bytes key = 4;
}
+message HSPKIKeys {
+ bytes ca = 1;
+ bytes cert = 2;
+ bytes key = 3;
+ string principal = 4;
+}
+
service Prodvider {
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);
}
diff --git a/cluster/prodvider/service.go b/cluster/prodvider/service.go
index 0409884..17dfe6e 100644
--- a/cluster/prodvider/service.go
+++ b/cluster/prodvider/service.go
@@ -69,14 +69,22 @@
return nil, status.Error(codes.Unavailable, "could not set up objects in Kubernetes")
}
- keys, err := p.kubernetesCreds(username)
+ kubernetesKeys, err := p.kubernetesCreds(username)
if err != nil {
glog.Errorf("kubernetesCreds(%q): %v", username, err)
return nil, status.Error(codes.Unavailable, "could not generate k8s keys")
}
+
+ hspkiKeys, err := p.hspkiCreds(username)
+ if err != nil {
+ glog.Errorf("hspkiCreds(%q): %v", username, err)
+ return nil, status.Error(codes.Unavailable, "could not generate hspki keys")
+ }
+
return &pb.AuthenticateResponse{
Result: pb.AuthenticateResponse_RESULT_AUTHENTICATED,
- KubernetesKeys: keys,
+ KubernetesKeys: kubernetesKeys,
+ HspkiKeys: hspkiKeys,
}, nil
}