Saturday, March 10, 2012

Greening the IT landscape with an eight year desktop replacement plan

At some point soon after being hired in my current position I was told that we had a five year desktop replacement schedule but basically it was a verbal agreement that was made at some point in the past between parties that were no longer involved in the process. While attempting to maintain that replacement schedule I was asked to justify it in order to obtain funding.  In the ensuing conversations it became clear that what the business needed was computers that function reliably allowing employees to be productive, not computers less than five years old.  Once I realized that I was not really going to be able to maintain a five year replacement schedule I decided to determine what replacement schedule would meet the business requirements while maintaining the highest possible quality of service.  I was presented with something that a friend of mine would call an "enabling constraint".  The denial of funding actually served as a catalyst to reevaluate the existing assumptions about hardware refresh cycles in my own mind as well as within the organization and the larger IT community.  Ultimately I created a plan for implementing an eight year PC lifecycle that I can manage, that the business can fund and that also has the added benefit of being more environmentally sustainable than a shorter hardware refresh cycle. 

I used the concepts of systems administration philosophy as a basis for creating a plan for an eight year replacement schedule and I will also need to follow them to successfully execute it.  This is how they are presented on the Red Hat website:  "document everything", "know your resources", "know your users", "know your business", "plan ahead" and "expect the unexpected".

Document Everything:

One of the tenets of Systems Administration philosophy is "document everything".  This applies as much to building a business case for making a purchase as it does for configuration change management.  Creating a document or actually a handful of documents that outlines your long term hardware replacement plan is essential to being able to follow through and stick to that schedule.  In addition to being necessary for managing short term processes, documenting everything is essential to creating long-term IT strategy in an organization.  The process of documentation should not only record the current process, it should also serve as a catalyst for thinking critically about the process being documented. 

Know your resources:

I have an inventory of PC's that is kept current by a centralized monitoring and management tool, so I know what hardware I have and can keep track of when it needs to be replaced or upgraded.  The limiting constraint will be knowing what financial resources I have at my disposal during a give time period and allocating them appropriately to meet the goals that are documented in the eight year plan.  Determining what to upgrade or replace and when will be determined by knowing my users and knowing my business.

Know your users and know your business:

In our business, like many, we are using computers for a variety of functions by a variety of people.  Some functions require more computing resources than others and people use their computers in different ways.  People that are content producers, like people in the marketing department that use their computers to edit graphics and photos, will require a computer with more resources than somebody that is primarily a content consumer, reading documents and using web-mail.  Scientists that are analyzing large data sets will require more resources than somebody doing word processing.  In our business I know that we have users and applications that fall everywhere along the spectrum.  Over time I can rotate new equipment into the positions where they are used for applications that will require more resources while the older equipment can be used where fewer resources are required.

Plan ahead:

Once the applications and required resources are documented, a replacement schedule can be created.  With the replacement schedule in hand a budget can be made, resource acquisition can be planned, and management buy-in can be sought.  Planning ahead will also allow for communication with the departments or individuals whose machines will be replaced in a given time frame.  Planning and scheduling the upgrades well in advance will allow for the changes to be managed  efficiently and for requirements and expectations to be managed effectively.  Planning changes well in advance makes the transitions easier for everyone involved from management and finance to IT staff and end-users.

Expect the Unexpected:

There will be hardware failures, this is just a fact of life.  All systems tend toward entropy and computers, no matter how well designed and well built, are not exempt from the laws of physics.  Hardware failures will happen and when they do spare machines will need to be available so that the business impact of the failure will be minimized.  Spare parts will also need to be kept on hand for some of the minor failures where a full computer replacement would be wasteful.

Additional considerations:

Another thing to consider when planning to keep hardware longer is maintenance.  Rather than simply moving the PC to a new user as-is at the 4 year mark, the computer can be given a tune up before being given to the next person with no down-time to end users.  The computer can be re-imaged to a clean basic configuration and additional RAM can be added at a relatively low cost if necessary.  With the right free and open source tools in place this can be accomplished with little additional expenditure of both financial and time resources.


Greening the IT landscape:

