published on 16 June 2017

One of the best ways to ensure good, solid security on systems across your organization is to establish baselines defining a minimum set of standards for access, firewalls and services. Puppet can help you define, build, and maintain those baselines in a straightforward way that’s just not possible when working at scale.

In this blog post, I cover a few basic steps you can take with Puppet to improve your security by building in some basic assertions about the state of your servers. We'll start with defining manifests that limit access to specific machines and users, and using Puppet to define firewall rules. Then I’ll talk a little about how these two steps — like other automated security measures — can give you a great audit trail that helps prove those measures are in place.

In many larger organizations, centralized authentication is the norm. In that environment you won’t be managing individual users on each machine, but you’ll still have service users, and you want to know that additional users haven’t been added to the servers. Puppet can do both of those if you configure it correctly.

Getting started

When approaching security, I always like to remind people that it’s not something you add on at the end, or on a whim. Building secure systems starts with building tight, robust systems from the outset. My approach has always been to build the base machines with the bare minimum that will boot in your environment, and then start the configuration management process.

In a RedHat or CentOS environment, this means a Kickstart file with a Puppet agent and whatever else you require. Puppet can then do the work of taking that machine from bare bones to whatever is needed to meet your business requirements. Do it this way, and you don’t have to worry about uninstalling unneeded services or removing default users. This baseline is the foundation for your secure systems.

The examples below presume that you’ve got Puppet installed and have at least one working node. Given that, let’s look at how Puppet can help you manage users and groups.

Improving security by better managing users and groups

Some of the sample code below can be done from the command line, but I’ll mostly be talking about using your Puppet master manifest, which is located here:

/etc/puppetlabs/code/environments/production/manifests/site.pp

If it doesn’t exist, create a site.pp file in that directory. That’s where you’ll save these configurations to ensure they are applied universally to all your nodes.

The first thing you should do is create a hash to use for the password so the real password doesn’t appear in plain text. You could do this by setting the password on one of your machines and getting the result out of /etc/shadow, or you could use the stdlib function pwhash to generate it. You don’t want to check the cleartext password in, though!

Armed with the password hash, you can use this small bit of Puppet code to create a typical user, adding him or her to a group, defining a home directory and defining a few other parameters:

user { 'bweiss':
     ensure           => 'present',
     gid              => '501',
     home             => '/home/bweiss',
     password         => '$1$9EGIYjL3$OFw9NSsHa.Wk0RwUHq4G31',
     password_max_age => '99999',
     password_min_age => '0',
     shell            => '/bin/bash',
     uid              => '501',
}

