blob: dbebf73c0ad2de523136e9e90d3df4c15ced9b01 [file] [log] [blame]
Serge Bazanskidbfc6382020-06-19 20:35:43 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Serge Bazanski9411f7c2021-03-10 13:12:53 +010017// package pki builds upon metropolis/pkg/pki/ to provide an
18// etcd-backed implementation of all x509 PKI Certificates/CAs required to run
19// Kubernetes.
20// Most elements of the PKI are 'static' long-standing certificates/credentials
21// stored within etcd. However, this package also provides a method to generate
22// 'volatile' (in-memory) certificates/credentials for per-node Kubelets and
23// any client certificates.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020024package pki
25
26import (
Serge Bazanskie88ffe92023-03-21 13:38:46 +010027 "bytes"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020028 "context"
Serge Bazanskie88ffe92023-03-21 13:38:46 +010029 "crypto/ed25519"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020030 "crypto/rand"
31 "crypto/rsa"
32 "crypto/x509"
Serge Bazanskie88ffe92023-03-21 13:38:46 +010033 "encoding/hex"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020034 "encoding/pem"
35 "fmt"
36 "net"
37
Lorenz Brund13c1c62022-03-30 19:58:58 +020038 clientv3 "go.etcd.io/etcd/client/v3"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020039 "k8s.io/client-go/tools/clientcmd"
40 configapi "k8s.io/client-go/tools/clientcmd/api"
41
Serge Bazanski31370b02021-01-07 16:31:14 +010042 common "source.monogon.dev/metropolis/node"
Serge Bazanskie88ffe92023-03-21 13:38:46 +010043 "source.monogon.dev/metropolis/node/core/consensus"
Serge Bazanski9411f7c2021-03-10 13:12:53 +010044 opki "source.monogon.dev/metropolis/pkg/pki"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020045)
46
Serge Bazanski9411f7c2021-03-10 13:12:53 +010047// KubeCertificateName is an enum-like unique name of a static Kubernetes
48// certificate. The value of the name is used as the unique part of an etcd
49// path where the certificate and key are stored.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020050type KubeCertificateName string
51
52const (
53 // The main Kubernetes CA, used to authenticate API consumers, and servers.
54 IdCA KubeCertificateName = "id-ca"
55
56 // Kubernetes apiserver server certificate.
57 APIServer KubeCertificateName = "apiserver"
58
Serge Bazanski9411f7c2021-03-10 13:12:53 +010059 // APIServer client certificate used to authenticate to kubelets.
60 APIServerKubeletClient KubeCertificateName = "apiserver-kubelet-client"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020061
Serge Bazanski216fe7b2021-05-21 18:36:16 +020062 // Kubernetes Controller manager client certificate, used to authenticate
63 // to the apiserver.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020064 ControllerManagerClient KubeCertificateName = "controller-manager-client"
Serge Bazanski216fe7b2021-05-21 18:36:16 +020065 // Kubernetes Controller manager server certificate, used to run its HTTP
66 // server.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020067 ControllerManager KubeCertificateName = "controller-manager"
68
69 // Kubernetes Scheduler client certificate, used to authenticate to the apiserver.
70 SchedulerClient KubeCertificateName = "scheduler-client"
71 // Kubernetes scheduler server certificate, used to run its HTTP server.
72 Scheduler KubeCertificateName = "scheduler"
73
Serge Bazanski216fe7b2021-05-21 18:36:16 +020074 // Root-on-kube (system:masters) client certificate. Used to control the
75 // apiserver (and resources) by Metropolis internally.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020076 Master KubeCertificateName = "master"
77
78 // OpenAPI Kubernetes Aggregation CA.
Serge Bazanski216fe7b2021-05-21 18:36:16 +020079 // https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/#ca-reusage-and-conflicts
Serge Bazanskidbfc6382020-06-19 20:35:43 +020080 AggregationCA KubeCertificateName = "aggregation-ca"
81 FrontProxyClient KubeCertificateName = "front-proxy-client"
Lorenz Bruncc078df2021-12-23 11:51:55 +010082 // The Metropolis authentication proxy needs to be able to proxy requests
83 // and assert the established identity to the Kubernetes API server.
84 MetropolisAuthProxyClient KubeCertificateName = "metropolis-auth-proxy-client"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020085)
86
87const (
Serge Bazanski9411f7c2021-03-10 13:12:53 +010088 // etcdPrefix is where all the PKI data is stored in etcd.
89 etcdPrefix = "/kube-pki/"
Serge Bazanski216fe7b2021-05-21 18:36:16 +020090 // serviceAccountKeyName is the etcd path part that is used to store the
91 // ServiceAccount authentication secret. This is not a certificate, just an
92 // RSA key.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020093 serviceAccountKeyName = "service-account-privkey"
94)
95
Serge Bazanski9411f7c2021-03-10 13:12:53 +010096// PKI manages all PKI resources required to run Kubernetes on Metropolis. It
97// contains all static certificates, which can be retrieved, or be used to
98// generate Kubeconfigs from.
99type PKI struct {
100 namespace opki.Namespace
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200101 KV clientv3.KV
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100102 Certificates map[KubeCertificateName]*opki.Certificate
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200103}
104
Serge Bazanski6fdca3f2023-03-20 17:47:07 +0100105func New(kv clientv3.KV, clusterDomain string) *PKI {
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100106 pki := PKI{
107 namespace: opki.Namespaced(etcdPrefix),
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200108 KV: kv,
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100109 Certificates: make(map[KubeCertificateName]*opki.Certificate),
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200110 }
111
112 make := func(i, name KubeCertificateName, template x509.Certificate) {
Serge Bazanski52538842021-08-11 16:22:41 +0200113 pki.Certificates[name] = &opki.Certificate{
114 Namespace: &pki.namespace,
115 Issuer: pki.Certificates[i],
116 Name: string(name),
117 Template: template,
118 Mode: opki.CertificateManaged,
119 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200120 }
121
Serge Bazanski52538842021-08-11 16:22:41 +0200122 pki.Certificates[IdCA] = &opki.Certificate{
123 Namespace: &pki.namespace,
124 Issuer: opki.SelfSigned,
125 Name: string(IdCA),
126 Template: opki.CA("Metropolis Kubernetes ID CA"),
127 Mode: opki.CertificateManaged,
128 }
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100129 make(IdCA, APIServer, opki.Server(
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200130 []string{
131 "kubernetes",
132 "kubernetes.default",
133 "kubernetes.default.svc",
Lorenz Brun78cefca2022-06-20 12:59:55 +0000134 "kubernetes.default.svc." + clusterDomain,
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200135 "localhost",
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100136 // Domain used to access the apiserver by Kubernetes components themselves,
137 // without going over Kubernetes networking. This domain only lives as a set of
138 // entries in local hostsfiles.
139 "metropolis-kube-apiserver",
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200140 },
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200141 // TODO(q3k): add service network internal apiserver address
142 []net.IP{{10, 0, 255, 1}, {127, 0, 0, 1}},
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200143 ))
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100144 make(IdCA, APIServerKubeletClient, opki.Client("metropolis:apiserver-kubelet-client", nil))
145 make(IdCA, ControllerManagerClient, opki.Client("system:kube-controller-manager", nil))
146 make(IdCA, ControllerManager, opki.Server([]string{"kube-controller-manager.local"}, nil))
147 make(IdCA, SchedulerClient, opki.Client("system:kube-scheduler", nil))
148 make(IdCA, Scheduler, opki.Server([]string{"kube-scheduler.local"}, nil))
149 make(IdCA, Master, opki.Client("metropolis:master", []string{"system:masters"}))
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200150
Serge Bazanski52538842021-08-11 16:22:41 +0200151 pki.Certificates[AggregationCA] = &opki.Certificate{
152 Namespace: &pki.namespace,
153 Issuer: opki.SelfSigned,
154 Name: string(AggregationCA),
155 Template: opki.CA("Metropolis OpenAPI Aggregation CA"),
156 Mode: opki.CertificateManaged,
157 }
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100158 make(AggregationCA, FrontProxyClient, opki.Client("front-proxy-client", nil))
Lorenz Bruncc078df2021-12-23 11:51:55 +0100159 make(AggregationCA, MetropolisAuthProxyClient, opki.Client("metropolis-auth-proxy-client", nil))
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200160
161 return &pki
162}
163
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100164// FromLocalConsensus returns a PKI stored on the given local consensus instance,
165// in the correct etcd namespace.
166func FromLocalConsensus(ctx context.Context, svc consensus.ServiceHandle) (*PKI, error) {
167 // TODO(q3k): make this configurable
168 clusterDomain := "cluster.local"
169
170 cstW := svc.Watch()
171 defer cstW.Close()
172 cst, err := cstW.Get(ctx, consensus.FilterRunning)
173 if err != nil {
174 return nil, fmt.Errorf("waiting for local consensus: %w", err)
175 }
176 kkv, err := cst.KubernetesClient()
177 if err != nil {
178 return nil, fmt.Errorf("retrieving kubernetes client: %w", err)
179 }
180 pki := New(kkv, clusterDomain)
181 // Run EnsureAll ASAP to prevent race conditions between two kpki instances
182 // attempting to initialize the PKI data at the same time.
183 if err := pki.EnsureAll(ctx); err != nil {
184 return nil, fmt.Errorf("initial ensure failed: %w", err)
185 }
186 return pki, nil
187}
188
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200189// EnsureAll ensures that all static certificates (and the serviceaccount key)
190// are present on etcd.
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100191func (k *PKI) EnsureAll(ctx context.Context) error {
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200192 for n, v := range k.Certificates {
Serge Bazanski52538842021-08-11 16:22:41 +0200193 _, err := v.Ensure(ctx, k.KV)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200194 if err != nil {
195 return fmt.Errorf("could not ensure certificate %q exists: %w", n, err)
196 }
197 }
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200198 _, err := k.ServiceAccountKey(ctx)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200199 if err != nil {
200 return fmt.Errorf("could not ensure service account key exists: %w", err)
201 }
202 return nil
203}
204
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200205// Kubeconfig generates a kubeconfig blob for a given certificate name. The
206// same lifetime semantics as in .Certificate apply.
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100207func (k *PKI) Kubeconfig(ctx context.Context, name KubeCertificateName, endpoint KubernetesAPIEndpoint) ([]byte, error) {
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200208 c, ok := k.Certificates[name]
209 if !ok {
210 return nil, fmt.Errorf("no certificate %q", name)
211 }
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100212 return Kubeconfig(ctx, k.KV, c, endpoint)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200213}
214
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200215// Certificate retrieves an x509 DER-encoded (but not PEM-wrapped) key and
216// certificate for a given certificate name.
217// If the requested certificate is volatile, it will be created on demand.
218// Otherwise it will be created on etcd (if not present), and retrieved from
219// there.
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100220func (k *PKI) Certificate(ctx context.Context, name KubeCertificateName) (cert, key []byte, err error) {
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200221 c, ok := k.Certificates[name]
222 if !ok {
223 return nil, nil, fmt.Errorf("no certificate %q", name)
224 }
Serge Bazanski52538842021-08-11 16:22:41 +0200225 cert, err = c.Ensure(ctx, k.KV)
226 if err != nil {
227 return
228 }
229 key, err = c.PrivateKeyX509()
230 return
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200231}
232
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100233// A KubernetesAPIEndpoint describes where a Kubeconfig will make a client
234// attempt to connect to reach the Kubernetes apiservers(s).
235type KubernetesAPIEndpoint string
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200236
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100237var (
238 // KubernetesAPIEndpointForWorker points Kubernetes workers to connect to a
239 // locally-running apiproxy, which in turn loadbalances the connection to
240 // controller nodes running in the cluster.
241 KubernetesAPIEndpointForWorker = KubernetesAPIEndpoint(fmt.Sprintf("https://127.0.0.1:%d", common.KubernetesWorkerLocalAPIPort))
242 // KubernetesAPIEndpointForController points Kubernetes controllers to connect to
243 // the locally-running API server.
244 KubernetesAPIEndpointForController = KubernetesAPIEndpoint(fmt.Sprintf("https://127.0.0.1:%d", common.KubernetesAPIPort))
245)
246
247// KubeconfigRaw emits a Kubeconfig for a given set of certificates, private key,
248// and a KubernetesAPIEndpoint. This function does not rely on the rest of the
249// (K)PKI infrastructure.
250func KubeconfigRaw(cacert, cert []byte, priv ed25519.PrivateKey, endpoint KubernetesAPIEndpoint) ([]byte, error) {
251 caX, _ := x509.ParseCertificate(cacert)
252 certX, _ := x509.ParseCertificate(cert)
253 if err := certX.CheckSignatureFrom(caX); err != nil {
254 return nil, fmt.Errorf("given ca does not sign given cert")
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200255 }
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100256 pub1 := priv.Public().(ed25519.PublicKey)
257 pub2 := certX.PublicKey.(ed25519.PublicKey)
258 if !bytes.Equal(pub1, pub2) {
259 return nil, fmt.Errorf("given private key does not match given cert (cert: %s, key: %s)", hex.EncodeToString(pub2), hex.EncodeToString(pub1))
260 }
261
262 key, err := x509.MarshalPKCS8PrivateKey(priv)
Serge Bazanski52538842021-08-11 16:22:41 +0200263 if err != nil {
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100264 return nil, fmt.Errorf("could not marshal private key: %w", err)
Serge Bazanski52538842021-08-11 16:22:41 +0200265 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200266
267 kubeconfig := configapi.NewConfig()
268
269 cluster := configapi.NewCluster()
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200270
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100271 cluster.Server = string(endpoint)
272
273 cluster.CertificateAuthorityData = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cacert})
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200274 kubeconfig.Clusters["default"] = cluster
275
276 authInfo := configapi.NewAuthInfo()
277 authInfo.ClientCertificateData = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert})
278 authInfo.ClientKeyData = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key})
279 kubeconfig.AuthInfos["default"] = authInfo
280
281 ct := configapi.NewContext()
282 ct.Cluster = "default"
283 ct.AuthInfo = "default"
284 kubeconfig.Contexts["default"] = ct
285
286 kubeconfig.CurrentContext = "default"
287 return clientcmd.Write(*kubeconfig)
288}
289
Serge Bazanskie88ffe92023-03-21 13:38:46 +0100290// Kubeconfig generates a kubeconfig blob for this certificate. The same
291// lifetime semantics as in .Ensure apply.
292func Kubeconfig(ctx context.Context, kv clientv3.KV, c *opki.Certificate, endpoint KubernetesAPIEndpoint) ([]byte, error) {
293 cert, err := c.Ensure(ctx, kv)
294 if err != nil {
295 return nil, fmt.Errorf("could not ensure certificate exists: %w", err)
296 }
297 if len(c.PrivateKey) != ed25519.PrivateKeySize {
298 return nil, fmt.Errorf("certificate has no associated private key")
299 }
300 ca, err := c.Issuer.CACertificate(ctx, kv)
301 if err != nil {
302 return nil, fmt.Errorf("could not get CA certificate: %w", err)
303 }
304
305 return KubeconfigRaw(ca, cert, c.PrivateKey, endpoint)
306}
307
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200308// ServiceAccountKey retrieves (and possibly generates and stores on etcd) the
309// Kubernetes service account key. The returned data is ready to be used by
310// Kubernetes components (in PKIX form).
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100311func (k *PKI) ServiceAccountKey(ctx context.Context) ([]byte, error) {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200312 // TODO(q3k): this should be abstracted away once we abstract away etcd
313 // access into a library with try-or-create semantics.
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100314 path := fmt.Sprintf("%s%s.der", etcdPrefix, serviceAccountKeyName)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200315
316 // Try loading key from etcd.
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200317 keyRes, err := k.KV.Get(ctx, path)
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200318 if err != nil {
319 return nil, fmt.Errorf("failed to get key from etcd: %w", err)
320 }
321
322 if len(keyRes.Kvs) == 1 {
323 // Certificate and key exists in etcd, return that.
324 return keyRes.Kvs[0].Value, nil
325 }
326
327 // No key found - generate one.
328 keyRaw, err := rsa.GenerateKey(rand.Reader, 2048)
329 if err != nil {
330 panic(err)
331 }
332 key, err := x509.MarshalPKCS8PrivateKey(keyRaw)
333 if err != nil {
334 panic(err) // Always a programmer error
335 }
336
337 // Save to etcd.
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200338 _, err = k.KV.Put(ctx, path, string(key))
Serge Bazanskidbfc6382020-06-19 20:35:43 +0200339 if err != nil {
340 err = fmt.Errorf("failed to write newly generated key: %w", err)
341 }
342 return key, nil
343}
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100344
Serge Bazanskife39cc22023-03-21 14:21:54 +0100345// Kubelet returns a pair of server/client ceritficates for the Kubelet to use.
346func (k *PKI) Kubelet(ctx context.Context, name string, pubkey ed25519.PublicKey) (server *opki.Certificate, client *opki.Certificate, err error) {
347 name = fmt.Sprintf("system:node:%s", name)
348 err = k.EnsureAll(ctx)
349 if err != nil {
350 return nil, nil, fmt.Errorf("could not ensure certificates exist: %w", err)
351 }
352 kubeCA := k.Certificates[IdCA]
353 serverName := fmt.Sprintf("kubelet-%s-server", name)
354 server = &opki.Certificate{
355 Name: serverName,
356 Namespace: &k.namespace,
357 Issuer: kubeCA,
358 Template: opki.Server([]string{name}, nil),
359 Mode: opki.CertificateExternal,
360 PublicKey: pubkey,
361 }
362 clientName := fmt.Sprintf("kubelet-%s-client", name)
363 client = &opki.Certificate{
364 Name: clientName,
365 Namespace: &k.namespace,
366 Issuer: kubeCA,
367 Template: opki.Client(name, []string{"system:nodes"}),
368 Mode: opki.CertificateExternal,
369 PublicKey: pubkey,
370 }
371 return server, client, nil
372}
373
374// CSIProvisioner returns a certificate to be used by the CSI provisioner running
375// on a worker node.
376func (k *PKI) CSIProvisioner(ctx context.Context, name string, pubkey ed25519.PublicKey) (client *opki.Certificate, err error) {
377 name = fmt.Sprintf("metropolis:csi-provisioner:%s", name)
378 err = k.EnsureAll(ctx)
379 if err != nil {
380 return nil, fmt.Errorf("could not ensure certificates exist: %w", err)
381 }
382 kubeCA := k.Certificates[IdCA]
383 clientName := fmt.Sprintf("csi-provisioner-%s", name)
384 client = &opki.Certificate{
385 Name: clientName,
386 Namespace: &k.namespace,
387 Issuer: kubeCA,
388 Template: opki.Client(name, []string{"metropolis:csi-provisioner"}),
389 Mode: opki.CertificateExternal,
390 PublicKey: pubkey,
391 }
392 return client, nil
393}
394
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100395// VolatileKubelet returns a pair of server/client ceritficates for the Kubelet
Serge Bazanski52538842021-08-11 16:22:41 +0200396// to use. The certificates are ephemeral, meaning they are not stored in etcd,
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100397// and instead are regenerated any time this function is called.
398func (k *PKI) VolatileKubelet(ctx context.Context, name string) (server *opki.Certificate, client *opki.Certificate, err error) {
399 name = fmt.Sprintf("system:node:%s", name)
400 err = k.EnsureAll(ctx)
401 if err != nil {
Serge Bazanski52538842021-08-11 16:22:41 +0200402 return nil, nil, fmt.Errorf("could not ensure certificates exist: %w", err)
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100403 }
404 kubeCA := k.Certificates[IdCA]
Serge Bazanski52538842021-08-11 16:22:41 +0200405 server = &opki.Certificate{
406 Namespace: &k.namespace,
407 Issuer: kubeCA,
408 Template: opki.Server([]string{name}, nil),
409 Mode: opki.CertificateEphemeral,
410 }
411 client = &opki.Certificate{
412 Namespace: &k.namespace,
413 Issuer: kubeCA,
414 Template: opki.Client(name, []string{"system:nodes"}),
415 Mode: opki.CertificateEphemeral,
416 }
417 return server, client, nil
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100418}
419
420// VolatileClient returns a client certificate for Kubernetes clients to use.
421// The generated certificate will place the user in the given groups, and with
422// a given identiy as the certificate's CN.
423func (k *PKI) VolatileClient(ctx context.Context, identity string, groups []string) (*opki.Certificate, error) {
424 if err := k.EnsureAll(ctx); err != nil {
425 return nil, fmt.Errorf("could not ensure certificates exist: %w", err)
426 }
Serge Bazanski52538842021-08-11 16:22:41 +0200427 return &opki.Certificate{
428 Namespace: &k.namespace,
429 Issuer: k.Certificates[IdCA],
430 Template: opki.Client(identity, groups),
431 Mode: opki.CertificateEphemeral,
432 }, nil
Serge Bazanski9411f7c2021-03-10 13:12:53 +0100433}