You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@accumulo.apache.org by mhd wrk <mh...@gmail.com> on 2018/07/03 23:33:42 UTC

Connector user switches between threads!

Here's the test case conditions:

-Kerberized cluster
-Thread one authenticates as user1 (using keytab) and start performing a
long running task on a specific table.
-Thread two simply authenticates as user2 (using username  and password ).

My observation is that as soon as thread two logins, thread one runs into
the exception below.

java.lang.RuntimeException:
org.apache.accumulo.core.client.AccumuloSecurityException: Error
BAD_CREDENTIALS for user Principal in credentials object should match
kerberos principal. Expected 'user2@example' but was 'user1@example' on
table user1.test_table(ID:3) - Username or Password is Invalid
    at
org.apache.accumulo.core.client.impl.ScannerIterator.hasNext(ScannerIterator.java:161)
    at java.lang.Iterable.forEach(Iterable.java:74)
    at com.example.test.TestBug$1.run(TestBug.java:53)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.accumulo.core.client.AccumuloSecurityException: Error
BAD_CREDENTIALS for user Principal in credentials object should match
kerberos principal. Expected 'user2@example' but was 'user1@example' on
table user1.test_table(ID:3) - Username or Password is Invalid
    at
org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:465)
    at
org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:285)
    at
org.apache.accumulo.core.client.impl.ScannerIterator$Reader.run(ScannerIterator.java:80)
    at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at
org.apache.accumulo.fate.util.LoggingRunnable.run(LoggingRunnable.java:35)
    ... 1 more
Caused by: ThriftSecurityException(user:Principal in credentials object
should match kerberos principal. Expected 'user2@example' but was
'user1@example', code:BAD_CREDENTIALS)
    at
org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result$startScan_resultStandardScheme.read(TabletClientService.java:6696)
    at
org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result$startScan_resultStandardScheme.read(TabletClientService.java:6673)
    at
org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result.read(TabletClientService.java:6596)
    at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:78)
    at
org.apache.accumulo.core.tabletserver.thrift.TabletClientService$Client.recv_startScan(TabletClientService.java:232)
    at
org.apache.accumulo.core.tabletserver.thrift.TabletClientService$Client.startScan(TabletClientService.java:208)
    at
org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:410)
    ... 6 more



========================
Java class to reproduce the issue
========================

package com.example.test;

import org.apache.accumulo.core.client.ClientConfiguration;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.ZooKeeperInstance;
import org.apache.accumulo.core.client.security.tokens.KerberosToken;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import java.io.File;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;

public class TestBug {

    public static void main(String[] args) throws Exception {

        final String hadoopHome = "/path/to/hadoophome";
        final String hadoopConfigFile = "/path/to/my-site.xml";

        final String accumuloTableName = "test_table";

        final String user1Name = "user1@example";
        final String user1Keytab = "/etc/security/keytabs/user1.keytab";

        final String user2Name = "user2@example";
        final String user2Password = "user2password";

        System.out.println("===================== Initializing Hadoop");
        System.setProperty("hadoop.home.dir", hadoopHome);
        Configuration hadoopConf = new Configuration();
        hadoopConf.addResource(new Path(hadoopConfigFile));
        UserGroupInformation.setConfiguration(hadoopConf);

        Thread scanner = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("=====================
Authenticate as user1 using keytab");
                    Connector connector = new
ZooKeeperInstance(ClientConfiguration.loadDefault())
                            .getConnector(user1Name, new
KerberosToken(user1Name, new File(user1Keytab), true));

                    for ( int i = 0; true; ++i) {
                        System.out.println("===================== scan
table - " + i);
                        connector.createScanner(accumuloTableName, new
Authorizations()).forEach(e -> System.out.println(e));
                        Thread.sleep(1000);
                    }
                }catch(Exception x) {
                    x.printStackTrace();
                }

            }
        });

        Thread authenticator = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("=====================
authenticate as user2 using password");

                    LoginContext loginCtx = new
LoginContext("MyClientJaas", new CallbackHandler() {
                        public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
                            for (Callback c : callbacks) {
                                if (c instanceof NameCallback)
                                    ((NameCallback) c).setName(user2Name);
                                if (c instanceof PasswordCallback)
                                    ((PasswordCallback)
c).setPassword(user2Password.toCharArray());
                            }
                        }
                    });
                    loginCtx.login();


