Code Monkey home page Code Monkey logo

weakforced's Introduction

Weakforced

The goal of 'wforce' is to detect brute forcing of passwords across many servers, services and instances. In order to support the real world, brute force detection policy can be tailored to deal with "bulk, but legitimate" users of your service, as well as botnet-wide slowscans of passwords.

The aim is to support the largest of installations, providing services to hundreds of millions of users. The current version of weakforced is not quite there yet, although it certainly scales to support up to ten million users, if not more. The limiting factor is number of logins per second at peak.

Wforce is a project by Dovecot, PowerDNS and Open-Xchange. For historical reasons, it lives in the PowerDNS github organization. If you have any questions, email [email protected].

For detailed technical documentation, please go to https://powerdns.github.io/weakforced/.

Here is how it works:

  • Report successful logins via JSON http-api
  • Report unsuccessful logins via JSON http-api
  • Query if a login should be allowed to proceed, should be delayed, or ignored via http-api
  • API for querying the status of logins, IP addresses etc.
  • Runtime console for server introspection

wforce is aimed to receive message from services like:

  • IMAP
  • POP3
  • Webmail logins
  • FTP logins
  • Authenticated SMTP
  • Self-service logins
  • Password recovery services

By gathering failed and successful login attempts from as many services as possible, brute forcing attacks as well as other suspicious behaviour can be detected and prevented more effectively.

Inspiration: http://www.techspot.com/news/58199-developer-reported-icloud-brute-force-password-hack-to-apple-nearly-six-month-ago.html

Installing

Docker:

There is a docker image hosted on docker hub, see https://powerdns.github.io/weakforced/ for more details.

From GitHub:

The easy way:

$ git clone https://github.com/PowerDNS/weakforced.git
$ cd weakforced
$ git submodule init
$ git submodule update
$ builder/build.sh debian-bullseye | debian-stretch | centos-7 | ol-8 | amazon-2

This will build packages for the appropriate OS. You will need docker and docker-compose for the builder to work.

The hard way:

$ git clone https://github.com/PowerDNS/weakforced.git
$ cd weakforced
$ autoreconf -i
$ ./configure
$ make

This requires recent versions of libtool, automake and autoconf to be installed.

