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}