29 July 2017

Puppet Part Two: Groups, Users and Simple Manifests

In my first Puppet post I went through the setup process on both the Ubuntu and CentOS Linux distributions. That post ended with four VMs:

o baratheon, the Puppet server
o stark, running Ubuntu
o bolton, running CentOS
o lannister, running CentOS

My previous post ended with all four systems polling baratheon (the Puppet server is configured to manage itself) but nothing was actually managed by it. Two of the VMs, baratheon and stark, have a group and user named 'test'. The other two, bolton and lannister, have a group and user named 'demo'. In this post I want to:

o remove the 'demo' user
o remove the 'demo' group
o remove the 'test' user
o remove the 'test' group
o standardise with a 'secops' group
o standardise with a 'secops' user
o make sure the 'secops' user's home directory is created
o make sure the 'secops' user is in the sudo group
o set an initial password for the 'secops' user
o set a password expiration of ninety days for the 'secops' user

All of this can be done using Puppet's built-in features.

Users, Groups and Resource Types


Puppet has what it calls "resource types", built-ins for basic system functions. Tonight I want to focus on two of them, the "group" and "user" types. If you want to read all about them, the Puppet documentation is rather good:


I'm intentionally linking to the 4.10 documentation because that is the version currently installed via pc1. They have a drop-down available for viewing the documentation from other releases. You can install a newer version, 5.0, but none of the 3rd-party modules I've started using officially support 5 so I'm sticking with 4.10!

That's all well and good...but how does one use them?

Adding a Group


I'm going to start with the manifest for stark and adding the 'secops' group. I'm adding the group before the user because I want the user to be a member of the group and if the group doesn't exist, the user creation can fail.

I created stark's manifest (/etc/puppetlabs/code/environments/production/manifests/stark.pp) in my previous post but all I put in it was an empty definition, basically the bare minimum for the Puppet agent to successfully "check in". I could start with the manifest for the Puppet server, baratheon, but I want to make sure the configuration does what I want it to before I risk losing the only user I have on my Puppet server!

Right now, stark's manifest looks like this:

node stark { }

The basic format for adding a group is:

group { 'resource_title':
  ensure => present
}

This will make sure a group named 'resource_title' gets created. You can also remove a group with "ensure => absent". There is an option to explicitly name a group with "name => 'group'", otherwise it is implied that you want to use 'resource_title' as your group name. The more I think about it, the more I like this functionality - it means you can do something like this:

group { 'add_special_group':
  name => 'special',
  ensure => present
}
group { 'remove_old_group':
  name => 'old_group',
  ensure => absent
}

With that said, to add a group called 'secops' with the 'group' resource type, I can change the manifest to look like this:

node stark {
  group {
    'secops':
      ensure => present
  }
}

Notice I moved the "resource title" to a line by itself. That's a personal preference because I don't like having anything after the opening brace - either way works.

Once the file is saved, that's it, that's all I have to do. The next time stark checks in with baratheon, it will get its new manifest and will add a group named 'secops'. If the group already exists it won't do anything because the requirement is already met. Since just waiting for stark to check in and update at its next interval is kind of boring, I'm going to force the check-in with 'puppet agent --test':



Now that I have a 'secops' group, I can add a 'secops' user who is a member of that group.

Adding a User


The syntax for the 'user' type is identical to that for the 'group' type (all Puppet resource types use the same syntax). For example, if I want to make sure a user named 'special_user' is present, I can do this:

user {
  'special_user':
    ensure => present
}

If the user doesn't exist, Puppet will add a user named 'special_user', a group named 'special_user' and create a home directory for that user. For the average user that's exactly what I might want but what if I want a group named 'infosec' and accounts for 'analyst_0', 'analyst_1' and 'analyst_2' that are all members of the 'infosec' and 'sudo' groups? Puppet can do that!

Building off the above, to add my 'secops' user I will use:

user {
  'secops':
    ensure => present,
    gid => 'secops',
    groups => ["sudo"],
    managehome => true,
    password_max_age => '90',
    password => '$1$ekSmGk/O$ne219/isubq6Q26jE8CKa.'
  }

This does *a lot*. Let's step through it.

First, it makes sure there is a user named 'secops'. Then it sets the primary group for the user to 'secops' and makes sure the account is also a member of the 'sudo' group. "sudo" doesn't need to be passed as an array because I'm only adding the user to that one additional group but I'm doing it as an array anyway to show you can pass an array of groups. Even though Puppet defaults to managing whether a home directory is created, I explicitly tell it to manage this user's home directory. On Linux, Solaris and AIX, Puppet can set a password expiration age so I'm setting that to 90 days. Finally, I'm providing the password hash for my user. Linux can use multiple hash types for passwords, from MD5 to SHA512, and in production I might use SHA512, but since I'm typing this password hash I'm going to use MD5. An easy way to get an acceptable hash is with 'openssl passwd -1', which then prompts for the value to hash and uses MD5 to hash it (if you're curious, the password I hashed is 'secopspass'; if you want to crack that hash, the salt is the value between the second and third $ symbols).

The actual manifest and the results of forcing the check-in look like this:



Notice how the user now shows up in /etc/passwd, they're in the groups 'secops' and 'sudo' and they have a home directory of '/home/secops'. Success!

Removing the Existing Users


Now that I have the user and group added that I want, I can set about removing the existing users I no longer need. This is almost a copy-and-paste of something I wrote above. To remove the 'test' user, I can use:

user {
  'test':
    ensure => absent,
    managehome => true
}

I also want to remove the *group* named 'test', since that group only existed for the 'test' user. This would look like:

group {
  'test':
    ensure => absent
}

Since I want to remove the 'demo' user from other systems, I'm going to go ahead and add a section for that as well. It won't do anything on stark but this is setup for my third post.

user {
  'test':
    ensure => absent,
    managehome => true
}
user {
  'demo':
    ensure => absent,
    managehome => true
}
group {
  'test':
    ensure => absent
}
group {
  'demo':
    ensure => absent
}

When the Puppet agent on stark checks in, it will delete the 'test' user -- but that's the user I'm using! To avoid any issues, I've logged out as the 'test' user and logged in with 'secops' (remember, the password is 'secopspass'). The manifest and results of forcing the check-in look like this (so that I could get it in a screenshot, I've moved the resource titles to the same line as the opening braces):



The user and group have been removed: the account doesn't show up in /etc/passwd, the group doesn't show up in /etc/group and the user's home directory is gone. I now have my 'standard' set of end-users and groups set on stark!

Wrapping Up


My goal was to show how to add and remove both users and groups with Puppet - we've achieved that. To add more users to stark I would just add more user sections. The same goes for groups - to add more, just add more sections.

That's _great_ for stark, but what about the other systems? It's a bit tedious to copy and paste all of that Puppet code into the manifest for each system, isn't it? I'm only working with four servers so it isn't too terrible but imagine having to add a user to forty, four hundred or maybe even four *thousand* systems. Copying that code into each system's manifest would take longer than both writing AND RUNNING the script to do the work for you. That's where modules, roles and profiles come in and in my next post I'm going to cover how to create a profile for (and assign that profile to) all of my Linux servers so that when I need to change the password hash, add new users or change the password age, I only have to edit one file and everything gets sorted as systems check in.

No comments:

Post a Comment

Note: only a member of this blog may post a comment.

A New Year, A New Lab -- libvirt and kvm

For years I have done the bulk of my personal projects with either virtualbox or VMWare Professional (all of the SANS courses use VMWare). R...