UserGroupInformation.loginUserFromSubject(loginCtx.getSubject());
                    UserGroupInformation ugi =
UserGroupInformation.getUGIFromSubject(loginCtx.getSubject());
                    Connector newConnector =
ugi.doAs((PrivilegedExceptionAction<Connector>) () -> {
                        KerberosToken token = new KerberosToken();
                        return new
ZooKeeperInstance(ClientConfiguration.loadDefault()).getConnector(token.getPrincipal(),
token);
                    });

                } catch (Exception x) {
                    x.printStackTrace();
                }
            }
        });

        scanner.start();
        scanner.join(1000);

        authenticator.start();

        scanner.join();
        authenticator.join();
    }
}


========================
my jaas config file content:
========================

MyClientJaas {
  com.sun.security.auth.module.Krb5LoginModule required client=TRUE;
};

Client {
  com.sun.security.auth.module.Krb5LoginModule required
  useKeyTab=true
  keyTab="/etc/security/keytabs/user1.keytab"
  storeKey=true
  useTicketCache=false
  doNotPrompt=true
  debug=true
  principal="user1@example";
};

Re: Connector user switches between threads!

Posted by Josh Elser <el...@apache.org>.
Please read the Javadoc for the KerberosToken constructor as it should 
explain what's happening (in addition with Christopher's previous comment).

