mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-09 17:55:10 -04:00
Create internal package for joinservice
This commit is contained in:
parent
43eb94b6dc
commit
2083d37b11
10 changed files with 25 additions and 33 deletions
119
joinservice/internal/kubernetesca/kubernetesca.go
Normal file
119
joinservice/internal/kubernetesca/kubernetesca.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
package kubernetesca
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/bootstrapper/util"
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
"github.com/edgelesssys/constellation/internal/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
caCertFilename = "/etc/kubernetes/pki/ca.crt"
|
||||
caKeyFilename = "/etc/kubernetes/pki/ca.key"
|
||||
)
|
||||
|
||||
// KubernetesCA handles signing of certificates using the Kubernetes root CA.
|
||||
type KubernetesCA struct {
|
||||
log *logger.Logger
|
||||
file file.Handler
|
||||
}
|
||||
|
||||
// New creates a new KubernetesCA.
|
||||
func New(log *logger.Logger, fileHandler file.Handler) *KubernetesCA {
|
||||
return &KubernetesCA{
|
||||
log: log,
|
||||
file: fileHandler,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCertificate creates a certificate for a node and signs it using the Kubernetes root CA.
|
||||
func (c KubernetesCA) GetCertificate(nodeName string) (cert []byte, key []byte, err error) {
|
||||
c.log.Debugf("Loading Kubernetes CA certificate")
|
||||
parentCertRaw, err := c.file.Read(caCertFilename)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
parentCertPEM, _ := pem.Decode(parentCertRaw)
|
||||
parentCert, err := x509.ParseCertificate(parentCertPEM.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c.log.Debugf("Loading Kubernetes CA private key")
|
||||
parentKeyRaw, err := c.file.Read(caKeyFilename)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
parentKeyPEM, _ := pem.Decode(parentKeyRaw)
|
||||
var parentKey any
|
||||
switch parentKeyPEM.Type {
|
||||
case "EC PRIVATE KEY":
|
||||
parentKey, err = x509.ParseECPrivateKey(parentKeyPEM.Bytes)
|
||||
case "RSA PRIVATE KEY":
|
||||
parentKey, err = x509.ParsePKCS1PrivateKey(parentKeyPEM.Bytes)
|
||||
case "PRIVATE KEY":
|
||||
parentKey, err = x509.ParsePKCS8PrivateKey(parentKeyPEM.Bytes)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported key type %q", parentCertPEM.Type)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c.log.Infof("Creating kubelet private key")
|
||||
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keyBytes, err := x509.MarshalECPrivateKey(privK)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
kubeletKey := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
})
|
||||
|
||||
c.log.Infof("Creating kubelet certificate")
|
||||
serialNumber, err := util.GenerateCertificateSerialNumber()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
// Create the kubelet certificate
|
||||
// For a reference on the certificate fields, see: https://kubernetes.io/docs/setup/best-practices/certificates/
|
||||
certTmpl := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
NotBefore: now.Add(-2 * time.Hour),
|
||||
NotAfter: now.Add(24 * 365 * time.Hour),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"system:nodes"},
|
||||
CommonName: fmt.Sprintf("system:node:%s", nodeName),
|
||||
},
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
},
|
||||
IsCA: false,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
certRaw, err := x509.CreateCertificate(rand.Reader, certTmpl, parentCert, &privK.PublicKey, parentKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
kubeletCert := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certRaw,
|
||||
})
|
||||
|
||||
return kubeletCert, kubeletKey, nil
|
||||
}
|
206
joinservice/internal/kubernetesca/kubernetesca_test.go
Normal file
206
joinservice/internal/kubernetesca/kubernetesca_test.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
package kubernetesca
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
"github.com/edgelesssys/constellation/internal/logger"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestGetCertificate(t *testing.T) {
|
||||
ecCert, ecKey := mustCreateCert(mustCreateECKey)
|
||||
rsaCert, rsaKey := mustCreateCert(mustCreateRSAKey)
|
||||
testCert, testKey := mustCreateCert(mustCreatePKCS8Key)
|
||||
unsupportedKey := []byte(`-----BEGIN SOME KEY-----
|
||||
Q29uc3RlbGxhdGlvbg==
|
||||
-----END SOME KEY-----`)
|
||||
invalidKey := []byte(`-----BEGIN PRIVATE KEY-----
|
||||
Q29uc3RlbGxhdGlvbg==
|
||||
-----END PRIVATE KEY-----`)
|
||||
invalidCert := []byte(`-----BEGIN CERTIFICATE-----
|
||||
Q29uc3RlbGxhdGlvbg==
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
testCases := map[string]struct {
|
||||
caCert []byte
|
||||
caKey []byte
|
||||
wantErr bool
|
||||
}{
|
||||
"success ec key": {
|
||||
caCert: ecCert,
|
||||
caKey: ecKey,
|
||||
},
|
||||
"success rsa key": {
|
||||
caCert: rsaCert,
|
||||
caKey: rsaKey,
|
||||
},
|
||||
"success any key": {
|
||||
caCert: testCert,
|
||||
caKey: testKey,
|
||||
},
|
||||
"unsupported key": {
|
||||
caCert: ecCert,
|
||||
caKey: unsupportedKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid key": {
|
||||
caCert: ecCert,
|
||||
caKey: invalidKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid certificate": {
|
||||
caCert: invalidCert,
|
||||
caKey: ecKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"no ca certificate": {
|
||||
caKey: ecKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"no ca key": {
|
||||
caCert: ecCert,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
file := file.NewHandler(afero.NewMemMapFs())
|
||||
|
||||
if len(tc.caCert) > 0 {
|
||||
require.NoError(file.Write(caCertFilename, tc.caCert, 0o644))
|
||||
}
|
||||
if len(tc.caKey) > 0 {
|
||||
require.NoError(file.Write(caKeyFilename, tc.caKey, 0o644))
|
||||
}
|
||||
|
||||
ca := New(
|
||||
logger.NewTest(t),
|
||||
file,
|
||||
)
|
||||
|
||||
nodeName := "test"
|
||||
kubeCert, kubeKey, err := ca.GetCertificate(nodeName)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(err)
|
||||
certPEM, _ := pem.Decode(kubeCert)
|
||||
require.NotNil(certPEM)
|
||||
cert, err := x509.ParseCertificate(certPEM.Bytes)
|
||||
require.NoError(err)
|
||||
assert.Equal("system:node:"+nodeName, cert.Subject.CommonName)
|
||||
assert.Equal("system:nodes", cert.Subject.Organization[0])
|
||||
assert.Equal(x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, cert.KeyUsage)
|
||||
assert.Equal(x509.ExtKeyUsageClientAuth, cert.ExtKeyUsage[0])
|
||||
assert.False(cert.IsCA)
|
||||
assert.True(cert.BasicConstraintsValid)
|
||||
|
||||
keyPEM, _ := pem.Decode(kubeKey)
|
||||
require.NotNil(keyPEM)
|
||||
key, err := x509.ParseECPrivateKey(keyPEM.Bytes)
|
||||
require.NoError(err)
|
||||
require.IsType(&ecdsa.PublicKey{}, cert.PublicKey)
|
||||
assert.Equal(&key.PublicKey, cert.PublicKey.(*ecdsa.PublicKey))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustCreateCert(getKey func() (crypto.PrivateKey, []byte)) ([]byte, []byte) {
|
||||
caPriv, keyPEM := getKey()
|
||||
caTemplate := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "kubernetes",
|
||||
},
|
||||
NotBefore: time.Now().Add(-2 * time.Hour),
|
||||
IsCA: true,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
}
|
||||
caCert, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, publicKey(caPriv), caPriv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
caCertPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: caCert,
|
||||
})
|
||||
|
||||
return caCertPEM, keyPEM
|
||||
}
|
||||
|
||||
func mustCreateECKey() (crypto.PrivateKey, []byte) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyBytes, err := x509.MarshalECPrivateKey(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
})
|
||||
}
|
||||
|
||||
func mustCreatePKCS8Key() (crypto.PrivateKey, []byte) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyBytes, err := x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
})
|
||||
}
|
||||
|
||||
func mustCreateRSAKey() (crypto.PrivateKey, []byte) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||
return key, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
})
|
||||
}
|
||||
|
||||
func publicKey(priv crypto.PrivateKey) any {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &k.PublicKey
|
||||
case *ecdsa.PrivateKey:
|
||||
return &k.PublicKey
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue