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  
22  import java.io.UnsupportedEncodingException;
23  import java.nio.ByteBuffer;
24  import java.nio.charset.CharacterCodingException;
25  import java.nio.charset.Charset;
26  import java.nio.charset.CharsetDecoder;
27  import java.security.PrivilegedExceptionAction;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.SortedMap;
33  import java.util.TreeMap;
34  
35  import javax.security.auth.Subject;
36  import javax.security.auth.login.AppConfigurationEntry;
37  import javax.security.auth.login.Configuration;
38  import javax.security.auth.login.LoginContext;
39  
40  import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
41  import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
42  import org.apache.hadoop.hbase.thrift.generated.Hbase;
43  import org.apache.hadoop.hbase.thrift.generated.TCell;
44  import org.apache.hadoop.hbase.thrift.generated.TRowResult;
45  import org.apache.hadoop.hbase.util.Base64;
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    static protected String doAsUser = null;
68    static protected String principal = null;
69  
70    public static void main(String[] args) throws Exception {
71  
72      if (args.length < 3 || args.length > 4) {
73  
74        System.out.println("Invalid arguments!");
75        System.out.println("Usage: HttpDoAsClient host port doAsUserName [security=true]");
76        System.exit(-1);
77      }
78  
79      host = args[0];
80      port = Integer.parseInt(args[1]);
81      doAsUser = args[2];
82      if (args.length > 3) {
83        secure = Boolean.parseBoolean(args[3]);
84        principal = getSubject().getPrincipals().iterator().next().getName();
85      }
86  
87      final HttpDoAsClient client = new HttpDoAsClient();
88      Subject.doAs(getSubject(),
89          new PrivilegedExceptionAction<Void>() {
90            @Override
91            public Void run() throws Exception {
92              client.run();
93              return null;
94            }
95          });
96    }
97  
98    HttpDoAsClient() {
99      decoder = Charset.forName("UTF-8").newDecoder();
100   }
101 
102   // Helper to translate byte[]'s to UTF8 strings
103   private String utf8(byte[] buf) {
104     try {
105       return decoder.decode(ByteBuffer.wrap(buf)).toString();
106     } catch (CharacterCodingException e) {
107       return "[INVALID UTF-8]";
108     }
109   }
110 
111   // Helper to translate strings to UTF8 bytes
112   private byte[] bytes(String s) {
113     try {
114       return s.getBytes("UTF-8");
115     } catch (UnsupportedEncodingException e) {
116       e.printStackTrace();
117       return null;
118     }
119   }
120 
121   private void run() throws Exception {
122     TTransport transport = new TSocket(host, port);
123 
124     transport.open();
125     String url = "http://" + host + ":" + port;
126     THttpClient httpClient = new THttpClient(url);
127     httpClient.open();
128     TProtocol protocol = new TBinaryProtocol(httpClient);
129     Hbase.Client client = new Hbase.Client(protocol);
130 
131     byte[] t = bytes("demo_table");
132 
133     //
134     // Scan all tables, look for the demo table and delete it.
135     //
136     System.out.println("scanning tables...");
137     for (ByteBuffer name : refresh(client, httpClient).getTableNames()) {
138       System.out.println("  found: " + utf8(name.array()));
139       if (utf8(name.array()).equals(utf8(t))) {
140         if (refresh(client, httpClient).isTableEnabled(name)) {
141           System.out.println("    disabling table: " + utf8(name.array()));
142           refresh(client, httpClient).disableTable(name);
143         }
144         System.out.println("    deleting table: " + utf8(name.array()));
145         refresh(client, httpClient).deleteTable(name);
146       }
147     }
148 
149 
150 
151     //
152     // Create the demo table with two column families, entry: and unused:
153     //
154     ArrayList<ColumnDescriptor> columns = new ArrayList<ColumnDescriptor>();
155     ColumnDescriptor col;
156     col = new ColumnDescriptor();
157     col.name = ByteBuffer.wrap(bytes("entry:"));
158     col.timeToLive = Integer.MAX_VALUE;
159     col.maxVersions = 10;
160     columns.add(col);
161     col = new ColumnDescriptor();
162     col.name = ByteBuffer.wrap(bytes("unused:"));
163     col.timeToLive = Integer.MAX_VALUE;
164     columns.add(col);
165 
166     System.out.println("creating table: " + utf8(t));
167     try {
168 
169       refresh(client, httpClient).createTable(ByteBuffer.wrap(t), columns);
170     } catch (AlreadyExists ae) {
171       System.out.println("WARN: " + ae.message);
172     }
173 
174     System.out.println("column families in " + utf8(t) + ": ");
175     Map<ByteBuffer, ColumnDescriptor> columnMap = refresh(client, httpClient)
176         .getColumnDescriptors(ByteBuffer.wrap(t));
177     for (ColumnDescriptor col2 : columnMap.values()) {
178       System.out.println("  column: " + utf8(col2.name.array()) + ", maxVer: " + Integer.toString(col2.maxVersions));
179     }
180 
181     transport.close();
182     httpClient.close();
183   }
184 
185   private Hbase.Client refresh(Hbase.Client client, THttpClient httpClient) {
186     httpClient.setCustomHeader("doAs", doAsUser);
187     if(secure) {
188       try {
189         httpClient.setCustomHeader("Authorization", generateTicket());
190       } catch (GSSException e) {
191         e.printStackTrace();
192       }
193     }
194     return client;
195   }
196 
197   private String generateTicket() throws GSSException {
198     final GSSManager manager = GSSManager.getInstance();
199     // Oid for kerberos principal name
200     Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1");
201     Oid KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
202     final GSSName clientName = manager.createName(principal,
203         krb5PrincipalOid);
204     final GSSCredential clientCred = manager.createCredential(clientName,
205         8 * 3600,
206         KERB_V5_OID,
207         GSSCredential.INITIATE_ONLY);
208 
209     final GSSName serverName = manager.createName(principal, krb5PrincipalOid);
210 
211     final GSSContext context = manager.createContext(serverName,
212         KERB_V5_OID,
213         clientCred,
214         GSSContext.DEFAULT_LIFETIME);
215     context.requestMutualAuth(true);
216     context.requestConf(false);
217     context.requestInteg(true);
218 
219     final byte[] outToken = context.initSecContext(new byte[0], 0, 0);
220     StringBuffer outputBuffer = new StringBuffer();
221     outputBuffer.append("Negotiate ");
222     outputBuffer.append(Base64.encodeBytes(outToken).replace("\n", ""));
223     System.out.print("Ticket is: " + outputBuffer);
224     return outputBuffer.toString();
225   }
226 
227   private void printVersions(ByteBuffer row, List<TCell> versions) {
228     StringBuilder rowStr = new StringBuilder();
229     for (TCell cell : versions) {
230       rowStr.append(utf8(cell.value.array()));
231       rowStr.append("; ");
232     }
233     System.out.println("row: " + utf8(row.array()) + ", values: " + rowStr);
234   }
235 
236   private void printRow(TRowResult rowResult) {
237     // copy values into a TreeMap to get them in sorted order
238 
239     TreeMap<String, TCell> sorted = new TreeMap<String, TCell>();
240     for (Map.Entry<ByteBuffer, TCell> column : rowResult.columns.entrySet()) {
241       sorted.put(utf8(column.getKey().array()), column.getValue());
242     }
243 
244     StringBuilder rowStr = new StringBuilder();
245     for (SortedMap.Entry<String, TCell> entry : sorted.entrySet()) {
246       rowStr.append(entry.getKey());
247       rowStr.append(" => ");
248       rowStr.append(utf8(entry.getValue().value.array()));
249       rowStr.append("; ");
250     }
251     System.out.println("row: " + utf8(rowResult.row.array()) + ", cols: " + rowStr);
252   }
253 
254   static Subject getSubject() throws Exception {
255     if (!secure) return new Subject();
256     /*
257      * To authenticate the DemoClient, kinit should be invoked ahead.
258      * Here we try to get the Kerberos credential from the ticket cache.
259      */
260     LoginContext context = new LoginContext("", new Subject(), null,
261         new Configuration() {
262           @Override
263           public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
264             Map<String, String> options = new HashMap<String, String>();
265             options.put("useKeyTab", "false");
266             options.put("storeKey", "false");
267             options.put("doNotPrompt", "true");
268             options.put("useTicketCache", "true");
269             options.put("renewTGT", "true");
270             options.put("refreshKrb5Config", "true");
271             options.put("isInitiator", "true");
272             String ticketCache = System.getenv("KRB5CCNAME");
273             if (ticketCache != null) {
274               options.put("ticketCache", ticketCache);
275             }
276             options.put("debug", "true");
277 
278             return new AppConfigurationEntry[]{
279                 new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
280                     AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
281                     options)};
282           }
283         });
284     context.login();
285     return context.getSubject();
286   }
287 }