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}