Invariant Properties

  • rss
  • Home

Embedded KDC Server using Apache MiniKDC

Bear Giles | November 19, 2017

One of the biggest headaches when working with Kerberos is that you need to set up external files in order to use it. That should be a simple one-time change but it can introduce subtle issues such as forcing developers to be on the corporate VPN when doing a build on their laptop.

The Hadoop developers already have a solution – the “MiniKDC” embedded KDC server. This class can be used to create a temporary KDC in the build environment that eliminates any need for external files or network resources. This approach comes at a cost – on my system it takes about 15 seconds to stand up the embedded server. You don’t want to run these tests on every build but a brief delay during integration tests is better than introducing a dependency on a VPN and a running server.

Update regarding ticket caches on 11/22/2017

Important update on ticket caches (and TGT) on 11/22/2017. I need to emphasize that the standard implementation of the Krb5LoginModule does not create ticket cache files. That may not be clear below. I will post a followup article that discusses using the external kinit program to create Kerberos ticket caches.

Embedded Servers and JUnit 4 Rules

Modern test frameworks have a way to stand up test resources before test and tear them down afterwards. With JUnit 4 this is done with Rules. A rule is an annotation that the test runner recognizes and knows how to use. The details are different in other test frameworks (or JUnit 5) but the underlying concepts are the same.

An embedded KDC is a class-level external resource.

  1. public class EmbeddedKdcResource extends ExternalResource {
  2.     private final File baseDir;
  3.     private MiniKdc kdc;
  4.  
  5.     public EmbeddedKdcResource() {
  6.         try {
  7.             baseDir = Files.createTempDirectory("mini-kdc_").toFile();
  8.         } catch (IOException e) {
  9.             // throw AssertionError so we don't have to deal with handling declared
  10.             // exceptions when creating a @ClassRule object.
  11.             throw new AssertionError("unable to create temporary directory: " + e.getMessage());
  12.         }
  13.     }
  14.  
  15.     /***
  16.      * Start KDC.
  17.      */
  18.     @Override
  19.     public void before() throws Exception {
  20.  
  21.         final Properties kdcConf = MiniKdc.createConf();
  22.         kdcConf.setProperty(MiniKdc.INSTANCE, "DefaultKrbServer");
  23.         kdcConf.setProperty(MiniKdc.ORG_NAME, "EMBEDDED");
  24.         kdcConf.setProperty(MiniKdc.ORG_DOMAIN, "INVARIANTPROPERTIES.COM");
  25.  
  26.         // several sources say to use extremely short lifetimes in test environment.
  27.         // however setting these values results in errors.
  28.         //kdcConf.setProperty(MiniKdc.MAX_TICKET_LIFETIME, "15_000");
  29.         //kdcConf.setProperty(MiniKdc.MAX_RENEWABLE_LIFETIME, "30_000");
  30.  
  31.         kdc = new MiniKdc(kdcConf, baseDir);
  32.         kdc.start();
  33.  
  34.         // this is the standard way to set the default location of the JAAS config file.
  35.         // we don't need to do this since we handle it programmatically.
  36.         //System.setProperty("java.security.krb5.conf", kdc.getKrb5conf().getAbsolutePath());
  37.     }
  38.  
  39.     /**
  40.      * Shut down KDC, delete temporary directory.
  41.      */
  42.     @Override
  43.     public void after() {
  44.         if (kdc != null) {
  45.             kdc.stop();
  46.         }
  47.     }
  48.  
  49.     /**
  50.      * Get realm.
  51.      */
  52.     public String getRealm() {
  53.         return kdc.getRealm();
  54.     }
  55.  
  56.     /**
  57.      * Create a keytab file with entries for specified user(s).
  58.      *
  59.      * @param keytabFile
  60.      * @param names
  61.      * @throws Exception
  62.      */
  63.     public void createKeytabFile(File keytabFile, String... names) throws Exception {
  64.         kdc.createPrincipal(keytabFile, names);
  65.     }
  66. }
public class EmbeddedKdcResource extends ExternalResource {
    private final File baseDir;
    private MiniKdc kdc;

    public EmbeddedKdcResource() {
        try {
            baseDir = Files.createTempDirectory("mini-kdc_").toFile();
        } catch (IOException e) {
            // throw AssertionError so we don't have to deal with handling declared
            // exceptions when creating a @ClassRule object.
            throw new AssertionError("unable to create temporary directory: " + e.getMessage());
        }
    }

