m/node/kubernetes/pki: refactor out CA functionality
This factors out all non-k8s-specific CA functionality from
metropolis/node/kubernetes/pki into metropolis/pkg/pki.
This will allow us to re-use the same PKI-in-CA system to issue
certificates for the Metropolis cluster and nodes.
We also drive-by change some Kubernetes/PKI interactions to make things
cleaner. Notably, this implements Certificate.Mount to return a
fileargs.FileArgs containing all the files neede to use this
Certificate.
Test Plan: covered by current e2e tests. An etcd harness to test this independently would be nice, though.
X-Origin-Diff: phab/D709
GitOrigin-RevId: bdc9ff215b94c9192f65c6da8935fe2818fd14ad
diff --git a/metropolis/node/kubernetes/kubelet.go b/metropolis/node/kubernetes/kubelet.go
index 741dba8..0cacaef 100644
--- a/metropolis/node/kubernetes/kubelet.go
+++ b/metropolis/node/kubernetes/kubelet.go
@@ -19,7 +19,6 @@
import (
"context"
"encoding/json"
- "encoding/pem"
"fmt"
"io"
"net"
@@ -29,10 +28,10 @@
kubeletconfig "k8s.io/kubelet/config/v1beta1"
"source.monogon.dev/metropolis/node/core/localstorage"
- "source.monogon.dev/metropolis/node/core/localstorage/declarative"
"source.monogon.dev/metropolis/node/kubernetes/pki"
"source.monogon.dev/metropolis/node/kubernetes/reconciler"
"source.monogon.dev/metropolis/pkg/fileargs"
+ opki "source.monogon.dev/metropolis/pkg/pki"
"source.monogon.dev/metropolis/pkg/supervisor"
)
@@ -42,42 +41,32 @@
KubeletDirectory *localstorage.DataKubernetesKubeletDirectory
EphemeralDirectory *localstorage.EphemeralDirectory
Output io.Writer
- KPKI *pki.KubernetesPKI
+ KPKI *pki.PKI
+
+ mount *opki.FilesystemCertificate
+ mountKubeconfigPath string
}
func (s *kubeletService) createCertificates(ctx context.Context) error {
- identity := fmt.Sprintf("system:node:%s", s.NodeName)
-
- ca := s.KPKI.Certificates[pki.IdCA]
- cacert, _, err := ca.Ensure(ctx, s.KPKI.KV)
+ server, client, err := s.KPKI.VolatileKubelet(ctx, s.NodeName)
if err != nil {
- return fmt.Errorf("could not ensure ca certificate: %w", err)
+ return fmt.Errorf("when generating local kubelet credentials: %w", err)
}
- kubeconfig, err := pki.New(ca, "", pki.Client(identity, []string{"system:nodes"})).Kubeconfig(ctx, s.KPKI.KV)
+ clientKubeconfig, err := pki.Kubeconfig(ctx, s.KPKI.KV, client)
if err != nil {
- return fmt.Errorf("could not create volatile kubelet client cert: %w", err)
+ return fmt.Errorf("when generating kubeconfig: %w", err)
}
- cert, key, err := pki.New(ca, "", pki.Server([]string{s.NodeName}, nil)).Ensure(ctx, s.KPKI.KV)
+ // Use a single fileargs mount for server certificate and client kubeconfig.
+ mounted, err := server.Mount(ctx, s.KPKI.KV)
if err != nil {
- return fmt.Errorf("could not create volatile kubelet server cert: %w", err)
+ return fmt.Errorf("could not mount kubelet cert dir: %w", err)
}
+ // mounted is closed by Run() on process exit.
- // TODO(q3k): this should probably become its own function //metropolis/node/kubernetes/pki.
- for _, el := range []struct {
- target declarative.FilePlacement
- data []byte
- }{
- {s.KubeletDirectory.Kubeconfig, kubeconfig},
- {s.KubeletDirectory.PKI.CACertificate, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cacert})},
- {s.KubeletDirectory.PKI.Certificate, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert})},
- {s.KubeletDirectory.PKI.Key, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key})},
- } {
- if err := el.target.Write(el.data, 0400); err != nil {
- return fmt.Errorf("could not write %v: %w", el.target, err)
- }
- }
+ s.mount = mounted
+ s.mountKubeconfigPath = mounted.ArgPath("kubeconfig", clientKubeconfig)
return nil
}
@@ -93,13 +82,13 @@
Kind: "KubeletConfiguration",
APIVersion: kubeletconfig.GroupName + "/v1beta1",
},
- TLSCertFile: s.KubeletDirectory.PKI.Certificate.FullPath(),
- TLSPrivateKeyFile: s.KubeletDirectory.PKI.Key.FullPath(),
+ TLSCertFile: s.mount.CertPath,
+ TLSPrivateKeyFile: s.mount.KeyPath,
TLSMinVersion: "VersionTLS13",
ClusterDNS: clusterDNS,
Authentication: kubeletconfig.KubeletAuthentication{
X509: kubeletconfig.KubeletX509Authentication{
- ClientCAFile: s.KubeletDirectory.PKI.CACertificate.FullPath(),
+ ClientCAFile: s.mount.CACertPath,
},
},
// TODO(q3k): move reconciler.False to a generic package, fix the following references.
@@ -123,6 +112,7 @@
if err := s.createCertificates(ctx); err != nil {
return fmt.Errorf("when creating certificates: %w", err)
}
+ defer s.mount.Close()
configRaw, err := json.Marshal(s.configure())
if err != nil {
@@ -137,7 +127,7 @@
fargs.FileOpt("--config", "config.json", configRaw),
"--container-runtime=remote",
fmt.Sprintf("--container-runtime-endpoint=unix://%s", s.EphemeralDirectory.Containerd.ClientSocket.FullPath()),
- fmt.Sprintf("--kubeconfig=%s", s.KubeletDirectory.Kubeconfig.FullPath()),
+ fmt.Sprintf("--kubeconfig=%s", s.mountKubeconfigPath),
fmt.Sprintf("--root-dir=%s", s.KubeletDirectory.FullPath()),
)
cmd.Env = []string{"PATH=/kubernetes/bin"}