Skip to content

Domain Access Control

German Vekhorev edited this page Jul 7, 2021 · 14 revisions

About

Keiko Domain Access Control (DAC) actively limits plugins' run-time permissions by preventing them from executing actions that they are not allowed to. It is capable of both logging almost all plugins' activity, and prevent suspicious operations from happening in the first place — before your server is damaged.

Rules, and what are they eaten with

Keiko can't know for sure which actions you consider "safe" and which you consider "malicious". The rules system helps it determine that. Each action has an own section in the RuntimeProtect configuration (runtimeprotect.yml), with its own set of rules.

Notifications (logging)

Each operation has a log option, which specifies the level of logging that Keiko should use when a plugin performs this operation. For example, if you set log: info in settings for operation file_read, Keiko will print an "info" message whenever a plugin reads a file, as shown in the second picture at the top of this page. In these notifications, you can see:

  • which operations was performed (there are both system operations (such as file read and connection open) and minecraft operations (such as player op and player de-op));
  • name of the plugin that performed this operation;
  • fully qualified name of the class that performed this operation (this class is inside the plugin whose name is reported);
  • name of the method that performed this operation (this method is inside the class whose fully qualified name is reported).

Identity- and argument-specific rules

Such rules are only effective for certain identities performing the action with certain arguments. By "identity" we mean, generally speaking, code source, which can be one of:

  • PLUGIN — Name of plugin (as specified in its plugin.yml) that invoked this action.
  • SOURCE — Fully qualified class name (with package) and method name that invoked this action.
  • ALL — Used to create an argument-specific rule for all plugins and sources (no identity filtering).

Run-time identities specification is similar to the static inspections exclusions syntax

The basic rule syntax effective for all types of actions is:

<"ALLOW"/"DENY"> <identity> <argument-1> [argument-2] [...argument-n]

where <identity> should be something that identifies/filters code source, e.g.:

  • ALL — to accept calls from all sources (no filtering);
  • PLUGIN=PluginName — to only accept calls from plugin with name PluginName;
  • SOURCE=fully.qualified.ClassName — to only accept calls from class with name ClassName located in package fully.qualified;
  • SOURCE=fully.qualified.ClassName#methodName — to only accept calls from method with name methodName of class with name ClassName located in package fully.qualified.

NOTE #1: class constructors are also methods. Their name is <init>. Constructors are called whenever a new instance of a class (object) is created at runtime. Any class has a constructor. Constructors "without" additional code (that is explicitly written by programmer) are called default. For example, when the program creates a new instance of, say, class AccountsManager located in package xyz.test, it invokes method xyz.test.AccountsManager#<init>.

NOTE #2: static blocks are methods as well. Their name is <clinit>. Code in static blocks is normally called only once per class — upon its load by ClassLoader, usually upon the first access of other code to this class.

You may have noticed that only one argument is marked as "required" in the above-stated syntax. That is not really correct. Actually, this means that there will always be at least one argument in all actions' rules configuration (just because Keiko's rules parser works that way), and the actual number of arguments is totally action-dependent.

Make sure to look at configuration comments to each action in runtimeprotect.yml for a more accurate syntax specific to the action you want to edit rules for.

Actions whose rules take no arguments (e.g. properties_access, socket_factory_set, etc.) must be given * (an asterisk) as an argument. Just because.

Bottom-most rules have higher priority. This means that if rule ALLOW ALL * is below rule DENY ALL *, then Keiko will allow all plugins to perform this operation on all arguments. That is, the above operation will be overwritten. This may be used to define the "default" rules (for plugins that have no explicit rules for them below in the rules list), as shown in the example below.

Configuration example

Let's take a look at the list of rules for action file_read and explain everything in detail:

     # File read control.
     #
     # Rules syntax:
     #
     #     <"ALLOW"/"DENY"> <identity> <file name>
     #
     # Examples:
     #
     #     # Allow all plugins to create any plugins in their plugins/ folder:
     #     - ALLOW ALL {plugins_folder}/{plugin_name}
     #
     #     # Forbid plugin "NotMalware4sure" to write in server's "server.properties" file:
     #     - DENY PLUGIN=NotMalware4sure {server_folder}/server.properties