In addition to providing the business with a desktop replacement plan that meets the financial goals of the organization by reducing annual desktop replacement cost by 27%, this plan also contributes to resource conservation in a more global way.  By increasing the working life of each piece of equipment, fewer natural resources will need to be consumed over time to perform computing functions for the business.  Many hardware vendors have "green computing" marketing campaigns that advertise the energy efficiency of their new hardware, but not a single one of them advocates buying fewer computers and keeping them for longer periods of time as a way to decrease the environmental impact of IT infrastructure.  The total number of computers consumed in a given period can be reduced by 36% by increasing the planned hardware refresh interval from 5 years to 8 years.  This change is not only good for the business' bottom line, but for the environment as well.


Saturday, March 3, 2012

PowerShell scripting for automation and documentation

In a previous post I shared a PowerShell script that automated finding folder sizes.  That script was meant to be reused as-is by having parameters passed to it when it is called from a PowerShell window.  The script that I'm posting today is different.  This one is meant to not only automate a process but document what I did at a particular time.  I cover two rules of systems administration in one script "automate everything" and "document everything".  I save a copy of these one-time scripts to use as a template when I need to perform the same operation under different conditions.  They also serve as a record of how I did something in a particular instance because I save them as part of my change management documentation.  Here's a sanitized version of the script that I have saved as my template.

#==========================================================================================
# PowerShell Script
#
# Name:    robocopyFilesToArchive.ps1
# Purpose: use robocopy to move files from one drive to another
#          and keep a log of the move
# Author:  Matthew Sanaker, matthew.sanaker.com
# Date:    3/2/2012
#
#==========================================================================================
#
# USAGE:   this is a one-time script written to automate and document this process
#          to reuse it save it with a new name and change variables as necessary
#
#==========================================================================================

$date = Get-Date;
$year = $date.Year;
$month = "{0:D2}" -f $date.Month;
$day = "{0:D2}" -f $date.Day;
$timestamp = $year.ToString() + $month.ToString() + $day.ToString();

$source = "D:\Data";
$destination = "E:\Archives\Data";
$logFolder = "E:\robocopyLogs";
$logFile = $logFolder + "\" + "robocopyData_$timestamp.log";

if (!(Test-Path $logFolder))
{
New-Item $logFolder -type directory;
}

if (!(Test-Path $destination))
{
New-Item $destination -type directory;
}

robocopy $source $destination /E /ZB /COPY:DATOU /MOVE /R:2 /W:1 /LOG+:$logFile /NP /TEE;





What the script does:

The first block of code retrieves the current date from the system, takes it apart and reassembles it into a time stamp string variable that resembles a conventional BIND DNS serial number.  I like to use these serial numbers at the end of configuration files, log files and other documents to keep them simultaneously sorted by name and date when displayed in the filesystem.  First I use Get-Date to build my $date object.  I then get the year, month and day by turning those properties from my $date object into new objects.  I use .Net number formatting when creating the month and day objects so that I always have two digit months and days.  ' "{0:D2}" -f ' tells PowerShell take object 0 and display it with 2 digits of precision adding leading zeros if necessary. (follow footnote 1 for more options and information)

In the next block I create the variables that I pass to robocopy to move my data.  Those are self-explanatory.  The next two blocks test the paths for both my log file and the destination of my data and create them if they don't exist.  Since robocopy won't create folders for either the destination that you specify or the folder you want your log to be written to, you have to make sure that they exist before you run robocopy otherwise robocopy with throw an error and exit.  In PowerShell, the "!" acts to negate a statement.  Test-Path returns a boolean, so if a file path exists, it returns true otherwise it returns false.  So the statement "if (!(Test-Path $destination))" says "if the folder $destination doesn't exist".  If the if statement evaluates the condition in parenthesis and it is true, PowerShell executes the code between the curly braces.  If the condition in parenthesis is false PowerShell skips the code in the curly braces and moves on.  The statement in the curly braces in each block creates the folder in question.

