2023-03-28 19:12:30 +00:00
// Copyright (c) HashiCorp, Inc.
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
2023-03-28 19:12:30 +00:00
2018-12-19 08:22:49 +00:00
package create
import (
"crypto/x509"
"flag"
"fmt"
"net"
2022-11-10 16:26:01 +00:00
"os"
2018-12-19 08:22:49 +00:00
"strings"
"github.com/hashicorp/consul/command/flags"
"github.com/hashicorp/consul/command/tls"
2020-01-31 16:12:36 +00:00
"github.com/hashicorp/consul/lib/file"
2019-06-27 20:22:07 +00:00
"github.com/hashicorp/consul/tlsutil"
2018-12-19 08:22:49 +00:00
"github.com/mitchellh/cli"
)
func New ( ui cli . Ui ) * cmd {
c := & cmd { UI : ui }
c . init ( )
return c
}
type cmd struct {
2019-04-04 12:32:02 +00:00
UI cli . Ui
flags * flag . FlagSet
ca string
key string
server bool
client bool
cli bool
dc string
days int
domain string
help string
2020-03-09 20:59:02 +00:00
node string
2019-04-04 12:32:02 +00:00
dnsnames flags . AppendSliceValue
ipaddresses flags . AppendSliceValue
prefix string
2018-12-19 08:22:49 +00:00
}
func ( c * cmd ) init ( ) {
c . flags = flag . NewFlagSet ( "" , flag . ContinueOnError )
2019-01-23 18:14:28 +00:00
c . flags . StringVar ( & c . ca , "ca" , "#DOMAIN#-agent-ca.pem" , "Provide path to the ca. Defaults to #DOMAIN#-agent-ca.pem." )
2018-12-19 08:22:49 +00:00
c . flags . StringVar ( & c . key , "key" , "#DOMAIN#-agent-ca-key.pem" , "Provide path to the key. Defaults to #DOMAIN#-agent-ca-key.pem." )
c . flags . BoolVar ( & c . server , "server" , false , "Generate server certificate." )
c . flags . BoolVar ( & c . client , "client" , false , "Generate client certificate." )
2020-03-09 20:59:02 +00:00
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>." )
2018-12-19 08:22:49 +00:00
c . flags . BoolVar ( & c . cli , "cli" , false , "Generate cli certificate." )
c . flags . IntVar ( & c . days , "days" , 365 , "Provide number of days the certificate is valid for from now on. Defaults to 1 year." )
c . flags . StringVar ( & c . dc , "dc" , "dc1" , "Provide the datacenter. Matters only for -server certificates. Defaults to dc1." )
c . flags . StringVar ( & c . domain , "domain" , "consul" , "Provide the domain. Matters only for -server certificates." )
c . flags . Var ( & c . dnsnames , "additional-dnsname" , "Provide an additional dnsname for Subject Alternative Names. " +
2019-04-04 12:32:02 +00:00
"localhost is always included. This flag may be provided multiple times." )
c . flags . Var ( & c . ipaddresses , "additional-ipaddress" , "Provide an additional ipaddress for Subject Alternative Names. " +
"127.0.0.1 is always included. This flag may be provided multiple times." )
2018-12-19 08:22:49 +00:00
c . help = flags . Usage ( help , c . flags )
}
func ( c * cmd ) Run ( args [ ] string ) int {
if err := c . flags . Parse ( args ) ; err != nil {
if err == flag . ErrHelp {
return 0
}
c . UI . Error ( fmt . Sprintf ( "Failed to parse args: %v" , err ) )
return 1
}
if c . ca == "" {
c . UI . Error ( "Please provide the ca" )
return 1
}
if c . key == "" {
c . UI . Error ( "Please provide the key" )
return 1
}
if ! ( ( c . server && ! c . client && ! c . cli ) ||
( ! c . server && c . client && ! c . cli ) ||
( ! c . server && ! c . client && c . cli ) ) {
c . UI . Error ( "Please provide either -server, -client, or -cli" )
return 1
}
2020-03-09 20:59:02 +00:00
if c . node != "" && ! c . server {
c . UI . Error ( "-node requires -server" )
return 1
}
2018-12-19 08:22:49 +00:00
var DNSNames [ ] string
var IPAddresses [ ] net . IP
var extKeyUsage [ ] x509 . ExtKeyUsage
var name , prefix string
for _ , d := range c . dnsnames {
if len ( d ) > 0 {
DNSNames = append ( DNSNames , strings . TrimSpace ( d ) )
}
}
2019-04-04 12:32:02 +00:00
for _ , i := range c . ipaddresses {
if len ( i ) > 0 {
IPAddresses = append ( IPAddresses , net . ParseIP ( strings . TrimSpace ( i ) ) )
}
}
2018-12-19 08:22:49 +00:00
if c . server {
name = fmt . Sprintf ( "server.%s.%s" , c . dc , c . domain )
2020-01-31 16:12:36 +00:00
2020-03-09 20:59:02 +00:00
if c . node != "" {
nodeName := fmt . Sprintf ( "%s.server.%s.%s" , c . node , c . dc , c . domain )
DNSNames = append ( DNSNames , nodeName )
}
2020-01-31 16:12:36 +00:00
DNSNames = append ( DNSNames , name )
DNSNames = append ( DNSNames , "localhost" )
2019-04-04 12:32:02 +00:00
IPAddresses = append ( IPAddresses , net . ParseIP ( "127.0.0.1" ) )
2018-12-19 08:22:49 +00:00
extKeyUsage = [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth , x509 . ExtKeyUsageClientAuth }
prefix = fmt . Sprintf ( "%s-server-%s" , c . dc , c . domain )
2020-01-31 16:12:36 +00:00
2018-12-19 08:22:49 +00:00
} else if c . client {
name = fmt . Sprintf ( "client.%s.%s" , c . dc , c . domain )
DNSNames = append ( DNSNames , [ ] string { name , "localhost" } ... )
2019-04-04 12:32:02 +00:00
IPAddresses = append ( IPAddresses , net . ParseIP ( "127.0.0.1" ) )
2018-12-19 08:22:49 +00:00
extKeyUsage = [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageClientAuth , x509 . ExtKeyUsageServerAuth }
prefix = fmt . Sprintf ( "%s-client-%s" , c . dc , c . domain )
} else if c . cli {
name = fmt . Sprintf ( "cli.%s.%s" , c . dc , c . domain )
DNSNames = [ ] string { name , "localhost" }
prefix = fmt . Sprintf ( "%s-cli-%s" , c . dc , c . domain )
} else {
c . UI . Error ( "Neither client, cli nor server - should not happen" )
return 1
}
var pkFileName , certFileName string
max := 10000
for i := 0 ; i <= max ; i ++ {
tmpCert := fmt . Sprintf ( "%s-%d.pem" , prefix , i )
tmpPk := fmt . Sprintf ( "%s-%d-key.pem" , prefix , i )
if tls . FileDoesNotExist ( tmpCert ) && tls . FileDoesNotExist ( tmpPk ) {
certFileName = tmpCert
pkFileName = tmpPk
break
}
if i == max {
c . UI . Error ( "Could not find a filename that doesn't already exist" )
return 1
}
}
caFile := strings . Replace ( c . ca , "#DOMAIN#" , c . domain , 1 )
keyFile := strings . Replace ( c . key , "#DOMAIN#" , c . domain , 1 )
2022-11-10 16:26:01 +00:00
cert , err := os . ReadFile ( caFile )
2018-12-19 08:22:49 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error reading CA: %s" , err ) )
return 1
}
2022-11-10 16:26:01 +00:00
key , err := os . ReadFile ( keyFile )
2018-12-19 08:22:49 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error reading CA key: %s" , err ) )
return 1
}
if c . server {
c . UI . Info (
` == > WARNING : Server Certificates grants authority to become a
server and access all state in the cluster including root keys
and all ACL tokens . Do not distribute them to production hosts
that are not server nodes . Store them as securely as CA keys . ` )
}
c . UI . Info ( "==> Using " + caFile + " and " + keyFile )
2019-06-27 20:22:07 +00:00
signer , err := tlsutil . ParseSigner ( string ( key ) )
2018-12-19 08:22:49 +00:00
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
2021-03-22 09:16:41 +00:00
pub , priv , err := tlsutil . GenerateCert ( tlsutil . CertOpts {
Signer : signer , CA : string ( cert ) , Name : name , Days : c . days ,
DNSNames : DNSNames , IPAddresses : IPAddresses , ExtKeyUsage : extKeyUsage ,
} )
2018-12-19 08:22:49 +00:00
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
2019-06-27 20:22:07 +00:00
if err = tlsutil . Verify ( string ( cert ) , pub , name ) ; err != nil {
2020-01-31 16:12:36 +00:00
c . UI . Error ( err . Error ( ) )
2018-12-19 08:22:49 +00:00
return 1
}
2020-01-31 16:12:36 +00:00
if err := file . WriteAtomicWithPerms ( certFileName , [ ] byte ( pub ) , 0755 , 0666 ) ; err != nil {
2018-12-19 08:22:49 +00:00
c . UI . Error ( err . Error ( ) )
return 1
}
c . UI . Output ( "==> Saved " + certFileName )
2021-12-08 19:16:36 +00:00
if err := file . WriteAtomicWithPerms ( pkFileName , [ ] byte ( priv ) , 0755 , 0600 ) ; err != nil {
2018-12-19 08:22:49 +00:00
c . UI . Error ( err . Error ( ) )
return 1
}
c . UI . Output ( "==> Saved " + pkFileName )
return 0
}
func ( c * cmd ) Synopsis ( ) string {
return synopsis
}
func ( c * cmd ) Help ( ) string {
return c . help
}
const synopsis = "Create a new certificate"
const help = `
Usage : consul tls cert create [ options ]
Create a new certificate
$ consul tls cert create - server
== > WARNING : Server Certificates grants authority to become a
server and access all state in the cluster including root keys
and all ACL tokens . Do not distribute them to production hosts
that are not server nodes . Store them as securely as CA keys .
== > Using consul - agent - ca . pem and consul - agent - ca - key . pem
2020-03-17 20:00:45 +00:00
== > Saved dc1 - server - consul - 0. pem
== > Saved dc1 - server - consul - 0 - key . pem
2020-07-30 18:46:42 +00:00
$ consul tls cert create - client
2018-12-19 08:22:49 +00:00
== > Using consul - agent - ca . pem and consul - agent - ca - key . pem
2020-03-17 20:00:45 +00:00
== > Saved dc1 - client - consul - 0. pem
== > Saved dc1 - client - consul - 0 - key . pem
2018-12-19 08:22:49 +00:00
`