Review Board 1.7.22


ZOOKEEPER-1181 : Fix problems with Kerberos TGT renewal

Review Request #1958 - Created Sept. 19, 2011 and updated

Eugene Koontz
ZOOKEEPER-1181
Reviewers
zookeeper
zookeeper-git
Currently, in Zookeeper trunk, there are two problems with Kerberos TGT renewal:

1. TGTs obtained from a keytab are not refreshed periodically. They should be, just as those from ticket cache are refreshed.

2. Ticket renewal should be retried if it fails. Ticket renewal might fail if two or more separate processes (different JVMs) running as the same user try to renew Kerberos credentials at the same time.

Have tested this with a Kerberized HBase/Hadoop cluster on Amazon EC2. Tested with a short Kerberos ticket life (modprinc -maxlife "5 minutes") for zookeeper server and clients. Tested with zookeeper server using a keytab and zookeeper client with ticket cache. Ran YCSB on HBase successfully on a one master, 3 regionserver cluster, where the master and 2 of the regionservers ran Quorum Peers.

Diff revision 1 (Latest)

  1. src/java/main/org/apache/zookeeper/Login.java: Loading...
src/java/main/org/apache/zookeeper/Login.java
Revision de64d0d New Change
[20] 33 lines
[+20]
34

    
   
34

   
35
import org.apache.log4j.Logger;
35
import org.apache.log4j.Logger;
36

    
   
36

   
37
import javax.security.auth.kerberos.KerberosTicket;
37
import javax.security.auth.kerberos.KerberosTicket;
38
import javax.security.auth.Subject;
38
import javax.security.auth.Subject;
39
import java.io.IOException;

   
40
import java.util.Date;
39
import java.util.Date;

    
   
40
import java.util.Random;
41
import java.util.Set;
41
import java.util.Set;
42

    
   
