2023-04-10 15:36:59 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2022-11-22 19:12:07 +00:00
|
|
|
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
|
2023-07-11 07:53:09 +00:00
|
|
|
|
|
|
|
// country is used to set a country code for the CA
|
|
|
|
country string
|
|
|
|
|
|
|
|
// postalCode is used to set a postal code for the CA
|
|
|
|
postalCode string
|
|
|
|
|
|
|
|
// province is used to set a province for the CA
|
|
|
|
province string
|
|
|
|
|
|
|
|
// locality is used to set a locality for the CA
|
|
|
|
locality string
|
|
|
|
|
|
|
|
// streetAddress is used to set a street address for the CA
|
|
|
|
streetAddress string
|
|
|
|
|
|
|
|
// organization is used to set an organization for the CA
|
|
|
|
organization string
|
|
|
|
|
|
|
|
// organizationalUnit is used to set an organizational unit for the CA
|
|
|
|
organizationalUnit string
|
2022-11-22 19:12:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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".
|
|
|
|
|
2023-07-11 07:53:09 +00:00
|
|
|
-country
|
|
|
|
Country of the CA. Defaults to "US".
|
|
|
|
|
2022-11-22 19:12:07 +00:00
|
|
|
-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".
|
|
|
|
|
2023-07-11 07:53:09 +00:00
|
|
|
-locality
|
|
|
|
Locality of the CA. Defaults to "San Francisco".
|
|
|
|
|
2022-11-22 19:12:07 +00:00
|
|
|
-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.
|
2023-07-11 07:53:09 +00:00
|
|
|
|
|
|
|
-organization
|
|
|
|
Organization of the CA. Defaults to "HashiCorp Inc.".
|
|
|
|
|
|
|
|
-organizational-unit
|
|
|
|
Organizational Unit of the CA. Defaults to "Nomad".
|
|
|
|
|
|
|
|
-postal-code
|
|
|
|
Postal Code of the CA. Defaults to "94105".
|
|
|
|
|
|
|
|
-province
|
|
|
|
Province of the CA. Defaults to "CA".
|
|
|
|
|
|
|
|
-street-address
|
|
|
|
Street Address of the CA. Defaults to "101 Second Street".
|
|
|
|
|
2022-11-22 19:12:07 +00:00
|
|
|
`
|
|
|
|
return strings.TrimSpace(helpText)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *TLSCACreateCommand) AutocompleteFlags() complete.Flags {
|
|
|
|
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
|
|
|
complete.Flags{
|
2023-07-11 07:53:09 +00:00
|
|
|
"-additional-domain": complete.PredictAnything,
|
|
|
|
"-common-name": complete.PredictAnything,
|
|
|
|
"-days": complete.PredictAnything,
|
|
|
|
"-country": complete.PredictAnything,
|
|
|
|
"-domain": complete.PredictAnything,
|
|
|
|
"-locality": complete.PredictAnything,
|
|
|
|
"-name-constraint": complete.PredictAnything,
|
|
|
|
"-organization": complete.PredictAnything,
|
|
|
|
"-organizational-unit": complete.PredictAnything,
|
|
|
|
"-postal-code": complete.PredictAnything,
|
|
|
|
"-province": complete.PredictAnything,
|
|
|
|
"-street-address": complete.PredictAnything,
|
2022-11-22 19:12:07 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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", "")
|
2023-07-11 07:53:09 +00:00
|
|
|
flagSet.IntVar(&c.days, "days", 0, "")
|
2022-11-22 19:12:07 +00:00
|
|
|
flagSet.BoolVar(&c.constraint, "name-constraint", false, "")
|
2023-07-11 07:53:09 +00:00
|
|
|
flagSet.StringVar(&c.domain, "domain", "", "")
|
2022-11-22 19:12:07 +00:00
|
|
|
flagSet.StringVar(&c.commonName, "common-name", "", "")
|
2023-07-11 07:53:09 +00:00
|
|
|
flagSet.StringVar(&c.country, "country", "", "")
|
|
|
|
flagSet.StringVar(&c.postalCode, "postal-code", "", "")
|
|
|
|
flagSet.StringVar(&c.province, "province", "", "")
|
|
|
|
flagSet.StringVar(&c.locality, "locality", "", "")
|
|
|
|
flagSet.StringVar(&c.streetAddress, "street-address", "", "")
|
|
|
|
flagSet.StringVar(&c.organization, "organization", "", "")
|
|
|
|
flagSet.StringVar(&c.organizationalUnit, "organizational-unit", "", "")
|
2022-11-22 19:12:07 +00:00
|
|
|
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
|
|
|
|
}
|
2023-07-11 07:53:09 +00:00
|
|
|
if c.IsCustom() && c.days != 0 || c.IsCustom() {
|
|
|
|
c.domain = "nomad"
|
|
|
|
} else {
|
|
|
|
if c.commonName == "" {
|
|
|
|
c.Ui.Error("Please provide the -common-name flag when customizing the CA")
|
|
|
|
c.Ui.Error(commandErrorText(c))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
if c.country == "" {
|
|
|
|
c.Ui.Error("Please provide the -country flag when customizing the CA")
|
|
|
|
c.Ui.Error(commandErrorText(c))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.organization == "" {
|
|
|
|
c.Ui.Error("Please provide the -organization flag when customizing the CA")
|
|
|
|
c.Ui.Error(commandErrorText(c))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.organizationalUnit == "" {
|
|
|
|
c.Ui.Error("Please provide the -organizational-unit flag when customizing the CA")
|
|
|
|
c.Ui.Error(commandErrorText(c))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
}
|
2022-11-22 19:12:07 +00:00
|
|
|
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 {
|
2023-05-22 13:31:56 +00:00
|
|
|
constraints = []string{c.domain, "localhost", "nomad"}
|
2022-11-22 19:12:07 +00:00
|
|
|
constraints = append(constraints, c.additionalDomain...)
|
|
|
|
}
|
|
|
|
|
2023-07-11 07:53:09 +00:00
|
|
|
ca, pk, err := tlsutil.GenerateCA(tlsutil.CAOpts{
|
|
|
|
Name: c.commonName,
|
|
|
|
Days: c.days,
|
|
|
|
PermittedDNSDomains: constraints,
|
|
|
|
Country: c.country,
|
|
|
|
PostalCode: c.postalCode,
|
|
|
|
Province: c.province,
|
|
|
|
Locality: c.locality,
|
|
|
|
StreetAddress: c.streetAddress,
|
|
|
|
Organization: c.organization,
|
|
|
|
OrganizationalUnit: c.organizationalUnit,
|
|
|
|
})
|
2022-11-22 19:12:07 +00:00
|
|
|
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
|
|
|
|
}
|
2023-07-11 07:53:09 +00:00
|
|
|
|
|
|
|
// IsCustom checks whether any of TLSCACreateCommand parameters have been populated with
|
|
|
|
// non-default values.
|
|
|
|
func (c *TLSCACreateCommand) IsCustom() bool {
|
|
|
|
return c.commonName == "" &&
|
|
|
|
c.country == "" &&
|
|
|
|
c.postalCode == "" &&
|
|
|
|
c.province == "" &&
|
|
|
|
c.locality == "" &&
|
|
|
|
c.streetAddress == "" &&
|
|
|
|
c.organization == "" &&
|
|
|
|
c.organizationalUnit == ""
|
|
|
|
|
|
|
|
}
|