Once everything is set, robocopy is executed.  Robocopy has a lot of options, I will explain the ones that I have chosen.  $source and $destination are obvious.  "/E" says include empty subfolders.  "/ZB" help recover from errors where a copy gets interrupted or access is denied.  /COPY:DATOU says copy the data, file attributes, timestamp, owner and audit information.  I chose not to copy permissions because I was moving this data to a read-only archive.  /MOVE says copy both files and folders and delete them from the source when finished.  /R and /W are retry options, I specified retry twice and wait 1 second between retries.  Since I wanted to log robocopy's success or failure, I choose /LOG+ to append output to my specified log file, /NP (no progress) so that the file is actually readable and /TEE so that I can also see the output in the shell while it's happening.

Of course robocopy could have been called from a single line right in PowerShell or a cmd window.  The log file alone could serve as good documentation and I always open them up and search for the word "error", but having both files makes the documentation more complete.

(1)


Thursday, March 1, 2012

Getting folder sizes with PowerShell

Today was a good day.  I had an excuse to do some scripting.  One of the Systems Administration rules to live by is "Automate Everything".  Code is reusable, time spent clicking buttons in a GUI to get information is just that, time spent.  Time invested in writing a script to get information for you in a way that is repeatable is time invested.  It may seem like the same amount of time the first time around, but it will pay dividends the next time you don't have to spend time at the GUI clicking, not to mention that you can easily capture the information that you are looking for for further analysis.  Another nice thing about scripting is that you can schedule the collection of information and have it delivered to you.

PowerShell is an interesting environment.  It reminds me somewhat of a Linux shell and it can be scripted like Bash and Perl.  That's what the PowerShell developers were going for, I know, but it does make it much more likeable and useable than batch files and vbScript.  The other thing that I like about PowerShell is that it can easily give me access to .Net namespaces and their properties and methods.  I'm no C# programmer, but I've played with it a bit and it seems that PowerShell is the scripting version of C#.

So my task for today was to get the sizes of a bunch of folders on one of my file servers.  I wanted to know which ones would give up the most space if they were moved to another virtual-disk on that virtual machine.  I had one hard disk that was getting full and I would rather create another disk and move folders to an "archive" location than grow the disk or add another disk under a mount point.  Luckily for me the folders I am dealing with are already sorted by year, so it's just a matter of going back far enough to get the space I want without moving newer files that would inconvenience my users.

So I put together a little script, tested it and when I was satisfied that it would do what I wanted I set it running and went to lunch.  When I got back I had the data that I wanted.  The primary function in the script was something that I came across a couple of years ago and tucked away in a code snippet file.  I honestly can't remember where I found it otherwise I'd give credit where credit is due.  Here is the script:

#==========================================================================================
# PowerShell Script
#
# Name:    CalculateFolderSize.ps1
# Purpose: calculate the size of a folder and its subfolders
#          and return data in csv format
# Author:  Matthew Sanaker, matthew.sanaker.com
# Date:    3/1/2012
#
#==========================================================================================
#
#    USAGE:  CalculateFolderSize.ps1 calculates the size of a folder and its subfolders
#            the root folder and output file are passed as command-line arguments
#            data is returned in two comma delimited fields: folder name, size in GB
#            the output is one row per subfolder 

#
#==========================================================================================

param (
[parameter(Mandatory = $true)][system.IO.DirectoryInfo]$folder,
[parameter(Mandatory = $true)][string]$outFile,

[parameter(Mandatory = $false)][switch]$help
)

$showHelp = " `
    USAGE:  CalculateFolderSize.ps1 calculates the size of a folder and its subfolders
            the root folder and output file are passed as command-line arguments
            data is returned in two comma delimited fields:  folder name, size in GB
            the output is one row per subfolder
           
            example:  ./CalculateFolderSize.ps1 -folder C:\Data -outFile C:\dataSize.txt"
           
if ($help)
{
    $showHelp;
    break;
}

function Get-DirSize {
    param ([system.IO.DirectoryInfo] $dir)
    [decimal] $Size = 0;


    $files = $dir.GetFiles();
    foreach ($file in $files)
    {
        $size += $file.Length;
    }


    $dirs = $dir.GetDirectories()
    foreach ($d in $dirs)
    {
        $size += Get-DirSize($d);
    }
    return $Size;
}


try
{
    $subDirectories = $folder.GetDirectories()

    foreach ($dir in $subDirectories)
        {
        $size = Get-DirSize $dir;
        $GB = $size / 1GB;
        $foldername = $dir.FullName;
        $foldername + "," + $GB | Out-File -FilePath $outFile -Append -NoClobber;
        }
}
catch
{
    "Something does not compute, please check your input"
    "PowerShell Error Message: `
    "
    $error[0];
    $showHelp;
    return;
}

