Monday, January 16, 2017

Embedded Directory Service


This is the project for starting an embedded Directory Service and Kerberos which can be used to run locally or also can be used for JUNIT Testing for LDAP Integration use cases or Kerberos. This is also useful in Hadoop testing to mimic scenerios of a kerbeorized cluster.

Also, I added few Microsoft AD schema object like sAMAccountName and memberOf to mimic Active Directory. (One can look at /EmbeddedLdapKDC/src/main/resources/krish.schema)


So, the EmbeddedLdapKDC is an eclipse project and can be used to run it inside elipse or debugging if you change any code. The main class is com.krish.ead.server.EADServer.

For writing Junit Test cases, refer to the class com.krish.ead.server.KerberosLdapIntegrationTest. 

A lot of time we need to generate keytabs on the fly as needed for Hadoop related integration Testing also. Refer to the method com.krish.ead.server.KerberosLdapIntegrationTest.createKeytab.

We may want to kinit from unix shell if developing a shell script for various use cases like ldap search, or ldap modify etc. Copy the krb5.conf located here /EmbeddedLdapKDC/src/main/resources/krb5.conf and use this command. 
The default location in unix is /etc/krb5.conf.
env KRB5_CONFIG="location of krb5.conf" 
kinit krish

You may need to programmatically addUser or Groups or add users to the group. Refer to the class 
com.krish.directory.service.EadSchemaService, 

where various methods are exposed to do the same.

Alternatively, you may also use ldapsearch, ldapmodify, ldapadd etc from unix shell or in a script. Refer to the command below to add an user through ldif which is in Resources folder here.
bash>ldapmodify -H "ldap://localhost:10389" -D "cn=manager,ou=users,dc=jpmis,dc=com" -w manager -a -f captain.ldif

Note: By deafult, the embedded server will add users as Read only user, so if you need to add, delete or modify any objects one need to be login as cn=manager,ou=users,dc=jpmis,dc=com with password manager to do so,

The default port for LDAP Server is 10389 and Kerberos is 16088. Feel free to change this.

You can also start the server from shell. Go to StandaloneEmbeddedLdapKDC/bin and invoke ./ead.sh "instance name" "start or run"

In case, you are testing Kerberos with any Hadoop related use case, you may want to go through 

https://github.com/krishdey/EmbeddedLdapKDC

Further I enhanced this framework to solve Multi Region group mapping in Hadoop.

Say the User Groups are dispersed and in various region like EMEA, ASIAPAC and NAEAST configured in Microsoft Active Directory.

Using the hadoop multi group could impose a lot of latency.

So this comes to your rescue and also made parcels for deploying it through Cloudera Manager seamlessly.

https://github.com/krishdey/ApacheDSEmbeddedProgram








Sunday, April 10, 2016

Hadoop Kerberos Login with JAVA

In most of the  enterprise  Hadoop Clusters installation, the cluster is Kerberos enable.
Hence users need to either kinit when using shell based hadoop commands or  when using JAVA required to use the Hadoop API
UserGroupInformation.loginUserFromKeytab(principal, keytab);
Which is fine, when you have a pregenerated Keytab file. But in most of enterprise eco systems, the principal user which runs the job in production environment has to retrieve the password from a Password Vault like ManageEngine, Centrify, Password Vault Manager etc. And hence the keytab has to be generated on the fly before calling UserGroupInformation.loginUserFromKeytab(principal, keytab); 
This can be done by generating the keytab using JAVA as following. If you are using Hadoop classpath jars, these imports will be included by default as hadoop internally uses the same ApacheDS jars. You may include the maven dependency if you using maven.

 org.apache.directory.server
 apacheds-kerberos-codec
 2.0.0-M21


You can also use this createKeytab method to generate keytab with JAVA.

public static void createKeytab(File keytabFile, String principal, String passwd, String realm)
      throws Exception {
  
       Keytab keytab = new Keytab();
       List entries = new ArrayList();
       principal = principal + "@" + realm;
       KerberosTime timestamp = new KerberosTime();
       for (Map.Entry entry : KerberosKeyFactory
          .getKerberosKeys(principal, passwd).entrySet()) {
        EncryptionKey ekey = entry.getValue();
        byte keyVersion = (byte) ekey.getKeyVersion();
        entries
            .add(new KeytabEntry(principal, 1L, timestamp, keyVersion, ekey));

       }
         keytab.setEntries(entries);
         keytab.write(keytabFile);
    }

