Public Release
This commit is contained in:
124
cmd/nebula-cert/ca.go
Normal file
124
cmd/nebula-cert/ca.go
Normal file
@ -0,0 +1,124 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
)
|
||||
|
||||
type caFlags struct {
|
||||
set *flag.FlagSet
|
||||
name *string
|
||||
duration *time.Duration
|
||||
outKeyPath *string
|
||||
outCertPath *string
|
||||
groups *string
|
||||
}
|
||||
|
||||
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")
|
||||
cf.groups = cf.set.String("groups", "", "Optional: comma separated list of groups. This will limit which groups subordinate certs can use")
|
||||
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"}
|
||||
}
|
||||
|
||||
groups := []string{}
|
||||
if *cf.groups != "" {
|
||||
for _, rg := range strings.Split(*cf.groups, ",") {
|
||||
g := strings.TrimSpace(rg)
|
||||
if g != "" {
|
||||
groups = append(groups, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
132
cmd/nebula-cert/ca_test.go
Normal file
132
cmd/nebula-cert/ca_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
)
|
||||
|
||||
//TODO: test file permissions
|
||||
|
||||
func Test_caSummary(t *testing.T) {
|
||||
assert.Equal(t, "ca <flags>: create a self signed certificate authority", caSummary())
|
||||
}
|
||||
|
||||
func Test_caHelp(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
caHelp(ob)
|
||||
assert.Equal(
|
||||
t,
|
||||
"Usage of "+os.Args[0]+" ca <flags>: create a self signed certificate authority\n"+
|
||||
" -duration duration\n"+
|
||||
" \tOptional: amount of time the certificate should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\" (default 8760h0m0s)\n"+
|
||||
" -groups string\n"+
|
||||
" \tOptional: comma separated list of groups. This will limit which groups subordinate certs can use\n"+
|
||||
" -name string\n"+
|
||||
" \tRequired: name of the certificate authority\n"+
|
||||
" -out-crt string\n"+
|
||||
" \tOptional: path to write the certificate to (default \"ca.crt\")\n"+
|
||||
" -out-key string\n"+
|
||||
" \tOptional: path to write the private key to (default \"ca.key\")\n",
|
||||
ob.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func Test_ca(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
eb := &bytes.Buffer{}
|
||||
|
||||
// required args
|
||||
assertHelpError(t, ca([]string{"-out-key", "nope", "-out-crt", "nope", "duration", "100m"}, ob, eb), "-name is required")
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// failed key write
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args := []string{"-name", "test", "-duration", "100m", "-out-crt", "/do/not/write/pleasecrt", "-out-key", "/do/not/write/pleasekey"}
|
||||
assert.EqualError(t, ca(args, ob, eb), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// create temp key file
|
||||
keyF, err := ioutil.TempFile("", "test.key")
|
||||
assert.Nil(t, err)
|
||||
os.Remove(keyF.Name())
|
||||
|
||||
// failed cert write
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-name", "test", "-duration", "100m", "-out-crt", "/do/not/write/pleasecrt", "-out-key", keyF.Name()}
|
||||
assert.EqualError(t, ca(args, ob, eb), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// create temp cert file
|
||||
crtF, err := ioutil.TempFile("", "test.crt")
|
||||
assert.Nil(t, err)
|
||||
os.Remove(crtF.Name())
|
||||
os.Remove(keyF.Name())
|
||||
|
||||
// test proper cert with removed empty groups and subnets
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
||||
assert.Nil(t, ca(args, ob, eb))
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// read cert and key files
|
||||
rb, _ := ioutil.ReadFile(keyF.Name())
|
||||
lKey, b, err := cert.UnmarshalEd25519PrivateKey(rb)
|
||||
assert.Len(t, b, 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, lKey, 64)
|
||||
|
||||
rb, _ = ioutil.ReadFile(crtF.Name())
|
||||
lCrt, b, err := cert.UnmarshalNebulaCertificateFromPEM(rb)
|
||||
assert.Len(t, b, 0)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "test", lCrt.Details.Name)
|
||||
assert.Len(t, lCrt.Details.Ips, 0)
|
||||
assert.True(t, lCrt.Details.IsCA)
|
||||
assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Details.Groups)
|
||||
assert.Len(t, lCrt.Details.Subnets, 0)
|
||||
assert.Len(t, lCrt.Details.PublicKey, 32)
|
||||
assert.Equal(t, time.Duration(time.Minute*100), lCrt.Details.NotAfter.Sub(lCrt.Details.NotBefore))
|
||||
assert.Equal(t, "", lCrt.Details.Issuer)
|
||||
assert.True(t, lCrt.CheckSignature(lCrt.Details.PublicKey))
|
||||
|
||||
// create valid cert/key for overwrite tests
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
||||
assert.Nil(t, ca(args, ob, eb))
|
||||
|
||||
// test that we won't overwrite existing certificate file
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
||||
assert.EqualError(t, ca(args, ob, eb), "refusing to overwrite existing CA key: "+keyF.Name())
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// test that we won't overwrite existing key file
|
||||
os.Remove(keyF.Name())
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
||||
assert.EqualError(t, ca(args, ob, eb), "refusing to overwrite existing CA cert: "+crtF.Name())
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
os.Remove(keyF.Name())
|
||||
|
||||
}
|
65
cmd/nebula-cert/keygen.go
Normal file
65
cmd/nebula-cert/keygen.go
Normal file
@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/slackhq/nebula/cert"
|
||||
)
|
||||
|
||||
type keygenFlags struct {
|
||||
set *flag.FlagSet
|
||||
outKeyPath *string
|
||||
outPubPath *string
|
||||
}
|
||||
|
||||
func newKeygenFlags() *keygenFlags {
|
||||
cf := keygenFlags{set: flag.NewFlagSet("keygen", flag.ContinueOnError)}
|
||||
cf.set.Usage = func() {}
|
||||
cf.outPubPath = cf.set.String("out-pub", "", "Required: path to write the public key to")
|
||||
cf.outKeyPath = cf.set.String("out-key", "", "Required: path to write the private key to")
|
||||
return &cf
|
||||
}
|
||||
|
||||
func keygen(args []string, out io.Writer, errOut io.Writer) error {
|
||||
cf := newKeygenFlags()
|
||||
err := cf.set.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mustFlagString("out-key", cf.outKeyPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mustFlagString("out-pub", cf.outPubPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pub, rawPriv := x25519Keypair()
|
||||
|
||||
err = ioutil.WriteFile(*cf.outKeyPath, cert.MarshalX25519PrivateKey(rawPriv), 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while writing out-key: %s", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(*cf.outPubPath, cert.MarshalX25519PublicKey(pub), 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while writing out-pub: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func keygenSummary() string {
|
||||
return "keygen <flags>: create a public/private key pair. the public key can be passed to `nebula-cert sign`"
|
||||
}
|
||||
|
||||
func keygenHelp(out io.Writer) {
|
||||
cf := newKeygenFlags()
|
||||
out.Write([]byte("Usage of " + os.Args[0] + " " + keygenSummary() + "\n"))
|
||||
cf.set.SetOutput(out)
|
||||
cf.set.PrintDefaults()
|
||||
}
|
92
cmd/nebula-cert/keygen_test.go
Normal file
92
cmd/nebula-cert/keygen_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
)
|
||||
|
||||
//TODO: test file permissions
|
||||
|
||||
func Test_keygenSummary(t *testing.T) {
|
||||
assert.Equal(t, "keygen <flags>: create a public/private key pair. the public key can be passed to `nebula-cert sign`", keygenSummary())
|
||||
}
|
||||
|
||||
func Test_keygenHelp(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
keygenHelp(ob)
|
||||
assert.Equal(
|
||||
t,
|
||||
"Usage of "+os.Args[0]+" keygen <flags>: create a public/private key pair. the public key can be passed to `nebula-cert sign`\n"+
|
||||
" -out-key string\n"+
|
||||
" \tRequired: path to write the private key to\n"+
|
||||
" -out-pub string\n"+
|
||||
" \tRequired: path to write the public key to\n",
|
||||
ob.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func Test_keygen(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
eb := &bytes.Buffer{}
|
||||
|
||||
// required args
|
||||
assertHelpError(t, keygen([]string{"-out-pub", "nope"}, ob, eb), "-out-key is required")
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
assertHelpError(t, keygen([]string{"-out-key", "nope"}, ob, eb), "-out-pub is required")
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// failed key write
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args := []string{"-out-pub", "/do/not/write/pleasepub", "-out-key", "/do/not/write/pleasekey"}
|
||||
assert.EqualError(t, keygen(args, ob, eb), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// create temp key file
|
||||
keyF, err := ioutil.TempFile("", "test.key")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(keyF.Name())
|
||||
|
||||
// failed pub write
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-out-pub", "/do/not/write/pleasepub", "-out-key", keyF.Name()}
|
||||
assert.EqualError(t, keygen(args, ob, eb), "error while writing out-pub: open /do/not/write/pleasepub: "+NoSuchDirError)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// create temp pub file
|
||||
pubF, err := ioutil.TempFile("", "test.pub")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(pubF.Name())
|
||||
|
||||
// test proper keygen
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-out-pub", pubF.Name(), "-out-key", keyF.Name()}
|
||||
assert.Nil(t, keygen(args, ob, eb))
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// read cert and key files
|
||||
rb, _ := ioutil.ReadFile(keyF.Name())
|
||||
lKey, b, err := cert.UnmarshalX25519PrivateKey(rb)
|
||||
assert.Len(t, b, 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, lKey, 32)
|
||||
|
||||
rb, _ = ioutil.ReadFile(pubF.Name())
|
||||
lPub, b, err := cert.UnmarshalX25519PublicKey(rb)
|
||||
assert.Len(t, b, 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, lPub, 32)
|
||||
}
|
137
cmd/nebula-cert/main.go
Normal file
137
cmd/nebula-cert/main.go
Normal file
@ -0,0 +1,137 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var Build string
|
||||
|
||||
type helpError struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (he *helpError) Error() string {
|
||||
return he.s
|
||||
}
|
||||
|
||||
func newHelpErrorf(s string, v ...interface{}) error {
|
||||
return &helpError{s: fmt.Sprintf(s, v...)}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
help("", os.Stderr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
printVersion := flag.Bool("version", false, "Print version")
|
||||
flagHelp := flag.Bool("help", false, "Print command line usage")
|
||||
flagH := flag.Bool("h", false, "Print command line usage")
|
||||
printUsage := false
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flagH || *flagHelp {
|
||||
printUsage = true
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
if *printVersion {
|
||||
fmt.Printf("Version: %v", Build)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if len(args) < 1 {
|
||||
if printUsage {
|
||||
help("", os.Stderr)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
help("No mode was provided", os.Stderr)
|
||||
os.Exit(1)
|
||||
} else if printUsage {
|
||||
handleError(args[0], &helpError{}, os.Stderr)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch args[0] {
|
||||
case "ca":
|
||||
err = ca(args[1:], os.Stdout, os.Stderr)
|
||||
case "keygen":
|
||||
err = keygen(args[1:], os.Stdout, os.Stderr)
|
||||
case "sign":
|
||||
err = signCert(args[1:], os.Stdout, os.Stderr)
|
||||
case "print":
|
||||
err = printCert(args[1:], os.Stdout, os.Stderr)
|
||||
case "verify":
|
||||
err = verify(args[1:], os.Stdout, os.Stderr)
|
||||
default:
|
||||
err = fmt.Errorf("unknown mode: %s", args[0])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
os.Exit(handleError(args[0], err, os.Stderr))
|
||||
}
|
||||
}
|
||||
|
||||
func handleError(mode string, e error, out io.Writer) int {
|
||||
code := 1
|
||||
|
||||
// Handle -help, -h flags properly
|
||||
if e == flag.ErrHelp {
|
||||
code = 0
|
||||
e = &helpError{}
|
||||
} else if e != nil && e.Error() != "" {
|
||||
fmt.Fprintln(out, "Error:", e)
|
||||
}
|
||||
|
||||
switch e.(type) {
|
||||
case *helpError:
|
||||
switch mode {
|
||||
case "ca":
|
||||
caHelp(out)
|
||||
case "keygen":
|
||||
keygenHelp(out)
|
||||
case "sign":
|
||||
signHelp(out)
|
||||
case "print":
|
||||
printHelp(out)
|
||||
case "verify":
|
||||
verifyHelp(out)
|
||||
}
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
func help(err string, out io.Writer) {
|
||||
if err != "" {
|
||||
fmt.Fprintln(out, "Error:", err)
|
||||
fmt.Fprintln(out, "")
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Usage of %s <global flags> <mode>:\n", os.Args[0])
|
||||
fmt.Fprintln(out, " Global flags:")
|
||||
fmt.Fprintln(out, " -version: Prints the version")
|
||||
fmt.Fprintln(out, " -h, -help: Prints this help message")
|
||||
fmt.Fprintln(out, "")
|
||||
fmt.Fprintln(out, " Modes:")
|
||||
fmt.Fprintln(out, " "+caSummary())
|
||||
fmt.Fprintln(out, " "+keygenSummary())
|
||||
fmt.Fprintln(out, " "+signSummary())
|
||||
fmt.Fprintln(out, " "+printSummary())
|
||||
fmt.Fprintln(out, " "+verifySummary())
|
||||
}
|
||||
|
||||
func mustFlagString(name string, val *string) error {
|
||||
if *val == "" {
|
||||
return newHelpErrorf("-%s is required", name)
|
||||
}
|
||||
return nil
|
||||
}
|
81
cmd/nebula-cert/main_test.go
Normal file
81
cmd/nebula-cert/main_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//TODO: all flag parsing continueOnError will print to stderr on its own currently
|
||||
|
||||
func Test_help(t *testing.T) {
|
||||
expected := "Usage of " + os.Args[0] + " <global flags> <mode>:\n" +
|
||||
" Global flags:\n" +
|
||||
" -version: Prints the version\n" +
|
||||
" -h, -help: Prints this help message\n\n" +
|
||||
" Modes:\n" +
|
||||
" " + caSummary() + "\n" +
|
||||
" " + keygenSummary() + "\n" +
|
||||
" " + signSummary() + "\n" +
|
||||
" " + printSummary() + "\n" +
|
||||
" " + verifySummary() + "\n"
|
||||
|
||||
ob := &bytes.Buffer{}
|
||||
|
||||
// No error test
|
||||
help("", ob)
|
||||
assert.Equal(
|
||||
t,
|
||||
expected,
|
||||
ob.String(),
|
||||
)
|
||||
|
||||
// Error test
|
||||
ob.Reset()
|
||||
help("test error", ob)
|
||||
assert.Equal(
|
||||
t,
|
||||
"Error: test error\n\n"+expected,
|
||||
ob.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func Test_handleError(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
|
||||
// normal error
|
||||
handleError("", errors.New("test error"), ob)
|
||||
assert.Equal(t, "Error: test error\n", ob.String())
|
||||
|
||||
// unknown mode help error
|
||||
ob.Reset()
|
||||
handleError("", newHelpErrorf("test %s", "error"), ob)
|
||||
assert.Equal(t, "Error: test error\n", ob.String())
|
||||
|
||||
// test all modes with help error
|
||||
modes := map[string]func(io.Writer){"ca": caHelp, "print": printHelp, "sign": signHelp, "verify": verifyHelp}
|
||||
eb := &bytes.Buffer{}
|
||||
for mode, fn := range modes {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
fn(eb)
|
||||
|
||||
handleError(mode, newHelpErrorf("test %s", "error"), ob)
|
||||
assert.Equal(t, "Error: test error\n"+eb.String(), ob.String())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func assertHelpError(t *testing.T, err error, msg string) {
|
||||
switch err.(type) {
|
||||
case *helpError:
|
||||
// good
|
||||
default:
|
||||
t.Fatal("err was not a helpError")
|
||||
}
|
||||
|
||||
assert.EqualError(t, err, msg)
|
||||
}
|
80
cmd/nebula-cert/print.go
Normal file
80
cmd/nebula-cert/print.go
Normal file
@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type printFlags struct {
|
||||
set *flag.FlagSet
|
||||
json *bool
|
||||
path *string
|
||||
}
|
||||
|
||||
func newPrintFlags() *printFlags {
|
||||
pf := printFlags{set: flag.NewFlagSet("print", flag.ContinueOnError)}
|
||||
pf.set.Usage = func() {}
|
||||
pf.json = pf.set.Bool("json", false, "Optional: outputs certificates in json format")
|
||||
pf.path = pf.set.String("path", "", "Required: path to the certificate")
|
||||
|
||||
return &pf
|
||||
}
|
||||
|
||||
func printCert(args []string, out io.Writer, errOut io.Writer) error {
|
||||
pf := newPrintFlags()
|
||||
err := pf.set.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mustFlagString("path", pf.path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawCert, err := ioutil.ReadFile(*pf.path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read cert; %s", err)
|
||||
}
|
||||
|
||||
var c *cert.NebulaCertificate
|
||||
|
||||
for {
|
||||
c, rawCert, err = cert.UnmarshalNebulaCertificateFromPEM(rawCert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshaling cert: %s", err)
|
||||
}
|
||||
|
||||
if *pf.json {
|
||||
b, _ := json.Marshal(c)
|
||||
out.Write(b)
|
||||
out.Write([]byte("\n"))
|
||||
|
||||
} else {
|
||||
out.Write([]byte(c.String()))
|
||||
out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if rawCert == nil || len(rawCert) == 0 || strings.TrimSpace(string(rawCert)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printSummary() string {
|
||||
return "print <flags>: prints details about a certificate"
|
||||
}
|
||||
|
||||
func printHelp(out io.Writer) {
|
||||
pf := newPrintFlags()
|
||||
out.Write([]byte("Usage of " + os.Args[0] + " " + printSummary() + "\n"))
|
||||
pf.set.SetOutput(out)
|
||||
pf.set.PrintDefaults()
|
||||
}
|
119
cmd/nebula-cert/print_test.go
Normal file
119
cmd/nebula-cert/print_test.go
Normal file
@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_printSummary(t *testing.T) {
|
||||
assert.Equal(t, "print <flags>: prints details about a certificate", printSummary())
|
||||
}
|
||||
|
||||
func Test_printHelp(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
printHelp(ob)
|
||||
assert.Equal(
|
||||
t,
|
||||
"Usage of "+os.Args[0]+" print <flags>: prints details about a certificate\n"+
|
||||
" -json\n"+
|
||||
" \tOptional: outputs certificates in json format\n"+
|
||||
" -path string\n"+
|
||||
" \tRequired: path to the certificate\n",
|
||||
ob.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func Test_printCert(t *testing.T) {
|
||||
// Orient our local time and avoid headaches
|
||||
time.Local = time.UTC
|
||||
ob := &bytes.Buffer{}
|
||||
eb := &bytes.Buffer{}
|
||||
|
||||
// no path
|
||||
err := printCert([]string{}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assertHelpError(t, err, "-path is required")
|
||||
|
||||
// no cert at path
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
err = printCert([]string{"-path", "does_not_exist"}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assert.EqualError(t, err, "unable to read cert; open does_not_exist: "+NoSuchFileError)
|
||||
|
||||
// invalid cert at path
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
tf, err := ioutil.TempFile("", "print-cert")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
tf.WriteString("-----BEGIN NOPE-----")
|
||||
err = printCert([]string{"-path", tf.Name()}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assert.EqualError(t, err, "error while unmarshaling cert: input did not contain a valid PEM encoded block")
|
||||
|
||||
// test multiple certs
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
tf.Truncate(0)
|
||||
tf.Seek(0, 0)
|
||||
c := cert.NebulaCertificate{
|
||||
Details: cert.NebulaCertificateDetails{
|
||||
Name: "test",
|
||||
Groups: []string{"hi"},
|
||||
PublicKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
|
||||
},
|
||||
Signature: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
|
||||
}
|
||||
|
||||
p, _ := c.MarshalToPEM()
|
||||
tf.Write(p)
|
||||
tf.Write(p)
|
||||
tf.Write(p)
|
||||
|
||||
err = printCert([]string{"-path", tf.Name()}, ob, eb)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
"NebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\n",
|
||||
ob.String(),
|
||||
)
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// test json
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
tf.Truncate(0)
|
||||
tf.Seek(0, 0)
|
||||
c = cert.NebulaCertificate{
|
||||
Details: cert.NebulaCertificateDetails{
|
||||
Name: "test",
|
||||
Groups: []string{"hi"},
|
||||
PublicKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
|
||||
},
|
||||
Signature: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
|
||||
}
|
||||
|
||||
p, _ = c.MarshalToPEM()
|
||||
tf.Write(p)
|
||||
tf.Write(p)
|
||||
tf.Write(p)
|
||||
|
||||
err = printCert([]string{"-json", "-path", tf.Name()}, ob, eb)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
"{\"details\":{\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n{\"details\":{\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n{\"details\":{\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n",
|
||||
ob.String(),
|
||||
)
|
||||
assert.Equal(t, "", eb.String())
|
||||
}
|
227
cmd/nebula-cert/sign.go
Normal file
227
cmd/nebula-cert/sign.go
Normal file
@ -0,0 +1,227 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
)
|
||||
|
||||
type signFlags struct {
|
||||
set *flag.FlagSet
|
||||
caKeyPath *string
|
||||
caCertPath *string
|
||||
name *string
|
||||
ip *string
|
||||
duration *time.Duration
|
||||
inPubPath *string
|
||||
outKeyPath *string
|
||||
outCertPath *string
|
||||
groups *string
|
||||
subnets *string
|
||||
}
|
||||
|
||||
func newSignFlags() *signFlags {
|
||||
sf := signFlags{set: flag.NewFlagSet("sign", flag.ContinueOnError)}
|
||||
sf.set.Usage = func() {}
|
||||
sf.caKeyPath = sf.set.String("ca-key", "ca.key", "Optional: path to the signing CA key")
|
||||
sf.caCertPath = sf.set.String("ca-crt", "ca.crt", "Optional: path to the signing CA cert")
|
||||
sf.name = sf.set.String("name", "", "Required: name of the cert, usually a hostname")
|
||||
sf.ip = sf.set.String("ip", "", "Required: ip and network in CIDR notation to assign the cert")
|
||||
sf.duration = sf.set.Duration("duration", 0, "Required: how long the cert should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\"")
|
||||
sf.inPubPath = sf.set.String("in-pub", "", "Optional (if out-key not set): path to read a previously generated public key")
|
||||
sf.outKeyPath = sf.set.String("out-key", "", "Optional (if in-pub not set): path to write the private key to")
|
||||
sf.outCertPath = sf.set.String("out-crt", "", "Optional: path to write the certificate to")
|
||||
sf.groups = sf.set.String("groups", "", "Optional: comma separated list of groups")
|
||||
sf.subnets = sf.set.String("subnets", "", "Optional: comma seperated list of subnet this cert can serve for")
|
||||
return &sf
|
||||
|
||||
}
|
||||
|
||||
func signCert(args []string, out io.Writer, errOut io.Writer) error {
|
||||
sf := newSignFlags()
|
||||
err := sf.set.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mustFlagString("ca-key", sf.caKeyPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mustFlagString("ca-crt", sf.caCertPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mustFlagString("name", sf.name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mustFlagString("ip", sf.ip); err != nil {
|
||||
return err
|
||||
}
|
||||
if *sf.inPubPath != "" && *sf.outKeyPath != "" {
|
||||
return newHelpErrorf("cannot set both -in-pub and -out-key")
|
||||
}
|
||||
|
||||
rawCAKey, err := ioutil.ReadFile(*sf.caKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading ca-key: %s", err)
|
||||
}
|
||||
|
||||
caKey, _, err := cert.UnmarshalEd25519PrivateKey(rawCAKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing ca-key: %s", err)
|
||||
}
|
||||
|
||||
rawCACert, err := ioutil.ReadFile(*sf.caCertPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading ca-crt: %s", err)
|
||||
}
|
||||
|
||||
caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM(rawCACert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing ca-crt: %s", err)
|
||||
}
|
||||
|
||||
issuer, err := caCert.Sha256Sum()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while getting -ca-crt fingerprint: %s", err)
|
||||
}
|
||||
|
||||
if caCert.Expired(time.Now()) {
|
||||
return fmt.Errorf("ca certificate is expired")
|
||||
}
|
||||
|
||||
// if no duration is given, expire one second before the root expires
|
||||
if *sf.duration <= 0 {
|
||||
*sf.duration = time.Until(caCert.Details.NotAfter) - time.Second*1
|
||||
}
|
||||
|
||||
if caCert.Details.NotAfter.Before(time.Now().Add(*sf.duration)) {
|
||||
return fmt.Errorf("refusing to generate certificate with duration beyond root expiration: %s", caCert.Details.NotAfter)
|
||||
}
|
||||
|
||||
ip, ipNet, err := net.ParseCIDR(*sf.ip)
|
||||
if err != nil {
|
||||
return newHelpErrorf("invalid ip definition: %s", err)
|
||||
}
|
||||
ipNet.IP = ip
|
||||
|
||||
groups := []string{}
|
||||
if *sf.groups != "" {
|
||||
for _, rg := range strings.Split(*sf.groups, ",") {
|
||||
g := strings.TrimSpace(rg)
|
||||
if g != "" {
|
||||
groups = append(groups, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subnets := []*net.IPNet{}
|
||||
if *sf.subnets != "" {
|
||||
for _, rs := range strings.Split(*sf.subnets, ",") {
|
||||
rs := strings.Trim(rs, " ")
|
||||
if rs != "" {
|
||||
_, s, err := net.ParseCIDR(rs)
|
||||
if err != nil {
|
||||
return newHelpErrorf("invalid subnet definition: %s", err)
|
||||
}
|
||||
subnets = append(subnets, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pub, rawPriv []byte
|
||||
if *sf.inPubPath != "" {
|
||||
rawPub, err := ioutil.ReadFile(*sf.inPubPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading in-pub: %s", err)
|
||||
}
|
||||
pub, _, err = cert.UnmarshalX25519PublicKey(rawPub)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing in-pub: %s", err)
|
||||
}
|
||||
} else {
|
||||
pub, rawPriv = x25519Keypair()
|
||||
}
|
||||
|
||||
nc := cert.NebulaCertificate{
|
||||
Details: cert.NebulaCertificateDetails{
|
||||
Name: *sf.name,
|
||||
Ips: []*net.IPNet{ipNet},
|
||||
Groups: groups,
|
||||
Subnets: subnets,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(*sf.duration),
|
||||
PublicKey: pub,
|
||||
IsCA: false,
|
||||
Issuer: issuer,
|
||||
},
|
||||
}
|
||||
|
||||
if *sf.outKeyPath == "" {
|
||||
*sf.outKeyPath = *sf.name + ".key"
|
||||
}
|
||||
|
||||
if *sf.outCertPath == "" {
|
||||
*sf.outCertPath = *sf.name + ".crt"
|
||||
}
|
||||
|
||||
if _, err := os.Stat(*sf.outKeyPath); err == nil {
|
||||
return fmt.Errorf("refusing to overwrite existing key: %s", *sf.outKeyPath)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(*sf.outCertPath); err == nil {
|
||||
return fmt.Errorf("refusing to overwrite existing cert: %s", *sf.outCertPath)
|
||||
}
|
||||
|
||||
err = nc.Sign(caKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while signing: %s", err)
|
||||
}
|
||||
|
||||
if *sf.inPubPath == "" {
|
||||
err = ioutil.WriteFile(*sf.outKeyPath, cert.MarshalX25519PrivateKey(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(*sf.outCertPath, b, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while writing out-crt: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func x25519Keypair() ([]byte, []byte) {
|
||||
var pubkey, privkey [32]byte
|
||||
if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
curve25519.ScalarBaseMult(&pubkey, &privkey)
|
||||
return pubkey[:], privkey[:]
|
||||
}
|
||||
|
||||
func signSummary() string {
|
||||
return "sign <flags>: create and sign a certificate"
|
||||
}
|
||||
|
||||
func signHelp(out io.Writer) {
|
||||
sf := newSignFlags()
|
||||
out.Write([]byte("Usage of " + os.Args[0] + " " + signSummary() + "\n"))
|
||||
sf.set.SetOutput(out)
|
||||
sf.set.PrintDefaults()
|
||||
}
|
281
cmd/nebula-cert/sign_test.go
Normal file
281
cmd/nebula-cert/sign_test.go
Normal file
@ -0,0 +1,281 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
)
|
||||
|
||||
//TODO: test file permissions
|
||||
|
||||
func Test_signSummary(t *testing.T) {
|
||||
assert.Equal(t, "sign <flags>: create and sign a certificate", signSummary())
|
||||
}
|
||||
|
||||
func Test_signHelp(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
signHelp(ob)
|
||||
assert.Equal(
|
||||
t,
|
||||
"Usage of "+os.Args[0]+" sign <flags>: create and sign a certificate\n"+
|
||||
" -ca-crt string\n"+
|
||||
" \tOptional: path to the signing CA cert (default \"ca.crt\")\n"+
|
||||
" -ca-key string\n"+
|
||||
" \tOptional: path to the signing CA key (default \"ca.key\")\n"+
|
||||
" -duration duration\n"+
|
||||
" \tRequired: how long the cert should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\"\n"+
|
||||
" -groups string\n"+
|
||||
" \tOptional: comma separated list of groups\n"+
|
||||
" -in-pub string\n"+
|
||||
" \tOptional (if out-key not set): path to read a previously generated public key\n"+
|
||||
" -ip string\n"+
|
||||
" \tRequired: ip and network in CIDR notation to assign the cert\n"+
|
||||
" -name string\n"+
|
||||
" \tRequired: name of the cert, usually a hostname\n"+
|
||||
" -out-crt string\n"+
|
||||
" \tOptional: path to write the certificate to\n"+
|
||||
" -out-key string\n"+
|
||||
" \tOptional (if in-pub not set): path to write the private key to\n"+
|
||||
" -subnets string\n"+
|
||||
" \tOptional: comma seperated list of subnet this cert can serve for\n",
|
||||
ob.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func Test_signCert(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
eb := &bytes.Buffer{}
|
||||
|
||||
// required args
|
||||
|
||||
assertHelpError(t, signCert([]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-ip", "1.1.1.1/24", "-out-key", "nope", "-out-crt", "nope"}, ob, eb), "-name is required")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
assertHelpError(t, signCert([]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-out-key", "nope", "-out-crt", "nope"}, ob, eb), "-ip is required")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// cannot set -in-pub and -out-key
|
||||
assertHelpError(t, signCert([]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-in-pub", "nope", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope"}, ob, eb), "cannot set both -in-pub and -out-key")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// failed to read key
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args := []string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while reading ca-key: open ./nope: "+NoSuchFileError)
|
||||
|
||||
// failed to unmarshal key
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
caKeyF, err := ioutil.TempFile("", "sign-cert.key")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(caKeyF.Name())
|
||||
|
||||
args = []string{"-ca-crt", "./nope", "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while parsing ca-key: input did not contain a valid PEM encoded block")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// Write a proper ca key for later
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
caPub, caPriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
caKeyF.Write(cert.MarshalEd25519PrivateKey(caPriv))
|
||||
|
||||
// failed to read cert
|
||||
args = []string{"-ca-crt", "./nope", "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while reading ca-crt: open ./nope: "+NoSuchFileError)
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// failed to unmarshal cert
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
caCrtF, err := ioutil.TempFile("", "sign-cert.crt")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(caCrtF.Name())
|
||||
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while parsing ca-crt: input did not contain a valid PEM encoded block")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// write a proper ca cert for later
|
||||
ca := cert.NebulaCertificate{
|
||||
Details: cert.NebulaCertificateDetails{
|
||||
Name: "ca",
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Minute * 200),
|
||||
PublicKey: caPub,
|
||||
IsCA: true,
|
||||
},
|
||||
}
|
||||
b, _ := ca.MarshalToPEM()
|
||||
caCrtF.Write(b)
|
||||
|
||||
// failed to read pub
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-in-pub", "./nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while reading in-pub: open ./nope: "+NoSuchFileError)
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// failed to unmarshal pub
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
inPubF, err := ioutil.TempFile("", "in.pub")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(inPubF.Name())
|
||||
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-in-pub", inPubF.Name(), "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while parsing in-pub: input did not contain a valid PEM encoded block")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// write a proper pub for later
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
inPub, _ := x25519Keypair()
|
||||
inPubF.Write(cert.MarshalX25519PublicKey(inPub))
|
||||
|
||||
// bad ip cidr
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "a1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assertHelpError(t, signCert(args, ob, eb), "invalid ip definition: invalid CIDR address: a1.1.1.1/24")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// bad subnet cidr
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m", "-subnets", "a"}
|
||||
assertHelpError(t, signCert(args, ob, eb), "invalid subnet definition: invalid CIDR address: a")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// failed key write
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "/do/not/write/pleasecrt", "-out-key", "/do/not/write/pleasekey", "-duration", "100m", "-subnets", "10.1.1.1/32"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// create temp key file
|
||||
keyF, err := ioutil.TempFile("", "test.key")
|
||||
assert.Nil(t, err)
|
||||
os.Remove(keyF.Name())
|
||||
|
||||
// failed cert write
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "/do/not/write/pleasecrt", "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError)
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
os.Remove(keyF.Name())
|
||||
|
||||
// create temp cert file
|
||||
crtF, err := ioutil.TempFile("", "test.crt")
|
||||
assert.Nil(t, err)
|
||||
os.Remove(crtF.Name())
|
||||
|
||||
// test proper cert with removed empty groups and subnets
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Nil(t, signCert(args, ob, eb))
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// read cert and key files
|
||||
rb, _ := ioutil.ReadFile(keyF.Name())
|
||||
lKey, b, err := cert.UnmarshalX25519PrivateKey(rb)
|
||||
assert.Len(t, b, 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, lKey, 32)
|
||||
|
||||
rb, _ = ioutil.ReadFile(crtF.Name())
|
||||
lCrt, b, err := cert.UnmarshalNebulaCertificateFromPEM(rb)
|
||||
assert.Len(t, b, 0)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "test", lCrt.Details.Name)
|
||||
assert.Equal(t, "1.1.1.1/24", lCrt.Details.Ips[0].String())
|
||||
assert.Len(t, lCrt.Details.Ips, 1)
|
||||
assert.False(t, lCrt.Details.IsCA)
|
||||
assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Details.Groups)
|
||||
assert.Len(t, lCrt.Details.Subnets, 3)
|
||||
assert.Len(t, lCrt.Details.PublicKey, 32)
|
||||
assert.Equal(t, time.Duration(time.Minute*100), lCrt.Details.NotAfter.Sub(lCrt.Details.NotBefore))
|
||||
|
||||
sns := []string{}
|
||||
for _, sn := range lCrt.Details.Subnets {
|
||||
sns = append(sns, sn.String())
|
||||
}
|
||||
assert.Equal(t, []string{"10.1.1.1/32", "10.2.2.2/32", "10.5.5.5/32"}, sns)
|
||||
|
||||
issuer, _ := ca.Sha256Sum()
|
||||
assert.Equal(t, issuer, lCrt.Details.Issuer)
|
||||
|
||||
assert.True(t, lCrt.CheckSignature(caPub))
|
||||
|
||||
// test proper cert with in-pub
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-in-pub", inPubF.Name(), "-duration", "100m", "-groups", "1"}
|
||||
assert.Nil(t, signCert(args, ob, eb))
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// read cert file and check pub key matches in-pub
|
||||
rb, _ = ioutil.ReadFile(crtF.Name())
|
||||
lCrt, b, err = cert.UnmarshalNebulaCertificateFromPEM(rb)
|
||||
assert.Len(t, b, 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, lCrt.Details.PublicKey, inPub)
|
||||
|
||||
// test refuse to sign cert with duration beyond root
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "1000m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "refusing to generate certificate with duration beyond root expiration: "+ca.Details.NotAfter.Format("2006-01-02 15:04:05 +0000 UTC"))
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// create valid cert/key for overwrite tests
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Nil(t, signCert(args, ob, eb))
|
||||
|
||||
// test that we won't overwrite existing key file
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "refusing to overwrite existing key: "+keyF.Name())
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// test that we won't overwrite existing certificate file
|
||||
os.Remove(keyF.Name())
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "refusing to overwrite existing cert: "+crtF.Name())
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
}
|
4
cmd/nebula-cert/test_unix.go
Normal file
4
cmd/nebula-cert/test_unix.go
Normal file
@ -0,0 +1,4 @@
|
||||
package main
|
||||
|
||||
const NoSuchFileError = "no such file or directory"
|
||||
const NoSuchDirError = "no such file or directory"
|
4
cmd/nebula-cert/test_windows.go
Normal file
4
cmd/nebula-cert/test_windows.go
Normal file
@ -0,0 +1,4 @@
|
||||
package main
|
||||
|
||||
const NoSuchFileError = "The system cannot find the file specified."
|
||||
const NoSuchDirError = "The system cannot find the path specified."
|
86
cmd/nebula-cert/verify.go
Normal file
86
cmd/nebula-cert/verify.go
Normal file
@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type verifyFlags struct {
|
||||
set *flag.FlagSet
|
||||
caPath *string
|
||||
certPath *string
|
||||
}
|
||||
|
||||
func newVerifyFlags() *verifyFlags {
|
||||
vf := verifyFlags{set: flag.NewFlagSet("verify", flag.ContinueOnError)}
|
||||
vf.set.Usage = func() {}
|
||||
vf.caPath = vf.set.String("ca", "", "Required: path to a file containing one or more ca certificates")
|
||||
vf.certPath = vf.set.String("crt", "", "Required: path to a file containing a single certificate")
|
||||
return &vf
|
||||
}
|
||||
|
||||
func verify(args []string, out io.Writer, errOut io.Writer) error {
|
||||
vf := newVerifyFlags()
|
||||
err := vf.set.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mustFlagString("ca", vf.caPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mustFlagString("crt", vf.certPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawCACert, err := ioutil.ReadFile(*vf.caPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading ca: %s", err)
|
||||
}
|
||||
|
||||
caPool := cert.NewCAPool()
|
||||
for {
|
||||
rawCACert, err = caPool.AddCACertificate(rawCACert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while adding ca cert to pool: %s", err)
|
||||
}
|
||||
|
||||
if rawCACert == nil || len(rawCACert) == 0 || strings.TrimSpace(string(rawCACert)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rawCert, err := ioutil.ReadFile(*vf.certPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read crt; %s", err)
|
||||
}
|
||||
|
||||
c, _, err := cert.UnmarshalNebulaCertificateFromPEM(rawCert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing crt: %s", err)
|
||||
}
|
||||
|
||||
good, err := c.Verify(time.Now(), caPool)
|
||||
if !good {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySummary() string {
|
||||
return "verify <flags>: verifies a certificate isn't expired and was signed by a trusted authority."
|
||||
}
|
||||
|
||||
func verifyHelp(out io.Writer) {
|
||||
vf := newVerifyFlags()
|
||||
out.Write([]byte("Usage of " + os.Args[0] + " " + verifySummary() + "\n"))
|
||||
vf.set.SetOutput(out)
|
||||
vf.set.PrintDefaults()
|
||||
}
|
141
cmd/nebula-cert/verify_test.go
Normal file
141
cmd/nebula-cert/verify_test.go
Normal file
@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_verifySummary(t *testing.T) {
|
||||
assert.Equal(t, "verify <flags>: verifies a certificate isn't expired and was signed by a trusted authority.", verifySummary())
|
||||
}
|
||||
|
||||
func Test_verifyHelp(t *testing.T) {
|
||||
ob := &bytes.Buffer{}
|
||||
verifyHelp(ob)
|
||||
assert.Equal(
|
||||
t,
|
||||
"Usage of "+os.Args[0]+" verify <flags>: verifies a certificate isn't expired and was signed by a trusted authority.\n"+
|
||||
" -ca string\n"+
|
||||
" \tRequired: path to a file containing one or more ca certificates\n"+
|
||||
" -crt string\n"+
|
||||
" \tRequired: path to a file containing a single certificate\n",
|
||||
ob.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func Test_verify(t *testing.T) {
|
||||
time.Local = time.UTC
|
||||
ob := &bytes.Buffer{}
|
||||
eb := &bytes.Buffer{}
|
||||
|
||||
// required args
|
||||
assertHelpError(t, verify([]string{"-ca", "derp"}, ob, eb), "-crt is required")
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
assertHelpError(t, verify([]string{"-crt", "derp"}, ob, eb), "-ca is required")
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
// no ca at path
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
err := verify([]string{"-ca", "does_not_exist", "-crt", "does_not_exist"}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assert.EqualError(t, err, "error while reading ca: open does_not_exist: "+NoSuchFileError)
|
||||
|
||||
// invalid ca at path
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
caFile, err := ioutil.TempFile("", "verify-ca")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(caFile.Name())
|
||||
|
||||
caFile.WriteString("-----BEGIN NOPE-----")
|
||||
err = verify([]string{"-ca", caFile.Name(), "-crt", "does_not_exist"}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assert.EqualError(t, err, "error while adding ca cert to pool: input did not contain a valid PEM encoded block")
|
||||
|
||||
// make a ca for later
|
||||
caPub, caPriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
ca := cert.NebulaCertificate{
|
||||
Details: cert.NebulaCertificateDetails{
|
||||
Name: "test-ca",
|
||||
NotBefore: time.Now().Add(time.Hour * -1),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
PublicKey: caPub,
|
||||
IsCA: true,
|
||||
},
|
||||
}
|
||||
ca.Sign(caPriv)
|
||||
b, _ := ca.MarshalToPEM()
|
||||
caFile.Truncate(0)
|
||||
caFile.Seek(0, 0)
|
||||
caFile.Write(b)
|
||||
|
||||
// no crt at path
|
||||
err = verify([]string{"-ca", caFile.Name(), "-crt", "does_not_exist"}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assert.EqualError(t, err, "unable to read crt; open does_not_exist: "+NoSuchFileError)
|
||||
|
||||
// invalid crt at path
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
certFile, err := ioutil.TempFile("", "verify-cert")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(certFile.Name())
|
||||
|
||||
certFile.WriteString("-----BEGIN NOPE-----")
|
||||
err = verify([]string{"-ca", caFile.Name(), "-crt", certFile.Name()}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assert.EqualError(t, err, "error while parsing crt: input did not contain a valid PEM encoded block")
|
||||
|
||||
// unverifiable cert at path
|
||||
_, badPriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
certPub, _ := x25519Keypair()
|
||||
signer, _ := ca.Sha256Sum()
|
||||
crt := cert.NebulaCertificate{
|
||||
Details: cert.NebulaCertificateDetails{
|
||||
Name: "test-cert",
|
||||
NotBefore: time.Now().Add(time.Hour * -1),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
PublicKey: certPub,
|
||||
IsCA: false,
|
||||
Issuer: signer,
|
||||
},
|
||||
}
|
||||
|
||||
crt.Sign(badPriv)
|
||||
b, _ = crt.MarshalToPEM()
|
||||
certFile.Truncate(0)
|
||||
certFile.Seek(0, 0)
|
||||
certFile.Write(b)
|
||||
|
||||
err = verify([]string{"-ca", caFile.Name(), "-crt", certFile.Name()}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assert.EqualError(t, err, "certificate signature did not match")
|
||||
|
||||
// verified cert at path
|
||||
crt.Sign(caPriv)
|
||||
b, _ = crt.MarshalToPEM()
|
||||
certFile.Truncate(0)
|
||||
certFile.Seek(0, 0)
|
||||
certFile.Write(b)
|
||||
|
||||
err = verify([]string{"-ca", caFile.Name(), "-crt", certFile.Name()}, ob, eb)
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
assert.Nil(t, err)
|
||||
}
|
43
cmd/nebula/main.go
Normal file
43
cmd/nebula/main.go
Normal file
@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/slackhq/nebula"
|
||||
)
|
||||
|
||||
// A version string that can be set with
|
||||
//
|
||||
// -ldflags "-X main.Build=SOMEVERSION"
|
||||
//
|
||||
// at compile-time.
|
||||
var Build string
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "", "Path to either a file or directory to load configuration from")
|
||||
configTest := flag.Bool("test", false, "Test the config and print the end result. Non zero exit indicates a faulty config")
|
||||
printVersion := flag.Bool("version", false, "Print version")
|
||||
printUsage := flag.Bool("help", false, "Print command line usage")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *printVersion {
|
||||
fmt.Printf("Build: %s\n", Build)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *printUsage {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *configPath == "" {
|
||||
fmt.Println("-config flag must be set")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
nebula.Main(*configPath, *configTest, Build)
|
||||
}
|
Reference in New Issue
Block a user