001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.security.token;
019
020import com.google.protobuf.RpcCallback;
021import com.google.protobuf.RpcController;
022import com.google.protobuf.Service;
023
024import java.io.IOException;
025import java.util.Collections;
026
027import org.apache.hadoop.hbase.CoprocessorEnvironment;
028import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor;
029import org.apache.hadoop.hbase.coprocessor.HasRegionServerServices;
030import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;
031import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
032import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
033import org.apache.hadoop.hbase.ipc.RpcServer;
034import org.apache.hadoop.hbase.ipc.RpcServerInterface;
035import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos;
036import org.apache.hadoop.hbase.regionserver.RegionServerServices;
037import org.apache.hadoop.hbase.security.AccessDeniedException;
038import org.apache.hadoop.hbase.security.User;
039import org.apache.hadoop.security.UserGroupInformation;
040import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
041import org.apache.hadoop.security.token.SecretManager;
042import org.apache.hadoop.security.token.Token;
043import org.apache.yetus.audience.InterfaceAudience;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047/**
048 * Provides a service for obtaining authentication tokens via the
049 * {@link AuthenticationProtos} AuthenticationService coprocessor service.
050 */
051@CoreCoprocessor
052@InterfaceAudience.Private
053public class TokenProvider implements AuthenticationProtos.AuthenticationService.Interface,
054    RegionCoprocessor {
055
056  private static final Logger LOG = LoggerFactory.getLogger(TokenProvider.class);
057
058  private AuthenticationTokenSecretManager secretManager;
059
060
061  @Override
062  public void start(CoprocessorEnvironment env) {
063    // if running at region
064    if (env instanceof RegionCoprocessorEnvironment) {
065      RegionCoprocessorEnvironment regionEnv = (RegionCoprocessorEnvironment)env;
066      /* Getting the RpcServer from a RegionCE is wrong. There cannot be an expectation that Region
067       is hosted inside a RegionServer. If you need RpcServer, then pass in a RegionServerCE.
068       TODO: FIX.
069       */
070      RegionServerServices rss = ((HasRegionServerServices)regionEnv).getRegionServerServices();
071      RpcServerInterface server = rss.getRpcServer();
072      SecretManager<?> mgr = ((RpcServer)server).getSecretManager();
073      if (mgr instanceof AuthenticationTokenSecretManager) {
074        secretManager = (AuthenticationTokenSecretManager)mgr;
075      }
076    }
077  }
078
079  @Override
080  public void stop(CoprocessorEnvironment env) throws IOException {
081  }
082
083  /**
084   * @param ugi A user group information.
085   * @return true if delegation token operation is allowed
086   */
087  private boolean isAllowedDelegationTokenOp(UserGroupInformation ugi) throws IOException {
088    AuthenticationMethod authMethod = ugi.getAuthenticationMethod();
089    if (authMethod == AuthenticationMethod.PROXY) {
090      authMethod = ugi.getRealUser().getAuthenticationMethod();
091    }
092    if (authMethod != AuthenticationMethod.KERBEROS
093        && authMethod != AuthenticationMethod.KERBEROS_SSL
094        && authMethod != AuthenticationMethod.CERTIFICATE) {
095      return false;
096    }
097    return true;
098  }
099
100  // AuthenticationService implementation
101
102  @Override
103  public Iterable<Service> getServices() {
104    return Collections.singleton(
105        AuthenticationProtos.AuthenticationService.newReflectiveService(this));
106  }
107
108  @Override
109  public void getAuthenticationToken(RpcController controller,
110                                     AuthenticationProtos.GetAuthenticationTokenRequest request,
111                                     RpcCallback<AuthenticationProtos.GetAuthenticationTokenResponse> done) {
112    AuthenticationProtos.GetAuthenticationTokenResponse.Builder response =
113        AuthenticationProtos.GetAuthenticationTokenResponse.newBuilder();
114
115    try {
116      if (secretManager == null) {
117        throw new IOException(
118            "No secret manager configured for token authentication");
119      }
120      User currentUser = RpcServer.getRequestUser()
121          .orElseThrow(() -> new AccessDeniedException("No authenticated user for request!"));
122      UserGroupInformation ugi = currentUser.getUGI();
123      if (!isAllowedDelegationTokenOp(ugi)) {
124        LOG.warn("Token generation denied for user=" + currentUser.getName() + ", authMethod=" +
125            ugi.getAuthenticationMethod());
126        throw new AccessDeniedException(
127            "Token generation only allowed for Kerberos authenticated clients");
128      }
129
130      Token<AuthenticationTokenIdentifier> token =
131          secretManager.generateToken(currentUser.getName());
132      response.setToken(TokenUtil.toToken(token)).build();
133    } catch (IOException ioe) {
134      CoprocessorRpcUtils.setControllerException(controller, ioe);
135    }
136    done.run(response.build());
137  }
138
139  @Override
140  public void whoAmI(RpcController controller, AuthenticationProtos.WhoAmIRequest request,
141      RpcCallback<AuthenticationProtos.WhoAmIResponse> done) {
142    AuthenticationProtos.WhoAmIResponse.Builder response =
143        AuthenticationProtos.WhoAmIResponse.newBuilder();
144    RpcServer.getRequestUser().ifPresent(requestUser -> {
145      response.setUsername(requestUser.getShortName());
146      AuthenticationMethod method = requestUser.getUGI().getAuthenticationMethod();
147      if (method != null) {
148        response.setAuthMethod(method.name());
149      }
150    });
151    done.run(response.build());
152  }
153}