If you go through the class below, I have created the keytab file as a temp file with RW for user only.
Look at the method public static File createTempKeytabInDir(String dir) The reason being, you do not want the file to be there when you exit the JAVA application as it contain the user credentials and let JVM handles this.

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.net.URI;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
import org.apache.directory.server.kerberos.shared.keytab.Keytab;
import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
import org.apache.directory.shared.kerberos.KerberosTime;
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
import org.apache.directory.shared.kerberos.components.EncryptionKey;
import java.util.Set;
import java.util.HashSet;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.Files;


public class TestKerberosWithCreateKeytab {


    public static void main(String[] args) throws Exception {
        
        System.out.println("krish");
        File keytabFile = KrishUtils.createTempKeytabInDir("Some Dir or Home Dir");
        
        //Get the password from Password Vault and pass here.
        KrishUtils.createKeytab(keytabFile,"user","passwd","DOMAIN.EXAMPLE.COM");
        String keytab = keytabFile.getName();
        String principal = "user"+"@DOMAIN.EXAMPLE.COM";
        //Do kerberos login here
        KrishUtils.authenticate(keytab,principal); 
        //Do some HDFS operation to validate login  
        MyBackgroudMethod thread = new MyBackgroudMethod();
        thread.start();
   }

    static class MyBackgroudMethod extends Thread {

        @Override
        public void run(){
            while (true) {
                System.out.println("Executed!");
                try {
                    HdfsFileInfo hdfs = new HdfsFileInfo();
                    hdfs.callHdfs("hdfs://nameservice1","/tmp/krish.txt","checksum");
  
                    Thread.sleep(30*1000);  
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }
 
 
 static class HdfsFileInfo
 {

  public void callHdfs(String nameservice,String file,String checksum) throws Exception {
                   
   Configuration config = null;
   FileSystem fileSys = null;
   
   try {  
    config = new Configuration();
    fileSys = FileSystem.get(new URI(nameservice), config);

    if (checksum.equals("checksum")) 
    {
     System.out.println(fileSys.getFileChecksum(new Path(file)).toString());
    }
   }
   catch (Exception e) {
    System.out.println(e);
   }
   finally {
    if (fileSys !=null) {
     fileSys.close();
    }
   }

  } 
 }

 static class KrishUtils {
     
    public static File createTempKeytabInDir(String dir) throws Exception{
        File keytabFile = File.createTempFile("krish.keytab-",".tmp", new File(dir));
        keytabFile.deleteOnExit();
        Set perms = new HashSet();
        perms.add(PosixFilePermission.OWNER_READ);
        perms.add(PosixFilePermission.OWNER_WRITE);
        Files.setPosixFilePermissions(keytabFile.toPath(), perms);
        return keytabFile;
    }

   
     public static void createKeytab(File keytabFile, String principal, String passwd, String realm)
      throws Exception {
  
       Keytab keytab = new Keytab();
       List entries = new ArrayList();
       principal = principal + "@" + realm;
       KerberosTime timestamp = new KerberosTime();
       for (Map.Entry entry : KerberosKeyFactory
          .getKerberosKeys(principal, passwd).entrySet()) {
        EncryptionKey ekey = entry.getValue();
        byte keyVersion = (byte) ekey.getKeyVersion();
        entries
            .add(new KeytabEntry(principal, 1L, timestamp, keyVersion, ekey));

       }
         keytab.setEntries(entries);
         keytab.write(keytabFile);
    }

    
   public static synchronized UserGroupInformation authenticate(String keytab, String principal)
    throws AuthenticationFailed {
        File kfile = new File(keytab);
        if (!(kfile.isFile() && kfile.canRead())) {
            throw new IllegalArgumentException("The keyTab file: "
                                               + keytab + " is nonexistent or can't read. "
                                               + "Please specify a readable keytab file for Kerberos auth.");
        }
        try {
            principal = SecurityUtil.getServerPrincipal(principal, "");
        } catch (Exception e) {
            throw new AuthenticationFailed("Host lookup error when resolving principal " + principal, e);
        }
        try {
            UserGroupInformation.loginUserFromKeytab(principal, keytab);
            return UserGroupInformation.getLoginUser();
        } catch (IOException e) {
            throw new AuthenticationFailed("Login failed for principal " + principal, e);
        }
    }

     static class AuthenticationFailed extends Exception {
         public AuthenticationFailed(String reason, Exception cause) {
             super("Kerberos Authentication Failed. " + reason, cause);
         }
     }
  }
Post your questions and suggestions below, I will try to comment/reply to those.