It also requires:

  • A compiler supporting C++ 17
  • Lua 5.1+ development libraries (or LuaJIT if you configure --with-luajit)
  • Boost 1.61+
  • Protobuf compiler and protobuf development libraries
  • Getdns development libraries (if you want to use the DNS lookup functionality)
  • libsodium
  • python + virtualenv for regression testing
  • libgeoip-dev for GeoIP support
  • libsystemd-dev for systemd support
  • pandoc for building the manpages
  • libcurl-dev (OpenSSL version)
  • libhiredis-dev
  • libssl-dev
  • libprometheus-cpp (https://github.com/jupp0r/prometheus-cpp)
  • libmaxminddb-dev
  • libyaml-cpp-dev
  • libdrogon (https://github.com/drogonframework/drogon) - Used for the HTTP server
  • libjsoncpp-dev
  • libuuid-dev
  • libz-dev
  • docker for regression testing
  • python3 rather than python2
  • python-bottle for regression testing of webhooks

To build on OS X, brew install readline and use ./configure PKG_CONFIG_PATH=<path to your openssl installation> LDFLAGS=-L/usr/local/opt/readline/lib CPPFLAGS=-I/usr/local/opt/readline/include

Add --with-luajit to the end of the configure line if you want to use LuaJIT.

Policies

There is a sensible, if very simple, default policy in wforce.conf (running without this means no policy), and extensive support for crafting your own policies using the insanely great Lua scripting language.

Note that although there is a single Lua configuration file, the canonicalize, reset, report and allow functions run in different lua states from the rest of the configuration. This mostly "just works", but may lead to unexpected behaviour such as running Lua commands at the server Lua prompt, and getting multiple answers (because Lua commands are passed to all Lua states).

Sample:

-- set up the things we want to track
field_map = {}
-- use hyperloglog to track cardinality of (failed) password attempts
field_map["diffFailedPasswords"] = "hll"
-- track those things over 6x10 minute windows
newStringStatsDB("OneHourDB", 600, 6, field_map)

-- this function counts interesting things when "report" is invoked
function twreport(lt)
	sdb = getStringStatsDB("OneHourDB")
	if (not lt.success)
	then
	   sdb:twAdd(lt.remote, "diffFailedPasswords", lt.pwhash)
	   addrlogin = lt.remote:tostring() .. lt.login
	   sdb:twAdd(addrlogin, "diffFailedPasswords", lt.pwhash)
	end
end

function allow(lt)
	sdb = getStringStatsDB("OneHourDB")
	if(sdb:twGet(lt.remote, "diffFailedPasswords") > 50)
	then
		return -1, "", "", {} -- BLOCK!
	end
	// concatenate the IP address and login string
	addrlogin = lt.remote:tostring() .. lt.login	
	if(sdb:twGet(addrlogin, "diffFailedPasswords") > 3)
	then
		return 3, "tarpitted", "diffFailedPasswords", {} -- must wait for 3 seconds
	end

	return 0, "", "", {} -- OK!
end

Many more metrics are available to base decisions on. Some example code is in wforce.conf, and more extensive examples are in wforce.conf.example. For full documentation, use "man wforce.conf".

To report (if you configured with 'webserver("127.0.0.1:8084", "secret")'):

$ for a in {1..101}
  do
    curl -X POST -H "Content-Type: application/json" --data '{"login":"ahu", "remote": "127.0.0.1", "pwhash":"1234'$a'", "success":"false"}' \
    http://127.0.0.1:8084/?command=report -u wforce:secret
  done

This reports 101 failed logins for one user, but with different password hashes.

Now to look up if we're still allowed in:

$ curl -X POST -H "Content-Type: application/json" --data '{"login":"ahu", "remote": "127.0.0.1", "pwhash":"1234"}' \
  http://127.0.0.1:8084/?command=allow -u wforce:super
{"status": -1, "msg": "diffFailedPasswords"}

It appears we are not!

You can also provide additional information for use by weakforce using the optional "attrs" object. An example:

$ curl -X POST -H "Content-Type: application/json" --data '{"login":"ahu", "remote": "127.0.0.1",
"pwhash":"1234", "attrs":{"attr1":"val1", "attr2":"val2"}}' \
  http://127.0.0.1:8084/?command=allow -u wforce:super
{"status": 0, "msg": ""}

An example using the optional attrs object using multi-valued attributes:

$ curl -X POST -H "Content-Type: application/json" --data '{"login":"ahu", "remote": "127.0.0.1",
"pwhash":"1234", "attrs":{"attr1":"val1", "attr2":["val2","val3"]}}' \
  http://127.0.0.1:8084/?command=allow -u wforce:super
{"status": 0, "msg": ""}

There is also a command to reset the stats for a given login and/or IP Address, using the 'reset' command, the logic for which is also implemented in Lua. The default configuration for reset is as follows:

function reset(type, login, ip)
	 sdb = getStringStatsDB("OneHourDB")
	 if (string.find(type, "ip"))
	 then
		sdb:twReset(ip)
	 end
	 if (string.find(type, "login"))
	 then
		sdb:twReset(login)
	 end
	 if (string.find(type, "ip") and string.find(type, "login"))
	 then
		iplogin = ip:tostring() .. login
		sdb:twReset(iplogin)
	 end
	 return true
end

To test it out, try the following to reset the login 'ahu':

$ curl -X POST -H "Content-Type: application/json" --data '{"login":"ahu"}'\
  http://127.0.0.1:8084/?command=reset -u wforce:super
{"status": "ok"}

You can reset IP addresses also:

$ curl -X POST -H "Content-Type: application/json" --data '{"ip":"128.243.21.16"}'\
  http://127.0.0.1:8084/?command=reset -u wforce:super
{"status": "ok"}

Or both in the same command (this helps if you are tracking stats using compound keys combining both IP address and login):

$ curl -X POST -H "Content-Type: application/json" --data '{"login":"ahu", "ip":"FE80::0202:B3FF:FE1E:8329"}'\
  http://127.0.0.1:8084/?command=reset -u wforce:super
{"status": "ok"}

Finally there is a "ping" command, to check the server is up and answering requests:

$ curl -X GET http://127.0.0.1:8084/?command=ping -u wforce:super
{"status": "ok"}

Console

Available over TCP/IP, like this:

setKey("Ay9KXgU3g4ygK+qWT0Ut4gH8PPz02gbtPeXWPdjD0HE=")
controlSocket("0.0.0.0:4004")

Launch wforce as a daemon (wforce --daemon), to connect, run wforce -c. Comes with autocomplete and command history. If you put an actual IP address in place of 0.0.0.0, you can use the same config to listen and connect remotely.

