constellation/joinservice/internal/kubernetesca/kubernetesca_test.go
Daniel Weiße c6ff34f4d2 Use Certificate Requests to issue Kubelet Certificates and set CA (#261)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
2022-07-15 09:33:11 +02:00

284 lines
7.4 KiB
Go

package kubernetesca
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"strings"
"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"
kubeconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
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-----`)
defaultSigningRequestFunc := func() ([]byte, error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{kubeconstants.NodesGroup},
CommonName: kubeconstants.NodesUserPrefix + "test-node",
},
}
return x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
}
testCases := map[string]struct {
caCert []byte
caKey []byte
createSigningRequest func() ([]byte, error)
wantErr bool
}{
"success ec key": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: defaultSigningRequestFunc,
},
"success rsa key": {
caCert: rsaCert,
caKey: rsaKey,
createSigningRequest: defaultSigningRequestFunc,
},
"success any key": {
caCert: testCert,
caKey: testKey,
createSigningRequest: defaultSigningRequestFunc,
},
"unsupported key": {
caCert: ecCert,
caKey: unsupportedKey,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true,
},
"invalid key": {
caCert: ecCert,
caKey: invalidKey,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true,
},
"invalid certificate": {
caCert: invalidCert,
caKey: ecKey,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true,
},
"no ca certificate": {
caKey: ecKey,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true,
},
"no ca key": {
caCert: ecCert,
createSigningRequest: defaultSigningRequestFunc,
wantErr: true,
},
"no signing request": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: func() ([]byte, error) { return nil, nil },
wantErr: true,
},
"incorrect common name format": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: func() ([]byte, error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{kubeconstants.NodesGroup},
CommonName: "test-node",
},
}
return x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
},
wantErr: true,
},
"incorrect organization format": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: func() ([]byte, error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{"test"},
CommonName: kubeconstants.NodesUserPrefix + "test-node",
},
}
return x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
},
wantErr: true,
},
"no organization": {
caCert: ecCert,
caKey: ecKey,
createSigningRequest: func() ([]byte, error) {
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: kubeconstants.NodesUserPrefix + "test-node",
},
}
return x509.CreateCertificateRequest(rand.Reader, csrTemplate, privK)
},
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,
)
signingRequest, err := tc.createSigningRequest()
require.NoError(err)
kubeCert, err := ca.GetCertificate(signingRequest)
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.True(strings.HasPrefix(cert.Subject.CommonName, kubeconstants.NodesUserPrefix))
assert.Equal(kubeconstants.NodesGroup, 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)
})
}
}
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
}
}