mirror of https://gerrit.hackerspace.pl/hscloud
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>changes/89/1489/2
parent
777aab92a9
commit
3a6d67e0c4
|
@ -9,7 +9,7 @@ local kube = import "../../../kube/kube.libsonnet";
|
||||||
|
|
||||||
cfg:: {
|
cfg:: {
|
||||||
namespace: "prodvider",
|
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",
|
apiEndpoint: error "API endpoint must be set",
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ local kube = import "../../../kube/kube.libsonnet";
|
||||||
key: importstr "../../secrets/plain/ca-kube-prodvider.key",
|
key: importstr "../../secrets/plain/ca-kube-prodvider.key",
|
||||||
},
|
},
|
||||||
kube: {
|
kube: {
|
||||||
cert: importstr "../../certs/ca-kube.crt",
|
cert: importstr "../../certs/ca-kube-new.crt",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,5 +63,5 @@ container_push(
|
||||||
format = "Docker",
|
format = "Docker",
|
||||||
registry = "registry.k0.hswaw.net",
|
registry = "registry.k0.hswaw.net",
|
||||||
repository = "q3k/prodvider",
|
repository = "q3k/prodvider",
|
||||||
tag = "{BUILD_TIMESTAMP}-{STABLE_GIT_COMMIT}",
|
tag = "1680301337",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,113 +1,118 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cloudflare/cfssl/csr"
|
|
||||||
"github.com/cloudflare/cfssl/signer"
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"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 {
|
func (p *prodvider) selfCreds() grpc.ServerOption {
|
||||||
glog.Infof("Bootstrapping certificate for self (%q)...", flagProdviderCN)
|
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
|
// Create a cert
|
||||||
certPEM, err := p.makeSelfCertificate(csrPEM)
|
keyRaw, certRaw, err := p.makeSelfCertificate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Exitf("Could not sign certificate for self: %v", err)
|
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 {
|
if err != nil {
|
||||||
glog.Exitf("Could not use gRPC certificate: %v", err)
|
glog.Exitf("Could not use gRPC certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signerCert, _ := p.sign.Certificate("", "")
|
serverCert.Certificate = append(serverCert.Certificate, p.intermediateCACert.Raw)
|
||||||
serverCert.Certificate = append(serverCert.Certificate, signerCert.Raw)
|
|
||||||
|
|
||||||
return grpc.Creds(credentials.NewTLS(&tls.Config{
|
return grpc.Creds(credentials.NewTLS(&tls.Config{
|
||||||
Certificates: []tls.Certificate{serverCert},
|
Certificates: []tls.Certificate{serverCert},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prodvider) makeSelfCSR() ([]byte, []byte, error) {
|
func (p *prodvider) makeSelfCertificate() (ed25519.PrivateKey, []byte, error) {
|
||||||
signerCert, _ := p.sign.Certificate("", "")
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
|
||||||
req := &csr.CertificateRequest{
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
CN: flagProdviderCN,
|
if err != nil {
|
||||||
KeyRequest: &csr.BasicKeyRequest{
|
return nil, nil, err
|
||||||
A: "rsa",
|
|
||||||
S: 4096,
|
|
||||||
},
|
|
||||||
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],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Hosts: []string{flagProdviderCN},
|
|
||||||
}
|
}
|
||||||
|
template := &x509.Certificate{
|
||||||
g := &csr.Generator{
|
Subject: pkix.Name{
|
||||||
Validator: func(req *csr.CertificateRequest) error { return nil },
|
CommonName: flagProdviderCN,
|
||||||
}
|
|
||||||
|
|
||||||
return g.ProcessRequest(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *prodvider) makeSelfCertificate(csr []byte) ([]byte, error) {
|
|
||||||
req := signer.SignRequest{
|
|
||||||
Hosts: []string{flagProdviderCN},
|
|
||||||
Request: string(csr),
|
|
||||||
Profile: "server",
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
Names: []csr.Name{
|
NotBefore: time.Now(),
|
||||||
{
|
NotAfter: time.Now().Add(30 * 24 * time.Hour),
|
||||||
C: signerCert.Subject.Country[0],
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
ST: signerCert.Subject.Province[0],
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
L: signerCert.Subject.Locality[0],
|
SerialNumber: serialNumber,
|
||||||
O: o,
|
DNSNames: []string{flagProdviderCN},
|
||||||
OU: fmt.Sprintf("Prodvider Kubernetes Cert for %s/%s", username, o),
|
IPAddresses: []net.IP{
|
||||||
},
|
{127, 0, 0, 1},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
g := &csr.Generator{
|
pkey, skey, err := ed25519.GenerateKey(rand.Reader)
|
||||||
Validator: func(req *csr.CertificateRequest) error { return nil },
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
bytes, err := x509.CreateCertificate(rand.Reader, template, p.intermediateCACert, pkey, p.intermediateCAKey)
|
||||||
return g.ProcessRequest(req)
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return skey, bytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prodvider) makeKubernetesCertificate(csr []byte, notAfter time.Time) ([]byte, error) {
|
func (p *prodvider) makeKubernetesCertificate(username, o string, notAfter time.Time) (ed25519.PrivateKey, []byte, error) {
|
||||||
req := signer.SignRequest{
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
|
||||||
Hosts: []string{},
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
Request: string(csr),
|
if err != nil {
|
||||||
Profile: "client",
|
return nil, nil, err
|
||||||
NotAfter: notAfter,
|
|
||||||
}
|
}
|
||||||
return p.sign.Sign(req)
|
template := &x509.Certificate{
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{o},
|
||||||
|
OrganizationalUnit: []string{fmt.Sprintf("Prodvider Kubernetes Cert for %s/%s", username, o)},
|
||||||
|
CommonName: username,
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: notAfter,
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||||
|
DNSNames: []string{
|
||||||
|
username,
|
||||||
|
},
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
pkey, skey, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
bytes, err := x509.CreateCertificate(rand.Reader, template, p.intermediateCACert, pkey, p.intermediateCAKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return skey, bytes, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -19,62 +18,46 @@ import (
|
||||||
|
|
||||||
func (p *prodvider) kubernetesCreds(username string) (*pb.KubernetesKeys, error) {
|
func (p *prodvider) kubernetesCreds(username string) (*pb.KubernetesKeys, error) {
|
||||||
o := fmt.Sprintf("sso:%s", username)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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)
|
glog.Infof("Generated k8s certificate for %q", username)
|
||||||
return &pb.KubernetesKeys{
|
return &pb.KubernetesKeys{
|
||||||
Cluster: "k0.hswaw.net",
|
Cluster: "k0.hswaw.net",
|
||||||
// APIServerCA
|
// APIServerCA
|
||||||
Ca: p.kubeCAPEM,
|
Ca: serializeCert(p.kubeCACert.Raw),
|
||||||
// Chain of new cert + intermediate CA
|
// Chain of new cert + intermediate CA
|
||||||
Cert: chainPEM,
|
Cert: chainPEM,
|
||||||
Key: keyPEM,
|
Key: serializeKey(keyRaw),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prodvider) kubernetesConnect() error {
|
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 {
|
if err != nil {
|
||||||
return err
|
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)")
|
glog.Infof("Generated k8s certificate for self (system:masters)")
|
||||||
|
|
||||||
// Build certificate chain from our cert and intermediate CA.
|
// Build certificate chain from our cert and intermediate CA.
|
||||||
chainPEM := append(certPEM, caPEM...)
|
chainPEM := append(serializeCert(certBytes), serializeCert(p.intermediateCACert.Raw)...)
|
||||||
|
|
||||||
config := &rest.Config{
|
config := &rest.Config{
|
||||||
Host: flagKubernetesHost,
|
Host: flagKubernetesHost,
|
||||||
TLSClientConfig: rest.TLSClientConfig{
|
TLSClientConfig: rest.TLSClientConfig{
|
||||||
// Chain to authenticate ourselves (us + intermediate CA).
|
// Chain to authenticate ourselves (us + intermediate CA).
|
||||||
CertData: chainPEM,
|
CertData: chainPEM,
|
||||||
KeyData: keyPEM,
|
KeyData: serializeKey(keyRaw),
|
||||||
// APIServer CA for verification.
|
// APIServer CA for verification.
|
||||||
CAData: p.kubeCAPEM,
|
CAData: serializeCert(p.kubeCACert.Raw),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"io/ioutil"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cloudflare/cfssl/config"
|
|
||||||
"github.com/cloudflare/cfssl/signer/local"
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
|
@ -36,44 +37,66 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type prodvider struct {
|
type prodvider struct {
|
||||||
sign *local.Signer
|
k8s *kubernetes.Clientset
|
||||||
k8s *kubernetes.Clientset
|
srv *grpc.Server
|
||||||
srv *grpc.Server
|
|
||||||
kubeCAPEM []byte
|
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 {
|
func newProdvider() *prodvider {
|
||||||
policy := &config.Signing{
|
kubeCACert, err := loadCert(flagKubeCACertificatePath)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Exitf("Could not create signer: %v", err)
|
glog.Exitf("Loading kube CA certificate failed: %v", err)
|
||||||
}
|
}
|
||||||
|
intermediateCACert, err := loadCert(flagCACertificatePath)
|
||||||
kubeCAPEM, err := ioutil.ReadFile(flagKubeCACertificatePath)
|
|
||||||
if err != nil {
|
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{
|
return &prodvider{
|
||||||
sign: sign,
|
intermediateCAKey: intermediateCAKey,
|
||||||
kubeCAPEM: kubeCAPEM,
|
intermediateCACert: intermediateCACert,
|
||||||
|
kubeCACert: kubeCACert,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue