1// Copyright (C) 2019 Storj Labs, Inc. 2// See LICENSE for copying information. 3 4package trust 5 6import ( 7 "net" 8 "strconv" 9 "strings" 10 11 "github.com/zeebo/errs" 12 13 "storj.io/common/storj" 14) 15 16var ( 17 // ErrExclusion is an error class for exclusion related errors. 18 ErrExclusion = errs.Class("exclusion") 19) 20 21// NewExcluder takes a configuration string and returns an excluding Rule. 22// Accepted forms are 1) a Satellite ID followed by '@', 2) a hostname or IP 23// address, 3) a full Satellite URL. 24func NewExcluder(config string) (Rule, error) { 25 url, err := parseExcluderConfig(config) 26 if err != nil { 27 return nil, err 28 } 29 30 switch { 31 case url.Host == "": 32 return NewIDExcluder(url.ID), nil 33 case url.ID.IsZero(): 34 return NewHostExcluder(url.Host), nil 35 default: 36 return NewURLExcluder(url), nil 37 } 38} 39 40// URLExcluder excludes matching URLs. 41type URLExcluder struct { 42 url SatelliteURL 43} 44 45// NewURLExcluder returns a new URLExcluder. 46func NewURLExcluder(url SatelliteURL) *URLExcluder { 47 url.Host = normalizeHost(url.Host) 48 return &URLExcluder{ 49 url: url, 50 } 51} 52 53// IsTrusted returns true if the given Satellite is trusted and false otherwise. 54func (excluder *URLExcluder) IsTrusted(url SatelliteURL) bool { 55 url.Host = normalizeHost(url.Host) 56 return excluder.url != url 57} 58 59// String returns a string representation of the excluder. 60func (excluder *URLExcluder) String() string { 61 return excluder.url.String() 62} 63 64// IDExcluder excludes URLs matching a given URL. 65type IDExcluder struct { 66 id storj.NodeID 67} 68 69// NewIDExcluder returns a new IDExcluder. 70func NewIDExcluder(id storj.NodeID) *IDExcluder { 71 return &IDExcluder{ 72 id: id, 73 } 74} 75 76// IsTrusted returns true if the given Satellite is trusted and false otherwise. 77func (excluder *IDExcluder) IsTrusted(url SatelliteURL) bool { 78 return excluder.id != url.ID 79} 80 81// String returns a string representation of the excluder. 82func (excluder *IDExcluder) String() string { 83 return excluder.id.String() + "@" 84} 85 86// HostExcluder excludes URLs that match a given host. If the host is a domain 87// name then URLs in a subdomain of that domain are excluded as well. 88type HostExcluder struct { 89 host string 90 suffix string 91} 92 93// NewHostExcluder returns a new HostExcluder. 94func NewHostExcluder(host string) *HostExcluder { 95 host = normalizeHost(host) 96 97 // If it appears to be a domain name (i.e. has a dot) then configure the 98 // suffix as well 99 var suffix string 100 if strings.ContainsRune(host, '.') { 101 suffix = "." + host 102 } 103 return &HostExcluder{ 104 host: host, 105 suffix: suffix, 106 } 107} 108 109// IsTrusted returns true if the given Satellite is trusted and false otherwise. 110func (excluder *HostExcluder) IsTrusted(url SatelliteURL) bool { 111 host := normalizeHost(url.Host) 112 if excluder.host == host { 113 return false 114 } 115 116 if excluder.suffix != "" && strings.HasSuffix(host, excluder.suffix) { 117 return false 118 } 119 return true 120} 121 122// String returns a string representation of the excluder. 123func (excluder *HostExcluder) String() string { 124 return excluder.host 125} 126 127// parseExcluderConfig parses a excluder configuration. The following forms are accepted: 128// - Satellite ID followed by @ 129// - Satellite host 130// - Full Satellite URL (i.e. id@host:port). 131func parseExcluderConfig(s string) (SatelliteURL, error) { 132 url, err := storj.ParseNodeURL(s) 133 if err != nil { 134 return SatelliteURL{}, ErrExclusion.Wrap(err) 135 } 136 137 switch { 138 case url.ID.IsZero() && url.Address != "": 139 // Just the address was specified. Ensure it does not have a port. 140 _, _, err := net.SplitHostPort(url.Address) 141 if err == nil { 142 return SatelliteURL{}, ErrExclusion.New("host exclusion must not include a port") 143 } 144 return SatelliteURL{ 145 Host: url.Address, 146 }, nil 147 case !url.ID.IsZero() && url.Address == "": 148 // Just the ID was specified. 149 return SatelliteURL{ 150 ID: url.ID, 151 }, nil 152 } 153 154 // storj.ParseNodeURL will have already verified that the address is 155 // well-formed, so if SplitHostPort fails it should be due to the address 156 // not having a port. 157 host, portStr, err := net.SplitHostPort(url.Address) 158 if err != nil { 159 return SatelliteURL{}, ErrExclusion.New("satellite URL exclusion must specify a port") 160 } 161 162 // Port should already be numeric so this shouldn't fail, but just in case. 163 port, err := strconv.Atoi(portStr) 164 if err != nil { 165 return SatelliteURL{}, ErrExclusion.New("satellite URL exclusion port is not numeric") 166 } 167 168 return SatelliteURL{ 169 ID: url.ID, 170 Host: host, 171 Port: port, 172 }, nil 173} 174