I'll now to go over this a bit to explain what I did and why, so that if it doesn't suit your particular needs you will have a good idea of where to start taking it apart and changing it.

I always start my scripts out from a template with a header and I like to set parameters from the command line instead of hard-coding things.  Setting "[parameter(Mandatory = $true)]" will cause PowerShell to prompt the user for parameters if they are not given when the script is called.  The first parameter, "[system.IO.DirectoryInfo]$folder" is parent folder that you want to start searching from.  I used the .Net class as the object type because it seemed more straight-forward than getting input as a string and converting it later to the object type that I want to work with.  The second parameter is "[string]$outFile" which I will use as the name of the output file.  The next bit is "[switch]$help" which I like to use to pass a friendly help message.

The function that does all of the work is "Get-DirSize" which uses the "system.IO.DirectoryInfo" .Net class to work with the filesystem.  For some reason the "Get-ChildItem" cmdlet doesn't give you folder sizes in an intuitive way, so using the .Net class is actually more direct.  First each folder is entered and all of the files lengths are added up, then each subfolder is entered and the function is run recursively adding the file lengths to the over-all size that is kept in the variable "$size" which is finally returned as the value of the function.

First I pass our "$folder" parameter to the "system.IO.DirectoryInfo.GetDirectories()"method to get our list of subfolders that I keep in the variable "$subDirectories".  I then run a "foreach" loop over the array of sub-directory objects which processes each folder through the Get-DirSize function to return the size of each folder.  Before going on to the next sub-directory object I convert the size which is returned in bytes to something more useful, which for me today was gigabytes by saying "$GB = $size / 1GB".  Next I pull the name of the folder including the path using the "system.IO.DirectoryInfo.FullName" property of the sub-directory object.  Finally I concatenate the folder name, a comma and the folder size and pipe them out to "OutFile" passing it the "$outfile" parameter that I specified on the command line with the "-Append" and "-Noclobber" switches so that when my script is done I have a nice little csv file.  The last thing that I want to point out is the error handling.  It's just a simple "try" and "catch" block which could give you the opportunity to attempt to do something other than fail with an error.  In this case I display the error, show the help message and exit the script.  For a small script like this it's probably overkill, but I keep it in my template as a reminder for when I want to do something more complicated.  Now I can open that file in Excel to run auto-sum on all of the folder sizes, strip off meaningless decimal places or otherwise manipulate my data to get the answer that I'm looking for.

Monday, February 27, 2012

Deploying a Mediawiki server in an Active Directory environment

I recently implemented a knowledge base web application at work using Mediawiki.  There are a number of group collaboration packages out there, but I was really looking for something that would be extremely simple.  I wanted to create a knowledge base web site to replace a  bunch of folders full of Word documents shared on our servers.  Since all of this information is meant to be shared with anybody in the company my aim was to provide something that would make it easier for people to find information or at least make it easier for them to remember where to look.  I prefer to follow Unix philosophy whenever possible even though I am currently working in a predominantly Windows environment.  Part of that philosophy is keeping things as simple as possible and making each component in a system do one thing well.  With that in mind I didn't want to implement a complicated groupware system that had a bunch of features that would either confuse people or just not get used.  For this project I didn't need a document management system, enterprise social networking, project management, granular permission sets, etc.  I just wasn't looking for a one web application to rule them all sort of a setup.    My other criteria when choosing a software package was that the organization behind it have a good history, that they provide regular updates, that there is a good likelihood that the software will have a future, that there is good documentation for it and that data could be migrated to another platform in the future if necessary.  Basically it needed to be easy to install and maintain either by me or anyone else that needs to maintain it in the future and our data should not be locked into a proprietary format.  Mediawiki fit all of those requirements and it does the one thing that it was designed for well.

