Add the ability to customise the details of the CA (#17309)

Co-authored-by: James Rasell <jrasell@users.noreply.github.com>
This commit is contained in:
Lance Haig 2023-07-11 09:53:09 +02:00 committed by GitHub
parent 2b85290d55
commit 0455389534
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 330 additions and 48 deletions

3
.changelog/17309.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
cli: Add the ability to customize the details of the CA when running `nomad tls ca create`
```

View File

@ -34,6 +34,27 @@ type TLSCACreateCommand struct {
// additionalDomain provides a list of restricted domains to the CA which // additionalDomain provides a list of restricted domains to the CA which
// will then reject any domains other than these. // will then reject any domains other than these.
additionalDomain flags.StringFlag additionalDomain flags.StringFlag
// 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
} }
func (c *TLSCACreateCommand) Help() string { func (c *TLSCACreateCommand) Help() string {
@ -53,6 +74,9 @@ CA Create Options:
-common-name -common-name
Common Name of CA. Defaults to "Nomad Agent CA". Common Name of CA. Defaults to "Nomad Agent CA".
-country
Country of the CA. Defaults to "US".
-days -days
Provide number of days the CA is valid for from now on. Provide number of days the CA is valid for from now on.
Defaults to 5 years or 1825 days. Defaults to 5 years or 1825 days.
@ -61,12 +85,31 @@ CA Create Options:
Domain of Nomad cluster. Only used in combination with -name-constraint. Domain of Nomad cluster. Only used in combination with -name-constraint.
Defaults to "nomad". Defaults to "nomad".
-locality
Locality of the CA. Defaults to "San Francisco".
-name-constraint -name-constraint
Enables the DNS name restriction functionality to the CA. Results in the CA 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 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 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 going to be served over HTTPS its hostname must be added with
-additional-domain. Defaults to false. -additional-domain. Defaults to false.
-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".
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
@ -77,8 +120,15 @@ func (c *TLSCACreateCommand) AutocompleteFlags() complete.Flags {
"-additional-domain": complete.PredictAnything, "-additional-domain": complete.PredictAnything,
"-common-name": complete.PredictAnything, "-common-name": complete.PredictAnything,
"-days": complete.PredictAnything, "-days": complete.PredictAnything,
"-country": complete.PredictAnything,
"-domain": complete.PredictAnything, "-domain": complete.PredictAnything,
"-locality": complete.PredictAnything,
"-name-constraint": complete.PredictAnything, "-name-constraint": complete.PredictAnything,
"-organization": complete.PredictAnything,
"-organizational-unit": complete.PredictAnything,
"-postal-code": complete.PredictAnything,
"-province": complete.PredictAnything,
"-street-address": complete.PredictAnything,
}) })
} }
@ -97,10 +147,17 @@ func (c *TLSCACreateCommand) Run(args []string) int {
flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient) flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient)
flagSet.Usage = func() { c.Ui.Output(c.Help()) } flagSet.Usage = func() { c.Ui.Output(c.Help()) }
flagSet.Var(&c.additionalDomain, "additional-domain", "") flagSet.Var(&c.additionalDomain, "additional-domain", "")
flagSet.IntVar(&c.days, "days", 1825, "") flagSet.IntVar(&c.days, "days", 0, "")
flagSet.BoolVar(&c.constraint, "name-constraint", false, "") flagSet.BoolVar(&c.constraint, "name-constraint", false, "")
flagSet.StringVar(&c.domain, "domain", "nomad", "") flagSet.StringVar(&c.domain, "domain", "", "")
flagSet.StringVar(&c.commonName, "common-name", "", "") flagSet.StringVar(&c.commonName, "common-name", "", "")
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", "", "")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {
return 1 return 1
} }
@ -112,6 +169,32 @@ func (c *TLSCACreateCommand) Run(args []string) int {
c.Ui.Error(commandErrorText(c)) c.Ui.Error(commandErrorText(c))
return 1 return 1
} }
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
}
}
if c.domain != "" && c.domain != "nomad" && !c.constraint { if c.domain != "" && c.domain != "nomad" && !c.constraint {
c.Ui.Error("Please provide the -name-constraint flag to use a custom domain constraint") c.Ui.Error("Please provide the -name-constraint flag to use a custom domain constraint")
return 1 return 1
@ -143,7 +226,18 @@ func (c *TLSCACreateCommand) Run(args []string) int {
constraints = append(constraints, c.additionalDomain...) constraints = append(constraints, c.additionalDomain...)
} }
ca, pk, err := tlsutil.GenerateCA(tlsutil.CAOpts{Name: c.commonName, Days: c.days, Domain: c.domain, PermittedDNSDomains: constraints}) 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,
})
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
@ -163,3 +257,17 @@ func (c *TLSCACreateCommand) Run(args []string) int {
return 0 return 0
} }
// 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 == ""
}

View File

@ -6,7 +6,6 @@ package command
import ( import (
"crypto/x509" "crypto/x509"
"os" "os"
"strings"
"testing" "testing"
"time" "time"
@ -47,6 +46,10 @@ func TestCACreateCommand(t *testing.T) {
"-name-constraint=true", "-name-constraint=true",
"-domain=foo", "-domain=foo",
"-additional-domain=bar", "-additional-domain=bar",
"-common-name=CustomCA",
"-country=ZZ",
"-organization=CustOrg",
"-organizational-unit=CustOrgUnit",
}, },
"foo-agent-ca.pem", "foo-agent-ca.pem",
"foo-agent-ca-key.pem", "foo-agent-ca-key.pem",
@ -55,24 +58,20 @@ func TestCACreateCommand(t *testing.T) {
require.True(t, cert.PermittedDNSDomainsCritical) require.True(t, cert.PermittedDNSDomainsCritical)
require.Len(t, cert.PermittedDNSDomains, 4) require.Len(t, cert.PermittedDNSDomains, 4)
require.ElementsMatch(t, cert.PermittedDNSDomains, []string{"nomad", "foo", "localhost", "bar"}) require.ElementsMatch(t, cert.PermittedDNSDomains, []string{"nomad", "foo", "localhost", "bar"})
require.Equal(t, cert.Issuer.Organization, []string{"CustOrg"})
require.Equal(t, cert.Issuer.OrganizationalUnit, []string{"CustOrgUnit"})
require.Equal(t, cert.Issuer.Country, []string{"ZZ"})
require.Contains(t, cert.Issuer.CommonName, "CustomCA")
}, },
}, },
{"with common-name", {"ca custom date",
[]string{ []string{
"-common-name=foo", "-days=365",
}, },
"nomad-agent-ca.pem", "nomad-agent-ca.pem",
"nomad-agent-ca-key.pem", "nomad-agent-ca-key.pem",
func(t *testing.T, cert *x509.Certificate) { func(t *testing.T, cert *x509.Certificate) {
require.Equal(t, cert.Subject.CommonName, "foo") require.Equal(t, 365*24*time.Hour, time.Until(cert.NotAfter).Round(24*time.Hour))
},
},
{"without common-name",
[]string{},
"nomad-agent-ca.pem",
"nomad-agent-ca-key.pem",
func(t *testing.T, cert *x509.Certificate) {
require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Nomad Agent CA"))
}, },
}, },
} }
@ -97,5 +96,4 @@ func TestCACreateCommand(t *testing.T) {
require.NoError(t, os.Remove(tc.keyPath)) require.NoError(t, os.Remove(tc.keyPath))
}) })
} }
} }

View File

@ -14,6 +14,7 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"net" "net"
@ -66,7 +67,13 @@ type CAOpts struct {
Serial *big.Int Serial *big.Int
Days int Days int
PermittedDNSDomains []string PermittedDNSDomains []string
Domain string Country string
PostalCode string
Province string
Locality string
StreetAddress string
Organization string
OrganizationalUnit string
Name string Name string
} }
@ -81,10 +88,28 @@ type CertOpts struct {
ExtKeyUsage []x509.ExtKeyUsage ExtKeyUsage []x509.ExtKeyUsage
} }
// IsCustom checks whether any of CAOpts parameters have been populated with
// non-default values.
func (c *CAOpts) IsCustom() bool {
return c.Country == "" &&
c.PostalCode == "" &&
c.Province == "" &&
c.Locality == "" &&
c.StreetAddress == "" &&
c.Organization == "" &&
c.OrganizationalUnit == "" &&
c.Name == ""
}
// GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS) // GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS)
func GenerateCA(opts CAOpts) (string, string, error) { func GenerateCA(opts CAOpts) (string, string, error) {
signer := opts.Signer var (
var pk string id []byte
pk string
err error
signer = opts.Signer
sn = opts.Serial
)
if signer == nil { if signer == nil {
var err error var err error
signer, pk, err = GeneratePrivateKey() signer, pk, err = GeneratePrivateKey()
@ -93,12 +118,11 @@ func GenerateCA(opts CAOpts) (string, string, error) {
} }
} }
id, err := keyID(signer.Public()) id, err = keyID(signer.Public())
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
sn := opts.Serial
if sn == nil { if sn == nil {
var err error var err error
sn, err = GenerateSerialNumber() sn, err = GenerateSerialNumber()
@ -106,32 +130,55 @@ func GenerateCA(opts CAOpts) (string, string, error) {
return "", "", err return "", "", err
} }
} }
name := opts.Name
if name == "" { if opts.IsCustom() {
name = fmt.Sprintf("Nomad Agent CA %d", sn) opts.Name = fmt.Sprintf("Nomad Agent CA %d", sn)
if opts.Days == 0 {
opts.Days = 1825
}
opts.Country = "US"
opts.PostalCode = "94105"
opts.Province = "CA"
opts.Locality = "San Francisco"
opts.StreetAddress = "101 Second Street"
opts.Organization = "HashiCorp Inc."
opts.OrganizationalUnit = "Nomad"
} else {
if opts.Name == "" {
return "", "", errors.New("common name value not provided")
} else {
opts.Name = fmt.Sprintf("%s %d", opts.Name, sn)
}
if opts.Country == "" {
return "", "", errors.New("country value not provided")
} }
days := opts.Days if opts.Organization == "" {
if opts.Days == 0 { return "", "", errors.New("organization value not provided")
days = 365 }
if opts.OrganizationalUnit == "" {
return "", "", errors.New("organizational unit value not provided")
}
} }
// Create the CA cert // Create the CA cert
template := x509.Certificate{ template := x509.Certificate{
SerialNumber: sn, SerialNumber: sn,
Subject: pkix.Name{ Subject: pkix.Name{
Country: []string{"US"}, Country: []string{opts.Country},
PostalCode: []string{"94105"}, PostalCode: []string{opts.PostalCode},
Province: []string{"CA"}, Province: []string{opts.Province},
Locality: []string{"San Francisco"}, Locality: []string{opts.Locality},
StreetAddress: []string{"101 Second Street"}, StreetAddress: []string{opts.StreetAddress},
Organization: []string{"HashiCorp Inc."}, Organization: []string{opts.Organization},
CommonName: name, OrganizationalUnit: []string{opts.OrganizationalUnit},
CommonName: opts.Name,
}, },
BasicConstraintsValid: true, BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
IsCA: true, IsCA: true,
NotAfter: time.Now().AddDate(0, 0, days), NotAfter: time.Now().AddDate(0, 0, opts.Days),
NotBefore: time.Now(), NotBefore: time.Now(),
AuthorityKeyId: id, AuthorityKeyId: id,
SubjectKeyId: id, SubjectKeyId: id,

View File

@ -94,7 +94,7 @@ func TestGenerateCA(t *testing.T) {
require.Equal(t, true, cert.BasicConstraintsValid) require.Equal(t, true, cert.BasicConstraintsValid)
require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute) require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 365), time.Minute) require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 1825), time.Minute)
require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage) require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
}) })
@ -112,10 +112,110 @@ func TestGenerateCA(t *testing.T) {
require.Equal(t, true, cert.BasicConstraintsValid) require.Equal(t, true, cert.BasicConstraintsValid)
require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute) require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 365), time.Minute) require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 1825), time.Minute)
require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage) require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
}) })
t.Run("Custom CA", func(t *testing.T) {
ca, pk, err := GenerateCA(CAOpts{
Days: 6,
PermittedDNSDomains: []string{"domain1.com"},
Country: "ZZ",
PostalCode: "0000",
Province: "CustProvince",
Locality: "CustLocality",
StreetAddress: "CustStreet",
Organization: "CustOrg",
OrganizationalUnit: "CustUnit",
Name: "Custom CA",
})
require.NoError(t, err)
require.NotEmpty(t, ca)
require.NotEmpty(t, pk)
cert, err := parseCert(ca)
require.NoError(t, err)
require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Custom CA"))
require.True(t, strings.Contains(cert.PermittedDNSDomains[0], "domain1.com"))
require.True(t, strings.Contains(cert.Subject.Country[0], "ZZ"))
require.True(t, strings.Contains(cert.Subject.PostalCode[0], "0000"))
require.True(t, strings.Contains(cert.Subject.Province[0], "CustProvince"))
require.True(t, strings.Contains(cert.Subject.Locality[0], "CustLocality"))
require.True(t, strings.Contains(cert.Subject.StreetAddress[0], "CustStreet"))
require.True(t, strings.Contains(cert.Subject.Organization[0], "CustOrg"))
require.True(t, strings.Contains(cert.Subject.OrganizationalUnit[0], "CustUnit"))
require.Equal(t, true, cert.IsCA)
require.Equal(t, true, cert.BasicConstraintsValid)
require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 6), time.Minute)
require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
})
t.Run("Custom CA Custom Date", func(t *testing.T) {
ca, pk, err := GenerateCA(CAOpts{
Days: 365,
})
require.NoError(t, err)
require.NotEmpty(t, ca)
require.NotEmpty(t, pk)
cert, err := parseCert(ca)
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 365), time.Minute)
})
t.Run("Custom CA No CN", func(t *testing.T) {
ca, pk, err := GenerateCA(CAOpts{
Days: 6,
PermittedDNSDomains: []string{"domain1.com"},
Locality: "CustLocality",
})
require.ErrorContains(t, err, "common name value not provided")
require.Empty(t, ca)
require.Empty(t, pk)
})
t.Run("Custom CA No Country", func(t *testing.T) {
ca, pk, err := GenerateCA(CAOpts{
Days: 6,
PermittedDNSDomains: []string{"domain1.com"},
Name: "Custom CA",
Locality: "CustLocality",
})
require.ErrorContains(t, err, "country value not provided")
require.Empty(t, ca)
require.Empty(t, pk)
})
t.Run("Custom CA No Organization", func(t *testing.T) {
ca, pk, err := GenerateCA(CAOpts{
Days: 6,
PermittedDNSDomains: []string{"domain1.com"},
Name: "Custom CA",
Country: "ZZ",
Locality: "CustLocality",
})
require.ErrorContains(t, err, "organization value not provided")
// require.NoError(t, err)
require.Empty(t, ca)
require.Empty(t, pk)
})
t.Run("Custom CA No Organizational Unit", func(t *testing.T) {
ca, pk, err := GenerateCA(CAOpts{
Days: 6,
PermittedDNSDomains: []string{"domain1.com"},
Name: "Custom CA",
Country: "ZZ",
Locality: "CustLocality",
Organization: "CustOrg",
})
require.ErrorContains(t, err, "organizational unit value not provided")
require.Empty(t, ca)
require.Empty(t, pk)
})
} }
func TestGenerateCert(t *testing.T) { func TestGenerateCert(t *testing.T) {
@ -123,10 +223,16 @@ func TestGenerateCert(t *testing.T) {
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.Nil(t, err) require.Nil(t, err)
ca, _, err := GenerateCA(CAOpts{Signer: signer}) ca, _, err := GenerateCA(CAOpts{
Name: "Custom CA",
Country: "ZZ",
Organization: "CustOrg",
OrganizationalUnit: "CustOrgUnit",
Signer: signer},
)
require.Nil(t, err) require.Nil(t, err)
DNSNames := []string{"server.dc1.consul"} DNSNames := []string{"server.dc1.nomad"}
IPAddresses := []net.IP{net.ParseIP("123.234.243.213")} IPAddresses := []net.IP{net.ParseIP("123.234.243.213")}
extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
name := "Cert Name" name := "Cert Name"
@ -150,7 +256,7 @@ func TestGenerateCert(t *testing.T) {
caID, err := keyID(signer.Public()) caID, err := keyID(signer.Public())
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, caID, cert.AuthorityKeyId) require.Equal(t, caID, cert.AuthorityKeyId)
require.Contains(t, cert.Issuer.CommonName, "Nomad Agent CA") require.Contains(t, cert.Issuer.CommonName, "Custom CA")
require.Equal(t, false, cert.IsCA) require.Equal(t, false, cert.IsCA)
require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute) require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)

View File

@ -1370,7 +1370,13 @@ func newTLSTestHelper(t *testing.T) tlsTestHelper {
} }
// Generate CA certificate and write it to disk. // Generate CA certificate and write it to disk.
h.caPEM, h.pk, err = tlsutil.GenerateCA(tlsutil.CAOpts{Days: 5, Domain: "nomad"}) h.caPEM, h.pk, err = tlsutil.GenerateCA(tlsutil.CAOpts{
Name: "Nomad CA",
Country: "ZZ",
Days: 5,
Organization: "CustOrgUnit",
OrganizationalUnit: "CustOrgUnit",
})
must.NoError(t, err) must.NoError(t, err)
err = os.WriteFile(filepath.Join(h.dir, "ca.pem"), []byte(h.caPEM), 0600) err = os.WriteFile(filepath.Join(h.dir, "ca.pem"), []byte(h.caPEM), 0600)

View File

@ -24,7 +24,9 @@ nomad tls ca create [options]
`-additional-domain`. Can be used multiple times. This option can only used in `-additional-domain`. Can be used multiple times. This option can only used in
combination with `-domain` and `-name-constraint`. combination with `-domain` and `-name-constraint`.
- `common-name`: Common Name of CA. Defaults to Nomad Agent CA. - `-common-name`: Common Name of CA. Defaults to Nomad Agent CA.
- `-country`: Country of the CA. Defaults to "US".
- `-days=<int>`: Provide number of days the CA is valid for from now on, - `-days=<int>`: Provide number of days the CA is valid for from now on,
defaults to 5 years. defaults to 5 years.
@ -32,6 +34,8 @@ nomad tls ca create [options]
- `-domain=<string>`: Domain of nomad cluster. Only used in combination with - `-domain=<string>`: Domain of nomad cluster. Only used in combination with
`-name-constraint`. Defaults to `nomad`. `-name-constraint`. Defaults to `nomad`.
- `-locality`: Locality of the CA. Defaults to "San Francisco".
- `-name-constraint`: Add name constraints for the CA. Results in rejecting - `-name-constraint`: Add name constraints for the CA. Results in rejecting
certificates for other DNS than specified. If set to true, "localhost" and certificates for other DNS than specified. If set to true, "localhost" and
`-domain` will be added to the allowed DNS. Defaults to false. `-domain` will be added to the allowed DNS. Defaults to false.
@ -40,6 +44,16 @@ nomad tls ca create [options]
Nomad web UI over HTTPS its DNS must be added with `additional-domain`. It is Nomad web UI over HTTPS its DNS must be added with `additional-domain`. It is
not possible to add that after the fact. not possible to add that after the fact.
- `-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".
## Example ## Example
Create CA: Create CA: