View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.security;
20  
21  import java.io.BufferedInputStream;
22  import java.io.BufferedOutputStream;
23  import java.io.DataInputStream;
24  import java.io.DataOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28
29  import javax.security.sasl.Sasl;
30  import javax.security.sasl.SaslException;
31
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.hbase.classification.InterfaceAudience;
35  import org.apache.hadoop.io.WritableUtils;
36  import org.apache.hadoop.ipc.RemoteException;
37  import org.apache.hadoop.security.SaslInputStream;
38  import org.apache.hadoop.security.SaslOutputStream;
39  import org.apache.hadoop.security.token.Token;
40  import org.apache.hadoop.security.token.TokenIdentifier;
41
42  /**
43   * A utility class that encapsulates SASL logic for RPC client. Copied from
44   * <code>org.apache.hadoop.security</code>
45   */
46  @InterfaceAudience.Private
47  public class HBaseSaslRpcClient extends AbstractHBaseSaslRpcClient {
48
49    private static final Log LOG = LogFactory.getLog(HBaseSaslRpcClient.class);
50  
51    public HBaseSaslRpcClient(AuthMethod method, Token<? extends TokenIdentifier> token,
52        String serverPrincipal, boolean fallbackAllowed) throws IOException {
53      super(method, token, serverPrincipal, fallbackAllowed);
54    }
55
56    public HBaseSaslRpcClient(AuthMethod method, Token<? extends TokenIdentifier> token,
57        String serverPrincipal, boolean fallbackAllowed, String rpcProtection) throws IOException {
58      super(method, token, serverPrincipal, fallbackAllowed, rpcProtection);
59    }
60  
61    private static void readStatus(DataInputStream inStream) throws IOException {
62      int status = inStream.readInt(); // read status
63      if (status != SaslStatus.SUCCESS.state) {
64        throw new RemoteException(WritableUtils.readString(inStream),
65            WritableUtils.readString(inStream));
66      }
67    }
68
69    /**
70     * Do client side SASL authentication with server via the given InputStream and OutputStream
71     * @param inS InputStream to use
72     * @param outS OutputStream to use
73     * @return true if connection is set up, or false if needs to switch to simple Auth.
74     * @throws IOException
75     */
76    public boolean saslConnect(InputStream inS, OutputStream outS) throws IOException {
77      DataInputStream inStream = new DataInputStream(new BufferedInputStream(inS));
78      DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream(outS));
79
80      try {
81        byte[] saslToken = getInitialResponse();
82        if (saslToken != null) {
83          outStream.writeInt(saslToken.length);
84          outStream.write(saslToken, 0, saslToken.length);
85          outStream.flush();
86          if (LOG.isDebugEnabled()) {
87            LOG.debug("Have sent token of size " + saslToken.length + " from initSASLContext.");
88          }
89        }
90        if (!isComplete()) {
91          readStatus(inStream);
92          int len = inStream.readInt();
93          if (len == SaslUtil.SWITCH_TO_SIMPLE_AUTH) {
94            if (!fallbackAllowed) {
95              throw new IOException("Server asks us to fall back to SIMPLE auth, "
96                  + "but this client is configured to only allow secure connections.");
97            }
98            if (LOG.isDebugEnabled()) {
99              LOG.debug("Server asks us to fall back to simple auth.");
100           }
101           dispose();
102           return false;
103         }
104         saslToken = new byte[len];
105         if (LOG.isDebugEnabled()) {
106           LOG.debug("Will read input token of size " + saslToken.length
107               + " for processing by initSASLContext");
108         }
109         inStream.readFully(saslToken);
110       }
111
112       while (!isComplete()) {
113         saslToken = evaluateChallenge(saslToken);
114         if (saslToken != null) {
115           if (LOG.isDebugEnabled()) {
116             LOG.debug("Will send token of size " + saslToken.length + " from initSASLContext.");
117           }
118           outStream.writeInt(saslToken.length);
119           outStream.write(saslToken, 0, saslToken.length);
120           outStream.flush();
121         }
122         if (!isComplete()) {
123           readStatus(inStream);
124           saslToken = new byte[inStream.readInt()];
125           if (LOG.isDebugEnabled()) {
126             LOG.debug("Will read input token of size " + saslToken.length
127                 + " for processing by initSASLContext");
128           }
129           inStream.readFully(saslToken);
130         }
131       }
132       if (LOG.isDebugEnabled()) {
133         LOG.debug("SASL client context established. Negotiated QoP: "
134             + saslClient.getNegotiatedProperty(Sasl.QOP));
135       }
136       return true;
137     } catch (IOException e) {
138       try {
139         saslClient.dispose();
140       } catch (SaslException ignored) {
141         // ignore further exceptions during cleanup
142       }
143       throw e;
144     }
145   }
146
147   /**
148    * Get a SASL wrapped InputStream. Can be called only after saslConnect() has been called.
149    * @param in the InputStream to wrap
150    * @return a SASL wrapped InputStream
151    * @throws IOException
152    */
153   public InputStream getInputStream(InputStream in) throws IOException {
154     if (!saslClient.isComplete()) {
155       throw new IOException("Sasl authentication exchange hasn't completed yet");
156     }
157     return new SaslInputStream(in, saslClient);
158   }
159 
160   /**
161    * Get a SASL wrapped OutputStream. Can be called only after saslConnect() has been called.
162    * @param out the OutputStream to wrap
163    * @return a SASL wrapped OutputStream
164    * @throws IOException
165    */
166   public OutputStream getOutputStream(OutputStream out) throws IOException {
167     if (!saslClient.isComplete()) {
168       throw new IOException("Sasl authentication exchange hasn't completed yet");
169     }
170     return new SaslOutputStream(out, saslClient);
171   }
172 }