Notes from puppet cookbook:

Lessons learned:

  • Module’s init.pp file must contain a class of the same name.
  • Virtual resource (user) gets defined outside of define function which sets

Goals:

  1. Apply ssh configuration site wide.
    • Use Augeas for ssh/sshd config
    • virtual resources for ssh keys (in /etc/sshkeys...)
  2. Add users (done)
  3. Update passwords (done)
  4. Enforce password aging, etc. (done)
  5. Git hooks for syntax checking.
  6. Experiment w/different DC settings. Poss use aws?

Additional studying:

Chapter 1: Infrastructure:

  • Decentralized puppet: Basically, have the modules in git, have the clients periodically download them, then run puppet apply. Author says this circumvents the need for a centralized puppet master, eliminates a single point of failure. Doesn’t mention that it also gets around using the apache web server... Interesting concept. I’ll have to discuss that w/Justin.
  • Several sections of this chapter go through automating the decentralized management. I’m reading through the chapter; but, not trying the implementation yet.
  • Section goes over how to install, configure, and run puppet automatically from a brand new system.
  • OK: finally finished w/chapter 1. Author was getting the environment set up for the rest of the book. Seems to me that he breezed through the setup very quickly. But, it’s worth another look. Some of the stuff, RAKE access automation, for instance, looks very noteworthy.

