2019-11-19 18:00:20 +01:00
package main
import (
"crypto/rand"
"flag"
"fmt"
"io"
"io/ioutil"
2019-12-18 02:59:21 +01:00
"net"
2019-11-19 18:00:20 +01:00
"os"
"strings"
"time"
2021-02-12 01:53:25 +01:00
"github.com/skip2/go-qrcode"
2019-11-19 18:00:20 +01:00
"github.com/slackhq/nebula/cert"
2019-12-12 05:15:57 +01:00
"golang.org/x/crypto/ed25519"
2019-11-19 18:00:20 +01:00
)
type caFlags struct {
set * flag . FlagSet
name * string
duration * time . Duration
outKeyPath * string
outCertPath * string
2021-02-12 01:53:25 +01:00
outQRPath * string
2019-11-19 18:00:20 +01:00
groups * string
2019-12-18 02:59:21 +01:00
ips * string
subnets * string
2019-11-19 18:00:20 +01:00
}
func newCaFlags ( ) * caFlags {
cf := caFlags { set : flag . NewFlagSet ( "ca" , flag . ContinueOnError ) }
cf . set . Usage = func ( ) { }
cf . name = cf . set . String ( "name" , "" , "Required: name of the certificate authority" )
cf . duration = cf . set . Duration ( "duration" , time . Duration ( time . Hour * 8760 ) , "Optional: amount of time the certificate should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\"" )
cf . outKeyPath = cf . set . String ( "out-key" , "ca.key" , "Optional: path to write the private key to" )
cf . outCertPath = cf . set . String ( "out-crt" , "ca.crt" , "Optional: path to write the certificate to" )
2021-02-12 01:53:25 +01:00
cf . outQRPath = cf . set . String ( "out-qr" , "" , "Optional: output a qr code image (png) of the certificate" )
2019-11-19 18:00:20 +01:00
cf . groups = cf . set . String ( "groups" , "" , "Optional: comma separated list of groups. This will limit which groups subordinate certs can use" )
2021-12-08 04:40:30 +01:00
cf . ips = cf . set . String ( "ips" , "" , "Optional: comma separated list of ipv4 address and network in CIDR notation. This will limit which ipv4 addresses and networks subordinate certs can use for ip addresses" )
cf . subnets = cf . set . String ( "subnets" , "" , "Optional: comma separated list of ipv4 address and network in CIDR notation. This will limit which ipv4 addresses and networks subordinate certs can use in subnets" )
2019-11-19 18:00:20 +01:00
return & cf
}
func ca ( args [ ] string , out io . Writer , errOut io . Writer ) error {
cf := newCaFlags ( )
err := cf . set . Parse ( args )
if err != nil {
return err
}
if err := mustFlagString ( "name" , cf . name ) ; err != nil {
return err
}
if err := mustFlagString ( "out-key" , cf . outKeyPath ) ; err != nil {
return err
}
if err := mustFlagString ( "out-crt" , cf . outCertPath ) ; err != nil {
return err
}
if * cf . duration <= 0 {
return & helpError { "-duration must be greater than 0" }
}
2019-12-18 02:59:21 +01:00
var groups [ ] string
2019-11-19 18:00:20 +01:00
if * cf . groups != "" {
for _ , rg := range strings . Split ( * cf . groups , "," ) {
g := strings . TrimSpace ( rg )
if g != "" {
groups = append ( groups , g )
}
}
}
2019-12-18 02:59:21 +01:00
var ips [ ] * net . IPNet
if * cf . ips != "" {
for _ , rs := range strings . Split ( * cf . ips , "," ) {
rs := strings . Trim ( rs , " " )
if rs != "" {
ip , ipNet , err := net . ParseCIDR ( rs )
if err != nil {
return newHelpErrorf ( "invalid ip definition: %s" , err )
}
2021-12-08 04:40:30 +01:00
if ip . To4 ( ) == nil {
return newHelpErrorf ( "invalid ip definition: can only be ipv4, have %s" , rs )
}
2019-12-18 02:59:21 +01:00
ipNet . IP = ip
ips = append ( ips , ipNet )
}
}
}
var subnets [ ] * net . IPNet
if * cf . subnets != "" {
for _ , rs := range strings . Split ( * cf . subnets , "," ) {
rs := strings . Trim ( rs , " " )
if rs != "" {
_ , s , err := net . ParseCIDR ( rs )
if err != nil {
return newHelpErrorf ( "invalid subnet definition: %s" , err )
}
2021-12-08 04:40:30 +01:00
if s . IP . To4 ( ) == nil {
return newHelpErrorf ( "invalid subnet definition: can only be ipv4, have %s" , rs )
}
2019-12-18 02:59:21 +01:00
subnets = append ( subnets , s )
}
}
}
2019-11-19 18:00:20 +01:00
pub , rawPriv , err := ed25519 . GenerateKey ( rand . Reader )
if err != nil {
return fmt . Errorf ( "error while generating ed25519 keys: %s" , err )
}
nc := cert . NebulaCertificate {
Details : cert . NebulaCertificateDetails {
Name : * cf . name ,
Groups : groups ,
2019-12-18 02:59:21 +01:00
Ips : ips ,
Subnets : subnets ,
2019-11-19 18:00:20 +01:00
NotBefore : time . Now ( ) ,
NotAfter : time . Now ( ) . Add ( * cf . duration ) ,
PublicKey : pub ,
IsCA : true ,
} ,
}
if _ , err := os . Stat ( * cf . outKeyPath ) ; err == nil {
return fmt . Errorf ( "refusing to overwrite existing CA key: %s" , * cf . outKeyPath )
}
if _ , err := os . Stat ( * cf . outCertPath ) ; err == nil {
return fmt . Errorf ( "refusing to overwrite existing CA cert: %s" , * cf . outCertPath )
}
err = nc . Sign ( rawPriv )
if err != nil {
return fmt . Errorf ( "error while signing: %s" , err )
}
err = ioutil . WriteFile ( * cf . outKeyPath , cert . MarshalEd25519PrivateKey ( rawPriv ) , 0600 )
if err != nil {
return fmt . Errorf ( "error while writing out-key: %s" , err )
}
b , err := nc . MarshalToPEM ( )
if err != nil {
return fmt . Errorf ( "error while marshalling certificate: %s" , err )
}
err = ioutil . WriteFile ( * cf . outCertPath , b , 0600 )
if err != nil {
return fmt . Errorf ( "error while writing out-crt: %s" , err )
}
2021-02-12 01:53:25 +01:00
if * cf . outQRPath != "" {
b , err = qrcode . Encode ( string ( b ) , qrcode . Medium , - 5 )
if err != nil {
return fmt . Errorf ( "error while generating qr code: %s" , err )
}
err = ioutil . WriteFile ( * cf . outQRPath , b , 0600 )
if err != nil {
return fmt . Errorf ( "error while writing out-qr: %s" , err )
}
}
2019-11-19 18:00:20 +01:00
return nil
}
func caSummary ( ) string {
return "ca <flags>: create a self signed certificate authority"
}
func caHelp ( out io . Writer ) {
cf := newCaFlags ( )
out . Write ( [ ] byte ( "Usage of " + os . Args [ 0 ] + " " + caSummary ( ) + "\n" ) )
cf . set . SetOutput ( out )
cf . set . PrintDefaults ( )
}