Skip to main content
Version: 2.7

ARMR Mod

An ARMR Mod is a single program for an ARMR Engine. An ARMR Mod is a mandatory declaration that
defines the atomic executable unit of one-or-more ARMR Rules. An ARMR Rule cannot exist outside of
an ARMR Mod.

It is possible to define multiple ARMR Mods in a single .armr file if necessary. The example shown
below is the complete declaration of an ARMR Mod named "Security Rules". The keyword app is
used to declare an ARMR Mod, which takes a string that is used to identify this ARMR Mod with a
unique name. The unique name is also used for all events recorded by the ARMR Engine in the event log
file. Two ARMR Mods with the same name cannot be loaded at the same time. An ARMR Mod must
implement the mandatory requires() statement and contain at least one ARMR Rule. An empty ARMR Mod with no rules, as shown in the ARMR Mod Example, is not valid.

Requires

The requires() statement provides details about what is required for the ARMR Mod to run. It must be declared before any rules, otherwise the Mod will fail to load and an error will be logged in the CEF log. For now, the requires() statement only takes a single key:value pair that declares the minimum required ARMR language level to be supported by the agent for the ARMR App to run. Future versions of ARMR will support further overloading with additional requirements.

Version

The version() directive allows an ARMR developer to declare precedence between multiple ARMR Mods of the same name. When an ARMR developer commits a newer version of a mod, any older versions of the same mod will be ignored by the agent when present in the same load cycle. The version declaration is optional, but if provided, it needs to be specified before any rule is declared. Its default value when not declared is 1. In the case of multiple ARMR mods with the same name and different versions, only the mod with the highest version is committed to the agent.

version declaration is only supported since ARMR/2.3, inclusive.

Consider the following example:

app("Security Policy"):
requires(version:"ARMR/2.3")
version(2)
// some ARMR Rules
 
endapp

app("Security Policy"):
requires(version:"ARMR/2.3")
version(3)
// some ARMR Rules
 
endapp

In the above case, the ARMR mod with version 2 will be ignored and version 3 of the mod will be loaded instead. The following log message is generated for the mod with the lowest version:

in file "/tmp/example.armr": ARMR mod "Security Rules" overridden by mod with version "2"

In the case where two ARMR mods have the same version, then both mods will fail to load as they are in conflict about their name ID.

For every security event generated by a rule, the CEF extension appVersion indicates the version of its mod. See more details about CEF extensions later in the document.

Metadata

Since version 2.6 of the ARMR language, a metadata() directive can be declared within ARMR mods or rules. This declaration is useful when there is important or detailed information about the ARMR mod or rule that the developer might want to ship with it. Considering the case of an ARMR patch, the metadata could contain information about the vulnerability’s CVE or CWE, which software does it target, when was the patch created, what's the CVSS score of the vulnerability. All this data will then be parsed and modeled by the ARMR language and consumed by the Agents and the Portal, which can then interact with it programmatically through the ARMR API. This feature extends the bridge between ARMR mod developers and ARMR language consumers (systems that parse and interact with mods), making it possible for automatic ARMR mod integrations into these systems.

metadata declaration is only supported from ARMR/2.6, onwards.

The metadata statement is optional either at the ARMR mod or rule levels. In this section the focus will be at the mod level but this concept is similarly applied at the rule level. See Armr Rule section for how to declare it at the rule level.

Metadata declared at the mod level will be inherited by all rules within that mod.

The metadata() statement is declared as follows:

