whitelist.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // Package whitelist implements IP whitelisting for various types
  2. // of connections. Two types of access control lists (ACLs) are
  3. // supported: host-based and network-based.
  4. package whitelist
  5. import (
  6. "errors"
  7. "log"
  8. "net"
  9. "sort"
  10. "strings"
  11. "sync"
  12. )
  13. // An ACL stores a list of permitted IP addresses, and handles
  14. // concurrency as needed.
  15. type ACL interface {
  16. // Permitted takes an IP address, and returns true if the
  17. // IP address is whitelisted (e.g. permitted access).
  18. Permitted(net.IP) bool
  19. }
  20. // A HostACL stores a list of permitted hosts.
  21. type HostACL interface {
  22. ACL
  23. // Add takes an IP address and adds it to the whitelist so
  24. // that it is now permitted.
  25. Add(net.IP)
  26. // Remove takes an IP address and drops it from the whitelist
  27. // so that it is no longer permitted.
  28. Remove(net.IP)
  29. }
  30. // validIP takes an IP address (which is implemented as a byte slice)
  31. // and ensures that it is a possible address. Right now, this means
  32. // just doing length checks.
  33. func validIP(ip net.IP) bool {
  34. if len(ip) == 4 {
  35. return true
  36. }
  37. if len(ip) == 16 {
  38. return true
  39. }
  40. return false
  41. }
  42. // Basic implements a basic map-backed whitelister that uses an
  43. // RWMutex for conccurency. IPv4 addresses are treated differently
  44. // than an IPv6 address; namely, the IPv4 localhost will not match
  45. // the IPv6 localhost.
  46. type Basic struct {
  47. lock *sync.Mutex
  48. whitelist map[string]bool
  49. }
  50. // Permitted returns true if the IP has been whitelisted.
  51. func (wl *Basic) Permitted(ip net.IP) bool {
  52. if !validIP(ip) {
  53. return false
  54. }
  55. wl.lock.Lock()
  56. permitted := wl.whitelist[ip.String()]
  57. wl.lock.Unlock()
  58. return permitted
  59. }
  60. // Add whitelists an IP.
  61. func (wl *Basic) Add(ip net.IP) {
  62. if !validIP(ip) {
  63. return
  64. }
  65. wl.lock.Lock()
  66. defer wl.lock.Unlock()
  67. wl.whitelist[ip.String()] = true
  68. }
  69. // Remove clears the IP from the whitelist.
  70. func (wl *Basic) Remove(ip net.IP) {
  71. if !validIP(ip) {
  72. return
  73. }
  74. wl.lock.Lock()
  75. defer wl.lock.Unlock()
  76. delete(wl.whitelist, ip.String())
  77. }
  78. // NewBasic returns a new initialised basic whitelist.
  79. func NewBasic() *Basic {
  80. return &Basic{
  81. lock: new(sync.Mutex),
  82. whitelist: map[string]bool{},
  83. }
  84. }
  85. // MarshalJSON serialises a host whitelist to a comma-separated list of
  86. // hosts, implementing the json.Marshaler interface.
  87. func (wl *Basic) MarshalJSON() ([]byte, error) {
  88. wl.lock.Lock()
  89. defer wl.lock.Unlock()
  90. var ss = make([]string, 0, len(wl.whitelist))
  91. for ip := range wl.whitelist {
  92. ss = append(ss, ip)
  93. }
  94. out := []byte(`"` + strings.Join(ss, ",") + `"`)
  95. return out, nil
  96. }
  97. // UnmarshalJSON implements the json.Unmarshaler interface for host
  98. // whitelists, taking a comma-separated string of hosts.
  99. func (wl *Basic) UnmarshalJSON(in []byte) error {
  100. if in[0] != '"' || in[len(in)-1] != '"' {
  101. return errors.New("whitelist: invalid whitelist")
  102. }
  103. if wl.lock == nil {
  104. wl.lock = new(sync.Mutex)
  105. }
  106. wl.lock.Lock()
  107. defer wl.lock.Unlock()
  108. netString := strings.TrimSpace(string(in[1 : len(in)-1]))
  109. nets := strings.Split(netString, ",")
  110. wl.whitelist = map[string]bool{}
  111. for i := range nets {
  112. addr := strings.TrimSpace(nets[i])
  113. if addr == "" {
  114. continue
  115. }
  116. ip := net.ParseIP(addr)
  117. if ip == nil {
  118. wl.whitelist = nil
  119. return errors.New("whitelist: invalid IP address " + addr)
  120. }
  121. wl.whitelist[addr] = true
  122. }
  123. return nil
  124. }
  125. // DumpBasic returns a whitelist as a byte slice where each IP is on
  126. // its own line.
  127. func DumpBasic(wl *Basic) []byte {
  128. wl.lock.Lock()
  129. defer wl.lock.Unlock()
  130. var addrs = make([]string, 0, len(wl.whitelist))
  131. for ip := range wl.whitelist {
  132. addrs = append(addrs, ip)
  133. }
  134. sort.Strings(addrs)
  135. addrList := strings.Join(addrs, "\n")
  136. return []byte(addrList)
  137. }
  138. // LoadBasic loads a whitelist from a byteslice.
  139. func LoadBasic(in []byte) (*Basic, error) {
  140. wl := NewBasic()
  141. addrs := strings.Split(string(in), "\n")
  142. for _, addr := range addrs {
  143. ip := net.ParseIP(addr)
  144. if ip == nil {
  145. return nil, errors.New("whitelist: invalid address")
  146. }
  147. wl.Add(ip)
  148. }
  149. return wl, nil
  150. }
  151. // HostStub allows host whitelisting to be added into a system's flow
  152. // without doing anything yet. All operations result in warning log
  153. // messages being printed to stderr. There is no mechanism for
  154. // squelching these messages short of modifying the log package's
  155. // default logger.
  156. type HostStub struct{}
  157. // Permitted always returns true, but prints a warning message alerting
  158. // that whitelisting is stubbed.
  159. func (wl HostStub) Permitted(ip net.IP) bool {
  160. log.Printf("WARNING: whitelist check for %s but whitelisting is stubbed", ip)
  161. return true
  162. }
  163. // Add prints a warning message about whitelisting being stubbed.
  164. func (wl HostStub) Add(ip net.IP) {
  165. log.Printf("WARNING: IP %s added to whitelist but whitelisting is stubbed", ip)
  166. }
  167. // Remove prints a warning message about whitelisting being stubbed.
  168. func (wl HostStub) Remove(ip net.IP) {
  169. log.Printf("WARNING: IP %s removed from whitelist but whitelisting is stubbed", ip)
  170. }
  171. // NewHostStub returns a new stubbed host whitelister.
  172. func NewHostStub() HostStub {
  173. log.Println("WARNING: whitelisting is being stubbed")
  174. return HostStub{}
  175. }