42

   
43
public class Login {
43
public class Login {
44
    Logger LOG = Logger.getLogger(Login.class);
44
    Logger LOG = Logger.getLogger(Login.class);
45
    public CallbackHandler callbackHandler;
45
    public CallbackHandler callbackHandler;
46

    
   
46

   
47
    // LoginThread will sleep until 80% of time from last refresh to
47
    // LoginThread will sleep until 80% of time from last refresh to
48
    // ticket's expiry has been reached, at which time it will wake
48
    // ticket's expiry has been reached, at which time it will wake
49
    // and try to renew the ticket.
49
    // and try to renew the ticket.
50
    private static final float TICKET_RENEW_WINDOW = 0.80f;
50
    private static final float TICKET_RENEW_WINDOW = 0.80f;
51

    
   
51

   

    
   
52
    /**

    
   
53
     * Percentage of random jitter added to the renewal time

    
   
54
     */

    
   
55
    private static final float TICKET_RENEW_JITTER = 0.05f;

    
   
56

   
52
    // Regardless of TICKET_RENEW_WINDOW setting above and the ticket expiry time,
57
    // Regardless of TICKET_RENEW_WINDOW setting above and the ticket expiry time,
53
    // thread will not sleep between refresh attempts any less than 1 minute (60*1000 milliseconds = 1 minute).
58
    // thread will not sleep between refresh attempts any less than 1 minute (60*1000 milliseconds = 1 minute).
54
    // Change the '1' to e.g. 5, to change this to 5 minutes.
59
    // Change the '1' to e.g. 5, to change this to 5 minutes.
55
    private static final long MIN_TIME_BEFORE_RELOGIN = 1 * 60 * 1000L;
60
    private static final long MIN_TIME_BEFORE_RELOGIN = 1 * 60 * 1000L;
56

    
   
61

   
57
    private Subject subject = null;
62
    private Subject subject = null;
58
    private Thread t = null;
63
    private Thread t = null;
59
    private boolean isKrbTicket = false;
64
    private boolean isKrbTicket = false;
60
    private boolean isUsingTicketCache = false;
65
    private boolean isUsingTicketCache = false;

    
   
66
    private boolean isUsingKeytab = false;

    
   
67

   

    
   
68
    /** Random number generator */

    
   
69
    private static Random rng = new Random();

    
   
70

   

    
   
71
    private LoginContext login = null;

    
   
72
    private String loginContextName = null;

    
   
73
    private String keytabFile = null;

    
   
74
    private String principal = null;

    
   
75

   

    
   
76
    private long lastLogin = 0;

    
   
77

   
61
    /**
78
    /**
62
     * LoginThread constructor. The constructor starts the thread used
79
     * LoginThread constructor. The constructor starts the thread used
63
     * to periodically re-login to the Kerberos Ticket Granting Server.
80
     * to periodically re-login to the Kerberos Ticket Granting Server.
64
     * @param loginContextName
81
     * @param loginContextName
65
     *               name of section in JAAS file that will be use to login.
82
     *               name of section in JAAS file that will be use to login.
[+20] [20] 5 lines
[+20] public class Login {
71
     *               Thrown if authentication fails.
88
     *               Thrown if authentication fails.
72
     */
89
     */
73
    public Login(final String loginContextName, CallbackHandler callbackHandler)
90
    public Login(final String loginContextName, CallbackHandler callbackHandler)
74
      throws LoginException {
91
            throws LoginException {
75
        this.callbackHandler = callbackHandler;
92
        this.callbackHandler = callbackHandler;
76
        final LoginContext loginContext = login(loginContextName);
93
        login = login(loginContextName);
77
        subject = loginContext.getSubject();
94
        this.loginContextName = loginContextName;

    
   
95
        subject = login.getSubject();
78
        isKrbTicket = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
96
        isKrbTicket = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
79
        AppConfigurationEntry entries[] = Configuration.getConfiguration().getAppConfigurationEntry(loginContextName);
97
        AppConfigurationEntry entries[] = Configuration.getConfiguration().getAppConfigurationEntry(loginContextName);
80
        for (AppConfigurationEntry entry: entries) {
98
        for (AppConfigurationEntry entry: entries) {

    
   
99
            // there will only be a single entry, so this for() loop will only be iterated through once.
81
            if (entry.getOptions().get("useTicketCache") != null) {
100
            if (entry.getOptions().get("useTicketCache") != null) {
82
                String val = (String)entry.getOptions().get("useTicketCache");
101
                String val = (String)entry.getOptions().get("useTicketCache");
83
                if (val.equals("true")) {
102
                if (val.equals("true")) {
84
                    isUsingTicketCache = true;
103
                    isUsingTicketCache = true;
85
                }
104
                }

    
   
105
            }

    
   
106
            if (entry.getOptions().get("keyTab") != null) {

    
   
107
                keytabFile = (String)entry.getOptions().get("keyTab");

    
   
108
                isUsingKeytab = true;

    
   
109
            }

    
   
110
            if (entry.getOptions().get("principal") != null) {

    
   
111
                principal = (String)entry.getOptions().get("principal");

    
   
112
            }
86
                break;
113
            break;
87
            }
114
        }

    
   
115

   

    
   
116
        if (!isKrbTicket) {

    
   
117
            // if no TGT, do not bother with ticket management.

    
   
118
            return;
88
        }
119
        }
89
        if (isKrbTicket && isUsingTicketCache) {
120

   
90
            // Refresh the Ticket Granting Ticket (TGT) cache periodically. How often to refresh is determined by the
121
        // Refresh the Ticket Granting Ticket (TGT) periodically. How often to refresh is determined by the
91
            // TGT's existing expiry date and the configured MIN_TIME_BEFORE_RELOGIN. For testing and development,
122
        // TGT's existing expiry date and the configured MIN_TIME_BEFORE_RELOGIN. For testing and development,
92
            // you can decrease the interval of expiration of tickets (for example, to 3 minutes) by running :
123
        // you can decrease the interval of expiration of tickets (for example, to 3 minutes) by running :
93
            //  "modprinc -maxlife 3mins <principal>" in kadmin.
124
        //  "modprinc -maxlife 3mins <principal>" in kadmin.
94
            t = new Thread(new Runnable() {
125
        t = new Thread(new Runnable() {
95
                public void run() {
126
            public void run() {
[+20] [20] 5 lines
[+20] public class Login {
101
                        Date nextRefreshDate;
132
                    Date nextRefreshDate;
102
                        if (tgt == null) {
133
                    if (tgt == null) {
103
                            nextRefresh = now + MIN_TIME_BEFORE_RELOGIN;
134
                        nextRefresh = now + MIN_TIME_BEFORE_RELOGIN;
104
                            nextRefreshDate = new Date(nextRefresh);
135
                        nextRefreshDate = new Date(nextRefresh);
105
                            LOG.warn("No TGT found: will try again at " + nextRefreshDate);
136
                        LOG.warn("No TGT found: will try again at " + nextRefreshDate);
106
                        }
137
                    } else {
107
                        else {

   
108
                            // determine how long to sleep from looking at ticket's expiry.

   
109
                            // We must not allow the ticket to expire, but we should take into consideration

   
110
                            // MIN_TIME_BEFORE_RELOGIN. Will not sleep less than MIN_TIME_BEFORE_RELOGIN, except when

   
111
                            // unless it would cause ticket expiration.

   
112
                            nextRefresh = getRefreshTime(tgt);
138
                        nextRefresh = getRefreshTime(tgt);
113
                            long expiry = tgt.getEndTime().getTime();
139
                        long expiry = tgt.getEndTime().getTime();
114

    
   
140
                        Date expiryDate = new Date(expiry);

    
   
141
                        if ((isUsingTicketCache) && (tgt.getEndTime().equals(tgt.getRenewTill()))) {

    
   
142
                            LOG.error("The TGT cannot be renewed beyond the next expiry date: " + expiryDate + "." +

    
   
143
                                    "This process will not be able to authenticate new SASL connections after that " +

    
   
144
                                    "time (for example, it will not be authenticate a new connection with a Zookeeper " +

    
   
145
                                    "Quorum member).  Ask your system administrator to either increase the " +

    
   
146
                                    "'renew until' time by doing : 'modprinc -maxrenewlife " + principal + "' within " +

    
   
147
                                    "kadmin, or instead, to generate a keytab for " + principal + ". Because the TGT's " +

    
   
148
                                    "expiry cannot be further extended by refreshing, exiting refresh thread now.");

    
   
149
                            return;

    
   
150
                        }

    
   
151
                        // determine how long to sleep from looking at ticket's expiry.

    
   
152
                        // We should not allow the ticket to expire, but we should take into consideration

    
   
153
                        // MIN_TIME_BEFORE_RELOGIN. Will not sleep less than MIN_TIME_BEFORE_RELOGIN, unless doing so

    
   
154
                        // would cause ticket expiration.
115
                            if ((nextRefresh > expiry) ||
155
                        if ((nextRefresh > expiry) ||
116
                              ((now + MIN_TIME_BEFORE_RELOGIN) > expiry)) {
156
                                ((now + MIN_TIME_BEFORE_RELOGIN) > expiry)) {
117
                                // expiry is before next scheduled refresh).
157
                            // expiry is before next scheduled refresh).
118
                                LOG.info("refreshing now because expiry is before next scheduled refresh time.");
158
                            LOG.info("refreshing now because expiry is before next scheduled refresh time.");
119
                                nextRefresh = now;
159
                            nextRefresh = now;
120
                            }
160
                        } else {
121
                            else {

   
122
                                if (nextRefresh < (now + MIN_TIME_BEFORE_RELOGIN)) {
161
                            if (nextRefresh < (now + MIN_TIME_BEFORE_RELOGIN)) {
123
                                    // next scheduled refresh is sooner than (now + MIN_TIME_BEFORE_LOGIN).
162
                                // next scheduled refresh is sooner than (now + MIN_TIME_BEFORE_LOGIN).
124
                                    Date until = new Date(nextRefresh);
163
                                Date until = new Date(nextRefresh);
125
                                    Date newuntil = new Date(now + MIN_TIME_BEFORE_RELOGIN);
164
                                Date newuntil = new Date(now + MIN_TIME_BEFORE_RELOGIN);
126
                                    LOG.warn("TGT refresh thread time adjusted from : " + until + " to : " + newuntil + " since "
165
                                LOG.warn("TGT refresh thread time adjusted from : " + until + " to : " + newuntil + " since "
127
                                      + "the former is sooner than the minimum refresh interval ("
166
                                        + "the former is sooner than the minimum refresh interval ("
128
                                      + MIN_TIME_BEFORE_RELOGIN / 1000 + " seconds) from now.");
167
                                        + MIN_TIME_BEFORE_RELOGIN / 1000 + " seconds) from now.");
129
                                }
168
                            }
130
                                nextRefresh = Math.max(nextRefresh, now + MIN_TIME_BEFORE_RELOGIN);
169
                            nextRefresh = Math.max(nextRefresh, now + MIN_TIME_BEFORE_RELOGIN);
131
                            }
170
                        }
132
                            nextRefreshDate = new Date(nextRefresh);
171
                        nextRefreshDate = new Date(nextRefresh);
133
                            if (nextRefresh > expiry) {
172
                        if (nextRefresh > expiry) {
134
                                Date expiryDate = new Date(expiry);

   
135
                                LOG.error("next refresh: " + nextRefreshDate + " is later than expiry " + expiryDate
173
                            LOG.error("next refresh: " + nextRefreshDate + " is later than expiry " + expiryDate
136
                                  + ". This may indicated a clock skew problem. Check that this host and the KDC's "
174
                                    + ". This may indicate a clock skew problem. Check that this host and the KDC's "
137
                                  + "hosts' clocks are in sync.");
175
                                    + "hosts' clocks are in sync. Exiting refresh thread.");
138
                                return;
176
                            return;
139
                            }
177
                        }
140
                        }
178
                    }
141

    
   

   
142
                        if (now < nextRefresh) {
179
                    if (now < nextRefresh) {
143
                            Date until = new Date(nextRefresh);
180
                        Date until = new Date(nextRefresh);
144
                            LOG.info("TGT refresh thread sleeping until: " + until.toString());
181
                        LOG.info("TGT refresh sleeping until: " + until.toString());
145
                            try {
182
                        try {
146
                                Thread.sleep(nextRefresh - now);
183
                            Thread.sleep(nextRefresh - now);
147
                            }
184
                        } catch (InterruptedException ie) {
148
                            catch (InterruptedException ie) {

   
149
                                LOG.warn("TGT renewal thread has been interrupted and will exit.");
185
                            LOG.warn("TGT renewal thread has been interrupted and will exit.");
150
                                break;
186
                            break;
151
                            }
187
                        }
152
                        }
188
                    }
153
                        else {
189
                    else {
154
                            LOG.error("nextRefresh:" + nextRefreshDate + " is in the past: exiting refresh thread. Check"
190
                        LOG.error("nextRefresh:" + nextRefreshDate + " is in the past: exiting refresh thread. Check"
155
                              + " clock sync between this host and KDC - (KDC's clock is likely ahead of this host)."
191
                                + " clock sync between this host and KDC - (KDC's clock is likely ahead of this host)."
156
                              + " Manual intervention will be required for this client to successfully authenticate.");
192
                                + " Manual intervention will be required for this client to successfully authenticate."
157
                            // TODO: if we have a keytab, we can use that to re-initialize and avoid the need for
193
                                + " Exiting refresh thread.");
158
                            // manual intervention.

   
159
                            return;
194
                        return;
160
                        }
195
                    }
161

    
   
196
                    if (isUsingTicketCache) {
162
                        String cmd = "/usr/bin/kinit";
197
                        String cmd = "/usr/bin/kinit";
163
                        if (System.getProperty("zookeeper.kinit") != null) {
198
                        if (System.getProperty("zookeeper.kinit") != null) {
164
                            cmd = System.getProperty("zookeeper.kinit");
199
                            cmd = System.getProperty("zookeeper.kinit");
165
                        }
200
                        }
166
                        String kinitArgs = "-R";
201
                        String kinitArgs = "-R";

    
   
202
                        int retry = 1;

    
   
203
                        while (retry >= 0) {
167
                        try {
204
                            try {
168
                            Shell.execCommand(cmd,kinitArgs);
205
                                LOG.debug("running ticket cache refresh command: " + cmd + " " + kinitArgs);

    
   
206
                                Shell.execCommand(cmd, kinitArgs);

    
   
207
                                break;

    
   
208
                            } catch (Exception e) {

    
   
209
                                if (retry > 0) {

    
   
210
                                    --retry;

    
   
211
                                    // sleep for 10 seconds

    
   
212
                                    try {

    
   
213
                                        Thread.sleep(10 * 1000);

    
   
214
                                    } catch (InterruptedException ie) {

    
   
215
                                        LOG.error("Interrupted while renewing TGT, exiting Login thread");

    
   
216
                                        return;

    
   
217
                                    }

    
   
218
                                } else {

    
   
219
                                    LOG.warn("Could not renew TGT due to problem running shell command: '" + cmd

    
   
220
                                            + " " + kinitArgs + "'" + "; exception was:" + e + ". Exiting refresh thread.",e);

    
   
221
                                    return;
169
                        }
222
                                }
170
                        catch (Shell.ExitCodeException e) {

   
171
                            LOG.error("Could not renew TGT due to problem running shell command: '" + cmd

   
172
                              + " " + kinitArgs + "'" + "; exception was:" + e + ". Will try shell command again at: "

   
173
                              + nextRefreshDate);

   
174
                        }

   
175
                        catch (IOException e) {

   
176
                            LOG.error("Could not renew TGT due to problem running shell command: '" + cmd

   
177
                              + " " + kinitArgs + "'; exception was:" + e + ". Will try shell command again at: "

   
178
                              + nextRefreshDate);

   
179
                        }
223
                            }

    
   
224
                        }

    
   
225
                    }

    
   
226
                    try {

    
   
227
                        int retry = 1;

    
   
228
                        while (retry >= 0) {
180
                        try {
229
                            try {
181
                            reloginFromTicketCache(loginContextName, loginContext);
230
                                reLogin();
182
                            LOG.debug("renewed TGT successfully.");
231
                                break;

    
   
232
                            } catch (LoginException le) {

    
   
233
                                if (retry > 0) {

    
   
234
                                    --retry;

    
   
235
                                    // sleep for 10 seconds.

    
   
236
                                    try {

    
   
237
                                        Thread.sleep(10 * 1000);

    
   
238
                                    } catch (InterruptedException e) {

    
   
239
                                        LOG.error("Interrupted during login retry after LoginException:", le);

    
   
240
                                        throw le;
183
                        }
241
                                    }
184
                        catch (LoginException e) {
242
                                } else {
185
                            LOG.error("Could not renew TGT due to LoginException: " + e + "."
243
                                    LOG.error("Could not refresh TGT for principal: " + principal + ".", le);
186
                              + " Will try again at: "

   
187
                              + nextRefreshDate);

   
188
                        }
244
                                }
189
                    }
245
                            }
190
                }
246
                        }
191
            });
247
                    } catch (LoginException le) {
192
            t.setDaemon(true);
248
                        LOG.error("Failed to refresh TGT: refresh thread exiting now.",le);

    
   
249
                        break;

    
   
250
                    }
193
        }
251
                }
194
        else {

   
195
            LOG.error("Not using Ticket Granting Ticket cache: will not start a TGT renewal thread.");

   
196
        }
252
            }

    
   
253
        });

    
   
254
        t.setDaemon(true);
197
    }
255
    }
198

    
   
256

   
199
    public void startThreadIfNeeded() {
257
    public void startThreadIfNeeded() {
200
        // thread object 't' will be null if a refresh thread is not needed.
258
        // thread object 't' will be null if a refresh thread is not needed.
201
        if (t != null) {
259
        if (t != null) {
202
            t.start();
260
            t.start();
203
        }
261
        }
204
    }
262
    }
205

    
   
263

   

    
   
264
    public void shutdown() {

    
   
265
        if ((t != null) && (t.isAlive())) {

    
   
266
            t.interrupt();

    
   
267
            try {

    
   
268
                t.join();

    
   
269
            } catch (InterruptedException e) {

    
   
270
                LOG.warn("error while waiting for Login thread to shutdown: " + e);

    
   
271
            }

    
   
272
        }

    
   
273
    }

    
   
274

   
Moved from 218

    
   
275
    public Subject getSubject() {
Moved from 219

    
   
276
        return subject;
Moved from 220

    
   
277
    }
206

    
   
278

   
207
    private synchronized LoginContext login(final String loginContextName) throws LoginException {
279
    private synchronized LoginContext login(final String loginContextName) throws LoginException {
208
        if (loginContextName == null) {
280
        if (loginContextName == null) {
209
            throw new LoginException("loginContext name (JAAS file section header) was null. " +
281
            throw new LoginException("loginContext name (JAAS file section header) was null. " +
210
              "Please check your java.security.login.auth.config setting.");
282
                    "Please check your java.security.login.auth.config setting.");
211
        }
283
        }
212
        LoginContext loginContext = new LoginContext(loginContextName,callbackHandler);
284
        LoginContext loginContext = new LoginContext(loginContextName,callbackHandler);
213
        loginContext.login();
285
        loginContext.login();
214
        LOG.info("successfully logged in.");
286
        LOG.info("successfully logged in.");
215
        return loginContext;
287
        return loginContext;
216
    }
288
    }
217

    
   
289

   
218
    public Subject getSubject() {

   
219
        return subject;

   
220
    }

   
221

    
   

   
222
    // c.f. org.apache.hadoop.security.UserGroupInformation.
290
    // c.f. org.apache.hadoop.security.UserGroupInformation.
223
    private long getRefreshTime(KerberosTicket tgt) {
291
    private long getRefreshTime(KerberosTicket tgt) {
224
        long start = tgt.getStartTime().getTime();
292
        long start = tgt.getStartTime().getTime();
225
        long expires = tgt.getEndTime().getTime();
293
        long expires = tgt.getEndTime().getTime();
226
        LOG.info("TGT valid starting at: " + tgt.getStartTime().toString());
294
        LOG.info("TGT valid starting at:        " + tgt.getStartTime().toString());
227
        LOG.info("TGT expires: " + tgt.getEndTime().toString());
295
        LOG.info("TGT expires:                  " + tgt.getEndTime().toString());
228
        long proposedRefresh = start + (long) ((expires - start) * TICKET_RENEW_WINDOW);
296
        long proposedRefresh = start + (long) ((expires - start) *

    
   
297
                (TICKET_RENEW_WINDOW + (TICKET_RENEW_JITTER * rng.nextDouble())));
229
        if (proposedRefresh > expires) {
298
        if (proposedRefresh > expires) {
230
            // proposedRefresh is too far in the future: it's after ticket expires: simply return now.
299
            // proposedRefresh is too far in the future: it's after ticket expires: simply return now.
231
            return System.currentTimeMillis();
300
            return System.currentTimeMillis();
232
        }
301
        }
233
        else {
302
        else {
[+20] [20] 11 lines
[+20] [+] private synchronized KerberosTicket getTGT() {
245
            }
314
            }
246
        }
315
        }
247
        return null;
316
        return null;
248
    }
317
    }
