The Manifest

Let’s talk some code:

# This is an example proposed Puppet Common Module for SSH
#
# Usage Requirements:
# 1) Set $server in site.pp
#    Allows for a different fileserver then the real puppetmaster
# 2) Set $os to $operatingsystem
#    Saves typing, purely cosmetic
# 3) Set $osver to $operatingsystemrelease or $lsbdistrelease
#    $operatingsystemrelease is not available on all platforms
#

class ssh {

    # Distribution independent packages
    # See also our Operating System specific sub-classes
    @package { [
            "openssh-clients",
            "openssh-server",
            "denyhosts"
        ]:
        ensure => installed
    }

    # Virtual Resources get defined before we include $operatingsystem specific
    # classes, so that there is at least something to add and/or override.
    # 
    # Additionally, this way we can realize() in sub-classes as much as we want
    # to, and not concern ourselves with duplicate type definitions
    #

    @file { "/etc/denyhosts.conf":
        notify => Service["denyhosts"],
        require => Package["denyhosts"],
        source => [
            "puppet://$server/private/$domain/denyhosts/denyhosts.conf",
            "puppet://$server/files/denyhosts/denyhosts.conf",
            "puppet://$server/denyhosts/denyhosts.conf"
        ]
    }

    @file { "/etc/ssh/ssh_config":
        owner => "root",
        mode => 644,
        require => Package["openssh-clients"],
        source => [
            #
            # See rationale for an explanation on this list of sources
            # http://reductivelabs.com/trac/puppet/wiki/PuppetCommonModules/SSH
            #
            "puppet://$server/private/$domain/ssh/$os/$osver/ssh_config.$hostname",
            "puppet://$server/private/$domain/ssh/$os/$osver/ssh_config",
            "puppet://$server/private/$domain/ssh/$os/ssh_config.$hostname",
            "puppet://$server/private/$domain/ssh/$os/ssh_config",
            "puppet://$server/private/$domain/ssh/ssh_config.$hostname",
            "puppet://$server/private/$domain/ssh/ssh_config",
            "puppet://$server/files/ssh/$os/$osver/ssh_config.$hostname",
            "puppet://$server/files/ssh/$os/$osver/ssh_config",
            "puppet://$server/files/ssh/$os/ssh_config.$hostname",
            "puppet://$server/files/ssh/$os/ssh_config",
            "puppet://$server/files/ssh/ssh_config.$hostname",
            "puppet://$server/files/ssh/ssh_config",
            "puppet://$server/ssh/$os/$osver/ssh_config",
            "puppet://$server/ssh/$os/ssh_config",
            "puppet://$server/ssh/ssh_config"
        ],
        sourceselect => first
    }

    @file { "/etc/ssh/sshd_config":
        owner => "root",
        mode => 644,
        notify => Service["openssh-server"],
        require => Package["openssh-server"],
        source => [
            #
            # See rationale for an explanation on this list of sources
            # http://reductivelabs.com/trac/puppet/wiki/PuppetCommonModules/SSH
            #
            "puppet://$server/private/$domain/ssh/$os/$osver/sshd_config.$hostname",
            "puppet://$server/private/$domain/ssh/$os/$osver/sshd_config",
            "puppet://$server/private/$domain/ssh/$os/sshd_config.$hostname",
            "puppet://$server/private/$domain/ssh/$os/sshd_config",
            "puppet://$server/private/$domain/ssh/sshd_config.$hostname",
            "puppet://$server/private/$domain/ssh/sshd_config",
            "puppet://$server/files/ssh/$os/$osver/sshd_config.$hostname",
            "puppet://$server/files/ssh/$os/$osver/sshd_config",
            "puppet://$server/files/ssh/$os/sshd_config.$hostname",
            "puppet://$server/files/ssh/$os/sshd_config",
            "puppet://$server/files/ssh/sshd_config.$hostname",
            "puppet://$server/files/ssh/sshd_config",
            "puppet://$server/ssh/$os/$osver/sshd_config",
            "puppet://$server/ssh/$os/sshd_config",
            "puppet://$server/ssh/sshd_config"
        ],
        sourceselect => first
    }

    @service { "openssh-server":
        enable => true,
        ensure => running,
        require => [
            File["/etc/ssh/sshd_config"],

            Package["openssh-server"]
        ]
    }


    # Include operatingsystem specific subclass
    include ssh::$operatingsystem

    class client inherits ssh {
        realize(Package["openssh-clients"])
    }

    class server inherits ssh {

        realize(Package["openssh-server"])

        class noclient inherits server {
            # To create a server that cannot ssh out.
            # Note that this removes packages depending on
            # openssh-clients as well.

            Package["openssh-clients"] {
                ensure => absent
            }

            realize(Package["openssh-clients"])
        }

        class denyhosts inherits server {
            # Let the server deny hosts

