Using Puppet and Ldap to manage access.conf
Our organization is using ldap for user authentication. We've utilized pam_access to restrict user login's to machines. The pam_access module allows us to declare what user accounts are allowed to login to what machines. Pam_access also allows the use of netgroups to declare those user accounts and hosts. So we've structured our netgroups to define groups of users assigned to a particular role, and groups of machines purposed for a particular function, or what we call a platform. Since we base our management at the application level, we've structured them (what we call a platform) in ldap to provide group management for user access and hosts, based on the platform.
For example, given a platform name of "tkc" (TomKat Consulting - ;-) ), and environments of "dev", "ppe", and "prd" (for development, pre-production, and production) our netgroup definitions are as follows:
tkcdevusers: This netgroup holds references to user accounts that are considered developers for platform tkc
tkcqausers: This netgroup holds references to user accounts that are considered qa members for platform tkc
tkcopsusers: This netgroup holds references to user accounts that are considered operations support for platform tkc
tkcdevhosts: This netgroup holds references to hosts that are used for development by platform tkc
tkcppehosts: This netgroup holds references to hosts that are used for ppe by platform tkc
tkcprdhosts: This netgroup holds references to hosts that are used for production by platform tkc
So lets presume that we have tkc developer accounts. These accounts will only be allowed to login to only the tkc development machines.
login=toms
login=tees
Here's our tkc support accounts. These accounts will be allowed to login to any of the tkc machines.
login=miked
login=marys
login=mikep
login=kats
Here's our tkc qa support accounts. These accounts will be allowed to login to only the tkc ppe machines.
login=mikes
login=joanns
Here's our tkc hosts
development: tkc-dev-01, tkc-dev-02
ppe: tkc-ppe-01, tkc-ppe-02
prd: tkc-prd-01, tkc-prd-01
Our access rules are:
support accounts are allowed to login to all tkc hosts
dev accounts are allowed to login to only tkc dev hosts
qa accounts are allowed to login to only tkc ppe hosts
So our netgroup definitions would be as follows:
# definition for tkc development users
dn: cn=tkcdevusers,ou=netgroup,dc=tsand,dc=org
cn: tkcdevusers
objectclass: top
objectclass: nisNetgroup
nisNetgroupTriple: (,toms,)
nisNetgroupTriple: (,tees,)
# definition for tkc support (ops) users
dn: cn=tkcopsusers,ou=netgroup,dc=tsand,dc=org
cn: tkcopsusers
objectclass: top
objectclass: nisNetgroup
nisNetgroupTriple: (,miked,)
nisNetgroupTriple: (,marys,)
nisNetgroupTriple: (,mikep,)
nisNetgroupTriple: (,kats,)
dn: cn=tkcqausers,ou=netgroup,dc=tsand,dc=org
cn: tkcqausers
objectclass: top
objectclass: nisNetgroup
nisNetgroupTriple: (,mikes,)
nisNetgroupTriple: (,joanns,)
# definition for tkc development hosts
dn: cn=tkcdevhosts,ou=netgroup,dc=tsand,dc=org
cn: tkcdevhosts
objectclass: top
objectclass: nisNetgroup
nisNetgroupTriple: (tkc-dev-01,,)
nisNetgroupTriple: (tkc-dev-02,,)
# definition for tkc ppe hosts
dn: cn=tkcppehosts,ou=netgroup,dc=tsand,dc=org
cn: tkcppehosts
objectclass: top
objectclass: nisNetgroup
nisNetgroupTriple: (tkc-ppe-01,,)
nisNetgroupTriple: (tkc-ppe-02,,)
# definition for tkc prd hosts
dn: cn=tkcprdhosts,ou=netgroup,dc=tsand,dc=org
cn: tkcprdhosts
objectclass: top
objectclass: nisNetgroup
nisNetgroupTriple: (tkc-prd-01,,)
nisNetgroupTriple: (tkc-prd-02,,)
# we also group all user accounts into a global netgroup
dn: cn=tkcallusers,ou=netgroup,dc=tsand,dc=org
cn: tkcallusers
objectclass: top
objectclass: nisNetgroup
memberNisNetgroup: tkcdevusers
memberNisNetgroup: tkcopsusers
memberNisNetgroup: tkcqausers
# and the same for the hosts netgroup
dn: cn=tkcallhosts,ou=netgroup,dc=tsand,dc=org
cn: tkcallusers
objectclass: top
objectclass: nisNetgroup
memberNisNetgroup: tkcdevhosts
memberNisNetgroup: tkcppehosts
memberNisNetgroup: tkcprdhosts
So, to accomplish the login rule above, our /etc/security/access.conf file would look like this:
+ : @tkcopsusers@@tkcallhosts : cron crond ALL
+ : @tkcdevusers@@tkcdevhosts : cron crond ALL
+ : @tkcqausers@@tkcppehosts: cron crond ALL
To allow root login & cron services, linuxadmin staff and to deny all other users access to the machine, our access.conf will end up like this:
+ : root : cron crond ALL
+ : @linuxadmin : cron crond ALL
+ : @tkcopsusers@@tkcallhosts : cron crond ALL
+ : @tkcdevusers@@tkcdevhosts : cron crond ALL
+ : @tkcqausers@@tkcppehosts: cron crond ALL
- : ALL : ALL
We will have the same structure for all of the other platforms we support. The advantage to this structure is that when a new user or machine is added to the platform, we merely update the appropriate netgroups.
While we could easily just have one common access.conf distributed to all platforms, since the netgroup references provide very good access restrictions, for readability we'd like to keep the access.conf small, with only the necessary entries pertinent to the platform, environment & machine. This is where puppet will be applied with a template to generate the access.conf.
I considered using a file resource with a source reference to the module files directory, with sub-directories indicating the platform. Since we're already using ldap ENC with puppet and we've extended the schema to include attributes of platform, environment, datacenter and role, I felt it was a natural progression to put the access.conf entries into ldap as well. Besides, I can update ldap from any machine on the network that has the ldap-tools, and access to an account with rights. Ldap has an excellent ACL mechanism, so I don't need to open permissions on my puppetmaster modules directory and I don't have to hassle with constantly refreshing the modules from version control.
I structured the following branches in ldap
ou=Access,dc=tsand,dc=org
cn=head ---> this object holds the fixed "head" part of the access.conf, the root & linuxadmin entries
cn=tail ---> this object holds the fixed "tail" part of the access.conf, the deny all others entry
ou=tkc ---> platform branch
ou=dev ---> platform-dev branch
cn=access ---> access rules for platform-dev
ou=ppe ---> platform-ppe branch
cn=access ---> access rules for platform-ppe
ou=prd ---> platform-prd branch
cn=access ---> access rules for platform-prd
I could have easily eliminated the environment branches above, given the way we've structured our netgroups, but for completeness, I broke them all apart.
Here's what my ou=Access,dc=tsand,dc=org DIT looks like:
# Access, tsand.org
dn: ou=Access,dc=tsand,dc=org
objectClass: organizationalUnit
objectClass: top
ou: Access
# tkc, Access, tsand.org
dn: ou=tkc,ou=Access,dc=tsand,dc=org
objectClass: organizationalUnit
objectClass: top
ou: tkc
# prd, tkc, Access, tsand.org
dn: ou=prd,ou=tkc,ou=Access,dc=tsand,dc=org
objectClass: organizationalUnit
objectClass: top
ou: prd
# head, Access, tsand.org
dn: cn=head,ou=Access,dc=tsand,dc=org
accessEntry: + : root : cron crond ALL
accessEntry: + : @linuxadmins : cron crond ALL
cn: head
description: access.conf head
objectClass: access
objectClass: top
# tail, Access, tsand.org
dn: cn=tail,ou=Access,dc=tsand,dc=org
objectClass: access
objectClass: top
description: access.conf tail
accessEntry: - : ALL : ALL
cn: tail
# ppe, tkc, Access, tsand.org
dn: ou=ppe,ou=tkc,ou=Access,dc=tsand,dc=org
objectClass: organizationalUnit
objectClass: top
ou: ppe
# dev, tkc, Access, tsand.org
dn: ou=dev,ou=tkc,ou=Access,dc=tsand,dc=org
objectClass: organizationalUnit
objectClass: top
ou: dev
# access, dev, tkc, Access, tsand.org
dn: cn=access,ou=dev,ou=tkc,ou=Access,dc=tsand,dc=org
objectClass: access
objectClass: top
cn: access
accessEntry: + : @tkcopsusers@@tkcallhosts : cron crond ALL
accessEntry: + : @tkcdevusers@@tkcdevhosts : cron crond ALL
description: platform tkc, environment dev, access.conf entries
# access, ppe, tkc, Access, tsand.org
dn: cn=access,ou=ppe,ou=tkc,ou=Access,dc=tsand,dc=org
objectClass: access
objectClass: top
cn: access
accessEntry: + : @tkcopsusers@@tkcallhosts : cron crond ALL
accessEntry: + : @tkcqausers@@tkcppehosts : cron crond ALL
description: platform tkc, environment ppe, access.conf entries
# access, prd, tkc, Access, tsand.org
dn: cn=access,ou=prd,ou=tkc,ou=Access,dc=tsand,dc=org
objectClass: access
objectClass: top
cn: access
accessEntry: + : @tkcopsusers@@tkcallhosts : cron crond ALL
description: platform tkc, environment prd, access.conf entries
Puppet module: /etc/puppet/modules/access
/etc/puppet/modules/access/manifests/init.pp
# /etc/puppet/modules/access/manifests/init.pp
# vi:set nu ai ap aw smd showmatch tabstop=4 shiftwidth=4:
class access {
file { "/etc/security/access.conf":
ensure => present,
owner => root,
group => root,
mode => 0644,
backup => false,
content => template("access/access.conf.erb"),
}
}
/etc/puppet/modules/templates/access.conf.erb
<%
suffix = scope.lookupvar('ldapsuffix')
platform = scope.lookupvar('platform')
environment = scope.lookupvar('environment')
headuri = "ou=access," + suffix
middleuri = "ou=#{environment},ou=#{platform},#{headuri}"
head = %x[ldapsearch -xb "#{headuri}" "(cn=head)" "accessEntry" | sed -n 's/^accessEntry: //gp']
middle = %x[ldapsearch -xb "#{middleuri}" "(cn=access)" "accessEntry" | sed -n 's/^accessEntry: //gp']
tail = %x[ldapsearch -xb "#{headuri}" "(cn=tail)" "accessEntry" | sed -n 's/^accessEntry: //gp'] -%>
# this file is auto-generated by puppet
# DO NOT EDIT
#
# cn=head,<%= headuri %>
<%= head -%>
# cn=access,<%= middleuri %>
<%= middle -%>
# cn=tail,<%= headuri %>
<%= tail -%>
After the puppet module is processed, the output file /etc/security/access.conf would look like:
on tkc prd machines:
# this file is auto-generated by puppet
# DO NOT EDIT
#
# cn=head,ou=access,dc=tsand,dc=org
+ : root : cron crond ALL
+ : @linuxadmins : cron crond ALL
# cn=access,ou=prd,ou=tkc,ou=access,dc=tsand,dc=org
+ : @tkcopsusers@@tkcallhosts : cron crond ALL
# cn=tail,ou=access,dc=tsand,dc=org
- : ALL : ALL
on tkc ppe machines:
# this file is auto-generated by puppet
# DO NOT EDIT
#
# cn=head,ou=access,dc=tsand,dc=org
+ : root : cron crond ALL
+ : @linuxadmins : cron crond ALL
# cn=access,ou=ppe,ou=tkc,ou=access,dc=tsand,dc=org
+ : @tkcqausers@@tkcppehosts : cron crond ALL
+ : @tkcopsusers@@tkcallhosts : cron crond ALL
# cn=tail,ou=access,dc=tsand,dc=org
- : ALL : ALL
on tkc dev machines:
# this file is auto-generated by puppet
# DO NOT EDIT
#
# cn=head,ou=access,dc=tsand,dc=org
+ : root : cron crond ALL
+ : @linuxadmins : cron crond ALL
# cn=access,ou=dev,ou=tkc,ou=access,dc=tsand,dc=org
+ : @tkcdevusers@@tkcdevhosts : cron crond ALL
+ : @tkcopsusers@@tkcallhosts : cron crond ALL
# cn=tail,ou=access,dc=tsand,dc=org
- : ALL : ALL
So, I manage all users and hosts in ldap, along with the access.conf rules per platform and environment. There's no need to change anything in the puppet modules should we add or remove machines or users. And if I decide to add additional environments to the platform, I merely update the ldap DIT, with no changes in the puppet modules.
I updated the ldap schema to support my definition of the "access" object and the "accessEntry" attribute. We're using RedHat Directory Server, and I place the following entries into the 99user.ldif file:
objectClasses: ( access-oid NAME 'access' DESC '' SUP top STRUCTURAL MUST ( accessEntry $ cn ) MAY description X-ORIGIN 'user defined' )
attributeTypes: ( accessEntry-oid NAME 'accessEntry' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'user defined' )
I just used the directory server GUI console to add the above objectclass (access) and attribute (accessEntry, a simple string - multivalued)
My friend Wayne asked for this. Here ya go Wayne!
*heh*