2022-10-11 19:24:53 +00:00
|
|
|
// Package nebula contains helper functions and types which are useful for
|
|
|
|
// setting up nebula configs, processes, and deployments.
|
|
|
|
package nebula
|
|
|
|
|
|
|
|
import (
|
2022-10-15 11:14:38 +00:00
|
|
|
"crypto/ed25519"
|
2022-10-11 19:24:53 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/slackhq/nebula/cert"
|
|
|
|
"golang.org/x/crypto/curve25519"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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 {
|
2022-10-15 16:41:07 +00:00
|
|
|
CACert string
|
|
|
|
HostKey string
|
|
|
|
HostCert string
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 11:14:38 +00:00
|
|
|
// CACert contains the certificate and private files which can be used to create
|
|
|
|
// HostCerts. Each file is PEM encoded.
|
|
|
|
type CACert struct {
|
2022-10-15 16:41:07 +00:00
|
|
|
CACert string
|
|
|
|
CAKey string
|
2022-10-15 11:14:38 +00:00
|
|
|
}
|
|
|
|
|
2022-10-11 19:24:53 +00:00
|
|
|
// NewHostCert generates a new key/cert for a nebula host using the CA key
|
|
|
|
// which will be found in the adminFS.
|
|
|
|
func NewHostCert(
|
2022-10-16 20:17:26 +00:00
|
|
|
caCert CACert, hostName string, ip net.IP,
|
2022-10-11 19:24:53 +00:00
|
|
|
) (
|
|
|
|
HostCert, error,
|
|
|
|
) {
|
|
|
|
|
|
|
|
// The logic here is largely based on
|
|
|
|
// https://github.com/slackhq/nebula/blob/v1.4.0/cmd/nebula-cert/sign.go
|
|
|
|
|
2022-10-16 14:39:05 +00:00
|
|
|
caKey, _, err := cert.UnmarshalEd25519PrivateKey([]byte(caCert.CAKey))
|
2022-10-11 19:24:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return HostCert{}, fmt.Errorf("unmarshaling ca.key: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-10-16 14:39:05 +00:00
|
|
|
caCrt, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(caCert.CACert))
|
2022-10-11 19:24:53 +00:00
|
|
|
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)
|
|
|
|
|
2022-10-16 20:17:26 +00:00
|
|
|
subnet := caCrt.Details.Subnets[0]
|
|
|
|
if !subnet.Contains(ip) {
|
|
|
|
return HostCert{}, fmt.Errorf("invalid ip %q, not contained by network subnet %q", ip, subnet)
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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{
|
2022-10-16 20:17:26 +00:00
|
|
|
Name: hostName,
|
|
|
|
Ips: []*net.IPNet{{
|
|
|
|
IP: ip,
|
|
|
|
Mask: subnet.Mask,
|
|
|
|
}},
|
2022-10-11 19:24:53 +00:00
|
|
|
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{
|
2022-10-16 14:39:05 +00:00
|
|
|
CACert: caCert.CACert,
|
2022-10-15 16:41:07 +00:00
|
|
|
HostKey: string(hostKeyPEM),
|
|
|
|
HostCert: string(hostCrtPEM),
|
2022-10-11 19:24:53 +00:00
|
|
|
}, nil
|
|
|
|
}
|
2022-10-15 11:14:38 +00:00
|
|
|
|
|
|
|
// NewCACert generates a CACert. The domain should be the network's root domain,
|
|
|
|
// and is included in the signing certificate's Name field.
|
2022-10-16 20:17:26 +00:00
|
|
|
func NewCACert(domain string, subnet *net.IPNet) (CACert, error) {
|
2022-10-15 11:14:38 +00:00
|
|
|
|
|
|
|
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),
|
2022-10-16 20:17:26 +00:00
|
|
|
Subnets: []*net.IPNet{subnet},
|
2022-10-15 11:14:38 +00:00
|
|
|
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{
|
2022-10-15 16:41:07 +00:00
|
|
|
CACert: string(caCrtPem),
|
|
|
|
CAKey: string(caKeyPEM),
|
2022-10-15 11:14:38 +00:00
|
|
|
}, nil
|
|
|
|
}
|