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