cluster/prodvider: rewrite against x509 lib for ed25519 support
This gets rid of cfssl for the kubernetes bits of prodvider, instead
using plain crypto/x509. This also allows to support our new fancy
ED25519 CA.
Change-Id: If677b3f4523014f56ea802b87499d1c0eb6d92e9
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1489
Reviewed-by: q3k <q3k@hackerspace.pl>
diff --git a/cluster/kube/lib/prodvider.libsonnet b/cluster/kube/lib/prodvider.libsonnet
index 30b1674..f131681 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/q3k/prodvider:315532800-21bacc96d76e4f2074e769dfc65ab43702f52d10",
+ image: "registry.k0.hswaw.net/q3k/prodvider:1680301337",
apiEndpoint: error "API endpoint must be set",
@@ -19,7 +19,7 @@
key: importstr "../../secrets/plain/ca-kube-prodvider.key",
},
kube: {
- cert: importstr "../../certs/ca-kube.crt",
+ cert: importstr "../../certs/ca-kube-new.crt",
},
}
},
diff --git a/cluster/prodvider/BUILD.bazel b/cluster/prodvider/BUILD.bazel
index 81689c0..00dedc6 100644
--- a/cluster/prodvider/BUILD.bazel
+++ b/cluster/prodvider/BUILD.bazel
@@ -63,5 +63,5 @@
format = "Docker",
registry = "registry.k0.hswaw.net",
repository = "q3k/prodvider",
- tag = "{BUILD_TIMESTAMP}-{STABLE_GIT_COMMIT}",
+ tag = "1680301337",
)
diff --git a/cluster/prodvider/certs.go b/cluster/prodvider/certs.go
index 309af1f..acc89f9 100644
--- a/cluster/prodvider/certs.go
+++ b/cluster/prodvider/certs.go
@@ -1,113 +1,118 @@
package main
import (
+ "crypto/ed25519"
+ "crypto/rand"
"crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
"fmt"
+ "math/big"
+ "net"
"time"
- "github.com/cloudflare/cfssl/csr"
- "github.com/cloudflare/cfssl/signer"
"github.com/golang/glog"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
+func serializeCert(der []byte) []byte {
+ return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
+}
+
+func serializeKey(priv ed25519.PrivateKey) []byte {
+ pkcs8, err := x509.MarshalPKCS8PrivateKey(priv)
+ if err != nil {
+ return nil
+ }
+
+ block := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8})
+ return block
+}
+
func (p *prodvider) selfCreds() grpc.ServerOption {
glog.Infof("Bootstrapping certificate for self (%q)...", flagProdviderCN)
- // Create a key and CSR.
- csrPEM, keyPEM, err := p.makeSelfCSR()
- if err != nil {
- glog.Exitf("Could not generate key and CSR for self: %v", err)
- }
-
// Create a cert
- certPEM, err := p.makeSelfCertificate(csrPEM)
+ keyRaw, certRaw, err := p.makeSelfCertificate()
if err != nil {
glog.Exitf("Could not sign certificate for self: %v", err)
}
- serverCert, err := tls.X509KeyPair(certPEM, keyPEM)
+ serverCert, err := tls.X509KeyPair(serializeCert(certRaw), serializeKey(keyRaw))
if err != nil {
glog.Exitf("Could not use gRPC certificate: %v", err)
}
- signerCert, _ := p.sign.Certificate("", "")
- serverCert.Certificate = append(serverCert.Certificate, signerCert.Raw)
+ serverCert.Certificate = append(serverCert.Certificate, p.intermediateCACert.Raw)
return grpc.Creds(credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{serverCert},
}))
}
-func (p *prodvider) makeSelfCSR() ([]byte, []byte, error) {
- signerCert, _ := p.sign.Certificate("", "")
- req := &csr.CertificateRequest{
- CN: flagProdviderCN,
- KeyRequest: &csr.BasicKeyRequest{
- A: "rsa",
- S: 4096,
+func (p *prodvider) makeSelfCertificate() (ed25519.PrivateKey, []byte, error) {
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ return nil, nil, err
+ }
+ template := &x509.Certificate{
+ Subject: pkix.Name{
+ CommonName: flagProdviderCN,
},
- Names: []csr.Name{
- {
- C: signerCert.Subject.Country[0],
- ST: signerCert.Subject.Province[0],
- L: signerCert.Subject.Locality[0],
- O: signerCert.Subject.Organization[0],
- OU: signerCert.Subject.OrganizationalUnit[0],
- },
+ NotBefore: time.Now(),
+ NotAfter: time.Now().Add(30 * 24 * time.Hour),
+ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ SerialNumber: serialNumber,
+ DNSNames: []string{flagProdviderCN},
+ IPAddresses: []net.IP{
+ {127, 0, 0, 1},
},
- Hosts: []string{flagProdviderCN},
}
- g := &csr.Generator{
- Validator: func(req *csr.CertificateRequest) error { return nil },
+ pkey, skey, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ return nil, nil, err
}
-
- return g.ProcessRequest(req)
+ bytes, err := x509.CreateCertificate(rand.Reader, template, p.intermediateCACert, pkey, p.intermediateCAKey)
+ if err != nil {
+ return nil, nil, err
+ }
+ return skey, bytes, nil
}
-func (p *prodvider) makeSelfCertificate(csr []byte) ([]byte, error) {
- req := signer.SignRequest{
- Hosts: []string{flagProdviderCN},
- Request: string(csr),
- Profile: "server",
+func (p *prodvider) makeKubernetesCertificate(username, o string, notAfter time.Time) (ed25519.PrivateKey, []byte, error) {
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ return nil, nil, err
}
- return p.sign.Sign(req)
-}
-
-func (p *prodvider) makeKubernetesCSR(username, o string) ([]byte, []byte, error) {
- signerCert, _ := p.sign.Certificate("", "")
- req := &csr.CertificateRequest{
- CN: username,
- KeyRequest: &csr.BasicKeyRequest{
- A: "rsa",
- S: 4096,
+ template := &x509.Certificate{
+ Subject: pkix.Name{
+ Organization: []string{o},
+ OrganizationalUnit: []string{fmt.Sprintf("Prodvider Kubernetes Cert for %s/%s", username, o)},
+ CommonName: username,
},
- Names: []csr.Name{
- {
- C: signerCert.Subject.Country[0],
- ST: signerCert.Subject.Province[0],
- L: signerCert.Subject.Locality[0],
- O: o,
- OU: fmt.Sprintf("Prodvider Kubernetes Cert for %s/%s", username, o),
- },
+ NotBefore: time.Now(),
+ NotAfter: notAfter,
+ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
+ DNSNames: []string{
+ username,
},
+ SerialNumber: serialNumber,
}
- g := &csr.Generator{
- Validator: func(req *csr.CertificateRequest) error { return nil },
+ pkey, skey, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ return nil, nil, err
}
-
- return g.ProcessRequest(req)
-}
-
-func (p *prodvider) makeKubernetesCertificate(csr []byte, notAfter time.Time) ([]byte, error) {
- req := signer.SignRequest{
- Hosts: []string{},
- Request: string(csr),
- Profile: "client",
- NotAfter: notAfter,
+ bytes, err := x509.CreateCertificate(rand.Reader, template, p.intermediateCACert, pkey, p.intermediateCAKey)
+ if err != nil {
+ return nil, nil, err
}
- return p.sign.Sign(req)
+ return skey, bytes, nil
}
diff --git a/cluster/prodvider/kubernetes.go b/cluster/prodvider/kubernetes.go
index d7ad535..4f73ce4 100644
--- a/cluster/prodvider/kubernetes.go
+++ b/cluster/prodvider/kubernetes.go
@@ -2,7 +2,6 @@
import (
"context"
- "encoding/pem"
"fmt"
"time"
@@ -19,62 +18,46 @@
func (p *prodvider) kubernetesCreds(username string) (*pb.KubernetesKeys, error) {
o := fmt.Sprintf("sso:%s", username)
+ email := username + "@hackerspace.pl"
- csrPEM, keyPEM, err := p.makeKubernetesCSR(username+"@hackerspace.pl", o)
+ keyRaw, certBytes, err := p.makeKubernetesCertificate(email, o, time.Now().Add(13*time.Hour))
if err != nil {
return nil, err
}
- certPEM, err := p.makeKubernetesCertificate(csrPEM, time.Now().Add(13*time.Hour))
- if err != nil {
- return nil, err
- }
-
- caCert, _ := p.sign.Certificate("", "")
- caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert.Raw})
-
// Build certificate chain from new cert and intermediate CA.
- chainPEM := append(certPEM, caPEM...)
+ chainPEM := append(serializeCert(certBytes), serializeCert(p.intermediateCACert.Raw)...)
glog.Infof("Generated k8s certificate for %q", username)
return &pb.KubernetesKeys{
Cluster: "k0.hswaw.net",
// APIServerCA
- Ca: p.kubeCAPEM,
+ Ca: serializeCert(p.kubeCACert.Raw),
// Chain of new cert + intermediate CA
Cert: chainPEM,
- Key: keyPEM,
+ Key: serializeKey(keyRaw),
}, nil
}
func (p *prodvider) kubernetesConnect() error {
- csrPEM, keyPEM, err := p.makeKubernetesCSR("prodvider", "system:masters")
+ keyRaw, certBytes, err := p.makeKubernetesCertificate("prodvider", "system:masters", time.Now().Add(30*24*time.Hour))
if err != nil {
return err
}
- certPEM, err := p.makeKubernetesCertificate(csrPEM, time.Now().Add(30*24*time.Hour))
- if err != nil {
- return err
- }
-
- caCert, _ := p.sign.Certificate("", "")
-
- caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert.Raw})
-
glog.Infof("Generated k8s certificate for self (system:masters)")
// Build certificate chain from our cert and intermediate CA.
- chainPEM := append(certPEM, caPEM...)
+ chainPEM := append(serializeCert(certBytes), serializeCert(p.intermediateCACert.Raw)...)
config := &rest.Config{
Host: flagKubernetesHost,
TLSClientConfig: rest.TLSClientConfig{
// Chain to authenticate ourselves (us + intermediate CA).
CertData: chainPEM,
- KeyData: keyPEM,
+ KeyData: serializeKey(keyRaw),
// APIServer CA for verification.
- CAData: p.kubeCAPEM,
+ CAData: serializeCert(p.kubeCACert.Raw),
},
}
diff --git a/cluster/prodvider/main.go b/cluster/prodvider/main.go
index 7222a86..ca114f0 100644
--- a/cluster/prodvider/main.go
+++ b/cluster/prodvider/main.go
@@ -1,15 +1,16 @@
package main
import (
+ "crypto/ed25519"
+ "crypto/x509"
+ "encoding/pem"
"flag"
- "io/ioutil"
+ "fmt"
"math/rand"
"net"
"os"
"time"
- "github.com/cloudflare/cfssl/config"
- "github.com/cloudflare/cfssl/signer/local"
"github.com/golang/glog"
"google.golang.org/grpc"
"k8s.io/client-go/kubernetes"
@@ -36,44 +37,66 @@
}
type prodvider struct {
- sign *local.Signer
- k8s *kubernetes.Clientset
- srv *grpc.Server
- kubeCAPEM []byte
+ k8s *kubernetes.Clientset
+ srv *grpc.Server
+
+ intermediateCAKey ed25519.PrivateKey
+ intermediateCACert *x509.Certificate
+ kubeCACert *x509.Certificate
+}
+
+func loadCert(path string) (*x509.Certificate, error) {
+ b, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ block, _ := pem.Decode(b)
+ if block == nil {
+ return nil, fmt.Errorf("no PEM block found")
+ }
+ if block.Type != "CERTIFICATE" {
+ return nil, fmt.Errorf("unexpected PEM block: %q", block.Type)
+ }
+ return x509.ParseCertificate(block.Bytes)
+}
+
+func loadKey(path string) (ed25519.PrivateKey, error) {
+ bytes, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ block, _ := pem.Decode(bytes)
+ if block == nil {
+ return nil, fmt.Errorf("no PEM block found")
+ }
+ if block.Type != "PRIVATE KEY" {
+ return nil, fmt.Errorf("unexpected PEM block: %q", block.Type)
+ }
+ key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
+ if err != nil {
+ return nil, err
+ }
+ if k, ok := key.(ed25519.PrivateKey); ok {
+ return k, nil
+ }
+ return nil, fmt.Errorf("not an ED25519 key")
}
func newProdvider() *prodvider {
- policy := &config.Signing{
- Profiles: map[string]*config.SigningProfile{
- "server": &config.SigningProfile{
- Usage: []string{"signing", "key encipherment", "server auth"},
- ExpiryString: "30d",
- },
- "client": &config.SigningProfile{
- Usage: []string{"signing", "key encipherment", "client auth"},
- ExpiryString: "30d",
- },
- "client-server": &config.SigningProfile{
- Usage: []string{"signing", "key encipherment", "server auth", "client auth"},
- ExpiryString: "30d",
- },
- },
- Default: config.DefaultConfig(),
- }
-
- sign, err := local.NewSignerFromFile(flagCACertificatePath, flagCAKeyPath, policy)
+ kubeCACert, err := loadCert(flagKubeCACertificatePath)
if err != nil {
- glog.Exitf("Could not create signer: %v", err)
+ glog.Exitf("Loading kube CA certificate failed: %v", err)
}
-
- kubeCAPEM, err := ioutil.ReadFile(flagKubeCACertificatePath)
+ intermediateCACert, err := loadCert(flagCACertificatePath)
if err != nil {
- glog.Exitf("Could not read kube CA cert path: %v")
+ glog.Exitf("Loading intermediate CAcertificate failed: %v", err)
}
-
+ intermediateCAKey, err := loadKey(flagCAKeyPath)
return &prodvider{
- sign: sign,
- kubeCAPEM: kubeCAPEM,
+ intermediateCAKey: intermediateCAKey,
+ intermediateCACert: intermediateCACert,
+ kubeCACert: kubeCACert,
}
}