Monday, February 21, 2011

Maven Integration for Eclipse JDK Warning on Windows

If you start eclipse (or springsource tool suite) and get the following popup:



you need to point eclipse to a JDK. You can't do this within the IDE itself so just close it. Then go to the directory where you have eclipse installed. Edit eclipse.ini in an editor that understands unix-style line endings (like notepad++ or wordpad). If you're running STS, the file will be called STS.ini instead of eclipse.ini.

Look for the -vmargs line and insert a new line BEFORE it that specifies the location to your JDK's javaw executable. Like so:

Thoughts on Convention over Configuration

Having spent the majority of my professional life on Windows with .NET, I'm not much of a Java troubleshooter. I speak the language and am familiar with a slew of frameworks, but I just don't know the JVM like I know the CLR. Yet.

Additionally, I notice that the Java community loves to follow the newest hottest thing far more than the .NET community. One example of this is Java's love/hate relationship with XML.

Back when Enterprise Application Integration (EAI) was the hot buzzword, we started to see a lot of configuration done in XML. XML Configuration files became huge and unwieldy. People started to talk about "XML Hell" and eventually, we started moving towards "convention over configuration".

I'm a fan of focused, opinionated software - and I definitely think convention over configuration is the right way to go in the majority of circumstances... but some people have gone beyond convention over configuration into: loathing and avoidance of XML in all circumstances.

Eclipse, for example, uses an eclipse.ini file to provide bootstrap arguments like the location of the JVM. Its convention is argument name on one line followed by argument value on the next line - which is the standard convention for ini files. My problem was that I haven't edited an old-school ini file in so long that I just forgot the convention. I had argument and value on one line.

Now, it's entirely my fault for forgetting the convention, okay? I admit that. Everyone should know ini config file conventions. But if the config file had been XML, it wouldn't be possible for me to forget the convention because the XML would have just been invalid. Instead of getting a non-sequitur Maven Integration Warning about running on a JRE instead of a JDK, I could have gotten an exception that said the configuration is bad. In addition, given a schema, I could have verified the configuration file myself.

Don't be confused about what I'm trying to convey here - it's perfectly fine that eclipse uses a well-known convention (ini) for its configuration. I'm trying to get at something more important: when to choose "convention over configuration" vs "configuration over convention". I think that the answer is tied to the concept of friction.

Troubleshooting this problem I had definitely counts as development friction. Since the goal of software engineering is to deliver the maximum value to our customers, reducing friction has a direct correlation to customer value. Therefore, we ought to choose convention over configuration when the benefit of learning or remembering a convention outweighs the cost of learning or remembering it.

In the example of the eclipse configuration, the benefit of editing an ini file over an xml file was ZERO, and the cost of remembering the convention was non-zero. Again, this example is contrived because the non-zero cost was due to my forgetfulness - but the example holds for other conventions that are less obvious. Do not create your own little convention and make me learn it just so you can say you use convention over configuration. If you are designing a framework, do not cause my NET amount of friction to increase.

For me, this begs the question: how do we objectively measure the cost of a given convention? To the convention's author, the convention will seem "obvious" and therefore cannot be an objective observer.

Monday, February 7, 2011

Join CentOS to a Windows Domain

3/26/2012 update: I've now done this on CentOS 6.2 as well.

Since I've had to do this a number of times in the last few months, I thought I should post this here so I can't forget it.

Here's some specifics on what I'm using:
  • Windows Server 2008 R2
    • As dc1.devexample.com (192.168.0.201)
    • As Primary Domain Controller of devexample.com
    • As Windows Server 2008 R2 forest functional level
  • CentOS 5 / CentOS 6
    • As app01.devexample.com (192.168.0.203)
    • Samba 3.5.6 / Samba 3.6.3
  • An internet connection. If you are not going to have an internet connection, you'll want to pre-download the files you'll need. I suggest using 'yum downloadonly' to get them.
