NetTrust is a Dynamic Outbound Firewall Authorizer. It uses a DNS as a source of truth to allow/deny outbund requests
The ideas is that we want to grant network access only to networks or hosts that we trust. Trusted networks and hosts are whitelisted in Output Netfilter Hook, while all others are rejected.
To increase security or privacy, we usually want to block outbound traffic to:
- Blocked DNS Queries
- Direct Network Communication (Static IP, no Query made)
For the first item in the list, this is known as DNS Black hole and is a secure way to narrow down network communication only to trusted domains. However, not all processes (or javascript functions for example) use DNS Queries. There are many who use static IPs to communicate with the outside world. For example, a javascript function could dynamically fetch a list of hosts during render and forward traffic to them. For such case, DNS Blackholes are worethless.
Firewalls normally allow outbound access to all hosts but restrict inbound access to a selected few
_________ _____________________
| | Direct Communication IPv4: x.x.x.x | |
| Process |=--------------------------------------> | OUTBOUND: Allow All |
|_________| |_____________________|
|
| _____________________________
| | |
|<--------------------------------------------=| INBOUND: Few or Established |
|_____________________________|
The allow all to public but filter inbound works well with servers. There we trust the services that we run and we control components in a more strict way
But what happens when we want to filter and increase security on hosts that are not as restricted as servers or on hosts that may do many things, like personal computers. We install packages often, visit different websites which exposes us to different kind of tracking (telemetries, etc) and risks (hostile apps, bad javascript functions sending traffic to hosts we can not easilly stop)
The problem here is is that it is hard to filter all good hosts due to:
- big number of public IPs. The total number of IPv4 addresses may be small for the world, but is really huge to filter it in a list
- hosts usually change IPs, which makes the management of a whitelist even harder
A work around this issue (up to a way, because as always everything has its weaknesses), is to use DNS for traffic authorization.
DNS Authorizers are normal DNS hosts that we trust a lot. For example a local DNS service that we have configured to blacklist certain domains, or block all except some domains
With DNS Authorizer we can:
- Use them to filter our unwanted domains (most common, needs a list of bad hosts)
- Use them to filter in only wanted domains (most secure, needs a lot more work )
By using a DNS Authorizer we have the pros of a DNS Blackhole + easy filtering of IPs. All we need, is to block all outbound traffic and then allow only the traffic that DNS answers in the queries
This is how NetTrust works. It is small dns proxy with Netfilter management capabilities.
________________ ____________ ____________
| | Query: example.com | | Forward | |
| Host Process A |=------------------------> | NetTrust |=--------------> | DNS Server |
|________________| |____________| |____________|
In the above diagram queries are sent to NetTrust, and from there NetTrust forwards them to the DNS Server that either knows the question or has been configured to forward queries.
________________ ____________ ____________
| | Query: example.com | | Reply OK | |
| Host Process A |=------------------------> | NetTrust | <--------------=| DNS Server |
|________________| |____________| |____________|
| |
| |
| |
| |
| | _____________
| | | |
| |=-----> | Netfilter |
| | |
| -------------
______________ |
| | x.x.x.x is whitelisted |
| OUTPUT HOOK | <------------------------------------------------=|
|______________|
Once NetTrust receives a query response, it checks if there are any answers (hosts resolved). If there are, it proceeds by updating firewall rules (e.g. nftables) in order to allow network access to the resolved hosts. If there is no answer, or if the answer is 0.0.0.0, no action is taken. In all cases, the dns reply is sent back to the requestor process after a firewall decision has been made (if any).
NetTrust by default does not enable TTL on authorized hosts. The max authorized time a host can get is the time that NetTrust runs. Once NetTrust exits gracefully, it will clear the authorized hosts.
We can enable however TTL on authorized hosts. By adding a TTL, NetTrust will allow communication to that host for as long as TTL is set. Once a host is expired and no session is active (see Conntrack section below), it will be removed from the authorized list and will be expected by the process that wants to continue communication to resolve the host via the DNS again.
All sessions that have TTL enabled will be checked against two rules. The first rule is the TTL itself. If the host has not expired, nothing happens, if it has expired, then conntrack will be checked to ensure that no connection with the specific host is active. If a tuple contains the host, either in the src or dst, then the TTL will be renewed and the host will be checked again in the next expiration. If the host is not part of any conntrack connection, then the host will be removed from the cache and the firewall's authorized hosts set
_______ _____________
| | Get Expired Hosts | |
| Cache |=------------------------------> | TTL Checker |
|_______| |_____________|
|
|
__________|__________
| |
| Has host X Expired? |
|_____________________|
|
| Yes
_______________ __________|___________ ___________
| | No | | Yes | |
| De-Authorize | <------------------=| Is connection active |=--------------> | Renew TTL |
|_______________| |______________________| |___________|
|
|
|
___________ |
| | Get Active Connections |
| Conntrack |=---------------------------------> |
|___________|
To build NetTrust, simply issue:
go build -o nettrust cmd/nettrust.go
Note: NetTrust needs to interact Netfilter, for that, it requires root access
To run NetTrust, issue ./nettrust -fwd-addr "someIP:53" -listen-addr "127.0.0.1:53 -config config.json"
- listen-addr is the listening address that NetTrust will listen and forward dns queries
- fwd-addr is the address of the DNS Server that NetTrust will use to resolve queries
Example output
INFO[2022-02-12T20:40:16+02:00] Starting UDP DNS Server Component="DNS Server" Stage=Init
INFO[2022-02-12T20:40:16+02:00] Starting TCP DNS Server Component="DNS Server" Stage=Init
INFO[2022-02-12T20:40:16+02:00] Starting Component="[UDP] DNSServer" Stage=Init
INFO[2022-02-12T20:40:16+02:00] Starging Component="[TCP] DNSServer" Stage=Init
# Some time later
INFO[2022-02-12T20:41:02+02:00] [Blocked] Question: example.com. Component=Firewall Stage=Authorizer
INFO[2022-02-12T20:41:02+02:00] [Not Handled] Question: example.com.federation.local. - Is this local? Component=Firewall Stage=Authorizer
INFO[2022-02-12T20:41:14+02:00] [PTR] Question: 247.1.168.192.in-addr.arpa. resolved to arph.federation.local. Component=Firewall Stage=Authorizer
INFO[2022-02-12T20:41:36+02:00] [Blocked] Question: api.removedButWasSomeDomainHere. Component=Firewall Stage=Authorizer
INFO[2022-02-12T20:41:37+02:00] [Blocked] Question: api.removedButWasSomeDomainHere. Component=Firewall Stage=Authorizer
INFO[2022-02-12T20:41:38+02:00] [Blocked] Question: api.removedButWasSomeDomainHere. Component=Firewall Stage=Authorizer
INFO[2022-02-12T20:41:41+02:00] [Blocked] Question: api.removedButWasSomeDomainHere. Component=Firewall Stage=Authorizer
INFO[2022-02-12T20:41:49+02:00] [Blocked] Question: api.removedButWasSomeDomainHere. Component=Firewall Stage=Authorizer
# Some time later when I did a git push
INFO[2022-02-12T21:19:30+02:00] [Authorized] Question: github.com. Hosts: [140.82.121.4] Component=Firewall Stage=Authorizer
# Some time later when I did a git fetch
INFO[2022-02-12T21:41:54+02:00] [Already Authorized] Question: github.com. Host: 140.82.121.3 Component=Firewall Stage=Authorizer
The nftables authorized hosts set now looks like this
table ip net-trust {
set whitelist {
type ipv4_addr
elements = { 127.0.0.1, 192.168.178.21 }
}
set authorized {
type ipv4_addr
elements = { xyz.xyz.xyz.xyz, xyz.xyz.xyz.xyz,
xyz.xyz.xyz.xyz, xyz.xyz.xyz.xyz,
xyz.xyz.xyz.xyz, xyz.xyz.xyz.xyz,
xyz.xyz.xyz.xyz, xyz.xyz.xyz.xyz,
xyz.xyz.xyz.xyz, xyz.xyz.xyz.xyz,
xyz.xyz.xyz.xyz, xyz.xyz.xyz.xyz,
xyz.xyz.xyz.xyz, 140.82.121.3 }
}
chain authorized-output {
type filter hook output priority filter; policy drop;
ip daddr 127.0.0.0/8 counter packets 563 bytes 48587 accept
ip daddr 10.0.0.0/8 counter packets 0 bytes 0 accept
ip daddr 172.16.0.0/12 counter packets 0 bytes 0 accept
ip daddr 192.168.0.0/16 counter packets 273 bytes 20402 accept
ip daddr 100.64.0.0/10 counter packets 0 bytes 0 accept
ip daddr @whitelist accept
ip daddr @authorized accept
counter packets 23 bytes 2637 reject with icmp type net-unreachable
}
}
Check options below for additional configuration
NetTrust accepts the follwoing options
Usage of ./bin/nettrust:
-authorized-ttl int
Number of seconds a authorized host will be active before NetTrust expires it and expect a DNS query again (-1 do not expire)
-config string
Path to config.json
-dns-ttl-cache int
Number of seconds dns queries stay in cache (-1 to disable caching)
-do-not-flush-authorized-hosts
Do not clean up the authorized hosts list on exit. Use this together with do-not-flush-table to keep the NetTrust table as is on exit
-do-not-flush-table
Do not clean up tables when NetTrust exists. Use this flag if you want to continue to deny communication when NetTrust has exited
-firewall-backend string
NetTrust firewall backend [nftables/iptables/iptables-nft] that will be used to interact with Netfilter (nftables is only supported for now)
-firewall-drop-input
If enabled, NetTrust will drop input. Adds [ct state established,related accept] & ['lo' accept]. Should be enabled only when NetTrust runs in host
-firewall-type string
NetTrust firewall type. Supported types: OUTPUT (default), FORWARD. The type essentially tells NetTrust on which hook the rules will be added
-fwd-addr string
NetTrust forward dns address
-fwd-proto string
NetTrust dns forward protocol
-fwd-tls
Enable DoT. This expects that forward dns address supports DoT and fwd-proto is tcp
-fwd-tls-cert string
path to certificate that will be used to validate forward dns hostname. If you do not set this, the the host root CAs will be used
-listen-addr string
NetTrust listen dns address
-listen-cert string
path to certificate that will be used by the TCP DNS Service to serve DoT
-listen-cert-key string
path to the private key that will be used by the TCP DNS Service to serve DoT
-listen-tls
Enable tls listener, tls listener works only with the TCP DNS Service, UDP will continue to serve in plaintext mode
-ttl-check-ticker int
How often NetTrust should check the cache for expired authorized hosts (Checking is blocking, do not put small numbers)
-whitelist-loopback
Loopback network space 127.0.0.0/8 will be whitelisted (default true)
-whitelist-private
If 10.0.0.0/8, 172.16.0.0/16, 192.168.0.0/16, 100.64.0.0/10 will be whitelisted (default true)
You can also use a json config to set options.
{
"whitelist": {
"networks": [],
"hosts": []
},
"blacklist": {
"networks": [],
"hosts": []
},
"fwdAddr": "192.168.178.21:53", // Example address of local dns server
"fwdProto": "udp",
"fwdCaCert": "",
"fwdTLS": false,
"listenAddr": "127.0.0.1:53",
"listenTLS": false,
"listenCert": "",
"listenCertKey": "",
"firewallBackend": "nftables",
"firewallType": "OUTPUT",
"firewallDropInput": false,
"dnsTTLCache": -1,
"whitelistLoEnabled": true,
"whitelistPrivateEnabled": true,
"ttl": -1,
"ttlInterval": 30,
"doNotFlushTable": false, // Set this to true if you want to keep the rules and the chain when NetTrust has stopped
"doNotFlushAuthorizedHosts": false
}
Note: Config file options have lower priority from flag options. For example, if you start NetTrust with -fwd-tls
and you set fwdTLS: false
in the config, NetTrust will use tls since flags have the highest priority
Note: As you can imagine, with this option set to true, you will not be able to access any host that is not part of a whitelisted option (hosts, networks). If you enabled this option and you wish to revert back, simply start NetTrust again with this option set to false and then exit
If you wish to keep the table's content on NetTrust exit, then pass either -do-not-flush-table
via flags or doNotFlushTable: true
via config. NetTrust on exit will clear only the authorized set, that is the set that is populated via resolved hosts.
It will keep:
- The chain with the default policy to drop
- The whitelisted hosts
- The whitelisted networks
- Final reject verdict
Note: Whitelisting, blacklisting should be done automatically via DNS proxy. This option should be used if you want to add custom entries
We can add hosts or networks to whitelist or blacklist by using
- Environmental variables
- config file
config.json
Note: Networks are evaluated first in the chain managed by NetTrust (for additional info, see NFTables Overview section at the end of this Readme)
Whitelist a host by exporting NET_TRUST_WHITELIST_HOSTS_<someHost>=someIP
export NET_TRUST_WHITELIST_HOSTS_CUSTOM=192.168.1.1
Whitelist a network by exporting NET_TRUST_WHITELIST_NETWORK_<someNetworkName>=someNetwork
export NET_TRUST_WHITELIST_NETWORK_HOME=192.168.1.0/24
Note: blacklisting via env is not yet supported. Check config section in the next section to add blacklists
Use config.json
to whitelist or blacklist hosts and networks
{
"whitelist": {
"networks": [],
"hosts": []
},
"blacklist": {
"networks": [],
"hosts": []
}
}
Blacklisting instructs NetTrust to skip hosts that match the hostlist or are part of the network. Skipping is essentially blackist since chain's tailing policy is reject and chain's default policy is drop
NetTrust creates a table called net-trust
and a chain called authorized
. Inside the chain it also creates two sets
- whitelist: populated by whitelisted hosts during init of NetTrust. This set should stay static during the lifetime of NetTrust (or unless new hosts are whitelisted)
- authorized: this set is used to add authorized hosts. If TTL is set to
-1
then this set should only grow during the lifetime of NetTrust and emptied on exit (we empty to ensure we do not forget whitelisted hosts behind)
Example of a populated table and chain. Here 127.0.0.1 and 192.168.178.21 are redundant since we whitelisted the networks that contain them, but were added because 127.0.0.1 was the listening address of NetTrust dns proxy and 192.168.178.21 is the IP of a local DNS Black hole. We always whitelist listening address and forward address to ensure that NetTrust will work without issues for cases where NetTrust is started with -whitelist-private=false
table ip net-trust {
set whitelist {
type ipv4_addr
elements = { 127.0.0.1, 192.168.178.21 }
}
set authorized {
type ipv4_addr
}
chain authorized-output {
type filter hook output priority filter; policy drop;
ip daddr 127.0.0.0/8 counter packets 2389 bytes 469802 accept
ip daddr 10.0.0.0/8 counter packets 0 bytes 0 accept
ip daddr 172.16.0.0/12 counter packets 0 bytes 0 accept
ip daddr 192.168.0.0/16 counter packets 807 bytes 66255 accept
ip daddr 100.64.0.0/10 counter packets 0 bytes 0 accept
ip daddr @whitelist accept
ip daddr @authorized accept
counter packets 14 bytes 1370 reject with icmp type net-unreachable
}
}
As you may have noticed, there is no blacklist entry in the chain or in any set. This is because NetTrust uses deny all except firewall implementation. Blacklists are all hosts that are not resolved by the DNS Authority and the hosts added manually via the config file or env vars. The blacklisting is taking place in the DNS Proxy handler, there we check any returned results by the DNS Authority and skip them if they match a blacklist rule
If you need to remove NetTrust rules and chains manually, then please follow this section
To remove a rule, first get the rule's handle number
sudo nft list table net-trust -a
table ip net-trust { # handle 5
set whitelist { # handle 7
type ipv4_addr
elements = { 127.0.0.1, 192.168.178.21 }
}
set authorized { # handle 9
type ipv4_addr
elements = { 140.82.121.4 }
}
chain authorized-output { # handle 129
type filter hook output priority filter; policy drop;
ip daddr 127.0.0.0/8 counter packets 2174 bytes 196333 accept # handle 130
ip daddr 10.0.0.0/8 counter packets 0 bytes 0 accept # handle 131
ip daddr 172.16.0.0/12 counter packets 0 bytes 0 accept # handle 132
ip daddr 192.168.0.0/16 counter packets 105 bytes 8793 accept # handle 133
ip daddr 100.64.0.0/10 counter packets 0 bytes 0 accept # handle 134
ip daddr @whitelist accept # handle 135
ip daddr @authorized accept # handle 136
counter packets 90 bytes 8380 reject with icmp type net-unreachable # handle 137
}
}
To remove rule ip daddr 100.64.0.0/10 counter packets 0 bytes 0 accept # handle 134
as an example, issue
sudo nft delete rule net-trust authorized-output handle 134
To remove all rules from all chains, issue
sudo nft 'flush table net-trust'