app("CVE-2021-2432"):
requires(version: ARMR/2.6)
metadata(
cve: "CVE-2021-2432",
cvss: {
score: 3.7,
version: 3.0,
vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"},
description: "The vulnerability allows a remote non-authenticated attacker to
perform service disruption. The vulnerability exists due to improper
input validation within the JNDI component in Java SE. A remote
non-authenticated attacker can exploit this vulnerability to perform
service disruption.")

patch("JNDI component fix"):
...
endpatch
endapp

It takes a comma separated list of key value pairs written in free-form text, as with any ARMR language component. Any of the ARMR primitive types can be used as values: string literals, constants, floats, integers, booleans, lists or nested key value pairs.

Duplicate keys within metadata will generate an error message resulting in the mod being invalidated.

There is a list of metadata keys that Waratek has chosen to standardize, and so, their values will be strictly validated when an ARMR mod is parsed. The following table lists these keys and the enforced structure of their corresponding values:

KeyDescriptionEnforced value structureARMR language example cases
cveCVE ID of the vulnerabilityAny non empty string literal or a list of non empty string literals
cve: "CVE-2020-4000"
cve: ["CVE-2020-4000", "CVE-2020-4001"]

| | cwe | CWE category of the vulnerability | Any non empty string literal or a list of non empty string literals |


cwe: "CWE-89"
cwe: ["CWE-89", "CWE-564"]

| | cvss | CVSS score of the vulnerability | A list comprised of the following key value pairs:- key score where its value must be a float

  • key version where its value must be a float

  • key vector where its value must be a string literal |


cvss: {score: 10.0, version: 3.1, vector: "..."}

| | description | Text describing what the vulnerability or the mod or rule | Single string literal |


description: "The vulnerability allows a remote non-authenticated
attacker to perform service disruption."

| | affected-os | Operating systems affected by the vulnerabilty | Any non empty string literal or a list of non empty string literals |


affected-os: "Windows"

affected-os: ["Windows", "Linux"]

| | affected-product-name | Name of the product affected by the vulnerability | Single string literal |

affected-product-name: "Struts 2"

| | affected-product-version | Version of the product affected by the vulnerability | Single string literal or a list of ranges. A range is comprised of a key range and a value comprised of 2 key value pairs: from and to. Multiple ranges can be defined. If a single string is specified a single range will be interpreted internally with the same value for from and to key value pairs.

range: {from: "1.0.0", to: "1.0.0"}

|


affected-product-version: "2.5.27"

affected-product-version: {range: {from: "2.5.20", to: "2.5.27"}}

affected-product-version: {
range: {from: "2.5.20", to: "2.5.27"},
range: {from: "2.6.0", to: "2.6.8"}}

| | creation-time | Time when the mod or rule was created | Single string literal |


creation-time: "Tue 02 Nov 2021 15:46:13 GMT"

| | version | Development version of the rule | Integer |


version: 2

|

None of the metadata keys above are mandatory, they can be added as required. Other keys not in the table above can be used by the ARMR developer. These are classified as non-standardized metadata and their values will not be validated in any way, so they can have any desired structure. The example below is valid:


app("2021 JULY CPU"):
requires(version: ARMR/2.6)
metadata(foo: "bar")
patch("JNDI component fix"):
...
endpatch
endapp


app("2021 JULY CPU"):
requires(version: ARMR/2.6)
metadata(complex: {foo: "bar"})
patch("JNDI component fix"):
...
endpatch
endapp

Logging metadata

The metadata key log inside the metadata statement is reserved for giving any extra functional meaning to the metadata declared within its value. As an example:


app("CVE-2021-2432"):
requires(version: ARMR/2.6)
metadata(
log: {
cve: "CVE-2021-2432"
},
cvss: {
score: 3.7,
version: 3.0,
vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"},
description: "The vulnerability allows a remote non-authenticated attacker to
perform service disruption. The vulnerability exists due to improper
input validation within the JNDI component in Java SE. A remote
non-authenticated attacker can exploit this vulnerability to perform
service disruption.")

patch("JNDI component fix"):
...
endpatch

endapp

In the above example log is not a metadata key but a keyword to tell ARMR consumers that this metadata should be logged in security events. As for the case of agents, a CEF extension will be logged in every event of every rule defined within the mod, since metadata is declared at the mod level:


CEF:0|ARMR:2021 JULY CPU|2021 JULY CPU|2.6|validate component|Load Rule|Low|rt=Feb 23 2022 17:39:02.844 +0000 appVersion=1 cve=["CVE-2021-2432"] dvchost=test-host ruleType=patch procid=324782 securityFeature=patch outcome=success

The ARMR developer has two ways for marking metadata as loggable:

  • Multiple log key value pairs, or

  • Single log key value pair containing all the metadata to be logged.


app("CVE-2021-2432"):
requires(version: ARMR/2.6)
metadata(
log: {
cve: "CVE-2021-2432",
description: "The vulnerability allows a remote non-authenticated attacker to
perform service disruption. The vulnerability exists due to improper
input validation within the JNDI component in Java SE. A remote
non-authenticated attacker can exploit this vulnerability to perform
service disruption."
})

patch("JNDI component fix"):
...
endpatch

endapp


app("CVE-2021-2432"):
requires(version: ARMR/2.6)
metadata(
log: {
cve: "CVE-2021-2432"
},
log: {
description: "The vulnerability allows a remote non-authenticated attacker to
perform service disruption. The vulnerability exists due to improper
input validation within the JNDI component in Java SE. A remote
non-authenticated attacker can exploit this vulnerability to perform
service disruption."
})

patch("JNDI component fix"):
...
endpatch

endapp

There is a set of security CEF extensions that are reserved to Waratek agents and cannot be logged as part of CEF events. These extensions are: agentName, ruleType, rt, dvchost, procid, nodeid, appVersion and securityFeature. If any of the keys above are used as metadata and marked for logging, the agent will log a one-time only CEF event indicating the error and the extension will not be logged in any of the events.

The list of reserved extensions may grow in the future as and when any new extensions are introduced by Waratek.

The same occurs in the case that metadata keys are marked for logging and duplicate any extension already present in a normal security event. The agent security logging data will always take precedence over metadata logged keys. A one-time only CEF event error will be logged in this case and the metadata key will be ignored from the CEF event.

ARMR Language Level

As shown in the example, this ARMR App requires a minimum ARMR language level of 2.0 to be supported by the ARMR agent. Both the version key and string value are required to be stated. The ARMR language level version is based on Semantic Versioning format of major.update. Incrementing the major value represents new functionality that is backwards incompatible with the previous release. Incrementing the update value means new functionality has been added that does not break backwards compatibility. In the case of an update, agents that are in the same major version range, but have a lower update value, will simply ignore new functionality. If either the version string is invalid or the version is unsupported, the app will fail to load and an error message will be printed to the CEF log file.

ARMR Mod Example

The following example shows a well formatted ARMR Mod, so long as at least one ARMR Rule is contained within the ARMR Mod. Please consult the ARMR Rule section for more information on available rule types.

|


app("Security Policy"):
requires(version:"ARMR/2.3")
// some ARMR Rules

endapp

|

In the example shown here, the requires() statement is missing.

|


app("Security Policy"):

endapp

|

The invalid ARMR Mod would generate an error messages in the security log file.

|


<unknown>: line 3: col 0: Invalid input: 'endapp' expecting: 'requires'

|