Multiple AllowGroups entries in sshd_config with Puppet and Augeas

2,341 views
Skip to first unread message

Hugo Cisneiros (Eitch)

unread,
Dec 22, 2010, 8:21:58 PM12/22/10
to puppet...@googlegroups.com
Hi,

After extensively looking into puppet + augeas for managing the
AllowGroups in sshd_config, I came to the conclusion that it won't
work as I expected :( So I'm sharing my thoughts here.

The main objective is allowing multiple groups per-node, depending on
what the security team wants. Since I want this to be dynamic, I
created a define in a class:

class ssh::server::config inherits ssh::config {
define addallowgroup() {
augeas {
"sshd_conf_group_${name}":
context => "/files/etc/ssh/sshd_config",
require => File["/etc/ssh/sshd_config"],
notify => Service["sshd"],
changes => "set AllowGroups/*[last()+1] ${name}",
onlyif => " match AllowGroups/*[.='${name}'] size == 0";
}
}
}

Then on a node, I can use this:

node "webserver" {
ssh::server::config::addallowgroup { ["test1", "test2", "test3"]: }
}

Sadly, the "changes" and "onlyif" lines in the augeas type does not
work because the sshd_config's lens creates a unique node/label for
each option. Quoting Augeas' website:

"
http://augeas.net/page/Adding_nodes_to_the_tree

You can use a special trick to append to a list of nodes that all have
the same name, for example to append a new alias to an entry in
/etc/hosts:

set $hosts/1/alias[last()+1] myhost.example.com

The predicate [last()+1] forces set to create a new node. Of course,
after the node is created, it is now reachable as
$hosts/1/alias[last()]. It's important to remember that creating nodes
with set can only work if the labels for all the nodes that need to be
created are known explicitly. In particular, you can't add a new host
entry using something like set $hosts/*[last()+1]/ipaddr 192.168.0.1 —
there's no way for Augeas to know what the new node for *[last()+1]
should be called.
"

In the example on hosts, the "alias" label is already named. So I
can't think on adding another node/label dynamically.

The alternative could be creating one augeas type for each group and
using them on the nodes, like this:

augeas {
"sshd_conf_group_test1":
context => "/files/etc/ssh/sshd_config",
require => File["/etc/ssh/sshd_config"],
notify => Service["sshd"],
changes => "set AllowGroups/1 test1",
onlyif => " match AllowGroups/1[.='test1'] size == 0";

"sshd_conf_group_test2":
context => "/files/etc/ssh/sshd_config",
require => File["/etc/ssh/sshd_config"],
notify => Service["sshd"],
changes => "set AllowGroups/2 test2",
onlyif => " match AllowGroups/2[.='test2'] size == 0";

"sshd_conf_group_test1":
context => "/files/etc/ssh/sshd_config",
require => File["/etc/ssh/sshd_config"],
notify => Service["sshd"],
changes => "set AllowGroups/3 test3",
onlyif => " match AllowGroups/3[.='test3'] size == 0";
}

When we have much groups, this becomes very long :(

Anyone here have some idea for a good practice? :) Or maybe this is
just plain impossible.

Versions:
puppet-0.25.5
augeas-0.7.3

Thanks!

--
[]'s
Hugo
www.devin.com.br

Brian Gallew

unread,
Dec 23, 2010, 2:44:33 PM12/23/10
to puppet...@googlegroups.com
So, once again usability is sacrificed in favor of purity.

If you are using Puppet 2.6.1, you can move evaluation of the sshd_config.erb template to a "post" stage, and then generate your AllowGroups stanza from the class list (which will be complete).  Here's an excerpt from my sshd_config.erb that I used before upgrading to 2.6.3 (which broke this behavior):

# This is where the access control bits live
<%
my_login_groups = ['root', 'wheel', 'sysadmin']
my_login_groups << 'oinstall' if classes.index('oracle') != nil
my_login_groups << 'dba' if classes.index('oracle') != nil
my_login_groups << 'puppet' if classes.index('puppet::master') != nil
my_login_groups << 'jboss' if classes.index('jboss') != nil
my_login_groups << 'nagios' if classes.index('nagios::server') != nil
# more deleted
%>
AllowGroups <%= my_login_groups.join(' ') %>
With 2.6.3, you have to do this in a two-stage process.  First, write a custom fact that returns the contents of classlist.txt on the client.  Then change the above code to work with that fact.  In my environment, the equivalent to the above looks like this:

# This is where the access control bits live
<%
### "pps" stands for "Puppet purity sucks"
pps = cprt_classes.split(',')
my_login_groups = ['root', 'wheel', 'sysadmin']
my_login_groups << 'oinstall' if pps.index('oracle') != nil
my_login_groups << 'dba' if pps.index('oracle') != nil
my_login_groups << 'puppet' if pps.index('puppet::master') != nil
my_login_groups << 'jboss' if pps.index('jboss') != nil
my_login_groups << 'nagios' if pps.index('nagios::server') != nil
# more deleted
%>
AllowGroups <%= my_login_groups.join(' ') %>

My irritation with the behavioral change aside, this works just as well as the code above, except that it takes two Puppet runs to be correct.

If the Puppet DSL had some kind of useful container type, we could put all the configuration bits in the manifests instead of duplicating this crazy templating all over the place.



--
You received this message because you are subscribed to the Google Groups "Puppet Users" group.
To post to this group, send email to puppet...@googlegroups.com.
To unsubscribe from this group, send email to puppet-users...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.


nvanheu...@kronostechnologies.com

unread,
Apr 12, 2016, 8:58:38 AM4/12/16
to Puppet Users, hugo.ci...@gmail.com
I know this might be old but I finally found a way to do this.

set /files/etc/ssh/sshd_config/AllowGroups/0[last()+1] ${username}

This will append a node.

Cheers,
Reply all
Reply to author
Forward
0 new messages