Continuous integration for Puppet modules

I just patched puppet-gluster and puppet-ipa to bring their infrastructure up to date with the current state of affairs…

What’s new?

  • Better README’s
  • Rake syntax checking (fewer oopsies)
  • CI (testing) with travis on git push (automatic testing for everyone)
  • Use of .pmtignore to ignore files from puppet module packages (finally)
  • Pushing modules to the forge with blacksmith (sweet!)

This last point deserves another mention. Puppetlabs created the “forge” to try to provide some sort of added value to their stewardship. Personally, I like to look for code on github instead, but nevertheless, some do use the forge. The problem is that to upload new releases, you need to click your mouse like a windows user! Someone has finally solved that problem! If you use blacksmith, a new build is just a rake push away!

Have a look at this example commit if you’re interested in seeing the plumbing.

Better documentation and FAQ answering:

I’ve answered a lot of questions by email, but this only helps out individuals. From now on, I’d appreciate if you asked your question in the form of a patch to my FAQ. (puppet-gluster, puppet-ipa)

I’ll review and merge your patch, including a follow-up patch with the answer! This way you’ll get more familiar with git and sending small patches, everyone will benefit from the response, and I’ll be able to point you to the docs (and even a specific commit) to avoid responding to already answered questions. You’ll also have the commit information of something else who already had this problem. Cool, right?

Happy hacking,

James

Hybrid management of FreeIPA types with Puppet

(Note: this hybrid management technique is being demonstrated in the puppet-ipa module for FreeIPA, but the idea could be used for other modules and scenarios too. See below for some use cases…)

The error message that puppet hackers are probably most familiar is:

Error: Duplicate declaration: Thing[/foo/bar] is already declared in file /tmp/baz.pp:2; 
cannot redeclare at /tmp/baz.pp:4 on node computer.example.com

Typically this means that there is either a bug in your code, or someone has defined something more than once. As annoying as this might be, a compile error happens for a reason: puppet detected a problem, and it is giving you a chance to fix it, without first running code that could otherwise leave your machine in an undefined state.

The fundamental problem

The fundamental problem is that two or more contradictory declarative definitions might not be able to be properly resolved. For example, assume the following code:

package { 'awesome':
    ensure => present,
}

package { 'awesome':
    ensure => absent,
}

Since the above are contradictory, they can’t be reconciled, and a compiler error occurs. If they were identical, or if they would produce the same effect, then it wouldn’t be an issue, however this is not directly allowed due to a flaw in the design of puppet core. (There is an ensure_resource workaround, to be used very cautiously!)

FreeIPA types

The puppet-ipa module exposes a bunch of different types that map to FreeIPA objects. The most common are users, hosts, and services. If you run a dedicated puppet shop, then puppet can be your interface to manage FreeIPA, and life will go on as usual. The caveat is that FreeIPA provides a stunning web-ui, and a powerful cli, and it would be a shame to ignore both of these.

The FreeIPA webui is gorgeous. It even gets better in the new 4.0 release.

The FreeIPA webui is gorgeous. It even gets better in the new 4.0 release.

Hybrid management

As the title divulges, my puppet-ipa module actually allows hybrid management of the FreeIPA types. This means that puppet can be used in conjunction with the web-ui and the cli to create/modify/delete FreeIPA types. This took a lot of extra thought and engineering to make possible, but I think it was worth the work. This feature is optional, but if you do want to use it, you’ll need to let puppet know of your intentions. Here’s how…

Type excludes

In order to tell puppet to leave certain types alone, the main ipa::server class has type_excludes. Here is an excerpt from that code:

