Ver código fonte

Root constraint usage and support in nebula-cert

Nate Brown 5 anos atrás
pai
commit
328db6bb82

+ 23 - 4
cert/cert.go

@@ -267,11 +267,30 @@ func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error
 		return false, fmt.Errorf("certificate signature did not match")
 	}
 
+	if err := nc.CheckRootConstrains(signer); err != nil {
+		return false, err
+	}
+
+	return true, nil
+}
+
+// CheckRootConstrains returns an error if the certificate violates constraints set on the root (groups, ips, subnets)
+func (nc *NebulaCertificate) CheckRootConstrains(signer *NebulaCertificate) error {
+	// Make sure this cert wasn't valid before the root
+	if signer.Details.NotAfter.Before(nc.Details.NotAfter) {
+		return fmt.Errorf("certificate expires after signing certificate")
+	}
+
+	// Make sure this cert isn't valid after the root
+	if signer.Details.NotBefore.After(nc.Details.NotBefore) {
+		return fmt.Errorf("certificate is valid before the signing certificate")
+	}
+
 	// If the signer has a limited set of groups make sure the cert only contains a subset
 	if len(signer.Details.InvertedGroups) > 0 {
 		for _, g := range nc.Details.Groups {
 			if _, ok := signer.Details.InvertedGroups[g]; !ok {
-				return false, fmt.Errorf("certificate contained a group not present on the signing ca: %s", g)
+				return fmt.Errorf("certificate contained a group not present on the signing ca: %s", g)
 			}
 		}
 	}
@@ -280,7 +299,7 @@ func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error
 	if len(signer.Details.Ips) > 0 {
 		for _, ip := range nc.Details.Ips {
 			if !netMatch(ip, signer.Details.Ips) {
-				return false, fmt.Errorf("certificate contained an ip assignment outside the limitations of the signing ca: %s", ip.String())
+				return fmt.Errorf("certificate contained an ip assignment outside the limitations of the signing ca: %s", ip.String())
 			}
 		}
 	}
@@ -289,12 +308,12 @@ func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error
 	if len(signer.Details.Subnets) > 0 {
 		for _, subnet := range nc.Details.Subnets {
 			if !netMatch(subnet, signer.Details.Subnets) {
-				return false, fmt.Errorf("certificate contained a subnet assignment outside the limitations of the signing ca: %s", subnet)
+				return fmt.Errorf("certificate contained a subnet assignment outside the limitations of the signing ca: %s", subnet)
 			}
 		}
 	}
 
-	return true, nil
+	return nil
 }
 
 // VerifyPrivateKey checks that the public key in the Nebula certificate and a supplied private key match

+ 38 - 1
cmd/nebula-cert/ca.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"net"
 	"os"
 	"strings"
 	"time"
@@ -21,6 +22,8 @@ type caFlags struct {
 	outKeyPath  *string
 	outCertPath *string
 	groups      *string
+	ips         *string
+	subnets     *string
 }
 
 func newCaFlags() *caFlags {
@@ -31,6 +34,8 @@ func newCaFlags() *caFlags {
 	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")
+	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")
 	return &cf
 }
 
@@ -55,7 +60,7 @@ func ca(args []string, out io.Writer, errOut io.Writer) error {
 		return &helpError{"-duration must be greater than 0"}
 	}
 
-	groups := []string{}
+	var groups []string
 	if *cf.groups != "" {
 		for _, rg := range strings.Split(*cf.groups, ",") {
 			g := strings.TrimSpace(rg)
@@ -65,6 +70,36 @@ func ca(args []string, out io.Writer, errOut io.Writer) error {
 		}
 	}
 
+	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)
+				}
+
+				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)
+				}
+				subnets = append(subnets, s)
+			}
+		}
+	}
+
 	pub, rawPriv, err := ed25519.GenerateKey(rand.Reader)
 	if err != nil {
 		return fmt.Errorf("error while generating ed25519 keys: %s", err)
@@ -74,6 +109,8 @@ func ca(args []string, out io.Writer, errOut io.Writer) error {
 		Details: cert.NebulaCertificateDetails{
 			Name:      *cf.name,
 			Groups:    groups,
+			Ips:       ips,
+			Subnets:   subnets,
 			NotBefore: time.Now(),
 			NotAfter:  time.Now().Add(*cf.duration),
 			PublicKey: pub,

+ 5 - 1
cmd/nebula-cert/ca_test.go

@@ -29,12 +29,16 @@ func Test_caHelp(t *testing.T) {
 			"    \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"+
+			"  -ips string\n"+
+			"    \tOptional: comma separated list of ip and network in CIDR notation. This will limit which ip addresses and networks 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",
+			"    \tOptional: path to write the private key to (default \"ca.key\")\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(),
 	)
 }

+ 4 - 4
cmd/nebula-cert/sign.go

@@ -103,10 +103,6 @@ func signCert(args []string, out io.Writer, errOut io.Writer) error {
 		*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)
@@ -165,6 +161,10 @@ func signCert(args []string, out io.Writer, errOut io.Writer) error {
 		},
 	}
 
+	if err := nc.CheckRootConstrains(caCert); err != nil {
+		return fmt.Errorf("refusing to sign, root certificate constraints violated: %s", err)
+	}
+
 	if *sf.outKeyPath == "" {
 		*sf.outKeyPath = *sf.name + ".key"
 	}

+ 1 - 1
cmd/nebula-cert/sign_test.go

@@ -253,7 +253,7 @@ func Test_signCert(t *testing.T) {
 	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.EqualError(t, signCert(args, ob, eb), "refusing to sign, root certificate constraints violated: certificate expires after signing certificate")
 	assert.Empty(t, ob.String())
 	assert.Empty(t, eb.String())