So far the ibox modules didn’t manage any authorized_keys for the user root, while the sshd module enforced that the root user can’t login using the password. We had the functionality already implemented in our internal code base, but it originated from the very early puppet days and it lacked several features, like purging non-managed keys or being able to selectively allow certain keys only on certain hosts.

With a recent commit to the ibox modules we introduced this functionality also on the ibox with all the missing features.

We introduced a new variable within the main ibox class called $root_keys and if this variable is not empty, we will generate a set of sshd::autorized_keys resources.

Furthermore, it will activate the purge_ssh_keys feature for the user root, purging any unmanaged keys in the authorized_keys file of the user root.

An implementation detail is that $root_keys is not a class variable, but just a normal variable within the class. This variable is being looked up using hiera_hash(). hiera_hash() and has the advantage that puppet will traverse your whole hiera-hierarchy and merge all the found hashes together into one. This will allow us to configure a set of keys for some users on all systems (putting them under ibox::root_keys in the defaults file), while allowing a key for a a user – that should have access to only one or a few systems – into the specific place in the hierarchy. Like:

$ cat hieradata/hosts/ibox-two.local.yaml
    key: 'AAAAA......'
$ cat hieradata/defaults.yaml
    key: 'AAAAA......'
    key: 'AAAAA......'

This will add the keys for userA & userB on all systems, while user1 only gets added to the host ibox-two.local. userA & userB will be added there as well.

Still: Why not a class parameter? If we would define $root_keys as a class parameter of the class ibox, puppet would start looking up the value for ibox::root_keys using its normal internal puppet hiera lookup mechanism and only reaching out to the code-default (which is hiera_hash('ibox::root_keys',{}) if nothing would be found. However, if we use the same lookup key also for the hiera_hash-lookup, puppet will already find something in the first face and this won’t be merged, because the lookup mechanism is using the standard priority mechanism.

We could have chosen another name either for the variable or the merge lookup key, which would have worked and done what we wanted. On the other hand: puppet would have still used its internal lookup mechanism first and we would have traversed the whole hierarchy without any need. Also it would have been possible to ignore the merge lookup by just setting a value for the wrong key in hiera. By not defining it as a class parameter, it makes the key more consistent, while avoiding any accidental overwrites of the features. Plus it saves us a few hiera calls.

Another implementation detail is the usage of stages. While stages seem to be nice to be able to have implicit dependencies without having to declare too many require/before statements, they can become very quickly quite ugly. This is one of the reasons, why the official documentation doesn’t recommend them. Except for package repository setups, which is exactly what we use them for. However, because we also manage a few files owned by the user root as part of the yum stage, this stage also requires the user root to be managed, as puppet’s autorequire feature will kick in and you hence will end up in a dependency loop. This is why we had to put the management of the user root (and it’s associated keys) to a dedicated stage that precedes the yum stage.