You also may want to add an SSH key for that user, using the (ssh_authorized_key)[https://docs.puppet.com/puppet/latest/types/ssh_authorized_key.html] type:

ssh_authorized_key { 'bweiss@bills-corporate-laptop':
    ensure  => present,
    user    => 'bweiss',
    type    => 'ssh-rsa',
    key     => 'ssh-key-string',
}

If your environment allows it, you can create users without passwords but with an SSH key, which means you don’t have to worry about password complexity at all.

Since we have security in mind, you should add this user to specific groups by adding the groups parameter:

groups => ['sudo', 'docker', 'mariadb', 'ssh','www-data'],

Creating and managing groups also can be easily done with Puppet, giving you a lot of control over a wide number of parameters, including:

group { 'resource title':
    name            => # (namevar) The group name.  
    ensure          => # Create or remove the group.   
    allowdupe       => # Whether to allow duplicate GIDs.
    attributes      => # Specify group AIX attributes, as an array.
    auth_membership => # Configures the behavior of the members.
    forcelocal      => # Forces the management of local accounts.
    gid             => # The group ID, specified numerically.
    members         => # The members of the group.
    system          => # Whether the group is a system group.
}

Clearly, there are a lot of choices, but you can create a group with a bare minimum:

group { 'log_users': 
    ensure => 'present',
}

As you think about creating groups and the users you include in them, think about who really needs to access what. Project A developers can access Project A box, right? But Project B developers shouldn’t need to. By defining users into groups, you get much more control over system access — and it’s something you might already be doing without Puppet.

If you have separate Linux and Windows teams, you might ask yourself if everyone needs to access every node, or if it makes more sense to create platform-specific groups. Ordinarily, you might skip this as unmanageable. With Puppet, you can easily include different group classes in your node or site manifests. Below, we use the results of Facter to first identify the operating system, and then give access only to the user groups you want:

if ($facts['os']['name'] == 'windows') {
    include windows_users
}
elseif ($facts[‘os’][‘name’] == 'Linux') {
    include linux_users
}

You can also manage the SSH config to allow only the correct groups to log in to your servers. Doing this plus managing the users provides a second level of security in case a user appears on the wrong machine.

Improve security by managing machine-level firewall rules

Most organizations have an edge firewall that controls most of the traffic entering and leaving their networks, and that’s a good thing. But firewalls aren’t just for the edge. With Puppet, you can get very specific about access to machines — both inside and outside your network.

Consider this: Should your application servers be allowed to communicate with each other via HTTP? Probably not. Should they be able to communicate with database servers? Yes, probably. With Puppet, you can turn these ideas into actions, which are then easy to replicate and trace.

This also provides a second level of protection in case something goes wrong with the edge firewall.

You can get started by installing the puppetlabs-firewall module if you’re using open-source Puppet, or the puppet-firewall module if you’re using Puppet Enterprise. Both enable control over a wide variety of firewall settings, including Linux iptables.

With these tools, you can create a base firewall class (here, called my_fw) that you can define in the normal Puppet way. In the example below, create the class and then add base parameters that can be applied to all your nodes. Note that each rule is given a number and a description that makes each rule self-evident and very readable, even for people who aren’t firewall experts:

class my_fw {
    Firewall {
        require => undef,
    }
    firewall { '000 accept all icmp':
        proto  => 'icmp',
        action => 'accept',
    }
    firewall { '001 accept all to lo interface':
        proto   => 'all',
        iniface => 'lo',
        action  => 'accept',
    }
    firewall { '002 reject local traffic not on loopback interface':
        iniface     => '! lo',
        proto       => 'all',
        destination => '127.0.0.1/8',
        action      => 'reject',
    }
    firewall { '003 accept related established rules':
        proto  => 'all',
        state  => ['RELATED', 'ESTABLISHED'],
        action => 'accept',
    }
    firewall { '999 reject other traffic':
        proto  => 'all',
        action => 'reject',
    }
}

You can see complete details about this example in the Puppet documentation. Once your Puppet nodes update, run $ sudo iptables -L and see something like this:

Chain INPUT (policy ACCEPT)
target    prot     opt       source    destination
ACCEPT    icmp     --        anywhere  anywhere    /* 000 accept all icmp */
ACCEPT    all      --        anywhere  anywhere    /* 001 accept all to lo interface */
REJECT    all      --        anywhere  loopback/8  /* 002 reject local traffic not on loopback interface */ 
ACCEPT    all      --        anywhere  anywhere    /* 003 accept related established rules */
REJECT    all      --        anywhere  anywhere

You could then add firewall rules to modules that install services. For instance, in a module that installs your web tier, you could include:

firewall { '100 allow HTTP traffic':
    proto  => tcp,
    dport  => [80, 443],
    action => accept
}

If you haven’t already, you should now notice how you could add variations to this basic example and specifically control access to different resources without much trouble. You might have sighed at the complexity of doing this manually, and so passed on this level of security, but Puppet makes it possible.

Use your classes and manifests as audit trails

Now that you’ve created and deployed user, group and firewall rules using Puppet classes and manifests, you can use them as security auditing tools. The above examples are just the start, but should give you a taste of what’s possible.

When the security team needs to guarantee that only a specific group of pre-approved developers has access to systems running on a customer’s application server, you can show your group's manifests. They’ll show compliance and your ability to make changes quickly. If someone leaves the team, you can edit a single manifest on your Puppet master and the developer will be removed from every node — and you can prove that's happened.

Because Puppet works on the principle of a desired state, users, groups and firewalls remain as you’ve described them. If someone purposely or inadvertently disables a firewall rule, Puppet will automatically make a corrective change to fix it. If you’re using Puppet Enterprise and its graphical dashboards, you can quickly see those changes in real time.

As you map out your security policies and procedures, Puppet can turn them into reality and give you important and immediate insight. If you set up only certain machines for, say, credit card processing, you can use Puppet to quickly identify them and enforce strict PCI rules on them. If someone drops a suspect file on a web server, starts a rogue service, adds themselves as a machine user or leaves behind some other indicator of compromise (IOC), Puppet can roll it back and alert you.

Security is easier with Puppet

No single tool can prevent all security breaches or problems, but Puppet offers a variety of ways for you to more easily add and maintain security measures that can harden your systems and make it much, much harder for bad guys to get a beachhead. You don’t have to start from scratch, either. Check out the dev-sec/puppet-os-hardening project on GitHub, and the SIMP tool as great examples of Puppet code written for security and compliance. Also look through the Puppet Forge (specifically the security modules) for prewritten, pretested modules that will help you get started.

Bill Weiss is a principal security architect at Puppet.

Learn more

Share via:
Posted in:

Add new comment

The content of this field is kept private and will not be shown publicly.

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.