249

    
   
318

   
250
    // TODO : refactor this with login() to maximize code-sharing.
319
    private boolean hasSufficientTimeElapsed() {
251
    public synchronized void reloginFromTicketCache(final String loginContextName, LoginContext loginContext)
320
        long now = System.currentTimeMillis();
252
        throws LoginException {
321
        if (now - getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) {
253
        if (!(isKrbTicket && isUsingTicketCache)) {
322
            LOG.warn("Not attempting to re-login since the last re-login was " +
254
            return;
323
                    "attempted less than " + (MIN_TIME_BEFORE_RELOGIN/1000) + " seconds"+
255
        }
324
                    " before.");
256
        if (loginContext == null) {
325
            return false;
257
            throw new LoginException("login must be done first");
326
        }
258
        }
327
        // register most recent relogin attempt
259
        String principalName = getPrincipalName();
328
        setLastLogin(now);
260
        try {
329
        return true;
261
            LOG.info("Logging out " + principalName);

   
262
            //clear up the Kerberos state. But the tokens are not cleared! As per

   
263
            //the Java kerberos login module code, only the kerberos credentials

   
264
            //are cleared.

   
265
            loginContext.logout();

   
266
            //login and also update the subject field of this instance to
Moved to 387

   
267
            //have the new credentials (pass it to the LoginContext constructor)
Moved to 388

   
268
            if (loginContextName == null) {

   
269
                throw new LoginException("loginContext name (JAAS file section header) was null. " +

   
270
                  "Please check your java.security.login.auth.config setting.");

   
271
            }

   
272
            if (subject == null) {

   
273
                throw new LoginException("login subject was null.");

   
274
            }

   
275
            LOG.info("Logging in " + principalName);

   
276
            loginContext.login();

   
277
            if (principalName.equals("(no principal name)")) {

   
278
                // try again to get the principal name, in case the ticket cache was manually refreshed.

   
279
                principalName = getPrincipalName();

   
280
            }

   
281
            LOG.info("Login successful for " + principalName);

   
282
        } catch (LoginException le) {

   
283
            throw new LoginException("Login failure for " + principalName);

   
284
        }

   
285
    }
330
    }
