Tuesday, December 27, 2011

Establishing a SSL connection to RabbitMQ using the .NET client

First, I'm making the assumption that you've read, re-read, and followed the SSL tutorial on rabbitmq's website.  If you haven't done everything that it's instructed you to do (including adding your certificates to the Windows Certificate Store using certmgr), none of this code is going to work for you.  If you have, this code should "Just Work™".

Here's the complete code file that works for me.  You will (obviously) need to change the names of the servers, and the thumbprint of your certificate.

Note that this code only uses your client and server's certificates to establish a secure connection.  You are still logging in as guest.  I will show you how to use your client certificate to authenticate yourself below.
using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;

using RabbitMQ.Client;
using RabbitMQ.Util;


namespace RabbitSslTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Program p = new Program();
            p.Start();

            Console.WriteLine();
            Console.WriteLine("Press <ENTER> to exit.");
            Console.ReadLine();
        }


        public void Start()
        {
            try
            {
                // we use the rabbit connection factory, just like normal
                ConnectionFactory cf = new ConnectionFactory();

                // set the hostname and the port
                cf.HostName = "rabbitmq-server";
                cf.Port = AmqpTcpEndpoint.DefaultAmqpSslPort;

                // I've imported my certificate into my certificate store
                // (the Personal/Certificates folder in the certmgr mmc snap-in)
                // Let's open that store right now.
                X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
                store.Open(OpenFlags.ReadOnly);

                // and find my certificate by its thumbprint.
                X509Certificate cert = store.Certificates
                    .Find(
                        X509FindType.FindByThumbprint,
                        "6d 36 c0 f9 c2 f8 72 05 d1 01 6c 54 e1 91 bb 2d 66 7b ac 94",
                        true
                    )
                    .OfType<X509Certificate>()
                    .First();

                // now, let's set the connection factory's ssl-specific settings
                // NOTE: it's absolutely required that what you set as Ssl.ServerName be
                //       what's on your rabbitmq server's certificate (its CN - common name)
                cf.Ssl.Certs = new X509CertificateCollection(new X509Certificate[] { cert });
                cf.Ssl.ServerName = "rabbitmq-server";
                cf.Ssl.Enabled = true;

                // we're ready to create an SSL connection to the rabbitmq server
                using (IConnection conn = cf.CreateConnection())
                {
                    using (IModel channel = conn.CreateModel())
                    {
                        channel.QueueDeclare("rabbitmq-dotnet-test", false, false, false, null);
                       
                        // create some basic properties for the message we're going to send
                        IBasicProperties props = channel.CreateBasicProperties();
                        props.ContentEncoding = "utf-8";
                        props.MessageId = Guid.NewGuid().ToString();

                        // publish a hello world message
                        channel.BasicPublish("", "rabbitmq-dotnet-test", props, Encoding.UTF8.GetBytes("Hello, World"));

                        // and go get it
                        BasicGetResult result = channel.BasicGet("rabbitmq-dotnet-test", true);

                        if (null == result)
                        {
                            Console.WriteLine("No message received.");
                        }
                        else
                        {
                            Console.WriteLine("-----==<([ Received ])>==-----");
                            DebugUtil.DumpProperties(result, Console.Out, 0);
                        }

                        channel.QueueDelete("rabbitmq-dotnet-test");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }
}
 And the output:

Using your certificate to authenticate
If you want to authenticate yourself with your certificate as well, you need to change the code and your rabbitmq server's configuration.

First, update your rabbitmq server's configuration.  If you don't know where to look for that, read this page.  Here's what mine looks like:
  [
  {rabbit, [
     {auth_mechanisms,['EXTERNAL']},
     {ssl_listeners, [5671]},
     {ssl_options, [{cacertfile,"C:/Path/To/Your/cacert.pem"},
                    {certfile,"C:/Path/To/Your/cert.pem"},
                    {keyfile,"C:/Path/To/Your/key.pem"},
                    {verify,verify_peer},
                    {fail_if_no_peer_cert,true}]}
   ]}
].
The thing to note there is the addition of {auth_mechanisms,['EXTERNAL']}.  Don't forget to stop your service, re-install it, and start it after making these changes.
rabbitmq-service.bat stop
rabbitmq-service.bat install
rabbitmq-service.bat start
Next, let's update our client code just a tad by setting the connection factory's auth mechanisms.

// we use the rabbit connection factory, just like normal ConnectionFactory cf = new ConnectionFactory();

// set the hostname and the port
cf.HostName = "rabbitmq-server";
cf.Port = AmqpTcpEndpoint.DefaultAmqpSslPort;
cf.AuthMechanisms = new AuthMechanismFactory[] { new ExternalMechanismFactory() };

// I've imported my certificate into my certificate store
// (the Personal/Certificates folder in the certmgr mmc snap-in)
// Let's open that store right now.
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
The other change I made was to insert a Console.ReadLine() just before all of my using () statements cause everything to close down.  By doing this, I can view the RabbitMQ management web app to ensure that I'm authenticating using my certificate.


Wednesday, December 21, 2011

Windows does not always honor DNS order

I was having a problem where some internal server names would become unresolvable after being resolvable.  After becoming tired of flushing the dns resolver cache, I finally opened wireshark to see what was going on.

To my surprise, windows was using my secondary DNS (8.8.8.8) instead of my primary, internal DNS!  After some searching, I finally found this knowledge base article.
This behavior occurs because the Windows XP DNS Client service (Dnscache) follows a certain algorithm when it decides the order in which it uses the DNS servers configured in the TCP/IP properties. If the DNS server list is reprioritized, the Windows XP DNS Client service resets the server priority at periodic intervals. By default, the server priorities are reset every 15 minutes.
 Luckily, the workaround in that same article fixes the issues I was having.
To work around this behavior, modify the registry so that the DNS server that is configured first is tried first on each query. Follow these steps, and then quit Registry Editor:
  1. Click Start, click Run, type regedit, and then click OK.
  2. Locate and then click the following key in the registry:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters
  3. On the Edit menu, point to New, and then click REG_DWORD.
  4. Type ServerPriorityTimeLimit, and then press ENTER.
  5. On the Edit menu, click Modify.
  6. Type 0, and then click OK.
When you set ServerPriorityTimeLimit to 0 (zero), the server priorities are reset before the DNS Client service decides which DNS server to use. You must restart Windows XP for these changes to take affect. Any other value used in this field will cause the default behavior.
Even though this article is for Windows XP, this worked for me on Windows 7.

Sunday, September 18, 2011

Getting Started with Ruby on Rails on Ubuntu 11.04

Recently, my friend Richard has been getting into Ruby. At first, I scoffed, telling Richard that Ruby might be a fine language, but I want nothing to do with the people who write in Ruby.

Remember back in the day when we all read slashdot? Pretty much anyone who admitted to running or even just not hating windows would eventually be set on fire in the comments. I imagine those zealots as the same group that evangelizes Ruby. I'm not alone in thinking this.

The problem, though, is that I'm starting to notice Ruby creeping into my radar more and more. My investigation of node.js made me realize that a lot of the frameworks I like are actually... ports from Ruby. I think technologies like LESS, SASS, and CoffeeScript are pretty cool. And although they're hardly exclusive to Ruby, that's the general direction they're coming from. It's even hard to read about state-of-the-industry testing practices without coming across rspec and cucumber.

Ruby is beginning to remind me of SmallTalk. The language was popular, sure, but what was really appealing was the set of people involved (like Kent Beck, Ward Cunningham, etc) and the concepts that were born from that community. Model-View-Controller. eXtreme Programming (XP). Maybe you've heard of those?

At this point, refusing to take a look at Ruby and learn from the concepts it's birthing would make me one of the snobs I so loathe.

Enough pontification. Let's get to the point - setting up Ruby on Rails on Ubuntu 11.04.

First, start step #2 on this blog: http://blog.zilverline.com/2011/03/28/getting-started-with-ruby-on-rails/. DO NOT install ruby via the ubuntu packages (apt-get). Instead, use RVM, described in step #2.

Once you get to step #6, you're going to hit your next error. Starting the rails server is going to cause this error:
Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs
for a list of available runtimes. (ExecJS::RuntimeUnavailable)
After searching for an hour or so, I finally found this question on stackoverflow. The answer that worked for me was to install node.js. Know how to do that? No problem - just follow these instructions here: http://www.giantflyingsaucer.com/blog/?p=2775.

Once installed, go back and execute 'rails server' and the server will start up! Open http://localhost:3000/ in your browser and you'll see your example rails project!

Have fun!

Tuesday, July 12, 2011

The Case for a Short Bus

We’re building an event-driven architecture (EDA) for our client, so it makes sense that a part of our domain model is going to deal with events. The mountain of concerns associated with moving events around in an EDA is most often handled by an enterprise service bus (ESB). How many concerns, you ask? Just take a look at Wikipedia’s list of “core capabilities” – the ones that “most observers accept” as part of an ESB’s responsibility. There’s a lot going on there! This is why I have a lot of respect for traditional ESBs and the developers who make them.

Unfortunately, in addition to respect, I also have a lot of trepidation when it comes to choosing, installing, configuring, and using an ESB. A lot of the installation and configuration deals with capabilities that I don’t use and the API for those capabilities distracts me from the main use case: creating and consuming events. I wish that traditional ESBs were more like the Spring stack – a set of capabilities that don’t depend on each other, but integrate seamlessly when I want them to.

Another pain point is the mismatch between what existing ESBs offer and what our EDA needs. For example, a benefit of ESBs is the ability to accept events on different types of endpoints (SOAP, REST, FTP, Flat Files, etc). That’s a great selling point if you’re integrating a disparate set of legacy components, but our situation is different. We are writing or integrating every creator and consumer of events, and even if that changes in the future we are in a position to say, “if you want to play with our system, you will do X, Y, and Z.” For us, many capabilities in traditional ESBs add no value.


In addition, there is friction between our team’s goal and an ESB vendor’s goal. ESB vendors make their products generic enough to appeal to many customers; we are fitting generic frameworks to the specific needs of one customer. The level of abstraction in a commercial ESB is likely to be greater than that of a home-brewed counterpart. Keeping with the previous example, “endpoints” are a level of abstraction that is necessary to support the different ways of transporting events. However, if your requirements allow you to take the “opinion” that all components in the architecture will only use AMQP to transport events, your ESB will be less involved – it will be “lighter”. The more “opinions” you can make about your ESB, the “lighter” it will be compared to existing ESB offerings.


Currently, our needs require more than simple publish/subscribe, but less than a full-blown ESB. When we laid out the responsibilities we wanted to assign to our eventing subsystem, we discovered that most responsibilities (like platform-agnostic event serialization and event persistence) already have great frameworks that perform that responsibility (and do little else). That means we won’t have to write everything from scratch; we just have to wire up a few frameworks. Our team considers this to be the best technical solution AND the solution requiring the least amount of effort.


Because it's a smaller bus for our special needs we’ve decided to call this style of ESB a Short Bus. Kudos to Richard for coming up with the name.


In the next few posts, I’m going to lay out the use cases that our Short Bus will have to support and discuss the frameworks we’ve decided to cobble together into a Short Bus.

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!