001/**
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *   http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License. See accompanying LICENSE file.
013 */
014package org.apache.hadoop.hdfs.server.datanode.web.webhdfs;
015
016import org.apache.commons.logging.Log;
017import org.apache.commons.logging.LogFactory;
018import org.apache.hadoop.conf.Configuration;
019import org.apache.hadoop.hdfs.DFSConfigKeys;
020import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
021import org.apache.hadoop.hdfs.server.common.JspHelper;
022import org.apache.hadoop.ipc.Client;
023import org.apache.hadoop.security.UserGroupInformation;
024import org.apache.hadoop.security.token.Token;
025
026import com.google.common.annotations.VisibleForTesting;
027import com.google.common.cache.Cache;
028import com.google.common.cache.CacheBuilder;
029
030import java.io.ByteArrayInputStream;
031import java.io.DataInputStream;
032import java.io.IOException;
033import java.util.concurrent.Callable;
034import java.util.concurrent.ExecutionException;
035import java.util.concurrent.TimeUnit;
036
037/**
038 * Create UGI from the request for the WebHDFS requests for the DNs. Note that
039 * the DN does not authenticate the UGI -- the NN will authenticate them in
040 * subsequent operations.
041 */
042public class DataNodeUGIProvider {
043  private final ParameterParser params;
044  @VisibleForTesting
045  static Cache<String, UserGroupInformation> ugiCache;
046  public static final Log LOG = LogFactory.getLog(Client.class);
047
048  DataNodeUGIProvider(ParameterParser params) {
049    this.params = params;
050  }
051
052  public static synchronized void init(Configuration conf) {
053    if (ugiCache == null) {
054      ugiCache = CacheBuilder
055          .newBuilder()
056          .expireAfterAccess(
057              conf.getInt(
058                  DFSConfigKeys.DFS_WEBHDFS_UGI_EXPIRE_AFTER_ACCESS_KEY,
059                  DFSConfigKeys.DFS_WEBHDFS_UGI_EXPIRE_AFTER_ACCESS_DEFAULT),
060              TimeUnit.MILLISECONDS).build();
061    }
062  }
063
064  @VisibleForTesting
065  void clearCache() throws IOException {
066    if (UserGroupInformation.isSecurityEnabled()) {
067      params.delegationToken().decodeIdentifier().clearCache();
068    }
069  }
070
071  UserGroupInformation ugi() throws IOException {
072    UserGroupInformation ugi;
073
074    try {
075      if (UserGroupInformation.isSecurityEnabled()) {
076        final Token<DelegationTokenIdentifier> token = params.delegationToken();
077
078        ugi = ugiCache.get(buildTokenCacheKey(token),
079            new Callable<UserGroupInformation>() {
080              @Override
081              public UserGroupInformation call() throws Exception {
082                return tokenUGI(token);
083              }
084            });
085      } else {
086        final String usernameFromQuery = params.userName();
087        final String doAsUserFromQuery = params.doAsUser();
088        final String remoteUser = usernameFromQuery == null ? JspHelper
089            .getDefaultWebUserName(params.conf()) // not specified in request
090            : usernameFromQuery;
091
092        ugi = ugiCache.get(
093            buildNonTokenCacheKey(doAsUserFromQuery, remoteUser),
094            new Callable<UserGroupInformation>() {
095              @Override
096              public UserGroupInformation call() throws Exception {
097                return nonTokenUGI(usernameFromQuery, doAsUserFromQuery,
098                    remoteUser);
099              }
100            });
101      }
102    } catch (ExecutionException e) {
103      Throwable cause = e.getCause();
104      if (cause instanceof IOException) {
105        throw (IOException) cause;
106      } else {
107        throw new IOException(cause);
108      }
109    }
110
111    return ugi;
112  }
113
114  private String buildTokenCacheKey(Token<DelegationTokenIdentifier> token) {
115    return token.buildCacheKey();
116  }
117
118  private UserGroupInformation tokenUGI(Token<DelegationTokenIdentifier> token)
119      throws IOException {
120    ByteArrayInputStream buf =
121      new ByteArrayInputStream(token.getIdentifier());
122    DataInputStream in = new DataInputStream(buf);
123    DelegationTokenIdentifier id = new DelegationTokenIdentifier();
124    id.readFields(in);
125    UserGroupInformation ugi = id.getUser();
126    ugi.addToken(token);
127    return ugi;
128  }
129
130  private String buildNonTokenCacheKey(String doAsUserFromQuery,
131      String remoteUser) throws IOException {
132    String key = doAsUserFromQuery == null ? String.format("{%s}", remoteUser)
133        : String.format("{%s}:{%s}", remoteUser, doAsUserFromQuery);
134    return key;
135  }
136
137  private UserGroupInformation nonTokenUGI(String usernameFromQuery,
138      String doAsUserFromQuery, String remoteUser) throws IOException {
139
140    UserGroupInformation ugi = UserGroupInformation
141        .createRemoteUser(remoteUser);
142    JspHelper.checkUsername(ugi.getShortUserName(), usernameFromQuery);
143    if (doAsUserFromQuery != null) {
144      // create and attempt to authorize a proxy user
145      ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, ugi);
146    }
147    return ugi;
148  }
149}