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