The Icinga configuration format was designed to be easy to use for novice users while at the same time providing more advanced features to expert users. At first glance it might look like a simple key-value configuration format:

However, it features quite a bit of functionality that elevates it to the level of scripting languages: variables, functions, control flow (if, for, while) and a whole lot more.

Icinga’s scripting language is used in several places:

configuration files

API filter expressions

auto-generated files used for keeping track of modified attributes

In this post I’d like to show how some of the config machinery works internally.

The vast majority of the config compiler is contained in the lib/config directory. It weighs in at about 4.5% of the entire code base (3059 out of 68851 lines of code as of July 2018). The compiler is made up of three major parts:

Lexer

The lexer (lib/config/config_lexer.ll, based on flex) takes the configuration source code in text form and breaks it up into tokens. In doing so the lexer recognizes all the basic building blocks that make up the language:

However, it has no understanding of how these tokens fit together syntactically. For that it forwards them to the parser. All in all the lexer is actually quite boring.

Parser/AST

The parser (lib/config/config_parser.yy, based on GNU Bison) takes the tokens from the lexer and tries to figure out whether they represent a valid program. In order to do so it has production rules which define the language’s syntax. As an example here’s one of those rules for “for” loops:

Here’s a list of some of the terms used in the production rule example:

Symbol

Description

T_FOR

Literal text “for”.

identifier

A valid identifier

T_FOLLOWS

Literal text “=>”.

T_IN

Literal text “in”.

BeginFlowControlBlock, EndFlowControlBlock

These functions enable the use of certain flow control statements which would otherwise not be allowed in code blocks. In this case the “continue” and “break” keywords can be used in the loop’s body.

rterm_scope_require_side_effect

A code block for which Icinga can’t prove that the last statement doesn’t modify the program state.
An example for a side-effect-free code block would be { 3 } because Icinga can prove that executing its last statement has no effect.

After matching the lexer tokens against its production rules the parser continues by constructing an abstract syntax tree (AST). The AST is an executable representation of the script’s code. Each node in the tree corresponds to an operation which Icinga can perform (e.g. “multiply two numbers”, “create an object”, “retrieve a variable’s value”). Here’s an example of an AST for the expression “2 + 3 * 5”:

Note how the parser supports operator precedence by placing the AddExpression AST node on top of the MultiplyExpression node.

Icinga’s AST supports a total of 52 different AST node types. Here are some of the more interesting ones:

Spawns the script debugger console if Icinga is started with the -X command-line option.

ImportExpression

Corresponds to the import keyword which can be used to import another object or template.

LogicalOrExpression

Implements the || operator. This is one of the AST node types which don’t necessarily evaluate all of its interior AST nodes, e.g. for true || func() the function call never happens.

SetExpression

This is essentially the = operator in its various forms, e.g. host_name = "localhost"

On their own the AST nodes just describe the semantical structure of the script. They don’t actually do anything in terms of performing any real actions.

VM

Icinga contains a virtual machine (in the language sense) for executing AST expressions (mostly lib/config/expression.cpp and lib/config/vmops.hpp). Given a reference to an AST node – such as root node from the example in the previous section – it attempts to evaluate that node. What that means exactly depends on the kind of AST node:

The LiteralExpression class represents bare values (e.g. strings, numbers and boolean literals). Evaluating a LiteralExpression AST node merely yields the value that is stored inside of that node, i.e. no calculation of any kind takes place. On the other hand, the AddExpression and MultiplyExpression AST nodes each have references to two other AST nodes. These are the operands which are used when asking an AddExpression or MultiplyExpression AST node for “their” value.

Some AST nodes require additional context in order to run. For example a script function needs a way to access its arguments. The VM provides these – and a way to store local variables for the duration of the script’s execution – through an instance of the StackFrame (lib/base/scriptframe.hpp) class.

Future Considerations

All in all the Icinga scripting language is actually fairly simple – at least when compared to other more sophisticated scripting engines like V8. In particular Icinga does not implement any kind of optimization.
A first step would be to get rid of the AST and implement a bytecode interpreter. This would most likely result in a significant performance boost – because it allows us to use the CPU cache much more effectively than with thousands, if not hundreds of thousands AST nodes scattered around the address space. It would also decrease memory usage both directly and indirectly by avoiding memory fragmentation.
However, for now the config compiler seems to be doing its job just fine and is probably one of the most solid parts of the Icinga codebase.

