nebula/main.go

447 lines
14 KiB
Go
Raw Normal View History

2019-11-19 18:00:20 +01:00
package nebula
import (
"encoding/binary"
"fmt"
"net"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/sirupsen/logrus"
"github.com/slackhq/nebula/sshd"
"gopkg.in/yaml.v2"
2019-11-19 18:00:20 +01:00
)
// The caller should provide a real logger, we have one just in case
2019-11-19 18:00:20 +01:00
var l = logrus.New()
type m map[string]interface{}
type CommandRequest struct {
Command string
Callback chan error
}
func Main(config *Config, configTest bool, block bool, buildVersion string, logger *logrus.Logger, tunFd *int, commandChan <-chan CommandRequest) error {
l = logger
2019-11-19 18:00:20 +01:00
l.Formatter = &logrus.TextFormatter{
FullTimestamp: true,
}
// Print the config if in test, the exit comes later
if configTest {
b, err := yaml.Marshal(config.Settings)
if err != nil {
return err
2019-11-19 18:00:20 +01:00
}
// Print the final config
2019-11-19 18:00:20 +01:00
l.Println(string(b))
}
err := configLogger(config)
2019-11-19 18:00:20 +01:00
if err != nil {
return NewContextualError("Failed to configure the logger", nil, err)
2019-11-19 18:00:20 +01:00
}
config.RegisterReloadCallback(func(c *Config) {
err := configLogger(c)
if err != nil {
l.WithError(err).Error("Failed to configure the logger")
}
})
// trustedCAs is currently a global, so loadCA operates on that global directly
trustedCAs, err = loadCAFromConfig(config)
if err != nil {
//The errors coming out of loadCA are already nicely formatted
return NewContextualError("Failed to load ca from config", nil, err)
2019-11-19 18:00:20 +01:00
}
l.WithField("fingerprints", trustedCAs.GetFingerprints()).Debug("Trusted CA fingerprints")
cs, err := NewCertStateFromConfig(config)
if err != nil {
//The errors coming out of NewCertStateFromConfig are already nicely formatted
return NewContextualError("Failed to load certificate from config", nil, err)
2019-11-19 18:00:20 +01:00
}
l.WithField("cert", cs.certificate).Debug("Client nebula certificate")
fw, err := NewFirewallFromConfig(cs.certificate, config)
if err != nil {
return NewContextualError("Error while loading firewall rules", nil, err)
2019-11-19 18:00:20 +01:00
}
l.WithField("firewallHash", fw.GetRuleHash()).Info("Firewall started")
// TODO: make sure mask is 4 bytes
tunCidr := cs.certificate.Details.Ips[0]
routes, err := parseRoutes(config, tunCidr)
if err != nil {
return NewContextualError("Could not parse tun.routes", nil, err)
2019-11-19 18:00:20 +01:00
}
2019-12-12 18:31:22 +01:00
unsafeRoutes, err := parseUnsafeRoutes(config, tunCidr)
if err != nil {
return NewContextualError("Could not parse tun.unsafe_routes", nil, err)
2019-12-12 18:31:22 +01:00
}
2019-11-19 18:00:20 +01:00
ssh, err := sshd.NewSSHServer(l.WithField("subsystem", "sshd"))
wireSSHReload(ssh, config)
if config.GetBool("sshd.enabled", false) {
err = configSSH(ssh, config)
if err != nil {
return NewContextualError("Error while configuring the sshd", nil, err)
2019-11-19 18:00:20 +01:00
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// All non system modifying configuration consumption should live above this line
// tun config, listeners, anything modifying the computer should be below
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var tun Inside
if !configTest {
config.CatchHUP()
switch {
case config.GetBool("tun.disabled", false):
tun = newDisabledTun(tunCidr, l)
case tunFd != nil:
tun, err = newTunFromFd(
*tunFd,
tunCidr,
config.GetInt("tun.mtu", DEFAULT_MTU),
routes,
unsafeRoutes,
config.GetInt("tun.tx_queue", 500),
)
default:
tun, err = newTun(
config.GetString("tun.dev", ""),
tunCidr,
config.GetInt("tun.mtu", DEFAULT_MTU),
routes,
unsafeRoutes,
config.GetInt("tun.tx_queue", 500),
)
}
if err != nil {
return NewContextualError("Failed to get a tun/tap device", nil, err)
}
2019-11-19 18:00:20 +01:00
}
// set up our UDP listener
udpQueues := config.GetInt("listen.routines", 1)
var udpServer *udpConn
if !configTest {
udpServer, err = NewListener(config.GetString("listen.host", "0.0.0.0"), config.GetInt("listen.port", 0), udpQueues > 1)
if err != nil {
return NewContextualError("Failed to open udp listener", nil, err)
}
udpServer.reloadConfig(config)
2019-11-19 18:00:20 +01:00
}
sigChan := make(chan os.Signal)
killChan := make(chan CommandRequest)
if commandChan != nil {
go func() {
cmd := CommandRequest{}
for {
cmd = <-commandChan
switch cmd.Command {
case "rebind":
udpServer.Rebind()
case "exit":
killChan <- cmd
}
}
}()
}
2019-11-19 18:00:20 +01:00
// Set up my internal host map
var preferredRanges []*net.IPNet
rawPreferredRanges := config.GetStringSlice("preferred_ranges", []string{})
// First, check if 'preferred_ranges' is set and fallback to 'local_range'
if len(rawPreferredRanges) > 0 {
for _, rawPreferredRange := range rawPreferredRanges {
_, preferredRange, err := net.ParseCIDR(rawPreferredRange)
if err != nil {
return NewContextualError("Failed to parse preferred ranges", nil, err)
2019-11-19 18:00:20 +01:00
}
preferredRanges = append(preferredRanges, preferredRange)
}
}
// local_range was superseded by preferred_ranges. If it is still present,
// merge the local_range setting into preferred_ranges. We will probably
// deprecate local_range and remove in the future.
rawLocalRange := config.GetString("local_range", "")
if rawLocalRange != "" {
_, localRange, err := net.ParseCIDR(rawLocalRange)
if err != nil {
return NewContextualError("Failed to parse local_range", nil, err)
2019-11-19 18:00:20 +01:00
}
// Check if the entry for local_range was already specified in
// preferred_ranges. Don't put it into the slice twice if so.
var found bool
for _, r := range preferredRanges {
if r.String() == localRange.String() {
found = true
break
}
}
if !found {
preferredRanges = append(preferredRanges, localRange)
}
}
hostMap := NewHostMap("main", tunCidr, preferredRanges)
hostMap.SetDefaultRoute(ip2int(net.ParseIP(config.GetString("default_route", "0.0.0.0"))))
2019-12-12 17:34:17 +01:00
hostMap.addUnsafeRoutes(&unsafeRoutes)
hostMap.metricsEnabled = config.GetBool("stats.message_metrics", false)
2019-12-12 17:34:17 +01:00
2019-11-19 18:00:20 +01:00
l.WithField("network", hostMap.vpnCIDR).WithField("preferredRanges", hostMap.preferredRanges).Info("Main HostMap created")
/*
config.SetDefault("promoter.interval", 10)
go hostMap.Promoter(config.GetInt("promoter.interval"))
*/
punchy := NewPunchyFromConfig(config)
if punchy.Punch && !configTest {
2019-11-19 18:00:20 +01:00
l.Info("UDP hole punching enabled")
go hostMap.Punchy(udpServer)
}
port := config.GetInt("listen.port", 0)
// If port is dynamic, discover it
if port == 0 && !configTest {
2019-11-19 18:00:20 +01:00
uPort, err := udpServer.LocalAddr()
if err != nil {
return NewContextualError("Failed to get listening port", nil, err)
2019-11-19 18:00:20 +01:00
}
port = int(uPort.Port)
}
amLighthouse := config.GetBool("lighthouse.am_lighthouse", false)
// warn if am_lighthouse is enabled but upstream lighthouses exists
rawLighthouseHosts := config.GetStringSlice("lighthouse.hosts", []string{})
if amLighthouse && len(rawLighthouseHosts) != 0 {
l.Warn("lighthouse.am_lighthouse enabled on node but upstream lighthouses exist in config")
}
lighthouseHosts := make([]uint32, len(rawLighthouseHosts))
for i, host := range rawLighthouseHosts {
ip := net.ParseIP(host)
if ip == nil {
return NewContextualError("Unable to parse lighthouse host entry", m{"host": host, "entry": i + 1}, nil)
}
if !tunCidr.Contains(ip) {
return NewContextualError("lighthouse host is not in our subnet, invalid", m{"vpnIp": ip, "network": tunCidr.String()}, nil)
}
lighthouseHosts[i] = ip2int(ip)
}
2019-11-19 18:00:20 +01:00
lightHouse := NewLightHouse(
amLighthouse,
ip2int(tunCidr.IP),
lighthouseHosts,
2019-11-19 18:00:20 +01:00
//TODO: change to a duration
config.GetInt("lighthouse.interval", 10),
port,
udpServer,
punchy.Respond,
punchy.Delay,
config.GetBool("stats.lighthouse_metrics", false),
2019-11-19 18:00:20 +01:00
)
remoteAllowList, err := config.GetAllowList("lighthouse.remote_allow_list", false)
Add lighthouse.{remoteAllowList,localAllowList} (#217) These settings make it possible to blacklist / whitelist IP addresses that are used for remote connections. `lighthouse.remoteAllowList` filters which remote IPs are allow when fetching from the lighthouse (or, if you are the lighthouse, which IPs you store and forward to querying hosts). By default, any remote IPs are allowed. You can provide CIDRs here with `true` to allow and `false` to deny. The most specific CIDR rule applies to each remote. If all rules are "allow", the default will be "deny", and vice-versa. If both "allow" and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0" as the default. lighthouse: remoteAllowList: # Example to block IPs from this subnet from being used for remote IPs. "172.16.0.0/12": false # A more complicated example, allow public IPs but only private IPs from a specific subnet "0.0.0.0/0": true "10.0.0.0/8": false "10.42.42.0/24": true `lighthouse.localAllowList` has the same logic as above, but it applies to the local addresses we advertise to the lighthouse. Additionally, you can specify an `interfaces` map of regular expressions to match against interface names. The regexp must match the entire name. All interface rules must be either true or false (and the default rule will be the inverse). CIDR rules are matched after interface name rules. Default is all local IP addresses. lighthouse: localAllowList: # Example to blacklist docker interfaces. interfaces: 'docker.*': false # Example to only advertise IPs in this subnet to the lighthouse. "10.0.0.0/8": true
2020-04-08 21:36:43 +02:00
if err != nil {
return NewContextualError("Invalid lighthouse.remote_allow_list", nil, err)
Add lighthouse.{remoteAllowList,localAllowList} (#217) These settings make it possible to blacklist / whitelist IP addresses that are used for remote connections. `lighthouse.remoteAllowList` filters which remote IPs are allow when fetching from the lighthouse (or, if you are the lighthouse, which IPs you store and forward to querying hosts). By default, any remote IPs are allowed. You can provide CIDRs here with `true` to allow and `false` to deny. The most specific CIDR rule applies to each remote. If all rules are "allow", the default will be "deny", and vice-versa. If both "allow" and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0" as the default. lighthouse: remoteAllowList: # Example to block IPs from this subnet from being used for remote IPs. "172.16.0.0/12": false # A more complicated example, allow public IPs but only private IPs from a specific subnet "0.0.0.0/0": true "10.0.0.0/8": false "10.42.42.0/24": true `lighthouse.localAllowList` has the same logic as above, but it applies to the local addresses we advertise to the lighthouse. Additionally, you can specify an `interfaces` map of regular expressions to match against interface names. The regexp must match the entire name. All interface rules must be either true or false (and the default rule will be the inverse). CIDR rules are matched after interface name rules. Default is all local IP addresses. lighthouse: localAllowList: # Example to blacklist docker interfaces. interfaces: 'docker.*': false # Example to only advertise IPs in this subnet to the lighthouse. "10.0.0.0/8": true
2020-04-08 21:36:43 +02:00
}
lightHouse.SetRemoteAllowList(remoteAllowList)
localAllowList, err := config.GetAllowList("lighthouse.local_allow_list", true)
Add lighthouse.{remoteAllowList,localAllowList} (#217) These settings make it possible to blacklist / whitelist IP addresses that are used for remote connections. `lighthouse.remoteAllowList` filters which remote IPs are allow when fetching from the lighthouse (or, if you are the lighthouse, which IPs you store and forward to querying hosts). By default, any remote IPs are allowed. You can provide CIDRs here with `true` to allow and `false` to deny. The most specific CIDR rule applies to each remote. If all rules are "allow", the default will be "deny", and vice-versa. If both "allow" and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0" as the default. lighthouse: remoteAllowList: # Example to block IPs from this subnet from being used for remote IPs. "172.16.0.0/12": false # A more complicated example, allow public IPs but only private IPs from a specific subnet "0.0.0.0/0": true "10.0.0.0/8": false "10.42.42.0/24": true `lighthouse.localAllowList` has the same logic as above, but it applies to the local addresses we advertise to the lighthouse. Additionally, you can specify an `interfaces` map of regular expressions to match against interface names. The regexp must match the entire name. All interface rules must be either true or false (and the default rule will be the inverse). CIDR rules are matched after interface name rules. Default is all local IP addresses. lighthouse: localAllowList: # Example to blacklist docker interfaces. interfaces: 'docker.*': false # Example to only advertise IPs in this subnet to the lighthouse. "10.0.0.0/8": true
2020-04-08 21:36:43 +02:00
if err != nil {
return NewContextualError("Invalid lighthouse.local_allow_list", nil, err)
Add lighthouse.{remoteAllowList,localAllowList} (#217) These settings make it possible to blacklist / whitelist IP addresses that are used for remote connections. `lighthouse.remoteAllowList` filters which remote IPs are allow when fetching from the lighthouse (or, if you are the lighthouse, which IPs you store and forward to querying hosts). By default, any remote IPs are allowed. You can provide CIDRs here with `true` to allow and `false` to deny. The most specific CIDR rule applies to each remote. If all rules are "allow", the default will be "deny", and vice-versa. If both "allow" and "deny" rules are present, then you MUST set a rule for "0.0.0.0/0" as the default. lighthouse: remoteAllowList: # Example to block IPs from this subnet from being used for remote IPs. "172.16.0.0/12": false # A more complicated example, allow public IPs but only private IPs from a specific subnet "0.0.0.0/0": true "10.0.0.0/8": false "10.42.42.0/24": true `lighthouse.localAllowList` has the same logic as above, but it applies to the local addresses we advertise to the lighthouse. Additionally, you can specify an `interfaces` map of regular expressions to match against interface names. The regexp must match the entire name. All interface rules must be either true or false (and the default rule will be the inverse). CIDR rules are matched after interface name rules. Default is all local IP addresses. lighthouse: localAllowList: # Example to blacklist docker interfaces. interfaces: 'docker.*': false # Example to only advertise IPs in this subnet to the lighthouse. "10.0.0.0/8": true
2020-04-08 21:36:43 +02:00
}
lightHouse.SetLocalAllowList(localAllowList)
2019-11-24 00:55:23 +01:00
//TODO: Move all of this inside functions in lighthouse.go
2019-11-19 18:00:20 +01:00
for k, v := range config.GetMap("static_host_map", map[interface{}]interface{}{}) {
vpnIp := net.ParseIP(fmt.Sprintf("%v", k))
if !tunCidr.Contains(vpnIp) {
return NewContextualError("static_host_map key is not in our subnet, invalid", m{"vpnIp": vpnIp, "network": tunCidr.String()}, nil)
}
2019-11-19 18:00:20 +01:00
vals, ok := v.([]interface{})
if ok {
for _, v := range vals {
parts := strings.Split(fmt.Sprintf("%v", v), ":")
addr, err := net.ResolveIPAddr("ip", parts[0])
if err == nil {
ip := addr.IP
port, err := strconv.Atoi(parts[1])
if err != nil {
return NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp}, err)
2019-11-19 18:00:20 +01:00
}
lightHouse.AddRemote(ip2int(vpnIp), NewUDPAddr(ip2int(ip), uint16(port)), true)
}
}
} else {
//TODO: make this all a helper
parts := strings.Split(fmt.Sprintf("%v", v), ":")
addr, err := net.ResolveIPAddr("ip", parts[0])
if err == nil {
ip := addr.IP
port, err := strconv.Atoi(parts[1])
if err != nil {
return NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp}, err)
2019-11-19 18:00:20 +01:00
}
lightHouse.AddRemote(ip2int(vpnIp), NewUDPAddr(ip2int(ip), uint16(port)), true)
}
}
}
2019-11-24 00:55:23 +01:00
err = lightHouse.ValidateLHStaticEntries()
if err != nil {
2019-11-24 00:55:23 +01:00
l.WithError(err).Error("Lighthouse unreachable")
}
var messageMetrics *MessageMetrics
if config.GetBool("stats.message_metrics", false) {
messageMetrics = newMessageMetrics()
} else {
messageMetrics = newMessageMetricsOnlyRecvError()
}
handshakeConfig := HandshakeConfig{
tryInterval: config.GetDuration("handshakes.try_interval", DefaultHandshakeTryInterval),
retries: config.GetInt("handshakes.retries", DefaultHandshakeRetries),
waitRotation: config.GetInt("handshakes.wait_rotation", DefaultHandshakeWaitRotation),
triggerBuffer: config.GetInt("handshakes.trigger_buffer", DefaultHandshakeTriggerBuffer),
messageMetrics: messageMetrics,
}
handshakeManager := NewHandshakeManager(tunCidr, preferredRanges, hostMap, lightHouse, udpServer, handshakeConfig)
lightHouse.handshakeTrigger = handshakeManager.trigger
2019-11-19 18:00:20 +01:00
//TODO: These will be reused for psk
//handshakeMACKey := config.GetString("handshake_mac.key", "")
//handshakeAcceptedMACKeys := config.GetStringSlice("handshake_mac.accepted_keys", []string{})
2019-11-19 18:00:20 +01:00
serveDns := config.GetBool("lighthouse.serve_dns", false)
2019-11-19 18:00:20 +01:00
checkInterval := config.GetInt("timers.connection_alive_interval", 5)
pendingDeletionInterval := config.GetInt("timers.pending_deletion_interval", 10)
ifConfig := &InterfaceConfig{
HostMap: hostMap,
Inside: tun,
Outside: udpServer,
certState: cs,
Cipher: config.GetString("cipher", "aes"),
Firewall: fw,
ServeDns: serveDns,
HandshakeManager: handshakeManager,
lightHouse: lightHouse,
checkInterval: checkInterval,
pendingDeletionInterval: pendingDeletionInterval,
DropLocalBroadcast: config.GetBool("tun.drop_local_broadcast", false),
DropMulticast: config.GetBool("tun.drop_multicast", false),
UDPBatchSize: config.GetInt("listen.batch", 64),
MessageMetrics: messageMetrics,
2019-11-19 18:00:20 +01:00
}
switch ifConfig.Cipher {
case "aes":
2020-03-30 20:23:55 +02:00
noiseEndianness = binary.BigEndian
2019-11-19 18:00:20 +01:00
case "chachapoly":
2020-03-30 20:23:55 +02:00
noiseEndianness = binary.LittleEndian
2019-11-19 18:00:20 +01:00
default:
return fmt.Errorf("unknown cipher: %v", ifConfig.Cipher)
2019-11-19 18:00:20 +01:00
}
var ifce *Interface
if !configTest {
ifce, err = NewInterface(ifConfig)
if err != nil {
return fmt.Errorf("failed to initialize interface: %s", err)
}
2019-11-19 18:00:20 +01:00
ifce.RegisterConfigChangeCallbacks(config)
2019-11-19 18:00:20 +01:00
go handshakeManager.Run(ifce)
go lightHouse.LhUpdateWorker(ifce)
}
2019-11-19 18:00:20 +01:00
err = startStats(config, configTest)
2019-11-19 18:00:20 +01:00
if err != nil {
return NewContextualError("Failed to start stats emitter", nil, err)
2019-11-19 18:00:20 +01:00
}
if configTest {
return nil
}
2019-11-19 18:00:20 +01:00
//TODO: check if we _should_ be emitting stats
go ifce.emitStats(config.GetDuration("stats.interval", time.Second*10))
attachCommands(ssh, hostMap, handshakeManager.pendingHostMap, lightHouse, ifce)
ifce.Run(config.GetInt("tun.routines", 1), udpQueues, buildVersion)
// Start DNS server last to allow using the nebula IP as lighthouse.dns.host
if amLighthouse && serveDns {
l.Debugln("Starting dns server")
go dnsMain(hostMap, config)
}
if block {
// Just sit here and be friendly, main thread.
shutdownBlock(ifce, sigChan, killChan)
} else {
// Even though we aren't blocking we still want to shutdown gracefully
go shutdownBlock(ifce, sigChan, killChan)
}
return nil
2019-11-19 18:00:20 +01:00
}
func shutdownBlock(ifce *Interface, sigChan chan os.Signal, killChan chan CommandRequest) {
var cmd CommandRequest
var sig string
2019-11-19 18:00:20 +01:00
signal.Notify(sigChan, syscall.SIGTERM)
signal.Notify(sigChan, syscall.SIGINT)
select {
case rawSig := <-sigChan:
sig = rawSig.String()
case cmd = <-killChan:
sig = "controlling app"
}
2019-11-19 18:00:20 +01:00
l.WithField("signal", sig).Info("Caught signal, shutting down")
//TODO: stop tun and udp routines, the lock on hostMap effectively does that though
2019-11-19 18:00:20 +01:00
//TODO: this is probably better as a function in ConnectionManager or HostMap directly
ifce.hostMap.Lock()
for _, h := range ifce.hostMap.Hosts {
if h.ConnectionState.ready {
ifce.send(closeTunnel, 0, h.ConnectionState, h, h.remote, []byte{}, make([]byte, 12, 12), make([]byte, mtu))
l.WithField("vpnIp", IntIp(h.hostId)).WithField("udpAddr", h.remote).
Debug("Sending close tunnel message")
}
}
ifce.hostMap.Unlock()
l.WithField("signal", sig).Info("Goodbye")
select {
case cmd.Callback <- nil:
default:
}
2019-11-19 18:00:20 +01:00
}