2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-06-02 09:58:19 -04:00
|
|
|
package kubernetesca
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"encoding/pem"
|
|
|
|
"math/big"
|
2022-07-15 03:33:11 -04:00
|
|
|
"strings"
|
2022-06-02 09:58:19 -04:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2022-09-21 07:47:57 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
2022-06-02 09:58:19 -04:00
|
|
|
"github.com/spf13/afero"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
2022-06-30 09:24:36 -04:00
|
|
|
"go.uber.org/goleak"
|
2022-07-15 03:33:11 -04:00
|
|
|
kubeconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
2022-06-02 09:58:19 -04:00
|
|
|
)
|
|
|
|
|
2022-06-30 09:24:36 -04:00
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
goleak.VerifyTestMain(m)
|
|
|
|
}
|
|
|
|
|
2022-06-02 09:58:19 -04:00
|
|
|
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-----`)
|
2022-07-15 03:33:11 -04:00
|
|
|
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)
|
|
|
|
}
|
2022-06-02 09:58:19 -04:00
|
|
|
|
|
|
|
testCases := map[string]struct {
|
2022-07-15 03:33:11 -04:00
|
|
|
caCert []byte
|
|
|
|
caKey []byte
|
|
|
|
createSigningRequest func() ([]byte, error)
|
|
|
|
wantErr bool
|
2022-06-02 09:58:19 -04:00
|
|
|
}{
|
|
|
|
"success ec key": {
|
2022-07-15 03:33:11 -04:00
|
|
|
caCert: ecCert,
|
|
|
|
caKey: ecKey,
|
|
|
|
createSigningRequest: defaultSigningRequestFunc,
|
2022-06-02 09:58:19 -04:00
|
|
|
},
|
|
|
|
"success rsa key": {
|
2022-07-15 03:33:11 -04:00
|
|
|
caCert: rsaCert,
|
|
|
|
caKey: rsaKey,
|
|
|
|
createSigningRequest: defaultSigningRequestFunc,
|
2022-06-02 09:58:19 -04:00
|
|
|
},
|
|
|
|
"success any key": {
|
2022-07-15 03:33:11 -04:00
|
|
|
caCert: testCert,
|
|
|
|
caKey: testKey,
|
|
|
|
createSigningRequest: defaultSigningRequestFunc,
|
2022-06-02 09:58:19 -04:00
|
|
|
},
|
|
|
|
"unsupported key": {
|
2022-07-15 03:33:11 -04:00
|
|
|
caCert: ecCert,
|
|
|
|
caKey: unsupportedKey,
|
|
|
|
createSigningRequest: defaultSigningRequestFunc,
|
|
|
|
wantErr: true,
|
2022-06-02 09:58:19 -04:00
|
|
|
},
|
|
|
|
"invalid key": {
|
2022-07-15 03:33:11 -04:00
|
|
|
caCert: ecCert,
|
|
|
|
caKey: invalidKey,
|
|
|
|
createSigningRequest: defaultSigningRequestFunc,
|
|
|
|
wantErr: true,
|
2022-06-02 09:58:19 -04:00
|
|
|
},
|
|
|
|
"invalid certificate": {
|
2022-07-15 03:33:11 -04:00
|
|
|
caCert: invalidCert,
|
|
|
|
caKey: ecKey,
|
|
|
|
createSigningRequest: defaultSigningRequestFunc,
|
|
|
|
wantErr: true,
|
2022-06-02 09:58:19 -04:00
|
|
|
},
|
|
|
|
"no ca certificate": {
|
2022-07-15 03:33:11 -04:00
|
|
|
caKey: ecKey,
|
|
|
|
createSigningRequest: defaultSigningRequestFunc,
|
|
|
|
wantErr: true,
|
2022-06-02 09:58:19 -04:00
|
|
|
},
|
|
|
|
"no ca key": {
|
2022-07-15 03:33:11 -04:00
|
|
|
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)
|
|
|
|
},
|
2022-06-02 09:58:19 -04:00
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
require := require.New(t)
|
|
|
|
|
2022-08-08 05:04:17 -04:00
|
|
|
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
2022-06-02 09:58:19 -04:00
|
|
|
|
|
|
|
if len(tc.caCert) > 0 {
|
2022-08-08 05:04:17 -04:00
|
|
|
require.NoError(fileHandler.Write(caCertFilename, tc.caCert, file.OptNone))
|
2022-06-02 09:58:19 -04:00
|
|
|
}
|
|
|
|
if len(tc.caKey) > 0 {
|
2022-08-08 05:04:17 -04:00
|
|
|
require.NoError(fileHandler.Write(caKeyFilename, tc.caKey, file.OptNone))
|
2022-06-02 09:58:19 -04:00
|
|
|
}
|
|
|
|
|
2022-06-28 10:51:30 -04:00
|
|
|
ca := New(
|
|
|
|
logger.NewTest(t),
|
2022-08-08 05:04:17 -04:00
|
|
|
fileHandler,
|
2022-06-28 10:51:30 -04:00
|
|
|
)
|
2022-06-02 09:58:19 -04:00
|
|
|
|
2022-07-15 03:33:11 -04:00
|
|
|
signingRequest, err := tc.createSigningRequest()
|
|
|
|
require.NoError(err)
|
|
|
|
kubeCert, err := ca.GetCertificate(signingRequest)
|
2022-06-02 09:58:19 -04:00
|
|
|
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)
|
2022-07-15 03:33:11 -04:00
|
|
|
assert.True(strings.HasPrefix(cert.Subject.CommonName, kubeconstants.NodesUserPrefix))
|
|
|
|
assert.Equal(kubeconstants.NodesGroup, cert.Subject.Organization[0])
|
2022-06-02 09:58:19 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|