satellite/contact: reject privateIPs in PingMe and CheckIn endpoints
prevent network enumeration by rejecting privateIPs in PingMe and Checkin endpoints Closes storj/storj-private#32 Change-Id: I63f00483ff4128ebd5fa9b7b8da826a5706748c9
This commit is contained in:
parent
f0b28d6326
commit
911cc1e163
@ -5,6 +5,7 @@ package contact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
@ -68,11 +69,15 @@ func (endpoint *Endpoint) CheckIn(ctx context.Context, req *pb.CheckInRequest) (
|
|||||||
return nil, rpcstatus.Error(rpcstatus.FailedPrecondition, errCheckInIdentity.New("failed to add peer identity entry for ID: %v", err).Error())
|
return nil, rpcstatus.Error(rpcstatus.FailedPrecondition, errCheckInIdentity.New("failed to add peer identity entry for ID: %v", err).Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedIPPort, resolvedNetwork, err := overlay.ResolveIPAndNetwork(ctx, req.Address)
|
resolvedIP, port, resolvedNetwork, err := overlay.ResolveIPAndNetwork(ctx, req.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
endpoint.log.Info("failed to resolve IP from address", zap.String("node address", req.Address), zap.Stringer("Node ID", nodeID), zap.Error(err))
|
endpoint.log.Info("failed to resolve IP from address", zap.String("node address", req.Address), zap.Stringer("Node ID", nodeID), zap.Error(err))
|
||||||
return nil, rpcstatus.Error(rpcstatus.InvalidArgument, errCheckInNetwork.New("failed to resolve IP from address: %s, err: %v", req.Address, err).Error())
|
return nil, rpcstatus.Error(rpcstatus.InvalidArgument, errCheckInNetwork.New("failed to resolve IP from address: %s, err: %v", req.Address, err).Error())
|
||||||
}
|
}
|
||||||
|
if !endpoint.service.allowPrivateIP && (!resolvedIP.IsGlobalUnicast() || resolvedIP.IsPrivate()) {
|
||||||
|
endpoint.log.Info("IP address not allowed", zap.String("node address", req.Address), zap.Stringer("Node ID", nodeID))
|
||||||
|
return nil, rpcstatus.Error(rpcstatus.InvalidArgument, errCheckInNetwork.New("IP address not allowed: %s", req.Address).Error())
|
||||||
|
}
|
||||||
|
|
||||||
nodeurl := storj.NodeURL{
|
nodeurl := storj.NodeURL{
|
||||||
ID: nodeID,
|
ID: nodeID,
|
||||||
@ -102,7 +107,7 @@ func (endpoint *Endpoint) CheckIn(ctx context.Context, req *pb.CheckInRequest) (
|
|||||||
Transport: pb.NodeTransport_TCP_TLS_GRPC,
|
Transport: pb.NodeTransport_TCP_TLS_GRPC,
|
||||||
},
|
},
|
||||||
LastNet: resolvedNetwork,
|
LastNet: resolvedNetwork,
|
||||||
LastIPPort: resolvedIPPort,
|
LastIPPort: net.JoinHostPort(resolvedIP.String(), port),
|
||||||
IsUp: pingNodeSuccess,
|
IsUp: pingNodeSuccess,
|
||||||
Capacity: req.Capacity,
|
Capacity: req.Capacity,
|
||||||
Operator: req.Operator,
|
Operator: req.Operator,
|
||||||
@ -157,6 +162,16 @@ func (endpoint *Endpoint) PingMe(ctx context.Context, req *pb.PingMeRequest) (_
|
|||||||
Address: req.Address,
|
Address: req.Address,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolvedIP, _, _, err := overlay.ResolveIPAndNetwork(ctx, req.Address)
|
||||||
|
if err != nil {
|
||||||
|
endpoint.log.Info("failed to resolve IP from address", zap.String("node address", req.Address), zap.Stringer("Node ID", nodeID), zap.Error(err))
|
||||||
|
return nil, rpcstatus.Error(rpcstatus.InvalidArgument, errCheckInNetwork.New("failed to resolve IP from address: %s, err: %v", req.Address, err).Error())
|
||||||
|
}
|
||||||
|
if !endpoint.service.allowPrivateIP && (!resolvedIP.IsGlobalUnicast() || resolvedIP.IsPrivate()) {
|
||||||
|
endpoint.log.Info("IP address not allowed", zap.String("node address", req.Address), zap.Stringer("Node ID", nodeID))
|
||||||
|
return nil, rpcstatus.Error(rpcstatus.InvalidArgument, errCheckInNetwork.New("IP address not allowed: %s", req.Address).Error())
|
||||||
|
}
|
||||||
|
|
||||||
if endpoint.service.timeout > 0 {
|
if endpoint.service.timeout > 0 {
|
||||||
var cancel func()
|
var cancel func()
|
||||||
ctx, cancel = context.WithTimeout(ctx, endpoint.service.timeout)
|
ctx, cancel = context.WithTimeout(ctx, endpoint.service.timeout)
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
ExternalAddress string `user:"true" help:"the public address of the node, useful for nodes behind NAT" default:""`
|
ExternalAddress string `user:"true" help:"the public address of the node, useful for nodes behind NAT" default:""`
|
||||||
Timeout time.Duration `help:"timeout for pinging storage nodes" default:"10m0s" testDefault:"1m"`
|
Timeout time.Duration `help:"timeout for pinging storage nodes" default:"10m0s" testDefault:"1m"`
|
||||||
|
AllowPrivateIP bool `help:"allow private IPs in CheckIn and PingMe" testDefault:"true" devDefault:"true" default:"false"`
|
||||||
|
|
||||||
RateLimitInterval time.Duration `help:"the amount of time that should happen between contact attempts usually" releaseDefault:"10m0s" devDefault:"1ns"`
|
RateLimitInterval time.Duration `help:"the amount of time that should happen between contact attempts usually" releaseDefault:"10m0s" devDefault:"1ns"`
|
||||||
RateLimitBurst int `help:"the maximum burst size for the contact rate limit token bucket" releaseDefault:"2" devDefault:"1000"`
|
RateLimitBurst int `help:"the maximum burst size for the contact rate limit token bucket" releaseDefault:"2" devDefault:"1000"`
|
||||||
@ -45,20 +46,22 @@ type Service struct {
|
|||||||
peerIDs overlay.PeerIdentities
|
peerIDs overlay.PeerIdentities
|
||||||
dialer rpc.Dialer
|
dialer rpc.Dialer
|
||||||
|
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
idLimiter *RateLimiter
|
idLimiter *RateLimiter
|
||||||
|
allowPrivateIP bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new contact service.
|
// NewService creates a new contact service.
|
||||||
func NewService(log *zap.Logger, self *overlay.NodeDossier, overlay *overlay.Service, peerIDs overlay.PeerIdentities, dialer rpc.Dialer, config Config) *Service {
|
func NewService(log *zap.Logger, self *overlay.NodeDossier, overlay *overlay.Service, peerIDs overlay.PeerIdentities, dialer rpc.Dialer, config Config) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
log: log,
|
log: log,
|
||||||
self: self,
|
self: self,
|
||||||
overlay: overlay,
|
overlay: overlay,
|
||||||
peerIDs: peerIDs,
|
peerIDs: peerIDs,
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
timeout: config.Timeout,
|
timeout: config.Timeout,
|
||||||
idLimiter: NewRateLimiter(config.RateLimitInterval, config.RateLimitBurst, config.RateLimitCacheSize),
|
idLimiter: NewRateLimiter(config.RateLimitInterval, config.RateLimitBurst, config.RateLimitCacheSize),
|
||||||
|
allowPrivateIP: config.AllowPrivateIP,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,15 +721,15 @@ func TestAddrtoNetwork_Conversion(t *testing.T) {
|
|||||||
defer ctx.Cleanup()
|
defer ctx.Cleanup()
|
||||||
|
|
||||||
ip := "8.8.8.8:28967"
|
ip := "8.8.8.8:28967"
|
||||||
resolvedIPPort, network, err := overlay.ResolveIPAndNetwork(ctx, ip)
|
resolvedIP, port, network, err := overlay.ResolveIPAndNetwork(ctx, ip)
|
||||||
require.Equal(t, "8.8.8.0", network)
|
require.Equal(t, "8.8.8.0", network)
|
||||||
require.Equal(t, ip, resolvedIPPort)
|
require.Equal(t, ip, net.JoinHostPort(resolvedIP.String(), port))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ipv6 := "[fc00::1:200]:28967"
|
ipv6 := "[fc00::1:200]:28967"
|
||||||
resolvedIPPort, network, err = overlay.ResolveIPAndNetwork(ctx, ipv6)
|
resolvedIP, port, network, err = overlay.ResolveIPAndNetwork(ctx, ipv6)
|
||||||
require.Equal(t, "fc00::", network)
|
require.Equal(t, "fc00::", network)
|
||||||
require.Equal(t, ipv6, resolvedIPPort)
|
require.Equal(t, ipv6, net.JoinHostPort(resolvedIP.String(), port))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,31 +648,31 @@ func (service *Service) DisqualifyNode(ctx context.Context, nodeID storj.NodeID,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPAndNetwork resolves the target address and determines its IP and /24 subnet IPv4 or /64 subnet IPv6.
|
// ResolveIPAndNetwork resolves the target address and determines its IP and /24 subnet IPv4 or /64 subnet IPv6.
|
||||||
func ResolveIPAndNetwork(ctx context.Context, target string) (ipPort, network string, err error) {
|
func ResolveIPAndNetwork(ctx context.Context, target string) (ip net.IP, port, network string, err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(target)
|
host, port, err := net.SplitHostPort(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, "", "", err
|
||||||
}
|
}
|
||||||
ipAddr, err := net.ResolveIPAddr("ip", host)
|
ipAddr, err := net.ResolveIPAddr("ip", host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If addr can be converted to 4byte notation, it is an IPv4 address, else its an IPv6 address
|
// If addr can be converted to 4byte notation, it is an IPv4 address, else its an IPv6 address
|
||||||
if ipv4 := ipAddr.IP.To4(); ipv4 != nil {
|
if ipv4 := ipAddr.IP.To4(); ipv4 != nil {
|
||||||
// Filter all IPv4 Addresses into /24 Subnet's
|
// Filter all IPv4 Addresses into /24 Subnet's
|
||||||
mask := net.CIDRMask(24, 32)
|
mask := net.CIDRMask(24, 32)
|
||||||
return net.JoinHostPort(ipAddr.String(), port), ipv4.Mask(mask).String(), nil
|
return ipAddr.IP, port, ipv4.Mask(mask).String(), nil
|
||||||
}
|
}
|
||||||
if ipv6 := ipAddr.IP.To16(); ipv6 != nil {
|
if ipv6 := ipAddr.IP.To16(); ipv6 != nil {
|
||||||
// Filter all IPv6 Addresses into /64 Subnet's
|
// Filter all IPv6 Addresses into /64 Subnet's
|
||||||
mask := net.CIDRMask(64, 128)
|
mask := net.CIDRMask(64, 128)
|
||||||
return net.JoinHostPort(ipAddr.String(), port), ipv6.Mask(mask).String(), nil
|
return ipAddr.IP, port, ipv6.Mask(mask).String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", "", errors.New("unable to get network for address " + ipAddr.String())
|
return nil, "", "", errors.New("unable to get network for address " + ipAddr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestVetNode directly sets a node's vetted_at timestamp to make testing easier.
|
// TestVetNode directly sets a node's vetted_at timestamp to make testing easier.
|
||||||
|
3
scripts/testdata/satellite-config.yaml.lock
vendored
3
scripts/testdata/satellite-config.yaml.lock
vendored
@ -277,6 +277,9 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
|||||||
# whether to load templates on each request
|
# whether to load templates on each request
|
||||||
# console.watch: false
|
# console.watch: false
|
||||||
|
|
||||||
|
# allow private IPs in CheckIn and PingMe
|
||||||
|
# contact.allow-private-ip: false
|
||||||
|
|
||||||
# the public address of the node, useful for nodes behind NAT
|
# the public address of the node, useful for nodes behind NAT
|
||||||
contact.external-address: ""
|
contact.external-address: ""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user