    /***
     * Start KDC.
     */
    @Override
    public void before() throws Exception {

        final Properties kdcConf = MiniKdc.createConf();
        kdcConf.setProperty(MiniKdc.INSTANCE, "DefaultKrbServer");
        kdcConf.setProperty(MiniKdc.ORG_NAME, "EMBEDDED");
        kdcConf.setProperty(MiniKdc.ORG_DOMAIN, "INVARIANTPROPERTIES.COM");

        // several sources say to use extremely short lifetimes in test environment.
        // however setting these values results in errors.
        //kdcConf.setProperty(MiniKdc.MAX_TICKET_LIFETIME, "15_000");
        //kdcConf.setProperty(MiniKdc.MAX_RENEWABLE_LIFETIME, "30_000");

        kdc = new MiniKdc(kdcConf, baseDir);
        kdc.start();

        // this is the standard way to set the default location of the JAAS config file.
        // we don't need to do this since we handle it programmatically.
        //System.setProperty("java.security.krb5.conf", kdc.getKrb5conf().getAbsolutePath());
    }

    /**
     * Shut down KDC, delete temporary directory.
     */
    @Override
    public void after() {
        if (kdc != null) {
            kdc.stop();
        }
    }

    /**
     * Get realm.
     */
    public String getRealm() {
        return kdc.getRealm();
    }

    /**
     * Create a keytab file with entries for specified user(s).
     *
     * @param keytabFile
     * @param names
     * @throws Exception
     */
    public void createKeytabFile(File keytabFile, String... names) throws Exception {
        kdc.createPrincipal(keytabFile, names);
    }
}

Functional Tests

Once we have an embedded KDC we can quickly write tests that attempt to get a JAAS LoginContext using Kerberos authentication. We call it a success if LoginContext#login() succeeds.

  1. public class BasicKdcTest {
  2.  
  3.     @ClassRule
  4.     public static final TemporaryFolder tmpDir = new TemporaryFolder();
  5.  
  6.     @ClassRule
  7.     public static final EmbeddedKdcResource kdc = new EmbeddedKdcResource();
  8.  
  9.     private static KerberosPrincipal alice;
  10.     private static KerberosPrincipal bob;
  11.     private static File keytabFile;
  12.     private static File ticketCacheFile;
  13.  
  14.     private KerberosUtilities utils = new KerberosUtilities();
  15.  
  16.     @BeforeClass
  17.     public static void createKeytabs() throws Exception {
  18.         // create Kerberos principal and keytab filename.
  19.         alice = new KerberosPrincipal("alice@" + kdc.getRealm());
  20.         bob = new KerberosPrincipal("bob@" + kdc.getRealm());
  21.         keytabFile = tmpDir.newFile("users.keytab");
  22.         ticketCacheFile = tmpDir.newFile("krb5cc_alice");
  23.  
  24.         // create keytab file containing key for Alice but not Bob.
  25.         kdc.createKeytabFile(keytabFile, "alice");
  26.  
  27.         assertThat("ticket cache does not exist", ticketCacheFile.exists(), equalTo(true));
  28.     }
  29.  
  30.     /**
  31.      * Test LoginContext login without TGT ticket (success).
  32.      *
  33.      * @throws LoginException
  34.      */
  35.     @Test
  36.     public void testLoginWithoutTgtSuccess() throws LoginException {
  37.         final LoginContext lc = utils.getKerberosLoginContext(alice, keytabFile);
  38.         lc.login();
  39.         assertThat("subject does not contain expected principal", lc.getSubject().getPrincipals(),
  40.                 contains(alice));
  41.         lc.logout();
  42.     }
  43.  
  44.     /**
  45.      * Test LoginContext login without TGT ticket (unknown user). This only
  46.      * tests for missing keytab entry, not a valid keytab file with an unknown user.
  47.      *
  48.      * @throws LoginException
  49.      */
  50.     @Test(expected = LoginException.class)
  51.     public void testLoginWithoutTgtUnknownUser() throws LoginException {
  52.         @SuppressWarnings("unused")
  53.         final LoginContext lc = utils.getKerberosLoginContext(bob, keytabFile);
  54.     }
  55.  
  56.     /**
  57.      * Test getKeyTab() method (success)
  58.      */
  59.     @Test
  60.     public void testGetKeyTabSuccess() throws LoginException {
  61.         assertThat("failed to see key", utils.getKeyTab(alice, keytabFile), notNullValue());
  62.     }
  63.  
  64.     /**
  65.      * Test getKeyTab() method (unknown user)
  66.      */
  67.     @Test(expected = LoginException.class)
  68.     public void testGetKeyTabUnknownUser() throws LoginException {
  69.         assertThat("failed to see key", utils.getKeyTab(bob, keytabFile), notNullValue());
  70.     }
  71. }
public class BasicKdcTest {

    @ClassRule
    public static final TemporaryFolder tmpDir = new TemporaryFolder();

    @ClassRule
    public static final EmbeddedKdcResource kdc = new EmbeddedKdcResource();

    private static KerberosPrincipal alice;
    private static KerberosPrincipal bob;
    private static File keytabFile;
    private static File ticketCacheFile;

    private KerberosUtilities utils = new KerberosUtilities();

