Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 1 | // 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 Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 17 | // package pki implements an x509 PKI (Public Key Infrastructure) system backed |
| 18 | // on etcd. |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 19 | package pki |
| 20 | |
| 21 | import ( |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 22 | "bytes" |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 23 | "context" |
| 24 | "crypto/ed25519" |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 25 | "crypto/rand" |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 26 | "crypto/x509" |
| 27 | "crypto/x509/pkix" |
| 28 | "encoding/pem" |
| 29 | "fmt" |
| 30 | "net" |
| 31 | |
Lorenz Brun | d13c1c6 | 2022-03-30 19:58:58 +0200 | [diff] [blame^] | 32 | clientv3 "go.etcd.io/etcd/client/v3" |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 33 | |
| 34 | "source.monogon.dev/metropolis/pkg/fileargs" |
| 35 | ) |
| 36 | |
| 37 | // Namespace represents some path in etcd where certificate/CA data will be |
| 38 | // stored. Creating a namespace via Namespaced then permits the consumer of |
| 39 | // this library to start creating certificates within this namespace. |
| 40 | type Namespace struct { |
| 41 | prefix string |
| 42 | } |
| 43 | |
Serge Bazanski | 216fe7b | 2021-05-21 18:36:16 +0200 | [diff] [blame] | 44 | // Namespaced creates a namespace for storing certificate data in etcd at a |
| 45 | // given 'path' prefix. |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 46 | func Namespaced(prefix string) Namespace { |
| 47 | return Namespace{ |
| 48 | prefix: prefix, |
| 49 | } |
| 50 | } |
| 51 | |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 52 | type CertificateMode int |
| 53 | |
| 54 | const ( |
| 55 | // CertificateManaged is a certificate whose key material is fully managed by |
| 56 | // the Certificate code. When set, PublicKey and PrivateKey must not be set by |
| 57 | // the user, and instead will be populated by the Ensure call. Name must be set, |
| 58 | // and will be used to store this Certificate and its keys within etcd. After |
| 59 | // the initial generation during Ensure, other Certificates with the same Name |
| 60 | // will be retrieved (including key material) from etcd. |
| 61 | CertificateManaged CertificateMode = iota |
| 62 | |
| 63 | // CertificateExternal is a certificate whose key material is not managed by |
| 64 | // Certificate or stored in etcd, but the X509 certificate itself is. PublicKey |
| 65 | // must be set while PrivateKey must not be set. Name must be set, and will be |
| 66 | // used to store the emitted X509 certificate in etcd on Ensure. After the |
| 67 | // initial generation during Ensure, other Certificates with the same Name will |
| 68 | // be retrieved (without key material) from etcd. |
| 69 | CertificateExternal |
| 70 | |
| 71 | // CertificateEphemeral is a certificate whose data (X509 certificate and |
| 72 | // possibly key material) is generated on demand each time Ensure is called. |
| 73 | // Nothing is stored in etcd or loaded from etcd. PrivateKey or PublicKey can be |
| 74 | // set, if both are nil then a new keypair will be generated. Name is ignored. |
| 75 | CertificateEphemeral |
| 76 | ) |
| 77 | |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 78 | // Certificate is the promise of a Certificate being available to the caller. |
| 79 | // In this case, Certificate refers to a pair of x509 certificate and |
| 80 | // corresponding private key. Certificates can be stored in etcd, and their |
| 81 | // issuers might also be store on etcd. As such, this type's methods contain |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 82 | // references to an etcd KV client. |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 83 | type Certificate struct { |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 84 | Namespace *Namespace |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 85 | |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 86 | // Issuer is the Issuer that will generate this certificate if one doesn't |
| 87 | // yet exist or etcd, or the requested certificate is ephemeral (not to be |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 88 | // stored on etcd). |
| 89 | Issuer Issuer |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 90 | // Name is a unique key for storing the certificate in etcd (if the requested |
| 91 | // certificate is not ephemeral). |
| 92 | Name string |
| 93 | // Template is an x509 certificate definition that will be used to generate |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 94 | // the certificate when issuing it. |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 95 | Template x509.Certificate |
| 96 | |
| 97 | // Mode in which this Certificate will operate. This influences the behaviour of |
| 98 | // the Ensure call. |
| 99 | Mode CertificateMode |
| 100 | |
| 101 | // PrivateKey is the private key for this Certificate. It should never be set by |
| 102 | // the user, and instead will be populated by the Ensure call for Managed |
| 103 | // Certificates. |
| 104 | PrivateKey ed25519.PrivateKey |
| 105 | |
| 106 | // PublicKey is the public key for this Certificate. It should only be set by |
| 107 | // the user for External or Ephemeral certificates, and will be populated by the |
| 108 | // next Ensure call if missing. |
| 109 | PublicKey ed25519.PublicKey |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | func (n *Namespace) etcdPath(f string, args ...interface{}) string { |
| 113 | return n.prefix + fmt.Sprintf(f, args...) |
| 114 | } |
| 115 | |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 116 | // Client makes a Kubernetes PKI-compatible client certificate template. |
| 117 | // Directly derived from Kubernetes PKI requirements documented at |
Serge Bazanski | 216fe7b | 2021-05-21 18:36:16 +0200 | [diff] [blame] | 118 | // https://kubernetes.io/docs/setup/best-practices/certificates/#configure-certificates-manually |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 119 | func Client(identity string, groups []string) x509.Certificate { |
| 120 | return x509.Certificate{ |
| 121 | Subject: pkix.Name{ |
| 122 | CommonName: identity, |
| 123 | Organization: groups, |
| 124 | }, |
| 125 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, |
| 126 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | // Server makes a Kubernetes PKI-compatible server certificate template. |
| 131 | func Server(dnsNames []string, ips []net.IP) x509.Certificate { |
| 132 | return x509.Certificate{ |
| 133 | Subject: pkix.Name{}, |
| 134 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, |
| 135 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, |
| 136 | DNSNames: dnsNames, |
| 137 | IPAddresses: ips, |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | // CA makes a Certificate that can sign other certificates. |
| 142 | func CA(cn string) x509.Certificate { |
| 143 | return x509.Certificate{ |
| 144 | Subject: pkix.Name{ |
| 145 | CommonName: cn, |
| 146 | }, |
| 147 | IsCA: true, |
| 148 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, |
| 149 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageOCSPSigning}, |
| 150 | } |
| 151 | } |
| 152 | |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 153 | // ensure returns a DER-encoded x509 certificate and internally encoded bare |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 154 | // ed25519 key for a given Certificate, in memory (if ephemeral), loading it |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 155 | // from etcd, or creating and saving it on etcd if needed. |
| 156 | // This function is safe to call in parallel from multiple etcd clients |
| 157 | // (including across machines), but it will error in case a concurrent |
| 158 | // certificate generation happens. These errors are, however, safe to retry - |
| 159 | // as long as all the certificate creators (ie., Metropolis nodes) run the same |
| 160 | // version of this code. |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 161 | func (c *Certificate) ensure(ctx context.Context, kv clientv3.KV) (cert []byte, err error) { |
| 162 | // Ensure key is available. |
| 163 | if err := c.ensureKey(ctx, kv); err != nil { |
| 164 | return nil, err |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 165 | } |
| 166 | |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 167 | switch c.Mode { |
| 168 | case CertificateEphemeral: |
| 169 | // TODO(q3k): cache internally? |
| 170 | cert, err = c.Issuer.Issue(ctx, c, kv) |
| 171 | if err != nil { |
| 172 | return nil, fmt.Errorf("failed to issue: %w", err) |
| 173 | } |
| 174 | return cert, nil |
| 175 | case CertificateManaged, CertificateExternal: |
| 176 | default: |
| 177 | return nil, fmt.Errorf("invalid certificate mode %v", c.Mode) |
| 178 | } |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 179 | |
Serge Bazanski | a41caac | 2021-08-12 17:00:55 +0200 | [diff] [blame] | 180 | if c.Name == "" { |
| 181 | if c.Mode == CertificateExternal { |
| 182 | return nil, fmt.Errorf("external certificate must have name set") |
| 183 | } else { |
| 184 | return nil, fmt.Errorf("managed certificate must have name set") |
| 185 | } |
| 186 | } |
| 187 | |
Serge Bazanski | 999e1db | 2021-11-30 20:37:38 +0100 | [diff] [blame] | 188 | certPath := c.Namespace.etcdPath("issued/%s-cert.der", c.Name) |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 189 | |
| 190 | // Try loading certificate from etcd. |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 191 | certRes, err := kv.Get(ctx, certPath) |
| 192 | if err != nil { |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 193 | return nil, fmt.Errorf("failed to get certificate from etcd: %w", err) |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 194 | } |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 195 | |
| 196 | if len(certRes.Kvs) == 1 { |
| 197 | certBytes := certRes.Kvs[0].Value |
| 198 | cert, err := x509.ParseCertificate(certBytes) |
| 199 | if err != nil { |
| 200 | return nil, fmt.Errorf("failed to parse certificate retrieved from etcd: %w", err) |
| 201 | } |
| 202 | pk, ok := cert.PublicKey.(ed25519.PublicKey) |
| 203 | if !ok { |
| 204 | return nil, fmt.Errorf("unexpected non-ed25519 certificate found in etcd") |
| 205 | } |
| 206 | if !bytes.Equal(pk, c.PublicKey) { |
| 207 | return nil, fmt.Errorf("certificate stored in etcd emitted for different public key") |
| 208 | } |
| 209 | // TODO(q3k): ensure issuer and template haven't changed |
| 210 | return certBytes, nil |
| 211 | } |
| 212 | |
| 213 | // No certificate found - issue one and save to etcd. |
| 214 | cert, err = c.Issuer.Issue(ctx, c, kv) |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 215 | if err != nil { |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 216 | return nil, fmt.Errorf("failed to issue: %w", err) |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 217 | } |
| 218 | |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 219 | res, err := kv.Txn(ctx). |
| 220 | If( |
| 221 | clientv3.Compare(clientv3.CreateRevision(certPath), "=", 0), |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 222 | ). |
| 223 | Then( |
| 224 | clientv3.OpPut(certPath, string(cert)), |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 225 | ).Commit() |
| 226 | if err != nil { |
| 227 | err = fmt.Errorf("failed to write newly issued certificate: %w", err) |
| 228 | } else if !res.Succeeded { |
| 229 | err = fmt.Errorf("certificate issuance transaction failed: concurrent write") |
| 230 | } |
| 231 | |
| 232 | return |
| 233 | } |
| 234 | |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 235 | // ensureKey retrieves or creates PublicKey as needed based on the Certificate |
| 236 | // Mode. For Managed Certificates and Ephemeral Certificates with no PrivateKey |
| 237 | // it will also populate PrivateKay. |
| 238 | func (c *Certificate) ensureKey(ctx context.Context, kv clientv3.KV) error { |
| 239 | // If we have a public key then we're all set. |
| 240 | if c.PublicKey != nil { |
| 241 | return nil |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 242 | } |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 243 | |
| 244 | // For ephemeral keys, we just generate them. |
| 245 | // For external keys, we can't do anything - not having the keys set means |
| 246 | // a programming error. |
| 247 | |
| 248 | switch c.Mode { |
| 249 | case CertificateEphemeral: |
| 250 | pub, priv, err := ed25519.GenerateKey(rand.Reader) |
| 251 | if err != nil { |
| 252 | return fmt.Errorf("when generating ephemeral key: %w", err) |
| 253 | } |
| 254 | c.PublicKey = pub |
| 255 | c.PrivateKey = priv |
| 256 | return nil |
| 257 | case CertificateExternal: |
| 258 | if c.PrivateKey != nil { |
| 259 | // We prohibit having PrivateKey set in External Certificates to simplify the |
| 260 | // different logic paths this library implements. Being able to assume External |
| 261 | // == PublicKey only makes things easier elsewhere. |
| 262 | return fmt.Errorf("external certificate must not have PrivateKey set") |
| 263 | } |
| 264 | return fmt.Errorf("external certificate must have PublicKey set") |
| 265 | case CertificateManaged: |
| 266 | default: |
| 267 | return fmt.Errorf("invalid certificate mode %v", c.Mode) |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 268 | } |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 269 | |
| 270 | // For managed keys, synchronize with etcd. |
| 271 | if c.Name == "" { |
| 272 | return fmt.Errorf("managed certificate must have Name set") |
| 273 | } |
| 274 | |
| 275 | // First, try loading. |
Serge Bazanski | 999e1db | 2021-11-30 20:37:38 +0100 | [diff] [blame] | 276 | privPath := c.Namespace.etcdPath("keys/%s-privkey.bin", c.Name) |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 277 | privRes, err := kv.Get(ctx, privPath) |
| 278 | if err != nil { |
| 279 | return fmt.Errorf("failed to get private key from etcd: %w", err) |
| 280 | } |
| 281 | if len(privRes.Kvs) == 1 { |
| 282 | privBytes := privRes.Kvs[0].Value |
| 283 | if len(privBytes) != ed25519.PrivateKeySize { |
| 284 | return fmt.Errorf("stored private key has invalid size") |
| 285 | } |
| 286 | c.PrivateKey = privBytes |
| 287 | c.PublicKey = c.PrivateKey.Public().(ed25519.PublicKey) |
| 288 | return nil |
| 289 | } |
| 290 | |
| 291 | // No key in etcd? Generate and save. |
| 292 | pub, priv, err := ed25519.GenerateKey(rand.Reader) |
| 293 | if err != nil { |
| 294 | return fmt.Errorf("while generating keypair: %w", err) |
| 295 | } |
| 296 | |
| 297 | res, err := kv.Txn(ctx). |
| 298 | If( |
| 299 | clientv3.Compare(clientv3.CreateRevision(privPath), "=", 0), |
| 300 | ). |
| 301 | Then( |
| 302 | clientv3.OpPut(privPath, string(priv)), |
| 303 | ).Commit() |
| 304 | if err != nil { |
| 305 | return fmt.Errorf("failed to write newly generated keypair: %w", err) |
| 306 | } else if !res.Succeeded { |
| 307 | return fmt.Errorf("key generation transaction failed: concurrent write") |
| 308 | } |
| 309 | |
Serge Bazanski | 999e1db | 2021-11-30 20:37:38 +0100 | [diff] [blame] | 310 | crlPath := c.crlPath() |
| 311 | emptyCRL, err := c.makeCRL(ctx, kv, nil) |
| 312 | if err != nil { |
| 313 | return fmt.Errorf("failed to generate empty CRL: %w", err) |
| 314 | } |
| 315 | |
| 316 | // Also attempt to emit an empty CRL if one doesn't exist yet. |
| 317 | _, err = kv.Txn(ctx). |
| 318 | If( |
| 319 | clientv3.Compare(clientv3.CreateRevision(crlPath), "=", 0), |
| 320 | ). |
| 321 | Then( |
| 322 | clientv3.OpPut(crlPath, string(emptyCRL)), |
| 323 | ).Commit() |
| 324 | if err != nil { |
| 325 | return fmt.Errorf("failed to upsert empty CRL") |
| 326 | } |
| 327 | |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 328 | c.PrivateKey = priv |
| 329 | c.PublicKey = pub |
| 330 | return nil |
| 331 | } |
| 332 | |
| 333 | // Ensure returns an x509 DER-encoded (but not PEM-encoded) certificate for a |
| 334 | // given Certificate. |
| 335 | // |
| 336 | // If the Certificate is ephemeral, each call to Ensure will cause a new |
| 337 | // certificate to be generated. Otherwise, it will be retrieved from etcd, or |
| 338 | // generated and stored there if needed. |
| 339 | func (c *Certificate) Ensure(ctx context.Context, kv clientv3.KV) (cert []byte, err error) { |
| 340 | return c.ensure(ctx, kv) |
| 341 | } |
| 342 | |
| 343 | func (c *Certificate) PrivateKeyX509() ([]byte, error) { |
| 344 | if c.PrivateKey == nil { |
| 345 | return nil, fmt.Errorf("certificate has no private key") |
| 346 | } |
| 347 | key, err := x509.MarshalPKCS8PrivateKey(c.PrivateKey) |
| 348 | if err != nil { |
| 349 | return nil, fmt.Errorf("could not marshal private key (data corruption?): %w", err) |
| 350 | } |
| 351 | return key, nil |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 352 | } |
| 353 | |
| 354 | // FilesystemCertificate is a fileargs.FileArgs wrapper which will contain PEM |
| 355 | // encoded certificate material when Mounted. This construct is useful when |
| 356 | // dealing with services that want to access etcd-backed certificates as files |
| 357 | // available locally. |
| 358 | // Paths to the available files are considered opaque and should not be leaked |
| 359 | // outside of the struct. Further restrictions on access to these files might |
| 360 | // be imposed in the future. |
| 361 | type FilesystemCertificate struct { |
| 362 | *fileargs.FileArgs |
| 363 | // CACertPath is the full path at which the CA certificate is available. |
| 364 | // Read only. |
| 365 | CACertPath string |
| 366 | // CertPath is the full path at which the certificate is available. Read |
| 367 | // only. |
| 368 | CertPath string |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 369 | // KeyPath is the full path at which the private key is available, or an empty |
| 370 | // string if the Certificate was created without a private key. Read only. |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 371 | KeyPath string |
| 372 | } |
| 373 | |
| 374 | // Mount returns a locally mounted FilesystemCertificate for this Certificate, |
| 375 | // which allows services to access this Certificate via local filesystem |
| 376 | // access. |
| 377 | // The embeded fileargs.FileArgs can also be used to add additional file-backed |
| 378 | // data under the same mount by calling ArgPath. |
| 379 | // The returned FilesystemCertificate must be Closed in order to prevent a |
| 380 | // system mount leak. |
| 381 | func (c *Certificate) Mount(ctx context.Context, kv clientv3.KV) (*FilesystemCertificate, error) { |
| 382 | fa, err := fileargs.New() |
| 383 | if err != nil { |
| 384 | return nil, fmt.Errorf("when creating fileargs mount: %w", err) |
| 385 | } |
| 386 | fs := &FilesystemCertificate{FileArgs: fa} |
| 387 | |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 388 | cert, err := c.Ensure(ctx, kv) |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 389 | if err != nil { |
| 390 | return nil, fmt.Errorf("when issuing certificate: %w", err) |
| 391 | } |
| 392 | |
| 393 | cacert, err := c.Issuer.CACertificate(ctx, kv) |
| 394 | if err != nil { |
| 395 | return nil, fmt.Errorf("when getting issuer CA: %w", err) |
| 396 | } |
| 397 | // cacert will be null if this is a self-signed certificate. |
| 398 | if cacert == nil { |
| 399 | cacert = cert |
| 400 | } |
| 401 | |
| 402 | fs.CACertPath = fs.ArgPath("ca.crt", pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cacert})) |
| 403 | fs.CertPath = fs.ArgPath("tls.crt", pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert})) |
Serge Bazanski | 5253884 | 2021-08-11 16:22:41 +0200 | [diff] [blame] | 404 | if c.PrivateKey != nil { |
| 405 | key, err := c.PrivateKeyX509() |
| 406 | if err != nil { |
| 407 | return nil, err |
| 408 | } |
| 409 | fs.KeyPath = fs.ArgPath("tls.key", pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key})) |
| 410 | } |
Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 411 | |
| 412 | return fs, nil |
| 413 | } |