On 7/3/18 11:50 PM, Mohammad Kargar wrote:
> Thanks for the insight. As of now I don't have any insight about the 
> required code changes :(
> I'll Try to do some debugging.
> 
> Cheers
> Mohammad
> 
> On Tue, Jul 3, 2018, 8:14 PM Christopher, <ctubbsii@apache.org 
> <ma...@apache.org>> wrote:
> 
>     It is known that Hadoop's implementation of Kerberos authentication
>     tokens is plagued by lack of thread safety (see
>     https://issues.apache.org/jira/browse/HADOOP-13066 for some
>     discussion) and UserGroupInformation is notoriously difficult to
>     reason about.
> 
>     Accumulo does not currently support the kind of multi-threaded
>     behavior you're using, but with some work, we probably could. Have
>     you any insight into what kinds of code changes would be required to
>     properly support this multi-threaded case with separate Kerberos
>     users in Accumulo?
> 
>     On Tue, Jul 3, 2018 at 7:33 PM mhd wrk <mhdwrkoffice@gmail.com
>     <ma...@gmail.com>> wrote:
> 
>         Here's the test case conditions:
> 
>         -Kerberized cluster
>         -Thread one authenticates as user1 (using keytab) and start
>         performing a long running task on a specific table.
>         -Thread two simply authenticates as user2 (using username  and
>         password ).
> 
>         My observation is that as soon as thread two logins, thread one
>         runs into the exception below.
> 
>         java.lang.RuntimeException:
>         org.apache.accumulo.core.client.AccumuloSecurityException: Error
>         BAD_CREDENTIALS for user Principal in credentials object should
>         match kerberos principal. Expected 'user2@example' but was
>         'user1@example' on table user1.test_table(ID:3) - Username or
>         Password is Invalid
>              at
>         org.apache.accumulo.core.client.impl.ScannerIterator.hasNext(ScannerIterator.java:161)
>              at java.lang.Iterable.forEach(Iterable.java:74)
>              at com.example.test.TestBug$1.run(TestBug.java:53)
>              at java.lang.Thread.run(Thread.java:748)
>         Caused by:
>         org.apache.accumulo.core.client.AccumuloSecurityException: Error
>         BAD_CREDENTIALS for user Principal in credentials object should
>         match kerberos principal. Expected 'user2@example' but was
>         'user1@example' on table user1.test_table(ID:3) - Username or
>         Password is Invalid
>              at
>         org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:465)
>              at
>         org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:285)
>              at
>         org.apache.accumulo.core.client.impl.ScannerIterator$Reader.run(ScannerIterator.java:80)
>              at
>         java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
>              at
>         java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
>              at
>         org.apache.accumulo.fate.util.LoggingRunnable.run(LoggingRunnable.java:35)
>              ... 1 more
>         Caused by: ThriftSecurityException(user:Principal in credentials
>         object should match kerberos principal. Expected 'user2@example'
>         but was 'user1@example', code:BAD_CREDENTIALS)
>              at
>         org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result$startScan_resultStandardScheme.read(TabletClientService.java:6696)
>              at
>         org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result$startScan_resultStandardScheme.read(TabletClientService.java:6673)
>              at
>         org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result.read(TabletClientService.java:6596)
>              at
>         org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:78)
>              at
>         org.apache.accumulo.core.tabletserver.thrift.TabletClientService$Client.recv_startScan(TabletClientService.java:232)
>              at
>         org.apache.accumulo.core.tabletserver.thrift.TabletClientService$Client.startScan(TabletClientService.java:208)
>              at
>         org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:410)
>              ... 6 more
> 
> 
> 
>         ========================
>         Java class to reproduce the issue
>         ========================
> 
>         package com.example.test;
> 
>         import org.apache.accumulo.core.client.ClientConfiguration;
>         import org.apache.accumulo.core.client.Connector;
>         import org.apache.accumulo.core.client.ZooKeeperInstance;
>         import org.apache.accumulo.core.client.security.tokens.KerberosToken;
>         import org.apache.accumulo.core.security.Authorizations;
>         import org.apache.hadoop.conf.Configuration;
>         import org.apache.hadoop.fs.Path;
>         import org.apache.hadoop.security.UserGroupInformation;
> 
>         import javax.security.auth.callback.Callback;
>         import javax.security.auth.callback.CallbackHandler;
>         import javax.security.auth.callback.NameCallback;
>         import javax.security.auth.callback.PasswordCallback;
>         import javax.security.auth.callback.UnsupportedCallbackException;
>         import javax.security.auth.login.LoginContext;
>         import java.io.File;
>         import java.io.IOException;
>         import java.security.PrivilegedExceptionAction;
> 
>         public class TestBug {
> 
>              public static void main(String[] args)throws Exception {
> 
>                  final String hadoopHome ="/path/to/hadoophome";
>                  final String hadoopConfigFile ="/path/to/my-site.xml";
> 
>                  final String accumuloTableName ="test_table";
> 
>                  final String user1Name ="user1@example";
>                  final String user1Keytab ="/etc/security/keytabs/user1.keytab";
> 
>                  final String user2Name ="user2@example";
>                  final String user2Password ="user2password";
> 
>                  System.out.println("===================== Initializing Hadoop");
>                  System.setProperty("hadoop.home.dir", hadoopHome);
>                  Configuration hadoopConf =new Configuration();
>                  hadoopConf.addResource(new Path(hadoopConfigFile));
>                  UserGroupInformation.setConfiguration(hadoopConf);
> 
>                  Thread scanner =new Thread(new Runnable() {
>                      @Override
>         public void run() {
>                          try {
>                              System.out.println("===================== Authenticate as user1 using keytab");
>                              Connector connector =new ZooKeeperInstance(ClientConfiguration.loadDefault())
>                                      .getConnector(user1Name,new KerberosToken(user1Name,new File(user1Keytab),true));
> 
>                              for (int i =0;true; ++i) {
>                                  System.out.println("===================== scan table - " + i);
>                                  connector.createScanner(accumuloTableName,new Authorizations()).forEach(e -> System.out.println(e));
>                                  Thread.sleep(1000);
>                              }
>                          }catch(Exception x) {
>                              x.printStackTrace();
>                          }
> 
>                      }
>                  });
> 
>                  Thread authenticator =new Thread(new Runnable() {
>                      @Override
>         public void run() {
>                          try {
>                              System.out.println("===================== authenticate as user2 using password");
> 
>                              LoginContext loginCtx =new LoginContext("MyClientJaas",new CallbackHandler() {
>                                  public void handle(Callback[] callbacks)throws IOException, UnsupportedCallbackException {
>                                      for (Callback c : callbacks) {
>                                          if (cinstanceof NameCallback)
>                                              ((NameCallback) c).setName(user2Name);
>                                          if (cinstanceof PasswordCallback)
>                                              ((PasswordCallback) c).setPassword(user2Password.toCharArray());
>                                      }
>                                  }
>                              });
>                              loginCtx.login();
> 
>                              UserGroupInformation.loginUserFromSubject(loginCtx.getSubject());
>                              UserGroupInformation ugi = UserGroupInformation.getUGIFromSubject(loginCtx.getSubject());
>                              Connector newConnector = ugi.doAs((PrivilegedExceptionAction<Connector>) () -> {
>                                  KerberosToken token =new KerberosToken();
>                                  return new ZooKeeperInstance(ClientConfiguration.loadDefault()).getConnector(token.getPrincipal(), token);
>                              });
> 
>                          }catch (Exception x) {
>                              x.printStackTrace();
>                          }
>                      }
>                  });
> 
>                  scanner.start();
>                  scanner.join(1000);
> 
>                  authenticator.start();
> 
>                  scanner.join();
>                  authenticator.join();
>              }
>         }
> 
> 
>         ========================
>         my jaas config file content:
>         ========================
> 
>         MyClientJaas {
>            com.sun.security.auth.module.Krb5LoginModule required
>         client=TRUE;
>         };
> 
>         Client {
>            com.sun.security.auth.module.Krb5LoginModule required
>            useKeyTab=true
>            keyTab="/etc/security/keytabs/user1.keytab"
>            storeKey=true
>            useTicketCache=false
>            doNotPrompt=true
>            debug=true
>            principal="user1@example";
>         };
> 