Chapter 2: Puppet Language and Style:

  • Some style info, most of which is self explanatory. 2 spaces vs my standard four. that might be a mite irritating.

  • puppet-lint does style checking, not syntax checking. While important, not 100% sure how relevant that will be for my specific case.

  • puppet parser validate ${file} does do syntax checking.

  • gem install puppet-module: enables auto creation, installation, and searching of modules on puppet forge. Unfortunately, puppet-module generate ${module} doesn’t work in my system

  • Overriding classes for specific nodes or service: Could be usefule for specific DMZ hosts that should block password authentication, for instance. Author suggests the following format:

    class ${host}_ssh inherits ssh {
        [ override config goes here
    }
    
  • Arrays:

    • Defined w/square brackets []

    • Instantiated w/colon ‘:’

      define lunchprint() {
          notifiy { "Lunch included: ${name}": }
      }
      $lunch = [ 'egg', 'beans','chips' ]
      lunchprint{$lunch: }
      
  • Hashes: Also supports hashes. Interesting.

Chapter 3:

  • Array of resources: ID a class where several instances of the same type of resource is defined, then group them together using an array:

    Instead of:

    package { 'sudo' : ensure => installed }
    package { 'unzip' : ensure => installed }
    package { 'locate' : ensure => installed }
    package { 'lsof' : ensure => installed }
    package { 'cron' : ensure => installed }
    package { 'rubygems' : ensure => installed }
    

    Use:

    package { [ 'sudo',
                'unzip',
                'locate',
                'lsof',
                'cron',
                'rubygems' ]:
        ensure => installed,
    }
    

    Notice the colon (:) at the end of the array definition/instantiation.

  • Definitions: psuedo-functions

    • Used to define variable:

      class testing
      {
          define tmpfile() {
              file { "/tmp/${name}":
                 content => "Hello, world\n",
              }
          }
      
          tmfile { ['a', 'b', 'c']: }
      }
      
    • Can accept parameters:

      class testing
      {
          define tmpfile($greeting) {
              file { "/tmp/${name}":
                 content => ${greeting},
              }
          }
      
          tmpfile { 'foo':
              greeting => "Fuck off, twit!\n",
          }
      }
      
    • Can accept multiple parameters too - useful for creating resolv.conf entries based on DC, perhaps?

  • Several seemingly nice features:

    • Tagging: method of including data w/o having to specify hostnames etc

    • node inheritance. Didn’t the last book say that was a bad idea?

    • class inheritance:

      • If going to override a parm, capitalize the first letter.

      • Can also undefine parameters or add data to existing parms:

        File [ '/etc/ntpd.conf' ] {
            source  => undef,
            content => 'server 0.uk.pool.ntp.org',
            require +> Package['tzdata'],
        }
        
    • Author suggests putting OS, architectural, and datacenter differences:

      • Node inheritance + overrides will help support different data centers transparently. Things to consider overriding:

        • resolv.conf
        • ldap.conf
        • ntp.conf
      • Architectural class to define differences in daemon names:

        $ssh_service = $::operatingsystem ? {
            /Ubunutu|Debian/ => 'ssh',
            default          => 'sshd',
        }
        

        Then, can use $ssh_service:

        service { $ssh_service:
            ensure => running,
        }
        

        Same thing for lib directories:

        $libdir = $::architecture ? {
            amd64   => '/usr/lib64',
            default => '/usr/lib',
        }
        
  • Facter facts can be used directly - I’ve even done so.

  • Running external scripts: This one seems like it’ll be well worth it.

    • Short version: put a script somewhere

    • Call it via generate commands:

      $message = generate(‘/usr/local/bin/testies’) notify { $message: }

    • I put in a multiline command and it worked like a champ. awk -F: '{printf("%s: %s\n", $1, $6)}' /etc/passwd

    • Potential uses:

      • Initially populating DC specific files
      • Setting root password? NOTE: No; done through user resource

Chapter 4: Files and packages

  • Quick edits:

    • This is one of the ones I was looking for.

    • Functions go in manifests/site.pp, or in a file included from there; but, not in nodes.pp.

    • One example:

      site.pp:

      define append_if_no_such_line($file, $line) {
          exec { "/bin/echo '${line}' >> '${file}'":
              unless => "bin/grep -Fx '${line}' '${file}'",
          }
      }
      

      nodes.pp:

      node 'cookbook' {
          append_if_no_such_file { 'enable-ip-conntract':
              file => '/etc/modules',
              line => 'ip_conntrack',
          }
      }
      
    • This will be very useful for:

      • Removing ssh keys
      • Adding groups to system-auth? Might be a little tricky
      • (un)defining parameters in config files (sudoers_base in ldap.conf)
  • Augeas:

    • A method of accessing/updating config files directly w/o having to write my own functions. Has an extensive but limited number of files it understands directly, but for the ones it does, this will dramatically speed the ability to update.

    • Example:

      nodes.pp:

      node '${node}' {
          augeas { 'enable-ip-forwarding':
              context => '/files/etc/sysctl.conf',
              changes => [ 'set net.ipv4.ip_forward 1'],
          }
      }
      
    • http://projects.puppetlabs.com/projects/1/wiki/Puppet_Augeas

    • Augeas page on puppetlabs gives, literally, the exact sshd_config environment that I’m looking for - oop, just noticed one exception, AuthorizedKeysFile:

      augeas { "sshd_config":
        context => "/files/etc/ssh/sshd_config",
        changes => [
          # track which key was used to logged in
          "set LogLevel VERBOSE",
          # permit root logins only using publickey
          "set PermitRootLogin without-password",
          # Change where authorized_keys files are stashed
          "AuthorizedKeysFile  /etc/sshkeys/authorized_keys.%u",
        ],
        notify => Service["sshd"],
      }
      
      service { "sshd":
        name => $operatingsystem ? {
          Debian => "ssh",
          default => "sshd",
        },
        require => Augeas["sshd_config"],
        enable => true,
        ensure => running,
      }
      
  • Comparing version numbers seems interesting:

    node '${node}' {
        $app_version = '1.2.2'
        $min_version = '1.2.10'
    
        if versioncmp($app_version, $min_version) >= 0 {
            notify { 'Version: OK': }
        } else {
            notify {'Upgrade needed': }
        }
    }
    
  • Went through more examples on templates.

  • Need to check out the gpg encryption. That looks intriguing. Book suggests it’s a way of keeping sensitve data secret. Uses a null passphrased gpg key to decrypt. The key is maintained outside of the puppet repos. Could be useful. I’ll have to ponder that.

Chapter 5: Users and Virtual resources

  • Virtual resources defined exactly like regular resources except use ‘@’:

    @package { 'apache2-mpm-worker': ensure => installed }
    
    • Package is defined in a virtual class of your choice using ‘@’

    • Package is instantiated using realize call (with a capital P?):

      realize( Package['${pkg_name}'])
      
    • Can also use collection syntax to instantiate which allows ability to use tags.

      Package <| tag = "security" |>
      
  • Managing users is next: this one’s important. Copious notes:

    • Most basic config:

      # cat init.pp
      class users {
          include users::virtual
          include users::admins
          include users::developers
      }
      # cat virtual.pp
      class users::virtual {
          @user { 'dkoleary': ensure => present }
          @user { 'trzator':  ensure => present }
          @user { 'ztp':      ensure => present }
          @user { 'oracle':   ensure => present }
          @user { 'weblogic': ensure => present }
          @user { 'apache':   ensure => present }
      }
      # head -3 ../../nodes.pp
      class base
      {   include osfiles, sudo, ssh, users::virtual   }
      node 'pm.olearycomputers.com' { include base,users::admins }
      

      That’s the most basic: basically, anwhere the virtual user’s defined it gets created. NOTE, in this config, the home directory does not get created.

    • Check this blog for a more detailed walk through. In the meantime, continuing with the book.

  • Managing ssh access: turned out to be quite ugly:

    • Book wasn’t really clear on where the @ssh_user was to be specified. Turns out, that’s outside of the defined function.

    • I can’t seem to specifiy a key type. Using configs as I have them would mean everyone would have rsa OR dsa keys; no support for both types.

    • I can’t seem to specify a comment field. Need to figure that one out too.

    • All that being said, at long last, it is mildly functional:

      # cat admins.pp virtual.pp
      class users::admins {
          search Users::Virtual
          realize(Ssh_user['dkoleary'])
      }
      
      class users::virtual {
          define ssh_user($key) {
              user { $name:
                  ensure     => present,
                  managehome => true,
              }
              file { "/home/${name}":
                  ensure  => directory,
                  mode    => '0750',
                  owner   => $name,
                  group   => $name,
              }
              file { "/home/${name}/.ssh":
                  ensure  => directory,
                  mode    => '0700',
                  owner   => $name,
                  require => File["/home/${name}"],
              }
              ssh_authorized_key { "${name}_key":
                  key     => $key,
                  type    => 'ssh-dss',
                  user    => $name,
                  require => File["/home/${name}/.ssh"],
              }
          }
          @ssh_user { 'dkoleary':
              key  => '[[snipped]]',
          }
      }
      
  • Managing env files: This one went smoother. Basically, a mod to the virtual.pp file. Could be more efficiently done w/a recursive copy of a /modules/users/${user} directory (discussed later):

    # cat virtual.pp
    class users::virtual {
        define user_file($user) {
            $source = regsubst($name, "^/home/${user}/(.*)$", "puppet:///modules/users/${user}-\\1")
            file { $name:
                source => $source,
                owner  => $user,
                group  => $user,
            }
        }
    
        define ssh_user($key, $ufile='') {
            user { $name:
                ensure     => present,
                managehome => true,
                shell      => '/bin/bash',
            }
            file { "/home/${name}":
                ensure  => directory,
                mode    => '0750',
                owner   => $name,
                group   => $name,
            }
            file { "/home/${name}/.ssh":
                ensure  => directory,
                mode    => '0700',
                owner   => $name,
                require => File["/home/${name}"],
            }
            ssh_authorized_key { "${name}_key":
                key     => $key,
                type    => 'ssh-dss',
                user    => $name,
                require => File["/home/${name}/.ssh"],
            }
            if $ufile {
                $filepath = regsubst($ufile, '^(.*)$',"/home/${name}/\\0",'G')
                user_file { $filepath:
                    user => $name,
                }
            }
        }
        @ssh_user { 'dkoleary':
            ufile => ['.bashrc','.kshrc','resize'],
            key  => '[[snipped]]',
        }
    }
    

    Short version:

    • Admins.pp realizes user dkoleary
    • function’s found in virtual.pp.
      • Creates the user
      • Creates the home directory
      • Creates the ~/.ssh directory
      • Creates the ~/.ssh/authorized_keys file
      • Calls user_file if $ufile is defined.
    • user_file
      • IDs the correct source filename from that provided.
      • Sets a file resource
  • F’ing outstanding We can use puppet to change root’s password. just did that using a straight user (non virtualized) defined in the virtual.pp file. Can also be done to enforce password aging:

    user {  'root':
        ensure           => present,
        comment          => 'OS admin account',
        password_max_age => 90,
        password         => '[[encrypted pwd snipped]]',
    }
    
  • Schedules: can tell when to apply resources. Schedule seems similar to some other s/w that I’ve used; but the exact one is escaping me.
node 'cookbook' {
    schedule { 'outside-office-hours':
        period => daily,
        range  => ['17:00-23:59','00:00-09:00'],
        repeat => 1
    }
        exec {'/bin/echo Doing some maintenance':
            schedule => 'outside-office-hours',
        }
}

Should consider making some schedules for things like maintenance
weekends, after hours, etc.
  • Hosts entries: Excellent. A method of ensuring that a host’s IP is in it’s own hosts table:

    node 'pm' {
        host { 'pm':
            ensure => present,
            ip     => '192.168.122.5',
            target => '/etc/hosts',
        }
    }
    
  • Recursive copies of directories works like a champ. Purge option will remove any files that didn’t come from puppet:

    # cat admins.pp
    class users::admins {
        search Users::Virtual
        realize(Ssh_user['dkoleary'])
        file { '/home/dkoleary/bin':
            ensure  => present,
            # purge => true,
            source  => 'puppet:///modules/users/dkoleary/bin',
            recurse => true,
        }
    }
    
  • Cleaning up old backup files. Another outstanding!:

    node 'web\*' {
        tidy {'/etc/httpd/conf/backups':
            age => '1d',
        }
    }
    
  • Audit capability could prove interesting - more for monitoring things that aren’t under puppet control.

Chapter 6: Applications:

Some interesting bits on apache. I skipped the remaining as I don’t do anything w/mysql, nginx, etc.

Chapter 7: Servers and cloud infrastructure:

Just read through this chapter as well. Interesting handling of the firewall. Probably means my firewall implementation is woefully out of date. Ec2 implementation was interesting; but not incredibly useful as the author used a ruby (fog) app to manage the instances.

Chapter 8: External tools and the puppet ecosystem:

  • Custom facts: Right out of the gate, author’s spot on on one of the things I’m looking for: machines in different datacenters using custom facts to set local DNS settings. My test didn’t work out so well. I think I’m going to have to use some ruby code to ID IP addresses and decide from there.

  • External facts: might be the answer. facter has to be > ver 1.7 which is available from puppet labs. Anything added to /etc/facter/facts.d is evaluated - scripts, text files, etc. Very easy! Text files are evaluated for key=value; scripts must echo output in the same format.

  • Hiera: effectively, a method of storing additional facts in a datafile vs having them in the individual manifests.

  • blueprint is an interesting method of documenting a system. It shows that I didn’t do the osfiles file resources correctly:

    file {
        '/etc':
            ensure => directory;
        '/etc/aliases.db':
            content => template('pm/etc/aliases.db'),
            ensure  => file,
            group   => root,
            mode    => 0644,
            owner   => root;
    [[snip]]
    

    instead of:

    file {  $osfiles::params::f_hosts:
            ensure => present,
            owner  => root,
            group  => root,
            mode   => '0644',
            source => 'puppet:///modules/osfiles/etc/hosts',
    }
    file {  $osfiles::params::f_nswitch:
            ensure => present,
            owner  => root,
            group  => root,
            mode   => '0644',
            source => 'puppet:///modules/osfiles/etc/nsswitch.conf',
    }
    

    Regardless, the author suggests the blueprinting function isn’t the best thing in the world.

  • External Node Classifier (ENC): method of obtaining list of classes to apply to a node from outside of puppet. Mayhaps this is what I need to use to ID dc specific info... http://docs.puppetlabs.com/guides/external_nodes.html

  • Custom resources require a knowledge of ruby; skipping this for the time being. Custom resources reuquire custom providers.

Chapter 9: Monitoring, reporting, and troubleshooting:

  • Two useful switches to puppet command:
    • –noop: no operation; a dry run. Puppet tells you what it would have done.
    • –debug: shows the details of every resource. LOTS of output.
  • exec -> logoutput => true will log the output of the command exec’ed.
  • puppet doc --all --outputdir=${dir} --mode rdoc --manifestdir=${Main}
    • really didn’t like my idea of softlink for nodes.pp
    • Also didn’t like the environments directory. Complained about duplicate declarations.
    • Once I cleaned those out, the resulting html seems like good info.
  • The graphviz app, now that’s an eye opener. My simple little environment ended up looking ugly as all fuck.
    • yum -y intall graphviz
    • puppet .. --graph
    • cd /var/lib/puppet/state/graphs
    • ``dot -Tpng -o ${outfile}.png ./relsationships.dot
  • puppet config print [ ${var} ] | more to see a list of configs and their settings.