mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2026-08-12 22:29:29 +04:00
Implement group password for session auth (#1350)
This adds a new `GroupPassword` option to the configuration file. Only nodes that share the same `GroupPassword` are able to exchange overlay traffic with each other, allowing the formation of closed networks. This does not affect peerings, nodes with different `GroupPassword` set can still forward traffic for each other and act as routers in the wider network. If the `GroupPassword` is set, then it is not possible to access public services or nodes on the public testnet. This is intentional. Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
This commit is contained in:
@@ -211,6 +211,9 @@ func main() {
|
||||
for _, peer := range cfg.Peers {
|
||||
options = append(options, core.Peer{URI: peer})
|
||||
}
|
||||
if cfg.GroupPassword != "" {
|
||||
options = append(options, core.GroupPassword(cfg.GroupPassword))
|
||||
}
|
||||
for intf, peers := range cfg.InterfacePeers {
|
||||
for _, peer := range peers {
|
||||
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/Arceliar/ironwood v0.0.0-20260117132459-7017dbc41d8e
|
||||
github.com/Arceliar/ironwood v0.0.0-20260601001307-e1eeb5b910a4
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
|
||||
github.com/cheggaaa/pb/v3 v3.1.7
|
||||
github.com/coder/websocket v1.8.14
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
github.com/Arceliar/ironwood v0.0.0-20260117132459-7017dbc41d8e h1:s7MuhcZu2hNfVvYLT4cKnQt7pZ1+z6eL2E48YlaAqzY=
|
||||
github.com/Arceliar/ironwood v0.0.0-20260117132459-7017dbc41d8e/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0=
|
||||
github.com/Arceliar/ironwood v0.0.0-20260601001307-e1eeb5b910a4 h1:WD8VAn6IGTeo5BZ84Zpo4szFl6y9s941efvbcIQuooY=
|
||||
github.com/Arceliar/ironwood v0.0.0-20260601001307-e1eeb5b910a4/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
|
||||
@@ -48,7 +48,8 @@ type NodeConfig struct {
|
||||
Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nThis is not required if you wish to establish outbound peerings only.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
|
||||
AdminListen string `json:",omitempty" comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
|
||||
MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Regex is a regular expression which is matched against an\ninterface name, and interfaces use the first configuration that they\nmatch against. Beacon controls whether or not your node advertises its\npresence to others, whereas Listen controls whether or not your node\nlistens out for and tries to connect to other advertising nodes. See\nhttps://yggdrasil-network.github.io/configurationref.html#multicastinterfaces\nfor more supported options."`
|
||||
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast.\nWARNING: THIS IS NOT A FIREWALL and DOES NOT limit who can reach\nopen ports or services running on your machine!"`
|
||||
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast.\nWARNING: THIS IS NOT A FIREWALL and DOES NOT limit who can reach\nopen ports or services running on your machine, for that see the\nGroupPassword option below."`
|
||||
GroupPassword string `comment:"Traffic is only allowed to/from nodes with the same group password.\nIf you want to form a private sub-network or ensure that other public\nusers cannot connect to your machines, choose a strong group password\nand then configure the same password only with other group members.\nIf left empty or not specified, public connectivity will be permitted.\nIf specified, you WILL NOT be able to reach public services or hosts.\nThis option DOES NOT affect peering connections or traffic routing."`
|
||||
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
|
||||
IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
|
||||
LogLookups bool `json:",omitempty"`
|
||||
|
||||
+3
-1
@@ -42,6 +42,7 @@ type Core struct {
|
||||
nodeinfo NodeInfo // immutable after startup
|
||||
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
|
||||
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
|
||||
groupPassword string // immutable after startup
|
||||
}
|
||||
pathNotify func(ed25519.PublicKey)
|
||||
}
|
||||
@@ -94,8 +95,9 @@ func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, erro
|
||||
keyXform := func(key ed25519.PublicKey) ed25519.PublicKey {
|
||||
return address.SubnetForKey(key).GetKey()
|
||||
}
|
||||
if c.PacketConn, err = iwe.NewPacketConn(
|
||||
if c.PacketConn, err = iwe.NewPacketConnWithPassword(
|
||||
c.secret,
|
||||
c.config.groupPassword,
|
||||
iwn.WithBloomTransform(keyXform),
|
||||
iwn.WithPeerMaxMessageSize(65535*2),
|
||||
iwn.WithPathNotify(c.doPathNotify),
|
||||
|
||||
@@ -2,7 +2,10 @@ package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -32,6 +35,13 @@ func require_NoError(t *testing.T, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func require_Error(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func require_Equal[T comparable](t *testing.T, a, b T) {
|
||||
t.Helper()
|
||||
if a != b {
|
||||
@@ -288,3 +298,83 @@ func TestAllowedPublicKeysLocal(t *testing.T) {
|
||||
require_True(t, peers[0].Up)
|
||||
require_True(t, peers[0].LastError == nil)
|
||||
}
|
||||
|
||||
func TestGroupPassword(t *testing.T) {
|
||||
logger := GetLoggerWithPrefix("", false)
|
||||
cfgA, cfgB, cfgC := config.GenerateConfig(), config.GenerateConfig(), config.GenerateConfig()
|
||||
require_NoError(t, cfgA.GenerateSelfSignedCertificate())
|
||||
require_NoError(t, cfgB.GenerateSelfSignedCertificate())
|
||||
require_NoError(t, cfgC.GenerateSelfSignedCertificate())
|
||||
|
||||
nodeA, err := New(cfgA.Certificate, logger, GroupPassword("test-group-password"))
|
||||
require_NoError(t, err)
|
||||
defer nodeA.Stop()
|
||||
|
||||
nodeB, err := New(cfgB.Certificate, logger, GroupPassword("test-group-password"))
|
||||
require_NoError(t, err)
|
||||
defer nodeB.Stop()
|
||||
|
||||
nodeC, err := New(cfgC.Certificate, logger, GroupPassword("different-test-group-password"))
|
||||
require_NoError(t, err)
|
||||
defer nodeC.Stop()
|
||||
|
||||
pathFound := map[string]chan struct{}{
|
||||
nodeB.LocalAddr().String(): make(chan struct{}, 1),
|
||||
nodeC.LocalAddr().String(): make(chan struct{}, 1),
|
||||
}
|
||||
nodeA.SetPathNotify(func(key ed25519.PublicKey) {
|
||||
pathFound[hex.EncodeToString(key)] <- struct{}{}
|
||||
})
|
||||
|
||||
u, err := url.Parse("tcp://localhost:0")
|
||||
require_NoError(t, err)
|
||||
|
||||
l, err := nodeA.Listen(u, "")
|
||||
require_NoError(t, err)
|
||||
|
||||
u, err = url.Parse("tcp://" + l.Addr().String())
|
||||
require_NoError(t, err)
|
||||
|
||||
require_NoError(t, nodeB.AddPeer(u, ""))
|
||||
require_NoError(t, nodeC.AddPeer(u, ""))
|
||||
|
||||
require_True(t, WaitConnected(nodeA, nodeB))
|
||||
require_True(t, WaitConnected(nodeA, nodeC))
|
||||
|
||||
var connA net.PacketConn = nodeA.PacketConn
|
||||
var connB net.PacketConn = nodeB.PacketConn
|
||||
var connC net.PacketConn = nodeC.PacketConn
|
||||
|
||||
go func() {
|
||||
var buf [1024]byte
|
||||
for t.Context().Err() == nil {
|
||||
// Needed as encrypted package relies on ReadFrom to process session acks.
|
||||
_, _, _ = connA.ReadFrom(buf[:])
|
||||
}
|
||||
}()
|
||||
|
||||
matchingPasswordMessage := []byte("matching group password")
|
||||
differentPasswordMessage := []byte("different group password")
|
||||
|
||||
_, err = connA.WriteTo(matchingPasswordMessage, connB.LocalAddr())
|
||||
require_NoError(t, err)
|
||||
|
||||
_, err = connA.WriteTo(differentPasswordMessage, connC.LocalAddr())
|
||||
require_NoError(t, err)
|
||||
|
||||
<-pathFound[nodeB.LocalAddr().String()]
|
||||
<-pathFound[nodeC.LocalAddr().String()]
|
||||
|
||||
var buf [1024]byte
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
require_NoError(t, connB.SetReadDeadline(deadline))
|
||||
require_NoError(t, connC.SetReadDeadline(deadline))
|
||||
|
||||
n, from, err := connB.ReadFrom(buf[:])
|
||||
require_NoError(t, err)
|
||||
require_Equal(t, from.String(), connA.LocalAddr().String())
|
||||
require_True(t, bytes.Equal(buf[:n], matchingPasswordMessage))
|
||||
|
||||
_, _, err = connC.ReadFrom(buf[:])
|
||||
require_Error(t, err)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ func (c *Core) _applyOption(opt SetupOption) (err error) {
|
||||
pk := [32]byte{}
|
||||
copy(pk[:], v)
|
||||
c.config._allowedPublicKeys[pk] = struct{}{}
|
||||
case GroupPassword:
|
||||
c.config.groupPassword = string(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -52,6 +54,7 @@ type NodeInfo map[string]interface{}
|
||||
type NodeInfoPrivacy bool
|
||||
type AllowedPublicKey ed25519.PublicKey
|
||||
type PeerFilter func(net.IP) bool
|
||||
type GroupPassword string
|
||||
|
||||
func (a ListenAddress) isSetupOption() {}
|
||||
func (a Peer) isSetupOption() {}
|
||||
@@ -59,3 +62,4 @@ func (a NodeInfo) isSetupOption() {}
|
||||
func (a NodeInfoPrivacy) isSetupOption() {}
|
||||
func (a AllowedPublicKey) isSetupOption() {}
|
||||
func (a PeerFilter) isSetupOption() {}
|
||||
func (a GroupPassword) isSetupOption() {}
|
||||
|
||||
Reference in New Issue
Block a user