// Package nebula contains helper functions and types which are useful for // setting up nebula configs, processes, and deployments. package nebula import ( crypticnet "cryptic-net" "crypto/ed25519" "crypto/rand" "fmt" "io" "io/fs" "net" "time" "github.com/slackhq/nebula/cert" "golang.org/x/crypto/curve25519" ) // TODO this should one day not be hardcoded var ipCIDRMask = func() net.IPMask { _, ipNet, err := net.ParseCIDR("10.10.0.0/16") if err != nil { panic(err) } return ipNet.Mask }() // HostCert contains the certificate and private key files which will need to // be present on a particular host. Each file is PEM encoded. type HostCert struct { CACert []byte HostKey []byte HostCert []byte } // CACert contains the certificate and private files which can be used to create // HostCerts. Each file is PEM encoded. type CACert struct { CACert []byte CAKey []byte } // NewHostCert generates a new key/cert for a nebula host using the CA key // which will be found in the adminFS. func NewHostCert( adminFS fs.FS, host crypticnet.NebulaHost, ) ( HostCert, error, ) { // The logic here is largely based on // https://github.com/slackhq/nebula/blob/v1.4.0/cmd/nebula-cert/sign.go caKeyPEM, err := fs.ReadFile(adminFS, "nebula/certs/ca.key") if err != nil { return HostCert{}, fmt.Errorf("reading ca.key from admin fs: %w", err) } caKey, _, err := cert.UnmarshalEd25519PrivateKey(caKeyPEM) if err != nil { return HostCert{}, fmt.Errorf("unmarshaling ca.key: %w", err) } caCrtPEM, err := fs.ReadFile(adminFS, "nebula/certs/ca.crt") if err != nil { return HostCert{}, fmt.Errorf("reading ca.crt from admin fs: %w", err) } caCrt, _, err := cert.UnmarshalNebulaCertificateFromPEM(caCrtPEM) if err != nil { return HostCert{}, fmt.Errorf("unmarshaling ca.crt: %w", err) } issuer, err := caCrt.Sha256Sum() if err != nil { return HostCert{}, fmt.Errorf("getting ca.crt issuer: %w", err) } expireAt := caCrt.Details.NotAfter.Add(-1 * time.Second) ip := net.ParseIP(host.IP) if ip == nil { return HostCert{}, fmt.Errorf("invalid host ip %q", host.IP) } ipNet := &net.IPNet{ IP: ip, Mask: ipCIDRMask, } var hostPub, hostKey []byte { var pubkey, privkey [32]byte if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil { return HostCert{}, fmt.Errorf("reading random bytes to form private key: %w", err) } curve25519.ScalarBaseMult(&pubkey, &privkey) hostPub, hostKey = pubkey[:], privkey[:] } hostCrt := cert.NebulaCertificate{ Details: cert.NebulaCertificateDetails{ Name: host.Name, Ips: []*net.IPNet{ipNet}, NotBefore: time.Now(), NotAfter: expireAt, PublicKey: hostPub, IsCA: false, Issuer: issuer, }, } if err := hostCrt.CheckRootConstrains(caCrt); err != nil { return HostCert{}, fmt.Errorf("validating certificate constraints: %w", err) } if err := hostCrt.Sign(caKey); err != nil { return HostCert{}, fmt.Errorf("signing host cert with ca.key: %w", err) } hostKeyPEM := cert.MarshalX25519PrivateKey(hostKey) hostCrtPEM, err := hostCrt.MarshalToPEM() if err != nil { return HostCert{}, fmt.Errorf("marshalling host.crt: %w", err) } return HostCert{ CACert: caCrtPEM, HostKey: hostKeyPEM, HostCert: hostCrtPEM, }, nil } // NewCACert generates a CACert. The domain should be the network's root domain, // and is included in the signing certificate's Name field. func NewCACert(domain string) (CACert, error) { pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { panic(fmt.Errorf("generating ed25519 key: %w", err)) } now := time.Now() expireAt := now.Add(2 * 365 * 24 * time.Hour) caCrt := cert.NebulaCertificate{ Details: cert.NebulaCertificateDetails{ Name: fmt.Sprintf("%s cryptic-net root cert", domain), NotBefore: now, NotAfter: expireAt, PublicKey: pubKey, IsCA: true, }, } if err := caCrt.Sign(privKey); err != nil { return CACert{}, fmt.Errorf("signing caCrt: %w", err) } caKeyPEM := cert.MarshalEd25519PrivateKey(privKey) caCrtPem, err := caCrt.MarshalToPEM() if err != nil { return CACert{}, fmt.Errorf("marshaling caCrt: %w", err) } return CACert{ CACert: caCrtPem, CAKey: caKeyPEM, }, nil }