View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.thrift;
20  
21  import sun.misc.BASE64Encoder;
22  
23  import java.io.UnsupportedEncodingException;
24  import java.nio.ByteBuffer;
25  import java.nio.charset.CharacterCodingException;
26  import java.nio.charset.Charset;
27  import java.nio.charset.CharsetDecoder;
28  import java.security.PrivilegedExceptionAction;
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.SortedMap;
34  import java.util.TreeMap;
35  
36  import javax.security.auth.Subject;
37  import javax.security.auth.login.AppConfigurationEntry;
38  import javax.security.auth.login.Configuration;
39  import javax.security.auth.login.LoginContext;
40  
41  import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
42  import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
43  import org.apache.hadoop.hbase.thrift.generated.Hbase;
44  import org.apache.hadoop.hbase.thrift.generated.TCell;
45  import org.apache.hadoop.hbase.thrift.generated.TRowResult;
46  import org.apache.thrift.protocol.TBinaryProtocol;
47  import org.apache.thrift.protocol.TProtocol;
48  import org.apache.thrift.transport.THttpClient;
49  import org.apache.thrift.transport.TSocket;
50  import org.apache.thrift.transport.TTransport;
51  import org.ietf.jgss.GSSContext;
52  import org.ietf.jgss.GSSCredential;
53  import org.ietf.jgss.GSSException;
54  import org.ietf.jgss.GSSManager;
55  import org.ietf.jgss.GSSName;
56  import org.ietf.jgss.Oid;
57  
58  /**
59   * See the instructions under hbase-examples/README.txt
60   */
61  public class HttpDoAsClient {
62  
63    static protected int port;
64    static protected String host;
65    CharsetDecoder decoder = null;
66    private static boolean secure = false;
67  
68    public static void main(String[] args) throws Exception {
69  
70      if (args.length < 2 || args.length > 3) {
71  
72        System.out.println("Invalid arguments!");
73        System.out.println("Usage: DemoClient host port [secure=false]");
74  
75        System.exit(-1);
76      }
77  
78      port = Integer.parseInt(args[1]);
79      host = args[0];
80      if (args.length > 2) {
81        secure = Boolean.parseBoolean(args[2]);
82      }
83  
84      final HttpDoAsClient client = new HttpDoAsClient();
85      Subject.doAs(getSubject(),
86          new PrivilegedExceptionAction<Void>() {
87            @Override
88            public Void run() throws Exception {
89              client.run();
90              return null;
91            }
92          });
93    }
94  
95    HttpDoAsClient() {
96      decoder = Charset.forName("UTF-8").newDecoder();
97    }
98  
99    // Helper to translate byte[]'s to UTF8 strings
100   private String utf8(byte[] buf) {
101     try {
102       return decoder.decode(ByteBuffer.wrap(buf)).toString();
103     } catch (CharacterCodingException e) {
104       return "[INVALID UTF-8]";
105     }
106   }
107 
108   // Helper to translate strings to UTF8 bytes
109   private byte[] bytes(String s) {
110     try {
111       return s.getBytes("UTF-8");
112     } catch (UnsupportedEncodingException e) {
113       e.printStackTrace();
114       return null;
115     }
116   }
117 
118   private void run() throws Exception {
119     TTransport transport = new TSocket(host, port);
120 
121     transport.open();
122     String url = "http://" + host + ":" + port;
123     THttpClient httpClient = new THttpClient(url);
124     httpClient.open();
125     TProtocol protocol = new TBinaryProtocol(httpClient);
126     Hbase.Client client = new Hbase.Client(protocol);
127 
128     byte[] t = bytes("demo_table");
129 
130     //
131     // Scan all tables, look for the demo table and delete it.
132     //
133     System.out.println("scanning tables...");
134     for (ByteBuffer name : refresh(client, httpClient).getTableNames()) {
135       System.out.println("  found: " + utf8(name.array()));
136       if (utf8(name.array()).equals(utf8(t))) {
137         if (client.isTableEnabled(name)) {
138           System.out.println("    disabling table: " + utf8(name.array()));
139           refresh(client, httpClient).disableTable(name);
140         }
141         System.out.println("    deleting table: " + utf8(name.array()));
142         refresh(client, httpClient).deleteTable(name);
143       }
144     }
145 
146 
147 
148     //
149     // Create the demo table with two column families, entry: and unused:
150     //
151     ArrayList<ColumnDescriptor> columns = new ArrayList<ColumnDescriptor>();
152     ColumnDescriptor col;
153     col = new ColumnDescriptor();
154     col.name = ByteBuffer.wrap(bytes("entry:"));
155     col.timeToLive = Integer.MAX_VALUE;
156     col.maxVersions = 10;
157     columns.add(col);
158     col = new ColumnDescriptor();
159     col.name = ByteBuffer.wrap(bytes("unused:"));
160     col.timeToLive = Integer.MAX_VALUE;
161     columns.add(col);
162 
163     System.out.println("creating table: " + utf8(t));
164     try {
165 
166       refresh(client, httpClient).createTable(ByteBuffer.wrap(t), columns);
167     } catch (AlreadyExists ae) {
168       System.out.println("WARN: " + ae.message);
169     }
170 
171     System.out.println("column families in " + utf8(t) + ": ");
172     Map<ByteBuffer, ColumnDescriptor> columnMap = refresh(client, httpClient)
173         .getColumnDescriptors(ByteBuffer.wrap(t));
174     for (ColumnDescriptor col2 : columnMap.values()) {
175       System.out.println("  column: " + utf8(col2.name.array()) + ", maxVer: " + Integer.toString(col2.maxVersions));
176     }
177 
178     transport.close();
179     httpClient.close();
180   }
181 
182   private Hbase.Client refresh(Hbase.Client client, THttpClient httpClient) {
183     if(secure) {
184       httpClient.setCustomHeader("doAs", "hbase");
185       try {
186         httpClient.setCustomHeader("Authorization", generateTicket());
187       } catch (GSSException e) {
188         e.printStackTrace();
189       }
190     }
191     return client;
192   }
193 
194   private String generateTicket() throws GSSException {
195     final GSSManager manager = GSSManager.getInstance();
196     // Oid for kerberos principal name
197     Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1");
198     Oid KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
199     final GSSName clientName = manager.createName("hbase/node-1.internal@INTERNAL",
200         krb5PrincipalOid);
201     final GSSCredential clientCred = manager.createCredential(clientName,
202         8 * 3600,
203         KERB_V5_OID,
204         GSSCredential.INITIATE_ONLY);
205 
206     final GSSName serverName = manager.createName("hbase/node-1.internal@INTERNAL", krb5PrincipalOid);
207 
208     final GSSContext context = manager.createContext(serverName,
209         KERB_V5_OID,
210         clientCred,
211         GSSContext.DEFAULT_LIFETIME);
212     context.requestMutualAuth(true);
213     context.requestConf(false);
214     context.requestInteg(true);
215 
216     final byte[] outToken = context.initSecContext(new byte[0], 0, 0);
217     StringBuffer outputBuffer = new StringBuffer();
218     outputBuffer.append("Negotiate ");
219     outputBuffer.append(new BASE64Encoder().encode(outToken).replace("\n", ""));
220     System.out.print("Ticket is: " + outputBuffer);
221     return outputBuffer.toString();
222   }
223 
224   private void printVersions(ByteBuffer row, List<TCell> versions) {
225     StringBuilder rowStr = new StringBuilder();
226     for (TCell cell : versions) {
227       rowStr.append(utf8(cell.value.array()));
228       rowStr.append("; ");
229     }
230     System.out.println("row: " + utf8(row.array()) + ", values: " + rowStr);
231   }
232 
233   private void printRow(TRowResult rowResult) {
234     // copy values into a TreeMap to get them in sorted order
235 
236     TreeMap<String, TCell> sorted = new TreeMap<String, TCell>();
237     for (Map.Entry<ByteBuffer, TCell> column : rowResult.columns.entrySet()) {
238       sorted.put(utf8(column.getKey().array()), column.getValue());
239     }
240 
241     StringBuilder rowStr = new StringBuilder();
242     for (SortedMap.Entry<String, TCell> entry : sorted.entrySet()) {
243       rowStr.append(entry.getKey());
244       rowStr.append(" => ");
245       rowStr.append(utf8(entry.getValue().value.array()));
246       rowStr.append("; ");
247     }
248     System.out.println("row: " + utf8(rowResult.row.array()) + ", cols: " + rowStr);
249   }
250 
251   static Subject getSubject() throws Exception {
252     if (!secure) return new Subject();
253     /*
254      * To authenticate the DemoClient, kinit should be invoked ahead.
255      * Here we try to get the Kerberos credential from the ticket cache.
256      */
257     LoginContext context = new LoginContext("", new Subject(), null,
258         new Configuration() {
259           @Override
260           public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
261             Map<String, String> options = new HashMap<String, String>();
262             options.put("useKeyTab", "false");
263             options.put("storeKey", "false");
264             options.put("doNotPrompt", "true");
265             options.put("useTicketCache", "true");
266             options.put("renewTGT", "true");
267             options.put("refreshKrb5Config", "true");
268             options.put("isInitiator", "true");
269             String ticketCache = System.getenv("KRB5CCNAME");
270             if (ticketCache != null) {
271               options.put("ticketCache", ticketCache);
272             }
273             options.put("debug", "true");
274 
275             return new AppConfigurationEntry[]{
276                 new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
277                     AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
278                     options)};
279           }
280         });
281     context.login();
282     return context.getSubject();
283   }
284 }