286

    
   
331

   
287
    private String getPrincipalName() {
332
    /**
288
        try {
333
     * Returns login object
289
            return getSubject().getPrincipals(KerberosPrincipal.class).toArray()[0].toString();
334
     * @return login

    
   
335
     */

    
   
336
    private LoginContext getLogin() {

    
   
337
        return login;
290
        }
338
    }
291
        catch (NullPointerException e) {
339

   
292
            LOG.warn("could not display principal name because login was null or login's subject was null: returning '(no principal found)'.");
340
    /**

    
   
341
     * Set the login object

    
   
342
     * @param login

    
   
343
     */

    
   
344
    private void setLogin(LoginContext login) {

    
   
345
        this.login = login;
293
        }
346
    }
294
        catch (ArrayIndexOutOfBoundsException e) {
347

   
295
            LOG.warn("could not display principal name because login's subject had no principals: returning '(no principal found)'.");
348
    /**

    
   
349
     * Set the last login time.

    
   
350
     * @param time the number of milliseconds since the beginning of time

    
   
351
     */

    
   
352
    private void setLastLogin(long time) {

    
   
353
        lastLogin = time;
296
        }
354
    }
297
        return "(no principal found)";
355

   

    
   
356
    /**

    
   
357
     * Get the time of the last login.

    
   
358
     * @return the number of milliseconds since the beginning of time.

    
   
359
     */

    
   
