Thursday, December 23, 2010

Creating a Kerberized client/server app in Ruby with GSSAPI

It's been awhile since I posted anything so I thought I'd write a little bit about the GSSAPI Ruby gem that I have been working on recently. GSSAPI and Kerberos are probably the biggest things I've longed for in Ruby for some time now. I work in an Enterprise environment for the State of North Dakota and we use Active Directory authentication for most everything. Since AD has Kerberos built-in, it opens a lot of options for authentication and encryption. I wanted to bring this functionality into the Ruby world so I wrote a gem that utilizes FFI and the underlying GSSAPI C libraries to do exactly that. Since the MIT Kerberos source comes with a sample client and server application I thought I would create a simple duplication of that in Ruby.

The Client

GSSAPI/Kerberos relies on a Service Principal Name(SPN) on the server. We specify it in the client code so that is why I mention it here. If you were creating an actual server for production you would want to create a separate SPN for it, but since this is a demo I will be using the 'host' SPN which should exist by the mere fact that the server is part of the Kerberos Realm. To run the client you will also need to have credentials of your own via 'kinit'.

Require the appropriate gems.
require 'gssapi'
require 'base64'
require 'socket'

Specify the server and the service name. Together these become the SPN.
host = 'example.org'
service = 'host'

Open up the connection to the server
sock = TCPSocket.new(host, 8082)

Initialize the GSSAPI security context and Base64 encode it so it is ready for transport. The Base64 encoding is not really necessary in this case, but since GSSAPI is often used in HTTP communication and is Base64 encoded there I am using it in this example. The GSSAPI::Simple wrapper is doing a lot in the background for you. It's importing the SPN for use in GSSAPI string types, managing memory and all of that good stuff. If you need to do something a bit out of the ordinary with GSSAPI and need to call the gss_* methods directly, check out the Simple wrapper for examples.
cli = GSSAPI::Simple.new(host, service)
tok = cli.init_context
stok = Base64.strict_encode64(tok)

Send the initial token, get back a response token and complete the security context. Once this step is done the set-up is complete and we are ready to start passing messages.
sock.write("#{stok}\n") # send initial token
stok = sock.gets.chomp # get back continuation token
ctx = cli.init_context(Base64.strict_decode64(stok.chomp)) # complete security context
puts "Connection #{(ctx ? 'succeeded' : 'failed')}"

Now just prompt for text from STDIN, encrypt the message and send it to the server. Do this until "exit" is typed on the command line.
begin
print "> "
msg = STDIN.gets.chomp
emsg = cli.wrap_message(msg)
sock.write("#{Base64.strict_encode64(emsg)}\n")
end while msg != 'exit'

sock.close

The Server


Set up is the same.
require 'gssapi'
require 'base64'
require 'socket'

The host and service should be the same as the client as well.
host = 'example.org'
service = 'host'

You won't always want to run as root so the system keytab may not be available. You can specify an alternate keytab as long as it contains the correct SPN.
keytab = "#{ENV['HOME']}/.gssapi/krb5.keytab" # this is optional, but probably required if not running as root

Start listening....
tcpsrv = TCPServer.new(host, 8082)

Initialize the security context for the server and acquire the credentials from the keytab.
srv = GSSAPI::Simple.new(host, service, keytab)
srv.acquire_credentials

Start the main server loop. This will listen for incoming requests, accept the incoming token, and send back a token so the client can complete the security context. After that it just listens for messages, decrypts them and prints them out. It will exit when it receives the "exit" message.
loop do
Thread.start(tcpsrv.accept) do |s|
print(s, "Accepted Connection\n")
stok = s.gets.chomp
print(s, "Received string#{stok}\n")
otok = srv.accept_context(Base64.strict_decode64(stok.chomp))
s.write("#{Base64.strict_encode64(otok)}\n")

begin
emsg = s.gets.chomp
msg = srv.unwrap_message(Base64.strict_decode64(emsg.chomp))
puts "Received: #{msg}"
end while msg != 'exit'

print(s, "Closing Socket\n")
s.close
end
end

That's really it and it's much shorter and easier to understand than the C counter-part. If you have issues running it make sure you have your credentials on the client (kinit) and the server has access to the appropriate keytab. These are the two big follies that will mess things up. Other than that have fun with the gssapi gem. If you use it and want to share, add your app to the gssapi gem wiki:
https://github.com/zenchild/gssapi/wiki/Users-of-GSSAPI

Cheers and Happy Solstice!

7 comments:

  1. hello sir,

    using the above methods i am getting the errror in the server ...unable to acquire credentials: GSS_S_COMPLETE not returned!!!

    how to setup the keytab etc..can u explain me a bit in it!!

    ReplyDelete
  2. Have you acquired your credentials with 'kinit'? Do they show up when you do a klist?

    What OS and Kerberos libraries are you using?

    ReplyDelete
  3. Hello sir, here is the complete problem discription.
    #I have kerberos running on a windows server 2008.
    #using that server i generated a keytab file for anks.key
    #i copied the file to my unix host and renamed it to krb5.key
    #i am using this file as keytab in the server program

    am still getting the error as unable to acquire credentials:GSS_S_COMPLETE not returned.

    i do have the client credentials and it is working fine.am only getting problems with the server.any help will be very useful to me!

    thank you

    ReplyDelete
  4. A couple things to try...

    1. Do you have the MIT Kerberos libraries installed? Ruby gssapi does not work with Heimdal at this time.

    2. Outside of Ruby, can you acquire credentials with the keytab file via 'kinit -kt krb5.key host/myhost@whatever'

    ReplyDelete
  5. hey hi,

    when i am using the above command it is saying key table entry not found while getting initial credentials.

    bt on doin klist -ke i get
    4 host/myuser.domain.com@DOMAIN.COM

    where myuser is my user on the AD itself!

    so what should i do next!

    am badly stuck...need ur help!! thanx in advance

    ReplyDelete
  6. hey also,
    can you give me ur mail id so that i can contact u.It would be very helpful on ur part because i am very badly stuck in this.

    Hoping for a reply soon!!
    thnx !!

    ReplyDelete
  7. Kerberos troubleshooting is way beyond the scope of this post and honestly I don't have the time to help figure that piece of it out. Here is my suggestion.

    1) Get basic Kerberos working with your box joined to the domain. See Samba's 'net ads join' and 'net ads keytab' commands.

    2) Get this working as root:
    'kinit -k host/myfqdn@MY.DOMAIN'

    3) Run the gssapi server piece as root without specifying a keytab and using the host/ service.

    4) Once all that is working and you still can't get my gem to work open up a ticket on Github.
    https://github.com/zenchild/gssapi

    ReplyDelete