Deploying automated CVE reporting for Solaris 11.3

With Solaris 11.2 Oracle started including quiet few new Solaris features for security and automated deployment. Besides bringing in immutable zones, which I didn’t get to write about yet (which is a shame since these are wonderful), and compliance, Solaris IPS received a new package called pkg://solaris/support/critical-patch-update/solaris-11-cpu. This package includes packages that are considered to be part of the Critical Patch Update. In addition to the package name and version this package now enables you to see which CVE each of these packages belong to.
You can use the pkg command to do some basic searches. Immutable zones, compliance and CVEs are only three of the different security features that were added to Solaris 11.2 and Solaris 11.3.

Most likely an admin will not want to login to each of his hundreds, thousands or even more Solaris installations in order to install needed packages and take care of a proper configuration. Can’t blame him. That’s probably what the Solaris team thought when puppet became part of the IPS repository with Solaris 11.2. There is not that much to say about it for those who do not know it. It does what is suppose to and is a relief for every admin if used right. In case you are interested in some really great articles go check out Manuel Zach’s blog. For automation in general you will want to go and also read Glynn Foster’s blog.
Now why am I writing about these “old” Solaris 11.2 features if Solaris 11.3 beta was released a few weeks ago already. Well, these are fundamental technologies in order to get the most out of Solaris 11.3.

Bringing Solaris IPS and NIST together

Companies mostly use an external software that alerts and reports every single CVE that is out there and triggers a service request for the responsible team. The thing is, it is slow, costs a lot and you get service requests for software that is not installed on any system. So what happens is the admin ends up checking it himself.

So I figured I will just do it Solaris style. By now as I write this post, we do have a fully automated reporting for CVEs for no extra costs at work.

Let’s start with IPS and CVEs. As mentioned before you will need to have a certain package installed.

# pkg install support/critical-patch-update/solaris-11-cpu

This package is updated with every SRU and will include every known CVE for Solaris 11. If you want to know a few basics about it read Darren Moffat’s blog.
Use the following command to see all the included packages:

# pkg contents -ro name,value solaris-11-cpu|grep '^CVE.*'
CVE-1999-0103                 pkg://solaris/system/security/kerberos-5@0.5.11,5.11-
CVE-2002-2443                 pkg://solaris/system/security/kerberos-5@0.5.11,5.11-
CVE-2003-0001                 pkg://solaris/driver/network/ethernet/pcn@0.5.11,5.11-
CVE-2004-0230                 pkg://solaris/system/kernel@0.5.11,5.11-
CVE-2004-0452                 pkg://solaris/runtime/perl-584/extra@5.8.4,5.11-
CVE-2004-0452                 pkg://solaris/runtime/perl-584@5.8.4,5.11-
CVE-2004-1019                 pkg://solaris/web/php-52/extension/php-apc@3.0.19,5.11-
CVE-2004-1019                 pkg://solaris/web/php-52/extension/php-idn@0.2.0,5.11-
CVE-2004-1019                 pkg://solaris/web/php-52/extension/php-memcache@2.2.5,5.11-
CVE-2004-1019                 pkg://solaris/web/php-52/extension/php-mysql@5.2.17,5.11-
CVE-2004-1019                 pkg://solaris/web/php-52/extension/php-pear@5.2.17,5.11-
CVE-2015-4024                 pkg://solaris/web/php-53/extension/php-tcpwrap@1.1.3,5.11-
CVE-2015-4024                 pkg://solaris/web/php-53/extension/php-xdebug@2.2.0,5.11-
CVE-2015-4024                 pkg://solaris/web/php-53/extension/php-zendopcache@7.0.2,5.11-
CVE-2015-4024                 pkg://solaris/web/php-53@5.3.29,5.11-
CVE-2015-4024                 pkg://solaris/web/server/apache-22/module/apache-php52@5.2.17,5.11-
CVE-2015-4024                 pkg://solaris/web/server/apache-22/module/apache-php53@5.3.29,5.11-
CVE-2015-4770                 pkg://solaris/system/file-system/ufs@0.5.11,5.11-
CVE-2015-4770                 pkg://solaris/system/kernel/platform@0.5.11,5.11-
CVE-2015-5073                 pkg://solaris/library/pcre@8.37,5.11-
CVE-2015-5477                 pkg://solaris/network/dns/bind@,5.11-
CVE-2015-5477                 pkg://solaris/network/dns/bind@,5.11-
CVE-2015-5477                 pkg://solaris/service/network/dns/bind@,5.11-
CVE-2015-5477                 pkg://solaris/service/network/dns/bind@,5.11-

