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.apache.hadoop.hbase.security.visibility.VisibilityUtils.SYSTEM_LABEL; 022import static org.junit.jupiter.api.Assertions.assertEquals; 023import static org.junit.jupiter.api.Assertions.assertFalse; 024import static org.junit.jupiter.api.Assertions.assertThrows; 025import static org.junit.jupiter.api.Assertions.assertTrue; 026 027import java.io.IOException; 028import java.security.PrivilegedExceptionAction; 029import java.util.List; 030import java.util.concurrent.atomic.AtomicBoolean; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.TableNameTestExtension; 033import org.apache.hadoop.hbase.client.Connection; 034import org.apache.hadoop.hbase.client.ConnectionFactory; 035import org.apache.hadoop.hbase.client.Result; 036import org.apache.hadoop.hbase.client.ResultScanner; 037import org.apache.hadoop.hbase.client.Scan; 038import org.apache.hadoop.hbase.client.Table; 039import org.apache.hadoop.hbase.security.User; 040import org.apache.hadoop.hbase.testclassification.MediumTests; 041import org.apache.hadoop.hbase.testclassification.SecurityTests; 042import org.apache.hadoop.hbase.util.Bytes; 043import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread; 044import org.apache.hadoop.hbase.util.Threads; 045import org.junit.jupiter.api.BeforeAll; 046import org.junit.jupiter.api.Tag; 047import org.junit.jupiter.api.Test; 048import org.junit.jupiter.api.TestInfo; 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051 052import org.apache.hbase.thirdparty.com.google.protobuf.ByteString; 053 054import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 055import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.RegionActionResult; 056import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.NameBytesPair; 057import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.ListLabelsResponse; 058import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse; 059 060@Tag(SecurityTests.TAG) 061@Tag(MediumTests.TAG) 062public class TestVisibilityLabelsWithDefaultVisLabelService extends VisibilityLabelsTestBase { 063 064 private static final Logger LOG = 065 LoggerFactory.getLogger(TestVisibilityLabelsWithDefaultVisLabelService.class); 066 067 @BeforeAll 068 public static void setupBeforeClass() throws Exception { 069 // setup configuration 070 conf = TEST_UTIL.getConfiguration(); 071 VisibilityTestUtil.enableVisiblityLabels(conf); 072 conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class, 073 ScanLabelGenerator.class); 074 conf.set("hbase.superuser", "admin"); 075 TEST_UTIL.startMiniCluster(2); 076 SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" }); 077 USER1 = User.createUserForTesting(conf, "user1", new String[] {}); 078 079 // Wait for the labels table to become available 080 TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000); 081 addLabels(); 082 } 083 084 @Test 085 public void testVisibilityLabelsInPutsThatDoesNotMatchAnyDefinedLabels() throws Exception { 086 TableName tableName = name.getTableName(); 087 assertThrows(Exception.class, 088 () -> createTableAndWriteDataWithLabels(tableName, "SAMPLE_LABEL", "TEST"), 089 "Should have failed with failed sanity check exception"); 090 } 091 092 @Test 093 public void testAddLabels() throws Throwable { 094 PrivilegedExceptionAction<VisibilityLabelsResponse> action = 095 new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 096 @Override 097 public VisibilityLabelsResponse run() throws Exception { 098 String[] labels = { "L1", SECRET, "L2", "invalid~", "L3" }; 099 VisibilityLabelsResponse response = null; 100 try (Connection conn = ConnectionFactory.createConnection(conf)) { 101 response = VisibilityClient.addLabels(conn, labels); 102 } catch (Throwable e) { 103 throw new IOException(e); 104 } 105 List<RegionActionResult> resultList = response.getResultList(); 106 assertEquals(5, resultList.size()); 107 assertTrue(resultList.get(0).getException().getValue().isEmpty()); 108 assertEquals("org.apache.hadoop.hbase.DoNotRetryIOException", 109 resultList.get(1).getException().getName()); 110 assertTrue(Bytes.toString(resultList.get(1).getException().getValue().toByteArray()) 111 .contains("org.apache.hadoop.hbase.security.visibility.LabelAlreadyExistsException: " 112 + "Label 'secret' already exists")); 113 assertTrue(resultList.get(2).getException().getValue().isEmpty()); 114 assertTrue(resultList.get(3).getException().getValue().isEmpty()); 115 assertTrue(resultList.get(4).getException().getValue().isEmpty()); 116 return null; 117 } 118 }; 119 SUPERUSER.runAs(action); 120 } 121 122 @Test 123 public void testAddVisibilityLabelsOnRSRestart() throws Exception { 124 List<RegionServerThread> regionServerThreads = 125 TEST_UTIL.getHBaseCluster().getRegionServerThreads(); 126 for (RegionServerThread rsThread : regionServerThreads) { 127 rsThread.getRegionServer().abort("Aborting "); 128 } 129 // Start one new RS 130 RegionServerThread rs = TEST_UTIL.getHBaseCluster().startRegionServer(); 131 waitForLabelsRegionAvailability(rs.getRegionServer()); 132 final AtomicBoolean vcInitialized = new AtomicBoolean(true); 133 do { 134 PrivilegedExceptionAction<VisibilityLabelsResponse> action = 135 new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 136 @Override 137 public VisibilityLabelsResponse run() throws Exception { 138 String[] labels = { SECRET, CONFIDENTIAL, PRIVATE, "ABC", "XYZ" }; 139 try (Connection conn = ConnectionFactory.createConnection(conf)) { 140 VisibilityLabelsResponse resp = VisibilityClient.addLabels(conn, labels); 141 List<RegionActionResult> results = resp.getResultList(); 142 if (results.get(0).hasException()) { 143 NameBytesPair pair = results.get(0).getException(); 144 Throwable t = ProtobufUtil.toException(pair); 145 LOG.debug("Got exception writing labels", t); 146 if (t instanceof VisibilityControllerNotReadyException) { 147 vcInitialized.set(false); 148 LOG.warn("VisibilityController was not yet initialized"); 149 Threads.sleep(10); 150 } else { 151 vcInitialized.set(true); 152 } 153 } else LOG.debug("new labels added: " + resp); 154 } catch (Throwable t) { 155 throw new IOException(t); 156 } 157 return null; 158 } 159 }; 160 SUPERUSER.runAs(action); 161 } while (!vcInitialized.get()); 162 // Scan the visibility label 163 Scan s = new Scan(); 164 s.setAuthorizations(new Authorizations(VisibilityUtils.SYSTEM_LABEL)); 165 166 int i = 0; 167 try (Table ht = TEST_UTIL.getConnection().getTable(LABELS_TABLE_NAME); 168 ResultScanner scanner = ht.getScanner(s)) { 169 while (true) { 170 Result next = scanner.next(); 171 if (next == null) { 172 break; 173 } 174 i++; 175 } 176 } 177 // One label is the "system" label. 178 assertEquals(13, i, "The count should be 13"); 179 } 180 181 @Test 182 public void testListLabels() throws Throwable { 183 PrivilegedExceptionAction<ListLabelsResponse> action = 184 new PrivilegedExceptionAction<ListLabelsResponse>() { 185 @Override 186 public ListLabelsResponse run() throws Exception { 187 ListLabelsResponse response = null; 188 try (Connection conn = ConnectionFactory.createConnection(conf)) { 189 response = VisibilityClient.listLabels(conn, null); 190 } catch (Throwable e) { 191 throw new IOException(e); 192 } 193 // The addLabels() in setup added: 194 // { SECRET, TOPSECRET, CONFIDENTIAL, PUBLIC, PRIVATE, COPYRIGHT, ACCENT, 195 // UNICODE_VIS_TAG, UC1, UC2 }; 196 // The previous tests added 2 more labels: ABC, XYZ 197 // The 'system' label is excluded. 198 List<ByteString> labels = response.getLabelList(); 199 assertEquals(12, labels.size()); 200 assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(SECRET)))); 201 assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(TOPSECRET)))); 202 assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(CONFIDENTIAL)))); 203 assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes("ABC")))); 204 assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes("XYZ")))); 205 assertFalse(labels.contains(ByteString.copyFrom(Bytes.toBytes(SYSTEM_LABEL)))); 206 return null; 207 } 208 }; 209 SUPERUSER.runAs(action); 210 } 211 212 @Test 213 public void testListLabelsWithRegEx() throws Throwable { 214 PrivilegedExceptionAction<ListLabelsResponse> action = 215 new PrivilegedExceptionAction<ListLabelsResponse>() { 216 @Override 217 public ListLabelsResponse run() throws Exception { 218 ListLabelsResponse response = null; 219 try (Connection conn = ConnectionFactory.createConnection(conf)) { 220 response = VisibilityClient.listLabels(conn, ".*secret"); 221 } catch (Throwable e) { 222 throw new IOException(e); 223 } 224 // Only return the labels that end with 'secret' 225 List<ByteString> labels = response.getLabelList(); 226 assertEquals(2, labels.size()); 227 assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(SECRET)))); 228 assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(TOPSECRET)))); 229 return null; 230 } 231 }; 232 SUPERUSER.runAs(action); 233 } 234 235 @Test 236 public void testVisibilityLabelsOnWALReplay(TestInfo testInfo) throws Exception { 237 final TableName tableName = TableName 238 .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName())); 239 try (Table table = createTableAndWriteDataWithLabels(tableName, 240 "(" + SECRET + "|" + CONFIDENTIAL + ")", PRIVATE)) { 241 List<RegionServerThread> regionServerThreads = 242 TEST_UTIL.getHBaseCluster().getRegionServerThreads(); 243 for (RegionServerThread rsThread : regionServerThreads) { 244 rsThread.getRegionServer().abort("Aborting "); 245 } 246 // Start one new RS 247 RegionServerThread rs = TEST_UTIL.getHBaseCluster().startRegionServer(); 248 waitForLabelsRegionAvailability(rs.getRegionServer()); 249 Scan s = new Scan(); 250 s.setAuthorizations(new Authorizations(SECRET)); 251 ResultScanner scanner = table.getScanner(s); 252 Result[] next = scanner.next(3); 253 assertTrue(next.length == 1); 254 } 255 } 256}