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.security.Principal;
024import java.security.PrivilegedExceptionAction;
025import java.util.ArrayList;
026import java.util.Base64;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031import javax.security.auth.Subject;
032import javax.security.auth.login.AppConfigurationEntry;
033import javax.security.auth.login.Configuration;
034import javax.security.auth.login.LoginContext;
035import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
036import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
037import org.apache.hadoop.hbase.thrift.generated.Hbase;
038import org.apache.hadoop.hbase.thrift.generated.TCell;
039import org.apache.hadoop.hbase.thrift.generated.TRowResult;
040import org.apache.hadoop.hbase.util.Bytes;
041import org.apache.hadoop.hbase.util.ClientUtils;
042import org.apache.thrift.protocol.TBinaryProtocol;
043import org.apache.thrift.protocol.TProtocol;
044import org.apache.thrift.transport.THttpClient;
045import org.apache.thrift.transport.TSocket;
046import org.apache.thrift.transport.TTransport;
047import org.apache.yetus.audience.InterfaceAudience;
048import org.ietf.jgss.GSSContext;
049import org.ietf.jgss.GSSCredential;
050import org.ietf.jgss.GSSException;
051import org.ietf.jgss.GSSManager;
052import org.ietf.jgss.GSSName;
053import org.ietf.jgss.Oid;
054
055/**
056 * See the instructions under hbase-examples/README.txt
057 */
058@InterfaceAudience.Private
059public class HttpDoAsClient {
060
061  static protected int port;
062  static protected String host;
063  private static boolean secure = false;
064  static protected String doAsUser = null;
065  static protected String principal = null;
066
067  public static void main(String[] args) throws Exception {
068
069    if (args.length < 3 || args.length > 4) {
070
071      System.out.println("Invalid arguments!");
072      System.out.println("Usage: HttpDoAsClient host port doAsUserName [security=true]");
073      System.exit(-1);
074    }
075
076    host = args[0];
077    port = Integer.parseInt(args[1]);
078    doAsUser = args[2];
079    if (args.length > 3) {
080      secure = Boolean.parseBoolean(args[3]);
081      principal = getSubject().getPrincipals().iterator().next().getName();
082    }
083
084    final HttpDoAsClient client = new HttpDoAsClient();
085    Subject.doAs(getSubject(),
086        new PrivilegedExceptionAction<Void>() {
087          @Override
088          public Void run() throws Exception {
089            client.run();
090            return null;
091          }
092        });
093  }
094
095  HttpDoAsClient() {
096  }
097
098  // Helper to translate strings to UTF8 bytes
099  private byte[] bytes(String s) {
100    try {
101      return s.getBytes("UTF-8");
102    } catch (UnsupportedEncodingException e) {
103      e.printStackTrace();
104      return null;
105    }
106  }
107
108  private void run() throws Exception {
109    TTransport transport = new TSocket(host, port);
110
111    transport.open();
112    String url = "http://" + host + ":" + port;
113    THttpClient httpClient = new THttpClient(url);
114    httpClient.open();
115    TProtocol protocol = new TBinaryProtocol(httpClient);
116    Hbase.Client client = new Hbase.Client(protocol);
117
118    byte[] t = bytes("demo_table");
119
120    //
121    // Scan all tables, look for the demo table and delete it.
122    //
123    System.out.println("scanning tables...");
124    for (ByteBuffer name : refresh(client, httpClient).getTableNames()) {
125      System.out.println("  found: " + ClientUtils.utf8(name.array()));
126      if (ClientUtils.utf8(name.array()).equals(ClientUtils.utf8(t))) {
127        if (refresh(client, httpClient).isTableEnabled(name)) {
128          System.out.println("    disabling table: " + ClientUtils.utf8(name.array()));
129          refresh(client, httpClient).disableTable(name);
130        }
131        System.out.println("    deleting table: " + ClientUtils.utf8(name.array()));
132        refresh(client, httpClient).deleteTable(name);
133      }
134    }
135
136
137
138    //
139    // Create the demo table with two column families, entry: and unused:
140    //
141    ArrayList<ColumnDescriptor> columns = new ArrayList<>(2);
142    ColumnDescriptor col;
143    col = new ColumnDescriptor();
144    col.name = ByteBuffer.wrap(bytes("entry:"));
145    col.timeToLive = Integer.MAX_VALUE;
146    col.maxVersions = 10;
147    columns.add(col);
148    col = new ColumnDescriptor();
149    col.name = ByteBuffer.wrap(bytes("unused:"));
150    col.timeToLive = Integer.MAX_VALUE;
151    columns.add(col);
152
153    System.out.println("creating table: " + ClientUtils.utf8(t));
154    try {
155
156      refresh(client, httpClient).createTable(ByteBuffer.wrap(t), columns);
157    } catch (AlreadyExists ae) {
158      System.out.println("WARN: " + ae.message);
159    }
160
161    System.out.println("column families in " + ClientUtils.utf8(t) + ": ");
162    Map<ByteBuffer, ColumnDescriptor> columnMap = refresh(client, httpClient)
163        .getColumnDescriptors(ByteBuffer.wrap(t));
164    for (ColumnDescriptor col2 : columnMap.values()) {
165      System.out.println("  column: " + ClientUtils.utf8(col2.name.array()) + ", maxVer: "
166          + col2.maxVersions);
167    }
168
169    transport.close();
170    httpClient.close();
171  }
172
173  private Hbase.Client refresh(Hbase.Client client, THttpClient httpClient) {
174    httpClient.setCustomHeader("doAs", doAsUser);
175    if(secure) {
176      try {
177        httpClient.setCustomHeader("Authorization", generateTicket());
178      } catch (GSSException e) {
179        e.printStackTrace();
180      }
181    }
182    return client;
183  }
184
185  private String generateTicket() throws GSSException {
186    final GSSManager manager = GSSManager.getInstance();
187    // Oid for kerberos principal name
188    Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1");
189    Oid KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
190    final GSSName clientName = manager.createName(principal,
191        krb5PrincipalOid);
192    final GSSCredential clientCred = manager.createCredential(clientName,
193        8 * 3600,
194        KERB_V5_OID,
195        GSSCredential.INITIATE_ONLY);
196
197    final GSSName serverName = manager.createName(principal, krb5PrincipalOid);
198
199    final GSSContext context = manager.createContext(serverName,
200        KERB_V5_OID,
201        clientCred,
202        GSSContext.DEFAULT_LIFETIME);
203    context.requestMutualAuth(true);
204    context.requestConf(false);
205    context.requestInteg(true);
206
207    final byte[] outToken = context.initSecContext(new byte[0], 0, 0);
208    StringBuffer outputBuffer = new StringBuffer();
209    outputBuffer.append("Negotiate ");
210    outputBuffer.append(Bytes.toString(Base64.getEncoder().encode(outToken)));
211    System.out.print("Ticket is: " + outputBuffer);
212    return outputBuffer.toString();
213  }
214
215  private void printVersions(ByteBuffer row, List<TCell> versions) {
216    StringBuilder rowStr = new StringBuilder();
217    for (TCell cell : versions) {
218      rowStr.append(ClientUtils.utf8(cell.value.array()));
219      rowStr.append("; ");
220    }
221    System.out.println("row: " + ClientUtils.utf8(row.array()) + ", values: " + rowStr);
222  }
223
224  private void printRow(TRowResult rowResult) {
225    ClientUtils.printRow(rowResult);
226  }
227
228  static Subject getSubject() throws Exception {
229    if (!secure) return new Subject();
230    /*
231     * To authenticate the DemoClient, kinit should be invoked ahead.
232     * Here we try to get the Kerberos credential from the ticket cache.
233     */
234    LoginContext context = new LoginContext("", new Subject(), null,
235        new Configuration() {
236          @Override
237          public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
238            Map<String, String> options = new HashMap<>();
239            options.put("useKeyTab", "false");
240            options.put("storeKey", "false");
241            options.put("doNotPrompt", "true");
242            options.put("useTicketCache", "true");
243            options.put("renewTGT", "true");
244            options.put("refreshKrb5Config", "true");
245            options.put("isInitiator", "true");
246            String ticketCache = System.getenv("KRB5CCNAME");
247            if (ticketCache != null) {
248              options.put("ticketCache", ticketCache);
249            }
250            options.put("debug", "true");
251
252            return new AppConfigurationEntry[]{
253                new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
254                    AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
255                    options)};
256          }
257        });
258    context.login();
259    return context.getSubject();
260  }
261}