Skip to main content

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:

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.

main.tw
# 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

main.tw
# 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

main.tw
...
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)

main.tw
...
RULE "repeated_login_failure"
LABEL UserEmail AS "potential_ato_target"
WHERE UserFailedLogInAttempt <= 1;
...

Side Effects

main.tw
...
# 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 calling jsonExtract
  • we used the string feature UserEmail instead of UserEmailToken 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:

main.tw
# 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);