126 lines
3.0 KiB
Go
126 lines
3.0 KiB
Go
|
// 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/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
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
}
|