# special
# NOTE: host_excludes is matched with bash regexp matching in: [[ =~ ]]
# if the string regexp passed contains quotes, string matching is done:
# $string='"hostname.example.com"' vs: $regexp='hostname.example.com' !
# obviously, each pattern in the array is tried, and any match will do.
# invalid expressions might cause breakage! use this at your own risk!!
# remember that you are matching against the fqdn's, which have dots...
# a value of true, will automatically add the * character to match all.
$host_excludes = [],       # never purge these host excludes...
$service_excludes = [],    # never purge these service excludes...
$user_excludes = [],       # never purge these user excludes...

Each of these excludes lets you specify a pattern (or an array of patterns) which will be matched against each defined type, and which, if matched, will ensure that your type is not removed if the puppet definition for it is undefined.

Currently these type_excludes support pattern matching in bash regexp syntax. If there is a strong demand for regexp matching in either python or ruby syntax, then I will add it. In addition, other types of exclusions could be added. If you’d like to exclude based on some types value, creation time, or some other property, these could be investigated. The important thing is to understand your use case, so that I know what is both useful and necessary.

Here is an example of some host_excludes:

class { '::ipa::server':
    host_excludes => [
        "'foo-42.example.com'",                  # exact string match
        '"foo-bar.example.com"',                 # exact string match
        "^[a-z0-9-]*\\-foo\\.example\\.com$",    # *-foo.example.com or:
        "^[[:alpha:]]{1}[[:alnum:]-]*\\-foo\\.example\\.com$",
        "^foo\\-[0-9]{1,}\\.example\\.com"       # foo-<\d>.example.com
    ],
}

This example and others are listed in the examples/ folder.

Type modification

Each type in puppet has a $modify parameter. The significance of this is quite simple: if this value is set to false, then puppet will not be able to modify the type. (It will be able to remove the type if it becomes undefined, which is what the type_excludes mentioned above is used for.)

This $modify parameter is particularly useful if you’d like to define your types with puppet, but allow them to be modified afterwards by either the web-ui or the cli. If you change a users phone number, and this parameter is false, then it will not be reverted by puppet. The usefulness of this field is that it allows you to define the type, so that if it is removed manually in the FreeIPA directory, then puppet will notice its absence, and re-create it with the defaults you originally defined.

Here is an example user definition that is using $modify:

ipa::server::user { 'arthur@EXAMPLE.COM':
    first => 'Arthur',
    last => 'Guyton',
    jobtitle => 'Physiologist',
    orgunit => 'Research',
    #modify => true, # optional, since true is the default
}

By default, in true puppet style, the $modify parameter defaults to true. One thing to keep in mind: if you decide to update the puppet definition, then the type will get updated, which could potentially overwrite any manual change you made.

Type watching

Type watching is the strict form of type modification. As with type modification, each type has a $watch parameter. This also defaults to true. When this parameter is true, each puppet run will compare the parameters defined in puppet with what is set on the FreeIPA server. If they are different, then puppet will run a modify command so that harmony is reconciled. This is particularly useful for ensuring that the policy that you’ve defined for certain types in puppet definitions is respected.

Here’s an example:

ipa::server::host { 'nfs':    # NOTE: adding .${domain} is a good idea....
    domain => 'example.com',
    macaddress => "00:11:22:33:44:55",
    random => true,        # set a one time password randomly
    locality => 'Montreal, Canada',
    location => 'Room 641A',
    platform => 'Supermicro',
    osstring => 'RHEL 6.6 x86_64',
    comment => 'Simple NFSv4 Server',
    watch => true,    # read and understand the docs well
}

If someone were to change one of these parameters, puppet would revert it. This detection happens through an elaborate difference engine. This was mentioned briefly in an earlier article, and is probably worth looking at if you’re interested in python and function decorators.

Keep in mind that it logically follows that you must be able to $modify to be able to $watch. If you forget and make this mistake, puppet-ipa will report the error. You can however, have different values of $modify and $watch per individual type.

Use cases

