mirror of
https://github.com/hyprspace/hyprspace.git
synced 2026-09-12 19:51:07 +05:00
Implementation for basic service access control (#95)
* Added ServicesACL config Signed-off-by: Roland Urbano <urbano.roland@gmail.com> * Adding filter to service receiver Signed-off-by: Roland Urbano <urbano.roland@gmail.com> * Implemented ACL handling in ServiceNetwork Signed-off-by: Roland Urbano <roland.urbano@nts.eu> * Updated Schema and added parsing for acl config Signed-off-by: Roland Urbano <urbano.roland@gmail.com> * Updated service package and fixed ACL handling Signed-off-by: Roland Urbano <urbano.roland@gmail.com> * go-jsonschema: 0.16.0 -> 0.22.0 * nixos: set acls on services directly * config: move ServicesACL to Services * svc: simplify acl logic * remove whitespace * apply nixfmt --------- Signed-off-by: Roland Urbano <urbano.roland@gmail.com> Signed-off-by: Roland Urbano <roland.urbano@nts.eu> Co-authored-by: Max <max@privatevoid.net>
This commit is contained in:
parent
ff94c7869e
commit
ed30c60c95
@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@ -26,7 +27,7 @@ type Config struct {
|
||||
PrivateKey crypto.PrivKey `json:"-"`
|
||||
BuiltinAddr4 net.IP `json:"-"`
|
||||
BuiltinAddr6 net.IP `json:"-"`
|
||||
Services map[string]multiaddr.Multiaddr `json:"-"`
|
||||
Services map[string]Service `json:"-"`
|
||||
}
|
||||
|
||||
// Peer defines a peer in the configuration. We might add more to this later.
|
||||
@ -49,6 +50,17 @@ type RouteTableEntry struct {
|
||||
Target Peer
|
||||
}
|
||||
|
||||
// Service represents the configuration for a specific service provided by this node.
|
||||
// Whitelist and Blacklist allow fine granularity in access control.
|
||||
// If Blacklist is set, this will be evaluated first and any client id present in Blacklist will
|
||||
// have access denied. Whitelist is evaluated after.
|
||||
type Service struct {
|
||||
Target multiaddr.Multiaddr
|
||||
EnableWhitelist bool
|
||||
Whitelist map[peer.ID]struct{}
|
||||
Blacklist map[peer.ID]struct{}
|
||||
}
|
||||
|
||||
func (rte RouteTableEntry) Network() net.IPNet {
|
||||
return rte.Net
|
||||
}
|
||||
@ -144,13 +156,34 @@ func Read(path string) (*Config, error) {
|
||||
result.Peers[i] = p
|
||||
}
|
||||
|
||||
result.Services = make(map[string]multiaddr.Multiaddr)
|
||||
for name, addrString := range input.Services {
|
||||
addr, err := multiaddr.NewMultiaddr(addrString)
|
||||
result.Services = make(map[string]Service)
|
||||
for name, service := range input.Services {
|
||||
addr, err := multiaddr.NewMultiaddr(service.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Services[name] = addr
|
||||
whitelist := make(map[peer.ID]struct{})
|
||||
blacklist := make(map[peer.ID]struct{})
|
||||
for _, p := range service.Acl.Whitelist {
|
||||
cfgPeer, found := FindPeerByCLIRef(result.Peers, p)
|
||||
if !found {
|
||||
return nil, errors.New("unknown peer: " + p)
|
||||
}
|
||||
whitelist[cfgPeer.ID] = struct{}{}
|
||||
}
|
||||
for _, peerStr := range service.Acl.Blacklist {
|
||||
cfgPeer, found := FindPeerByCLIRef(result.Peers, peerStr)
|
||||
if !found {
|
||||
return nil, errors.New("unknown peer: " + peerStr)
|
||||
}
|
||||
blacklist[cfgPeer.ID] = struct{}{}
|
||||
}
|
||||
result.Services[name] = Service{
|
||||
Target: addr,
|
||||
EnableWhitelist: service.Acl.EnableWhitelist,
|
||||
Whitelist: whitelist,
|
||||
Blacklist: blacklist,
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite path of config to input.
|
||||
|
||||
@ -6,16 +6,18 @@
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "go-jsonschema";
|
||||
version = "0.16.0";
|
||||
version = "0.22.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "omissis";
|
||||
repo = "go-jsonschema";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-+CapTmg4RObK6mzjAS/EFbX4s2AtQvlFXmT119aUkZA=";
|
||||
hash = "sha256-ffrP4L5cfK75Tw/xfcdXAwGUP8WLL+81ltBDb/P5Gwo=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-gk+aKGqcHEjuYxc2o+83HA2AxU+jT7URt0N/q+uyUtA=";
|
||||
env.GOWORK = "off";
|
||||
|
||||
vendorHash = "sha256-mCOJ8GROrbNXH7CSLLMZj/4wTa65hscTt8RzIxzgG+A=";
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
|
||||
inherit (lib) types mkOption;
|
||||
inherit (lib) types mkOption mkEnableOption;
|
||||
|
||||
t = {
|
||||
multiAddr = types.strMatching "/.*[^/]" // {
|
||||
@ -42,6 +42,40 @@ let
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
service = types.submodule {
|
||||
options = {
|
||||
target = mkOption {
|
||||
type = t.multiAddr;
|
||||
description = "Target address.";
|
||||
example = "/tcp/8080";
|
||||
};
|
||||
|
||||
acl = {
|
||||
enableWhitelist = mkEnableOption "whitelist enforcement";
|
||||
|
||||
whitelist = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "List of peers that are allowed to connect.";
|
||||
example = [
|
||||
"12D3KooWQWiPeNvXFdHFTrustedPeer"
|
||||
"@goodpeer"
|
||||
];
|
||||
default = null;
|
||||
};
|
||||
|
||||
blacklist = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "List of peers that are explicitly not allowed to connect.";
|
||||
example = [
|
||||
"12D3KooWQWiPeNvXFdHFUntrustedPeer"
|
||||
"@badpeer"
|
||||
];
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
@ -82,12 +116,23 @@ in
|
||||
};
|
||||
|
||||
services = mkOption {
|
||||
type = types.attrsOf t.multiAddr;
|
||||
type = types.attrsOf t.service;
|
||||
description = "The services this node provides via the Service Network.";
|
||||
default = { };
|
||||
example = {
|
||||
"www-local" = "/tcp/8080";
|
||||
"gameserver" = "/ip4/10.0.0.2/tcp/27015";
|
||||
www-local = {
|
||||
target = "/tcp/8080";
|
||||
};
|
||||
gameserver = {
|
||||
target = "/ip4/10.0.0.2/tcp/27015";
|
||||
acl = {
|
||||
enableWhitelist = true;
|
||||
whitelist = [
|
||||
"@friend1"
|
||||
"@friend2"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -180,8 +180,8 @@ func (node *Node) Run() error {
|
||||
|
||||
serviceNet := svc.NewServiceNetwork(node.p2p, node.cfg, node.tunDev)
|
||||
|
||||
for name, addr := range node.cfg.Services {
|
||||
proxy, err := svc.ProxyTo(addr)
|
||||
for name, service := range node.cfg.Services {
|
||||
proxy, err := svc.ProxyTo(service.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -30,12 +30,14 @@ type ServiceNetwork struct {
|
||||
activeAddrs map[[16]byte]struct{}
|
||||
activePorts map[[16]byte]map[uint16]struct{}
|
||||
listeners map[[2]byte]Proxy
|
||||
services map[[2]byte]config.Service
|
||||
}
|
||||
|
||||
func (sn *ServiceNetwork) Register(serviceName string, proxy Proxy) {
|
||||
svcId := config.MkServiceID(serviceName)
|
||||
sn.listeners[svcId] = proxy
|
||||
logger.With(zap.String("name", serviceName), zap.ByteString("id", svcId[:]), zap.String("description", proxy.Description)).Debug("Registered service")
|
||||
sn.services[svcId] = sn.config.Services[serviceName]
|
||||
logger.With(zap.String("name", serviceName), zap.String("id", fmt.Sprintf("%x", svcId[:])), zap.String("description", proxy.Description)).Info("Registered service")
|
||||
}
|
||||
|
||||
func (sn *ServiceNetwork) EnsureListener(addr [16]byte, port uint16) bool {
|
||||
@ -131,6 +133,7 @@ func NewServiceNetwork(host host.Host, cfg *config.Config, tunDev *hstun.TUN) Se
|
||||
activeAddrs: make(map[[16]byte]struct{}),
|
||||
activePorts: make(map[[16]byte]map[uint16]struct{}),
|
||||
listeners: make(map[[2]byte]Proxy),
|
||||
services: make(map[[2]byte]config.Service),
|
||||
}
|
||||
|
||||
host.SetStreamHandler(Protocol, sn.streamHandler())
|
||||
|
||||
@ -71,6 +71,7 @@ type RemoteServiceProxyStatus byte
|
||||
const (
|
||||
RS_OK RemoteServiceProxyStatus = 0xf1
|
||||
RS_NOT_SUPPORTED RemoteServiceProxyStatus = 0xf2
|
||||
RS_NOT_AUTHORIZED RemoteServiceProxyStatus = 0xf3
|
||||
)
|
||||
|
||||
func RemoteServiceProxy(host host.Host, p peer.ID, svcId [2]byte) Proxy {
|
||||
@ -95,7 +96,7 @@ func RemoteServiceProxy(host host.Host, p peer.ID, svcId [2]byte) Proxy {
|
||||
logger.With(err).Error("Failed to read from stream")
|
||||
return
|
||||
} else if buf[0] != byte(RS_OK) {
|
||||
logger.With(zap.String("peer", p.String()), zap.ByteString("service", svcId[:])).Warn("Peer does not support service")
|
||||
logger.With(zap.String("peer", p.String()), zap.String("service", fmt.Sprintf("%x", svcId[:]))).Warn("Peer does not support service")
|
||||
return
|
||||
}
|
||||
pipe(conn, stream)
|
||||
|
||||
@ -5,8 +5,17 @@ import (
|
||||
|
||||
"github.com/hyprspace/hyprspace/config"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (sn *ServiceNetwork) isRemoteBlocked(svcId [2]byte, remotePeer peer.ID) bool {
|
||||
sv := sn.services[svcId]
|
||||
_, isWhitelisted := sv.Whitelist[remotePeer]
|
||||
_, isBlacklisted := sv.Blacklist[remotePeer]
|
||||
return isBlacklisted || (sv.EnableWhitelist && !isWhitelisted)
|
||||
}
|
||||
|
||||
func (sn *ServiceNetwork) streamHandler() func(network.Stream) {
|
||||
return func(stream network.Stream) {
|
||||
if _, ok := config.FindPeer(sn.config.Peers, stream.Conn().RemotePeer()); !ok {
|
||||
@ -23,6 +32,17 @@ func (sn *ServiceNetwork) streamHandler() func(network.Stream) {
|
||||
}
|
||||
svcId := [2]byte(buf)
|
||||
if proxy, ok := sn.listeners[svcId]; ok {
|
||||
remotePeer := stream.Conn().RemotePeer()
|
||||
if sn.isRemoteBlocked(svcId, remotePeer) {
|
||||
logger.With(zap.String("service ID", fmt.Sprintf("%x", svcId[:]))).Debug("Connection from non-allowed peer")
|
||||
_, err := stream.Write([]byte{byte(RS_NOT_AUTHORIZED)})
|
||||
if err != nil {
|
||||
logger.With(err).Error("Failed to send RS_NOT_AUTHORIZED")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_, err := stream.Write([]byte{byte(RS_OK)})
|
||||
if err != nil {
|
||||
logger.With(err).Error("Failed to write stream")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user