From d4b81f9b8d61c40103987ee798bad84eb319f6ad Mon Sep 17 00:00:00 2001 From: Nathan Brown Date: Thu, 11 Feb 2021 18:53:25 -0600 Subject: [PATCH] Add QR code support to `nebula-cert` (#297) --- cmd/nebula-cert/ca.go | 15 +++++++++++++++ cmd/nebula-cert/ca_test.go | 2 ++ cmd/nebula-cert/print.go | 33 ++++++++++++++++++++++++++++++--- cmd/nebula-cert/print_test.go | 2 ++ cmd/nebula-cert/sign.go | 15 +++++++++++++++ cmd/nebula-cert/sign_test.go | 3 ++- go.mod | 1 + go.sum | 2 ++ 8 files changed, 69 insertions(+), 4 deletions(-) diff --git a/cmd/nebula-cert/ca.go b/cmd/nebula-cert/ca.go index 00a9661..9fe1192 100644 --- a/cmd/nebula-cert/ca.go +++ b/cmd/nebula-cert/ca.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/skip2/go-qrcode" "github.com/slackhq/nebula/cert" "golang.org/x/crypto/ed25519" ) @@ -21,6 +22,7 @@ type caFlags struct { duration *time.Duration outKeyPath *string outCertPath *string + outQRPath *string groups *string ips *string subnets *string @@ -33,6 +35,7 @@ func newCaFlags() *caFlags { 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.outQRPath = cf.set.String("out-qr", "", "Optional: output a qr code image (png) of the certificate") cf.groups = cf.set.String("groups", "", "Optional: comma separated list of groups. This will limit which groups subordinate certs can use") cf.ips = cf.set.String("ips", "", "Optional: comma separated list of ip and network in CIDR notation. This will limit which ip addresses and networks subordinate certs can use") cf.subnets = cf.set.String("subnets", "", "Optional: comma separated list of ip and network in CIDR notation. This will limit which subnet addresses and networks subordinate certs can use") @@ -146,6 +149,18 @@ func ca(args []string, out io.Writer, errOut io.Writer) error { return fmt.Errorf("error while writing out-crt: %s", err) } + 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) + } + } + return nil } diff --git a/cmd/nebula-cert/ca_test.go b/cmd/nebula-cert/ca_test.go index 70bc74a..84ed791 100644 --- a/cmd/nebula-cert/ca_test.go +++ b/cmd/nebula-cert/ca_test.go @@ -37,6 +37,8 @@ func Test_caHelp(t *testing.T) { " \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"+ + " -out-qr string\n"+ + " \tOptional: output a qr code image (png) of the certificate\n"+ " -subnets string\n"+ " \tOptional: comma separated list of ip and network in CIDR notation. This will limit which subnet addresses and networks subordinate certs can use\n", ob.String(), diff --git a/cmd/nebula-cert/print.go b/cmd/nebula-cert/print.go index fd0fcf5..222dbc0 100644 --- a/cmd/nebula-cert/print.go +++ b/cmd/nebula-cert/print.go @@ -9,19 +9,22 @@ import ( "os" "strings" + "github.com/skip2/go-qrcode" "github.com/slackhq/nebula/cert" ) type printFlags struct { - set *flag.FlagSet - json *bool - path *string + set *flag.FlagSet + json *bool + outQRPath *string + 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.outQRPath = pf.set.String("out-qr", "", "Optional: output a qr code image (png) of the certificate") pf.path = pf.set.String("path", "", "Required: path to the certificate") return &pf @@ -44,6 +47,8 @@ func printCert(args []string, out io.Writer, errOut io.Writer) error { } var c *cert.NebulaCertificate + var qrBytes []byte + part := 0 for { c, rawCert, err = cert.UnmarshalNebulaCertificateFromPEM(rawCert) @@ -61,9 +66,31 @@ func printCert(args []string, out io.Writer, errOut io.Writer) error { out.Write([]byte("\n")) } + if *pf.outQRPath != "" { + b, err := c.MarshalToPEM() + if err != nil { + return fmt.Errorf("error while marshalling cert to PEM: %s", err) + } + qrBytes = append(qrBytes, b...) + } + if rawCert == nil || len(rawCert) == 0 || strings.TrimSpace(string(rawCert)) == "" { break } + + part++ + } + + if *pf.outQRPath != "" { + b, err := qrcode.Encode(string(qrBytes), qrcode.Medium, -5) + if err != nil { + return fmt.Errorf("error while generating qr code: %s", err) + } + + err = ioutil.WriteFile(*pf.outQRPath, b, 0600) + if err != nil { + return fmt.Errorf("error while writing out-qr: %s", err) + } } return nil diff --git a/cmd/nebula-cert/print_test.go b/cmd/nebula-cert/print_test.go index bed5116..5d6a38e 100644 --- a/cmd/nebula-cert/print_test.go +++ b/cmd/nebula-cert/print_test.go @@ -23,6 +23,8 @@ func Test_printHelp(t *testing.T) { "Usage of "+os.Args[0]+" print : prints details about a certificate\n"+ " -json\n"+ " \tOptional: outputs certificates in json format\n"+ + " -out-qr string\n"+ + " \tOptional: output a qr code image (png) of the certificate\n"+ " -path string\n"+ " \tRequired: path to the certificate\n", ob.String(), diff --git a/cmd/nebula-cert/sign.go b/cmd/nebula-cert/sign.go index 7e3949e..bfff252 100644 --- a/cmd/nebula-cert/sign.go +++ b/cmd/nebula-cert/sign.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/skip2/go-qrcode" "github.com/slackhq/nebula/cert" "golang.org/x/crypto/curve25519" ) @@ -25,6 +26,7 @@ type signFlags struct { inPubPath *string outKeyPath *string outCertPath *string + outQRPath *string groups *string subnets *string } @@ -40,6 +42,7 @@ func newSignFlags() *signFlags { 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.outQRPath = sf.set.String("out-qr", "", "Optional: output a qr code image (png) of the certificate") 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 @@ -203,6 +206,18 @@ func signCert(args []string, out io.Writer, errOut io.Writer) error { return fmt.Errorf("error while writing out-crt: %s", err) } + if *sf.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(*sf.outQRPath, b, 0600) + if err != nil { + return fmt.Errorf("error while writing out-qr: %s", err) + } + } + return nil } diff --git a/cmd/nebula-cert/sign_test.go b/cmd/nebula-cert/sign_test.go index 88f7f3e..bf94af6 100644 --- a/cmd/nebula-cert/sign_test.go +++ b/cmd/nebula-cert/sign_test.go @@ -45,6 +45,8 @@ func Test_signHelp(t *testing.T) { " \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"+ + " -out-qr string\n"+ + " \tOptional: output a qr code image (png) of the certificate\n"+ " -subnets string\n"+ " \tOptional: comma seperated list of subnet this cert can serve for\n", ob.String(), @@ -286,5 +288,4 @@ func Test_signCert(t *testing.T) { assert.EqualError(t, signCert(args, ob, eb), "refusing to overwrite existing cert: "+crtF.Name()) assert.Empty(t, ob.String()) assert.Empty(t, eb.String()) - } diff --git a/go.mod b/go.mod index 5233ef7..ed0a165 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/prometheus/procfs v0.0.8 // indirect github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 github.com/sirupsen/logrus v1.4.2 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b github.com/stretchr/testify v1.6.1 github.com/vishvananda/netlink v1.0.1-0.20190522153524-00009fb8606a diff --git a/go.sum b/go.sum index f7771f6..aa4d26d 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,8 @@ github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqn github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=