With this hybrid management feature, a bunch of new use cases are now possible! Here are a few ideas:

  • Manage users, hosts, and services that your infrastructure requires, with puppet, but manage non-critical types manually.
  • Manage FreeIPA servers with puppet, but let HR manage user entries with the web-ui.
  • Manage new additions with puppet, but exclude historical entries from management while gradually migrating this data into puppet/hiera as time permits.
  • Use the cli without fear that puppet will revert your work.
  • Use puppet to ensure that certain types are present, but manage their data manually.
  • Exclude your development subdomain or namespace from puppet management.
  • Assert policy over a select set of types, but manage everything else by web-ui and cli.

Testing with Vagrant

You might want to test this all out. It’s all pretty automatic if you’ve followed along with my earlier vagrant work and my puppet-gluster work. You don’t have to use vagrant, but it’s all integrated for you in case that saves you time! The short summary is:

$ git clone --recursive https://github.com/purpleidea/puppet-ipa
$ cd puppet-ipa/vagrant/
$ vs
$ # edit puppet-ipa.yaml (although it's not necessary)
$ # edit puppet/manifests/site.pp (optionally, to add any types)
$ vup ipa1 # it can take a while to download freeipa rpm's
$ vp ipa1 # let the keepalived vip settle
$ vp ipa1 # once settled, ipa-server-install should run
$ vfwd ipa1 80:80 443:443 # if you didn't port forward before...
# echo '127.0.0.1   ipa1.example.com ipa1' >> /etc/hosts
$ firefox https://ipa1.example.com/ # accept self-sign https cert

Conclusion

Sorry that I didn’t write this article sooner. This feature has been baked in for a while now, but I simply forgot to blog about it! Since puppet-ipa is getting quite mature, it might be time for me to create some more formal documentation. Until then,

Happy hacking,

James

 

Securely managing secrets for FreeIPA with Puppet

Configuration management is an essential part of securing your infrastructure because it can make sure that it is set up correctly. It is essential that configuration management only enhance security, and not weaken it. Unfortunately, the status-quo of secret management in puppet is pretty poor.

In the worst (and most common) case, plain text passwords are found in manifests. If the module author tried harder, sometimes these password strings are pre-hashed (and sometimes salted) and fed directly into the consumer. (This isn’t always possible without modifying the software you’re managing.)

On better days, these strings are kept separate from the code in unencrypted yaml files, and if the admin is smart enough to store their configurations in git, they hopefully separated out the secrets into a separate repository. Of course none of these solutions are very convincing to someone who puts security at the forefront.

This article describes how I use puppet to correctly and securely setup FreeIPA.

Background:

FreeIPA is an excellent piece of software that combines LDAP and Kerberos with an elegant web ui and command line interface. It can also glue in additional features like NTP. It is essential for any infrastructure that wants single sign on, and unified identity management and security. It is a key piece of infrastructure since you can use it as a cornerstone, and build out your infrastructures from that centrepiece. (I hope to make the puppet-ipa module at least half as good as what the authors have done with FreeIPA core.)

Mechanism:

Passing a secret into the FreeIPA server for installation is simply not possible without it touching puppet. The way I work around this limitation is by generating the dm_password on the FreeIPA server at install time! This typically looks like:

/usr/sbin/ipa-server-install --hostname='ipa.example.com' --domain='example.com' --realm='EXAMPLE.COM' --ds-password=`/usr/bin/pwgen 16 1 | /usr/bin/tee >( /usr/bin/gpg --homedir '/var/lib/puppet/tmp/ipa/gpg/' --encrypt --trust-model always --recipient '24090D66' > '/var/lib/puppet/tmp/ipa/gpg/dm_password.gpg' ) | /bin/cat | /bin/cat` --admin-password=`/usr/bin/pwgen 16 1 | /usr/bin/tee >( /usr/bin/gpg --homedir '/var/lib/puppet/tmp/ipa/gpg/' --encrypt --trust-model always --recipient '24090D66' > '/var/lib/puppet/tmp/ipa/gpg/admin_password.gpg' ) | /bin/cat | /bin/cat` --idstart=16777216 --no-ntp --selfsign --unattended

