mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-08-03 20:44:14 -04:00
Use Certificate Requests to issue Kubelet Certificates and set CA (#261)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
49e98286a9
commit
c6ff34f4d2
13 changed files with 451 additions and 159 deletions
|
@ -1,18 +1,18 @@
|
|||
package kubernetesca
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/bootstrapper/util"
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
"github.com/edgelesssys/constellation/internal/logger"
|
||||
kubeconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -35,22 +35,22 @@ func New(log *logger.Logger, fileHandler file.Handler) *KubernetesCA {
|
|||
}
|
||||
|
||||
// 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) {
|
||||
func (c KubernetesCA) GetCertificate(csr []byte) (cert []byte, err error) {
|
||||
c.log.Debugf("Loading Kubernetes CA certificate")
|
||||
parentCertRaw, err := c.file.Read(caCertFilename)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
parentCertPEM, _ := pem.Decode(parentCertRaw)
|
||||
parentCert, err := x509.ParseCertificate(parentCertPEM.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.log.Debugf("Loading Kubernetes CA private key")
|
||||
parentKeyRaw, err := c.file.Read(caKeyFilename)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
parentKeyPEM, _ := pem.Decode(parentKeyRaw)
|
||||
var parentKey any
|
||||
|
@ -62,30 +62,34 @@ func (c KubernetesCA) GetCertificate(nodeName string) (cert []byte, key []byte,
|
|||
case "PRIVATE KEY":
|
||||
parentKey, err = x509.ParsePKCS8PrivateKey(parentKeyPEM.Bytes)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported key type %q", parentCertPEM.Type)
|
||||
return nil, fmt.Errorf("unsupported key type %q", parentCertPEM.Type)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.log.Infof("Creating kubelet private key")
|
||||
privK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
certRequest, err := x509.ParseCertificateRequest(csr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, err := x509.MarshalECPrivateKey(privK)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
if err := certRequest.CheckSignature(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kubeletKey := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
})
|
||||
|
||||
c.log.Infof("Creating kubelet certificate")
|
||||
if len(certRequest.Subject.Organization) != 1 {
|
||||
return nil, errors.New("certificate request must have exactly one organization")
|
||||
}
|
||||
if certRequest.Subject.Organization[0] != kubeconstants.NodesGroup {
|
||||
return nil, fmt.Errorf("certificate request must have organization %q but has %q", kubeconstants.NodesGroup, certRequest.Subject.Organization[0])
|
||||
}
|
||||
if !strings.HasPrefix(certRequest.Subject.CommonName, kubeconstants.NodesUserPrefix) {
|
||||
return nil, fmt.Errorf("certificate request must have common name prefix %q but is %q", kubeconstants.NodesUserPrefix, certRequest.Subject.CommonName)
|
||||
}
|
||||
|
||||
serialNumber, err := util.GenerateCertificateSerialNumber()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
@ -95,25 +99,25 @@ func (c KubernetesCA) GetCertificate(nodeName string) (cert []byte, key []byte,
|
|||
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,
|
||||
Subject: certRequest.Subject,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
},
|
||||
IsCA: false,
|
||||
BasicConstraintsValid: true,
|
||||
IPAddresses: certRequest.IPAddresses,
|
||||
}
|
||||
certRaw, err := x509.CreateCertificate(rand.Reader, certTmpl, parentCert, &privK.PublicKey, parentKey)
|
||||
|
||||
certRaw, err := x509.CreateCertificate(rand.Reader, certTmpl, parentCert, certRequest.PublicKey, parentKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
kubeletCert := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certRaw,
|
||||
})
|
||||
|
||||
return kubeletCert, kubeletKey, nil
|
||||
return kubeletCert, nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -19,6 +20,7 @@ import (
|
|||
"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) {
|
||||
|
@ -38,45 +40,126 @@ Q29uc3RlbGxhdGlvbg==
|
|||
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
|
||||
wantErr bool
|
||||
caCert []byte
|
||||
caKey []byte
|
||||
createSigningRequest func() ([]byte, error)
|
||||
wantErr bool
|
||||
}{
|
||||
"success ec key": {
|
||||
caCert: ecCert,
|
||||
caKey: ecKey,
|
||||
caCert: ecCert,
|
||||
caKey: ecKey,
|
||||
createSigningRequest: defaultSigningRequestFunc,
|
||||
},
|
||||
"success rsa key": {
|
||||
caCert: rsaCert,
|
||||
caKey: rsaKey,
|
||||
caCert: rsaCert,
|
||||
caKey: rsaKey,
|
||||
createSigningRequest: defaultSigningRequestFunc,
|
||||
},
|
||||
"success any key": {
|
||||
caCert: testCert,
|
||||
caKey: testKey,
|
||||
caCert: testCert,
|
||||
caKey: testKey,
|
||||
createSigningRequest: defaultSigningRequestFunc,
|
||||
},
|
||||
"unsupported key": {
|
||||
caCert: ecCert,
|
||||
caKey: unsupportedKey,
|
||||
wantErr: true,
|
||||
caCert: ecCert,
|
||||
caKey: unsupportedKey,
|
||||
createSigningRequest: defaultSigningRequestFunc,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid key": {
|
||||
caCert: ecCert,
|
||||
caKey: invalidKey,
|
||||
wantErr: true,
|
||||
caCert: ecCert,
|
||||
caKey: invalidKey,
|
||||
createSigningRequest: defaultSigningRequestFunc,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid certificate": {
|
||||
caCert: invalidCert,
|
||||
caKey: ecKey,
|
||||
wantErr: true,
|
||||
caCert: invalidCert,
|
||||
caKey: ecKey,
|
||||
createSigningRequest: defaultSigningRequestFunc,
|
||||
wantErr: true,
|
||||
},
|
||||
"no ca certificate": {
|
||||
caKey: ecKey,
|
||||
wantErr: true,
|
||||
caKey: ecKey,
|
||||
createSigningRequest: defaultSigningRequestFunc,
|
||||
wantErr: true,
|
||||
},
|
||||
"no ca key": {
|
||||
caCert: ecCert,
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
@ -100,8 +183,9 @@ Q29uc3RlbGxhdGlvbg==
|
|||
file,
|
||||
)
|
||||
|
||||
nodeName := "test"
|
||||
kubeCert, kubeKey, err := ca.GetCertificate(nodeName)
|
||||
signingRequest, err := tc.createSigningRequest()
|
||||
require.NoError(err)
|
||||
kubeCert, err := ca.GetCertificate(signingRequest)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
|
@ -112,19 +196,12 @@ Q29uc3RlbGxhdGlvbg==
|
|||
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.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)
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue