-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
tls_cert_create.go
293 lines (241 loc) · 8.27 KB
/
tls_cert_create.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
package command
import (
"crypto/x509"
"fmt"
"net"
"os"
"strings"
"github.com/posener/complete"
"github.com/hashicorp/nomad/helper/flags"
"github.com/hashicorp/nomad/helper/tlsutil"
"github.com/hashicorp/nomad/lib/file"
)
type TLSCertCreateCommand struct {
Meta
// dnsNames is a list of additional dns records to add to the SAN addresses
dnsNames flags.StringFlag
// ipAddresses is a list of additional IP address records to add to the SAN
// addresses
ipAddresses flags.StringFlag
// ca is used to set a custom CA certificate to create certificates from.
ca string
cli bool
client bool
// key is used to set the custom CA certificate key when creating
// certificates.
key string
// days is the number of days the certificate will be valid for.
days int
// cluster_region is used to add the region name to the certifacte SAN
// records
cluster_region string
// domain is used to provide a custom domain for the certificate.
domain string
server bool
}
func (c *TLSCertCreateCommand) Help() string {
helpText := `
Usage: nomad tls cert create [options]
Create a new TLS certificate to use within the Nomad cluster TLS
configuration. You should use the -client, -server or -cli options to create
certificates for these roles.
Certificate Create Options:
-additional-dnsname
Provide an additional dnsname for Subject Alternative Names.
"localhost" is always included. This flag may be provided multiple times.
-additional-ipaddress
Provide an additional ipaddress for Subject Alternative Names.
"127.0.0.1" is always included. This flag may be provided multiple times.
-ca
Provide path to the certificate authority certificate. Defaults to
#DOMAIN#-agent-ca.pem.
-cli
Generate a certificate for use with the Nomad CLI.
-client
Generate a client certificate.
-cluster-region
Provide the datacenter. Only used for -server certificates.
Defaults to "global".
-days
Provide number of days the certificate is valid for from now on.
Defaults to 1 year.
-domain
Provide the domain. Only used for -server certificates.
-key
Provide path to the certificate authority key. Defaults to
#DOMAIN#-agent-ca-key.pem.
-server
Generate a server certificate.
`
return strings.TrimSpace(helpText)
}
func (c *TLSCertCreateCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-additional-dnsname": complete.PredictAnything,
"-additional-ipaddress": complete.PredictAnything,
"-ca": complete.PredictAnything,
"-cli": complete.PredictNothing,
"-client": complete.PredictNothing,
"-days": complete.PredictAnything,
"-cluster-region": complete.PredictAnything,
"-domain": complete.PredictAnything,
"-key": complete.PredictAnything,
"-server": complete.PredictNothing,
})
}
func (c *TLSCertCreateCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *TLSCertCreateCommand) Synopsis() string {
return "Create a new TLS certificate"
}
func (c *TLSCertCreateCommand) Name() string { return "tls cert create" }
func (c *TLSCertCreateCommand) Run(args []string) int {
flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient)
flagSet.Usage = func() { c.Ui.Output(c.Help()) }
flagSet.Var(&c.dnsNames, "additional-dnsname", "")
flagSet.Var(&c.ipAddresses, "additional-ipaddress", "")
flagSet.StringVar(&c.ca, "ca", "#DOMAIN#-agent-ca.pem", "")
flagSet.BoolVar(&c.cli, "cli", false, "")
flagSet.BoolVar(&c.client, "client", false, "")
flagSet.StringVar(&c.key, "key", "#DOMAIN#-agent-ca-key.pem", "")
flagSet.IntVar(&c.days, "days", 365, "")
flagSet.StringVar(&c.cluster_region, "cluster-region", "global", "")
flagSet.StringVar(&c.domain, "domain", "nomad", "")
flagSet.BoolVar(&c.server, "server", false, "")
if err := flagSet.Parse(args); err != nil {
return 1
}
// Check that we got no arguments
args = flagSet.Args()
if l := len(args); l < 0 || l > 1 {
c.Ui.Error("This command takes up to one argument")
c.Ui.Error(commandErrorText(c))
return 1
}
if c.ca == "" {
c.Ui.Error("Please provide the ca")
return 1
}
if c.key == "" {
c.Ui.Error("Please provide the key")
return 1
}
if !((c.server && !c.client && !c.cli) ||
(!c.server && c.client && !c.cli) ||
(!c.server && !c.client && c.cli)) {
c.Ui.Error("Please provide either -server, -client, or -cli")
return 1
}
var DNSNames []string
var IPAddresses []net.IP
var extKeyUsage []x509.ExtKeyUsage
var name, prefix string
for _, d := range c.dnsNames {
if len(d) > 0 {
DNSNames = append(DNSNames, strings.TrimSpace(d))
}
}
for _, i := range c.ipAddresses {
if len(i) > 0 {
IPAddresses = append(IPAddresses, net.ParseIP(strings.TrimSpace(i)))
}
}
if c.server {
name = fmt.Sprintf("server.%s.%s", c.cluster_region, c.domain)
DNSNames = append(DNSNames, name)
DNSNames = append(DNSNames, "localhost")
IPAddresses = append(IPAddresses, net.ParseIP("127.0.0.1"))
extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
prefix = fmt.Sprintf("%s-server-%s", c.cluster_region, c.domain)
} else if c.client {
name = fmt.Sprintf("client.%s.%s", c.cluster_region, c.domain)
DNSNames = append(DNSNames, []string{name, "localhost"}...)
IPAddresses = append(IPAddresses, net.ParseIP("127.0.0.1"))
extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}
prefix = fmt.Sprintf("%s-client-%s", c.cluster_region, c.domain)
} else if c.cli {
name = fmt.Sprintf("cli.%s.%s", c.cluster_region, c.domain)
DNSNames = []string{name, "localhost"}
prefix = fmt.Sprintf("%s-cli-%s", c.cluster_region, c.domain)
} else {
c.Ui.Error("Neither client, cli nor server - should not happen")
return 1
}
var pkFileName, certFileName string
tmpCert := fmt.Sprintf("%s.pem", prefix)
tmpPk := fmt.Sprintf("%s-key.pem", prefix)
// Check if the CA file already exists
if !(fileDoesNotExist(tmpCert)) {
c.Ui.Error(fmt.Sprintf("Certificate file '%s' already exists", tmpCert))
return 1
}
// Check if the Key file file already exists
if !(fileDoesNotExist(tmpPk)) {
c.Ui.Error(fmt.Sprintf("Key file '%s' already exists", tmpPk))
return 1
}
certFileName = tmpCert
pkFileName = tmpPk
caFile := strings.Replace(c.ca, "#DOMAIN#", c.domain, 1)
keyFile := strings.Replace(c.key, "#DOMAIN#", c.domain, 1)
cert, err := os.ReadFile(caFile)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading CA: %s", err))
return 1
}
caKey, err := os.ReadFile(keyFile)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading CA key: %s", err))
return 1
}
if c.server {
c.Ui.Warn(
`==> WARNING: Server Certificates grants authority to become a
server and access all state in the cluster including root keys
and all ACL tokens. Do not distribute them to production hosts
that are not server nodes. Store them as securely as CA keys.`)
}
c.Ui.Info("==> Using CA file " + caFile + " and CA key " + keyFile)
signer, err := tlsutil.ParseSigner(string(caKey))
if err != nil {
c.Ui.Error(err.Error())
return 1
}
pub, priv, err := tlsutil.GenerateCert(tlsutil.CertOpts{
Signer: signer, CA: string(cert), Name: name, Days: c.days,
DNSNames: DNSNames, IPAddresses: IPAddresses, ExtKeyUsage: extKeyUsage,
})
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if err = tlsutil.Verify(string(cert), pub, name); err != nil {
c.Ui.Error(err.Error())
return 1
}
if err := file.WriteAtomicWithPerms(certFileName, []byte(pub), 0755, 0666); err != nil {
c.Ui.Error(err.Error())
return 1
}
if c.server {
c.Ui.Output("==> Server Certificate saved to " + certFileName)
} else if c.client {
c.Ui.Output("==> Client Certificate saved to " + certFileName)
} else if c.cli {
c.Ui.Output("==> Cli Certificate saved to " + certFileName)
}
if err := file.WriteAtomicWithPerms(pkFileName, []byte(priv), 0755, 0600); err != nil {
c.Ui.Error(err.Error())
return 1
}
if c.server {
c.Ui.Output("==> Server Certificate key saved to " + pkFileName)
} else if c.client {
c.Ui.Output("==> Client Certificate key saved to " + pkFileName)
} else if c.cli {
c.Ui.Output("==> CLI Certificate key saved to " + pkFileName)
}
return 0
}