Skip to content

Commit

Permalink
Move pki utils to separate pkiutil package (#541)
Browse files Browse the repository at this point in the history
  • Loading branch information
neoaggelos committed Jul 12, 2024
1 parent 4e96859 commit 28ffd37
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 62 deletions.
4 changes: 2 additions & 2 deletions src/k8s/pkg/k8sd/app/hooks_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (

"github.com/canonical/k8s/pkg/k8sd/database"
databaseutil "github.com/canonical/k8s/pkg/k8sd/database/util"
"github.com/canonical/k8s/pkg/k8sd/pki"
"github.com/canonical/k8s/pkg/k8sd/types"
"github.com/canonical/k8s/pkg/utils"
pkiutil "github.com/canonical/k8s/pkg/utils/pki"
"github.com/canonical/microcluster/state"
)

Expand All @@ -26,7 +26,7 @@ func (a *App) onStart(s *state.State) error {
return nil, fmt.Errorf("failed to load RSA key from configuration: %w", err)
}
keyPEM := cfg.Certificates.GetK8sdPublicKey()
key, err := pki.LoadRSAPublicKey(cfg.Certificates.GetK8sdPublicKey())
key, err := pkiutil.LoadRSAPublicKey(cfg.Certificates.GetK8sdPublicKey())
if err != nil && keyPEM != "" {
return nil, fmt.Errorf("failed to load RSA key: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions src/k8s/pkg/k8sd/controllers/update_node_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import (
"time"

"github.com/canonical/k8s/pkg/client/kubernetes"
"github.com/canonical/k8s/pkg/k8sd/pki"
"github.com/canonical/k8s/pkg/k8sd/types"
"github.com/canonical/k8s/pkg/snap"
snaputil "github.com/canonical/k8s/pkg/snap/util"
pkiutil "github.com/canonical/k8s/pkg/utils/pki"
)

// UpdateNodeConfigurationController asynchronously performs updates of the cluster config.
Expand Down Expand Up @@ -98,7 +98,7 @@ func (c *UpdateNodeConfigurationController) Run(ctx context.Context, getClusterC

func (c *UpdateNodeConfigurationController) reconcile(ctx context.Context, client *kubernetes.Client, config types.ClusterConfig) error {
keyPEM := config.Certificates.GetK8sdPrivateKey()
key, err := pki.LoadRSAPrivateKey(keyPEM)
key, err := pkiutil.LoadRSAPrivateKey(keyPEM)
if err != nil && keyPEM != "" {
return fmt.Errorf("failed to load cluster RSA key: %w", err)
}
Expand Down
38 changes: 20 additions & 18 deletions src/k8s/pkg/k8sd/pki/control_plane.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"crypto/x509/pkix"
"fmt"
"net"

pkiutil "github.com/canonical/k8s/pkg/utils/pki"
)

// ControlPlanePKI is a list of all certificates we require for a control plane node.
Expand Down Expand Up @@ -105,7 +107,7 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
if !c.allowSelfSignedCA {
return fmt.Errorf("kubernetes CA not specified and generating self-signed CA not allowed")
}
cert, key, err := generateSelfSignedCA(pkix.Name{CommonName: "kubernetes-ca"}, c.years, 2048)
cert, key, err := pkiutil.GenerateSelfSignedCA(pkix.Name{CommonName: "kubernetes-ca"}, c.years, 2048)
if err != nil {
return fmt.Errorf("failed to generate kubernetes CA: %w", err)
}
Expand All @@ -118,20 +120,20 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
if !c.allowSelfSignedCA {
return fmt.Errorf("kubernetes client CA not specified and generating self-signed CA not allowed")
}
cert, key, err := generateSelfSignedCA(pkix.Name{CommonName: "kubernetes-ca-client"}, c.years, 2048)
cert, key, err := pkiutil.GenerateSelfSignedCA(pkix.Name{CommonName: "kubernetes-ca-client"}, c.years, 2048)
if err != nil {
return fmt.Errorf("failed to generate kubernetes client CA: %w", err)
}
c.ClientCACert = cert
c.ClientCAKey = key
}

serverCACert, serverCAKey, err := loadCertificate(c.CACert, c.CAKey)
serverCACert, serverCAKey, err := pkiutil.LoadCertificate(c.CACert, c.CAKey)
if err != nil {
return fmt.Errorf("failed to parse kubernetes CA: %w", err)
}

clientCACert, clientCAKey, err := loadCertificate(c.ClientCACert, c.ClientCAKey)
clientCACert, clientCAKey, err := pkiutil.LoadCertificate(c.ClientCACert, c.ClientCAKey)
if err != nil {
return fmt.Errorf("failed to parse kubernetes CA: %w", err)
}
Expand All @@ -141,7 +143,7 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
if !c.allowSelfSignedCA {
return fmt.Errorf("front-proxy CA not specified and generating self-signed CA not allowed")
}
cert, key, err := generateSelfSignedCA(pkix.Name{CommonName: "front-proxy-ca"}, c.years, 2048)
cert, key, err := pkiutil.GenerateSelfSignedCA(pkix.Name{CommonName: "front-proxy-ca"}, c.years, 2048)
if err != nil {
return fmt.Errorf("failed to generate front-proxy CA: %w", err)
}
Expand All @@ -151,19 +153,19 @@ func (c *ControlPlanePKI) CompleteCertificates() error {

// Generate front proxy client certificate (ok to override)
if c.FrontProxyClientCert == "" || c.FrontProxyClientKey == "" {
frontProxyCACert, frontProxyCAKey, err := loadCertificate(c.FrontProxyCACert, c.FrontProxyCAKey)
frontProxyCACert, frontProxyCAKey, err := pkiutil.LoadCertificate(c.FrontProxyCACert, c.FrontProxyCAKey)
switch {
case err != nil:
return fmt.Errorf("failed to parse front proxy CA: %w", err)
case frontProxyCAKey == nil:
return fmt.Errorf("using an external front proxy CA without providing the front-proxy-client certificate is not possible")
}

template, err := generateCertificate(pkix.Name{CommonName: "front-proxy-client"}, c.years, false, nil, nil)
template, err := pkiutil.GenerateCertificate(pkix.Name{CommonName: "front-proxy-client"}, c.years, false, nil, nil)
if err != nil {
return fmt.Errorf("failed to generate front-proxy-client certificate: %w", err)
}
cert, key, err := signCertificate(template, 2048, frontProxyCACert, &frontProxyCAKey.PublicKey, frontProxyCAKey)
cert, key, err := pkiutil.SignCertificate(template, 2048, frontProxyCACert, &frontProxyCAKey.PublicKey, frontProxyCAKey)
if err != nil {
return fmt.Errorf("failed to sign front-proxy-client certificate: %w", err)
}
Expand All @@ -178,7 +180,7 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
return fmt.Errorf("service account signing key not specified and generating new key is not allowed")
}

key, _, err := generateRSAKey(2048)
key, _, err := pkiutil.GenerateRSAKey(2048)
if err != nil {
return fmt.Errorf("failed to generate service account key: %w", err)
}
Expand All @@ -192,14 +194,14 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
return fmt.Errorf("using an external kubernetes CA without providing the kubelet certificate is not possible")
}

template, err := generateCertificate(
template, err := pkiutil.GenerateCertificate(
pkix.Name{CommonName: fmt.Sprintf("system:node:%s", c.hostname), Organization: []string{"system:nodes"}},
c.years, false, append(c.dnsSANs, c.hostname), append(c.ipSANs, machineIPs...),
)
if err != nil {
return fmt.Errorf("failed to generate kubelet certificate: %w", err)
}
cert, key, err := signCertificate(template, 2048, serverCACert, &serverCAKey.PublicKey, serverCAKey)
cert, key, err := pkiutil.SignCertificate(template, 2048, serverCACert, &serverCAKey.PublicKey, serverCAKey)
if err != nil {
return fmt.Errorf("failed to sign kubelet certificate: %w", err)
}
Expand All @@ -214,11 +216,11 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
return fmt.Errorf("using an external kubernetes CA without providing the apiserver-kubelet-client certificate is not possible")
}

template, err := generateCertificate(pkix.Name{CommonName: "apiserver-kubelet-client", Organization: []string{"system:masters"}}, c.years, false, nil, nil)
template, err := pkiutil.GenerateCertificate(pkix.Name{CommonName: "apiserver-kubelet-client", Organization: []string{"system:masters"}}, c.years, false, nil, nil)
if err != nil {
return fmt.Errorf("failed to generate apiserver-kubelet-client certificate: %w", err)
}
cert, key, err := signCertificate(template, 2048, clientCACert, &clientCAKey.PublicKey, clientCAKey)
cert, key, err := pkiutil.SignCertificate(template, 2048, clientCACert, &clientCAKey.PublicKey, clientCAKey)
if err != nil {
return fmt.Errorf("failed to sign apiserver-kubelet-client certificate: %w", err)
}
Expand All @@ -233,15 +235,15 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
return fmt.Errorf("using an external kubernetes CA without providing the apiserver certificate is not possible")
}

template, err := generateCertificate(
template, err := pkiutil.GenerateCertificate(
pkix.Name{CommonName: "kube-apiserver"},
c.years,
false,
append(c.dnsSANs, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster", "kubernetes.default.svc.cluster.local"), append(c.ipSANs, machineIPs...))
if err != nil {
return fmt.Errorf("failed to generate apiserver certificate: %w", err)
}
cert, key, err := signCertificate(template, 2048, serverCACert, &serverCAKey.PublicKey, serverCAKey)
cert, key, err := pkiutil.SignCertificate(template, 2048, serverCACert, &serverCAKey.PublicKey, serverCAKey)
if err != nil {
return fmt.Errorf("failed to sign apiserver certificate: %w", err)
}
Expand All @@ -268,12 +270,12 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
return fmt.Errorf("using an external kubernetes CA client without providing the %s certificate is not possible", i.name)
}

template, err := generateCertificate(pkix.Name{CommonName: i.cn, Organization: i.o}, c.years, false, nil, nil)
template, err := pkiutil.GenerateCertificate(pkix.Name{CommonName: i.cn, Organization: i.o}, c.years, false, nil, nil)
if err != nil {
return fmt.Errorf("failed to generate %s client certificate: %w", i.name, err)
}

cert, key, err := signCertificate(template, 2048, clientCACert, &clientCAKey.PublicKey, clientCAKey)
cert, key, err := pkiutil.SignCertificate(template, 2048, clientCACert, &clientCAKey.PublicKey, clientCAKey)
if err != nil {
return fmt.Errorf("failed to sign %s client certificate: %w", i.name, err)
}
Expand All @@ -289,7 +291,7 @@ func (c *ControlPlanePKI) CompleteCertificates() error {
return fmt.Errorf("cluster keypair not specified and generating new key is not allowed")
}

priv, pub, err := generateRSAKey(2048)
priv, pub, err := pkiutil.GenerateRSAKey(2048)
if err != nil {
return fmt.Errorf("failed to generate cluster keypair: %w", err)
}
Expand Down
5 changes: 3 additions & 2 deletions src/k8s/pkg/k8sd/pki/control_plane_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"

"github.com/canonical/k8s/pkg/k8sd/pki"
pkiutil "github.com/canonical/k8s/pkg/utils/pki"
. "github.com/onsi/gomega"
)

Expand All @@ -38,9 +39,9 @@ func TestControlPlaneCertificates(t *testing.T) {
t.Run("K8sdKey", func(t *testing.T) {
g := NewWithT(t)

priv, err := pki.LoadRSAPrivateKey(c.K8sdPrivateKey)
priv, err := pkiutil.LoadRSAPrivateKey(c.K8sdPrivateKey)
g.Expect(err).ToNot(HaveOccurred())
pub, err := pki.LoadRSAPublicKey(c.K8sdPublicKey)
pub, err := pkiutil.LoadRSAPublicKey(c.K8sdPublicKey)
g.Expect(err).ToNot(HaveOccurred())

// generate a hash to sign
Expand Down
6 changes: 4 additions & 2 deletions src/k8s/pkg/k8sd/pki/k8sdqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"crypto/x509/pkix"
"fmt"
"net"

pkiutil "github.com/canonical/k8s/pkg/utils/pki"
)

// K8sDqlitePKI is a list of certificates required by the k8s-dqlite datastore.
Expand Down Expand Up @@ -57,11 +59,11 @@ func (c *K8sDqlitePKI) CompleteCertificates() error {
return fmt.Errorf("k8s-dqlite certificate not specified and generating self-signed certificates is not allowed")
}

template, err := generateCertificate(pkix.Name{CommonName: "k8s"}, c.years, false, append(c.dnsSANs, c.hostname), append(c.ipSANs, net.IP{127, 0, 0, 1}))
template, err := pkiutil.GenerateCertificate(pkix.Name{CommonName: "k8s"}, c.years, false, append(c.dnsSANs, c.hostname), append(c.ipSANs, net.IP{127, 0, 0, 1}))
if err != nil {
return fmt.Errorf("failed to generate k8s-dqlite certificate: %w", err)
}
cert, key, err := signCertificate(template, 2048, template, nil, nil)
cert, key, err := pkiutil.SignCertificate(template, 2048, template, nil, nil)
if err != nil {
return fmt.Errorf("failed to self-sign k8s-dqlite certificate: %w", err)
}
Expand Down
14 changes: 8 additions & 6 deletions src/k8s/pkg/k8sd/pki/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"crypto/x509/pkix"
"fmt"
"net"

pkiutil "github.com/canonical/k8s/pkg/utils/pki"
)

type WorkerNodePKI struct {
Expand All @@ -23,12 +25,12 @@ type WorkerNodePKI struct {

// CompleteWorkerNodePKI generates the PKI needed for a worker node.
func (c *ControlPlanePKI) CompleteWorkerNodePKI(hostname string, nodeIP net.IP, bits int) (*WorkerNodePKI, error) {
serverCACert, serverCAKey, err := loadCertificate(c.CACert, c.CAKey)
serverCACert, serverCAKey, err := pkiutil.LoadCertificate(c.CACert, c.CAKey)
if err != nil {
return nil, fmt.Errorf("failed to load kubernetes CA: %w", err)
}

clientCACert, clientCAKey, err := loadCertificate(c.ClientCACert, c.ClientCAKey)
clientCACert, clientCAKey, err := pkiutil.LoadCertificate(c.ClientCACert, c.ClientCAKey)
if err != nil {
return nil, fmt.Errorf("failed to load kubernetes client CA: %w", err)
}
Expand All @@ -37,11 +39,11 @@ func (c *ControlPlanePKI) CompleteWorkerNodePKI(hostname string, nodeIP net.IP,

// we have a cluster CA key, sign the kubelet server certificate
if serverCAKey != nil {
template, err := generateCertificate(pkix.Name{CommonName: fmt.Sprintf("system:node:%s", hostname), Organization: []string{"system:nodes"}}, c.years, false, []string{hostname}, []net.IP{{127, 0, 0, 1}, nodeIP})
template, err := pkiutil.GenerateCertificate(pkix.Name{CommonName: fmt.Sprintf("system:node:%s", hostname), Organization: []string{"system:nodes"}}, c.years, false, []string{hostname}, []net.IP{{127, 0, 0, 1}, nodeIP})
if err != nil {
return nil, fmt.Errorf("failed to generate kubelet certificate for hostname=%s address=%s: %w", hostname, nodeIP.String(), err)
}
cert, key, err := signCertificate(template, bits, serverCACert, &serverCAKey.PublicKey, serverCAKey)
cert, key, err := pkiutil.SignCertificate(template, bits, serverCACert, &serverCAKey.PublicKey, serverCAKey)
if err != nil {
return nil, fmt.Errorf("failed to sign kubelet certificate for hostname=%s address=%s: %w", hostname, nodeIP.String(), err)
}
Expand All @@ -62,12 +64,12 @@ func (c *ControlPlanePKI) CompleteWorkerNodePKI(hostname string, nodeIP net.IP,
{name: "kubelet", cn: fmt.Sprintf("system:node:%s", hostname), o: []string{"system:nodes"}, cert: &pki.KubeletClientCert, key: &pki.KubeletClientKey},
} {
if *i.cert == "" || *i.key == "" {
template, err := generateCertificate(pkix.Name{CommonName: i.cn, Organization: i.o}, c.years, false, nil, nil)
template, err := pkiutil.GenerateCertificate(pkix.Name{CommonName: i.cn, Organization: i.o}, c.years, false, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to generate %s client certificate: %w", i.name, err)
}

cert, key, err := signCertificate(template, 2048, clientCACert, &clientCAKey.PublicKey, clientCAKey)
cert, key, err := pkiutil.SignCertificate(template, 2048, clientCACert, &clientCAKey.PublicKey, clientCAKey)
if err != nil {
return nil, fmt.Errorf("failed to sign %s client certificate: %w", i.name, err)
}
Expand Down
24 changes: 13 additions & 11 deletions src/k8s/pkg/k8sd/pki/worker_test.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
package pki
package pki_test

import (
"crypto/x509/pkix"
"net"
"testing"

"github.com/canonical/k8s/pkg/k8sd/pki"
pkiutil "github.com/canonical/k8s/pkg/utils/pki"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
)

func TestControlPlanePKI_CompleteWorkerNodePKI(t *testing.T) {

g := NewWithT(t)
serverCACert, serverCAKey, err := generateSelfSignedCA(pkix.Name{CommonName: "kubernetes-ca"}, 1, 2048)
serverCACert, serverCAKey, err := pkiutil.GenerateSelfSignedCA(pkix.Name{CommonName: "kubernetes-ca"}, 1, 2048)
g.Expect(err).ToNot(HaveOccurred())
clientCACert, clientCAKey, err := generateSelfSignedCA(pkix.Name{CommonName: "kubernetes-ca-client"}, 1, 2048)
clientCACert, clientCAKey, err := pkiutil.GenerateSelfSignedCA(pkix.Name{CommonName: "kubernetes-ca-client"}, 1, 2048)
g.Expect(err).ToNot(HaveOccurred())

for _, tc := range []struct {
name string
withCerts func(*ControlPlanePKI)
withCerts func(*pki.ControlPlanePKI)
expectErr bool
expectPKITo types.GomegaMatcher
}{
{
name: "WithCACertAndKeys",
withCerts: func(pki *ControlPlanePKI) {
withCerts: func(pki *pki.ControlPlanePKI) {
pki.CACert = serverCACert
pki.CAKey = serverCAKey
pki.ClientCACert = clientCACert
Expand All @@ -44,26 +46,26 @@ func TestControlPlanePKI_CompleteWorkerNodePKI(t *testing.T) {
},
{
name: "WithoutCerts",
withCerts: func(pki *ControlPlanePKI) {},
withCerts: func(pki *pki.ControlPlanePKI) {},
expectErr: true,
},
{
name: "WithoutCACert",
withCerts: func(pki *ControlPlanePKI) {
withCerts: func(pki *pki.ControlPlanePKI) {
pki.ClientCACert = clientCACert
},
expectErr: true,
},
{
name: "WithoutClientCACert",
withCerts: func(pki *ControlPlanePKI) {
withCerts: func(pki *pki.ControlPlanePKI) {
pki.CACert = serverCACert
},
expectErr: true,
},
{
name: "OnlyServerCAKey",
withCerts: func(pki *ControlPlanePKI) {
withCerts: func(pki *pki.ControlPlanePKI) {
pki.CACert = serverCACert
pki.CAKey = serverCAKey
pki.ClientCACert = clientCACert
Expand All @@ -81,7 +83,7 @@ func TestControlPlanePKI_CompleteWorkerNodePKI(t *testing.T) {
},
{
name: "OnlyClientCAKey",
withCerts: func(pki *ControlPlanePKI) {
withCerts: func(pki *pki.ControlPlanePKI) {
pki.CACert = serverCACert
pki.ClientCACert = clientCACert
pki.ClientCAKey = clientCAKey
Expand All @@ -100,7 +102,7 @@ func TestControlPlanePKI_CompleteWorkerNodePKI(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
cp := NewControlPlanePKI(ControlPlanePKIOpts{Years: 10})
cp := pki.NewControlPlanePKI(pki.ControlPlanePKIOpts{Years: 10})
tc.withCerts(cp)

pki, err := cp.CompleteWorkerNodePKI("worker", net.IP{10, 0, 0, 1}, 2048)
Expand Down
Loading

0 comments on commit 28ffd37

Please sign in to comment.