-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* v2 mvp * minor changes and fixes * fixed the timer on geoip, added loglevel * added cidr acl * Update install.sh * minor yq fixes * better acl docs and var names * allow individual binds for different services * added ability to generate default config yaml * clearer parameters in geoip configs * cleaner installer wizard * added version info in build * removed extra whitespace * ACL overhaul * tweaked release parameters * converted defaultconfig to boolean * removed unused doh client * doh fixes * change override log to debug * doq connects back to the correct upstream * bugfixes in dns and ACL * moved back to zerolog and pretty logging * more fixes in logging format * fix in debug logging of DoQ * minor changes in cidr acl logic * add acl tests (#47) * add acl tests * moved yaml configs to the test file * added back previous tests and fixed the run function --------- Co-authored-by: Ali Mosajjal <hi@n0p.me> * acl stringifier * minor docs changes in the install.sh script --------- Co-authored-by: Andreas Groll <10852221+holygrolli@users.noreply.github.com>
- Loading branch information
1 parent
b20546a
commit 510eee6
Showing
31 changed files
with
2,862 additions
and
713 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,5 +23,6 @@ _testmain.go | |
*.test | ||
*.prof | ||
sniproxy | ||
config.yaml | ||
|
||
.TODO.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package acl | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"sort" | ||
|
||
"github.com/knadh/koanf" | ||
"github.com/rs/zerolog" | ||
) | ||
|
||
// Decision is the type of decision that an ACL can make for each connection info | ||
type Decision uint8 | ||
|
||
func (d Decision) String() string { | ||
switch d { | ||
case Accept: | ||
return "Accept" | ||
case Reject: | ||
return "Reject" | ||
case ProxyIP: | ||
return "ProxyIP" | ||
case OriginIP: | ||
return "OriginIP" | ||
case Override: | ||
return "Override" | ||
default: | ||
return "Unknown" | ||
} | ||
} | ||
|
||
const ( | ||
// Accept shows the indifference of the ACL to the connection | ||
Accept Decision = iota | ||
// Reject shows that the ACL has rejected the connection. each ACL should check this before proceeding to check the connection against its rules | ||
Reject | ||
// ProxyIP shows that the ACL has decided to proxy the connection through sniproxy rather than the origin IP | ||
ProxyIP | ||
// OriginIP shows that the ACL has decided to proxy the connection through the origin IP rather than sniproxy | ||
OriginIP | ||
// Override shows that the ACL has decided to override the connection and proxy it through the specified DstIP and DstPort | ||
Override | ||
) | ||
|
||
// ConnInfo contains all the information about a connection that is available | ||
// it also serves as an ACL enforcer in a sense that if IsRejected is set to true | ||
// the connection is dropped | ||
type ConnInfo struct { | ||
SrcIP net.Addr | ||
DstIP net.TCPAddr | ||
Domain string | ||
Decision | ||
} | ||
|
||
type ByPriority []ACL | ||
|
||
func (a ByPriority) Len() int { return len(a) } | ||
func (a ByPriority) Swap(i, j int) { a[i], a[j] = a[j], a[i] } | ||
func (a ByPriority) Less(i, j int) bool { return a[i].Priority() < a[j].Priority() } | ||
|
||
type ACL interface { | ||
Decide(*ConnInfo) error | ||
Name() string | ||
Priority() uint | ||
ConfigAndStart(*zerolog.Logger, *koanf.Koanf) error | ||
} | ||
|
||
// StartACLs starts all the ACLs that have been configured and registered | ||
func StartACLs(log *zerolog.Logger, k *koanf.Koanf) ([]ACL, error) { | ||
var a []ACL | ||
aclK := k.Cut("acl") | ||
for _, acl := range availableACLs { | ||
// cut each konaf based on the name of the ACL | ||
// only configure if the "enabled" key is set to true | ||
if !aclK.Bool(fmt.Sprintf("%s.enabled", (acl).Name())) { | ||
continue | ||
} | ||
var l = log.With().Str("acl", (acl).Name()).Logger() | ||
// we pass the full config to each ACL so that they can cut it themselves. it's needed for some ACLs that need | ||
// to read the config of other ACLs or the global config | ||
if err := acl.ConfigAndStart(&l, k); err != nil { | ||
log.Warn().Msgf("failed to start ACL %s with error %s", (acl).Name(), err) | ||
return a, err | ||
} | ||
a = append(a, acl) | ||
log.Info().Msgf("started ACL: '%s'", (acl).Name()) | ||
|
||
} | ||
return a, nil | ||
} | ||
|
||
// MakeDecision loops through all the ACLs and makes a decision for the connection | ||
func MakeDecision(c *ConnInfo, a []ACL) error { | ||
sort.Sort(ByPriority(a)) | ||
for _, acl := range a { | ||
if err := acl.Decide(c); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// each ACL should register itself by appending itself to this list | ||
var availableACLs []ACL |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
package acl | ||
|
||
import ( | ||
"net" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
"github.com/knadh/koanf" | ||
"github.com/knadh/koanf/parsers/yaml" | ||
"github.com/knadh/koanf/providers/rawbytes" | ||
"github.com/rs/zerolog" | ||
) | ||
|
||
var logger = zerolog.New(os.Stderr).With().Timestamp().Logger().Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339, NoColor: true}) | ||
|
||
var configs = map[string]string{ | ||
"acl_domain.yaml": ` | ||
acl: | ||
domain: | ||
enabled: true | ||
priority: 20 | ||
path: ../domains.csv | ||
refresh_interval: 1h0m0s`, | ||
"acl_cidr.yaml": ` | ||
acl: | ||
cidr: | ||
enabled: true | ||
priority: 30 | ||
path: ../cidr.csv | ||
refresh_interval: 1h0m0s`, | ||
"acl_domain_cidr.yaml": ` | ||
acl: | ||
domain: | ||
enabled: true | ||
priority: 20 | ||
path: ../domains.csv | ||
refresh_interval: 1h0m0s | ||
cidr: | ||
enabled: true | ||
priority: 30 | ||
path: ../cidr.csv | ||
refresh_interval: 1h0m0s`, | ||
"acl_cidr_domain.yaml": ` | ||
acl: | ||
domain: | ||
enabled: true | ||
priority: 20 | ||
path: ../domains.csv | ||
refresh_interval: 1h0m0s | ||
cidr: | ||
enabled: true | ||
priority: 19 | ||
path: ../cidr.csv | ||
refresh_interval: 1h0m0s`, | ||
} | ||
|
||
func TestMakeDecision(t *testing.T) { | ||
// Test cases | ||
cases := []struct { | ||
connInfo *ConnInfo | ||
config string | ||
expected Decision | ||
}{ | ||
{ | ||
// domain in domains.csv | ||
connInfo: mockConnInfo("1.1.1.1", "ipinfo.io"), | ||
config: configs["acl_domain.yaml"], | ||
expected: ProxyIP, | ||
}, | ||
{ | ||
// domain NOT in domains.csv | ||
connInfo: mockConnInfo("2.2.2.2", "google.de"), | ||
config: configs["acl_domain.yaml"], | ||
expected: OriginIP, | ||
}, | ||
{ | ||
// ip REJECT in cidr.csv | ||
// if you want to whitelist IPs then you must include "0.0.0.0/0,reject" otherwise always accepted!! | ||
connInfo: mockConnInfo("1.1.1.1", "google.de"), | ||
config: configs["acl_cidr.yaml"], | ||
expected: Reject, | ||
}, | ||
{ | ||
// ip ACCEPT in cidr.csv | ||
connInfo: mockConnInfo("77.77.1.1", "google.de"), | ||
config: configs["acl_cidr.yaml"], | ||
expected: Accept, | ||
}, | ||
{ | ||
// ip ACCEPT in cidr.csv, still no ProxyIP (acl.domain not enabled) | ||
connInfo: mockConnInfo("77.77.1.1", "ipinfo.io"), | ||
config: configs["acl_cidr.yaml"], | ||
expected: Accept, | ||
}, | ||
{ | ||
// domain in domains.csv, ip ACCEPT in cidr.csv | ||
connInfo: mockConnInfo("77.77.1.1", "ipinfo.io"), | ||
config: configs["acl_domain_cidr.yaml"], | ||
expected: ProxyIP, | ||
}, | ||
{ | ||
// domain NOT in domains.csv, ip ACCEPT in cidr.csv | ||
connInfo: mockConnInfo("77.77.1.1", "google.de"), | ||
config: configs["acl_domain_cidr.yaml"], | ||
expected: OriginIP, | ||
}, | ||
{ | ||
// domain in domains.csv, ip REJECT in cidr.csv | ||
connInfo: mockConnInfo("1.1.1.1", "ipinfo.io"), | ||
config: configs["acl_domain_cidr.yaml"], | ||
expected: Reject, // still returns OriginIP in DNS !!! | ||
}, | ||
{ | ||
// domain NOT in domains.csv, ip REJECT in cidr.csv | ||
connInfo: mockConnInfo("1.1.1.1", "google.de"), | ||
config: configs["acl_domain_cidr.yaml"], | ||
expected: Reject, // still returns OriginIP in DNS !!! | ||
}, | ||
{ | ||
// domain NOT in domains.csv, ip ACCEPT in cidr.csv | ||
connInfo: mockConnInfo("77.77.1.1", "google.de"), | ||
config: configs["acl_cidr_domain.yaml"], | ||
expected: OriginIP, | ||
}, | ||
{ | ||
// domain in domains.csv, ip ACCEPT in cidr.csv | ||
connInfo: mockConnInfo("77.77.1.1", "ipinfo.io"), | ||
config: configs["acl_cidr_domain.yaml"], | ||
expected: ProxyIP, | ||
}, | ||
{ | ||
// domain in domains.csv, ip REJECT in cidr.csv | ||
connInfo: mockConnInfo("1.1.1.1", "google.de"), | ||
config: configs["acl_cidr_domain.yaml"], | ||
expected: Reject, | ||
}, | ||
{ | ||
// domain NOT in domains.csv, ip REJECT in cidr.csv | ||
connInfo: mockConnInfo("1.1.1.1", "google.de"), | ||
config: configs["acl_cidr_domain.yaml"], | ||
expected: Reject, | ||
}, | ||
} | ||
|
||
// Run the test cases | ||
for _, tc := range cases { | ||
t.Run(tc.config, func(t *testing.T) { | ||
MakeDecision(tc.connInfo, getAcls(&logger, tc.config)) | ||
if tc.expected != tc.connInfo.Decision { | ||
t.Errorf("MakeDecision (domain=%v,ip=%v,config=%v) decided %v, expected %v", tc.connInfo.Domain, tc.connInfo.SrcIP, tc.config, tc.connInfo.Decision, tc.expected) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// TestReverse tests the reverse function | ||
func TestReverse(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
s string | ||
want string | ||
}{ | ||
{name: "test1", s: "abc", want: "cba"}, | ||
{name: "test2", s: "a", want: "a"}, | ||
{name: "test3", s: "aab", want: "baa"}, | ||
{name: "test4", s: "zzZ", want: "Zzz"}, | ||
{name: "test5", s: "ab2", want: "2ba"}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := reverse(tt.s); got != tt.want { | ||
t.Errorf("reverse() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func getAcls(log *zerolog.Logger, config string) []ACL { | ||
var k = koanf.New(".") | ||
if err := k.Load(rawbytes.Provider([]byte(config)), yaml.Parser()); err != nil { | ||
log.Fatal().Msgf("error loading config file: %v", err) | ||
} | ||
a, err := StartACLs(&logger, k) | ||
if err != nil { | ||
panic(err) | ||
} | ||
// we need this to give acl time to (re)load | ||
time.Sleep(1 * time.Second) | ||
return a | ||
} | ||
|
||
func mockConnInfo(srcIP string, domain string) *ConnInfo { | ||
addr, err := net.ResolveTCPAddr("tcp", srcIP+":80") | ||
|
||
if err != nil { | ||
logger.Fatal().Msgf("error parsing ip from string: %v", err) | ||
} | ||
|
||
return &ConnInfo{ | ||
SrcIP: addr, | ||
Domain: domain, | ||
} | ||
} |
Oops, something went wrong.