[ 
https://issues.apache.org/jira/browse/DRILL-4280?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15877136#comment-15877136
 ] 

ASF GitHub Bot commented on DRILL-4280:
---------------------------------------

Github user laurentgo commented on a diff in the pull request:

    https://github.com/apache/drill/pull/578#discussion_r102344639
  
    --- Diff: 
exec/java-exec/src/main/java/org/apache/drill/exec/rpc/security/ServerAuthenticationHandler.java
 ---
    @@ -0,0 +1,269 @@
    +/**
    + * Licensed to the Apache Software Foundation (ASF) under one
    + * or more contributor license agreements.  See the NOTICE file
    + * distributed with this work for additional information
    + * regarding copyright ownership.  The ASF licenses this file
    + * to you under the Apache License, Version 2.0 (the
    + * "License"); you may not use this file except in compliance
    + * with the License.  You may obtain a copy of the License at
    + * <p>
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * <p>
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package org.apache.drill.exec.rpc.security;
    +
    +import com.google.common.collect.ImmutableMap;
    +import com.google.common.collect.Maps;
    +import com.google.protobuf.ByteString;
    +import com.google.protobuf.Internal.EnumLite;
    +import com.google.protobuf.InvalidProtocolBufferException;
    +import io.netty.buffer.ByteBuf;
    +import io.netty.buffer.ByteBufInputStream;
    +import org.apache.drill.exec.proto.UserBitShared.SaslMessage;
    +import org.apache.drill.exec.proto.UserBitShared.SaslStatus;
    +import org.apache.drill.exec.rpc.RequestHandler;
    +import org.apache.drill.exec.rpc.Response;
    +import org.apache.drill.exec.rpc.ResponseSender;
    +import org.apache.drill.exec.rpc.RpcException;
    +import org.apache.drill.exec.rpc.ServerConnection;
    +import org.apache.hadoop.security.UserGroupInformation;
    +
    +import javax.security.sasl.SaslException;
    +import javax.security.sasl.SaslServer;
    +import java.io.IOException;
    +import java.lang.reflect.UndeclaredThrowableException;
    +import java.security.PrivilegedExceptionAction;
    +import java.util.EnumMap;
    +import java.util.Map;
    +
    +import static com.google.common.base.Preconditions.checkNotNull;
    +
    +public class ServerAuthenticationHandler<C extends ServerConnection, T 
extends EnumLite> implements RequestHandler<C> {
    +  private static final org.slf4j.Logger logger =
    +      org.slf4j.LoggerFactory.getLogger(ServerAuthenticationHandler.class);
    +
    +  private static final ImmutableMap<SaslStatus, SaslResponseProcessor> 
RESPONSE_PROCESSORS;
    +
    +  static {
    +    final Map<SaslStatus, SaslResponseProcessor> map = new 
EnumMap<>(SaslStatus.class);
    +    map.put(SaslStatus.SASL_START, new SaslStartProcessor());
    +    map.put(SaslStatus.SASL_IN_PROGRESS, new SaslInProgressProcessor());
    +    map.put(SaslStatus.SASL_SUCCESS, new SaslSuccessProcessor());
    +    map.put(SaslStatus.SASL_FAILED, new SaslFailedProcessor());
    +    RESPONSE_PROCESSORS = Maps.immutableEnumMap(map);
    +  }
    +
    +  private final RequestHandler<C> requestHandler;
    +  private final int saslRequestTypeValue;
    +  private final T saslResponseType;
    +
    +  public ServerAuthenticationHandler(final RequestHandler<C> 
requestHandler, final int saslRequestTypeValue,
    +                                     final T saslResponseType) {
    +    this.requestHandler = requestHandler;
    +    this.saslRequestTypeValue = saslRequestTypeValue;
    +    this.saslResponseType = saslResponseType;
    +  }
    +
    +  @Override
    +  public void handle(C connection, int rpcType, ByteBuf pBody, ByteBuf 
dBody, ResponseSender sender)
    +      throws RpcException {
    +    final String remoteAddress = connection.getRemoteAddress().toString();
    +
    +    // exchange involves server "challenges" and client "responses" 
(initiated by client)
    +    if (saslRequestTypeValue == rpcType) {
    +      final SaslMessage saslResponse;
    +      try {
    +        saslResponse = SaslMessage.PARSER.parseFrom(new 
ByteBufInputStream(pBody));
    +      } catch (final InvalidProtocolBufferException e) {
    +        handleAuthFailure(connection, remoteAddress, sender, e, 
saslResponseType);
    +        return;
    +      }
    +
    +      logger.trace("Received SASL message {} from {}", 
saslResponse.getStatus(), remoteAddress);
    +      final SaslResponseProcessor processor = 
RESPONSE_PROCESSORS.get(saslResponse.getStatus());
    +      if (processor == null) {
    +        logger.info("Unknown message type from client from {}. Will stop 
authentication.", remoteAddress);
    +        handleAuthFailure(connection, remoteAddress, sender, new 
SaslException("Received unexpected message"),
    +            saslResponseType);
    +        return;
    +      }
    +
    +      final SaslResponseContext<C, T> context = new 
SaslResponseContext<>(saslResponse, connection, remoteAddress,
    +          sender, requestHandler, saslResponseType);
    +      try {
    +        processor.process(context);
    +      } catch (final Exception e) {
    +        handleAuthFailure(connection, remoteAddress, sender, e, 
saslResponseType);
    +      }
    +    } else {
    +
    +      // this handler only handles messages of SASL_MESSAGE_VALUE type
    +
    +      // drop connection
    +      connection.close();
    +
    +      // the response type for this request type is likely known from 
UserRpcConfig,
    +      // but the client should not be making any requests before 
authenticating.
    +      throw new UnsupportedOperationException(
    +          String.format("Request of type %d is not allowed without 
authentication. " +
    +                  "Client on %s must authenticate before making requests. 
Connection dropped.",
    +              rpcType, remoteAddress));
    +    }
    +  }
    +
    +  private static class SaslResponseContext<C extends ServerConnection, T 
extends EnumLite> {
    +
    +    final SaslMessage saslResponse;
    +    final C connection;
    +    final String remoteAddress;
    +    final ResponseSender sender;
    +    final RequestHandler<C> requestHandler;
    +    final T saslResponseType;
    +
    +    SaslResponseContext(SaslMessage saslResponse, C connection, String 
remoteAddress, ResponseSender sender,
    +                        RequestHandler<C> requestHandler, T 
saslResponseType) {
    +      this.saslResponse = checkNotNull(saslResponse);
    +      this.connection = checkNotNull(connection);
    +      this.remoteAddress = checkNotNull(remoteAddress);
    +      this.sender = checkNotNull(sender);
    +      this.requestHandler = checkNotNull(requestHandler);
    +      this.saslResponseType = checkNotNull(saslResponseType);
    +    }
    +  }
    +
    +  private interface SaslResponseProcessor {
    +
    +    /**
    +     * Process response from client, and if there are no exceptions, send 
response using
    +     * {@link SaslResponseContext#sender}. Otherwise, throw the exception.
    +     *
    +     * @param context response context
    +     */
    +    void process(SaslResponseContext context) throws Exception;
    +
    +  }
    +
    +  private static class SaslStartProcessor implements SaslResponseProcessor 
{
    +
    +    @Override
    +    public void process(final SaslResponseContext context) throws 
Exception {
    +      
context.connection.initSaslServer(context.saslResponse.getMechanism());
    +
    +      // assume #evaluateResponse must be called at least once
    +      
RESPONSE_PROCESSORS.get(SaslStatus.SASL_IN_PROGRESS).process(context);
    +    }
    +  }
    +
    +  private static class SaslInProgressProcessor implements 
