github.com/criteo-forks/consul@v1.4.5-criteonogrpc/command/tls/cert/create/tls_cert_create.go (about)

     1  package create
     2  
     3  import (
     4  	"crypto/x509"
     5  	"flag"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/consul/command/flags"
    13  	"github.com/hashicorp/consul/command/tls"
    14  	"github.com/mitchellh/cli"
    15  )
    16  
    17  func New(ui cli.Ui) *cmd {
    18  	c := &cmd{UI: ui}
    19  	c.init()
    20  	return c
    21  }
    22  
    23  type cmd struct {
    24  	UI       cli.Ui
    25  	flags    *flag.FlagSet
    26  	ca       string
    27  	key      string
    28  	server   bool
    29  	client   bool
    30  	cli      bool
    31  	dc       string
    32  	days     int
    33  	domain   string
    34  	help     string
    35  	dnsnames flags.AppendSliceValue
    36  	prefix   string
    37  }
    38  
    39  func (c *cmd) init() {
    40  	c.flags = flag.NewFlagSet("", flag.ContinueOnError)
    41  	c.flags.StringVar(&c.ca, "ca", "#DOMAIN#-agent-ca.pem", "Provide path to the ca. Defaults to #DOMAIN#-agent-ca.pem.")
    42  	c.flags.StringVar(&c.key, "key", "#DOMAIN#-agent-ca-key.pem", "Provide path to the key. Defaults to #DOMAIN#-agent-ca-key.pem.")
    43  	c.flags.BoolVar(&c.server, "server", false, "Generate server certificate.")
    44  	c.flags.BoolVar(&c.client, "client", false, "Generate client certificate.")
    45  	c.flags.BoolVar(&c.cli, "cli", false, "Generate cli certificate.")
    46  	c.flags.IntVar(&c.days, "days", 365, "Provide number of days the certificate is valid for from now on. Defaults to 1 year.")
    47  	c.flags.StringVar(&c.dc, "dc", "dc1", "Provide the datacenter. Matters only for -server certificates. Defaults to dc1.")
    48  	c.flags.StringVar(&c.domain, "domain", "consul", "Provide the domain. Matters only for -server certificates.")
    49  	c.flags.Var(&c.dnsnames, "additional-dnsname", "Provide an additional dnsname for Subject Alternative Names. "+
    50  		"127.0.0.1 and localhost are always included. This flag may be provided multiple times.")
    51  	c.help = flags.Usage(help, c.flags)
    52  }
    53  
    54  func (c *cmd) Run(args []string) int {
    55  	if err := c.flags.Parse(args); err != nil {
    56  		if err == flag.ErrHelp {
    57  			return 0
    58  		}
    59  		c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
    60  		return 1
    61  	}
    62  	if c.ca == "" {
    63  		c.UI.Error("Please provide the ca")
    64  		return 1
    65  	}
    66  	if c.key == "" {
    67  		c.UI.Error("Please provide the key")
    68  		return 1
    69  	}
    70  
    71  	if !((c.server && !c.client && !c.cli) ||
    72  		(!c.server && c.client && !c.cli) ||
    73  		(!c.server && !c.client && c.cli)) {
    74  		c.UI.Error("Please provide either -server, -client, or -cli")
    75  		return 1
    76  	}
    77  
    78  	var DNSNames []string
    79  	var IPAddresses []net.IP
    80  	var extKeyUsage []x509.ExtKeyUsage
    81  	var name, prefix string
    82  
    83  	for _, d := range c.dnsnames {
    84  		if len(d) > 0 {
    85  			DNSNames = append(DNSNames, strings.TrimSpace(d))
    86  		}
    87  	}
    88  
    89  	if c.server {
    90  		name = fmt.Sprintf("server.%s.%s", c.dc, c.domain)
    91  		DNSNames = append(DNSNames, []string{name, "localhost"}...)
    92  		IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
    93  		extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
    94  		prefix = fmt.Sprintf("%s-server-%s", c.dc, c.domain)
    95  	} else if c.client {
    96  		name = fmt.Sprintf("client.%s.%s", c.dc, c.domain)
    97  		DNSNames = append(DNSNames, []string{name, "localhost"}...)
    98  		IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
    99  		extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}
   100  		prefix = fmt.Sprintf("%s-client-%s", c.dc, c.domain)
   101  	} else if c.cli {
   102  		name = fmt.Sprintf("cli.%s.%s", c.dc, c.domain)
   103  		DNSNames = []string{name, "localhost"}
   104  		prefix = fmt.Sprintf("%s-cli-%s", c.dc, c.domain)
   105  	} else {
   106  		c.UI.Error("Neither client, cli nor server - should not happen")
   107  		return 1
   108  	}
   109  
   110  	var pkFileName, certFileName string
   111  	max := 10000
   112  	for i := 0; i <= max; i++ {
   113  		tmpCert := fmt.Sprintf("%s-%d.pem", prefix, i)
   114  		tmpPk := fmt.Sprintf("%s-%d-key.pem", prefix, i)
   115  		if tls.FileDoesNotExist(tmpCert) && tls.FileDoesNotExist(tmpPk) {
   116  			certFileName = tmpCert
   117  			pkFileName = tmpPk
   118  			break
   119  		}
   120  		if i == max {
   121  			c.UI.Error("Could not find a filename that doesn't already exist")
   122  			return 1
   123  		}
   124  	}
   125  
   126  	caFile := strings.Replace(c.ca, "#DOMAIN#", c.domain, 1)
   127  	keyFile := strings.Replace(c.key, "#DOMAIN#", c.domain, 1)
   128  	cert, err := ioutil.ReadFile(caFile)
   129  	if err != nil {
   130  		c.UI.Error(fmt.Sprintf("Error reading CA: %s", err))
   131  		return 1
   132  	}
   133  	key, err := ioutil.ReadFile(keyFile)
   134  	if err != nil {
   135  		c.UI.Error(fmt.Sprintf("Error reading CA key: %s", err))
   136  		return 1
   137  	}
   138  
   139  	if c.server {
   140  		c.UI.Info(
   141  			`==> WARNING: Server Certificates grants authority to become a
   142      server and access all state in the cluster including root keys
   143      and all ACL tokens. Do not distribute them to production hosts
   144      that are not server nodes. Store them as securely as CA keys.`)
   145  	}
   146  	c.UI.Info("==> Using " + caFile + " and " + keyFile)
   147  
   148  	signer, err := tls.ParseSigner(string(key))
   149  	if err != nil {
   150  		c.UI.Error(err.Error())
   151  		return 1
   152  	}
   153  
   154  	sn, err := tls.GenerateSerialNumber()
   155  	if err != nil {
   156  		c.UI.Error(err.Error())
   157  		return 1
   158  	}
   159  
   160  	pub, priv, err := tls.GenerateCert(signer, string(cert), sn, name, c.days, DNSNames, IPAddresses, extKeyUsage)
   161  	if err != nil {
   162  		c.UI.Error(err.Error())
   163  		return 1
   164  	}
   165  
   166  	if err = tls.Verify(string(cert), pub, name); err != nil {
   167  		c.UI.Error("==> " + err.Error())
   168  		return 1
   169  	}
   170  
   171  	certFile, err := os.Create(certFileName)
   172  	if err != nil {
   173  		c.UI.Error(err.Error())
   174  		return 1
   175  	}
   176  	certFile.WriteString(pub)
   177  	c.UI.Output("==> Saved " + certFileName)
   178  
   179  	pkFile, err := os.Create(pkFileName)
   180  	if err != nil {
   181  		c.UI.Error(err.Error())
   182  		return 1
   183  	}
   184  	pkFile.WriteString(priv)
   185  	c.UI.Output("==> Saved " + pkFileName)
   186  
   187  	return 0
   188  }
   189  
   190  func (c *cmd) Synopsis() string {
   191  	return synopsis
   192  }
   193  
   194  func (c *cmd) Help() string {
   195  	return c.help
   196  }
   197  
   198  const synopsis = "Create a new certificate"
   199  const help = `
   200  Usage: consul tls cert create [options]
   201  
   202    Create a new certificate
   203  
   204    $ consul tls cert create -server
   205    ==> WARNING: Server Certificates grants authority to become a
   206        server and access all state in the cluster including root keys
   207        and all ACL tokens. Do not distribute them to production hosts
   208        that are not server nodes. Store them as securely as CA keys.
   209    ==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
   210    ==> Saved consul-server-dc1-0.pem
   211    ==> Saved consul-server-dc1-0-key.pem
   212    $ consul tls cert -client
   213    ==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
   214    ==> Saved consul-client-dc1-0.pem
   215    ==> Saved consul-client-dc1-0-key.pem
   216  `