360
    private long getLastLogin() {

    
   
361
        return lastLogin;
298
    }
362
    }
299

    
   
363

   
300
    public void shutdown() {
364
    /**
301
        if ((t != null) && (t.isAlive())) {
365
     * Re-login a principal. This method assumes that {@link #login(String)} has happened already.
302
            t.interrupt();
366
     * @throws javax.security.auth.login.LoginException on a failure
303
            try {
367
     */
304
                t.join();
368
    // c.f. HADOOP-6559

    
   
369
    private synchronized void reLogin()

    
   
370
            throws LoginException {

    
   
371
        if (!isKrbTicket) {

    
   
372
            return;
305
            }
373
        }
306
            catch (InterruptedException e) {
374
        LoginContext login = getLogin();
307
                LOG.error("error while waiting for Login thread to shutdown: " + e);
375
        if (login  == null) {

    
   
376
            throw new LoginException("login must be done first");
308
            }
377
        }

    
   
378
        if (!hasSufficientTimeElapsed()) {

    
   
379
            return;

    
   
380
        }

    
   
381
        LOG.info("Initiating logout for " + principal);

    
   
382
        synchronized (Login.class) {

    
   
383
            //clear up the kerberos state. But the tokens are not cleared! As per

    
   
384
            //the Java kerberos login module code, only the kerberos credentials

    
   
385
            //are cleared

    
   
386
            login.logout();
Moved from 266

    
   
387
            //login and also update the subject field of this instance to
Moved from 267

    
   
388
            //have the new credentials (pass it to the LoginContext constructor)

    
   
389
            login = new LoginContext(loginContextName, getSubject());

    
   
390
            LOG.info("Initiating re-login for " + principal);

    
   
391
            login.login();

    
   
392
            setLogin(login);
309
        }
393
        }
310
    }
394
    }
311

    
   

   
312
}
395
}
313

    
   

   
  1. src/java/main/org/apache/zookeeper/Login.java: Loading...