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 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 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.security.visibility; 019 020import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME; 021import static org.junit.Assert.assertArrayEquals; 022import static org.junit.Assert.assertEquals; 023import static org.junit.Assert.assertTrue; 024 025import java.io.IOException; 026import java.security.PrivilegedExceptionAction; 027import java.util.ArrayList; 028import java.util.List; 029import java.util.Optional; 030import java.util.concurrent.atomic.AtomicInteger; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.hbase.ArrayBackedTag; 033import org.apache.hadoop.hbase.Cell; 034import org.apache.hadoop.hbase.CellScanner; 035import org.apache.hadoop.hbase.CellUtil; 036import org.apache.hadoop.hbase.ExtendedCell; 037import org.apache.hadoop.hbase.HBaseClassTestRule; 038import org.apache.hadoop.hbase.HBaseConfiguration; 039import org.apache.hadoop.hbase.HBaseTestingUtil; 040import org.apache.hadoop.hbase.HConstants; 041import org.apache.hadoop.hbase.KeyValue; 042import org.apache.hadoop.hbase.KeyValueUtil; 043import org.apache.hadoop.hbase.PrivateCellUtil; 044import org.apache.hadoop.hbase.TableName; 045import org.apache.hadoop.hbase.Tag; 046import org.apache.hadoop.hbase.TagType; 047import org.apache.hadoop.hbase.client.Admin; 048import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 049import org.apache.hadoop.hbase.client.Connection; 050import org.apache.hadoop.hbase.client.ConnectionFactory; 051import org.apache.hadoop.hbase.client.Durability; 052import org.apache.hadoop.hbase.client.Get; 053import org.apache.hadoop.hbase.client.Put; 054import org.apache.hadoop.hbase.client.Result; 055import org.apache.hadoop.hbase.client.ResultScanner; 056import org.apache.hadoop.hbase.client.Scan; 057import org.apache.hadoop.hbase.client.Table; 058import org.apache.hadoop.hbase.client.TableDescriptor; 059import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 060import org.apache.hadoop.hbase.codec.KeyValueCodecWithTags; 061import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 062import org.apache.hadoop.hbase.coprocessor.ObserverContext; 063import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor; 064import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; 065import org.apache.hadoop.hbase.coprocessor.RegionObserver; 066import org.apache.hadoop.hbase.replication.ReplicationEndpoint; 067import org.apache.hadoop.hbase.replication.ReplicationPeerConfig; 068import org.apache.hadoop.hbase.security.User; 069import org.apache.hadoop.hbase.testclassification.MediumTests; 070import org.apache.hadoop.hbase.testclassification.SecurityTests; 071import org.apache.hadoop.hbase.util.Bytes; 072import org.apache.hadoop.hbase.wal.WAL.Entry; 073import org.apache.hadoop.hbase.wal.WALEdit; 074import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster; 075import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 076import org.junit.Assert; 077import org.junit.Before; 078import org.junit.ClassRule; 079import org.junit.Rule; 080import org.junit.Test; 081import org.junit.experimental.categories.Category; 082import org.junit.rules.TestName; 083import org.slf4j.Logger; 084import org.slf4j.LoggerFactory; 085 086import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse; 087 088@Category({ SecurityTests.class, MediumTests.class }) 089public class TestVisibilityLabelsReplication { 090 091 @ClassRule 092 public static final HBaseClassTestRule CLASS_RULE = 093 HBaseClassTestRule.forClass(TestVisibilityLabelsReplication.class); 094 095 private static final Logger LOG = LoggerFactory.getLogger(TestVisibilityLabelsReplication.class); 096 protected static final int NON_VIS_TAG_TYPE = 100; 097 protected static final String TEMP = "temp"; 098 protected static Configuration conf; 099 protected static Configuration conf1; 100 protected static TableName TABLE_NAME = TableName.valueOf("TABLE_NAME"); 101 protected static Admin admin; 102 public static final String TOPSECRET = "topsecret"; 103 public static final String PUBLIC = "public"; 104 public static final String PRIVATE = "private"; 105 public static final String CONFIDENTIAL = "confidential"; 106 public static final String COPYRIGHT = "\u00A9ABC"; 107 public static final String ACCENT = "\u0941"; 108 public static final String SECRET = "secret"; 109 public static final String UNICODE_VIS_TAG = 110 COPYRIGHT + "\"" + ACCENT + "\\" + SECRET + "\"" + "\u0027&\\"; 111 public static HBaseTestingUtil TEST_UTIL; 112 public static HBaseTestingUtil TEST_UTIL1; 113 public static final byte[] row1 = Bytes.toBytes("row1"); 114 public static final byte[] row2 = Bytes.toBytes("row2"); 115 public static final byte[] row3 = Bytes.toBytes("row3"); 116 public static final byte[] row4 = Bytes.toBytes("row4"); 117 public final static byte[] fam = Bytes.toBytes("info"); 118 public final static byte[] qual = Bytes.toBytes("qual"); 119 public final static byte[] value = Bytes.toBytes("value"); 120 protected static ZKWatcher zkw1; 121 protected static ZKWatcher zkw2; 122 protected static int expected[] = { 4, 6, 4, 0, 3 }; 123 private static final String NON_VISIBILITY = "non-visibility"; 124 protected static String[] expectedVisString = 125 { "(\"secret\"&\"topsecret\"&\"public\")|(\"topsecret\"&\"confidential\")", 126 "(\"public\"&\"private\")|(\"topsecret\"&\"private\")|" 127 + "(\"confidential\"&\"public\")|(\"topsecret\"&\"confidential\")", 128 "(!\"topsecret\"&\"secret\")|(!\"topsecret\"&\"confidential\")", "(\"secret\"&\"" + COPYRIGHT 129 + "\\\"" + ACCENT + "\\\\" + SECRET + "\\\"" + "\u0027&\\\\" + "\")" }; 130 131 @Rule 132 public final TestName TEST_NAME = new TestName(); 133 public static User SUPERUSER, USER1; 134 135 @Before 136 public void setup() throws Exception { 137 // setup configuration 138 conf = HBaseConfiguration.create(); 139 conf.setInt("hfile.format.version", 3); 140 conf.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/1"); 141 conf.setInt("replication.source.size.capacity", 10240); 142 conf.setLong("replication.source.sleepforretries", 100); 143 conf.setInt("hbase.regionserver.maxlogs", 10); 144 conf.setLong("hbase.master.logcleaner.ttl", 10); 145 conf.setInt("zookeeper.recovery.retry", 1); 146 conf.setInt("zookeeper.recovery.retry.intervalmill", 10); 147 conf.setLong(HConstants.THREAD_WAKE_FREQUENCY, 100); 148 conf.setInt("replication.stats.thread.period.seconds", 5); 149 conf.setBoolean("hbase.tests.use.shortcircuit.reads", false); 150 setVisibilityLabelServiceImpl(conf); 151 conf.setStrings(HConstants.REPLICATION_CODEC_CONF_KEY, KeyValueCodecWithTags.class.getName()); 152 VisibilityTestUtil.enableVisiblityLabels(conf); 153 conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, 154 VisibilityReplication.class.getName()); 155 conf.setStrings(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY, SimpleCP.class.getName()); 156 // Have to reset conf1 in case zk cluster location different 157 // than default 158 conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class, 159 ScanLabelGenerator.class); 160 conf.set("hbase.superuser", User.getCurrent().getShortName()); 161 SUPERUSER = User.createUserForTesting(conf, User.getCurrent().getShortName(), 162 new String[] { "supergroup" }); 163 // User.createUserForTesting(conf, User.getCurrent().getShortName(), new 164 // String[] { "supergroup" }); 165 USER1 = User.createUserForTesting(conf, "user1", new String[] {}); 166 TEST_UTIL = new HBaseTestingUtil(conf); 167 TEST_UTIL.startMiniZKCluster(); 168 MiniZooKeeperCluster miniZK = TEST_UTIL.getZkCluster(); 169 zkw1 = new ZKWatcher(conf, "cluster1", null, true); 170 171 // Base conf2 on conf1 so it gets the right zk cluster. 172 conf1 = HBaseConfiguration.create(conf); 173 conf1.setInt("hfile.format.version", 3); 174 conf1.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/2"); 175 conf1.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6); 176 conf1.setBoolean("hbase.tests.use.shortcircuit.reads", false); 177 conf1.setStrings(HConstants.REPLICATION_CODEC_CONF_KEY, KeyValueCodecWithTags.class.getName()); 178 conf1.setStrings(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY, 179 TestCoprocessorForTagsAtSink.class.getName()); 180 // setVisibilityLabelServiceImpl(conf1); 181 USER1 = User.createUserForTesting(conf1, "user1", new String[] {}); 182 TEST_UTIL1 = new HBaseTestingUtil(conf1); 183 TEST_UTIL1.setZkCluster(miniZK); 184 zkw2 = new ZKWatcher(conf1, "cluster2", null, true); 185 186 TEST_UTIL.startMiniCluster(1); 187 // Wait for the labels table to become available 188 TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000); 189 TEST_UTIL1.startMiniCluster(1); 190 191 admin = TEST_UTIL.getAdmin(); 192 ReplicationPeerConfig rpc = 193 ReplicationPeerConfig.newBuilder().setClusterKey(TEST_UTIL1.getRpcConnnectionURI()).build(); 194 admin.addReplicationPeer("2", rpc); 195 196 Admin hBaseAdmin = TEST_UTIL.getAdmin(); 197 TableDescriptor tableDescriptor = 198 TableDescriptorBuilder.newBuilder(TABLE_NAME).setColumnFamily(ColumnFamilyDescriptorBuilder 199 .newBuilder(fam).setScope(HConstants.REPLICATION_SCOPE_GLOBAL).build()).build(); 200 try { 201 hBaseAdmin.createTable(tableDescriptor); 202 } finally { 203 if (hBaseAdmin != null) { 204 hBaseAdmin.close(); 205 } 206 } 207 Admin hBaseAdmin1 = TEST_UTIL1.getAdmin(); 208 try { 209 hBaseAdmin1.createTable(tableDescriptor); 210 } finally { 211 if (hBaseAdmin1 != null) { 212 hBaseAdmin1.close(); 213 } 214 } 215 addLabels(); 216 setAuths(conf); 217 setAuths(conf1); 218 } 219 220 protected static void setVisibilityLabelServiceImpl(Configuration conf) { 221 conf.setClass(VisibilityLabelServiceManager.VISIBILITY_LABEL_SERVICE_CLASS, 222 DefaultVisibilityLabelServiceImpl.class, VisibilityLabelService.class); 223 } 224 225 @Test 226 public void testVisibilityReplication() throws Exception { 227 int retry = 0; 228 try (Table table = writeData(TABLE_NAME, 229 "(" + SECRET + "&" + PUBLIC + ")" + "|(" + CONFIDENTIAL + ")&(" + TOPSECRET + ")", 230 "(" + PRIVATE + "|" + CONFIDENTIAL + ")&(" + PUBLIC + "|" + TOPSECRET + ")", 231 "(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET, 232 CellVisibility.quote(UNICODE_VIS_TAG) + "&" + SECRET)) { 233 Scan s = new Scan(); 234 s.setAuthorizations( 235 new Authorizations(SECRET, CONFIDENTIAL, PRIVATE, TOPSECRET, UNICODE_VIS_TAG)); 236 ResultScanner scanner = table.getScanner(s); 237 Result[] next = scanner.next(4); 238 239 assertTrue(next.length == 4); 240 CellScanner cellScanner = next[0].cellScanner(); 241 cellScanner.advance(); 242 Cell current = cellScanner.current(); 243 assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), current.getRowLength(), 244 row1, 0, row1.length)); 245 cellScanner = next[1].cellScanner(); 246 cellScanner.advance(); 247 current = cellScanner.current(); 248 assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), current.getRowLength(), 249 row2, 0, row2.length)); 250 cellScanner = next[2].cellScanner(); 251 cellScanner.advance(); 252 current = cellScanner.current(); 253 assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), current.getRowLength(), 254 row3, 0, row3.length)); 255 cellScanner = next[3].cellScanner(); 256 cellScanner.advance(); 257 current = cellScanner.current(); 258 assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), current.getRowLength(), 259 row4, 0, row4.length)); 260 try (Table table2 = TEST_UTIL1.getConnection().getTable(TABLE_NAME)) { 261 s = new Scan(); 262 // Ensure both rows are replicated 263 scanner = table2.getScanner(s); 264 next = scanner.next(4); 265 while (next.length == 0 && retry <= 10) { 266 scanner = table2.getScanner(s); 267 next = scanner.next(4); 268 Thread.sleep(2000); 269 retry++; 270 } 271 assertTrue(next.length == 4); 272 verifyGet(row1, expectedVisString[0], expected[0], false, TOPSECRET, CONFIDENTIAL); 273 TestCoprocessorForTagsAtSink.tags.clear(); 274 verifyGet(row2, expectedVisString[1], expected[1], false, CONFIDENTIAL, PUBLIC); 275 TestCoprocessorForTagsAtSink.tags.clear(); 276 verifyGet(row3, expectedVisString[2], expected[2], false, PRIVATE, SECRET); 277 verifyGet(row3, "", expected[3], true, TOPSECRET, SECRET); 278 verifyGet(row4, expectedVisString[3], expected[4], false, UNICODE_VIS_TAG, SECRET); 279 } 280 } 281 } 282 283 protected static void doAssert(byte[] row, String visTag) throws Exception { 284 if (VisibilityReplicationEndPointForTest.lastEntries == null) { 285 return; // first call 286 } 287 Assert.assertEquals(1, VisibilityReplicationEndPointForTest.lastEntries.size()); 288 List<Cell> cells = VisibilityReplicationEndPointForTest.lastEntries.get(0).getEdit().getCells(); 289 Assert.assertEquals(4, cells.size()); 290 boolean tagFound = false; 291 for (Cell cell : cells) { 292 if ( 293 (Bytes.equals(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength(), row, 0, 294 row.length)) 295 ) { 296 List<Tag> tags = PrivateCellUtil.getTags((ExtendedCell) cell); 297 for (Tag tag : tags) { 298 if (tag.getType() == TagType.STRING_VIS_TAG_TYPE) { 299 assertEquals(visTag, Tag.getValueAsString(tag)); 300 tagFound = true; 301 break; 302 } 303 } 304 } 305 } 306 assertTrue(tagFound); 307 } 308 309 protected void verifyGet(final byte[] row, final String visString, final int expected, 310 final boolean nullExpected, final String... auths) throws IOException, InterruptedException { 311 PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() { 312 @Override 313 public Void run() throws Exception { 314 try (Connection connection = ConnectionFactory.createConnection(conf1); 315 Table table2 = connection.getTable(TABLE_NAME)) { 316 CellScanner cellScanner; 317 Cell current; 318 Get get = new Get(row); 319 get.setAuthorizations(new Authorizations(auths)); 320 Result result = table2.get(get); 321 cellScanner = result.cellScanner(); 322 boolean advance = cellScanner.advance(); 323 if (nullExpected) { 324 assertTrue(!advance); 325 return null; 326 } 327 current = cellScanner.current(); 328 assertArrayEquals(CellUtil.cloneRow(current), row); 329 for (Tag tag : TestCoprocessorForTagsAtSink.tags) { 330 LOG.info("The tag type is " + tag.getType()); 331 } 332 assertEquals(expected, TestCoprocessorForTagsAtSink.tags.size()); 333 Tag tag = TestCoprocessorForTagsAtSink.tags.get(1); 334 if (tag.getType() != NON_VIS_TAG_TYPE) { 335 assertEquals(TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE, tag.getType()); 336 } 337 tag = TestCoprocessorForTagsAtSink.tags.get(0); 338 boolean foundNonVisTag = false; 339 for (Tag t : TestCoprocessorForTagsAtSink.tags) { 340 if (t.getType() == NON_VIS_TAG_TYPE) { 341 assertEquals(TEMP, Tag.getValueAsString(t)); 342 foundNonVisTag = true; 343 break; 344 } 345 } 346 doAssert(row, visString); 347 assertTrue(foundNonVisTag); 348 return null; 349 } 350 } 351 }; 352 USER1.runAs(scanAction); 353 } 354 355 public static void addLabels() throws Exception { 356 PrivilegedExceptionAction<VisibilityLabelsResponse> action = 357 new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 358 @Override 359 public VisibilityLabelsResponse run() throws Exception { 360 String[] labels = { SECRET, TOPSECRET, CONFIDENTIAL, PUBLIC, PRIVATE, UNICODE_VIS_TAG }; 361 try (Connection conn = ConnectionFactory.createConnection(conf)) { 362 VisibilityClient.addLabels(conn, labels); 363 } catch (Throwable t) { 364 throw new IOException(t); 365 } 366 return null; 367 } 368 }; 369 SUPERUSER.runAs(action); 370 } 371 372 public static void setAuths(final Configuration conf) throws Exception { 373 PrivilegedExceptionAction<VisibilityLabelsResponse> action = 374 new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 375 @Override 376 public VisibilityLabelsResponse run() throws Exception { 377 try (Connection conn = ConnectionFactory.createConnection(conf)) { 378 return VisibilityClient.setAuths(conn, 379 new String[] { SECRET, CONFIDENTIAL, PRIVATE, TOPSECRET, UNICODE_VIS_TAG }, "user1"); 380 } catch (Throwable e) { 381 throw new Exception(e); 382 } 383 } 384 }; 385 VisibilityLabelsResponse response = SUPERUSER.runAs(action); 386 } 387 388 static Table writeData(TableName tableName, String... labelExps) throws Exception { 389 Table table = TEST_UTIL.getConnection().getTable(TABLE_NAME); 390 int i = 1; 391 List<Put> puts = new ArrayList<>(labelExps.length); 392 for (String labelExp : labelExps) { 393 Put put = new Put(Bytes.toBytes("row" + i)); 394 put.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value); 395 put.setCellVisibility(new CellVisibility(labelExp)); 396 put.setAttribute(NON_VISIBILITY, Bytes.toBytes(TEMP)); 397 puts.add(put); 398 i++; 399 } 400 table.put(puts); 401 return table; 402 } 403 404 // A simple BaseRegionbserver impl that allows to add a non-visibility tag from the 405 // attributes of the Put mutation. The existing cells in the put mutation is overwritten 406 // with a new cell that has the visibility tags and the non visibility tag 407 public static class SimpleCP implements RegionCoprocessor, RegionObserver { 408 @Override 409 public Optional<RegionObserver> getRegionObserver() { 410 return Optional.of(this); 411 } 412 413 @Override 414 public void prePut(ObserverContext<? extends RegionCoprocessorEnvironment> e, Put m, 415 WALEdit edit, Durability durability) throws IOException { 416 byte[] attribute = m.getAttribute(NON_VISIBILITY); 417 byte[] cf = null; 418 List<Cell> updatedCells = new ArrayList<>(); 419 if (attribute != null) { 420 for (List<? extends Cell> edits : m.getFamilyCellMap().values()) { 421 for (Cell cell : edits) { 422 KeyValue kv = KeyValueUtil.ensureKeyValue((ExtendedCell) cell); 423 if (cf == null) { 424 cf = CellUtil.cloneFamily(kv); 425 } 426 Tag tag = new ArrayBackedTag((byte) NON_VIS_TAG_TYPE, attribute); 427 List<Tag> tagList = 428 new ArrayList<>(PrivateCellUtil.getTags((ExtendedCell) cell).size() + 1); 429 tagList.add(tag); 430 tagList.addAll(PrivateCellUtil.getTags((ExtendedCell) cell)); 431 Cell newcell = PrivateCellUtil.createCell(kv, tagList); 432 ((List<Cell>) updatedCells).add(newcell); 433 } 434 } 435 m.getFamilyCellMap().remove(cf); 436 // Update the family map 437 m.getFamilyCellMap().put(cf, updatedCells); 438 } 439 } 440 } 441 442 public static class TestCoprocessorForTagsAtSink implements RegionCoprocessor, RegionObserver { 443 public static List<Tag> tags = null; 444 445 @Override 446 public Optional<RegionObserver> getRegionObserver() { 447 return Optional.of(this); 448 } 449 450 @Override 451 public void postGetOp(ObserverContext<? extends RegionCoprocessorEnvironment> e, Get get, 452 List<Cell> results) throws IOException { 453 if (results.size() > 0) { 454 // Check tag presence in the 1st cell in 1st Result 455 if (!results.isEmpty()) { 456 Cell cell = results.get(0); 457 tags = PrivateCellUtil.getTags((ExtendedCell) cell); 458 } 459 } 460 } 461 } 462 463 /** 464 * An extn of VisibilityReplicationEndpoint to verify the tags that are replicated 465 */ 466 public static class VisibilityReplicationEndPointForTest extends VisibilityReplicationEndpoint { 467 static AtomicInteger replicateCount = new AtomicInteger(); 468 static volatile List<Entry> lastEntries = null; 469 470 public VisibilityReplicationEndPointForTest(ReplicationEndpoint endpoint, 471 VisibilityLabelService visibilityLabelsService) { 472 super(endpoint, visibilityLabelsService); 473 } 474 475 @Override 476 public boolean replicate(ReplicateContext replicateContext) { 477 boolean ret = super.replicate(replicateContext); 478 lastEntries = replicateContext.getEntries(); 479 replicateCount.incrementAndGet(); 480 return ret; 481 } 482 } 483}