To get some stats, try:

> stats()
40 reports, 8 allow-queries, 40 entries in database

The wforce manpage describes the command-line options and all the possible console commands in more detail.

Spec

Wforce accepts reports with 4 mandatory fields plus multiple optional fields.

Mandatory:

  • login (string): the user name or number or whatever
  • remote (ip address): the address the user arrived on
  • pwhash (string): a highly truncated hash of the password used
  • success (boolean): was the login a success or not?

Optional:

  • policy_reject (boolean) - If the login was not successful only because of a policy-based reject from wforce (i.e. the username and password were correct).
  • attrs (json object): additional information about the login. For example, attributes from a user database.
  • device_id (string) - A string that represents the device that the user logged in from. For HTTP this would typically be the User-Agent string, and for IMAP it would be the IMAP client ID command string.
  • protocol (string) - A string representing the protocol used to login, e.g. "http", "imap", "pop3".
  • tls (boolean) - Whether or not the login was secured with TLS.

The entire HTTP API is documented using the excellent OpenAPI (swagger) specification.

The pwhash field deserves some clarification. In order to distinguish actual brute forcing of a password, and repeated incorrect but identical login attempts, we need some marker that tells us if passwords are different.

Naively, we could hash the password, but this would spread knowledge of secret credentials beyond where it should reasonably be. Even if we salt and iterate the hash, or use a specific 'slow' hash, we're still spreading knowledge.

However, if we take any kind of hash and truncate it severely, for example to 12 bits, the hash tells us very little about the password itself - since one in 4096 random strings will match it anyhow. But for detecting multiple identical logins, it is good enough.

For additional security, hash the login name together with the password - this prevents detecting different logins that might have the same password.

NOTE: wforce does not require any specific kind of hashing scheme, but it is important that all services reporting successful/failed logins use the same scheme!

When in doubt, try:

TRUNCATE(SHA256(SECRET + LOGIN + '\x00' + PASSWORD), 12)

Which denotes to take the first 12 bits of the hash of the concatenation of a secret, the login, a 0 byte and the password. Prepend 4 0 bits to get something that can be expressed as two bytes.

API Calls

We can call 'report', and 'allow' commands. The optional 'attrs' field enables the client to send additional data to weakforced.

To report, POST to /?command=report a JSON object with fields from the LoginTuple as described above.

To request if a login should be allowed, POST to /?command=allow, again with the LoginTuple. The result is a JSON object with a "status" field. If this is -1, do not perform login validation (i.e. provide no clue to the client if the password was correct or not, or even if the account exists).

If 0, allow login validation to proceed. If a positive number, sleep this many seconds until allowing login validation to proceed.

Custom API Endpoints

You can create custom API commands (REST Endpoints) using the following configuration:

setCustomEndpoint("custom", customfunc)

which will create a new API command "custom", which calls the Lua function "customfunc" whenever that command is invoked. Parameters to custom commands are always in the same form, which is key-value pairs wrapped in an 'attrs' object. For example, the following parameters sents as json in the message body would be valid:

{ "attrs" : { "key" : "value" }}

Custom functions return values are also key-value pairs, this time wrapped in an 'r_attrs' object, along with a boolean success field, for example:

{ "r_attrs" : { "key" : "value" }, "success" : true}

An example configuration for a custom API endpoint would look like:

function custom(args)
	for k,v in pairs(args.attrs) do
		infoLog("custom func argument attrs", { key=k, value=v });
	end
	-- return consists of a boolean, followed by { key-value pairs }
	return true, { key=value }
end
setCustomEndpoint("custom", custom)

An example curl command would be:

% curl -v -X POST -H "Content-Type: application/json" --data
  '{"attrs":{"login1":"ahu", "remote": "127.0.0.1",  "pwhash":"1234"}}'
  http://127.0.0.1:8084/?command=custom -u wforce:super
{"r_attrs": {}, "success": true}

WebHooks

It is possible to configure webhooks, which get called whenever specific events occur. To do this, use the "addWebHook" configuration command. For example:

config_keys={}
config_keys["url"] = "http://webhooks.example.com:8080/webhook/"
config_keys["secret"] = "verysecretcode"
events = { "report", "allow" }
addWebHook(events, config_keys)

The above will call the webhook at the specified url, for every report and allow command received, with the body of the POST containing the original json data sent to wforce. For more information use "man wforce.conf" and "man wforce_webhook".

Custom WebHooks