I expect that the majority of our users will be content consumers rather than contributors, so I was not terribly concerned with authenticating everyone.  If all somebody needs to do is read something I didn't want to force them to log into the site.  I did however want some amount of accountability for the edits that are made to the pages in the knowledge base so it needed to have a user authentication system to log users in before editing content.  If you are implementing any sort of application that uses authentication, you'll can make both using your application and administering it much easier by using a centralized authentication mechanism.  So my one requirement in this regard was that the system must allow them to use their existing Active Directory user account to log in.  Using Ryan Lane's "LdapAuthentication" extension made things easier for me and our users.

The installation instructions for this extension are well documented so I won't go into great detail here. Basically you need to install the required software packages on your system and install the certificate(s) of the domain controller(s) and configure open-ldap telling it where you have stored those certificates.  Next you'll need to create a user with read access to Active Directory to act as a proxy for your application.  A regular user with no special privileges will work just fine.  My proxy user is just a member of the "Domain Users" group and that's it.  Download the latest tarball of the extension and extract the contents into its own directory underneath your extensions directory and you're ready to configure it.

I found some of the documentation of the configuration to be somewhat incomplete but appropriately the documentation was hosted on a Mediawiki site, so I created a user account and made some edits to add what I found to be lacking.  Here is the configuration that is in my "LocalSettings.php" file.

# End of automatically generated settings.
# Add more configuration options below.

#LdapAuthentication Configuration

require_once("$IP/extensions/LdapAuthentication/LdapAuthentication.php");
$wgAuth = new LdapAuthenticationPlugin();
$wgLDAPDomainNames = array("domain");
$wgLDAPServerNames = array("domain" => "domainController.domain.local");
$wgLDAPSearchStrings = array("domain" =>"domain\\USER-NAME");
$wgLDAPSearchAttributes = array("domain" => "sAMAccountName");
$wgLDAPBaseDNs = array("domain" => "dc=domain,dc=local");
$wgLDAPEncryptionType = array("domain" => "ssl");
$wgMinimalPasswordLength = 1;
$wgLDAPDisableAutoCreate = array("domain"=>false);
$wgLDAPProxyAgent =  array("domain" => "cn=proxyUser,ou=organizationalUnit,dc=domain,dc=local");
$wgLDAPProxyAgentPassword = array("domain"=>"V+r<I(DNm8%vA");


Some explanation of these lines are in order to fully understand what you'll need to fill in for your particular environment.   I'll now go over the configuration.

require_once("$IP/extensions/LdapAuthentication/LdapAuthentication.php");
$wgAuth = new LdapAuthenticationPlugin();

These two lines tell your Mediawiki installation to load the extension and create a new instance of the LdapAuthenticationPlugin class to use as its authentication mechanism.  Please take care to ensure that "$IP/extensions/..." is the actual path into which you unpacked your extension.

Anywhere that you see "domain" in my example, just substitute the NETBIOS domain name for your AD domain.  The $wgLDAPServerNames array is a space separated list of your domain controllers that you'd like your server to talk to.  $wgLDAPDomainNames, $wgLDAPSearchStrings and $wgLDAPSearchAttributes are self-documenting.  

Your $wgLDAPBaseDNs variable is where you would like the extension to start searching for users.  In the example above it would start at the domain root, but you can start at any arbitrary container as long as all of your users accounts will be found below there in the AD structure, e.g. cn=users,dc=domain,dc=local or ou=users,ou=northamerica,dc=domain,dc=local.  

Using "ssl" for $wgLDAPEncryptionType will keep your domain credentials secure from eavesdropping while they are sent over the wire.  

Setting $wgMinimalPasswordLength to "1" just means that you can't have a blank password.  

The variable $wgLDAPDisableAutoCreate should be set to false if you would like to have Mediawiki automatically create new Mediawiki user accounts for any users that it finds in Active Directory.  If you comment out or delete this line, the default will be false however explicitly setting it makes for good documentation of what your intention is.

Your $wgLDAPProxyAgent will be the full LDAP distinguished name of your proxy user account and $wgLDAPProxyAgentPassword should be a randomly generated complex password such as the example above.

Running a Mediawiki server makes for an easy way for people to share information in a format that they are comfortable using.  Running it with the LDAP Authentication extension installed introduces a modicum of accountability for those that choose to edit or post content with the ease of using account credentials that they already have.

Saturday, February 25, 2012