I've spun up a brand new CentOS 5.5 VM and logged in. Oh man, does anyone else just love logging into a fresh install? Is my nerd showing? Sorry!

The first thing I need to do is take on great responsibility, so for that I'm going to need great power: let's add my account to the sudoers list.
su -
EDITOR=nano visudo
That's better. Now we can stop acting as root.
exit
Now that I can tell the operating system what to do, let's update it.

While that downloads and installs, let's add a /sbin and /usr/sbin to your path. These directories have a lot of commands that need to be run by root. Since we're going to "sudo" a lot of these commands later, having them in our path will save us from having to fully qualify the path all the time (e.g /usr/sbin/something -arg -foo).
nano ~/.bash_profile
Now is also a good time to update our hostname and network settings. On your network at home, you probably just let DHCP assign you an IP address. In a kerberized network environment, though, you'll want to assign your computers a static IP address.
sudo nano /etc/sysconfig/network
and then create an entry for that hostname in your hosts file...
sudo nano /etc/hosts


And now, assuming that you're using eth0 as your ethernet device,
sudo nano /etc/sysconfig/network-scripts/ifcfg-eth0
Since you're configuring everything statically instead of relying on DHCP, you need to configure your DNS server(s) manually too.
sudo nano /etc/resolv.conf
Finally before we reboot, let's configure our network time protocol (ntp) client. KERBEROS WILL NOT WORK unless the computers involved (KDC, Server, Client) have synchronized clocks. Variances larger than 1-2 minutes will cause kerberos to fail.

Configuring NTP correctly is actually not as easy as it should be. I don't understand why "hey go sync your clock with that guy over there every so often" requires so much work, but it does. I found a good tutorial over here, which I will now distill for you.

We're going to completely replace the existing configuration, so you should rename it to a backup file.
sudo mv /etc/ntp.conf /etc/ntp.orig
And then we can create our own
sudo nano /etc/ntp.conf

We're not done! Next, a separate ticker file must be created that lists the servers.
su -
awk '/^server/ {print $2}' /etc/ntp.conf | grep -v '127.127.1.0' > /etc/ntp/step-tickers
And then! We need to sync our clock and then configure the OS to sync every time it boots.