This command is approximately what puppet generates. The interesting part is:

--ds-password=`/usr/bin/pwgen 16 1 | /usr/bin/tee >( /usr/bin/gpg --homedir '/var/lib/puppet/tmp/ipa/gpg/' --encrypt --trust-model always --recipient '24090D66' > '/var/lib/puppet/tmp/ipa/gpg/dm_password.gpg' ) | /bin/cat | /bin/cat`

If this is hard to follow, here is the synopsis:

  1. The pwgen command is used generate a password.
  2. The password is used for installation.
  3. The password is encrypted with the users GPG key and saved to a file for retrieval.
  4. The encrypted password is (optionally) sent out via email to the admin.

Note that the email portion wasn’t shown since it makes the command longer.

Where did my GPG key come from?

Any respectable FreeIPA admin should already have their own GPG key. If they don’t, they probably shouldn’t be managing a security appliance. You can either pass the public key to gpg_publickey or specify a keyserver with gpg_keyserver. In either case you must supply a valid recipient (-r) string to gpg_recipient. In my case, I use my keyid of 24090D66, which can be used to find my key on the public keyservers. In either case, puppet knows how to import it and use it correctly. A security audit is welcome!

You’ll be pleased to know that I deliberately included the options to use your own keyserver, or to specify your public key manually if you don’t want it stored on any key servers.

But, I want a different password!

It’s recommended that you use the secure password that has been generated for you. There are a few options if you don’t like this approach:

  • The puppet module allows you to specify the password as a string. This isn’t recommended, but it is useful for testing and compatibility with legacy puppet environments that don’t care about security.
  • You can use the secure password initially to authenticate with your FreeIPA server, and then change the password to the one you desire. Doing this is outside the scope of this article, and you should consult the FreeIPA documentation.
  • You can use puppet to regenerate a new password for you. This hasn’t been implemented yet, but will be coming eventually.
  • You can use the interactive password helper. This takes the place of the pwgen command. This will be implemented if there is enough demand. During installation, the admin will be able to connect to a secure console to specify the password.

Other suggestions will be considered.

What about the admin password?

The admin_password is generated following the same process that was used for the dm_password. The chance that the two passwords match is probably about:

1/((((26*2)+10)^16)^2) = ~4.4e-58

In other words, very unlikely.

Testing this easily:

Testing this out is quite straightforward. This process has been integrated with vagrant for easy testing. Start by setting up vagrant if you haven’t already:

Vagrant on Fedora with libvirt (reprise)

Once you are comfortable with vagrant, follow these steps for using Puppet-IPA:

git clone --recursive https://github.com/purpleidea/puppet-ipa
cd vagrant/
vagrant status
# edit the puppet-ipa.yaml file to add your keyid in the recipient field
# if you do not add a keyid, then a password of 'password' will be used
# this default is only used in the vagrant development environment
vagrant up puppet
vagrant up ipa

You should now have a working FreeIPA server. Login as root with:

vscreen root@ipa

yay!

Hope you enjoyed this.

Happy hacking,

James

 

Pushing Puppet at Puppet Camp DC, LISA 2013

Hi there,

I hope you enjoyed my “Pushing Puppet (to its limit)” talk and demos from Puppet Camp D.C., LISA 2013. As requested, I’ve posted the code and slides.

Here is the code:

https://github.com/purpleidea/puppet-pushing

This module will require three modules as dependencies. The dependencies are:

Each example doesn’t require all the dependencies, so if you’re only interested in the FSM, you only need that module.

Here are the slides:

https://github.com/purpleidea/puppet-pushing/blob/master/talks/pushing-puppet.pdf

Here is the bug fix to fix my third Exec[‘again’] demo:

https://github.com/purpleidea/puppet-common/commit/df3d004044f013415bb6001a2defd64b587d3b85

