Running your own custom tests in Lynis

Running your own custom tests in Lynis

Although Lynis has many tests built-in, there are enough reasons to create your own custom tests. Instead of patching up existing files, there is a better way to run them and make use of existing functions.

Within this article we have a look on how to create your own tests and what functions can be used. With the software being open source and licensed under GPL, you have the flexibility to see existing tests and adjust them to your needs.

Creating tests

Displays that no custom tests were running in Lynis

No custom tests enabled in Lynis

Creating custom tests is easy, as the toolkit of Lynis is written in shell script. So there is a lot of examples available in the already existing tests. To make it even easier, there is a small template available, which is named tests_custom.template and located in the include directory.

First step is to copy this file and name it tests_custom.

# cd include
# cp tests_custom.template tests_custom

Within this template there is an example test defined.

    # Test        : CUST-0010
    # Description : Check for something interesting - template
    #               This test first checks if OpenSSL binary was found
    if [ ! -z "${OPENSSLBINARY}" ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi
    Register --test-no CUST-0010 --preqs-met ${PREQS_MET} --weight L --network NO --description "My description"
    # Or you could use this one without any dependencies
    # Register --test-no CUST-0010 --weight L --network NO --description "My description"
    if [ ${SKIPTEST} -eq 0 ]; then
        logtext "Test: checking something"
        ReportWarning ${TEST_NO} "M" "Test warning"
        if [ ${FOUND} -eq 0 ]; then
            Display --indent 4 --text "- Performing custom test 1" --result OK --color GREEN
            logtext "Result: the test looks great!"
            Display --indent 4 --text "- Performing custom test 1" --result WARNING --color RED
            logtext "Result: hmm bad result of this test :("
            ReportSuggestion ${TEST_NO} "This could be better!"


This test start with checking if the OPENSSLBINARY variable has been set (“! -z” means is it not zero or empty). If it is set, then the prerequisites of this particular test have been set and checking when using the Register function.

When no prerequisites apply, remove the parameter from the Register function.

Register the test

The Register function actually registers the related test and determines if there are any reasons to skip the test. If not, the variable SKIPTEST will be set to 0.

This function needs at least the test-no, weight, network and description.

With the –test-no we define an unique identifier for your test. Use always “CUST-” followed by a four digit number.

The –weight defines how important the test is, using L (low), M (medium) or H (high). When unsure, use M. By using the –network parameter, you can define if it needs network access, or not. Most tests won’t use any network function, so it is safe to use “NO” as default value.

Tips for tests

From here on your test can do everything which is allowed in shell scripting. Run commands and test configuration files.

We suggest to avoid “exotic” commands and use as much bourne scripting (not bourne again, or bash). The reason is portability and it avoids that you have to add additional checks. When using commands which are not on the system, it may result in unexpected outcomes or errors on screen.

Make sure to initialize any variables which you will reuse at a later stage. For example we use COUNT and FOUND a lot. We initialize those values to zero at the beginning of each test where it is used. The reason is to avoid a false positive in a test running at a later stage.

Testing your custom tests

When you are done with adding your tests, you can easily test them by only running the custom category.

./lynis -c -Q --tests-category "custom"

After running Lynis again, it should show your custom test(s) and the related result. For example after enabling the test in the template:

Shows that custom test was executed in Lynis output

Output of custom test in Lynis

Common functions

During the creation of your own tests, you can reuse existing functions. These will simplify the way data is logged to a log file, or displayed on screen.


The Display function shows text on screen.

It uses at least the indent and text parameters. Optionally are the result and related color of the result.

Usage: Display –indent <number of spaces> –text “Your personal text” –result “<status>” –color “<color code>”

The status usually is something like OK, Found, BAD, NONE, where color is one of the colors defined in the include/consts file.


This function will log a string to the defined log file.

Usage: logtext “This is a test”


Use this function to trigger the exception message at the end of the scan, when an unexpected result occurred and you did not catch it with a normal “if” statement. For example: if you expect a test to exit with exit code 0 or 1 and it actually gave back “2”, then trigger the exception. This helps in debugging and monitoring for unexpected results.

Usage: ReportException “${TEST_NO}:1″ “Unexpected result from running command ps”


With this function we can show a suggestion on screen (at the end).

Usage: ReportSuggestion “${TEST_NO}” “We suggest to install package X”


Similar to ReportSuggestion, except that it shows the message in the warning box. Also, it gives a weight (Low/Medium/High) to the finding.

Usage: ReportWarning “${TEST_NO” “M” “We discovered a medium scored finding”

Other functions

Of course there are more functions available. They are stored in include/functions, with a description. By searching for the related function in the existing tests, they provide many example on how to use the function.


Want to stay up-to-date, follow the project on GitHub: Lynis


OpenSCAP on CentOS 7 – Installing from source

OpenSCAP on CentOS 7

Installing from source

Security automation is hot and we love it. One way is using the OpenSCAP toolkit. Unfortunately it is not mature enough, so you might want to build and install it from source. We share our findings while creating our test environment.

Install required components

On our minimum installed CentOS 7 system, we need to install a few components. Most are related to compiling C++ and parsing XML files. Since we like to use Git, let’s start with that and obtain the source code of OpenSCAP:

mkdir /root/openscap-build && cd /root/openscap-build
yum install git
git clone
cd openscap/

Next is installing the related components to build the toolkit:

yum install gcc
yum install autoconf automake libtool
yum install libcurl-devel libxml2-devel libxslt-devel pcre-devel swig
yum install python-devel

Optional components

To support as much as possible, we want to install some additional components. They are not needed for everything, but depending on the system may be useful (e.g. RPM for CentOS).

yum install rpm-devel libselinux-devel systemd-devel GConf2-devel

We skip isaconf, as this is related to Solaris.

Build OpenSCAP from source

Time to build OpenSCAP from the source files:

make clean && ./ && ./configure && make

If everything went fine, it should end with leaving the directories and a successful compilation (something like this):

make[3]: Leaving directory `/root/openscap/openscap/swig/python2'
Making all in python3
make[3]: Entering directory `/root/openscap/openscap/swig/python3'
make[3]: Nothing to be done for `all'.
make[3]: Leaving directory `/root/openscap/openscap/swig/python3'
make[3]: Entering directory `/root/openscap/openscap/swig'
make[3]: Nothing to be done for `all-am'.
make[3]: Leaving directory `/root/openscap/openscap/swig'
make[2]: Leaving directory `/root/openscap/openscap/swig'
make[2]: Entering directory `/root/openscap/openscap'
make[2]: Leaving directory `/root/openscap/openscap'
make[1]: Leaving directory `/root/openscap/openscap'

So if the build was successful, we can optionally install the toolkit:

make install

In our case there are some builds between what the original CentOS 7 package provided and the newer compiled binary in /usr/local/bin:

[root@localhost openscap]# /bin/oscap -V | grep oscap
OpenSCAP command line tool (oscap) 1.0.3
[root@localhost openscap]# /usr/local/bin/oscap -V | grep oscap
OpenSCAP command line tool (oscap) 1.2.0

Happy auditing!



Yum plugins: Available plugins and built-in security support

Enhancing yum

Determine available plugins and built-in security support

To enhance the support in our auditing tool Lynis, we wanted to know if yum supports security related functions by using a plugin or having it as built-in functionality.


Yum, or Yellowdog Updater Modified, is a software management tool for Linux based systems. Usually it is used on systems running SuSE or Red Hat based (like RHEL, Fedora or CentOS). Plugins extend the functionality of yum, to improve its functionality.

One plugin may select the fastest software mirror, so you don’t have to benchmark them manually. Another great plugin helps with security and shows what security related updates are available. Nowadays, this functionality is built-in, as the demand for this functionality is huge.

In our case we want to audit the yum tool set and determine if we have the plugin available, or dealing with the built-in functions. Let’s start with the plugins..

Yum plugins

We can query the repository for packages which put files in the /usr/lib/yum-plugins directory. We have two options for that, using yum provides, or the repoquery utility.

yum provides "/usr/lib/yum-plugins/*"
 Loaded plugins: fastestmirror
 Loading mirror speeds from cached hostfile
 * base:
 * extras:
 * updates:
 PackageKit-yum-plugin-0.8.9-11.el7.centos.x86_64 : Tell PackageKit to check for updates when yum exits
 Repo        : base
 Matched from:
 Filename    : /usr/lib/yum-plugins/
 Filename    : /usr/lib/yum-plugins/refresh-packagekit.pyo
 Filename    : /usr/lib/yum-plugins/refresh-packagekit.pyc
kabi-yum-plugins-1.0-2.el7.centos.noarch : The CentOS Linux kernel ABI yum plugin
 Repo        : base
 Matched from:
 Filename    : /usr/lib/yum-plugins/
 Filename    : /usr/lib/yum-plugins/kabi.pyo
 Filename    : /usr/lib/yum-plugins/kabi.pyc
subscription-manager-1.10.14-7.el7.centos.x86_64 : Tools and libraries for subscription and repository management
 Repo        : base
 Matched from:
 Filename    : /usr/lib/yum-plugins/subscription-manager.pyc
 Filename    : /usr/lib/yum-plugins/subscription-manager.pyo
 Filename    : /usr/lib/yum-plugins/
 Filename    : /usr/lib/yum-plugins/product-id.pyc
 Filename    : /usr/lib/yum-plugins/
 Filename    : /usr/lib/yum-plugins/product-id.pyo

Besides the interesting file paths, it doesn’t give much more pointers at this moment. Lets try repoquery:

[root@localhost Lynis]# repoquery -f "/usr/lib/yum-plugins/*" | sort | uniq

Built-in support

Since the security plugin does not show up in any of these listings, we use the discovered file path. Searching in this directory shows the existing yum plugins:

[root@localhost Lynis]# find /usr/lib/yum-plugins/

It is clear only fastestmirror is available. Let’s analyze the yum binary.

[root@localhost Lynis]# file /usr/bin/yum
 /usr/bin/yum: Python script, ASCII text executable
[root@localhost Lynis]# grep -i security /usr/bin/yum

No hit, so we have to look inside the Python script:

[root@localhost Lynis]# cat /usr/bin/yum
import sys
    import yum
except ImportError:
    print >> sys.stderr, """\
There was a problem importing one of the Python modules
required to run yum. The error leading to this problem was:


Please install a package which provides this module, or
verify that the module is installed correctly.

It's possible that the above module doesn't match the
current version of Python, which is:

If you cannot solve this problem yourself, please go to
the yum faq at:

""" % (sys.exc_value, sys.version)

sys.path.insert(0, '/usr/share/yum-cli')
    import yummain
    yummain.user_main(sys.argv[1:], exit_code=True)
except KeyboardInterrupt, e:
    print >> sys.stderr, "\n\nExiting on user cancel."

By catting the file we can see it includes the /usr/share/yum-cli directory. Grepping through this directory quickly shows one pointer on how to detect if we have security support built-in.

[root@localhost Lynis]# grep -r security /usr/share/yum-cli
 /usr/share/yum-cli/            self.base.updateinfo_filters['security'] =
 /usr/share/yum-cli/        group.add_option("--security", action="store_true",
 /usr/share/yum-cli/                help=_("Include security relevant packages, in updates"))
 /usr/share/yum-cli/                help=_("Include security relevant packages matching the severity, in updates"))
 Binary file /usr/share/yum-cli/cli.pyc matches
 /usr/share/yum-cli/                   'list-security'      : 'list',
 /usr/share/yum-cli/                   'info-security'      : 'info',
 /usr/share/yum-cli/        return "[info|list|...] [security|...] [installed|available|all] [pkgs|id]"
 /usr/share/yum-cli/            if tn == 'security' and notice['severity']:
 /usr/share/yum-cli/            if tn == 'security' and notice['severity']:
 /usr/share/yum-cli/            if notice['type'] == 'security':
 /usr/share/yum-cli/        for T in ('newpackage', 'security', 'bugfix', 'enhancement'):
 /usr/share/yum-cli/                'security' : 'Security',
 /usr/share/yum-cli/        for T in ('newpackage', 'security', 'bugfix', 'enhancement'):
 /usr/share/yum-cli/            if T == 'security' and len(sev_counts) == 1:
 /usr/share/yum-cli/            if T == 'security' and len(sev_counts) != 1:
 /usr/share/yum-cli/                    args = (maxsize, sev_counts[sn],sn or '?', outT['security'])
 /usr/share/yum-cli/                 "sec" : "security",
 Binary file /usr/share/yum-cli/yumcommands.pyc matches

Great, this provides at least some guidance. For now we use the line with group.add_option to determine that support is built into the yum toolset itself. This enables checking for yum plugins and built-in support.

« Older Entries