(assuming you're still running as root from the last step)
ntpdate -u dc1.devexample.com
chkconfig --level 345 ntpd on
Whew, all that just for clock synchronization! Now we can stop acting as root:
exit
Our server is now configured as app01.devexample.com, is set to have the IP address 192.168.0.203, and will sync its clock with the domain controller.

Let's do one last thing while your OS is updating: set firewall and SELinux policies. The first time I attempted to join a CentOS box to the domain, I pulled my hair out for a couple of days trying to debug this error: "bind failed on pipe socket /var/lib/samba/winbindd_privileged/pipe: Permission denied". I've only discovered one thing that will make this error go away: SELinux settings. You can either set the system SELinux policy from "Enforcing" to "Permissive", or you can keep it as "Enforcing" but turn off all samba protection. Up to you and your requirements, but for this blog post I'm going to set SELinux to "Permissive".

Open System > Administration > Security Level and Firewall and allow samba through the firewall on the "Firewall Options" tab.

Then go to the "SELinux" tab and change the setting to "Permissive" and apply the changes.

If you haven't installed an X server, you can just edit /etc/selinux/config and set 
SELINUX=permissive.
 
That's it for now: go have a coffee. Or a beer. When the update is finished, reboot. I'll wait right here for you. (P.S. - if you can't get to the internet when you come back, make sure your /etc/resolv.conf still has our custom entries in it!).



Welcome back!

We've already come a long way together and we haven't even begun to join our box to the domain, have we? For this reason, you might want to consider tools like Centrify or Likewise-Open. I've never used either of them myself, but I'm a fan of the group policy and auditing capability that they advertise. Even after you finish following all of my steps, you're still not going to have those two capabilities.

It's finally time to download and install samba. At http://enterprisesamba.org, you'll find already-compiled binaries of the latest samba for CentOS 5.5 (and CentOS 6 too). These instructions assume you have a connection to the interwebs, so if you don't you'll need to pre-download these files and put them into the correct location yourself.

The enterprise samba site provides a yum repository so that you can install their binaries via the yum command line. Here's how you install the repository:
cd /etc/yum.repos.d
sudo wget http://ftp.sernet.de/pub/samba/3.5/centos/5/sernet-samba.repo # if centos 5
sudo wget http://ftp.sernet.de/pub/samba/3.6/centos/6/sernet-samba.repo # if centos 6
Now that you have the repository installed, you can install samba:
sudo yum install samba3 samba3-client samba3-winbind libsmbclient0 libwbclient0
yum will figure out all the dependencies, which you should accept.

Okay! Let's configure pam, nsswitch, samba, and kerberos! I'm going to use the built-in GUI that comes with CentOS 5.5, but I'll also post screenshots of the files that get updated by the GUI so you can edit the files manually if you aren't running X.

NEW CENTOS 6 INSTRUCTIONS!!  If you are running CentOS 6 instead of CentOS 5, do not use the GUI as I just suggested.  Instead, let's use the authconfig command-line tool to get everything correct.

sudo authconfig
  --enableldap
  --enablemkhomedir
  --ldapserver=ldap://dc1.devexample.com:389
  --ldapbasedn=dc=devexample,dc=com
  --enablewinbind
  --enablewinbindauth
  --smbsecurity ads
  --enablewinbindoffline
  --smbservers=dc1.devexample.com
  --smbworkgroup=DEVEXAMPLE
  --smbrealm DEVEXAMPLE.COM
  --winbindtemplateshell=/bin/bash
  --disableldaptls
  --enablekrb5
  --krb5kdc=dc1.devexample.com
  --krb5adminserver=dc1.devexample.com
  --krb5realm=DEVEXAMPLE.COM
  --enablekrb5kdcdns
  --enablekrb5realmdns
  --smbidmapuid=16777216-33554431
  --smbidmapgid=16777216-33554431
  --winbindseparator=+
  --winbindtemplatehomedir=/home/%U
  --winbindtemplateshell=/bin/bash
  --enablewinbindusedefaultdomain
  --enablewinbindoffline
  --winbindjoin=Administrator
  --disablesssd
  --disablesssdauth
  --enablepamaccess
  --enablesysnetauth
  --nostart
  --update

This will ask you for the domain administrator's password and immediately join you to the domain.  If you have your own domain administrator account that isn't 'Administrator', change --winbindjoin=Administrator to your account name.  Only do this if your account is a domain admin.

This ought to have joined you successfully to the domain in which case you only need to add an entry into DNS on your Windows Domain Controller before you're completely done. See my instructions below on how to add a DNS entry.

END OF CENTOS 6 INSTRUCTIONS.

Open your Authentication Configuration GUI from System > Administration > Authentication.

Check "Enable Winbind Support" and then "Configure Winbind". Here are the settings I'm using - you'll want to replace them with your own, obviously. Don't click "Join Domain" - you have more configuring before you're ready to do that!

It's important that your winbind ADS realm is in ALL UPPERCASE. Notice that the winbind domain doesn't have .com after it - DEVEXAMPLE is the NetBios name of the domain, which is what winbind wants. Also notice that I've set Winbind Domain Controllers to a star (*). This will make winbind use DNS to lookup the domain controller for the devexample.com domain. This is recommended because if you set it to dc1.devexample.com and that server dies, your CentOS box won't automatically go to the backup domain controller. Also, if you want to do things like kerberize JBoss, you need winbind to use DNS to lookup the domain controller because otherwise Java will construct an incorrect service principal name when it tries to bind to LDAP.

Click OK and go to the Authentication tab. Check "Enable Kerberos Support" and then "Configure Kerberos...". Here's what my settings look like.

It's important that the REALM be in ALL UPPERCASE. Click OK. Now check "Enable Winbind Support" and "Configure Winbind...". Again, here are my settings. Again, don't click "Join Domain".

Click OK and then go to the options tab. Here's what I have for that tab:

Click OK to close out the GUI. We're almost done! The reason that we didn't click "Join Domain" at all was because we still have a little bit of configuration and cleanup to do before we attempt domain joining.

There's nothing that needs to be done in your /etc/nsswitch.conf file, but I'm including it here in case you are running a headless server and need to edit it yourself.


First, we need to add some parameters to the pam configuration file, /etc/pam.d/system-auth. Just a few lines up from the bottom of the file, find:
session     optional      pam_mkhomedir.so
and add the parameter skel=/etc/skel umask=0027
sudo nano /etc/pam.d/system-auth
What did you just do? When new domain users log onto your CentOS box for the first time, their home directory will be created and its contents will be copied from /etc/skel and permissions on the new user's home directory will be reasonable.

Next, let's clean up our kerberos configuration file. Even though you just used the GUI to configure kerberos for your domain, /etc/krb5.conf still has "fake" entries for example.com that we should remove. Look at the image below to see the final state of my krb5.conf file.

Finally, let's edit our samba/winbind configuration. Here's my final state:


These are the lines that I added or changed:















template homedir = /home/%U

This tells the system to create home directories for new domain users in /home/[username]. This is nice and clean and lives side-by-side with local users. Because of this, if you're going to have local system users or if your domain forest has more than one domain in it, you'll want to set this to /home/%D/%U instead. That way, there will be no chance of name conflicts. None of those caveats apply to my example, so I used the simpler option (/home/%U).

winbind enum users / groups

This causes the 'getent passwd' and 'getent group' commands to include domain users and groups in its output.

winbind separator

You know how on windows your domain account can be represented as DOMAIN\user? Well, using a backslash as a separator can cause real problems in Linux, or at least headaches and confusion. So what I've done is configure the separator to be the plus symbol (+) - because it has far fewer issues in Linux (actually, I've never had any). Domain users will now log onto your CentOS box as DOMAIN+user. Any scripts that reference domain users or groups will also use DOMAIN+user.

winbind use default domain = true

When this is set to true, I can log into my CentOS box with domain accounts without having to prefix the username with DOMAIN+. If your users will be logging in from multiple domains in equal proportion, you probably don't want to set this to true.
Now that we've configured samba and winbind, let's tell CentOS to start those services every time it boots.
sudo chkconfig --level 345 smb on
sudo chkconfig --level 345 winbind on
We are getting so close to the end! Before we join to the domain, let's configure our sudoers list so that Domain Admins can execute commands as root.
EDITOR=nano visudo
That's it for configuring your CentOS box for membership in an AD domain. You have one more thing to do before joining - create a DNS entry on your domain controller!

Log onto your Windows domain controller and open up your DNS Manager from Administrative Tools. First, you're going to create a reverse lookup zone (if one doesn't already exist), then you're going to add an A entry for your CentOS box.

If you don't have a reverse lookup zone, follow these instructions to create one. When you're done, your DNS Manager ought to look like this:
Now, create an A record for your CentOS box:


IT'S TIME! (Unless you are using CentOS 6, in which case you've already joined to the domain.  Skip the join command here, but definitely reboot even on CentOS 6)

Back on your CentOS box, join to the domain:
su -
net ads join -U Administrator osName="CentOS" osVer="5.5"

Reboot! Reboot! When your CentOS box comes back up, login as a domain user. GOGOGO!

While you're rebooting your CentOS machine, go back to your domain controller and notice that there's now an APP01 entry in your "Active Directory Users and Computers" MMC.


Once your CentOS box comes back up, login with a domain user account and see that it knows who you are and what groups you're a member of.

You have successfully joined CentOS to your Windows domain!