forked from hswaw/hscloud
*: 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
This commit is contained in:
parent
1e1a4ddfc8
commit
f3312ef77e
12 changed files with 268 additions and 20 deletions
|
@ -9,7 +9,7 @@ local kube = import "../../../kube/kube.libsonnet";
|
|||
|
||||
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",
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
|||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"hspki.go",
|
||||
"kubernetes.go",
|
||||
"prodaccess.go",
|
||||
],
|
||||
|
@ -11,6 +12,7 @@ go_library(
|
|||
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",
|
||||
|
|
33
cluster/prodaccess/hspki.go
Normal file
33
cluster/prodaccess/hspki.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -99,6 +99,9 @@ func authenticate(ctx context.Context, prodvider pb.ProdviderClient) bool {
|
|||
}
|
||||
|
||||
useKubernetesKeys(res.KubernetesKeys)
|
||||
fmt.Printf("-> Kubernetes credentials installed\n")
|
||||
useHSPKIKeys(res.HspkiKeys)
|
||||
fmt.Printf("-> HSPKI credentials installed\n")
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ go_library(
|
|||
name = "go_default_library",
|
||||
srcs = [
|
||||
"certs.go",
|
||||
"hspki.go",
|
||||
"kubernetes.go",
|
||||
"main.go",
|
||||
"service.go",
|
||||
|
@ -15,6 +16,7 @@ go_library(
|
|||
"//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 @@ container_push(
|
|||
image = ":runtime",
|
||||
format = "Docker",
|
||||
registry = "registry.k0.hswaw.net",
|
||||
repository = "cluster/prodvider",
|
||||
repository = "q3k/prodvider",
|
||||
tag = "{BUILD_TIMESTAMP}-{STABLE_GIT_COMMIT}",
|
||||
)
|
||||
|
|
99
cluster/prodvider/hspki.go
Normal file
99
cluster/prodvider/hspki.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ message AuthenticateResponse {
|
|||
}
|
||||
Result result = 1;
|
||||
KubernetesKeys kubernetes_keys = 2;
|
||||
HSPKIKeys hspki_keys = 3;
|
||||
}
|
||||
|
||||
message KubernetesKeys {
|
||||
|
@ -24,6 +25,13 @@ message KubernetesKeys {
|
|||
bytes key = 4;
|
||||
}
|
||||
|
||||
message HSPKIKeys {
|
||||
bytes ca = 1;
|
||||
bytes cert = 2;
|
||||
bytes key = 3;
|
||||
string principal = 4;
|
||||
}
|
||||
|
||||
service Prodvider {
|
||||
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);
|
||||
}
|
||||
|
|
|
@ -69,14 +69,22 @@ func (p *prodvider) Authenticate(ctx context.Context, req *pb.AuthenticateReques
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["grpc.go"],
|
||||
srcs = [
|
||||
"grpc.go",
|
||||
"locate.go",
|
||||
],
|
||||
importpath = "code.hackerspace.pl/hscloud/go/pki",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
@ -210,18 +209,19 @@ func WithServerHSPKI() []grpc.ServerOption {
|
|||
return []grpc.ServerOption{}
|
||||
}
|
||||
|
||||
serverCert, err := tls.LoadX509KeyPair(flagCertificatePath, flagKeyPath)
|
||||
loc, err := loadCredentials()
|
||||
if err != nil {
|
||||
glog.Exitf("WithServerHSPKI: loadCredentials: %v", err)
|
||||
}
|
||||
|
||||
serverCert, err := tls.X509KeyPair(loc.cert, loc.key)
|
||||
if err != nil {
|
||||
glog.Exitf("WithServerHSPKI: cannot load service certificate/key: %v", err)
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
ca, err := ioutil.ReadFile(flagCAPath)
|
||||
if err != nil {
|
||||
glog.Exitf("WithServerHSPKI: cannot load CA certificate: %v", err)
|
||||
}
|
||||
if ok := certPool.AppendCertsFromPEM(ca); !ok {
|
||||
glog.Exitf("WithServerHSPKI: cannot use CA certificate: %v", err)
|
||||
if ok := certPool.AppendCertsFromPEM(loc.ca); !ok {
|
||||
glog.Exitf("WithServerHSPKI: cannot use CA certificate")
|
||||
}
|
||||
|
||||
creds := grpc.Creds(credentials.NewTLS(&tls.Config{
|
||||
|
@ -243,16 +243,17 @@ func WithClientHSPKI() grpc.DialOption {
|
|||
return grpc.WithInsecure()
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
ca, err := ioutil.ReadFile(flagCAPath)
|
||||
loc, err := loadCredentials()
|
||||
if err != nil {
|
||||
glog.Exitf("WithClientHSPKI: cannot load CA certificate: %v", err)
|
||||
}
|
||||
if ok := certPool.AppendCertsFromPEM(ca); !ok {
|
||||
glog.Exitf("WithClientHSPKI: cannot use CA certificate: %v", err)
|
||||
glog.Exitf("WithServerHSPKI: loadCredentials: %v", err)
|
||||
}
|
||||
|
||||
clientCert, err := tls.LoadX509KeyPair(flagCertificatePath, flagKeyPath)
|
||||
certPool := x509.NewCertPool()
|
||||
if ok := certPool.AppendCertsFromPEM(loc.ca); !ok {
|
||||
glog.Exitf("WithServerHSPKI: cannot use CA certificate")
|
||||
}
|
||||
|
||||
clientCert, err := tls.X509KeyPair(loc.cert, loc.key)
|
||||
if err != nil {
|
||||
glog.Exitf("WithClientHSPKI: cannot load service certificate/key: %v", err)
|
||||
}
|
||||
|
|
88
go/pki/locate.go
Normal file
88
go/pki/locate.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package pki
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// DeveloperCredentialsLocation returns the path containing HSPKI credentials
|
||||
// on developer machines. These are provisioned by //cluster/prodaccess, and
|
||||
// are used if available.
|
||||
func DeveloperCredentialsLocation() (string, error) {
|
||||
cfgDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
glog.Exitf("UserConfigDir: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/hspki", cfgDir), nil
|
||||
}
|
||||
|
||||
type creds struct {
|
||||
ca []byte
|
||||
cert []byte
|
||||
key []byte
|
||||
}
|
||||
|
||||
func loadDeveloperCredentials() (*creds, error) {
|
||||
path, err := DeveloperCredentialsLocation()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DeveloperCredentialsLocation: %w")
|
||||
}
|
||||
|
||||
c := creds{}
|
||||
for _, el := range []struct {
|
||||
target *[]byte
|
||||
path string
|
||||
}{
|
||||
{&c.ca, path + "/" + "ca.crt"},
|
||||
{&c.cert, path + "/" + "tls.crt"},
|
||||
{&c.key, path + "/" + "tls.key"},
|
||||
} {
|
||||
data, err := ioutil.ReadFile(el.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ReadFile(%q): %w", el.path, err)
|
||||
}
|
||||
*el.target = data
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func loadFlagCredentials() (*creds, error) {
|
||||
c := creds{}
|
||||
for _, el := range []struct {
|
||||
target *[]byte
|
||||
path string
|
||||
}{
|
||||
{&c.ca, flagCAPath},
|
||||
{&c.cert, flagCertificatePath},
|
||||
{&c.key, flagKeyPath},
|
||||
} {
|
||||
data, err := ioutil.ReadFile(el.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ReadFile(%q): %w", el.path, err)
|
||||
}
|
||||
*el.target = data
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func loadCredentials() (*creds, error) {
|
||||
dev, err := loadDeveloperCredentials()
|
||||
if err == nil {
|
||||
return dev, nil
|
||||
}
|
||||
glog.Warningf("Could not load developer PKI credentials: %v", err)
|
||||
|
||||
fl, err := loadFlagCredentials()
|
||||
if err == nil {
|
||||
return fl, err
|
||||
}
|
||||
glog.Warningf("Could not load flag-defined PKI credentials: %v", err)
|
||||
|
||||
return nil, fmt.Errorf("could not load any credentials")
|
||||
}
|
Loading…
Reference in a new issue