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 }