allowed ips plugin (#1510)

This commit is contained in:
Ian Bashford 2020-11-15 19:59:58 +00:00 committed by GitHub
parent 6b6c6753aa
commit 90a9a9d992
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 205 additions and 0 deletions

View File

@ -67,6 +67,7 @@ type Config struct {
AllowedName AllowedNameConfig `toml:"allowed_names"`
BlockIP BlockIPConfig `toml:"blocked_ips"`
BlockIPLegacy BlockIPConfigLegacy `toml:"ip_blacklist"`
AllowIP AllowIPConfig `toml:"allowed_ips"`
ForwardFile string `toml:"forwarding_rules"`
CloakFile string `toml:"cloaking_rules"`
CaptivePortalFile string `toml:"captive_portal_handler"`
@ -213,6 +214,12 @@ type BlockIPConfigLegacy struct {
Format string `toml:"log_format"`
}
type AllowIPConfig struct {
File string `toml:"allowed_ips_file"`
LogFile string `toml:"log_file"`
Format string `toml:"log_format"`
}
type AnonymizedDNSRouteConfig struct {
ServerName string `toml:"server_name"`
RelayNames []string `toml:"via"`
@ -558,6 +565,18 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
proxy.blockIPFormat = config.BlockIP.Format
proxy.blockIPLogFile = config.BlockIP.LogFile
if len(config.AllowIP.Format) == 0 {
config.AllowIP.Format = "tsv"
} else {
config.AllowIP.Format = strings.ToLower(config.AllowIP.Format)
}
if config.AllowIP.Format != "tsv" && config.AllowIP.Format != "ltsv" {
return errors.New("Unsupported allowed_ips log format")
}
proxy.allowedIPFile = config.AllowIP.File
proxy.allowedIPFormat = config.AllowIP.Format
proxy.allowedIPLogFile = config.AllowIP.LogFile
proxy.forwardFile = config.ForwardFile
proxy.cloakFile = config.CloakFile
proxy.captivePortalFile = config.CaptivePortalFile

View File

@ -0,0 +1,7 @@
##############################
# Allowed IPs List #
##############################
#192.168.0.*
#fe80:53:* # IPv6 prefix example
#81.169.145.105

View File

@ -545,6 +545,33 @@ cache_neg_max_ttl = 600
#########################################################
# Pattern-based allowed IPs lists (blocklists bypass) #
#########################################################
## Allowed IP lists support the same patterns as IP blocklists
## If an IP response matches an allow ip entry, the corresponding session
## will bypass IP filters.
##
## Time-based rules are also supported to make some websites only accessible at specific times of the day.
[allowed_ips]
## Path to the file of allowed ip rules (absolute, or relative to the same directory as the config file)
# allowed_ips_file = 'allowed-ips.txt'
## Optional path to a file logging allowed queries
# log_file = 'allowed-ips.log'
## Optional log format: tsv or ltsv (default: tsv)
# log_format = 'tsv'
##########################################
# Time access restrictions #
##########################################

View File

@ -0,0 +1,146 @@
package main
import (
"errors"
"fmt"
"io"
"net"
"strings"
"time"
iradix "github.com/hashicorp/go-immutable-radix"
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
)
type PluginAllowedIP struct {
allowedPrefixes *iradix.Tree
allowedIPs map[string]interface{}
logger io.Writer
format string
}
func (plugin *PluginAllowedIP) Name() string {
return "allow_ip"
}
func (plugin *PluginAllowedIP) Description() string {
return "Allows DNS queries containing specific IP addresses"
}
func (plugin *PluginAllowedIP) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of allowed IP rules from [%s]", proxy.allowedIPFile)
bin, err := ReadTextFile(proxy.allowedIPFile)
if err != nil {
return err
}
plugin.allowedPrefixes = iradix.New()
plugin.allowedIPs = make(map[string]interface{})
for lineNo, line := range strings.Split(string(bin), "\n") {
line = TrimAndStripInlineComments(line)
if len(line) == 0 {
continue
}
ip := net.ParseIP(line)
trailingStar := strings.HasSuffix(line, "*")
if len(line) < 2 || (ip != nil && trailingStar) {
dlog.Errorf("Suspicious allowed IP rule [%s] at line %d", line, lineNo)
continue
}
if trailingStar {
line = line[:len(line)-1]
}
if strings.HasSuffix(line, ":") || strings.HasSuffix(line, ".") {
line = line[:len(line)-1]
}
if len(line) == 0 {
dlog.Errorf("Empty allowed IP rule at line %d", lineNo)
continue
}
if strings.Contains(line, "*") {
dlog.Errorf("Invalid rule: [%s] - wildcards can only be used as a suffix at line %d", line, lineNo)
continue
}
line = strings.ToLower(line)
if trailingStar {
plugin.allowedPrefixes, _, _ = plugin.allowedPrefixes.Insert([]byte(line), 0)
} else {
plugin.allowedIPs[line] = true
}
}
if len(proxy.allowedIPLogFile) == 0 {
return nil
}
plugin.logger = Logger(proxy.logMaxSize, proxy.logMaxAge, proxy.logMaxBackups, proxy.allowedIPLogFile)
plugin.format = proxy.allowedIPFormat
return nil
}
func (plugin *PluginAllowedIP) Drop() error {
return nil
}
func (plugin *PluginAllowedIP) Reload() error {
return nil
}
func (plugin *PluginAllowedIP) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
answers := msg.Answer
if len(answers) == 0 {
return nil
}
allowed, reason, ipStr := false, "", ""
for _, answer := range answers {
header := answer.Header()
Rrtype := header.Rrtype
if header.Class != dns.ClassINET || (Rrtype != dns.TypeA && Rrtype != dns.TypeAAAA) {
continue
}
if Rrtype == dns.TypeA {
ipStr = answer.(*dns.A).A.String()
} else if Rrtype == dns.TypeAAAA {
ipStr = answer.(*dns.AAAA).AAAA.String() // IPv4-mapped IPv6 addresses are converted to IPv4
}
if _, found := plugin.allowedIPs[ipStr]; found {
allowed, reason = true, ipStr
break
}
match, _, found := plugin.allowedPrefixes.Root().LongestPrefix([]byte(ipStr))
if found {
if len(match) == len(ipStr) || (ipStr[len(match)] == '.' || ipStr[len(match)] == ':') {
allowed, reason = true, string(match)+"*"
break
}
}
}
if allowed {
pluginsState.sessionData["whitelisted"] = true
if plugin.logger != nil {
qName := pluginsState.qName
var clientIPStr string
if pluginsState.clientProto == "udp" {
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else {
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
}
var line string
if plugin.format == "tsv" {
now := time.Now()
year, month, day := now.Date()
hour, minute, second := now.Clock()
tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second)
line = fmt.Sprintf("%s\t%s\t%s\t%s\t%s\n", tsStr, clientIPStr, StringQuote(qName), StringQuote(ipStr), StringQuote(reason))
} else if plugin.format == "ltsv" {
line = fmt.Sprintf("time:%d\thost:%s\tqname:%s\tip:%s\tmessage:%s\n", time.Now().Unix(), clientIPStr, StringQuote(qName), StringQuote(ipStr), StringQuote(reason))
} else {
dlog.Fatalf("Unexpected log format: [%s]", plugin.format)
}
if plugin.logger == nil {
return errors.New("Log file not initialized")
}
_, _ = plugin.logger.Write([]byte(line))
}
}
return nil
}

View File

@ -132,6 +132,9 @@ func (proxy *Proxy) InitPluginsGlobals() error {
if len(proxy.nxLogFile) != 0 {
*responsePlugins = append(*responsePlugins, Plugin(new(PluginNxLog)))
}
if len(proxy.allowedIPFile) != 0 {
*responsePlugins = append(*responsePlugins, Plugin(new(PluginAllowedIP)))
}
if len(proxy.blockNameFile) != 0 {
*responsePlugins = append(*responsePlugins, Plugin(new(PluginBlockNameResponse)))
}

View File

@ -48,6 +48,9 @@ type Proxy struct {
forwardFile string
blockIPFormat string
blockIPLogFile string
allowedIPFile string
allowedIPFormat string
allowedIPLogFile string
queryLogFormat string
blockIPFile string
whitelistNameFormat string