Re: Connector user switches between threads!

Posted by Mohammad Kargar <mk...@phemi.com>.
Thanks for the insight. As of now I don't have any insight about the
required code changes :(
I'll Try to do some debugging.

Cheers
Mohammad

On Tue, Jul 3, 2018, 8:14 PM Christopher, <ct...@apache.org> wrote:

> It is known that Hadoop's implementation of Kerberos authentication tokens
> is plagued by lack of thread safety (see
> https://issues.apache.org/jira/browse/HADOOP-13066 for some discussion)
> and UserGroupInformation is notoriously difficult to reason about.
>
> Accumulo does not currently support the kind of multi-threaded behavior
> you're using, but with some work, we probably could. Have you any insight
> into what kinds of code changes would be required to properly support this
> multi-threaded case with separate Kerberos users in Accumulo?
>
> On Tue, Jul 3, 2018 at 7:33 PM mhd wrk <mh...@gmail.com> wrote:
>
>> Here's the test case conditions:
>>
>> -Kerberized cluster
>> -Thread one authenticates as user1 (using keytab) and start performing a
>> long running task on a specific table.
>> -Thread two simply authenticates as user2 (using username  and password ).
>>
>> My observation is that as soon as thread two logins, thread one runs into
>> the exception below.
>>
>> java.lang.RuntimeException:
>> org.apache.accumulo.core.client.AccumuloSecurityException: Error
>> BAD_CREDENTIALS for user Principal in credentials object should match
>> kerberos principal. Expected 'user2@example' but was 'user1@example' on
>> table user1.test_table(ID:3) - Username or Password is Invalid
>>     at
>> org.apache.accumulo.core.client.impl.ScannerIterator.hasNext(ScannerIterator.java:161)
>>     at java.lang.Iterable.forEach(Iterable.java:74)
>>     at com.example.test.TestBug$1.run(TestBug.java:53)
>>     at java.lang.Thread.run(Thread.java:748)
>> Caused by: org.apache.accumulo.core.client.AccumuloSecurityException:
>> Error BAD_CREDENTIALS for user Principal in credentials object should match
>> kerberos principal. Expected 'user2@example' but was 'user1@example' on
>> table user1.test_table(ID:3) - Username or Password is Invalid
>>     at
>> org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:465)
>>     at
>> org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:285)
>>     at
>> org.apache.accumulo.core.client.impl.ScannerIterator$Reader.run(ScannerIterator.java:80)
>>     at
>> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
>>     at
>> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
>>     at
>> org.apache.accumulo.fate.util.LoggingRunnable.run(LoggingRunnable.java:35)
>>     ... 1 more
>> Caused by: ThriftSecurityException(user:Principal in credentials object
>> should match kerberos principal. Expected 'user2@example' but was
>> 'user1@example', code:BAD_CREDENTIALS)
>>     at
>> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result$startScan_resultStandardScheme.read(TabletClientService.java:6696)
>>     at
>> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result$startScan_resultStandardScheme.read(TabletClientService.java:6673)
>>     at
>> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result.read(TabletClientService.java:6596)
>>     at
>> org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:78)
>>     at
>> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$Client.recv_startScan(TabletClientService.java:232)
>>     at
>> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$Client.startScan(TabletClientService.java:208)
>>     at
>> org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:410)
>>     ... 6 more
>>
>>
>>
>> ========================
>> Java class to reproduce the issue
>> ========================
>>
>> package com.example.test;
>>
>> import org.apache.accumulo.core.client.ClientConfiguration;
>> import org.apache.accumulo.core.client.Connector;
>> import org.apache.accumulo.core.client.ZooKeeperInstance;
>> import org.apache.accumulo.core.client.security.tokens.KerberosToken;
>> import org.apache.accumulo.core.security.Authorizations;
>> import org.apache.hadoop.conf.Configuration;
>> import org.apache.hadoop.fs.Path;
>> import org.apache.hadoop.security.UserGroupInformation;
>>
>> import javax.security.auth.callback.Callback;
>> import javax.security.auth.callback.CallbackHandler;
>> import javax.security.auth.callback.NameCallback;
>> import javax.security.auth.callback.PasswordCallback;
>> import javax.security.auth.callback.UnsupportedCallbackException;
>> import javax.security.auth.login.LoginContext;
>> import java.io.File;
>> import java.io.IOException;
>> import java.security.PrivilegedExceptionAction;
>>
>> public class TestBug {
>>
>>     public static void main(String[] args) throws Exception {
>>
>>         final String hadoopHome = "/path/to/hadoophome";
>>         final String hadoopConfigFile = "/path/to/my-site.xml";
>>
>>         final String accumuloTableName = "test_table";
>>
>>         final String user1Name = "user1@example";
>>         final String user1Keytab = "/etc/security/keytabs/user1.keytab";
>>
>>         final String user2Name = "user2@example";
>>         final String user2Password = "user2password";
>>
>>         System.out.println("===================== Initializing Hadoop");
>>         System.setProperty("hadoop.home.dir", hadoopHome);
>>         Configuration hadoopConf = new Configuration();
>>         hadoopConf.addResource(new Path(hadoopConfigFile));
>>         UserGroupInformation.setConfiguration(hadoopConf);
>>
>>         Thread scanner = new Thread(new Runnable() {
>>             @Override
>>             public void run() {
>>                 try {
>>                     System.out.println("===================== Authenticate as user1 using keytab");
>>                     Connector connector = new ZooKeeperInstance(ClientConfiguration.loadDefault())
>>                             .getConnector(user1Name, new KerberosToken(user1Name, new File(user1Keytab), true));
>>
>>                     for ( int i = 0; true; ++i) {
>>                         System.out.println("===================== scan table - " + i);
>>                         connector.createScanner(accumuloTableName, new Authorizations()).forEach(e -> System.out.println(e));
>>                         Thread.sleep(1000);
>>                     }
>>                 }catch(Exception x) {
>>                     x.printStackTrace();
>>                 }
>>
>>             }
>>         });
>>
>>         Thread authenticator = new Thread(new Runnable() {
>>             @Override
>>             public void run() {
>>                 try {
>>                     System.out.println("===================== authenticate as user2 using password");
>>
>>                     LoginContext loginCtx = new LoginContext("MyClientJaas", new CallbackHandler() {
>>                         public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
>>                             for (Callback c : callbacks) {
>>                                 if (c instanceof NameCallback)
>>                                     ((NameCallback) c).setName(user2Name);
>>                                 if (c instanceof PasswordCallback)
>>                                     ((PasswordCallback) c).setPassword(user2Password.toCharArray());
>>                             }
>>                         }
>>                     });
>>                     loginCtx.login();
>>
>>                     UserGroupInformation.loginUserFromSubject(loginCtx.getSubject());
>>                     UserGroupInformation ugi = UserGroupInformation.getUGIFromSubject(loginCtx.getSubject());
>>                     Connector newConnector = ugi.doAs((PrivilegedExceptionAction<Connector>) () -> {
>>                         KerberosToken token = new KerberosToken();
>>                         return new ZooKeeperInstance(ClientConfiguration.loadDefault()).getConnector(token.getPrincipal(), token);
>>                     });
>>
>>                 } catch (Exception x) {
>>                     x.printStackTrace();
>>                 }
>>             }
>>         });
>>
>>         scanner.start();
>>         scanner.join(1000);
>>
>>         authenticator.start();
>>
>>         scanner.join();
>>         authenticator.join();
>>     }
>> }
>>
>>
>> ========================
>> my jaas config file content:
>> ========================
>>
>> MyClientJaas {
>>   com.sun.security.auth.module.Krb5LoginModule required client=TRUE;
>> };
>>
>> Client {
>>   com.sun.security.auth.module.Krb5LoginModule required
>>   useKeyTab=true
>>   keyTab="/etc/security/keytabs/user1.keytab"
>>   storeKey=true
>>   useTicketCache=false
>>   doNotPrompt=true
>>   debug=true
>>   principal="user1@example";
>> };
>>
>>

