mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2024-12-23 16:37:31 +00:00
83ec58afc7
After #1175 removed ioctl(2) fallback code shelling out to ifconfig(8), there is no code left (compiled on OpenBSD) that would fork(2) or execve(2). Drop the ability to run any executable file to double down on this, thus reducing the attack surface of this this experimental, internet facing daemon running as root. pledge(2) is doable, but needs more polish. unveil(2), however, is as simple as it gets. On other systems, this code is a NOOP, but can still help to implement similar safety belts.
350 lines
9.1 KiB
Go
350 lines
9.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"regexp"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"suah.dev/protect"
|
|
|
|
"github.com/gologme/log"
|
|
gsyslog "github.com/hashicorp/go-syslog"
|
|
"github.com/hjson/hjson-go/v4"
|
|
"github.com/kardianos/minwinsvc"
|
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
|
)
|
|
|
|
type node struct {
|
|
core *core.Core
|
|
tun *tun.TunAdapter
|
|
multicast *multicast.Multicast
|
|
admin *admin.AdminSocket
|
|
}
|
|
|
|
// The main function is responsible for configuring and starting Yggdrasil.
|
|
func main() {
|
|
// Not all operations are coverable with pledge(2), so immediately
|
|
// limit file system access with unveil(2), effectively preventing
|
|
// "proc exec" promises right from the start:
|
|
//
|
|
// - read arbitrary config file
|
|
// - create/write arbitrary log file
|
|
// - read/write/chmod/remove admin socket, if at all
|
|
if err := protect.Unveil("/", "rwc"); err != nil {
|
|
panic(fmt.Sprintf("unveil: / rwc: %v", err))
|
|
}
|
|
if err := protect.UnveilBlock(); err != nil {
|
|
panic(fmt.Sprintf("unveil: %v", err))
|
|
}
|
|
|
|
genconf := flag.Bool("genconf", false, "print a new config to stdout")
|
|
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
|
|
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
|
|
normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised")
|
|
exportkey := flag.Bool("exportkey", false, "use in combination with either -useconf or -useconffile, outputs your private key in PEM format")
|
|
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
|
|
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
|
|
ver := flag.Bool("version", false, "prints the version of this build")
|
|
logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
|
|
getaddr := flag.Bool("address", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 address")
|
|
getsnet := flag.Bool("subnet", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 subnet")
|
|
getpkey := flag.Bool("publickey", false, "use in combination with either -useconf or -useconffile, outputs your public key")
|
|
loglevel := flag.String("loglevel", "info", "loglevel to enable")
|
|
chuserto := flag.String("user", "", "user (and, optionally, group) to set UID/GID to")
|
|
flag.Parse()
|
|
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
// Catch interrupts from the operating system to exit gracefully.
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
|
|
// Create a new logger that logs output to stdout.
|
|
var logger *log.Logger
|
|
switch *logto {
|
|
case "stdout":
|
|
logger = log.New(os.Stdout, "", log.Flags())
|
|
|
|
case "syslog":
|
|
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil {
|
|
logger = log.New(syslogger, "", log.Flags()&^(log.Ldate|log.Ltime))
|
|
}
|
|
|
|
default:
|
|
if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
|
|
logger = log.New(logfd, "", log.Flags())
|
|
}
|
|
}
|
|
if logger == nil {
|
|
logger = log.New(os.Stdout, "", log.Flags())
|
|
logger.Warnln("Logging defaulting to stdout")
|
|
}
|
|
if *normaliseconf {
|
|
setLogLevel("error", logger)
|
|
} else {
|
|
setLogLevel(*loglevel, logger)
|
|
}
|
|
|
|
cfg := config.GenerateConfig()
|
|
var err error
|
|
switch {
|
|
case *ver:
|
|
fmt.Println("Build name:", version.BuildName())
|
|
fmt.Println("Build version:", version.BuildVersion())
|
|
return
|
|
|
|
case *autoconf:
|
|
// Use an autoconf-generated config, this will give us random keys and
|
|
// port numbers, and will use an automatically selected TUN interface.
|
|
|
|
case *useconf:
|
|
if _, err := cfg.ReadFrom(os.Stdin); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
case *useconffile != "":
|
|
f, err := os.Open(*useconffile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if _, err := cfg.ReadFrom(f); err != nil {
|
|
panic(err)
|
|
}
|
|
_ = f.Close()
|
|
|
|
case *genconf:
|
|
cfg.AdminListen = ""
|
|
var bs []byte
|
|
if *confjson {
|
|
bs, err = json.MarshalIndent(cfg, "", " ")
|
|
} else {
|
|
bs, err = hjson.Marshal(cfg)
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(string(bs))
|
|
return
|
|
|
|
default:
|
|
fmt.Println("Usage:")
|
|
flag.PrintDefaults()
|
|
|
|
if *getaddr || *getsnet {
|
|
fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.")
|
|
}
|
|
return
|
|
}
|
|
|
|
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
|
|
publicKey := privateKey.Public().(ed25519.PublicKey)
|
|
|
|
switch {
|
|
case *getaddr:
|
|
addr := address.AddrForKey(publicKey)
|
|
ip := net.IP(addr[:])
|
|
fmt.Println(ip.String())
|
|
return
|
|
|
|
case *getsnet:
|
|
snet := address.SubnetForKey(publicKey)
|
|
ipnet := net.IPNet{
|
|
IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
|
|
Mask: net.CIDRMask(len(snet)*8, 128),
|
|
}
|
|
fmt.Println(ipnet.String())
|
|
return
|
|
|
|
case *getpkey:
|
|
fmt.Println(hex.EncodeToString(publicKey))
|
|
return
|
|
|
|
case *normaliseconf:
|
|
cfg.AdminListen = ""
|
|
if cfg.PrivateKeyPath != "" {
|
|
cfg.PrivateKey = nil
|
|
}
|
|
var bs []byte
|
|
if *confjson {
|
|
bs, err = json.MarshalIndent(cfg, "", " ")
|
|
} else {
|
|
bs, err = hjson.Marshal(cfg)
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(string(bs))
|
|
return
|
|
|
|
case *exportkey:
|
|
pem, err := cfg.MarshalPEMPrivateKey()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(string(pem))
|
|
return
|
|
}
|
|
|
|
n := &node{}
|
|
|
|
// Set up the Yggdrasil node itself.
|
|
{
|
|
iprange := net.IPNet{
|
|
IP: net.ParseIP("200::"),
|
|
Mask: net.CIDRMask(7, 128),
|
|
}
|
|
options := []core.SetupOption{
|
|
core.NodeInfo(cfg.NodeInfo),
|
|
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
|
|
core.PeerFilter(func(ip net.IP) bool {
|
|
return !iprange.Contains(ip)
|
|
}),
|
|
}
|
|
for _, addr := range cfg.Listen {
|
|
options = append(options, core.ListenAddress(addr))
|
|
}
|
|
for _, peer := range cfg.Peers {
|
|
options = append(options, core.Peer{URI: peer})
|
|
}
|
|
for intf, peers := range cfg.InterfacePeers {
|
|
for _, peer := range peers {
|
|
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
|
|
}
|
|
}
|
|
for _, allowed := range cfg.AllowedPublicKeys {
|
|
k, err := hex.DecodeString(allowed)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
options = append(options, core.AllowedPublicKey(k[:]))
|
|
}
|
|
if n.core, err = core.New(cfg.Certificate, logger, options...); err != nil {
|
|
panic(err)
|
|
}
|
|
address, subnet := n.core.Address(), n.core.Subnet()
|
|
logger.Printf("Your public key is %s", hex.EncodeToString(n.core.PublicKey()))
|
|
logger.Printf("Your IPv6 address is %s", address.String())
|
|
logger.Printf("Your IPv6 subnet is %s", subnet.String())
|
|
}
|
|
|
|
// Set up the admin socket.
|
|
{
|
|
options := []admin.SetupOption{
|
|
admin.ListenAddress(cfg.AdminListen),
|
|
}
|
|
if cfg.LogLookups {
|
|
options = append(options, admin.LogLookups{})
|
|
}
|
|
if n.admin, err = admin.New(n.core, logger, options...); err != nil {
|
|
panic(err)
|
|
}
|
|
if n.admin != nil {
|
|
n.admin.SetupAdminHandlers()
|
|
}
|
|
}
|
|
|
|
// Set up the multicast module.
|
|
{
|
|
options := []multicast.SetupOption{}
|
|
for _, intf := range cfg.MulticastInterfaces {
|
|
options = append(options, multicast.MulticastInterface{
|
|
Regex: regexp.MustCompile(intf.Regex),
|
|
Beacon: intf.Beacon,
|
|
Listen: intf.Listen,
|
|
Port: intf.Port,
|
|
Priority: uint8(intf.Priority),
|
|
Password: intf.Password,
|
|
})
|
|
}
|
|
if n.multicast, err = multicast.New(n.core, logger, options...); err != nil {
|
|
panic(err)
|
|
}
|
|
if n.admin != nil && n.multicast != nil {
|
|
n.multicast.SetupAdminHandlers(n.admin)
|
|
}
|
|
}
|
|
|
|
// Set up the TUN module.
|
|
{
|
|
options := []tun.SetupOption{
|
|
tun.InterfaceName(cfg.IfName),
|
|
tun.InterfaceMTU(cfg.IfMTU),
|
|
}
|
|
if n.tun, err = tun.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil {
|
|
panic(err)
|
|
}
|
|
if n.admin != nil && n.tun != nil {
|
|
n.tun.SetupAdminHandlers(n.admin)
|
|
}
|
|
}
|
|
|
|
//Windows service shutdown
|
|
minwinsvc.SetOnExit(func() {
|
|
logger.Infof("Shutting down service ...")
|
|
cancel()
|
|
// Wait for all parts to shutdown properly
|
|
<-done
|
|
})
|
|
|
|
// Change user if requested
|
|
if *chuserto != "" {
|
|
err = chuser(*chuserto)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Block until we are told to shut down.
|
|
<-ctx.Done()
|
|
|
|
// Shut down the node.
|
|
_ = n.admin.Stop()
|
|
_ = n.multicast.Stop()
|
|
_ = n.tun.Stop()
|
|
n.core.Stop()
|
|
}
|
|
|
|
func setLogLevel(loglevel string, logger *log.Logger) {
|
|
levels := [...]string{"error", "warn", "info", "debug", "trace"}
|
|
loglevel = strings.ToLower(loglevel)
|
|
|
|
contains := func() bool {
|
|
for _, l := range levels {
|
|
if l == loglevel {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
if !contains() { // set default log level
|
|
logger.Infoln("Loglevel parse failed. Set default level(info)")
|
|
loglevel = "info"
|
|
}
|
|
|
|
for _, l := range levels {
|
|
logger.EnableLevel(l)
|
|
if l == loglevel {
|
|
break
|
|
}
|
|
}
|
|
}
|