            realize(
                File["/etc/denyhosts.conf"],
                Package["denyhosts"],
                Package["openssh-server"]
            )

            service { "denyhosts":
                enable => true,
                ensure => running,
                require => [
                    File["/etc/denyhosts.conf"],
                    Package["denyhosts"]
                ]
            }
        }
    }

    class Debian inherits ssh {
        File["/etc/ssh/ssh_config"] {
            group => "root"
        }

        Package["openssh-clients"] {
            name => "openssh-client"
        }

        Service["openssh-server"] {
            name => "ssh",
            pattern => "sshd"
        }
    }

    class Fedora inherits ssh {
        File["/etc/ssh/ssh_config"] {
            group => "root"
        }

        Service["openssh-server"] {
            name => "sshd",
            hasrestart => true,
            hasstatus => true,
            restart => "/etc/init.d/sshd restart",
            status => "/etc/init.d/sshd status"
        }
    }

    class RedHat inherits Fedora {
    }

    class CentOS inherits RedHat {
    }

    class Darwin inherits ssh {
        Package["openssh-clients"] {
            source => "http://server/foo"
        }

        File["/etc/denyhosts.conf"] {
            group => "wheel"
        }

        File["/etc/ssh/ssh_config"] {
            group => "wheel",
            path => "/etc/ssh_config"
        }

        File["/etc/ssh/sshd_config"] {
            group => "wheel",
            path => "/etc/sshd_config"
        }

    }
}

Hope you liked it ;–)

Rationale

Here’s a little rationale on the things you just read and probably had some questions about.

Yuk, this is one large file

  1. It fits better on a wiki page ;–)
  2. Red Hat / CentOS has puppet-0.24.4 and does not do the import magic. It could do the imports, but it requires import statements per file (not exactly making the module any more portable).

Virtual Resource in top class

Defining the resources shared over multiple of the ssh sub-classes as a virtual resource in the top-class allows us to override parameters in operating system specific sub-classes, as well as realize() the virtual resources more then once, not only throughout the module, but also when required by other modules (can’t come up with an appropriate example).

Operating system specific classes

The operating system specific sub-classes you see in the module are meant to provide the most flexible and efficient operating system specific attributes to resources (especially the virtual resources defined at the beginning).

Huge list of possible sources

The huge list of possible sources is something I’ve worked out over several customers.

$server

Setting $server in site.pp for example allows you to use a server other then your puppetmaster as a dedicated fileserver.

$os ?? $osver ??

A very, very nasty example is using “UsePAM yes” in /etc/ssh/sshd_config on Red Hat Enterprise Linux 3 machines ^1. The SSH server won’t stop working (e.g. not error out with Syntax Error Messages), but remote access over SSH is impossible. This introduced the requirement of being able to have Operating System ($os) and Operating System version ($osver) specific files. It is not required to maintain the trees, but the module obviously needs to provide the best default for every operating system and operating system version that has these kind of exceptions.

Using $os is far from required; these are variables that have been assigned in site.pp because I’m a lazy guy and dislike typing $operatingsystem twice.

Using $osver however is something that surfaced when it seemed $operatingsystemrelease was not available on Fedora / Red Hat / CentOS boxes. Instead, I’m using $lsbdistrelease (and sometimes a custom fact such as $operatingsystemmajorrelease). However, using $operatingsystemrelease and/or $lsbdistrelease isn’t very efficient (again the typing comes to mind), so I assign these facts to the $osver variable.

$domain ??

When merging organizations, working with the legacy (non-managed) domain from the merger requires you to specify some kind of level of distinction between organizations. I’ve chosen to use the domain name space, which also allows me to run these domain specific trees from separate Source Control Management systems, and thus separate access control.

I needed to use a fact here, since that is defined before a manifest is parsed, and you cannot override a fact. So far for the domain specific tree.

Then why files/ ??

The merger continues (or you had none to begin with), and the files/ fileserver mount allows you to source the file in a puppetmaster global sense, without modifying the module. Modifying the module means committing, possibly pushing (but not likely) the changes somewhere, and resolving potential conflicts afterwards.

Module default

The module default should be similar to the default configuration on a distribution, or the best overall default if that is something that can ever be agreed upon.

Using the $hostname

Using the hostname, you are able to provide specific configuration on a per host basis, or on a per group of hosts basis. If some of the SSH server you have in your organization require only users in the group wheel to be able to login (for example), then create a file called sshd_config.wheel-only and create a symbolic link sshd_config.$hostname => sshd_config.wheel-only.

Possible improvement: Using the $fqdn of a machine instead might work better in some cases.

Another option is to specify variables throughout the manifests, which is practically never going to end given the amount of tweakable settings per application, and is no more efficient then creating the symbolic link.

^1\ yes,\ rebuilding\ the\ ruby\ stack\ is\ included\ to\ make\ RHEL3\ machines\ do\ puppet^