open-nomad/command/tls_ca_create.go
Lance Haig 568da5918b
cli: tls certs not created with correct SANs (#16959)
The `nomad tls cert` command did not create certificates with the correct SANs for
them to work with non default domain and region names. This changset updates the
code to support non default domains and regions in the certificates.
2023-05-22 09:31:56 -04:00

166 lines
4.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package command
import (
"fmt"
"strings"
"github.com/posener/complete"
"github.com/hashicorp/nomad/helper/flags"
"github.com/hashicorp/nomad/helper/tlsutil"
"github.com/hashicorp/nomad/lib/file"
)
type TLSCACreateCommand struct {
Meta
// days is the number of days the CA will be valid for
days int
// constraint boolean enables the name constraint option in the CA which
// will then reject any domains other than the ones stiputalted in -domain
// and -addtitional-domain.
constraint bool
// domain is used to provide a custom domain for the CA
domain string
// commonName is used to set a common name for the CA
commonName string
// additionalDomain provides a list of restricted domains to the CA which
// will then reject any domains other than these.
additionalDomain flags.StringFlag
}
func (c *TLSCACreateCommand) Help() string {
helpText := `
Usage: nomad tls ca create [options]
Create a new certificate authority.
CA Create Options:
-additional-domain
Add additional DNS zones to the allowed list for the CA. The server will
reject certificates for DNS names other than those specified in -domain and
-additional-domain. This flag can be used multiple times. Only used in
combination with -domain and -name-constraint.
-common-name
Common Name of CA. Defaults to "Nomad Agent CA".
-days
Provide number of days the CA is valid for from now on.
Defaults to 5 years or 1825 days.
-domain
Domain of Nomad cluster. Only used in combination with -name-constraint.
Defaults to "nomad".
-name-constraint
Enables the DNS name restriction functionality to the CA. Results in the CA
rejecting certificates for any other DNS zone. If enabled, localhost and the
value of -domain will be added to the allowed DNS zones field. If the UI is
going to be served over HTTPS its hostname must be added with
-additional-domain. Defaults to false.
`
return strings.TrimSpace(helpText)
}
func (c *TLSCACreateCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-additional-domain": complete.PredictAnything,
"-common-name": complete.PredictAnything,
"-days": complete.PredictAnything,
"-domain": complete.PredictAnything,
"-name-constraint": complete.PredictAnything,
})
}
func (c *TLSCACreateCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *TLSCACreateCommand) Synopsis() string {
return "Create a certificate authority for Nomad"
}
func (c *TLSCACreateCommand) Name() string { return "tls ca create" }
func (c *TLSCACreateCommand) Run(args []string) int {
flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient)
flagSet.Usage = func() { c.Ui.Output(c.Help()) }
flagSet.Var(&c.additionalDomain, "additional-domain", "")
flagSet.IntVar(&c.days, "days", 1825, "")
flagSet.BoolVar(&c.constraint, "name-constraint", false, "")
flagSet.StringVar(&c.domain, "domain", "nomad", "")
flagSet.StringVar(&c.commonName, "common-name", "", "")
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.domain != "" && c.domain != "nomad" && !c.constraint {
c.Ui.Error("Please provide the -name-constraint flag to use a custom domain constraint")
return 1
}
if c.domain == "nomad" && c.constraint {
c.Ui.Error("Please provide the -domain flag if you want to enable custom domain constraints")
return 1
}
if c.additionalDomain != nil && c.domain == "" && !c.constraint {
c.Ui.Error("Please provide the -name-constraint flag to use a custom domain constraints")
return 1
}
certFileName := fmt.Sprintf("%s-agent-ca.pem", c.domain)
pkFileName := fmt.Sprintf("%s-agent-ca-key.pem", c.domain)
if !(fileDoesNotExist(certFileName)) {
c.Ui.Error(fmt.Sprintf("CA certificate file '%s' already exists", certFileName))
return 1
}
if !(fileDoesNotExist(pkFileName)) {
c.Ui.Error(fmt.Sprintf("CA key file '%s' already exists", pkFileName))
return 1
}
constraints := []string{}
if c.constraint {
constraints = []string{c.domain, "localhost", "nomad"}
constraints = append(constraints, c.additionalDomain...)
}
ca, pk, err := tlsutil.GenerateCA(tlsutil.CAOpts{Name: c.commonName, Days: c.days, Domain: c.domain, PermittedDNSDomains: constraints})
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if err := file.WriteAtomicWithPerms(certFileName, []byte(ca), 0755, 0666); err != nil {
c.Ui.Error(err.Error())
return 1
}
c.Ui.Output("==> CA certificate saved to: " + certFileName)
if err := file.WriteAtomicWithPerms(pkFileName, []byte(pk), 0755, 0600); err != nil {
c.Ui.Error(err.Error())
return 1
}
c.Ui.Output("==> CA certificate key saved to: " + pkFileName)
return 0
}