github.com/hernad/nomad@v1.6.112/command/tls_ca_create.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/posener/complete"
    11  
    12  	"github.com/hernad/nomad/helper/flags"
    13  	"github.com/hernad/nomad/helper/tlsutil"
    14  	"github.com/hernad/nomad/lib/file"
    15  )
    16  
    17  type TLSCACreateCommand struct {
    18  	Meta
    19  
    20  	// days is the number of days the CA will be valid for
    21  	days int
    22  
    23  	// constraint boolean enables the name constraint option in the CA which
    24  	// will then reject any domains other than the ones stiputalted in -domain
    25  	// and -addtitional-domain.
    26  	constraint bool
    27  
    28  	// domain is used to provide a custom domain for the CA
    29  	domain string
    30  
    31  	// commonName is used to set a common name for the CA
    32  	commonName string
    33  
    34  	// additionalDomain provides a list of restricted domains to the CA which
    35  	// will then reject any domains other than these.
    36  	additionalDomain flags.StringFlag
    37  
    38  	// country is used to set a country code for the CA
    39  	country string
    40  
    41  	// postalCode is used to set a postal code for the CA
    42  	postalCode string
    43  
    44  	// province is used to set a province for the CA
    45  	province string
    46  
    47  	// locality is used to set a locality for the CA
    48  	locality string
    49  
    50  	// streetAddress is used to set a street address for the CA
    51  	streetAddress string
    52  
    53  	// organization is used to set an organization for the CA
    54  	organization string
    55  
    56  	// organizationalUnit is used to set an organizational unit for the CA
    57  	organizationalUnit string
    58  }
    59  
    60  func (c *TLSCACreateCommand) Help() string {
    61  	helpText := `
    62  Usage: nomad tls ca create [options]
    63  
    64    Create a new certificate authority.
    65  
    66  CA Create Options:
    67  
    68    -additional-domain
    69      Add additional DNS zones to the allowed list for the CA. The server will
    70      reject certificates for DNS names other than those specified in -domain and
    71      -additional-domain. This flag can be used multiple times. Only used in
    72      combination with -domain and -name-constraint.
    73  
    74    -common-name
    75      Common Name of CA. Defaults to "Nomad Agent CA".
    76  
    77    -country
    78      Country of the CA. Defaults to "US".
    79  
    80    -days
    81      Provide number of days the CA is valid for from now on.
    82      Defaults to 5 years or 1825 days.
    83  
    84    -domain
    85      Domain of Nomad cluster. Only used in combination with -name-constraint.
    86      Defaults to "nomad".
    87  
    88    -locality
    89      Locality of the CA. Defaults to "San Francisco".
    90  
    91    -name-constraint
    92      Enables the DNS name restriction functionality to the CA. Results in the CA
    93      rejecting certificates for any other DNS zone. If enabled, localhost and the
    94      value of -domain will be added to the allowed DNS zones field. If the UI is
    95      going to be served over HTTPS its hostname must be added with
    96      -additional-domain. Defaults to false.
    97  
    98    -organization
    99      Organization of the CA. Defaults to "HashiCorp Inc.".
   100  
   101    -organizational-unit
   102      Organizational Unit of the CA. Defaults to "Nomad".
   103  
   104    -postal-code
   105      Postal Code of the CA. Defaults to "94105".
   106  
   107    -province
   108      Province of the CA. Defaults to "CA".
   109  
   110    -street-address
   111      Street Address of the CA. Defaults to "101 Second Street".
   112  
   113  `
   114  	return strings.TrimSpace(helpText)
   115  }
   116  
   117  func (c *TLSCACreateCommand) AutocompleteFlags() complete.Flags {
   118  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
   119  		complete.Flags{
   120  			"-additional-domain":   complete.PredictAnything,
   121  			"-common-name":         complete.PredictAnything,
   122  			"-days":                complete.PredictAnything,
   123  			"-country":             complete.PredictAnything,
   124  			"-domain":              complete.PredictAnything,
   125  			"-locality":            complete.PredictAnything,
   126  			"-name-constraint":     complete.PredictAnything,
   127  			"-organization":        complete.PredictAnything,
   128  			"-organizational-unit": complete.PredictAnything,
   129  			"-postal-code":         complete.PredictAnything,
   130  			"-province":            complete.PredictAnything,
   131  			"-street-address":      complete.PredictAnything,
   132  		})
   133  }
   134  
   135  func (c *TLSCACreateCommand) AutocompleteArgs() complete.Predictor {
   136  	return complete.PredictNothing
   137  }
   138  
   139  func (c *TLSCACreateCommand) Synopsis() string {
   140  	return "Create a certificate authority for Nomad"
   141  }
   142  
   143  func (c *TLSCACreateCommand) Name() string { return "tls ca create" }
   144  
   145  func (c *TLSCACreateCommand) Run(args []string) int {
   146  
   147  	flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient)
   148  	flagSet.Usage = func() { c.Ui.Output(c.Help()) }
   149  	flagSet.Var(&c.additionalDomain, "additional-domain", "")
   150  	flagSet.IntVar(&c.days, "days", 0, "")
   151  	flagSet.BoolVar(&c.constraint, "name-constraint", false, "")
   152  	flagSet.StringVar(&c.domain, "domain", "", "")
   153  	flagSet.StringVar(&c.commonName, "common-name", "", "")
   154  	flagSet.StringVar(&c.country, "country", "", "")
   155  	flagSet.StringVar(&c.postalCode, "postal-code", "", "")
   156  	flagSet.StringVar(&c.province, "province", "", "")
   157  	flagSet.StringVar(&c.locality, "locality", "", "")
   158  	flagSet.StringVar(&c.streetAddress, "street-address", "", "")
   159  	flagSet.StringVar(&c.organization, "organization", "", "")
   160  	flagSet.StringVar(&c.organizationalUnit, "organizational-unit", "", "")
   161  	if err := flagSet.Parse(args); err != nil {
   162  		return 1
   163  	}
   164  
   165  	// Check that we got no arguments
   166  	args = flagSet.Args()
   167  	if l := len(args); l < 0 || l > 1 {
   168  		c.Ui.Error("This command takes up to one argument")
   169  		c.Ui.Error(commandErrorText(c))
   170  		return 1
   171  	}
   172  	if c.IsCustom() && c.days != 0 || c.IsCustom() {
   173  		c.domain = "nomad"
   174  	} else {
   175  		if c.commonName == "" {
   176  			c.Ui.Error("Please provide the -common-name flag when customizing the CA")
   177  			c.Ui.Error(commandErrorText(c))
   178  			return 1
   179  		}
   180  		if c.country == "" {
   181  			c.Ui.Error("Please provide the -country flag when customizing the CA")
   182  			c.Ui.Error(commandErrorText(c))
   183  			return 1
   184  		}
   185  
   186  		if c.organization == "" {
   187  			c.Ui.Error("Please provide the -organization flag when customizing the CA")
   188  			c.Ui.Error(commandErrorText(c))
   189  			return 1
   190  		}
   191  
   192  		if c.organizationalUnit == "" {
   193  			c.Ui.Error("Please provide the -organizational-unit flag when customizing the CA")
   194  			c.Ui.Error(commandErrorText(c))
   195  			return 1
   196  		}
   197  	}
   198  	if c.domain != "" && c.domain != "nomad" && !c.constraint {
   199  		c.Ui.Error("Please provide the -name-constraint flag to use a custom domain constraint")
   200  		return 1
   201  	}
   202  	if c.domain == "nomad" && c.constraint {
   203  		c.Ui.Error("Please provide the -domain flag if you want to enable custom domain constraints")
   204  		return 1
   205  	}
   206  	if c.additionalDomain != nil && c.domain == "" && !c.constraint {
   207  		c.Ui.Error("Please provide the -name-constraint flag to use a custom domain constraints")
   208  		return 1
   209  	}
   210  
   211  	certFileName := fmt.Sprintf("%s-agent-ca.pem", c.domain)
   212  	pkFileName := fmt.Sprintf("%s-agent-ca-key.pem", c.domain)
   213  
   214  	if !(fileDoesNotExist(certFileName)) {
   215  		c.Ui.Error(fmt.Sprintf("CA certificate file '%s' already exists", certFileName))
   216  		return 1
   217  	}
   218  	if !(fileDoesNotExist(pkFileName)) {
   219  		c.Ui.Error(fmt.Sprintf("CA key file '%s' already exists", pkFileName))
   220  		return 1
   221  	}
   222  
   223  	constraints := []string{}
   224  	if c.constraint {
   225  		constraints = []string{c.domain, "localhost", "nomad"}
   226  		constraints = append(constraints, c.additionalDomain...)
   227  	}
   228  
   229  	ca, pk, err := tlsutil.GenerateCA(tlsutil.CAOpts{
   230  		Name:                c.commonName,
   231  		Days:                c.days,
   232  		PermittedDNSDomains: constraints,
   233  		Country:             c.country,
   234  		PostalCode:          c.postalCode,
   235  		Province:            c.province,
   236  		Locality:            c.locality,
   237  		StreetAddress:       c.streetAddress,
   238  		Organization:        c.organization,
   239  		OrganizationalUnit:  c.organizationalUnit,
   240  	})
   241  	if err != nil {
   242  		c.Ui.Error(err.Error())
   243  		return 1
   244  	}
   245  
   246  	if err := file.WriteAtomicWithPerms(certFileName, []byte(ca), 0755, 0666); err != nil {
   247  		c.Ui.Error(err.Error())
   248  		return 1
   249  	}
   250  	c.Ui.Output("==> CA certificate saved to: " + certFileName)
   251  
   252  	if err := file.WriteAtomicWithPerms(pkFileName, []byte(pk), 0755, 0600); err != nil {
   253  		c.Ui.Error(err.Error())
   254  		return 1
   255  	}
   256  	c.Ui.Output("==> CA certificate key saved to: " + pkFileName)
   257  
   258  	return 0
   259  }
   260  
   261  // IsCustom checks whether any of TLSCACreateCommand parameters have been populated with
   262  // non-default values.
   263  func (c *TLSCACreateCommand) IsCustom() bool {
   264  	return c.commonName == "" &&
   265  		c.country == "" &&
   266  		c.postalCode == "" &&
   267  		c.province == "" &&
   268  		c.locality == "" &&
   269  		c.streetAddress == "" &&
   270  		c.organization == "" &&
   271  		c.organizationalUnit == ""
   272  
   273  }