Configuring Apache to use SSL to secure web traffic

Configuring Apache to secure web traffic is pretty straight forward but as with many things one simple error can cause it to fail to work as it is intended.  I would like to share how I've configured Apache to use SSL to secure web traffic and I'll include a couple of pitfalls that you may run across.  In the end it is a simple process, but it is an essential one to keep your data and your users safe from the possibility of usernames and passwords or other sensitive information from being captured somewhere between the server and the client.  I use SSL to secure all of my web servers whether they are running on Windows or Linux and whether they are accessible from the public internet or only from the local network.  No matter how secure you may think your LAN is or how trustworthy your users are, encrypting network traffic will guarantee that client-server communications are secured against eavesdropping.  Of course any traffic containing sensitive information traversing a public network should be secured without question.

Before configuring Apache, you must first acquire an SSL certificate from a certificate authority that your users will trust.  Depending on where your web site or application will be accessible and what resources you have at your disposal, you can either obtain a certificate from a trusted third party CA or from a CA in your own enterprise public key infrastructure.  I happen to work with a predominantly Windows infrastructure, so I used my Windows Server 2008 R2 CA to generate the certificate.  If you decide, like I did, to use a certificate that is not from a third party CA, Firefox users will be presented with the warning message "This connection is untrusted".  Each user will have to import either the server certificate or the CA certificate in Firefox to avoid the warning. Internet Explorer, Chrome and Safari browsers will trust the Windows CA because they use the integrated Windows certificate store which will contain a copy of your Windows CA certificate and trust it.  If you're expecting that a lot of your site visitors will be using non-windows devices or predominantly Firefox, then save yourself a support headache and buy a certificate from a third party CA.

Once you have your server certificate you can finish the SSL configuration. The particular server that I'm using as an example is running CentOS 6.2.  You will need to check file locations for your distribution if it is different.  The default configuration for Apache running on CentOS is to use '/etc/httpd/conf/httpd.conf' as well as all '.conf' files located in '/etc/httpd/conf.d' because 'httpd.conf' contains the following directive.