Now we got the information of which Solaris IPS package belongs to which CVE-ID. That’s nice but how do we get all the other CVE information? Base score, summary, access vector, etc.?! In order to add more details to it I imported the NIST nvd-files into a sqlite3 database.
The files can be either downloaded as a compressed gz-file or regular xml. For more information visit
I imported the xml files into a sqlite3 database for a better performance. If you don’t want to work your way through the xml structures yourself use this python program that I came across while writing it myself. I like the approach of just having to do:

# curl | nvd2sqlite3 -d /wherever/you/like/to/keep/the/dbfile

In order to keep your NIST CVE database current I put the commands in a script and created a crontab entry.


curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb
curl | nvd2sqlite3 -d /data/shares/NIST/cvedb


0 5 * * * /scripts/admin/

Alright this gives us a database with all the information we need and want. The schema of the sqlite3 database looks like this:

sqlite> .schema
CREATE TABLE nvd (access_vector varchar,
                                            access_complexity varchar,
                                            authentication varchar,
                                            availability_impact varchar,
                                            confidentiality_impact varchar,
                                            cve_id text primary key,
                                            integrity_impact varchar,
                                            last_modified_datetime varchar,
                                            published_datetime varchar,
                                            score real,
                                            summary varchar,
                                            urls varchar,

Next to do is to match the data of the db with the IPS information. When I started working on this I focused on console output only but when I looked at our centralized compliance reports I wanted the same thing for CVEs. A central CVE reporting. So I ended up with writing the output to a html-file on an apache webserver.

Since the standard Perl in Solaris does not contain DBD::SQLite I switched to Python. does the following:

  • get all the installed package information from IPS
  • get all the information from the solaris-11-cpu package
  • match the above data and filter which pkg is installed and what version does it have (lower version = unpatched CVE)
  • create html report file with all the needed elements
  • connect to the sqlite3 db and get cve_id, access_vector, score and summary
  • write the select output to file, sorted by unpatched and patched CVEs

The CVE report looks like this:

What we got now is a script that pulls in all the nvd information from NIST and stores it in a sqlite3 database. And we got a script that matches these information with the installed IPS packages and generates a CVE report in a html format.

Scheduled Services with Solaris 11.3

Next up is to automatically generate these reports. With Solaris 11.2 cron would be the way to do it. Trivial entry in the crontab and done.

30 5 * * * /scripts/admin/

With Solaris 11.3 cron is almost obsolete. Why? Because of SMF and the new scheduled and periodic services. I’m not gonna talk about why SMF is great or not. To me it is great and I never ran into any serious problem. If it is a Solaris 11.3 installation I will move custom cronjobs to SMF and create scheduled services.
What these do is the same as cron plus everything else SMF has to offer.
The scheduled service I use for the CVE reporting is the following:

<?xml version="1.0" ?>
<!DOCTYPE service_bundle
  SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
    Manifest created by svcbundle (2015-Sep-04 15:05:15+0200)
<service_bundle type="manifest" name="site/cveList">
    <service version="1" type="service" name="site/cveList">
            The following dependency keeps us from starting until the
            multi-user milestone is reached.
        <dependency restart_on="none" type="service"
            name="multi_user_dependency" grouping="require_all">
            <service_fmri value="svc:/milestone/multi-user"/>
        <instance enabled="true" name="default" >
                                        <method_credential user='root' group='root' />

Use svcbundle to generate your own manifest.

# svcbundle -o /var/tmp/cveList.xml -s service-name=site/cveList -s start-method=/lib/svc/method/ -s interval=day -s hour=5 -s minute=30
# svccfg validate /var/tmp/cveList.xml

It’s as easy as that. Add to it whatever you feel like and is needed. Mail reporting in case of a status change for example.

Well now we have a service that generates a CVE report every day at 5:30am of a server.
We need more so let’s move on to the next piece.

Building a custom IPS package

The best way to deploy any piece of software on a Solaris 11.x server is with IPS.
IPS packages are very easy to use when they already build and published. List, install, info, uninstall, contents, search, freeze, unfreeze, etc.. It is always the same command pattern that makes it that way. But how do you build your own packages. That is always a bit more tricky than using them. Instead of explaining how it works I will just link to another article written by Glynn Foster which covers everything you need to know.
If you don’t want to type in every single step this little script might help. Adjust your IPS repository and your paths and all you need is a so called mog file which in this case could look like this:

set name=pkg.fmri value=pkg://custom/security/custom-cveList@1.0.2
set name=variant.arch value=sparc value=i386
set name=pkg.description value="custom CVE reporting"
set name=pkg.summary value="custom Solaris CVE reports"
<transform dir path=lib$ -> drop>
<transform dir path=lib/svc$ -> drop>
<transform dir path=lib/svc/manifest$ -> drop>
<transform dir path=lib/svc/manifest/site$ -> set owner root>
<transform dir path=lib/svc/manifest/site$ -> set group sys>
<transform file path=lib/svc/manifest/site/cveList\.xml$ -> set owner root>
<transform file path=lib/svc/manifest/site/cveList\.xml$ -> set group bin>
<transform file path=lib/svc/manifest/site/cveList\.xml$ -> set mode 0444>
<transform file path=lib/svc/manifest/site/cveList\.xml$ -> default restart_fmri svc:/system/manifest-import:default>
<transform dir path=lib/svc/method$ -> drop>
<transform file path=lib/svc/method/cveList\.py$ -> set owner root>
<transform file path=lib/svc/method/cveList\.py$ -> set group bin>
<transform file path=lib/svc/method/cveList\.py$ -> set mode 0555>

Besides the mog file you just enter the path to your proto directory that includes the software that is suppose to be packaged up and you are good to go. You will be asked to type in the name of the package and that’s it. Rest is done automatically. You might have to adjust the configuration inside of your mog file in case of unresolved dependencies for example. Should you be missing a custom IPS repo create one right quick and then start packaging.

Creating a custom IPS repo and share it via nfs:

# zfs create -po mountpoint=/ips/custom rpool/ips/custom
# zfs list -r rpool/ips
rpool/ips          62K  36.2G    31K  /rpool/ips
rpool/ips/custom   31K  36.2G    31K  /ips/custom
# pkgrepo create /ips/custom
# zfs set share=name=custom_ips,path=/ips/custom,prot=nfs rpool/ips/custom
# zfs set share.nfs=on rpool/ips/custom
# zfs get share
NAME                                                           PROPERTY  VALUE  SOURCE
rpool/ips/custom                                               share     name=custom_ips,path=/ips/custom,prot=nfs  local

Let’s actually build the cveList IPS pkg.

# /scripts/admin/ /scripts/admin/IPS/CVE/MOG/custom-cveList.mog /scripts/admin/IPS/CVE/PROTO.CVE

Need some information about the package. Answer the following questions to generate a mogrify-file (package_name.mog) or if you have a package_name.mog template execute this script with args:

 /scripts/admin/ [path_to_mog_file] [path_to_proto_dir]

Enter Package Name (eg. custom-compliance): custom-cveList

Ready! Generating the manifest.

pkgsend generate... OK
pkgmogrify... OK
pkgdepend generate... OK
pkgdepend resolve... OK
eliminating version numbers on required dependencies... OK
testing manifest against Solaris 11.2 repository, pkglint ... 
Lint engine setup...

Ignoring -r option, existing image found.
Starting lint run...


Review the manifest file custom-cveList.p5m.4.res!

publish the ips package with:
pkgsend publish -s file:///data/ips/custom -d /scripts/admin/IPS/CVE/PROTO.CVE /scripts/admin/IPS/CVE/custom-cveList.p5m.4.res

check the package with:
pkg refresh
pkg info -r custom-cveList
pkg contents -m -r custom-cveList
pkg install -nv custom-cveList

remove it:
pkgrepo remove -s file:///data/ips/custom pkg://custom/security/custom-cveList@1.0.2

Et voilà, the package is ready to be published.

# pkgsend publish -s file:///data/ips/custom -d /scripts/admin/IPS/CVE/PROTO.CVE /scripts/admin/IPS/CVE/custom-cveList.p5m.4.res
# pkg refresh
# pkg info custom-cveList
             Name: security/custom-cveList
          Summary: custom Solaris CVE reports
      Description: custom CVE reporting
            State: Installed
        Publisher: custom
          Version: 1.0.2
           Branch: None
   Packaging Date: Tue Sep 08 16:48:50 2015
Last Install Time: Tue Sep 08 16:52:07 2015
             Size: 8.80 kB
             FMRI: pkg://custom/security/custom-cveList@1.0.2:20150908T164850Z

DONE! At least with getting CVEs, matching CVEs, scheduling reports and building a package out of all of this.

Let’s deploy.

Let puppet do your job

In this case I am already running a puppet master and several puppet agents. Since I talked about multiple hundreds or thousands of Solaris installations a master-agent setup is exactly what we want.
Nobody has the time and endurance to login on each system and do a pkg install custom-cveList.
I figured a puppet module would be just what I want.
And to save time, here it is:

# cat /etc/puppet/modules/cve/manifests/init.pp
class cve {
        if $::operatingsystemrelease == '11.3' or $::operatingsystemmajrelease == '12' {
                package { 'custom-cveList':
                        ensure => 'present',
        if $::operatingsystemrelease == '11.2' {
                cron { 'cveList' :
                        ensure => 'present',
                        command => '/scripts/admin/',
                        user => 'root',
                        hour => 5,
                        minute => 30,

The first if-statement will install the recently build and published IPS pkg. Since scheduled services are not available in Solaris 11.2 I had to add a crontab entry for that case, which would be the second if-statement in the above.
Now just add it to your /etc/puppet/manifest/site.pp and you are all set up.

node default {
        include nameservice
        include tsm
        include arc
        include compliance
        include mounts
        include users
        include cve

This is it now. From now on, every single Solaris server that runs a puppet agent will have your custom CVE reporting deployed.
Reading all this actually takes longer than just doing it and you only need to go through all of this once.

I know this looks like it is a lot but it really isn’t. If you want to leave out the IPS part just add your scripts and service/cron to your puppet configuration.
This is easier to handle than a third party tool. If want you could just implement it to your ITIL process and its tools to automate CVE service request handling.


4 Replies to “Deploying automated CVE reporting for Solaris 11.3”

  1. Pingback: ZFS lz4 compression with Solaris 11.3 | onlinedevice

  2. UX-admin

    I modified your program as follows:

    – instead of a custom parser, you now sport a scalable, standard getopts(1) parser, enabling you to add as many options as you want;
    – removed “help”, “–help” and such, as they are GNU, and GNU is not UNIX nor standards compliant (you should write a manual page to go along with the program);
    – the program now supports the optional “-r ##.#”, for example “-r 11.1”, so that you can dynamically switch between repository versions without having to modify the code;
    – removed the “.sh” postfix, as this is not Windows, UNIX does not care, and neither should the user what type of an executable it is;
    – renamed the program to “bip” (build IPS package), in line with the UNIX tradition of least amount of typing (rm(1), mv(1), ls(1), and such);
    – the program now returns standard error codes defined in /usr/include/sys/errno.h.

  3. UX-admin

    # If the environment variable DEBUG is set to true, set it to that,
    # otherwise set it to false(1).
    DEBUG=”${DEBUG:-false}”; export DEBUG
    ${DEBUG} && set -x
    # We want POSIX AWK.
    PATH=”/usr/xpg4/bin:${PATH}”; export PATH
    # Plumbing for syslogd(1M).
    Self=`basename $0`
    SelfLength=`echo “Usage: ${Self}” | awk ‘{print length($0);}’`

    if [ ${USER} != “root” ]
    printf “%s: this program requires super-user privileges.\n” “${Self}”

    ${DEBUG} && set -x
    printf “Usage: %s -p package_name -d /path/to/proto/dir\n” “${Self}” 1>&2
    printf “%${SelfLength}s [-r repository_version] [-D]\n” ” ” 1>&2
    printf “%${SelfLength}s [-h|-?]\n” ” ” 1>&2

    ${DEBUG} && set -x
    if [ $1 -eq 0 ]
    printf “OK.\n”

    if [ $1 -ne 0 ]
    printf “FAILED.\n”

    while getopts p:d:r:Dh Option
    case “${Option}”



    DEBUG=”true”; export DEBUG

    exit 0

    exit $?
    shift `expr ${OPTIND} – 1`
    if [ “${PackageName}” = “” ]
    printf “%s: -p PackageName is a required option.\n” “${Self}” 1>&2
    # ECANCELLED (see /usr/include/errno.h).
    if [ “${PackageProtoDir}” = “” ]
    printf “%s: -d /path/to/proto/dir is a required option.\n” “${Self}” 1>&2
    RepositoryVersion=`echo “${RepositoryVersion}” | awk ‘/[[:digit:]]{2}\.[[:digit:]]$/’`
    if [ “${RepositoryVersion}” = “” ]
    printf “%s: repository version does not match the ##_# pattern, such as \”11_1\”.\n” “${Self}” 1>&2
    if [ ${ErrorCode} -ne 0 ]
    exit ${ErrorCode}
    build_dir=`echo “${PackageProtoDir}” | sed “s/\/PROTO.*//”`

    if [ ! -s “${MogFile}” ]
    printf “%s: %s is empty or does not exist.\n” “${Self}” “${MogFile}” 1>&2
    # Exit with ENOENT (“no such file or directory”).
    exit 2
    # Main program.
    printf “%s: generating the manifest… ” “${Self}”
    pkgsend generate $PackageProtoDir | pkgfmt -u > $PackageName.p5m.1
    PrintState $?

    printf “%s: mogrifying… ” “${Self}”
    pkgmogrify $PackageName.p5m.1 $MogFile | pkgfmt -u > $PackageName.p5m.2
    PrintState $?

    printf “%s: running pkgdepend generate… ” “${Self}”
    pkgdepend generate -md $PackageProtoDir $PackageName.p5m.2 | pkgfmt -u > $PackageName.p5m.3
    PrintState $?

    printf “%s: running pkgdepend resolve… ” “${Self}”
    pkgdepend resolve -m $PackageName.p5m.3
    PrintState $?

    printf “%s: eliminating version numbers on required dependencies… ” “${Self}”
    sed ‘s/\(@[0-9\.]*-[0-9\.]*\)//’ $PackageName.p5m.3.res > $PackageName.p5m.4.res
    PrintState $?

    printf “%s: testing manifest against Solaris %s repository, pkglint… ” “${Self}” `echo “${RepositoryVersion}” | sed -e ‘s/_/\./g’`
    pkglint -c ./lint-cache_${RepositoryVersion} -r file:///data/ips/sol${RepositoryVersion} ${PackageName}.p5m.4.res
    PrintState $?

    printf “Review the manifest file %s!\n” “${PackageName}.p5m.4.res”
    cat <<- _EOF_

    Publish the IPS package with:
    pkgsend publish -s file:///data/ips/custom -d $PackageProtoDir ${PackageName}.p5m.4.res

    check the package with:
    pkg refresh
    pkg info -r $PackageName
    pkg contents -m -r $PackageName
    pkg install -nv $PackageName

    to remove the package:
    pkgrepo remove -s file:///data/ips/custom `grep pkg.fmri $MogFile | cut -d '=' -f3`

  4. UX-admin

    Unfortunately the blogging software mangled all the pretty program formatting. Now that you have a getopts(1) parser, you could also add a “-p” switch to enable you to optionally publish the package. This is left as an exercise for the original author and the reader.

    Questions? E-mail.


Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.