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:
parent
2b85290d55
commit
0455389534
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
cli: Add the ability to customize the details of the CA when running `nomad tls ca create`
|
||||||
|
```
|
|
@ -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 == ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue