October 2008


UPDATE: I discovered a ubuntu package called “likewise-open” that handles the AD integration very easily.

At work, we run a mostly Windows network — most people spend their day logged into a workstation on our Windows domain.

We have some random unix servers around, and people with accounts on said servers have to maintain credentials in as many places as they have unix accounts. Being on the network team, I have access to many unix machines, such as firewalls, dev boxes, production servers, etc. A couple years ago, I wrote a script to help me change my password, so that when I changed my password, I’d be able to change it everywhere. Currently, there are 12 machines that the script logs into for me. One advantage of this system is that I get a lot of practice typing new passwords, and haven’t ever forgotten the new password. Among the obvious disadvantages of this arrangement is that it’s easy for a change to go unnoticed, which leaves stale passwords in new machines and makes me have to wait for network timeouts when old machines get turned off.

So, enter a new virtual unix machine I’m setting up. I’d really like to integrate it with ActiveDirectory. It’s really time for SSO. It’s way past time, in fact.

My distro of choice is ubuntu. The latest release available is 8.10 (beta). In the rest of this post, I’ll cover getting Ubuntu installed in a virtual machine in Microsoft’s Virtual PC, and how to get said virtual machine onto the domain.

Step 1 is installing Ubuntu into a VM. This is already documented by corey-m in a post titled “Installing Ubuntu Server 8.04 Hardy in Microsoft Virtual PC 2007.” The only thing I did differently was, towards the end, to edit the grub configuration while I was in the recovery console. This removed the need to change boot params during boot.

NOTE: If you install on Hyper-V, you don’t need “noreplace-paravirt”, and, in fact, that option seems to slow the VM down quite a bit.

Step 2 is hooking into AD. For this, I started with a HOWTO on using Winbind to authenticate to Active Directory, from ubuntu’s community wiki. I deviated a bit from the steps listed, so I’ll include the full details here. I am definitely not an expert on the intricacies of samba or AD, so some of this may be cargo cult cruft; apologies if I make you do a bunch of pointless stuff.

The basic steps will be:

  • Install and configure KRB5.
  • Install and configure Samba.
  • Configure PAM.
  • Configure sudo.

Install KRB5

Ensure that /etc/hostname and /etc/hosts (and DNS, if applicable) agree on what your hostname is. hostname –fqd should show the same thing.

sudo apt-get install krb5-user libpam-krb5 krb5-config libkadm55

You’ll be prompted for the kerberos server names. Use your domain controllers’ names.

After installing, I edited /etc/krb5.conf and removed all the extra domains and added DNS mappings in the [domain_realm] section for my domain.

To test, do kinit <username>. You’ll be prompted for your password. Assuming it succeeds, klist will show a ticket. If it fails and says “Client not found in Kerberos database while getting initial credentials”, then kerberos couldn’t find your username. If it says, “Preauthentication failed while getting initial credentials”, then your password is wrong.

Install and configure Samba

sudo apt-get install winbind samba

Replace /etc/samba/smb.conf with this, customized for your network:

[global]
  workgroup = MYDOMAIN
  netbios name = MYMACHINE

  winbind refresh tickets = yes
  idmap domains = MYDOMAIN
  idmap config MYDOMAIN:backend = rid
  idmap config MYDOMAIN:range   = 10000-99999

  winbind enum users = yes
  winbind enum groups = yes

  template homedir = /home/%D/%U
  template shell = /bin/bash

  security = ads
  encrypt passwords = yes
  realm = MY.KERBEROS.COM

Note the lack of spaces around the idmap range. The version of samba in 8.10beta fails to parse the range if there are spaces (e.g. “1000 – 2000”).

One of the things that is different from the HOWTO is the idmap configuration. The default idmap implementation does a simple mapping of SIDs to unix IDs… the first user that logs in will be 10000, the second 10001, etc. If the users log into another server in a different order, they’ll get different user ids. The rid backend provides a deterministic mapping, so it’s only important that the ranges are the same on each unix server. There is an ad backend that uses IDs stored in ActiveDirectory by SFU, if you’ve got that installed.

After modifying smb.conf, restart samba and winbind.

sudo /etc/init.d/winbind stop
sudo /etc/init.d/samba restart
sudo /etc/init.d/winbind start

Join the domain.

sudo net ads join -U <AD account>

Restart samba again. Run wbinfo -u, and you should see a list of users from Active Directory. If not, something is broken.

Configure PAM

Edit /etc/nsswitch.conf and change the lines that look like this

passwd: compat
group: compat

to look like this

passwd: compat winbind
group: compat winbind

Now run sudo getent passwd and you should see the normal /etc/passwd entries, followed by passwd entries for the domain users.

On Ubuntu 8.10, there’s a new auto-configuration tool for PAM, pam-auth-update. If you don’t have pam-auth-update, you should probably just modify your pam configuration as per the HOWTO, rather than using the following configuration for PAM.

If you’re using the new PAM, create the file /usr/share/pam-configs/winbind with the following contents:

Name: Active Directory via Winbind
Default: yes
Priority: 500
Auth-Type: Primary
Auth:
        [success=end default=ignore]    pam_winbind.so krb5_auth krb5_ccache_type=FILE try_first_pass
Auth-Initial:
        [success=end default=ignore]    pam_winbind.so krb5_auth krb5_ccache_type=FILE
Account-Type: Primary
Account:
        [success=end default=ignore]    pam_winbind.so
Session-Type: Additional
Session:
        required        pam_mkhomedir.so umask=0022 skel=/etc/skel

The krb5_auth options aren’t required, so leave them off if you have no use for kerberos.

Now run sudo pam-auth-update to push the configuration to /etc/pam.d. Test that you can log in as a domain user (e.g. ssh -l DOMAIN\\user localhost). Test that you can log in as a regular user.

Configure sudo

Presumably, you’ll be basically ignoring your local passwd file from now on, so you’ll want the AD-backed users to be able to use sudo to do admin-ish things.

sudo vi sudoers

Edit appropriately. For example, I added the following line.

%DOMAIN\\Domain\ Admins ALL=(ALL) ALL

Now you’re set up with AD accounts on Ubuntu 8.10. In the future, I’m planning to add a rails application with a SQL Server database behind it, so there may be more in the future for this post.

For a recent project, we built a rails application that accepted logins for two different classes of user. It was logically two applications, but since they both worked on the same domain model, we made them the same physical application, for simplicity.

One class of user was the customer of our client. These users were associated with their organization, so we used an “are you logged in?” type of authorization scheme, where any user in the organization could view and modify that organization’s information, but could view and modify no other organization’s information.

The second class of user was broadly termed “admin” users, and was basically people within our client’s company. These users had varying permissions, based on their roles in the company. Because they had varying authorities, we needed a better authorization system than “are you logged in?”

The system we came up with was fairly novel, though not as good as the easy role-based authorization that I read about yesterday. I thought I’d write it up anyways. The system was basically the same as any role-based permissioning system, but the things we decided to apply permissions to were actions.

So there were users, roles, and permissions. Users stored the usual stuff. User has_and_belongs_to_many roles. Roles have a name. Role has_and_belongs_to_many permissions. Permission has a controller and an action.

Thanks to the conventions of rails, we were able to define permissions such as “controller = *, action = show” and add that to the “read-only” role. We deviated from rails’s conventions in a couple of places, but it turned out to be few enough that we were able to set up a few simple permissions, and group them into the roles that the customer needed.

The big advantage of this system was that we needed to write the authorization code once: verify that the current user is allowed to execute the current action.

A disadvantage is that pages didn’t necessarily know if they were linking to a permission denied error. We probably could have resolved this, but we felt that it would work out OK if all the functionality was advertised to everyone that had read access, and if someone needed more functionality than they had, they’d click a link, get a friendly “permission denied, but ask your local admin if you need more access than you have” page.

I skipped all the code in this post, but hopefully you get the gist.

On my current project, I was tasked with adding a WebDAV folder to an ASP.NET application. I found a WebDAV component on sourceforge, and thought I’d give it a try, prior to investigating the less-free options. It took an hour or so to get the sample web application working. It looked like it had the hooks necessary in order to tie into the existing authentication and authorization in the application, so I started integrating it into the application.

My first pass at integrating it was to do everything at once: tie in WebDAV.net, add authentication and authorization, and change the WebDAV/file mapping from root = root to root/a/b/c = root. Needless to say, this was too big of a chunk to bite off.

So step 1 turned into getting it so that I could open the application with a WebDAV client. This involved tweaking another HttpModule that was throwing a NullReferenceException because app.User wasn’t defined. Things seemed to work, so I checked it in.

