2019-11-16 00:20:44 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package trust
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
2019-12-27 11:48:47 +00:00
|
|
|
"storj.io/common/storj"
|
2019-11-16 00:20:44 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrExclusion is an error class for exclusion related errors
|
|
|
|
ErrExclusion = errs.Class("exclusion")
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewExcluder takes a configuration string and returns an excluding Rule.
|
|
|
|
// Accepted forms are 1) a Satellite ID followed by '@', 2) a hostname or IP
|
|
|
|
// address, 3) a full Satellite URL.
|
|
|
|
func NewExcluder(config string) (Rule, error) {
|
|
|
|
url, err := parseExcluderConfig(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case url.Host == "":
|
|
|
|
return NewIDExcluder(url.ID), nil
|
|
|
|
case url.ID.IsZero():
|
|
|
|
return NewHostExcluder(url.Host), nil
|
|
|
|
default:
|
|
|
|
return NewURLExcluder(url), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// URLExcluder excludes matching URLs
|
|
|
|
type URLExcluder struct {
|
|
|
|
url SatelliteURL
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewURLExcluder returns a new URLExcluder
|
|
|
|
func NewURLExcluder(url SatelliteURL) *URLExcluder {
|
|
|
|
url.Host = normalizeHost(url.Host)
|
|
|
|
return &URLExcluder{
|
|
|
|
url: url,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsTrusted returns true if the given Satellite is trusted and false otherwise
|
|
|
|
func (excluder *URLExcluder) IsTrusted(url SatelliteURL) bool {
|
|
|
|
url.Host = normalizeHost(url.Host)
|
|
|
|
return excluder.url != url
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a string representation of the excluder
|
|
|
|
func (excluder *URLExcluder) String() string {
|
|
|
|
return excluder.url.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// IDExcluder excludes URLs matching a given URL
|
|
|
|
type IDExcluder struct {
|
|
|
|
id storj.NodeID
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIDExcluder returns a new IDExcluder
|
|
|
|
func NewIDExcluder(id storj.NodeID) *IDExcluder {
|
|
|
|
return &IDExcluder{
|
|
|
|
id: id,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsTrusted returns true if the given Satellite is trusted and false otherwise
|
|
|
|
func (excluder *IDExcluder) IsTrusted(url SatelliteURL) bool {
|
|
|
|
return excluder.id != url.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a string representation of the excluder
|
|
|
|
func (excluder *IDExcluder) String() string {
|
|
|
|
return excluder.id.String() + "@"
|
|
|
|
}
|
|
|
|
|
|
|
|
// HostExcluder excludes URLs that match a given host. If the host is a domain
|
|
|
|
// name then URLs in a subdomain of that domain are excluded as well.
|
|
|
|
type HostExcluder struct {
|
|
|
|
host string
|
|
|
|
suffix string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewHostExcluder returns a new HostExcluder
|
|
|
|
func NewHostExcluder(host string) *HostExcluder {
|
|
|
|
host = normalizeHost(host)
|
|
|
|
|
|
|
|
// If it appears to be a domain name (i.e. has a dot) then configure the
|
|
|
|
// suffix as well
|
|
|
|
var suffix string
|
|
|
|
if strings.ContainsRune(host, '.') {
|
|
|
|
suffix = "." + host
|
|
|
|
}
|
|
|
|
return &HostExcluder{
|
|
|
|
host: host,
|
|
|
|
suffix: suffix,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsTrusted returns true if the given Satellite is trusted and false otherwise
|
|
|
|
func (excluder *HostExcluder) IsTrusted(url SatelliteURL) bool {
|
|
|
|
host := normalizeHost(url.Host)
|
|
|
|
if excluder.host == host {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if excluder.suffix != "" && strings.HasSuffix(host, excluder.suffix) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a string representation of the excluder
|
|
|
|
func (excluder *HostExcluder) String() string {
|
|
|
|
return excluder.host
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseExcluderConfig parses a excluder configuration. The following forms are accepted:
|
|
|
|
// - Satellite ID followed by @
|
|
|
|
// - Satellite host
|
|
|
|
// - Full Satellite URL (i.e. id@host:port)
|
|
|
|
func parseExcluderConfig(s string) (SatelliteURL, error) {
|
|
|
|
url, err := storj.ParseNodeURL(s)
|
|
|
|
if err != nil {
|
|
|
|
return SatelliteURL{}, ErrExclusion.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case url.ID.IsZero() && url.Address != "":
|
|
|
|
// Just the address was specified. Ensure it does not have a port.
|
|
|
|
_, _, err := net.SplitHostPort(url.Address)
|
|
|
|
if err == nil {
|
|
|
|
return SatelliteURL{}, ErrExclusion.New("host exclusion must not include a port")
|
|
|
|
}
|
|
|
|
return SatelliteURL{
|
|
|
|
Host: url.Address,
|
|
|
|
}, nil
|
|
|
|
case !url.ID.IsZero() && url.Address == "":
|
|
|
|
// Just the ID was specified.
|
|
|
|
return SatelliteURL{
|
|
|
|
ID: url.ID,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// storj.ParseNodeURL will have already verified that the address is
|
|
|
|
// well-formed, so if SplitHostPort fails it should be due to the address
|
|
|
|
// not having a port.
|
|
|
|
host, portStr, err := net.SplitHostPort(url.Address)
|
|
|
|
if err != nil {
|
|
|
|
return SatelliteURL{}, ErrExclusion.New("satellite URL exclusion must specify a port")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Port should already be numeric so this shouldn't fail, but just in case.
|
|
|
|
port, err := strconv.Atoi(portStr)
|
|
|
|
if err != nil {
|
|
|
|
return SatelliteURL{}, ErrExclusion.New("satellite URL exclusion port is not numeric")
|
|
|
|
}
|
|
|
|
|
|
|
|
return SatelliteURL{
|
|
|
|
ID: url.ID,
|
|
|
|
Host: host,
|
|
|
|
Port: port,
|
|
|
|
}, nil
|
|
|
|
}
|