SaslResponseProcessor {
    +
    +    @Override
    +    public void process(final SaslResponseContext context) throws 
Exception {
    +      final SaslMessage.Builder challenge = SaslMessage.newBuilder();
    +      final SaslServer saslServer = context.connection.getSaslServer();
    +
    +      final byte[] challengeBytes = evaluateResponse(saslServer, 
context.saslResponse.getData().toByteArray());
    +
    +      if (saslServer.isComplete()) {
    +        challenge.setStatus(SaslStatus.SASL_SUCCESS);
    +        if (challengeBytes != null) {
    +          challenge.setData(ByteString.copyFrom(challengeBytes));
    +        }
    +
    +        handleSuccess(context, challenge, saslServer);
    +      } else {
    +        challenge.setStatus(SaslStatus.SASL_IN_PROGRESS)
    +            .setData(ByteString.copyFrom(challengeBytes));
    +        context.sender.send(new Response(context.saslResponseType, 
challenge.build()));
    +      }
    +    }
    +  }
    +
    +  // only when client succeeds first
    +  private static class SaslSuccessProcessor implements 
SaslResponseProcessor {
    +
    +    @Override
    +    public void process(final SaslResponseContext context) throws 
Exception {
    +      // at this point, #isComplete must be false; so try once, fail 
otherwise
    +      final SaslServer saslServer = context.connection.getSaslServer();
    +
    +      evaluateResponse(saslServer, 
context.saslResponse.getData().toByteArray()); // discard challenge
    +
    +      if (saslServer.isComplete()) {
    +        final SaslMessage.Builder challenge = SaslMessage.newBuilder();
    +        challenge.setStatus(SaslStatus.SASL_SUCCESS);
    +
    +        handleSuccess(context, challenge, saslServer);
    +      } else {
    +        logger.info("Failed to authenticate client from {}", 
context.remoteAddress);
    +        throw new SaslException("Client allegedly succeeded 
authentication, but server did not. Suspicious?");
    +      }
    +    }
    +  }
    +
    +  private static class SaslFailedProcessor implements 
SaslResponseProcessor {
    +
    +    @Override
    +    public void process(final SaslResponseContext context) throws 
Exception {
    +      logger.info("Client from {} failed authentication graciously, and 
does not want to continue.",
    +          context.remoteAddress);
    +      throw new SaslException("Client graciously failed authentication");
    +    }
    +  }
    +
    +  private static byte[] evaluateResponse(final SaslServer saslServer,
    +                                         final byte[] responseBytes) 
throws SaslException {
    +    try {
    +      return UserGroupInformation.getLoginUser().doAs(new 
PrivilegedExceptionAction<byte[]>() {
    +        @Override
    +        public byte[] run() throws Exception {
    +          return saslServer.evaluateResponse(responseBytes);
    +        }
    +      });
    +    } catch (final UndeclaredThrowableException e) {
    +      throw new SaslException(String.format("Unexpected failure trying to 
authenticate using %s",
    +          saslServer.getMechanismName()), e.getCause());
    +    } catch (final IOException | InterruptedException e) {
    +      if (e instanceof SaslException) {
    +        throw (SaslException) e;
    +      } else {
    +        throw new SaslException(String.format("Unexpected failure trying 
to authenticate using %s",
    +            saslServer.getMechanismName()), e);
    +      }
    +    }
    +  }
    +
    +  @SuppressWarnings("unchecked")
    +  private static void handleSuccess(final SaslResponseContext context, 
final SaslMessage.Builder challenge,
    +                                    final SaslServer saslServer) throws 
IOException {
    +    context.connection.changeHandlerTo(context.requestHandler);
    +    context.connection.finalizeSaslSession();
    +    context.sender.send(new Response(context.saslResponseType, 
challenge.build()));
    +
    +    // setup security layers here..
    --- End diff --
    
    we should probably dispose of the saslServer (`saslServer#dispose()`)


> Kerberos Authentication
> -----------------------
>
>                 Key: DRILL-4280
>                 URL: https://issues.apache.org/jira/browse/DRILL-4280
>             Project: Apache Drill
>          Issue Type: Improvement
>            Reporter: Keys Botzum
>            Assignee: Sudheesh Katkam
>              Labels: security
>
> Drill should support Kerberos based authentication from clients. This means 
> that both the ODBC and JDBC drivers as well as the web/REST interfaces should 
> support inbound Kerberos. For Web this would most likely be SPNEGO while for 
> ODBC and JDBC this will be more generic Kerberos.
> Since Hive and much of Hadoop supports Kerberos there is a potential for a 
> lot of reuse of ideas if not implementation.
> Note that this is related to but not the same as 
> https://issues.apache.org/jira/browse/DRILL-3584 



--
This message was sent by Atlassian JIRA
(v6.3.15#6346)

Reply via email to