Include conf.d/*.conf

The only indication that I'm running this server over SSL from httpd.conf is the directive:

ServerName server.domain.local:443 
  
I left 'Listen 80' in 'httpd.conf' so that my clients could connect to the web site without specifying either "https" or appending ":443" to my site address.  Everything else is configured in '/etc/httpd/conf.d/ssl.conf'. Be sure that only one of your .conf files contains the directive 'Listen 443', which I have in ssl.conf.  If you include it in both your 'httpd.conf' and 'ssl.conf' Apache will fail to start with an "address already in use" error message.

You can leave the majority of  'ssl.conf' with the default configuration that ships with Apache but make sure that it includes 'SSLEngine On'.  The primary lines that you will need to edit in 'ssl.conf' tell Apache which certificates to present to your users' web browsers as well as which private key is associated with the server certificate.  It is the public key / private key pair that are the basis of SSL cryptography.  The public key is provided to the client in the certificate and the private key is held by the server.  Those keys combined with agreed upon encryption algorithms and random number generation provide the session keys that are used by both parties to encrypt a particular conversation.  Here are the directives from 'ssl.conf':

SSLCertificateFile /etc/pki/tls/certs/server.domain.local.crt
SSLCertificateKeyfile /etc/pki/tls/private/server.domain.local.key
SSLCACertificateFile /etc/pki/tls/certs/ca.domain.local.crt
SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt 

The names of these directives make them self-documenting.  The last line is not strictly needed and I'm not using it, but I thought I'd include it for completeness.  My server certificate was generated by the root CA in my PKI, so in my case there is no server chain to speak of.  It's also not needed if your server certificate contains a concatenated list of all of the certificates of any intermediate certificate authorities in the certification chain.

To test your configuration run 'service httpd restart' and browse to your site in the web browsers that your users are likely to be using.  If everything is working properly you should see http redirect to https automatically.  If something isn't working right, check your configuration files for misspellings or other syntax errors and check '/var/log/httpd/error_log' for any other indications of what might be going wrong.  One thing that lies outside of the Apache configuration that will stop traffic cold is firewall rules.  Check your iptables rules to make sure that you are allowing inbound connections on ports 80 and 443.  If you have any doubts about where a problem may lie you can turn your firewall off temporarily just to rule out that possibility.  Securing your web traffic with SSL is fairly easy, but it is essential for keeping your data and your users safe.

Thursday, February 23, 2012

Using a certificate issued by an Active Directory Certificate Authority to secure Apache on Linux

Getting Linux servers to inter-operate with Microsoft's Active Directory can sometimes be challenging.  Finding documentation for getting those two operating systems to talk to each other can be equally challenging.  Sometimes the information that I've found has been out of date or the sources contained little typos which caused commands to fail on one system or another. 

Last weekend I was working on setting up a Linux virtual machine to serve up a Mediawiki site that will act as a knowledge base where I work.  I chose to install a Linux based Mediawiki server  because it does what I want and nothing more and it does it well.  In my case I decided to set up a CentOS 6.2 server to run Apache for me.  Secondly, by not installing another Windows server I've just saved the company I work for about $700 in licensing fees.  So why CentOS you ask?  Well I started out playing with Red Hat Linux when you could still choose "Red Neck" as your installation language and years later got my first IT job working for a local ISP that was running a mix of Red Hat and SunOS servers.  So I like to use CentOS because I know my way around it better than the other Linux distributions out there and it is maintained on an enterprise lifecycle like its upstream counterpart.  My Windows Active Directory environment is running on Windows Server 2008 R2 at the 2008 R2 forest and domain functional levels and my Certificate Authority is running on 2008 R2 as well.

Next you might ask, why do you need to use SSL for a server on your own LAN?  If you are asking people to enter a username and password on a website, then you should secure the communication between the browser and the server no matter how secure you think the network is.  It's just the right thing to do.

After putting together little bits and pieces of information from a variety of websites and a little bit of trial and error, I came up with the procedure detailed below.  While working on this, like my other projects, I kept notes on what worked and what didn't and I thought that I'd share this so that perhaps somebody else will have an easier time setting up something similar.  Let me also say that this is not the only way to accomplish this task.  There are other ways of generating your certificate request and there are other ways of generating the certificate, but these seemed the most straight forward provided that you have root access to your Linux server and Domain Administrator privileges on your Windows CA.


On the Linux server

First you need to use OpenSSL to create the keys that are used to secure traffic to your site. You will want to do this where you are going to store your private keys. On a Red Hat based system it will be '/etc/pki/tls/private'  Once you are working in that directory type:
 
openssl genrsa -out site.domain.local.key 2048
 
This will create a 2048 bit private key and put it in the file site.domain.local.key. This private key is not encrypted so be sure to keep it in a secure location. Keeping it in '/etc/pki/tls/private' should keep it secure. Next you will need to create a certificate request file to submit to the AD CA so that it can generate your certificate.  Generate the certificate request using:

openssl req -new -key site.domain.local.key -out site.domain.local.req
 
This command will prompt you for information that is used by the CA when it creates the certificate.  If you've ever requested an SSL certificate before this information will be familiar to you. The most important one is the common name (CN), which is the fully qualified domain name of your site. When that command has finished you need to copy your certificate request file to a location where you can access it from the Windows Certificate Authority.

On the Windows server 


The Windows CA will not take the request as it was generated by your Linux server. It will be expecting a special certificate attribute that tells it what certificate template to use when creating the certificate. In this case we want to use the "WebServer" template which the Windows CA will use to determine the "Key Usage" attributes that it writes to the certificate. Using that template it will write "Digital Signature" and "Key Encipherment (a0)", which is the intended use of this certificate.  If you haven't already, you will need to add "Web Server" to the list of certificate templates that your CA uses to issue certificates using the Certification Authority mmc. With the certificate request file available you will need to open an elevated command prompt to make the certificate request. At the command prompt type:

certreq -attrib "CertificateTemplate:WebServer" site.domain.local.req
 
When prompted, save the certificate as 'site.domain.local.crt'. You can now copy that certificate back to your Linux server and place it in '/etc/pki/tls/certs' or the appropriate location for your Linux distribution.


Now that you have your certificate, you can go back you your Linux server and configure Apache to use SSL and to authenticate your users using Active Directory but that is a subject for another post.