Step 2 was to tie in auth. I got the WebDAV component to set the response status to 401, but it and the Forms authentication disagreed about what that meant: WebDAV wanted to send a HTTP Basic Auth request, and ASP.NET wanted the user to be redirected to the login page. After fighting with this for a day or so, it became clear that the WebDAV part of the site would need to be a separate ASP.NET application. Also, another dev pointed out that the WebDAV module was messing up other parts of the site.

So I created a new project and plopped the WebDAV module in it. It took some time to get it back to the point where it was running (the HttpModule wasn’t being loaded at one point, and it wasn’t giving me either warnings or errors). Next was to create a connection to the database…

I tried copying over configuration from the main application to the new application in order to get all of the dependency injection stuff to work on the WebDAV application. This way I could do ServiceLocator<ILoginDataProvider>.Get(), just like the main application did when the user logged in. I was creeping towards this, when I realized that most of the high-level auth code in the main application assumed that it was running in a forms-based authentication environment (e.g. the data provider pulled information from the session, if it was constructed with the CompositeWeb services container), and I needed to do a protocol-based authentication.

I had an epiphany at this point — why complicate things by using CompositeWeb when all I needed was to make two calls to the LoginDataProvider? Two concrete constructor calls later, I had auth implemented for the WebDAV web application.

Along the way, I discovered that the best way to test was to set up a virtual directory in IIS. Visual Studio was helpful enough to create the virtual directory of my choosing. In order to get WebDAV to work correctly, I needed to add the ASP.NET ISAPI dll as a wildcard handler, and I needed to tell IIS that there would never be any authentication (i.e. disable digest, basic, and integrated authentication). Disabling integrated authentication makes VS tell you that you can’t debug the application, but this is a lie… Build the application, then Debug, Attach to Process, and pick aspnet_wp.exe. You just can’t use the F5 shortcut that normally does all of that for you.

So, here’s what I’d say about integrating WebDAV into an existing ASP.NET application:

  • WebDAV.net works fine (though the framework has a couple of very obvious bugs when you don’t set up debug logging).
  • Make it a separate application. If you want it to look like it’s a subfolder of your application, deploy it to a subdirectory of the application.
  • Don’t try to use CompositeWeb and WebDAV.net at the same time.

My first career aspiration was to be a baseball player. I came of age while Ryne Sandberg was earning his way into the Hall of Fame, and I dreamed of being second baseman for the Cubs.

My second idea was to be a writer. I’ve never been particularly verbose, but in fifth grade, I remember spending hours on a story that I was writing for school, and I really enjoyed it.

My third idea was to be a computer graphics programmer. It was fueled by the toy programs I wrote on Commodore 64s and my family’s 286, and Jurassic Park really ignited this desire. In high school, I gave a presentation in English class on how to program, and brought in the family C64 to demonstrate. The computer graphics angle morphed into general researchy computing, and led to my current businessy software job. I still dream of getting into something that deals with high performance computing, but I’m pretty happy doing Ruby on Rails development, with some .NET sprinkled here and there.

I’ve been a practicing software developer for about eight years. My biggest professional leap came about five years ago when I was working very closely with a guy who was about two years further into his career. His code reviews, and the ideas he was putting into white-paper form captured my interest. He introduced me to new practices and patterns, as well as patterns and practices that I had fallen into (and appreciated having a name and set form for) and that I had only a nominal familiarity with. One of the key moments for me was when I realized that reviewing and editing prose is very similar to reviewing and editing code. A part of my brain that had been mostly dormant at work engaged, and I’ve been a much better developer since that moment.

I’ve been thinking about starting a technical blog on a public network (i.e. not just on a private server at work) for a while. I have another blog, but I mostly just grouse there. Before this week, I wasn’t sure how much I’d be able to contribute to the community, and whether it would be worthwhile to start a blog. This week (and really, for the past 6 months or so), I’ve started seeing how the things I’m doing are applicable more broadly than on my projects. I’m hopeful that having this blog will help me to think about the problems I’m solving in a way that’s more generally applicable so that I can share my solutions.

I also realized this morning that this could be a good combination of job aspirations 2 and 3 — writing about software. I’ve been interested in doing more writing (not necessarily software-related), so I’ll start by writing about something that I know pretty well.

Maybe someday I’ll become a modern-day Charles Dickens.