1.   file_read:
2.     log: off
3.     rules:
4.       - DENY ALL *
5.       - ALLOW ALL {plugins_folder}/{plugin_name}
6.       - ALLOW ALL {plugins_folder}/{plugin_name}/*
7.       - ALLOW ALL {plugin_jar_path}
8.       - ALLOW ALL {plugins_folder}/bStats/config.yml
9.       - ALLOW ALL {plugins_folder}/PluginMetrics/config.yml
10.      - ALLOW ALL {java_folder}/*
11.      - ALLOW ALL /tmp
12.      - ALLOW SOURCE=com.comphenix.protocol.ProtocolLib#checkConflictingVersions {plugins_folder}
13.      - ALLOW SOURCE=com.comphenix.protocol.ProtocolLib#checkConflictingVersions {plugins_folder}/*

In line 2 we tell Keiko that we do not want to see console messages like Plugin XXX has just read file YYY.

line 3: begin our rules list for operation file_read.

line 4: prevent all plugins from reading any files. Plugins/files that will not match any of the filters below this rule will get their file read calls suspended. But plugins/files that will match an ALLOW filter below will overwrite the rule in this line (#4), and will be allowed to perform the file read operation.

line 5: allow all plugins to read contents of their own folder in plugins/ (e.g. for plugin Reflex that's plugins/Reflex.

line 6: allow all plugins to read all files in their own folder in plugins/ (see above for details).

line 7: allow all plugins to read their own JAR file (needed by plugins to extract files inside their JARs such as config.yml and other).

lines 8-9: allow all plugins to read configuration files of bStats and PluginMetrics — used for statistics.

line 10: allow all plugins to read files of the local JRE (Java Runtime Environment) installation — needed by plugins that interact with security in any way, specifically, for encryption (Java Cryptography Extension).

line 11: allow all plugins to read contents of the machine's temporary folder (appears to be used somewhere deep, internally).

lines 12-13: allow method checkConflictingVersions of class ProtocolLib in package com.comphenix.protocol to read contents of the server's plugins/ folder and everything inside. Used by ProtocolLib to find incompatible plugins' versions.

Placeholders, variables, simple regular expressions (RegEx)

As you may have noticed, there are several "strange" "magic" words/symbols used in DAC rules. Here is a complete list of all of them:

Placeholder Description
{java_folder} Denotes your JRE (Java Runtime Environment) installation folder (e.g. /opt/jvm/jdk1.8.0_251/jre).
{server_folder} Denotes your Minecraft server folder (e.g. /home/john/minecraft/skywars-server-1).
{plugins_folder} Denotes your Minecraft server's plugins/ folder (e.g. /home/john/minecraft/skywars-server-1/plugins).
{plugin_name} Denotes name of the plugin that invoked this action (as specified in its plugin.yml).
{plugin_jar_path} Denotes absolute path to JAR file of the plugin that invoked this action (e.g. /home/john/minecraft/skywars-server-1/plugins/CoolChatFilter.jar).
* (wildcard/asterisk) [Simple RegEx] Implies any character sequence, including empty sequence. Can be literally whatever string of any length, including no string (emptiness).
? (question mark) [Simple RegEx] Implies at least zero and at most one character.
+ (plus sign) [Simple RegEx] Implies at least one character (like *, but does not match empty sequences).

Ordinary regular expressions are also supported! This means that you can also use stuff like [a-zA-Z]{2,5} to match from 2 to 5 Latin alphabet characters (both lower- and upper-case), and more! Google a bit to learn more about RegEx.

It may sometimes be needed to match symbols */? as is, i.e. do not treat them as wildcards. You can do so by adding prefix NO_WILDCARDS :: (with a trailing space!):

Wildcards:

command_execution:
...
rules:
  - "ALLOW ALL rm -rf plugins/*"
  # This will allow all plugins to execute commands like "rm -rf plugins/", 
  # "rm -rf plugins/j", "rm -rf plugins/WQOIEOsajfd-qw214.jar", etc.

No wildcards:

command_execution:
...
rules:
  - "ALLOW ALL NO_WILDCARDS :: rm -rf plugins/*"
  # This will allow all plugins to execute exactly one command:
  # "rm -rf plugins/*". Commands like "rm -rf plugins/sdf" will NOT work because
  # the asterisk * symbol here is not a wildcard - it is a regular symbol. Same for "?".

NOTE: the NO_WILDCARDS :: prefix also disables all other RegExs in the rule (e.g. [A-Z] and similar).