System administrators who log onto a number of machines often don’t do so as
normal user (as they maybe ought to), then switching to root, but instead
directly log on as root. In an environment thus controlled by different
administrators, they commonly share a single user environment which is usually
set up by the system via a combination of /etc/profile
and ~/.profile
,
~/.bashrc
or similar. One difficulty in using a common environment is that
administrators desiring different settings need to either manually set up their
environment each time they log on, or otherwise make profiles which are
littered with _if_s. Even this is difficult to do if there is no exact method
of determining where the person logged in from (i.e. which client IP address)
or who the human being logging on actually is (i.e. what is the guy’s name?).
A sample /etc/profile
on one of my machines illustrates the point. Here the
IP address from where I log on is determined by looking for the tty name in
the list of currently logged on users.
If the workstations I log on from didn’t have a static IP address, such as when arriving via a dynamically set up VPN connection, I wouldn’t be able to determine this at all. Even so, it is a PITA and it is error-prone and requires maintenance.
I use ssh whenever I log onto a Linux or Unix system, telnet being too insecure. And since I’m quite lazy, I use RSA keys to log on to the systems, which avoids me having to enter my password every time I connect to a system to quickly check a mail queue, or the current disk space on the machine.
This program was was created because I was sick and tired of always finding myself in a shell which was in emacs mode and which didn’t have my favorite aliases set ;-)
ldp relies on the fact that SSH can be configured to set an environment variable whenever I connect to it.
For the next step, I want to have access to a central profile whichever system I log on to. It is important to me to have certain environment variables set up, programs that I need launched, and most important, I want vi-style editing in both the shell and programs that support GNU readline (e.g. the mysql program, etc.).
LDAP to the rescue. Anybody who knows me also knows that I swear by LDAP, specifically OpenLDAP’s implementation of the LDAP directory.
Even though I’m going to store my profile in my LDAP directory entry, there is no reason for not doing it differently. A similar service could easily be created to retrieve profile and settings from an HTTP server such as the excellent Apache HTTP server, or even simply from a file server. My reason for using an LDAP directory server is that I have a number of slave replica servers which ensure high service availability (and the fact that LDAP simply rocks).
Modus Operandi
Invoked from /etc/profile
upon login, ldp checks for the presence of an
environment variable set by the SSH daemon and uses that to determine the
username of the person loggin on. It then searches the LDAP directory for an
object of class ldpObject
, and if it finds it, decodes the profile it
contains and stores it in a file for the user. If the username also exists
locally (i.e. can be found in either /etc/passwd
or whichever is defined by
the system’s name service switch NSS) the file ownership is changed
accordingly. Just before exiting, ldp simply issues the path of that file to
stdout, allowing the invoking shell to source that in to its own
environment.
Installation
Installation of ldp is quite simple:
- Download the distribution
- Unpack the distribution and edit
ldp.h
, adjusting the defines to your needs. - Check the
Makefile
, possibly adding paths to the location of your LDAP libs make
andmake test
. The latter will print out the values of the settings you defined inldp.h
. Review those to ensure they are correct.- Create the profile directory
LDP_PDIR
and set appropriate permissions - Install the binary into a convenient location
- Set up the LDAP directory, ensuring the schema for
ldpObject
is loaded - Optionally set up ACLs for the directory server
- If required, restart your directory server
- Add a profile for yourself
- Test ldp with
LDP_USER=myname ./ldp ls -l /var/spool/ldp
- If all works, set up sshd to permit user environment strings
- On the target machines, set up root’s
~/.ssh/authorized_keys
to enable$LDP_USER
- Edit
/etc/profile
or equivalent to source the result of ldp
Download
Unpack & Install
Retrieve and unpack the tarball with the usual commands.
configure ldp.h
After changing into the ldp distribution directory, edit the file ldp.h
and adjust the definitions according to the instructions.
Makefile
You may need to adjust the paths to your LDAP libraries in the Makefile. Then
type make
to build the program, followed by make test
to ensure that ldp
has correct settings.
Create profile directory
ldp stores profiles retrieved from the LDAP directory server in a so-called
profile directory, the name of which you specified as LDP_PDIR
in ldp.h
.
You have to create this directory and set appropriate permissions for it. Do a
make spooldir
, alternatively issuing the following commands if you prefer to
do so manually:
Install ldp
Install the binary ldp into a conventient location, such as /usr/local/bin
.
A make install
does that for you.
Setting up the LDAP Directory Server
In the examples below, I’m supposing you are using a newver version of OpenLDAP, otherwise the instructions will have to be modified according to the requirements of your directory server.
Schema
For storing the shell-profile, I’ve created an auxilliary object class called
ldpObject
with an attribute type ldpSHprofile which holds the octets of the
profile proper, but you are of course welcome to modify that to suit your
needs.
As defined here, ldpObject
is an auxiliary class which is fine for adding it
to an existing person or inetOrgPerson object. You might want to modify the
schema definition if you want ldpObject
to be kept in their own container.
Container
I have forseen ldpObject
objects to be added to an existing user’s entry
which already has a structural object class such as person or
inetOrgPerson. Alternatively, it could be added to an account object as
defined in the Cosine schema. Still another method is to simply change the
AUXILIARY
definition into STRUCTURAL
and create objects in their own
container.
Some samples:
Access Control
A profile might contain sensitive information, and as such it ought to be protected from casual view.
If you keep the ldpObject
attached to the people classes in the directory,
you can set up an ACL in OpenLDAP’s slapd.conf
to allow reading of these
objects only by the DN defined in ldp.h
, in which case the DN must match the
definition of LDP_BINDDN
. Furthermore, you’ll then need an entry in the
directory named as LDP_BINDDN
with the password defined in LDP_BINDPW
. The
following ACL snippet will allow the attribute type ldpSHprofile to be read
and modified by the user (self) and by the ldp-manager; all others have no
access to it.
If, on the other hand, you’ve chosen to put all ldpObjects into their own container, use an ACL similar to the following, which allows ldp to read all objects in the given container and, if the entry has an owner, will allow the entrie’s owner to modify the profile.
.profile into LDAP
My profile is a text file which I create and modify with an editor of course. A profile doesn’t usually contain secrets, but never the less it need not be easily readable to others. For this reason I decided to obfuscate it by encoding it in BASE-64 when storing it in the LDAP directory. Environments that require more security can easily add an ACL to the directory server to protect it from prying eyes (see below).
I note that the profile I’m editing here will be run on multiple machines, so
care is needed to make it as generic as possible. For example always use
references to $HOME
instead of hardcoding the path to a home directory.
After editing your profile, it needs to be converted to Base64 encoding. The
supplied encode.sh
script uses OpenSSL’s enc
utility to do so. If you don’t have OpenSSL, you might try using the supplied
encode.pl
which attempts to do the same with Perl’s MIME::Base64 module.
Use the included utility ldp-encode which will read your profile file, and
create an LDIF which can be directly given to ldapmodify. ldp-encode uses
the values of LDP_ATTRIB
and LDP_BASE
to create the relative distinguised
name (RDN) and distiniguished names (DN) respectively.
Using either ldapmodify or a graphical utility for managing your LDAP server,
you now have to add or modify the entry in which the profile will be stored,
adding the ldpObject
object class as well as loading the base64 encoded
profile into the ldpSHprofile attribute type.
ldapmodify
I can load this LDIF
into the directory with an
LDAP Administrator
In Softerra’s LDAP Administrator, search and select the entry you want to add the profile to. Then choose “New Attribute”, click on ldpSHprofile and press “Next”. When prompted for the attribute value, click on the “Load…” button and search for and load the Base64 file, alternatively pasting it into the dialog box.
GQ
Jarek Gawor’s LDAPbrowser/editor
Jarek Gawor’s LDAP Browser/Editor is a fine Java-based tool which works on both sundry Linux/Unix derivatives as well as on Windows.
In order to create the new attribute type, ensure you’ve copied the base64-encoded string of the profile into your clipboard. You can then add a new attribute of type ‘string’ and load that in the LDAP Browser/Editor
Configure ssh
ldp is designed to work with SSH in conjunction with RSA keys. There are a number of locations on the web that give detailed instructions on setting up ssh & generating keys for yourself.
sshd
SSH needs to allow an environment variable set upon logon. OpenSSH
is usually shipped with the option
disabled, but it has to be enabled for ldp to work. This option specifies
whether the file ~/.ssh/environment
and environment=
options in
~/.ssh/authorized_keys
are processed by sshd. The manual warns you, that
enabling environment processing may enable users to bypass access restrictions
in some configurations using mechanisms such as LD_PRELOAD. If you want to
ignore that warning, locate the file sshd_config
(often located in
/etc/ssh
) and add the line
to the bottom of your /etc/ssh/sshd_config
and restart the SSH daemon (on Redhat-based systems with service sshd restart). Note that this may open some security hole, so doing it is entirely your responsability: if your coffee machine burns out, or your home or office explode, it will be your fault. Check the manual for ssh(1), sshd(8) and sshd_config(5).
.ssh/authorized_keys
To ensure that a string containing my username is added to the environment whenever I log connect via SSH, I’ve set an environment option in the authorized_keys file for the root user in ~/.ssh/authorized_keys
.
Test
Test it by connecting to SSH. I’ll try with putty. If everything works, I should see something like this:
So, what have I gained so far? I am able to determine that I myself am logging on to the target system, irrespective of the client operating system, the ssh client and the source address of the computer I’m connecting from. Whenever I access the server with an ssh client and an RSA key, the environment will be populated with a variable LDP_USER
containing my username. Of course any other administrator having access to root’s ~/.ssh/authorized_keys
can set her own key up to contain an identical username. It is therefore worth noting that this isn’t terribly secure. On the other hand, there is never much that can be hidden from a root user, is there?
Set up /etc/profile
The ldp program (LDAP profile) is added to the system’s /etc/profile
by getting the shell to source the result of ldp:
Note the backticks; upon logging in, the shell (any of standard Unix shell (sh), the Bash shell (bash) or Korn shell (ksh)) will read in or source (note the leading period in the line) the result of the ldp program. If you look up the syntax of the source command, you’ll see that it expects a filename to read and execute commands from. That is exactly what ldp will do: it will print a filename from which the login shell will read further commands from.
Actually ldp will connect to an LDAP directory server, search for a profile entry for the user named in the environment variable $LDP_USER
and will store the content of that user’s profile into a temporary file, then printing the name of the file it wrote into to the standard output (stdout), thereby instructing the calling shell where to read commands from. Look at what happens when I execute ldp interactively on my system:
Notice the filename? That is the file into which ldp has written the content of the profile read from the directory. In actual fact therefore, sourcing the result of ldp is equivalent, for my user, to
which is a perfectly valid shell command, and by the way, is exactly what I want!
Security
The only really secure method of using ldp is to not use it at all. Apart from that, ldp should be owned by root and be mode 0100. Unfortunately, since ldp is run in the user’s context from /etc/profile
, it will probably require execute permission for user, group and others.
It is quite easy to trick ldp into returning another user’s profile. This as yet cannot easily be avoided. I wrote ldp for it to be used exclusively on trusted systems by the root user, and this is the way it has been tested. If you want to set up ldp for non-_root_ users, that is certainly possible, but first evaluate the risks.
Advanced Topics
.inputrc
One of the raisons d’être of ldp is to have the inputrc for any readline(3) enabled programs set up as well. This can be done in the profile retrieved via ldp, by creating the inputrc file on the fly.
ldp sets up two environment variables which can be used to determine the profile directory and the profile name used by it: they are $LDP_PDIR
and $LDP_PNAME
respectively. Using this information, my profile carries the following shell script as a here script in it:
While I’m logging on and the file created by ldp is being sourced in, this bit of shell script creates an inputrc for me and sets up $INPUTRC
to point to that file. Voilá!
Bypassing hardcoded LDAP URI
If you are deploying the ldp binary to a number of machines, the name of the LDAP server may differ depending on where it is located (i.e. in a DMZ for example). ldp uses the LDAP URI which is compiled into it (from ldp.h
), but that may be overriden by setting $LDP_LDAPURI
before its invocation.
That may help on occasion.
Credits & Copyright
The idea, implementation and code are mine and are copyright (C)2006 by Jan-Piet Mens. This software may be used freely.
ldp uses base64 encoding-decoding functions which includes software developed by the Kungliga Tekniska Hgskolan and its contributors. Please see base64.[ch]
for full details.
Changelog
- 0.3 2006-02-25
- Initial version
- 0.4 2006-02-27
- Added ldp-encode utility to create an LDIF for importing profile into LDAP directory.
- Added man-pages for ldp and ldp-encode
- 0.5 2006-02-27
- ldp-encode tries to determine whether to use Gecos or username for RDN
- ldp-encode has option -n to add objectClass and option -r to set RDN on the command line, overriding any guesswork
- ldp & ldp-encode show version with -v
- man-pages brought up to date
- removed warnings on MAX OS/X 10.4
- 0.6 2006-02-28
- ldp-encode printed Base64 data until size of input file instead of length of Base64 string.
- ldp sets $LDP_USERHOME to the home directory which belongs to $LDP_USER; this can be used in scripts to find the user’s real directory, as $HOME always belongs to the logged in user. To clarify: if root logs in with $LDP_USER=joe, $HOME will be something like ‘/’ whereas $LDP_USERHOME will be ‘/home/joe’.
- 0.7 2006-02-28
- sundry small fixes