blob: 2d678753216ab46f99e203cab56cf66d9831c205 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Serge Bazanskidbfc6382020-06-19 20:35:43 +02002// SPDX-License-Identifier: Apache-2.0
Serge Bazanskidbfc6382020-06-19 20:35:43 +02003
Tim Windelschmidt9f21f532024-05-07 15:14:20 +02004// package pki builds upon osbase/pki/ to provide an
Serge Bazanski9411f7c2021-03-10 13:12:53 +01005// etcd-backed implementation of all x509 PKI Certificates/CAs required to run
6// Kubernetes.
7// Most elements of the PKI are 'static' long-standing certificates/credentials
8// stored within etcd. However, this package also provides a method to generate
9// 'volatile' (in-memory) certificates/credentials for per-node Kubelets and
10// any client certificates.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020011package pki
12
13import (
Serge Bazanskie88ffe92023-03-21 13:38:46 +010014 "bytes"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020015 "context"
Serge Bazanskie88ffe92023-03-21 13:38:46 +010016 "crypto/ed25519"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020017 "crypto/rand"
18 "crypto/rsa"
19 "crypto/x509"
Serge Bazanskie88ffe92023-03-21 13:38:46 +010020 "encoding/hex"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020021 "encoding/pem"
22 "fmt"
23 "net"
24
Lorenz Brund13c1c62022-03-30 19:58:58 +020025 clientv3 "go.etcd.io/etcd/client/v3"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020026 "k8s.io/client-go/tools/clientcmd"
27 configapi "k8s.io/client-go/tools/clientcmd/api"
28
Serge Bazanski31370b02021-01-07 16:31:14 +010029 common "source.monogon.dev/metropolis/node"
Serge Bazanskie88ffe92023-03-21 13:38:46 +010030 "source.monogon.dev/metropolis/node/core/consensus"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020031 opki "source.monogon.dev/osbase/pki"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020032)
33
Serge Bazanski9411f7c2021-03-10 13:12:53 +010034// KubeCertificateName is an enum-like unique name of a static Kubernetes
35// certificate. The value of the name is used as the unique part of an etcd
36// path where the certificate and key are stored.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020037type KubeCertificateName string
38
39const (
40 // The main Kubernetes CA, used to authenticate API consumers, and servers.
41 IdCA KubeCertificateName = "id-ca"
42
43 // Kubernetes apiserver server certificate.
44 APIServer KubeCertificateName = "apiserver"
45
Serge Bazanski9411f7c2021-03-10 13:12:53 +010046 // APIServer client certificate used to authenticate to kubelets.
47 APIServerKubeletClient KubeCertificateName = "apiserver-kubelet-client"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020048
Serge Bazanski216fe7b2021-05-21 18:36:16 +020049 // Kubernetes Controller manager client certificate, used to authenticate
50 // to the apiserver.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020051 ControllerManagerClient KubeCertificateName = "controller-manager-client"
Serge Bazanski216fe7b2021-05-21 18:36:16 +020052 // Kubernetes Controller manager server certificate, used to run its HTTP
53 // server.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020054 ControllerManager KubeCertificateName = "controller-manager"
55
56 // Kubernetes Scheduler client certificate, used to authenticate to the apiserver.
57 SchedulerClient KubeCertificateName = "scheduler-client"
58 // Kubernetes scheduler server certificate, used to run its HTTP server.
59 Scheduler KubeCertificateName = "scheduler"
60
Serge Bazanski216fe7b2021-05-21 18:36:16 +020061 // Root-on-kube (system:masters) client certificate. Used to control the
62 // apiserver (and resources) by Metropolis internally.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020063 Master KubeCertificateName = "master"
64
65 // OpenAPI Kubernetes Aggregation CA.
Serge Bazanski216fe7b2021-05-21 18:36:16 +020066 // https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/#ca-reusage-and-conflicts
Serge Bazanskidbfc6382020-06-19 20:35:43 +020067 AggregationCA KubeCertificateName = "aggregation-ca"
68 FrontProxyClient KubeCertificateName = "front-proxy-client"
Lorenz Bruncc078df2021-12-23 11:51:55 +010069 // The Metropolis authentication proxy needs to be able to proxy requests
70 // and assert the established identity to the Kubernetes API server.
71 MetropolisAuthProxyClient KubeCertificateName = "metropolis-auth-proxy-client"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020072)
73
74const (
Serge Bazanski9411f7c2021-03-10 13:12:53 +010075 // etcdPrefix is where all the PKI data is stored in etcd.
76 etcdPrefix = "/kube-pki/"
Serge Bazanski216fe7b2021-05-21 18:36:16 +020077 // serviceAccountKeyName is the etcd path part that is used to store the
78 // ServiceAccount authentication secret. This is not a certificate, just an
79 // RSA key.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020080 serviceAccountKeyName = "service-account-privkey"
81)
82
Serge Bazanski9411f7c2021-03-10 13:12:53 +010083// PKI manages all PKI resources required to run Kubernetes on Metropolis. It
84// contains all static certificates, which can be retrieved, or be used to
85// generate Kubeconfigs from.
86type PKI struct {
87 namespace opki.Namespace
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020088 KV clientv3.KV
Serge Bazanski9411f7c2021-03-10 13:12:53 +010089 Certificates map[KubeCertificateName]*opki.Certificate
Serge Bazanskidbfc6382020-06-19 20:35:43 +020090}
91
Serge Bazanski6fdca3f2023-03-20 17:47:07 +010092func New(kv clientv3.KV, clusterDomain string) *PKI {
Serge Bazanski9411f7c2021-03-10 13:12:53 +010093 pki := PKI{
94 namespace: opki.Namespaced(etcdPrefix),
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020095 KV: kv,
Serge Bazanski9411f7c2021-03-10 13:12:53 +010096 Certificates: make(map[KubeCertificateName]*opki.Certificate),
Serge Bazanskidbfc6382020-06-19 20:35:43 +020097 }
98
Tim Windelschmidt38105672024-04-11 01:37:29 +020099 makeCert := func(i, name KubeCertificateName, template x509.Certificate) {
Serge Bazanski52538842021-08-11 16:22:41 +0200100 pki.Certificates[name] = &opki.Certificate{
101 Namespace: &pki.namespace,
102 Issuer: pki.Certificates[i],
103 Name: string(name),
104 Template: template,
105 Mode: opki.CertificateManaged,
106 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200107 }
108
Serge Bazanski52538842021-08-11 16:22:41 +0200109 pki.Certificates[IdCA] = &opki.Certificate{
110 Namespace: &pki.namespace,
111 Issuer: opki.SelfSigned,
112 Name: string(IdCA),
113 Template: opki.CA("Metropolis Kubernetes ID CA"),
114 Mode: opki.CertificateManaged,
115 }
Tim Windelschmidt38105672024-04-11 01:37:29 +0200116 makeCert(IdCA, APIServer, opki.Server(
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200117 []string{
118 "kubernetes",
119 "kubernetes.default",
120 "kubernetes.default.svc",
Lorenz Brun78cefca2022-06-20 12:59:55 +0000121 "kubernetes.default.svc." + clusterDomain,
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200122 "localhost",
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100123 // Domain used to access the apiserver by Kubernetes components themselves,
124 // without going over Kubernetes networking. This domain only lives as a set of
125 // entries in local hostsfiles.
126 "metropolis-kube-apiserver",
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200127 },
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200128 // TODO(q3k): add service network internal apiserver address
Lorenz Brun2f7e0a22023-06-22 16:56:13 +0200129 []net.IP{{10, 224, 0, 1}, {127, 0, 0, 1}},
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200130 ))
Tim Windelschmidt38105672024-04-11 01:37:29 +0200131 makeCert(IdCA, APIServerKubeletClient, opki.Client("metropolis:apiserver-kubelet-client", nil))
132 makeCert(IdCA, ControllerManagerClient, opki.Client("system:kube-controller-manager", nil))
133 makeCert(IdCA, ControllerManager, opki.Server([]string{"kube-controller-manager.local"}, nil))
134 makeCert(IdCA, SchedulerClient, opki.Client("system:kube-scheduler", nil))
135 makeCert(IdCA, Scheduler, opki.Server([]string{"kube-scheduler.local"}, nil))
136 makeCert(IdCA, Master, opki.Client("metropolis:master", []string{"system:masters"}))
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200137
Serge Bazanski52538842021-08-11 16:22:41 +0200138 pki.Certificates[AggregationCA] = &opki.Certificate{
139 Namespace: &pki.namespace,
140 Issuer: opki.SelfSigned,
141 Name: string(AggregationCA),
142 Template: opki.CA("Metropolis OpenAPI Aggregation CA"),
143 Mode: opki.CertificateManaged,
144 }
Tim Windelschmidt38105672024-04-11 01:37:29 +0200145 makeCert(AggregationCA, FrontProxyClient, opki.Client("front-proxy-client", nil))
146 makeCert(AggregationCA, MetropolisAuthProxyClient, opki.Client("metropolis-auth-proxy-client", nil))
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200147
148 return &pki
149}
150
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100151// FromLocalConsensus returns a PKI stored on the given local consensus instance,
152// in the correct etcd namespace.
153func FromLocalConsensus(ctx context.Context, svc consensus.ServiceHandle) (*PKI, error) {
154 // TODO(q3k): make this configurable
155 clusterDomain := "cluster.local"
156
157 cstW := svc.Watch()
158 defer cstW.Close()
159 cst, err := cstW.Get(ctx, consensus.FilterRunning)
160 if err != nil {
161 return nil, fmt.Errorf("waiting for local consensus: %w", err)
162 }
163 kkv, err := cst.KubernetesClient()
164 if err != nil {
165 return nil, fmt.Errorf("retrieving kubernetes client: %w", err)
166 }
167 pki := New(kkv, clusterDomain)
168 // Run EnsureAll ASAP to prevent race conditions between two kpki instances
169 // attempting to initialize the PKI data at the same time.
170 if err := pki.EnsureAll(ctx); err != nil {
171 return nil, fmt.Errorf("initial ensure failed: %w", err)
172 }
173 return pki, nil
174}
175
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200176// EnsureAll ensures that all static certificates (and the serviceaccount key)
177// are present on etcd.
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100178func (k *PKI) EnsureAll(ctx context.Context) error {
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200179 for n, v := range k.Certificates {
Serge Bazanski52538842021-08-11 16:22:41 +0200180 _, err := v.Ensure(ctx, k.KV)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200181 if err != nil {
182 return fmt.Errorf("could not ensure certificate %q exists: %w", n, err)
183 }
184 }
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200185 _, err := k.ServiceAccountKey(ctx)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200186 if err != nil {
187 return fmt.Errorf("could not ensure service account key exists: %w", err)
188 }
189 return nil
190}
191
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200192// Kubeconfig generates a kubeconfig blob for a given certificate name. The
193// same lifetime semantics as in .Certificate apply.
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100194func (k *PKI) Kubeconfig(ctx context.Context, name KubeCertificateName, endpoint KubernetesAPIEndpoint) ([]byte, error) {
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200195 c, ok := k.Certificates[name]
196 if !ok {
197 return nil, fmt.Errorf("no certificate %q", name)
198 }
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100199 return Kubeconfig(ctx, k.KV, c, endpoint)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200200}
201
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200202// Certificate retrieves an x509 DER-encoded (but not PEM-wrapped) key and
203// certificate for a given certificate name.
204// If the requested certificate is volatile, it will be created on demand.
205// Otherwise it will be created on etcd (if not present), and retrieved from
206// there.
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100207func (k *PKI) Certificate(ctx context.Context, name KubeCertificateName) (cert, key []byte, err error) {
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200208 c, ok := k.Certificates[name]
209 if !ok {
210 return nil, nil, fmt.Errorf("no certificate %q", name)
211 }
Serge Bazanski52538842021-08-11 16:22:41 +0200212 cert, err = c.Ensure(ctx, k.KV)
213 if err != nil {
214 return
215 }
216 key, err = c.PrivateKeyX509()
217 return
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200218}
219
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100220// A KubernetesAPIEndpoint describes where a Kubeconfig will make a client
221// attempt to connect to reach the Kubernetes apiservers(s).
222type KubernetesAPIEndpoint string
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200223
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100224var (
225 // KubernetesAPIEndpointForWorker points Kubernetes workers to connect to a
226 // locally-running apiproxy, which in turn loadbalances the connection to
227 // controller nodes running in the cluster.
228 KubernetesAPIEndpointForWorker = KubernetesAPIEndpoint(fmt.Sprintf("https://127.0.0.1:%d", common.KubernetesWorkerLocalAPIPort))
229 // KubernetesAPIEndpointForController points Kubernetes controllers to connect to
230 // the locally-running API server.
231 KubernetesAPIEndpointForController = KubernetesAPIEndpoint(fmt.Sprintf("https://127.0.0.1:%d", common.KubernetesAPIPort))
232)
233
234// KubeconfigRaw emits a Kubeconfig for a given set of certificates, private key,
235// and a KubernetesAPIEndpoint. This function does not rely on the rest of the
236// (K)PKI infrastructure.
237func KubeconfigRaw(cacert, cert []byte, priv ed25519.PrivateKey, endpoint KubernetesAPIEndpoint) ([]byte, error) {
238 caX, _ := x509.ParseCertificate(cacert)
239 certX, _ := x509.ParseCertificate(cert)
240 if err := certX.CheckSignatureFrom(caX); err != nil {
241 return nil, fmt.Errorf("given ca does not sign given cert")
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200242 }
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100243 pub1 := priv.Public().(ed25519.PublicKey)
244 pub2 := certX.PublicKey.(ed25519.PublicKey)
245 if !bytes.Equal(pub1, pub2) {
246 return nil, fmt.Errorf("given private key does not match given cert (cert: %s, key: %s)", hex.EncodeToString(pub2), hex.EncodeToString(pub1))
247 }
248
249 key, err := x509.MarshalPKCS8PrivateKey(priv)
Serge Bazanski52538842021-08-11 16:22:41 +0200250 if err != nil {
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100251 return nil, fmt.Errorf("could not marshal private key: %w", err)
Serge Bazanski52538842021-08-11 16:22:41 +0200252 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200253
254 kubeconfig := configapi.NewConfig()
255
256 cluster := configapi.NewCluster()
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200257
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100258 cluster.Server = string(endpoint)
259
260 cluster.CertificateAuthorityData = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cacert})
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200261 kubeconfig.Clusters["default"] = cluster
262
263 authInfo := configapi.NewAuthInfo()
264 authInfo.ClientCertificateData = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert})
265 authInfo.ClientKeyData = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key})
266 kubeconfig.AuthInfos["default"] = authInfo
267
268 ct := configapi.NewContext()
269 ct.Cluster = "default"
270 ct.AuthInfo = "default"
271 kubeconfig.Contexts["default"] = ct
272
273 kubeconfig.CurrentContext = "default"
274 return clientcmd.Write(*kubeconfig)
275}
276
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100277// Kubeconfig generates a kubeconfig blob for this certificate. The same
278// lifetime semantics as in .Ensure apply.
279func Kubeconfig(ctx context.Context, kv clientv3.KV, c *opki.Certificate, endpoint KubernetesAPIEndpoint) ([]byte, error) {
280 cert, err := c.Ensure(ctx, kv)
281 if err != nil {
282 return nil, fmt.Errorf("could not ensure certificate exists: %w", err)
283 }
284 if len(c.PrivateKey) != ed25519.PrivateKeySize {
285 return nil, fmt.Errorf("certificate has no associated private key")
286 }
287 ca, err := c.Issuer.CACertificate(ctx, kv)
288 if err != nil {
289 return nil, fmt.Errorf("could not get CA certificate: %w", err)
290 }
291
292 return KubeconfigRaw(ca, cert, c.PrivateKey, endpoint)
293}
294
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200295// ServiceAccountKey retrieves (and possibly generates and stores on etcd) the
296// Kubernetes service account key. The returned data is ready to be used by
297// Kubernetes components (in PKIX form).
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100298func (k *PKI) ServiceAccountKey(ctx context.Context) ([]byte, error) {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200299 // TODO(q3k): this should be abstracted away once we abstract away etcd
300 // access into a library with try-or-create semantics.
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100301 path := fmt.Sprintf("%s%s.der", etcdPrefix, serviceAccountKeyName)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200302
303 // Try loading key from etcd.
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200304 keyRes, err := k.KV.Get(ctx, path)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200305 if err != nil {
306 return nil, fmt.Errorf("failed to get key from etcd: %w", err)
307 }
308
309 if len(keyRes.Kvs) == 1 {
310 // Certificate and key exists in etcd, return that.
311 return keyRes.Kvs[0].Value, nil
312 }
313
314 // No key found - generate one.
315 keyRaw, err := rsa.GenerateKey(rand.Reader, 2048)
316 if err != nil {
317 panic(err)
318 }
319 key, err := x509.MarshalPKCS8PrivateKey(keyRaw)
320 if err != nil {
321 panic(err) // Always a programmer error
322 }
323
324 // Save to etcd.
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200325 _, err = k.KV.Put(ctx, path, string(key))
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200326 if err != nil {
Tim Windelschmidt096654a2024-04-18 23:10:19 +0200327 return nil, fmt.Errorf("failed to write newly generated key: %w", err)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200328 }
329 return key, nil
330}
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100331
Serge Bazanskife39cc22023-03-21 14:21:54 +0100332// Kubelet returns a pair of server/client ceritficates for the Kubelet to use.
333func (k *PKI) Kubelet(ctx context.Context, name string, pubkey ed25519.PublicKey) (server *opki.Certificate, client *opki.Certificate, err error) {
334 name = fmt.Sprintf("system:node:%s", name)
335 err = k.EnsureAll(ctx)
336 if err != nil {
337 return nil, nil, fmt.Errorf("could not ensure certificates exist: %w", err)
338 }
339 kubeCA := k.Certificates[IdCA]
340 serverName := fmt.Sprintf("kubelet-%s-server", name)
341 server = &opki.Certificate{
342 Name: serverName,
343 Namespace: &k.namespace,
344 Issuer: kubeCA,
345 Template: opki.Server([]string{name}, nil),
346 Mode: opki.CertificateExternal,
347 PublicKey: pubkey,
348 }
349 clientName := fmt.Sprintf("kubelet-%s-client", name)
350 client = &opki.Certificate{
351 Name: clientName,
352 Namespace: &k.namespace,
353 Issuer: kubeCA,
354 Template: opki.Client(name, []string{"system:nodes"}),
355 Mode: opki.CertificateExternal,
356 PublicKey: pubkey,
357 }
358 return server, client, nil
359}
360
361// CSIProvisioner returns a certificate to be used by the CSI provisioner running
362// on a worker node.
363func (k *PKI) CSIProvisioner(ctx context.Context, name string, pubkey ed25519.PublicKey) (client *opki.Certificate, err error) {
364 name = fmt.Sprintf("metropolis:csi-provisioner:%s", name)
365 err = k.EnsureAll(ctx)
366 if err != nil {
367 return nil, fmt.Errorf("could not ensure certificates exist: %w", err)
368 }
369 kubeCA := k.Certificates[IdCA]
370 clientName := fmt.Sprintf("csi-provisioner-%s", name)
371 client = &opki.Certificate{
372 Name: clientName,
373 Namespace: &k.namespace,
374 Issuer: kubeCA,
375 Template: opki.Client(name, []string{"metropolis:csi-provisioner"}),
376 Mode: opki.CertificateExternal,
377 PublicKey: pubkey,
378 }
379 return client, nil
380}
381
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100382// NetServices returns a certificate to be used by nfproxy and clusternet running
383// on a worker node.
384func (k *PKI) NetServices(ctx context.Context, name string, pubkey ed25519.PublicKey) (client *opki.Certificate, err error) {
385 name = fmt.Sprintf("metropolis:netservices:%s", name)
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100386 err = k.EnsureAll(ctx)
387 if err != nil {
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100388 return nil, fmt.Errorf("could not ensure certificates exist: %w", err)
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100389 }
390 kubeCA := k.Certificates[IdCA]
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100391 clientName := fmt.Sprintf("netservices-%s", name)
Serge Bazanski52538842021-08-11 16:22:41 +0200392 client = &opki.Certificate{
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100393 Name: clientName,
Serge Bazanski52538842021-08-11 16:22:41 +0200394 Namespace: &k.namespace,
395 Issuer: kubeCA,
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100396 Template: opki.Client(name, []string{"metropolis:netservices"}),
397 Mode: opki.CertificateExternal,
398 PublicKey: pubkey,
Serge Bazanski52538842021-08-11 16:22:41 +0200399 }
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100400 return client, nil
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100401}
402
403// VolatileClient returns a client certificate for Kubernetes clients to use.
404// The generated certificate will place the user in the given groups, and with
405// a given identiy as the certificate's CN.
406func (k *PKI) VolatileClient(ctx context.Context, identity string, groups []string) (*opki.Certificate, error) {
407 if err := k.EnsureAll(ctx); err != nil {
408 return nil, fmt.Errorf("could not ensure certificates exist: %w", err)
409 }
Serge Bazanski52538842021-08-11 16:22:41 +0200410 return &opki.Certificate{
411 Namespace: &k.namespace,
412 Issuer: k.Certificates[IdCA],
413 Template: opki.Client(identity, groups),
414 Mode: opki.CertificateEphemeral,
415 }, nil
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100416}