001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * <p/> 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * <p/> 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.test; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022 023import java.io.IOException; 024import java.security.PrivilegedExceptionAction; 025import java.util.List; 026 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.fs.Path; 029import org.apache.hadoop.hbase.HBaseConfiguration; 030import org.apache.hadoop.hbase.HColumnDescriptor; 031import org.apache.hadoop.hbase.HConstants; 032import org.apache.hadoop.hbase.HTableDescriptor; 033import org.apache.hadoop.hbase.IntegrationTestingUtility; 034import org.apache.hadoop.hbase.client.Admin; 035import org.apache.hadoop.hbase.client.Connection; 036import org.apache.hadoop.hbase.client.ConnectionFactory; 037import org.apache.hadoop.hbase.client.Put; 038import org.apache.hadoop.hbase.client.Result; 039import org.apache.hadoop.hbase.client.Scan; 040import org.apache.hadoop.hbase.client.ScannerCallable; 041import org.apache.hadoop.hbase.io.ImmutableBytesWritable; 042import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; 043import org.apache.hadoop.hbase.mapreduce.TableMapper; 044import org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl; 045import org.apache.hadoop.hbase.security.User; 046import org.apache.hadoop.hbase.security.visibility.Authorizations; 047import org.apache.hadoop.hbase.security.visibility.CellVisibility; 048import org.apache.hadoop.hbase.security.visibility.VisibilityClient; 049import org.apache.hadoop.hbase.security.visibility.VisibilityTestUtil; 050import org.apache.hadoop.hbase.testclassification.IntegrationTests; 051import org.apache.hadoop.hbase.util.AbstractHBaseTool; 052import org.apache.hadoop.hbase.util.Bytes; 053import org.apache.hadoop.io.BytesWritable; 054import org.apache.hadoop.io.NullWritable; 055import org.apache.hadoop.mapreduce.Counter; 056import org.apache.hadoop.mapreduce.Job; 057import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; 058import org.apache.hadoop.util.ToolRunner; 059import org.junit.experimental.categories.Category; 060 061import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 062 063/** 064 * A large test which loads a lot of data with cell visibility, and verifies the data. Test adds 2 065 * users with different sets of visibility labels authenticated for them. Every row (so cells in 066 * that) added with visibility expressions. In load step, 200 map tasks are launched, which in turn 067 * write loadmapper.num_to_write (default 100K) rows to an hbase table. Rows are written in blocks, 068 * for a total of 100 blocks. 069 * 070 * Verify step scans the table as both users with Authorizations. This step asserts that user can 071 * see only those rows (and so cells) with visibility for which they have label auth. 072 * 073 * This class can be run as a unit test, as an integration test, or from the command line. 074 * 075 * Originally taken from Apache Bigtop. 076 * Issue user names as comma seperated list. 077 *./hbase IntegrationTestWithCellVisibilityLoadAndVerify -u usera,userb 078 */ 079@Category(IntegrationTests.class) 080public class IntegrationTestWithCellVisibilityLoadAndVerify extends IntegrationTestLoadAndVerify { 081 private static final String ERROR_STR = 082 "Two user names are to be specified seperated by a ',' like 'usera,userb'"; 083 private static final char NOT = '!'; 084 private static final char OR = '|'; 085 private static final char AND = '&'; 086 private static final String TEST_NAME = "IntegrationTestCellVisibilityLoadAndVerify"; 087 private static final String CONFIDENTIAL = "confidential"; 088 private static final String TOPSECRET = "topsecret"; 089 private static final String SECRET = "secret"; 090 private static final String PUBLIC = "public"; 091 private static final String PRIVATE = "private"; 092 private static final String[] LABELS = { CONFIDENTIAL, TOPSECRET, SECRET, PRIVATE, PUBLIC }; 093 private static final String[] VISIBILITY_EXPS = { CONFIDENTIAL + AND + TOPSECRET + AND + PRIVATE, 094 CONFIDENTIAL + OR + TOPSECRET, PUBLIC, 095 '(' + SECRET + OR + PRIVATE + ')' + AND + NOT + CONFIDENTIAL }; 096 private static final int VISIBILITY_EXPS_COUNT = VISIBILITY_EXPS.length; 097 private static final byte[] TEST_FAMILY = Bytes.toBytes("f1"); 098 private static final byte[] TEST_QUALIFIER = Bytes.toBytes("q1"); 099 private static final String NUM_TO_WRITE_KEY = "loadmapper.num_to_write"; 100 private static final long NUM_TO_WRITE_DEFAULT = 100 * 1000; 101 private static final int SCANNER_CACHING = 500; 102 private static String USER_OPT = "users"; 103 private static String userNames = "user1,user2"; 104 105 private long numRowsLoadedWithExp1, numRowsLoadedWithExp2, numRowsLoadWithExp3, 106 numRowsLoadWithExp4; 107 private long numRowsReadWithExp1, numRowsReadWithExp2, numRowsReadWithExp3, numRowsReadWithExp4; 108 109 private static User USER1, USER2; 110 111 private enum Counters { 112 ROWS_VIS_EXP_1, ROWS_VIS_EXP_2, ROWS_VIS_EXP_3, ROWS_VIS_EXP_4; 113 } 114 115 @Override 116 public void setUpCluster() throws Exception { 117 util = getTestingUtil(null); 118 Configuration conf = util.getConfiguration(); 119 VisibilityTestUtil.enableVisiblityLabels(conf); 120 conf.set("hbase.superuser", User.getCurrent().getName()); 121 conf.setBoolean("dfs.permissions", false); 122 super.setUpCluster(); 123 String[] users = userNames.split(","); 124 if (users.length != 2) { 125 System.err.println(ERROR_STR); 126 throw new IOException(ERROR_STR); 127 } 128 System.out.println(userNames + " "+users[0]+ " "+users[1]); 129 USER1 = User.createUserForTesting(conf, users[0], new String[] {}); 130 USER2 = User.createUserForTesting(conf, users[1], new String[] {}); 131 addLabelsAndAuths(); 132 } 133 134 @Override 135 protected void addOptions() { 136 super.addOptions(); 137 addOptWithArg("u", USER_OPT, "User names to be passed"); 138 } 139 140 private void addLabelsAndAuths() throws Exception { 141 try { 142 VisibilityClient.addLabels(util.getConnection(), LABELS); 143 VisibilityClient.setAuths(util.getConnection(), new String[] { CONFIDENTIAL, TOPSECRET, 144 SECRET, PRIVATE }, USER1.getName()); 145 VisibilityClient.setAuths(util.getConnection(), new String[] { PUBLIC }, 146 USER2.getName()); 147 } catch (Throwable t) { 148 throw new IOException(t); 149 } 150 } 151 152 public static class LoadWithCellVisibilityMapper extends LoadMapper { 153 private Counter rowsExp1, rowsExp2, rowsExp3, rowsexp4; 154 155 @Override 156 public void setup(Context context) throws IOException { 157 super.setup(context); 158 rowsExp1 = context.getCounter(Counters.ROWS_VIS_EXP_1); 159 rowsExp2 = context.getCounter(Counters.ROWS_VIS_EXP_2); 160 rowsExp3 = context.getCounter(Counters.ROWS_VIS_EXP_3); 161 rowsexp4 = context.getCounter(Counters.ROWS_VIS_EXP_4); 162 } 163 164 @Override 165 protected void map(NullWritable key, NullWritable value, Context context) throws IOException, 166 InterruptedException { 167 String suffix = "/" + shortTaskId; 168 int BLOCK_SIZE = (int) (recordsToWrite / 100); 169 for (long i = 0; i < recordsToWrite;) { 170 for (long idx = 0; idx < BLOCK_SIZE && i < recordsToWrite; idx++, i++) { 171 int expIdx = rand.nextInt(BLOCK_SIZE) % VISIBILITY_EXPS_COUNT; 172 String exp = VISIBILITY_EXPS[expIdx]; 173 byte[] row = Bytes.add(Bytes.toBytes(i), Bytes.toBytes(suffix), Bytes.toBytes(exp)); 174 Put p = new Put(row); 175 p.addColumn(TEST_FAMILY, TEST_QUALIFIER, HConstants.EMPTY_BYTE_ARRAY); 176 p.setCellVisibility(new CellVisibility(exp)); 177 getCounter(expIdx).increment(1); 178 mutator.mutate(p); 179 180 if (i % 100 == 0) { 181 context.setStatus("Written " + i + "/" + recordsToWrite + " records"); 182 context.progress(); 183 } 184 } 185 // End of block, flush all of them before we start writing anything 186 // pointing to these! 187 mutator.flush(); 188 } 189 } 190 191 private Counter getCounter(int idx) { 192 switch (idx) { 193 case 0: 194 return rowsExp1; 195 case 1: 196 return rowsExp2; 197 case 2: 198 return rowsExp3; 199 case 3: 200 return rowsexp4; 201 default: 202 return null; 203 } 204 } 205 } 206 207 public static class VerifyMapper extends TableMapper<BytesWritable, BytesWritable> { 208 private Counter rowsExp1, rowsExp2, rowsExp3, rowsExp4; 209 210 @Override 211 public void setup(Context context) throws IOException { 212 rowsExp1 = context.getCounter(Counters.ROWS_VIS_EXP_1); 213 rowsExp2 = context.getCounter(Counters.ROWS_VIS_EXP_2); 214 rowsExp3 = context.getCounter(Counters.ROWS_VIS_EXP_3); 215 rowsExp4 = context.getCounter(Counters.ROWS_VIS_EXP_4); 216 } 217 218 @Override 219 protected void map(ImmutableBytesWritable key, Result value, Context context) 220 throws IOException, InterruptedException { 221 byte[] row = value.getRow(); 222 Counter c = getCounter(row); 223 c.increment(1); 224 } 225 226 private Counter getCounter(byte[] row) { 227 Counter c = null; 228 if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[0])) != -1) { 229 c = rowsExp1; 230 } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[1])) != -1) { 231 c = rowsExp2; 232 } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[2])) != -1) { 233 c = rowsExp3; 234 } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[3])) != -1) { 235 c = rowsExp4; 236 } 237 return c; 238 } 239 } 240 241 @Override 242 protected Job doLoad(Configuration conf, HTableDescriptor htd) throws Exception { 243 Job job = super.doLoad(conf, htd); 244 this.numRowsLoadedWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue(); 245 this.numRowsLoadedWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue(); 246 this.numRowsLoadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue(); 247 this.numRowsLoadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue(); 248 System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[0] + " : " 249 + this.numRowsLoadedWithExp1); 250 System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[1] + " : " 251 + this.numRowsLoadedWithExp2); 252 System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[2] + " : " 253 + this.numRowsLoadWithExp3); 254 System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[3] + " : " 255 + this.numRowsLoadWithExp4); 256 return job; 257 } 258 259 @Override 260 protected void setMapperClass(Job job) { 261 job.setMapperClass(LoadWithCellVisibilityMapper.class); 262 } 263 264 @Override 265 protected void doVerify(final Configuration conf, final HTableDescriptor htd) throws Exception { 266 System.out.println(String.format("Verifying for auths %s, %s, %s, %s", CONFIDENTIAL, TOPSECRET, 267 SECRET, PRIVATE)); 268 PrivilegedExceptionAction<Job> scanAction = new PrivilegedExceptionAction<Job>() { 269 @Override 270 public Job run() throws Exception { 271 return doVerify(conf, htd, CONFIDENTIAL, TOPSECRET, SECRET, PRIVATE); 272 } 273 }; 274 Job job = USER1.runAs(scanAction); 275 this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue(); 276 this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue(); 277 this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue(); 278 this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue(); 279 assertEquals(this.numRowsLoadedWithExp1, this.numRowsReadWithExp1); 280 assertEquals(this.numRowsLoadedWithExp2, this.numRowsReadWithExp2); 281 assertEquals(0, this.numRowsReadWithExp3); 282 assertEquals(0, this.numRowsReadWithExp4); 283 284 // PUBLIC label auth is not provided for user1 user. 285 System.out.println(String.format("Verifying for auths %s, %s", PRIVATE, PUBLIC)); 286 scanAction = new PrivilegedExceptionAction<Job>() { 287 @Override 288 public Job run() throws Exception { 289 return doVerify(conf, htd, PRIVATE, PUBLIC); 290 } 291 }; 292 job = USER1.runAs(scanAction); 293 this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue(); 294 this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue(); 295 this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue(); 296 this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue(); 297 assertEquals(0, this.numRowsReadWithExp1); 298 assertEquals(0, this.numRowsReadWithExp2); 299 assertEquals(0, this.numRowsReadWithExp3); 300 assertEquals(this.numRowsLoadWithExp4, this.numRowsReadWithExp4); 301 302 // Normal user only having PUBLIC label auth and can view only those cells. 303 System.out.println(String.format("Verifying for auths %s, %s", PRIVATE, PUBLIC)); 304 scanAction = new PrivilegedExceptionAction<Job>() { 305 @Override 306 public Job run() throws Exception { 307 return doVerify(conf, htd, PRIVATE, PUBLIC); 308 } 309 }; 310 job = USER2.runAs(scanAction); 311 this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue(); 312 this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue(); 313 this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue(); 314 this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue(); 315 assertEquals(0, this.numRowsReadWithExp1); 316 assertEquals(0, this.numRowsReadWithExp2); 317 assertEquals(this.numRowsLoadWithExp3, this.numRowsReadWithExp3); 318 assertEquals(0, this.numRowsReadWithExp4); 319 } 320 321 private Job doVerify(Configuration conf, HTableDescriptor htd, String... auths) 322 throws IOException, InterruptedException, ClassNotFoundException { 323 Path outputDir = getTestDir(TEST_NAME, "verify-output"); 324 Job job = new Job(conf); 325 job.setJarByClass(this.getClass()); 326 job.setJobName(TEST_NAME + " Verification for " + htd.getTableName()); 327 setJobScannerConf(job); 328 Scan scan = new Scan(); 329 scan.setAuthorizations(new Authorizations(auths)); 330 TableMapReduceUtil.initTableMapperJob(htd.getTableName().getNameAsString(), scan, 331 VerifyMapper.class, NullWritable.class, NullWritable.class, job); 332 TableMapReduceUtil.addDependencyJarsForClasses(job.getConfiguration(), AbstractHBaseTool.class); 333 int scannerCaching = conf.getInt("verify.scannercaching", SCANNER_CACHING); 334 TableMapReduceUtil.setScannerCaching(job, scannerCaching); 335 job.setNumReduceTasks(0); 336 FileOutputFormat.setOutputPath(job, outputDir); 337 assertTrue(job.waitForCompletion(true)); 338 return job; 339 } 340 341 private static void setJobScannerConf(Job job) { 342 job.getConfiguration().setBoolean(ScannerCallable.LOG_SCANNER_ACTIVITY, true); 343 long lpr = job.getConfiguration().getLong(NUM_TO_WRITE_KEY, NUM_TO_WRITE_DEFAULT) / 100; 344 job.getConfiguration().setInt(TableRecordReaderImpl.LOG_PER_ROW_COUNT, (int) lpr); 345 } 346 347 @Override 348 public void printUsage() { 349 System.err.println(this.getClass().getSimpleName() + " -u usera,userb [-Doptions]"); 350 System.err.println(" Loads a table with cell visibilities and verifies with Authorizations"); 351 System.err.println("Options"); 352 System.err 353 .println(" -Dloadmapper.table=<name> Table to write/verify (default autogen)"); 354 System.err.println(" -Dloadmapper.num_to_write=<n> " 355 + "Number of rows per mapper (default 100,000 per mapper)"); 356 System.err.println(" -Dloadmapper.numPresplits=<n> " 357 + "Number of presplit regions to start with (default 40)"); 358 System.err 359 .println(" -Dloadmapper.map.tasks=<n> Number of map tasks for load (default 200)"); 360 System.err.println(" -Dverify.scannercaching=<n> " 361 + "Number hbase scanner caching rows to read (default 50)"); 362 } 363 364 @Override 365 public int runTestFromCommandLine() throws Exception { 366 IntegrationTestingUtility.setUseDistributedCluster(getConf()); 367 int numPresplits = getConf().getInt("loadmapper.numPresplits", 5); 368 // create HTableDescriptor for specified table 369 HTableDescriptor htd = new HTableDescriptor(getTablename()); 370 htd.addFamily(new HColumnDescriptor(TEST_FAMILY)); 371 372 try (Connection conn = ConnectionFactory.createConnection(getConf()); 373 Admin admin = conn.getAdmin()) { 374 admin.createTable(htd, Bytes.toBytes(0L), Bytes.toBytes(-1L), numPresplits); 375 } 376 doLoad(getConf(), htd); 377 doVerify(getConf(), htd); 378 getTestingUtil(getConf()).deleteTable(getTablename()); 379 return 0; 380 } 381 382 @SuppressWarnings("unchecked") 383 @Override 384 protected void processOptions(CommandLine cmd) { 385 List args = cmd.getArgList(); 386 if (args.size() > 0) { 387 printUsage(); 388 throw new RuntimeException("No args expected."); 389 } 390 // We always want loadAndVerify action 391 args.add("loadAndVerify"); 392 if (cmd.hasOption(USER_OPT)) { 393 userNames = cmd.getOptionValue(USER_OPT); 394 } 395 super.processOptions(cmd); 396 } 397 398 public static void main(String argv[]) throws Exception { 399 Configuration conf = HBaseConfiguration.create(); 400 IntegrationTestingUtility.setUseDistributedCluster(conf); 401 int ret = ToolRunner.run(conf, new IntegrationTestWithCellVisibilityLoadAndVerify(), argv); 402 System.exit(ret); 403 } 404}