Re: Connector user switches between threads!

Posted by Christopher <ct...@apache.org>.
It is known that Hadoop's implementation of Kerberos authentication tokens
is plagued by lack of thread safety (see
https://issues.apache.org/jira/browse/HADOOP-13066 for some discussion) and
UserGroupInformation is notoriously difficult to reason about.

Accumulo does not currently support the kind of multi-threaded behavior
you're using, but with some work, we probably could. Have you any insight
into what kinds of code changes would be required to properly support this
multi-threaded case with separate Kerberos users in Accumulo?

On Tue, Jul 3, 2018 at 7:33 PM mhd wrk <mh...@gmail.com> wrote:

> Here's the test case conditions:
>
> -Kerberized cluster
> -Thread one authenticates as user1 (using keytab) and start performing a
> long running task on a specific table.
> -Thread two simply authenticates as user2 (using username  and password ).
>
> My observation is that as soon as thread two logins, thread one runs into
> the exception below.
>
> java.lang.RuntimeException:
> org.apache.accumulo.core.client.AccumuloSecurityException: Error
> BAD_CREDENTIALS for user Principal in credentials object should match
> kerberos principal. Expected 'user2@example' but was 'user1@example' on
> table user1.test_table(ID:3) - Username or Password is Invalid
>     at
> org.apache.accumulo.core.client.impl.ScannerIterator.hasNext(ScannerIterator.java:161)
>     at java.lang.Iterable.forEach(Iterable.java:74)
>     at com.example.test.TestBug$1.run(TestBug.java:53)
>     at java.lang.Thread.run(Thread.java:748)
> Caused by: org.apache.accumulo.core.client.AccumuloSecurityException:
> Error BAD_CREDENTIALS for user Principal in credentials object should match
> kerberos principal. Expected 'user2@example' but was 'user1@example' on
> table user1.test_table(ID:3) - Username or Password is Invalid
>     at
> org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:465)
>     at
> org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:285)
>     at
> org.apache.accumulo.core.client.impl.ScannerIterator$Reader.run(ScannerIterator.java:80)
>     at
> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
>     at
> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
>     at
> org.apache.accumulo.fate.util.LoggingRunnable.run(LoggingRunnable.java:35)
>     ... 1 more
> Caused by: ThriftSecurityException(user:Principal in credentials object
> should match kerberos principal. Expected 'user2@example' but was
> 'user1@example', code:BAD_CREDENTIALS)
>     at
> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result$startScan_resultStandardScheme.read(TabletClientService.java:6696)
>     at
> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result$startScan_resultStandardScheme.read(TabletClientService.java:6673)
>     at
> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$startScan_result.read(TabletClientService.java:6596)
>     at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:78)
>     at
> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$Client.recv_startScan(TabletClientService.java:232)
>     at
> org.apache.accumulo.core.tabletserver.thrift.TabletClientService$Client.startScan(TabletClientService.java:208)
>     at
> org.apache.accumulo.core.client.impl.ThriftScanner.scan(ThriftScanner.java:410)
>     ... 6 more
>
>
>
> ========================
> Java class to reproduce the issue
> ========================
>
> package com.example.test;
>
> import org.apache.accumulo.core.client.ClientConfiguration;
> import org.apache.accumulo.core.client.Connector;
> import org.apache.accumulo.core.client.ZooKeeperInstance;
> import org.apache.accumulo.core.client.security.tokens.KerberosToken;
> import org.apache.accumulo.core.security.Authorizations;
> import org.apache.hadoop.conf.Configuration;
> import org.apache.hadoop.fs.Path;
> import org.apache.hadoop.security.UserGroupInformation;
>
> import javax.security.auth.callback.Callback;
> import javax.security.auth.callback.CallbackHandler;
> import javax.security.auth.callback.NameCallback;
> import javax.security.auth.callback.PasswordCallback;
> import javax.security.auth.callback.UnsupportedCallbackException;
> import javax.security.auth.login.LoginContext;
> import java.io.File;
> import java.io.IOException;
> import java.security.PrivilegedExceptionAction;
>
> public class TestBug {
>
>     public static void main(String[] args) throws Exception {
>
>         final String hadoopHome = "/path/to/hadoophome";
>         final String hadoopConfigFile = "/path/to/my-site.xml";
>
>         final String accumuloTableName = "test_table";
>
>         final String user1Name = "user1@example";
>         final String user1Keytab = "/etc/security/keytabs/user1.keytab";
>
>         final String user2Name = "user2@example";
>         final String user2Password = "user2password";
>
>         System.out.println("===================== Initializing Hadoop");
>         System.setProperty("hadoop.home.dir", hadoopHome);
>         Configuration hadoopConf = new Configuration();
>         hadoopConf.addResource(new Path(hadoopConfigFile));
>         UserGroupInformation.setConfiguration(hadoopConf);
>
>         Thread scanner = new Thread(new Runnable() {
>             @Override
>             public void run() {
>                 try {
>                     System.out.println("===================== Authenticate as user1 using keytab");
>                     Connector connector = new ZooKeeperInstance(ClientConfiguration.loadDefault())
>                             .getConnector(user1Name, new KerberosToken(user1Name, new File(user1Keytab), true));
>
>                     for ( int i = 0; true; ++i) {
>                         System.out.println("===================== scan table - " + i);
>                         connector.createScanner(accumuloTableName, new Authorizations()).forEach(e -> System.out.println(e));
>                         Thread.sleep(1000);
>                     }
>                 }catch(Exception x) {
>                     x.printStackTrace();
>                 }
>
>             }
>         });
>
>         Thread authenticator = new Thread(new Runnable() {
>             @Override
>             public void run() {
>                 try {
>                     System.out.println("===================== authenticate as user2 using password");
>
>                     LoginContext loginCtx = new LoginContext("MyClientJaas", new CallbackHandler() {
>                         public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
>                             for (Callback c : callbacks) {
>                                 if (c instanceof NameCallback)
>                                     ((NameCallback) c).setName(user2Name);
>                                 if (c instanceof PasswordCallback)
>                                     ((PasswordCallback) c).setPassword(user2Password.toCharArray());
>                             }
>                         }
>                     });
>                     loginCtx.login();
>
>                     UserGroupInformation.loginUserFromSubject(loginCtx.getSubject());
>                     UserGroupInformation ugi = UserGroupInformation.getUGIFromSubject(loginCtx.getSubject());
>                     Connector newConnector = ugi.doAs((PrivilegedExceptionAction<Connector>) () -> {
>                         KerberosToken token = new KerberosToken();
>                         return new ZooKeeperInstance(ClientConfiguration.loadDefault()).getConnector(token.getPrincipal(), token);
>                     });
>
>                 } catch (Exception x) {
>                     x.printStackTrace();
>                 }
>             }
>         });
>
>         scanner.start();
>         scanner.join(1000);
>
>         authenticator.start();
>
>         scanner.join();
>         authenticator.join();
>     }
> }
>
>
> ========================
> my jaas config file content:
> ========================
>
> MyClientJaas {
>   com.sun.security.auth.module.Krb5LoginModule required client=TRUE;
> };
>
> Client {
>   com.sun.security.auth.module.Krb5LoginModule required
>   useKeyTab=true
>   keyTab="/etc/security/keytabs/user1.keytab"
>   storeKey=true
>   useTicketCache=false
>   doNotPrompt=true
>   debug=true
>   principal="user1@example";
> };
>
>