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