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.jupiter.api.Assertions.assertEquals; 022import static org.junit.jupiter.api.Assertions.assertNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024 025import java.io.IOException; 026import java.security.PrivilegedExceptionAction; 027import java.util.ArrayList; 028import java.util.List; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.HBaseTestingUtil; 031import org.apache.hadoop.hbase.HConstants; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.TableNameTestExtension; 034import org.apache.hadoop.hbase.client.Connection; 035import org.apache.hadoop.hbase.client.ConnectionFactory; 036import org.apache.hadoop.hbase.client.Get; 037import org.apache.hadoop.hbase.client.Put; 038import org.apache.hadoop.hbase.client.Result; 039import org.apache.hadoop.hbase.client.ResultScanner; 040import org.apache.hadoop.hbase.client.Scan; 041import org.apache.hadoop.hbase.client.Table; 042import org.apache.hadoop.hbase.security.User; 043import org.apache.hadoop.hbase.security.access.AccessController; 044import org.apache.hadoop.hbase.security.access.Permission; 045import org.apache.hadoop.hbase.security.access.PermissionStorage; 046import org.apache.hadoop.hbase.security.access.SecureTestUtil; 047import org.apache.hadoop.hbase.testclassification.MediumTests; 048import org.apache.hadoop.hbase.testclassification.SecurityTests; 049import org.apache.hadoop.hbase.util.Bytes; 050import org.junit.jupiter.api.AfterAll; 051import org.junit.jupiter.api.BeforeAll; 052import org.junit.jupiter.api.Tag; 053import org.junit.jupiter.api.Test; 054import org.junit.jupiter.api.TestInfo; 055 056import org.apache.hbase.thirdparty.com.google.protobuf.ByteString; 057 058import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse; 059import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse; 060 061@Tag(SecurityTests.TAG) 062@Tag(MediumTests.TAG) 063public class TestVisibilityLabelsWithACL { 064 065 private static final String PRIVATE = "private"; 066 private static final String CONFIDENTIAL = "confidential"; 067 private static final String SECRET = "secret"; 068 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 069 private static final byte[] row1 = Bytes.toBytes("row1"); 070 private final static byte[] fam = Bytes.toBytes("info"); 071 private final static byte[] qual = Bytes.toBytes("qual"); 072 private final static byte[] value = Bytes.toBytes("value"); 073 private static Configuration conf; 074 075 private static User SUPERUSER; 076 private static User NORMAL_USER1; 077 private static User NORMAL_USER2; 078 079 @BeforeAll 080 public static void setupBeforeClass() throws Exception { 081 // setup configuration 082 conf = TEST_UTIL.getConfiguration(); 083 SecureTestUtil.enableSecurity(conf); 084 conf.set("hbase.coprocessor.master.classes", 085 AccessController.class.getName() + "," + VisibilityController.class.getName()); 086 conf.set("hbase.coprocessor.region.classes", 087 AccessController.class.getName() + "," + VisibilityController.class.getName()); 088 TEST_UTIL.startMiniCluster(2); 089 090 TEST_UTIL.waitTableEnabled(PermissionStorage.ACL_TABLE_NAME.getName(), 50000); 091 // Wait for the labels table to become available 092 TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000); 093 addLabels(); 094 095 // Create users for testing 096 SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" }); 097 NORMAL_USER1 = User.createUserForTesting(conf, "user1", new String[] {}); 098 NORMAL_USER2 = User.createUserForTesting(conf, "user2", new String[] {}); 099 // Grant users EXEC privilege on the labels table. For the purposes of this 100 // test, we want to insure that access is denied even with the ability to access 101 // the endpoint. 102 SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER1.getShortName(), LABELS_TABLE_NAME, null, 103 null, Permission.Action.EXEC); 104 SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), LABELS_TABLE_NAME, null, 105 null, Permission.Action.EXEC); 106 } 107 108 @AfterAll 109 public static void tearDownAfterClass() throws Exception { 110 TEST_UTIL.shutdownMiniCluster(); 111 } 112 113 @Test 114 public void testScanForUserWithFewerLabelAuthsThanLabelsInScanAuthorizations(TestInfo testInfo) 115 throws Throwable { 116 String[] auths = { SECRET }; 117 String user = "user2"; 118 VisibilityClient.setAuths(TEST_UTIL.getConnection(), auths, user); 119 TableName tableName = TableName 120 .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName())); 121 final Table table = createTableAndWriteDataWithLabels(tableName, 122 SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE); 123 SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), tableName, null, null, 124 Permission.Action.READ); 125 PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() { 126 @Override 127 public Void run() throws Exception { 128 Scan s = new Scan(); 129 s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); 130 try (Connection connection = ConnectionFactory.createConnection(conf); 131 Table t = connection.getTable(table.getName())) { 132 ResultScanner scanner = t.getScanner(s); 133 Result result = scanner.next(); 134 assertTrue(!result.isEmpty()); 135 assertTrue(Bytes.equals(Bytes.toBytes("row2"), result.getRow())); 136 result = scanner.next(); 137 assertNull(result); 138 } 139 return null; 140 } 141 }; 142 NORMAL_USER2.runAs(scanAction); 143 } 144 145 @Test 146 public void testScanForSuperUserWithFewerLabelAuths(TestInfo testInfo) throws Throwable { 147 String[] auths = { SECRET }; 148 String user = "admin"; 149 try (Connection conn = ConnectionFactory.createConnection(conf)) { 150 VisibilityClient.setAuths(conn, auths, user); 151 } 152 TableName tableName = TableName 153 .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName())); 154 final Table table = createTableAndWriteDataWithLabels(tableName, 155 SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE); 156 PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() { 157 @Override 158 public Void run() throws Exception { 159 Scan s = new Scan(); 160 s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); 161 try (Connection connection = ConnectionFactory.createConnection(conf); 162 Table t = connection.getTable(table.getName())) { 163 ResultScanner scanner = t.getScanner(s); 164 Result[] result = scanner.next(5); 165 assertTrue(result.length == 2); 166 } 167 return null; 168 } 169 }; 170 SUPERUSER.runAs(scanAction); 171 } 172 173 @Test 174 public void testGetForSuperUserWithFewerLabelAuths(TestInfo testInfo) throws Throwable { 175 String[] auths = { SECRET }; 176 String user = "admin"; 177 VisibilityClient.setAuths(TEST_UTIL.getConnection(), auths, user); 178 TableName tableName = TableName 179 .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName())); 180 final Table table = createTableAndWriteDataWithLabels(tableName, 181 SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE); 182 PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() { 183 @Override 184 public Void run() throws Exception { 185 Get g = new Get(row1); 186 g.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); 187 try (Connection connection = ConnectionFactory.createConnection(conf); 188 Table t = connection.getTable(table.getName())) { 189 Result result = t.get(g); 190 assertTrue(!result.isEmpty()); 191 } 192 return null; 193 } 194 }; 195 SUPERUSER.runAs(scanAction); 196 } 197 198 @Test 199 public void testVisibilityLabelsForUserWithNoAuths(TestInfo testInfo) throws Throwable { 200 String user = "admin"; 201 String[] auths = { SECRET }; 202 try (Connection conn = ConnectionFactory.createConnection(conf)) { 203 VisibilityClient.clearAuths(conn, auths, user); // Removing all auths if any. 204 VisibilityClient.setAuths(conn, auths, "user1"); 205 } 206 TableName tableName = TableName 207 .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName())); 208 final Table table = createTableAndWriteDataWithLabels(tableName, SECRET); 209 SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER1.getShortName(), tableName, null, null, 210 Permission.Action.READ); 211 SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), tableName, null, null, 212 Permission.Action.READ); 213 PrivilegedExceptionAction<Void> getAction = new PrivilegedExceptionAction<Void>() { 214 @Override 215 public Void run() throws Exception { 216 Get g = new Get(row1); 217 g.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); 218 try (Connection connection = ConnectionFactory.createConnection(conf); 219 Table t = connection.getTable(table.getName())) { 220 Result result = t.get(g); 221 assertTrue(result.isEmpty()); 222 } 223 return null; 224 } 225 }; 226 NORMAL_USER2.runAs(getAction); 227 } 228 229 @Test 230 public void testLabelsTableOpsWithDifferentUsers() throws Throwable { 231 PrivilegedExceptionAction<VisibilityLabelsResponse> action = 232 new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 233 @Override 234 public VisibilityLabelsResponse run() throws Exception { 235 try (Connection conn = ConnectionFactory.createConnection(conf)) { 236 return VisibilityClient.addLabels(conn, new String[] { "l1", "l2" }); 237 } catch (Throwable e) { 238 } 239 return null; 240 } 241 }; 242 VisibilityLabelsResponse response = NORMAL_USER1.runAs(action); 243 assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", 244 response.getResult(0).getException().getName()); 245 assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", 246 response.getResult(1).getException().getName()); 247 248 action = new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 249 @Override 250 public VisibilityLabelsResponse run() throws Exception { 251 try (Connection conn = ConnectionFactory.createConnection(conf)) { 252 return VisibilityClient.setAuths(conn, new String[] { CONFIDENTIAL, PRIVATE }, "user1"); 253 } catch (Throwable e) { 254 } 255 return null; 256 } 257 }; 258 response = NORMAL_USER1.runAs(action); 259 assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", 260 response.getResult(0).getException().getName()); 261 assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", 262 response.getResult(1).getException().getName()); 263 264 action = new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 265 @Override 266 public VisibilityLabelsResponse run() throws Exception { 267 try (Connection conn = ConnectionFactory.createConnection(conf)) { 268 return VisibilityClient.setAuths(conn, new String[] { CONFIDENTIAL, PRIVATE }, "user1"); 269 } catch (Throwable e) { 270 } 271 return null; 272 } 273 }; 274 response = SUPERUSER.runAs(action); 275 assertTrue(response.getResult(0).getException().getValue().isEmpty()); 276 assertTrue(response.getResult(1).getException().getValue().isEmpty()); 277 278 action = new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 279 @Override 280 public VisibilityLabelsResponse run() throws Exception { 281 try (Connection conn = ConnectionFactory.createConnection(conf)) { 282 return VisibilityClient.clearAuths(conn, new String[] { CONFIDENTIAL, PRIVATE }, "user1"); 283 } catch (Throwable e) { 284 } 285 return null; 286 } 287 }; 288 response = NORMAL_USER1.runAs(action); 289 assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", 290 response.getResult(0).getException().getName()); 291 assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", 292 response.getResult(1).getException().getName()); 293 294 response = VisibilityClient.clearAuths(TEST_UTIL.getConnection(), 295 new String[] { CONFIDENTIAL, PRIVATE }, "user1"); 296 assertTrue(response.getResult(0).getException().getValue().isEmpty()); 297 assertTrue(response.getResult(1).getException().getValue().isEmpty()); 298 299 VisibilityClient.setAuths(TEST_UTIL.getConnection(), new String[] { CONFIDENTIAL, PRIVATE }, 300 "user3"); 301 PrivilegedExceptionAction<GetAuthsResponse> action1 = 302 new PrivilegedExceptionAction<GetAuthsResponse>() { 303 @Override 304 public GetAuthsResponse run() throws Exception { 305 try (Connection conn = ConnectionFactory.createConnection(conf)) { 306 return VisibilityClient.getAuths(conn, "user3"); 307 } catch (Throwable e) { 308 } 309 return null; 310 } 311 }; 312 GetAuthsResponse authsResponse = NORMAL_USER1.runAs(action1); 313 assertNull(authsResponse); 314 authsResponse = SUPERUSER.runAs(action1); 315 List<String> authsList = new ArrayList<>(authsResponse.getAuthList().size()); 316 for (ByteString authBS : authsResponse.getAuthList()) { 317 authsList.add(Bytes.toString(authBS.toByteArray())); 318 } 319 assertEquals(2, authsList.size()); 320 assertTrue(authsList.contains(CONFIDENTIAL)); 321 assertTrue(authsList.contains(PRIVATE)); 322 } 323 324 private static Table createTableAndWriteDataWithLabels(TableName tableName, String... labelExps) 325 throws Exception { 326 Table table = null; 327 try { 328 table = TEST_UTIL.createTable(tableName, fam); 329 int i = 1; 330 List<Put> puts = new ArrayList<>(labelExps.length); 331 for (String labelExp : labelExps) { 332 Put put = new Put(Bytes.toBytes("row" + i)); 333 put.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value); 334 put.setCellVisibility(new CellVisibility(labelExp)); 335 puts.add(put); 336 i++; 337 } 338 table.put(puts); 339 } finally { 340 if (table != null) { 341 table.close(); 342 } 343 } 344 return table; 345 } 346 347 private static void addLabels() throws IOException { 348 String[] labels = { SECRET, CONFIDENTIAL, PRIVATE }; 349 try { 350 VisibilityClient.addLabels(TEST_UTIL.getConnection(), labels); 351 } catch (Throwable t) { 352 throw new IOException(t); 353 } 354 } 355}