    @BeforeClass
    public static void createKeytabs() throws Exception {
        // create Kerberos principal and keytab filename.
        alice = new KerberosPrincipal("alice@" + kdc.getRealm());
        bob = new KerberosPrincipal("bob@" + kdc.getRealm());
        keytabFile = tmpDir.newFile("users.keytab");
        ticketCacheFile = tmpDir.newFile("krb5cc_alice");

        // create keytab file containing key for Alice but not Bob.
        kdc.createKeytabFile(keytabFile, "alice");

        assertThat("ticket cache does not exist", ticketCacheFile.exists(), equalTo(true));
    }

    /**
     * Test LoginContext login without TGT ticket (success).
     *
     * @throws LoginException
     */
    @Test
    public void testLoginWithoutTgtSuccess() throws LoginException {
        final LoginContext lc = utils.getKerberosLoginContext(alice, keytabFile);
        lc.login();
        assertThat("subject does not contain expected principal", lc.getSubject().getPrincipals(),
                contains(alice));
        lc.logout();
    }

    /**
     * Test LoginContext login without TGT ticket (unknown user). This only
     * tests for missing keytab entry, not a valid keytab file with an unknown user.
     *
     * @throws LoginException
     */
    @Test(expected = LoginException.class)
    public void testLoginWithoutTgtUnknownUser() throws LoginException {
        @SuppressWarnings("unused")
        final LoginContext lc = utils.getKerberosLoginContext(bob, keytabFile);
    }

    /**
     * Test getKeyTab() method (success)
     */
    @Test
    public void testGetKeyTabSuccess() throws LoginException {
        assertThat("failed to see key", utils.getKeyTab(alice, keytabFile), notNullValue());
    }

    /**
     * Test getKeyTab() method (unknown user)
     */
    @Test(expected = LoginException.class)
    public void testGetKeyTabUnknownUser() throws LoginException {
        assertThat("failed to see key", utils.getKeyTab(bob, keytabFile), notNullValue());
    }
}

Next Steps

The next article will discuss the Apache Hadoop UserGroupInformation class and how it connects to JAAS authentication.

Source

You can download the source for this article here: JAAS with Kerberos; Unit Test using Apache Hadoop Mini-KDC.

Categories
hadoop, java, security
Comments rss
Comments rss
Trackback
Trackback

« JAAS without configuration files; JAAS and Kerberos Setting Up SSH Identity Forwarding on Jump Hosts »

Leave a Reply

Click here to cancel reply.

You must be logged in to post a comment.

Archives

  • May 2020 (1)
  • March 2019 (1)
  • August 2018 (1)
  • May 2018 (1)
  • February 2018 (1)
  • November 2017 (4)
  • January 2017 (3)
  • June 2016 (1)
  • May 2016 (1)
  • April 2016 (2)
  • March 2016 (1)
  • February 2016 (3)
  • January 2016 (6)
  • December 2015 (2)
  • November 2015 (3)
  • October 2015 (2)
  • August 2015 (4)
  • July 2015 (2)
  • June 2015 (2)
  • January 2015 (1)
  • December 2014 (6)
  • October 2014 (1)
  • September 2014 (2)
  • August 2014 (1)
  • July 2014 (1)
  • June 2014 (2)
  • May 2014 (2)
  • April 2014 (1)
  • March 2014 (1)
  • February 2014 (3)
  • January 2014 (6)
  • December 2013 (13)
  • November 2013 (6)
  • October 2013 (3)
  • September 2013 (2)
  • August 2013 (5)
  • June 2013 (1)
  • May 2013 (2)
  • March 2013 (1)
  • November 2012 (1)
  • October 2012 (3)
  • September 2012 (2)
  • May 2012 (6)
  • January 2012 (2)
  • December 2011 (12)
  • July 2011 (1)
  • June 2011 (2)
  • May 2011 (5)
  • April 2011 (6)
  • March 2011 (4)
  • February 2011 (3)
  • October 2010 (6)
  • September 2010 (8)

Recent Posts

  • 8-bit Breadboard Computer: Good Encapsulation!
  • Where are all the posts?
  • Better Ad Blocking Through Pi-Hole and Local Caching
  • The difference between APIs and SPIs
  • Hadoop: User Impersonation with Kerberos Authentication

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Pages

  • About Me
  • Notebook: Common XML Tasks
  • Notebook: Database/Webapp Security
  • Notebook: Development Tips

Syndication

Java Code Geeks

Know Your Rights

Support Bloggers' Rights
Demand Your dotRIGHTS

Security

  • Dark Reading
  • Krebs On Security Krebs On Security
  • Naked Security Naked Security
  • Schneier on Security Schneier on Security
  • TaoSecurity TaoSecurity

Politics

  • ACLU ACLU
  • EFF EFF

News

  • Ars technica Ars technica
  • Kevin Drum at Mother Jones Kevin Drum at Mother Jones
  • Raw Story Raw Story
  • Tech Dirt Tech Dirt
  • Vice Vice

Spam Blocked

53,793 spam blocked by Akismet
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox