Skip to content

Commit

Permalink
Add command "nomad tls" (#14296)
Browse files Browse the repository at this point in the history
  • Loading branch information
lhaig committed Nov 22, 2022
1 parent 612f0a6 commit 8667dc2
Show file tree
Hide file tree
Showing 23 changed files with 1,510 additions and 12 deletions.
4 changes: 4 additions & 0 deletions .changelog/14296.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```release-note:improvement
cli: Added tls command to enable creating Certificate Authority and Self signed TLS certificates.
There are two sub commands `tls ca` and `tls cert` that are helpers when creating certificates.
```
35 changes: 35 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,41 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
Meta: meta,
}, nil
},
"tls": func() (cli.Command, error) {
return &TLSCommand{
Meta: meta,
}, nil
},
"tls ca": func() (cli.Command, error) {
return &TLSCACommand{
Meta: meta,
}, nil
},
"tls ca create": func() (cli.Command, error) {
return &TLSCACreateCommand{
Meta: meta,
}, nil
},
"tls ca info": func() (cli.Command, error) {
return &TLSCAInfoCommand{
Meta: meta,
}, nil
},
"tls cert": func() (cli.Command, error) {
return &TLSCertCommand{
Meta: meta,
}, nil
},
"tls cert create": func() (cli.Command, error) {
return &TLSCertCreateCommand{
Meta: meta,
}, nil
},
"tls cert info": func() (cli.Command, error) {
return &TLSCertInfoCommand{
Meta: meta,
}, nil
},
"ui": func() (cli.Command, error) {
return &UiCommand{
Meta: meta,
Expand Down
56 changes: 56 additions & 0 deletions command/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package command

import (
"os"
"strings"

"github.com/mitchellh/cli"
)

type TLSCommand struct {
Meta
}

func fileDoesNotExist(file string) bool {
if _, err := os.Stat(file); os.IsNotExist(err) {
return true
}
return false
}

func (c *TLSCommand) Help() string {
helpText := `
Usage: nomad tls <subcommand> <subcommand> [options]
This command groups subcommands for creating certificates for Nomad TLS configuration.
The TLS command allows operators to generate self signed certificates to use
when securing your Nomad cluster.
Some simple examples for creating certificates can be found here.
More detailed examples are available in the subcommands or the documentation.
Create a CA
$ nomad tls ca create
Create a server certificate
$ nomad tls cert create -server
Create a client certificate
$ nomad tls cert create -client
`
return strings.TrimSpace(helpText)
}

func (c *TLSCommand) Synopsis() string {
return "Generate Self Signed TLS Certificates for Nomad"
}

func (c *TLSCommand) Name() string { return "tls" }

func (c *TLSCommand) Run(_ []string) int {
return cli.RunResultHelp
}
44 changes: 44 additions & 0 deletions command/tls_ca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package command

import (
"strings"

"github.com/mitchellh/cli"
"github.com/posener/complete"
)

type TLSCACommand struct {
Meta
}

func (c *TLSCACommand) Help() string {
helpText := `
Usage: nomad tls ca <subcommand> [options]
This command groups subcommands for interacting with certificate authorities.
For examples, see the documentation.
Create a certificate authority.
$ nomad tls ca create
Show information about a certificate authority.
$ nomad tls ca info
`
return strings.TrimSpace(helpText)
}

func (c *TLSCACommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}

func (c *TLSCACommand) Synopsis() string {
return "Helpers for managing certificate authorities"
}

func (c *TLSCACommand) Name() string { return "tls ca" }

func (c *TLSCACommand) Run(_ []string) int {
return cli.RunResultHelp
}
162 changes: 162 additions & 0 deletions command/tls_ca_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
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
}

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".
-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".
-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.
`
return strings.TrimSpace(helpText)
}

func (c *TLSCACreateCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-additional-domain": complete.PredictAnything,
"-common-name": complete.PredictAnything,
"-days": complete.PredictAnything,
"-domain": complete.PredictAnything,
"-name-constraint": complete.PredictAnything,
})
}

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", "")
flagSet.IntVar(&c.days, "days", 1825, "")
flagSet.BoolVar(&c.constraint, "name-constraint", false, "")
flagSet.StringVar(&c.domain, "domain", "nomad", "")
flagSet.StringVar(&c.commonName, "common-name", "", "")
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
}
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 {
constraints = []string{c.domain, "localhost"}
constraints = append(constraints, c.additionalDomain...)
}

ca, pk, err := tlsutil.GenerateCA(tlsutil.CAOpts{Name: c.commonName, Days: c.days, Domain: c.domain, PermittedDNSDomains: constraints})
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
}
Loading

0 comments on commit 8667dc2

Please sign in to comment.