It’s my fault that I added the fancy –delta support, but forgot to test the simpler, version again. Woops.

I’ve previously written about some of this puppet material. Read through these articles for more background and details:

I haven’t yet written articles about all the techniques used during my talk. I’ll try to write future articles about these topics if you’re interested.

If anyone has some photos from the talk, I’d love for you to send me a copy.

Special thanks to Kara, Dawn and Puppet Labs for asking me to present.

If you’d like to invite me to teach, talk or consult, I’d love to come visit your {$SCHOOL, $WORK, $CITY, etc}. Contact me! I’ll be around in D.C. till Friday if you’d like to meet up and hack on some of the code or examples that I’ve published.

If you’re interested in looking at some of the “real work” modules that I’ve written, have a look through my github repositories. Puppet-Gluster and Puppet-IPA, are two that you might find most interesting.

There are a few that I haven’t yet published, so if you’re looking for a fancy module to do X, let me know and I might be a few commits away from something helpful that I haven’t made public yet.

I hope you enjoyed hacking on puppet with me, and please don’t be shy — leave me a comment about my talk, and ask questions if you have any.

Happy Hacking,

James

 

a puppet-ipa user type and a new difference engine

A simple hack to add a user type to my puppet-ipa module turned out to cause quite a stir. I’ve just pushed these changes out for your testing:

3 files changed, 1401 insertions(+), 215 deletions(-)

You should now have a highly capable user type, along with some quick examples.

I’ve also done a rewrite of the difference engine, so that it is cleaner and more robust. It now uses function decorators and individual function comparators to help wrangle the data into easily comparable forms. This should make adding future types easier, and less error prone. If you’re not comfortable with ruby, that’s okay, because it’s written in python!

Have a look at the commit message, and please test this code and let me know how it goes.

Happy hacking,

James

PS: This update also adds server configuration globals management which you may find useful. Not all keys are supported, but all the framework and placeholders have been added.

 

Fresh releases! puppet-ipa, puppet-nfs, puppet-gluster

I’ve been a little slow in making release announcements, so here’s some news:

I’ve just released the third stage of my puppet-ipa module. At the moment it now supports installation, managing of hosts, and managing of services. It integrates with my puppet-nfs module to allow you to easily setup and run an NFSv4 kerberized server and client.

While we’re at it, that’s some more news: I’ve just released a puppet-nfs module to make your /etc/exports management easier. It’s designed to manage other security types, or even to work without kerberos or any authentication at all, but I haven’t tested those.

Back to puppet-ipa for a moment. I’d like you to know that I went to great lengths to make this a very versatile module. Some users probably want certain resources managed by puppet, and others not. With the included features, you can even specify exclusion criteria so that a certain pattern of hosts aren’t touched by puppet. This is useful if you’re slowly converting your ipa setup to be managed by puppet.

You can use $watch and $modify, two special parameters that I added to precisely control what kind of changes you want to allow puppet to make. These are kind of complicated to explain, but suffice it to say that this module should handle whatever situation you’re in.

For the security minded folks, puppet-ipa, never transfers or touches a keytab file. It will securely and automatically provision your hosts and services without storing secret information in puppet. The module isn’t finished, but it’s built right.

Gluster users might find this particular trio useful for offering gluster backed, kerberized, NFS exports. Here’s an example that I made just for you.

Since you sound like you’re having fun deploying servers like crazy, it’s probably useful to have a puppet-cobbler module. I’ve released this module because it’s useful to me, however it really isn’t release ready, but I think it’s better than some (most?) of the other puppet-cobbler code that’s out there. One other warning is that I have a large rearchitecturing planned for this module, so don’t get too attached. It’s going to get better!

So that’s your lot for today, have fun, and

Happy Hacking!

James

PS: If you’re in a giving mood, I’m in the need for some x86_64 compatible test hardware. If you’re able to donate, please let me know!