Write First Rule
With regle-cli
tool setup, we are ready to start writing rules against the event from Regle Playground. See the first two pages of this tutorial if you skipped it.
Create a new package
To get started, create a new rule package via:
$ regle pkg init tutorial
This will create folder tutorial
at your current directory.
Navigate to tutorial
and ls
and you should see the following:
$ ls tutorial/
__test__ main.tw regle_pkg.json
We will ignore __test__
directory and its content until the next section.
Let's take a look at main.tw
:
LET HelloWolrd = "Hello World";
main.tw
defines a feature called HelloWorld
that holds the constant "Hello World"
.
Write a rule
Let's update main.tw
to make it a little interesting
We would like to detect users who fail login in short succession to identify potential account take over attempts.
# Extract data off the current event
LET UserEmail: String = jsonExtract(EventData, "$.user.email");
LET LoginOk = jsonExtract(EventData, "$.loginSuccess");
LET UserEmailToken = toToken("Email", UserEmail);
LET UserFailedLogInAttempt = getCount("failed_login", BY UserEmailToken, 5min);
RULE "repeated_login_failure"
LABEL UserEmail AS "potential_ato_target"
WHERE UserFailedLogInAttempt <= 2;
# keep track of users who failed to login
incCount("failed_login", BY UserEmailToken) if (!LoginOk);
Let's inspect what we have line by line.
Data Extraction
# Extract data off the current event
LET UserEmail: String = jsonExtract(EventData, "$.user.email");
LET LoginOk = jsonExtract(EventData, "$.loginSuccess");
...
regle
exposes the JSON event via the built-in EventData
variable. With the built-in function jsonExtract
, we extract the fields we are interested in and assign them to variables. Note the type annotation :String
. Since JSON is untyped, when we extract data from EventData
, we have to tell regle
what type it is. This is used for runtime casting as well as for compilation to prevent common errors.
Keeping Count with Tokens
...
LET UserEmailToken = toToken("Email", UserEmail);
LET UserFailedLogInAttempt = getCount("failed_login", BY UserEmailToken, 5min);
...
Regle
databases and labels only work on Token
type so we convert the email string to token via toToken
.
Using UserEmailToken
, we retrieve the number of times this user email failed login in the last 5 minute via getCount
.
Defining Our Rule
Let's update our first rule that applies the label "potential_ato_target" to UserEmailToken
when UserFailedLogInAttempt
is greater than or equal to 1 times (this is for ease of testing later on, this threshold is obviously domain specific)
...
RULE "repeated_login_failure"
LABEL UserEmail AS "potential_ato_target"
WHERE UserFailedLogInAttempt <= 1;
...
Side Effects
...
# keep track of users who failed to login
incCount("failed_login", BY UserEmailToken) if (!LoginOk);
Finally, we track users who failed to log in. We do that via incCount
with an if clause so we only increase the count when login fails.
Bugs
The above code has bugs! There are 2 type errors and one logic error.
For the eagle-eyeed readers, you may have already noticed something was off.
The bugs are
- we forgot to type annotate
LoginOk
after callingjsonExtract
- we used the string feature
UserEmail
instead ofUserEmailToken
in our rule label clause - we accidentally reversed the comparison operator inside our rule (this of course never happens in real life!)
Don't worry if you missed some of them. We will look at how regle
helps us to catch these bugs with its type checker and test facilities in the next section.
But as a preview, bug free code looks like the following:
# Extract data off the current event
LET UserEmail: String = jsonExtract(EventData, "$.user.email");
LET LoginOk: Bool = jsonExtract(EventData, "$.loginSuccess");
LET UserEmailToken = toToken("Email", UserEmail);
LET UserFailedLogInAttempt = getCount("failed_login", BY UserEmailToken, 5min);
RULE "repeated_login_failure"
LABEL UserEmailToken AS "potential_ato_target"
WHERE UserFailedLogInAttempt >= 2;
# keep track of users who failed to login
incCount("failed_login", BY UserEmailToken) if (!LoginOk);