001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.thrift;
020
021import java.io.UnsupportedEncodingException;
022import java.nio.ByteBuffer;
023import java.nio.charset.CharacterCodingException;
024import java.nio.charset.Charset;
025import java.nio.charset.CharsetDecoder;
026import java.security.PrivilegedExceptionAction;
027import java.util.ArrayList;
028import java.util.Base64;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.SortedMap;
033import java.util.TreeMap;
034import javax.security.auth.Subject;
035import javax.security.auth.login.AppConfigurationEntry;
036import javax.security.auth.login.Configuration;
037import javax.security.auth.login.LoginContext;
038import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
039import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
040import org.apache.hadoop.hbase.thrift.generated.Hbase;
041import org.apache.hadoop.hbase.thrift.generated.TCell;
042import org.apache.hadoop.hbase.thrift.generated.TRowResult;
043import org.apache.hadoop.hbase.util.Bytes;
044import org.apache.thrift.protocol.TBinaryProtocol;
045import org.apache.thrift.protocol.TProtocol;
046import org.apache.thrift.transport.THttpClient;
047import org.apache.thrift.transport.TSocket;
048import org.apache.thrift.transport.TTransport;
049import org.apache.yetus.audience.InterfaceAudience;
050import org.ietf.jgss.GSSContext;
051import org.ietf.jgss.GSSCredential;
052import org.ietf.jgss.GSSException;
053import org.ietf.jgss.GSSManager;
054import org.ietf.jgss.GSSName;
055import org.ietf.jgss.Oid;
056
057/**
058 * See the instructions under hbase-examples/README.txt
059 */
060@InterfaceAudience.Private
061public class HttpDoAsClient {
062
063  static protected int port;
064  static protected String host;
065  CharsetDecoder decoder = null;
066  private static boolean secure = false;
067  static protected String doAsUser = null;
068  static protected String principal = null;
069
070  public static void main(String[] args) throws Exception {
071
072    if (args.length < 3 || args.length > 4) {
073
074      System.out.println("Invalid arguments!");
075      System.out.println("Usage: HttpDoAsClient host port doAsUserName [security=true]");
076      System.exit(-1);
077    }
078
079    host = args[0];
080    port = Integer.parseInt(args[1]);
081    doAsUser = args[2];
082    if (args.length > 3) {
083      secure = Boolean.parseBoolean(args[3]);
084      principal = getSubject().getPrincipals().iterator().next().getName();
085    }
086
087    final HttpDoAsClient client = new HttpDoAsClient();
088    Subject.doAs(getSubject(),
089        new PrivilegedExceptionAction<Void>() {
090          @Override
091          public Void run() throws Exception {
092            client.run();
093            return null;
094          }
095        });
096  }
097
098  HttpDoAsClient() {
099    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<>(2);
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(Bytes.toString(Base64.getEncoder().encode(outToken)));
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<>();
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<>();
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}