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*