18699: boot.Supervisor support for multi-cluster config file.
[arvados.git] / lib / boot / cert.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package boot
6
7 import (
8         "context"
9         "fmt"
10         "io/ioutil"
11         "net"
12         "os"
13         "path/filepath"
14 )
15
16 // Create a root CA key and use it to make a new server
17 // certificate+key pair.
18 //
19 // In future we'll make one root CA key per host instead of one per
20 // cluster, so it only needs to be imported to a browser once for
21 // ongoing dev/test usage.
22 type createCertificates struct{}
23
24 func (createCertificates) String() string {
25         return "certificates"
26 }
27
28 func (createCertificates) Run(ctx context.Context, fail func(error), super *Supervisor) error {
29         // Generate root key
30         err := super.RunProgram(ctx, super.tempdir, runOptions{}, "openssl", "genrsa", "-out", "rootCA.key", "4096")
31         if err != nil {
32                 return err
33         }
34         // Generate a self-signed root certificate
35         err = super.RunProgram(ctx, super.tempdir, runOptions{}, "openssl", "req", "-x509", "-new", "-nodes", "-key", "rootCA.key", "-sha256", "-days", "3650", "-out", "rootCA.crt", "-subj", "/C=US/ST=MA/O=Example Org/CN=localhost")
36         if err != nil {
37                 return err
38         }
39         // Generate server key
40         err = super.RunProgram(ctx, super.tempdir, runOptions{}, "openssl", "genrsa", "-out", "server.key", "2048")
41         if err != nil {
42                 return err
43         }
44         // Build config file for signing request
45         defaultconf, err := ioutil.ReadFile("/etc/ssl/openssl.cnf")
46         if err != nil {
47                 return err
48         }
49         hostname, err := os.Hostname()
50         if err != nil {
51                 return fmt.Errorf("hostname: %w", err)
52         }
53         san := "DNS:localhost,DNS:localhost.localdomain,DNS:" + hostname
54         if super.ListenHost == hostname || super.ListenHost == "localhost" {
55                 // already have it
56         } else if net.ParseIP(super.ListenHost) != nil {
57                 san += fmt.Sprintf(",IP:%s", super.ListenHost)
58         } else {
59                 san += fmt.Sprintf(",DNS:%s", super.ListenHost)
60         }
61         conf := append(defaultconf, []byte(fmt.Sprintf("\n[SAN]\nsubjectAltName=%s\n", san))...)
62         err = ioutil.WriteFile(filepath.Join(super.tempdir, "server.cfg"), conf, 0644)
63         if err != nil {
64                 return err
65         }
66         // Generate signing request
67         err = super.RunProgram(ctx, super.tempdir, runOptions{}, "openssl", "req", "-new", "-sha256", "-key", "server.key", "-subj", "/C=US/ST=MA/O=Example Org/CN=localhost", "-reqexts", "SAN", "-config", "server.cfg", "-out", "server.csr")
68         if err != nil {
69                 return err
70         }
71         // Sign certificate
72         err = super.RunProgram(ctx, super.tempdir, runOptions{}, "openssl", "x509", "-req", "-in", "server.csr", "-CA", "rootCA.crt", "-CAkey", "rootCA.key", "-CAcreateserial", "-out", "server.crt", "-extfile", "server.cfg", "-extensions", "SAN", "-days", "3650", "-sha256")
73         if err != nil {
74                 return err
75         }
76         return nil
77 }