Most system administrators know how to use key-based authentication with SSH. Some of the more obvious benefits include agent forwarding (i.e. being able to use your SSH key on a remote system) and not having to remember passwords. There are, however, a few issues with having your SSH key on a general-purpose computer: Malware can obtain an unencrypted copy of your private SSH key fairly easily. Also, while migrating your key to another system is fairly easy it’s virtually impossible to securely use your SSH key on another untrusted system (e.g. at a customer).
This is where smart cards come in. A smart card stores certificates (such as your SSH key) and provides functionality for operating on those certificates (e.g. using their private key to sign or decrypt data). Smart cards come in various form factors: credit cards, SIM cards, etc. – which commonly require a separate card reader in order to be usable. However, there are also USB devices which implement all the usual smart card features in addition to other security features (e.g. requiring the user to press a key on the device before an authentication request is signed).
One such device is the Yubikey 4 which I’m personally using for SSH authentication.
The first step towards using a new Yubikey for SSH authentication is enabling the OpenPGP applet on it:

$ ykpersonalize -m82

I already had a PGP key, however in order to use it for authentication I had to create an additional subkey for the key usage type “authentication”. Here’s how that can be done:

The Yubikey 4 has three key slots which can be used for storing RSA keys with up to 4096 bits each. This might be an excellent opportunity to also move your signing and encryption key to your smart card – assuming you have an encrypted backup somewhere in case you lose access to your Yubikey.
The last step involves replacing ssh-agent with gpg-agent. This allows your SSH client to use your PGP certificates (including the authentication subkey we just created). In addition to that gpg-agent also supports regular SSH keys which might be useful if you have more than one SSH key and only plan to migrate one of them to your Yubikey:
I had to add the following snippet to my .profile file to start gpg-agent instead of ssh-agent:

As the name suggests Todoist is an app for tracking todos. I’ve been using it for the past couple of months during which it has become a daily companion in my quest for getting things done without forgetting half of my stuff (which – if you know me – is a common occurrence).
The basic feature set is quite straight-forward: The app lets you create tasks which by default end up in the “Inbox”. Todoist has native apps for macOS, Windows, iOS and Android. There’s also a web client in case you need to update your tasks on-the-go. For me personally the integration with Amazon Echo is particularly useful. Adding a new task is as simple as saying “Alexa, add a task…”.
Once you’ve created a task you can decide to assign your tasks to a project (e.g. “Work”, “Personal”, etc.) either right away or later on when you have a few minutes to spare. Each task can also be tagged to make it easier to find groups of specific tasks. For example I have two tags “LowEnergy” and “HighEnergy” so I can later on find all tasks which are either easy or hard to do. Tasks can be set to re-occur at specific intervals which range in complexity from “daily” to “every last friday”.
The mobile app supports location-based reminders. I have a recurring task “Get cash from the ATM” which the app dutifully reminds me about when I pass the ATM on my way to work.
I wouldn’t go so far as to put Todoist in the “life saver” category, however it has definitely become an integral part of my daily workflow. Consider giving it a try… even though unfortunately it isn’t entirely free.

Part of my “arriving at the office in the morning” ritual involves quitting all my personal applications (e.g. Sonos), re-enabling my work e-mail account and a whole slew of other tiny changes that differentiate my work environment from my home environment. While in itself this isn’t too much of a hassle it does get rather tedious after a while. Especially so if I forget to start certain apps like my Jabber client and don’t realize that until much later.
The ControlPlane application promises to solve this exact problem by running specific actions whenever it detects a location change.
In order to do this you first have to set up “contexts”: These are essentially the locations you want ControlPlane to be aware of. As a starting point I’ve created two contexts “Home” and “Work” for my most-frequently used locations:
The next step involves telling ControlPlane what kind of information it should use to determine where you are. ControlPlane supports a wide variety of so-called evidence sources for this, some of which include:

IP address range, nearby WiFi networks

Attached devices (screens, USB and bluetooth devices)

Bonjour services (e.g. AppleTV)

Once you’ve made up your mind about which evidence sources to use you need to actually configure rules for these sources. An example would be “If my laptop can see the WiFi network ‘netways’ I’m in the ‘Work’ environment.” You also get to choose a confidence rating for each of those rules. This is useful if some of your rules could potentially also match in other, unrelated environments – for example because the IP address range you’re using at work is also commonly used by other companies.
Once you’re sufficiently confident that your location detection rules are working reliably you can set up actions which ControlPlane automatically performs whenever you enter or leave a certain location:
For my personal use I’ve found the built-in library of actions to be quite useful. However, there are a few things that even ControlPlane doesn’t support out of the box – like disabling specific e-mail and Jabber accounts. Luckily it does let you can run arbitrary external applications, including ones you’ve built with macOS’s Script Editor application: