Tuesday, March 10, 2015

Using Puppet To Ensure Compliance - Part III

In this article we will look at a basic and very necessary task for any Operating System - disabling unnecessary services.  All Operating Systems will have services that may never be utilized.  Essentially opening a door to your system that doesn't need to be open.  Close it!  Here's how the Puppet code would look for RedHat and Solaris respectively.

class disable_services {

  case $osfamily {
    'RedHat': { $rhel_svcs =
                [ 'acpid',
                  'cups',
                  'iptables',
                  'ip6tables',
                  'rpcidmapd' ] }

    'Solaris': { $solaris_svcs =
                  [ 'svc:/network/rpc/cde-ttdbserver:tcp',
                    'svc:/network/rpc/cde-calendar-manager:default',
                    'svc:/application/graphical-login/cde-login',
                    'svc:/application/gdm2-login',
                    'svc:/system/webconsole:console',
                    'svc:/application/management/wbem',
                    'svc:/application/print/rfc1179',
                    'svc:/network/rpc/gss',
                    'svc:/system/filesystem/volfs',
                    'svc:/network/rpc/smserver' ] }

    default: { fail('Unrecognized operating system for class disable_services') }
  }

  service { [ $rhel_svcs, $solaris_svcs ]:
    ensure   => stopped,
    enable   => false,
  }

  package { [ $rhel_svcs ]:
    ensure   => absent,
  }
}


This code uses Ruby arrays exclusively.  Arrays allows us to list out multiple services.  How you format the array is up to you. I've listed out each service on a new line to make the code a bit more readable.  If you have a case statement you need a default action if the value of $osfamily is unknown.  Run "puppet parser validate init.pp" or "puppet-lint init.pp" to check your syntax before deploying Puppet code.

You might ask why disable iptables on a RedHat host?  This is just an example. Some arguments may suggest performance is an issue so turn off iptables.  Others may say why have multiple firewalls in the data path from the client to server.  My feelings are iptables is not a drag on performance.  I would also suggest using firewalls, local and network whenever possible.

Example puppet-lint syntax check:

WARNING: line has more than 80 characters on line 2
WARNING: line has more than 80 characters on line 26
WARNING: top-scope variable being used without an explicit namespace on line 6

There is one conditional statement and two basic reference types used.  Case sets up the code to first check the facter variable $osfamily.  Wait, did I spell factor wrong or is it really facter?  It's facter.  Facter is a dependency for the Puppet RPM - puppet-noarch.  Facter is pretty simple to understand.  It gathers facts and puts them into variables for use in Ruby code for Puppet.  You can write your own custom facts if you don't find the variable you want to use with your manifest.  In the case statement above we check if $osfamily is RedHat then Puppet will ensure the service is stopped and disabled (chkconfig'd off) and the package is removed if it exists.  The same concept applies for the $solaris_svcs variable.

We use the reference type service to ensure the RHEL and Solaris service variables are set to stop the service and disable it from startup.  Since RedHat uses different startup manifests than Solaris we rely on Puppet to select the appropriate provider.  A provider is the underlying command to stop the service.  For RedHat Puppet uses /sbin/service.  For Solaris Puppet uses svcadm. 

The final reference type is package.  Here we simply ask the package to be absent or uninstalled if it is installed.  How this works is also dependent on the provider.  For RedHat the provider could be 'rpm' or 'yum'.  Puppet will discover the appropriate provider and use it. This code works nicely for RedHat because the service and the package are named the same.  For example '/sbin/service cups stop' and 'yum erase cups' would work because the service and the package name are the same. Another way to think about it is cups and it's dependencies are both handled by the provider yum. For Solaris this doesn't work so well and the service name is very different from the package name.  So we disregard Solaris service packages in the package reference type.  To remove Solaris packages we would need another conditional statement (not shown) to ask if this host is Solaris then use pkgrm and remove those packages in an Ruby array. 

This article shows Ruby code for a Puppet manifest to identify RedHat and Solaris services and stop them.  This code will ensure the services listed are stopped, disabled and in some cases the packages (and package dependencies) will be uninstalled. For security purposes you will want to turn off all unnecessary services and ensure they remain off.  This will lower your operating system's footprint to the smallest attack surface possible. Beware when services are turned off it can (and often will) impact functionality of applications on your system. Make sure you test this type of Puppet code in a safe place - like a test or lab environment.