Custom webhooks can also be defined, which are not invoked based on specific events, but instead from Lua. Configuration is similar to normal webhooks:

config_keys={}
config_keys["url"] = "http://webhooks.example.com:8080/webhook/"
config_keys["secret"] = "verysecretcode"
config_keys["content-type"] = "application/json"
addCustomWebHook("mycustomhook", config_keys)

However, the webhook will only be invoked via the Lua "runCustomWebHook" command, for example:

runCustomWebHook(mycustomhook", "{ \"foo\":\"bar\" }")

The above command will invoke the custom webhook "mycustomhook" with the data contained in the second argument, which is simply a Lua string. No parsing of the data is performed, however the Content-Type of the webhook, which defaults to application/json can be customized as shown above.

Blacklists

Blacklisting capability is provided via either REST endpoints or Lua commands, to add/delete IP addresses, logins or IP:login tuples from the Blacklist. Blacklist information can be replicated (see below), and also optionally persisted in a Redis DB. Use "man wforce.conf" to learn more about the blacklist commands.

Load balancing: siblings

For high-availability or performance reasons it may be desireable to run multiple instances of wforce. To present a unified view of status however, these instances then need to share data. To do so, wforce implements a simple knowledge-sharing system.

The original version of wforce simply broadcast all received report tuples (best effort, UDP) to all siblings. However the latest version only broadcasts incremental changes to the underlying state databases, namely the stats dbs and the blacklist.

The sibling list is parsed such that we don't broadcast messages to ourselves accidentally, and can thus be identical across all servers.

Even if you configure siblings, stats db data is not replicated by default. To do this, use the "twEnableReplication()" command on each stats db for which you wish to enable replication. Blacklist information is automatically replicated if you have configured siblings.

To define siblings, use:

setKey("Ay9KXgU3g4ygK+qWT0Ut4gH8PPz02gbtPeXWPdjD0HE=")
addSibling("192.168.1.79")
addSibling("192.168.1.30")
addSibling("192.168.1.54")
siblingListener("0.0.0.0")

The first line sets the authentication and encryption key for our sibling communications. To make your own key (recommended), run makeKey() on the console and paste the output in all your configuration files.

This last line configures that we also listen to our other siblings (which is nice). The default port is 4001, the protocol is UDP.

To view sibling stats:

> siblings()
Address                             Send Successes  Send Failures  Rcv Successes   Rcv Failures     Note
192.168.1.79:4001                   18              7              0               0
192.168.1.30:4001                   25              0              0               0
192.168.1.54:4001                   0               0              0               0                Self

With this setup, several wforces are all kept in sync, and can be load balanced behind (for example) haproxy, which incidentally can also offer SSL.

GeoIP2 Support

GeoIP support is provided using the GeoIP2 Maxmind APIs DBs (i.e. DBs ending in .mmdb). This is the preferred integration to use, as support for GeoIP Legacy DBs will be discontinued by Maxmind in 2019.

GeoIP2 DBs are represented by a Lua object that is created with the following call:

newGeoIP2DB("Name", "/path/to/file.mmdb")

The Lua object is retrieved with the following call:

local mygeodb = getGeoIP2DB("Name")

You can then lookup information using the following calls:

  • lookupCountry() - Returns the 2 letter country code associated with the IP address
  • lookupISP() - Returns the name of the ISP associated with the IP address (requires the Maxmind ISP DB, which is only available on subscription)
  • lookupCity - Rather than only returning a city name, this call returns a Lua table which includes the following information:
    • country_code
    • country_name
    • region
    • city
    • postal_code
    • continent_code
    • latitude
    • longitude

For example:

local geoip_data = mygeodp:lookupCity(newCA("128.243.21.16"))
print(geoip_data.city)
print(geoip_data.longitude)
print(geoip_data.latitude)

Legacy GeoIP Support

Support for legacy GeoIP databases (i.e. ending in .dat) is deprecated, since Maxmind will be discontinuing support for them in 2019.

Three types of GeoIP lookup are supported:

  • Country lookups - Initialized with initGeoIPDB() and looked up with lookupCountry()
  • ISP Lookups - Initialized with initGeoIPISPDB() and looked up with lookupISP()
  • City Lookup - Initialized with initGeoIPCityDB() and looked up with lookupCity()

The Country and ISP lookups return a string, while lookupCity() returns a Lua map consisting of the following keys:

  • country_code
  • country_name
  • region
  • city
  • postal_code
  • continent_code
  • latitude
  • longitude

For example:

local geoip_data = lookupCity(newCA("128.243.21.16"))
print(geoip_data.city)

When a DB is initialized, wforce attempts to open both v4 and v6 versions of the database. If either is not found an error is thrown, so make sure both ipv4 and v6 versions of each DB are installed.

Additionally, when using the free/lite versions of the databases, you may see errors such as "initGeoIPCityDB(): Error initialising GeoIP (No geoip v6 city db available)". This is usually because the filenames for the "lite" DBs are not the same as the expected filenames for the full DBs, specifically all files must start with GeoIP rather than GeoLite. Creating symbolic links to the expected filenames will fix this problem, for example:

ln -s GeoLiteCityv6.dat GeoIPCityv6.dat

weakforced's People

Contributors

ahupowerdns avatar bgmex avatar chbruyand avatar cmouse avatar denji avatar dependabot[bot] avatar habbie avatar jsoref avatar justinclift avatar kleini avatar mkochenough avatar mrannanj avatar neilcook avatar pieterlexis avatar thuovila avatar villesavolainen avatar ychaouche avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

weakforced's Issues

Integrate LDAP support

Feature request -
It would be useful to have support for LDAP lookups integrated into Weakforced.
Although we can currently make LDAP queries by using the Lua LDAP module, this can be relatively slow, particularly when you're trying to make a fast allow() function call. An integrated LDAP lookup (similar to the integrated DNS lookup) could have caching (both positive and negative) to help speed things up, and better timeout handling; this would potentially allow people to configure per-account thresholds in their rules without performance problems.

Delegate blacklisting to Lua

Currently blacklisting is done in C++ before Lua is called. A global config key could disable this, so that when configured, blacklist checking is instead done by calling a lua function. This Lua function would check the blacklists, but then would also allow overriding with a custom lua function to do different stuff (like check attrs for per-user overrides or whatever).

Custom functions appear to return HTTP status 404

Created a custom function getstats to let me query the current database content. When calling this via the 8084 API, it returns the content correctly, but has an HTTP status 404.
The function returns a true status, and the calling IP is in the ACL subnet.

function getstats (args)
sdb = getStringStatsDB("OneHourDB")
if ( args.attrs.remote and args.attrs.remote ~= "" )
then
  ip = newCA(args.attrs.remote)
  fpbyip = sdb:twGet(ip, "failedPasswords")
end
fpbylogin = sdb:twGet(args.attrs.login, "failedPasswords")
return true, { login=args.attrs.login, ip=args.attrs.remote, failedpasswordsbylogin=fpbylogin, failedpasswordsbyip=fpbyip }
end
setCustomEndpoint("getstats",false,getstats)

This seems to be an error in the wforced handler?

Support HTTP persistent connections

Support HTTP 1.1 persistent connections and the HTTP 1.0 "Connection: keep-alive".

Also timeout connections that have not been used for N seconds (configurable).

Client gets disconnected on bad command

Product: weakforced
Version: 1:1.2.0-2

~# wforce  -c
Read configuration from '/etc/wforce.conf'
Connecting to 0.0.0.0:4004
> status
Fatal error: EOF while writing message

And logs show

Jan 11 15:44:19 dev02 wforce[29825]: Got control connection from 127.0.0.1:58726
Jan 11 15:44:22 dev02 wforce[29825]: Got an exception in client connection from 127.0.0.1:58726: [string "status"]:1: '=' expected near '<eof>'
Jan 11 15:44:22 dev02 wforce[29855]: Fatal error: EOF while writing message

Misc Questions

Lots of questions (but I didn't want to explode your "Issues" count in github)

  • Does the usual advice to use 'local' for all locally-scoped variables apply here as usual? Or is there any complication with 'local'? There aren't any 'local' instances in the wforce.conf or wforce.conf.example, so I wondered if there was a reason.

  • Is there a way to dump a database? Either in lua or via the CLI

  • Assuming there's a way to dump the db in lua or the cli (and if in lua, presumably I'd write a custom function to dump the database), will that lock anything? I.e. will traversing the db cause twGet/twAdd to block until the traversal is done?

  • If I'm collecting a number of stats, is it better to use fewer databases (with correspondingly larger # of entries), or more databases (more sharded)? Just wondering about local contention.

  • Is the RBL lookup a blocking function? That is, should I use them sparingly or be liberal with them? My RBLs will be served local to the box via unbound, so relatively quick.

  • Is there any way to do non-blocking calls to redis, a la https://github.com/openresty/lua-resty-redis ? I'd love to be able to track whitelists there, but not if calling out to redis is a blocking operation and ties down an entire thread.

  • I want to be able to set some masks on lt.remote addresses (but not set the whole database to use that mask, though I'm blanking on what that directive is). Is there a function to apply a netmask to a copy of lt.remote, or do I just need to do the usual conversion to Int and bitshift and convert back? The context is that I was to be able to add a stat to the db for both the full lt.remote IP as well as the /24 version of lt.remote

Sorry for so many questions. Thanks!

curl check does not fail but curl is still used

checking for curl-config... no
checking whether libcurl is usable... no

leads to

  CXX      wforce.o
In file included from wforce.hh:35:0,
                 from wforce.cc:24:
webhook.hh:25:23: fatal error: curl/curl.h: No such file or directory
compilation terminated.

Add GeoIPv2 traits to attributes exposed by interface

With GeoIPv2, a new attribute 'traits' is exposed, which includes is_anonymous_proxy which is, clearly, very useful for certain weakforced rules.
Can we have the 'is_anonymous_proxy' trait exposed by the weakforced as part of the loginrecord structure? The is_in_european_union flag might be useful to some people as well. Ideally, all attributes would be accessible from the loginrecord structure.

e.g.

$ mmdblookup --file /usr/share/GeoIP/GeoIP2-City.mmdb --ip 185.121.168.254
  {
    "registered_country": 
      {
        "geoname_id": 
          3202326 <uint32>
        "is_in_european_union": 
          true <boolean>
        "iso_code": 
          "HR" <utf8_string>
        "names": 
          {
               ...
          }
      }
    "traits": 
      {
        "is_anonymous_proxy": 
          true <boolean>
      }
  }

Restrict Lua includes to a whitelisted set of directories

  • Issue type: Security Feature request

Short description

Currently Lua can include files from anywhere in the filesystem, which is a possible attack vector e.g. Lua reading an auto-generated file in /tmp (although since we set private tmp that particular attack is actually not feasible).
This requests that we use the system ReadWritePaths=, ReadOnlyPaths=, InaccessiblePaths= to protect pdns from these kinds of attacks. This should work so that we whitelist specific directories and all other directories cannot be "seen" by the process (assuming the above systems.exec commands can enable this).
This doesn't stop an admin creating a world-writable directory underneath the whitelisted directories, but as lieter says, you can't guard against that kind of stupid. It does however stop admins from including Lua files from "anywhere" in the filesystem,.

Add stats counters for return codes and errors (Feature request)

It would be very useful for the existing stats call to also provide counters for the return codes, not just for the function calls.

IE, as well as counts on number of allow() and report() calls made, also counts on the number of allow() calls split by return code 0, -1, or >0.

Even better would be to have a generic count, so in the Lua code you could use something like
addstat(key) followed by increment(key) in an allow or report function, and then the normal stats() call would also return "counter.key: 1" or similar. I know we could implement this in lua using a database, but it would be slow.

Counters like this could then be fed into your favourite monitoring package (MRTG, Zabbix, Nagio, Cacti...) to obtain graphs of weakforced performance and behaviour.

wforce daemon cannot parse chunked request

Weakforced fails with following HTTP request:

POST /?command=allow HTTP/1.1
Host: localhost:8084
Connection: close
Content-Type: application/json
Authorization: Basic d2ZvcmNlOnN1cGVy
Content-Transfer-Encoding: chunked

0037
{"login":"cmouse","pwhash":"1234","remote":"127.0.0.1"}
0

I'd expect this to be parsed and processed.

Add allow/report/reset etc. stats to the 5 min stats logging

Currently:
2017-12-14T14:37:53.571193+00:00 dovauth-ch2g-03o wforce[28622]: stats last 300 secs: WTW_0_1=20194 WTW_1_10=1 WTW_10_100=0 WTW_100_1000=0 WTW_Slow=0 WTR_0_1=20175 WTR_1_10=19 WTR_10_100=0 WTR_100_1000=0 WTR_Slow=0

Proposed - something like:
2017-12-14T14:37:53.571193+00:00 dovauth-ch2g-03o wforce[28622]: stats last 300 secs: WTW_0_1=20194 WTW_1_10=1 WTW_10_100=0 WTW_100_1000=0 WTW_Slow=0 WTR_0_1=20175 WTR_1_10=19 WTR_10_100=0 WTR_100_1000=0 WTR_Slow=0 allow=30293 report=29283 reset=2345

Need to think about whether custom endpoints should be included.

expose generic redis API into wforce.conf

Use case

redis DB with a Set of allowed countries per login. e.g.:

redis-cli:

sadd darix DE

from the wforce.conf i could do something like: (pseudo code)

cur_ct = lookupCountry(lt.remote)
if (redis-call('EXISTS ' .. lt.login) == 1) then
   if (redis-call("SISMEMBER " .. lt.login .. " " .. cur_ct) == 0) then
     -- list present but the current country is not in the list
     return -1, "country blocked", "country blocked", { remoteCountry=cur_ct }
  end
else
-- case for all/no countries (depending on how you interpret no list)
end

Or would you prefer people use e.g. gh:nrk/redis-lua

resetEntity() command to reset time window stats for a particular key

New resetEntity() command in addition to existing allow() and report() commands over the web interface.

Should allow reset of tine window stats related to a particular key: username or IP address.

For example:

...?cmd=resetEntity/

{ "ip" = "10.3.4.5" }
or
{"ip" = "::1" }
or
{ "username" = "neilcook" }

The result should be that all entries of the supplied type with the supplied value as a key are deleted.

Make console command returns consistent

Currently the console commands are somewhat inconsistent. Some will return different strings for success/error, others will return nothing at all for either case, while errors appear in the logs. In future this should be consistent, and errors should not be restricted to the logs, but visible to the user (or application) that invoked the command.

getDBStats() returning wrong status

wforce v2.0.0-8

The getDBStats() call appears to always return a blacklisted status of true, regardless of the actual fact.

The bl_reason and bl_expiry return empty strings when the item is in reality not listed; possibly these should be absent?

$ curl -s -X GET -H Authorization: Basic XXXX -H Content-Type: application/json http://localhost:8084/?command=getBL
{  "bl_entries": [ { "expiration": "2019-Feb-20 05:23:45", "ip": "1.1.1.1/32", "reason": "Manual" }]}

These items are not in the blacklist but return the wrong value:

$ curl -s -X POST -H Authorization: Basic XXXX -H Content-Type: application/json --data {"login":"[email protected]"} http://localhost:8084/?command=getDBStats
{"bl_expire": "", "bl_reason": "", "blacklisted": true, "login": "[email protected]", "stats": {}}

$ curl -s -X POST -H Authorization: Basic XXXX -H Content-Type: application/json --data {"ip":"1.2.3.4"} http://localhost:8084/?command=getDBStats
{"bl_expire": "", "bl_reason": "", "blacklisted": true, "ip": "1.2.3.4", "stats": {}}

This item is in the blacklist and returns correctly:

$ curl -s -X POST -H Authorization: Basic XXXX -H Content-Type: application/json --data {"ip":"1.1.1.1"} http://localhost:8084/?command=getDBStats
{"bl_expire": "2019-Feb-20 05:23:45", "bl_reason": "Manual", "blacklisted": true, "ip": "1.1.1.1", "stats": {}}

Add prometheus metrics

All of the stats which are currently logged could also be available via prometheus, on the same web server that currently provides the REST API (e.g. on a different endpoint).

Migrate to Python 3

Currently the report api and the regressions tests use Python 2. This will be out of support by 2020.

Move all python to python 3.

Add support for ipcipher for logged IP addresses

ipcipher is described here:
https://powerdns.org/ipcipher/

This feature would be a global configuration option that causes all IP addresses (v4 and v6) to be encrypted using ipcipher before logging. Note that this would be for all "built-in" logging, and 'default' policy logging, but would not prevent people from using the custom logging functions to log unencrypted IP addresses. The encrypted IP address should be easily available for all functions, and/or a Lua function to encrypt the IP address.

A tool should be provided to enable the IP addresses in the (built-in) logs to be decrypted.

Debugging Lua scripts

When my runtime Lua is bad (and it often is), I get an error like:

Exception thrown by a callback function called by Lua

There's no identifying information about where the error occurred. I don't know if it's possible for wforce to capture (or if wforce even has visibility into the underlying exception), but if it is, it'd be extremely helpful if wforce reported the traceback for that exception (or even just the offending line). Or is there an option to enable that somewhere that I've missed?

Q: what is the average latency in a wforced cluster?

I have a question rather than a bug.
In a 3-node wforced cluster, if a report call causes an RRD item to be updated, how long (on average) would it take until this is replicated to all available siblings?
I am getting some unexpected behaviour in updates and need to rule this out.

Suggestion for twReset

There are times when I'd like to reset a key but only for a certain field name. For that case, I'm currently doing a db:twSub( .. , twGet( ... ) ) (i.e. subtracting the current value to get back to 0. Might be useful to have a second arg to twReset that takes the field name to reset.

make IPV6_PKTINFO available on osx

Commit 948216e makes sure we only try to use IPV6_PKTINFO when it is available, fixing compilation on OSX. However, that file was taken from PowerDNS which compiles fine on OSX without the ifdefs! It turns out one of the compiler flags that the pdns configure sets, makes the define available. We should port that part of configure to weakforced and dnsdist. We should also port the ifdefs to pdns.

Add ability to create "custom" REST API commands and hooks

Currently the main reporting commands are "report" and "allow", both of which are based on the LoginTuple structure and are based on the concept of reporting and allowing/rejecting login attempts. This is fine, but it would be good to be able to report on other types of actions, e.g. mail submissions. While we could add new commands to handle mail submissions, it might be better to allow "custom" commands, which allowed arbitrary data to be sent and returned via json key-value pairs. These could be configured from wforce.conf.

For example:

function reportMailSubmit(attrs)
if (attrs.expectedAttribute == "...")
then
...
end
return { return_value, message_for_client, log_message, return_attrs }
end

addCustomCommand("reportMailSubmit", reportMailSubmit)

Replication Forwarders

Currently replication is expected to be a full mesh network, i.e. we accept replication events from all configured siblings and also send replication events to all configured siblings. This does mean that as the number of servers increases, each server has to send every replication event to an ever increasing number of siblings. In multi-site environments, each server in each site has to know about and send events to every server in every site.

Thus it would also be useful (e.g. multi-site environment) to have the ability to configure a server that received replication events from all configured siblings, but only sent them on to a specific (separate) list of servers. This would allow point-to-point links for replication traffic between sites for example.

There are some challenges with this approach - e.g. how to handle redundancy. For example if I send all replication events from Site A to a server at Site B, which then forwarded those events to a single server at Site B, what happens if that server goes down. If the forwarder sends to multiple servers at Site B, how to prevent multiple replication events from being sent to the wforce servers in Site B.

Due to the above, the existing UDP transport for replication may not be the best transport for this problem. If we used HTTP for example, we could make use of load-balancing/HA solutions such as HAProxy.

Update json11

Please sync json11 to latest upstream version. The current one fails with clang 5.0 with

json11.cpp:153:24: error: invalid operands to binary expression ('nullptr_t' and 'nullptr_t')
        return m_value < static_cast<const Value<tag, T> *>(other)->m_value;
               ~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
json11.cpp:209:5: note: in instantiation of member function 'json11::Value<json11::Json::Type::NUL, nullptr_t>::less' requested here
    JsonNull() : Value(nullptr) {}
    ^
1 error generated.

Debian Files

  • The debian/control references a package called libprotobuf-def, but the package is actually libprotobuf-dev
  • The debian/postinst file is a little intrusive. It really shouldn't be updating the existing wforce.conf file unless it's creating it (IMO).
  • I updated my debian/postinst's definition of where to put wforce.conf to make it inline with other places that wforce seems to look for wforce.conf: WFORCECONF=/etc/wforce/wforce.conf
  • The version in debian/changelog is 0.9 and there's an issue with a newline in it which causes build scripts to abort:
    dpkg-buildpackage: warning: debian/changelog(l2): found end of file where expected first heading
  • The debian/changelog file's says 'wforced', where debian/control has 'wforce'. For me at least, I have to fix the name in debian/changelog to 'wforce' to get packaging to work.

Add second class of service for no-blacklisting

In an ISP environment, features are best architected with a view towards minimal support desk load.

Log inspection has shown that a significant percentage of policy hits are from subscriber IPs. Outright blocking those (either via IP, or login+ip) is problematic.

When the 1hour policy rules are set to tarpit, and the 24hour rules set to blacklist (via custom lua to update the reject counts based on tarpit hits), it's advantageous to have a second 'list of IPs' to consult, and to bypass the 24hour rules if a match is found.

Package report_api as a deployable webapp

Currently the report_api app is included as a non-production-ready web app.

It should be packaged separately as a deployable web app ready for production use.

Preferably by someone who knows how to do this (i.e. not @neilcook!)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.