// Package nebula contains helper functions and types which are useful for // setting up nebula configs, processes, and deployments. package nebula import ( "fmt" "net" "net/netip" "time" "github.com/slackhq/nebula/cert" ) // HostPublicCredentials contains certificate and signing public keys which are // able to be broadcast publicly. type HostPublicCredentials struct { Cert Certificate SigningKey SigningPublicKey } // HostPrivateCredentials contains the private key files which will // need to be present on a particular host. type HostPrivateCredentials struct { EncryptingPrivateKey EncryptingPrivateKey SigningPrivateKey SigningPrivateKey } // CAPublicCredentials contains certificate and signing public keys which are // able to be broadcast publicly. The signing public key is the same one which // is embedded into the certificate. type CAPublicCredentials struct { Cert Certificate SigningKey SigningPublicKey } // CACredentials contains the certificate and private files which can be used to // create and validate HostCredentials. Each file is PEM encoded. type CACredentials struct { Public CAPublicCredentials SigningPrivateKey SigningPrivateKey } // NewHostCert generates and signs a new host certificate containing the given // public key. func NewHostCert( caCreds CACredentials, hostPub EncryptingPublicKey, hostName HostName, ip netip.Addr, ) ( Certificate, error, ) { var ( caCert = caCreds.Public.Cert ipSlice = net.IP(ip.AsSlice()) ) issuer, err := caCert.inner.Sha256Sum() if err != nil { return Certificate{}, fmt.Errorf("getting ca.crt issuer: %w", err) } expireAt := caCert.inner.Details.NotAfter.Add(-1 * time.Second) subnet := caCert.inner.Details.Subnets[0] if !subnet.Contains(ipSlice) { return Certificate{}, fmt.Errorf("invalid ip %q, not contained by network subnet %q", ip, subnet) } hostCert := cert.NebulaCertificate{ Details: cert.NebulaCertificateDetails{ Name: string(hostName), Ips: []*net.IPNet{{ IP: ipSlice, Mask: subnet.Mask, }}, NotBefore: time.Now(), NotAfter: expireAt, PublicKey: hostPub.Bytes(), IsCA: false, Issuer: issuer, }, } if err := hostCert.CheckRootConstrains(&caCert.inner); err != nil { return Certificate{}, fmt.Errorf("validating certificate constraints: %w", err) } if err := signCert(&hostCert, caCreds.SigningPrivateKey); err != nil { return Certificate{}, fmt.Errorf("signing host cert with ca.key: %w", err) } return Certificate{hostCert}, nil } // NewHostCredentials generates a new key/cert for a nebula host using the CA // key which will be found in the adminFS. func NewHostCredentials( caCreds CACredentials, hostName HostName, ip netip.Addr, ) ( pub HostPublicCredentials, priv HostPrivateCredentials, err error, ) { var ( encPrivKey = NewEncryptingPrivateKey() encPubKey = encPrivKey.PublicKey() signingPubKey, signingPrivKey = GenerateSigningPair() ) hostCert, err := NewHostCert(caCreds, encPubKey, hostName, ip) if err != nil { err = fmt.Errorf("creating host certificate: %w", err) return } pub = HostPublicCredentials{ Cert: hostCert, SigningKey: signingPubKey, } priv = HostPrivateCredentials{ EncryptingPrivateKey: encPrivKey, SigningPrivateKey: signingPrivKey, } return } // NewCACredentials generates a CACredentials. The domain should be the network's root domain, // and is included in the signing certificate's Name field. func NewCACredentials(domain string, subnet IPNet) (CACredentials, error) { var ( signingPubKey, signingPrivKey = GenerateSigningPair() now = time.Now() expireAt = now.Add(2 * 365 * 24 * time.Hour) ) caCert := cert.NebulaCertificate{ Details: cert.NebulaCertificateDetails{ Name: fmt.Sprintf("%s isle root cert", domain), Subnets: []*net.IPNet{(*net.IPNet)(&subnet)}, NotBefore: now, NotAfter: expireAt, PublicKey: signingPubKey, IsCA: true, }, } if err := signCert(&caCert, signingPrivKey); err != nil { return CACredentials{}, fmt.Errorf("signing caCert: %w", err) } return CACredentials{ Public: CAPublicCredentials{ Cert: Certificate{caCert}, SigningKey: signingPubKey, }, SigningPrivateKey: signingPrivKey, }, nil }