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.assertEquals; 022import static org.junit.Assert.assertNull; 023import static org.junit.Assert.assertTrue; 024 025import com.google.protobuf.ByteString; 026import java.io.IOException; 027import java.security.PrivilegedExceptionAction; 028import java.util.ArrayList; 029import java.util.List; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.hbase.HBaseClassTestRule; 032import org.apache.hadoop.hbase.HBaseTestingUtility; 033import org.apache.hadoop.hbase.HConstants; 034import org.apache.hadoop.hbase.TableName; 035import org.apache.hadoop.hbase.client.Connection; 036import org.apache.hadoop.hbase.client.ConnectionFactory; 037import org.apache.hadoop.hbase.client.Get; 038import org.apache.hadoop.hbase.client.Put; 039import org.apache.hadoop.hbase.client.Result; 040import org.apache.hadoop.hbase.client.ResultScanner; 041import org.apache.hadoop.hbase.client.Scan; 042import org.apache.hadoop.hbase.client.Table; 043import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse; 044import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse; 045import org.apache.hadoop.hbase.security.User; 046import org.apache.hadoop.hbase.security.access.AccessController; 047import org.apache.hadoop.hbase.security.access.Permission; 048import org.apache.hadoop.hbase.security.access.PermissionStorage; 049import org.apache.hadoop.hbase.security.access.SecureTestUtil; 050import org.apache.hadoop.hbase.testclassification.MediumTests; 051import org.apache.hadoop.hbase.testclassification.SecurityTests; 052import org.apache.hadoop.hbase.util.Bytes; 053import org.junit.AfterClass; 054import org.junit.BeforeClass; 055import org.junit.ClassRule; 056import org.junit.Rule; 057import org.junit.Test; 058import org.junit.experimental.categories.Category; 059import org.junit.rules.TestName; 060 061@Category({ SecurityTests.class, MediumTests.class }) 062public class TestVisibilityLabelsWithACL { 063 064 @ClassRule 065 public static final HBaseClassTestRule CLASS_RULE = 066 HBaseClassTestRule.forClass(TestVisibilityLabelsWithACL.class); 067 068 private static final String PRIVATE = "private"; 069 private static final String CONFIDENTIAL = "confidential"; 070 private static final String SECRET = "secret"; 071 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 072 private static final byte[] row1 = Bytes.toBytes("row1"); 073 private final static byte[] fam = Bytes.toBytes("info"); 074 private final static byte[] qual = Bytes.toBytes("qual"); 075 private final static byte[] value = Bytes.toBytes("value"); 076 private static Configuration conf; 077 078 @Rule 079 public final TestName TEST_NAME = new TestName(); 080 private static User SUPERUSER; 081 private static User NORMAL_USER1; 082 private static User NORMAL_USER2; 083 084 @BeforeClass 085 public static void setupBeforeClass() throws Exception { 086 // setup configuration 087 conf = TEST_UTIL.getConfiguration(); 088 SecureTestUtil.enableSecurity(conf); 089 conf.set("hbase.coprocessor.master.classes", 090 AccessController.class.getName() + "," + VisibilityController.class.getName()); 091 conf.set("hbase.coprocessor.region.classes", 092 AccessController.class.getName() + "," + VisibilityController.class.getName()); 093 TEST_UTIL.startMiniCluster(2); 094 095 TEST_UTIL.waitTableEnabled(PermissionStorage.ACL_TABLE_NAME.getName(), 50000); 096 // Wait for the labels table to become available 097 TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000); 098 addLabels(); 099 100 // Create users for testing 101 SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" }); 102 NORMAL_USER1 = User.createUserForTesting(conf, "user1", new String[] {}); 103 NORMAL_USER2 = User.createUserForTesting(conf, "user2", new String[] {}); 104 // Grant users EXEC privilege on the labels table. For the purposes of this 105 // test, we want to insure that access is denied even with the ability to access 106 // the endpoint. 107 SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER1.getShortName(), LABELS_TABLE_NAME, null, 108 null, Permission.Action.EXEC); 109 SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), LABELS_TABLE_NAME, null, 110 null, Permission.Action.EXEC); 111 } 112 113 @AfterClass 114 public static void tearDownAfterClass() throws Exception { 115 TEST_UTIL.shutdownMiniCluster(); 116 } 117 118 @Test 119 public void testScanForUserWithFewerLabelAuthsThanLabelsInScanAuthorizations() throws Throwable { 120 String[] auths = { SECRET }; 121 String user = "user2"; 122 VisibilityClient.setAuths(TEST_UTIL.getConnection(), auths, user); 123 TableName tableName = TableName.valueOf(TEST_NAME.getMethodName()); 124 final Table table = createTableAndWriteDataWithLabels(tableName, 125 SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE); 126 SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), tableName, null, null, 127 Permission.Action.READ); 128 PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() { 129 @Override 130 public Void run() throws Exception { 131 Scan s = new Scan(); 132 s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); 133 try (Connection connection = ConnectionFactory.createConnection(conf); 134 Table t = connection.getTable(table.getName())) { 135 ResultScanner scanner = t.getScanner(s); 136 Result result = scanner.next(); 137 assertTrue(!result.isEmpty()); 138 assertTrue(Bytes.equals(Bytes.toBytes("row2"), result.getRow())); 139 result = scanner.next(); 140 assertNull(result); 141 } 142 return null; 143 } 144 }; 145 NORMAL_USER2.runAs(scanAction); 146 } 147 148 @Test 149 public void testScanForSuperUserWithFewerLabelAuths() throws Throwable { 150 String[] auths = { SECRET }; 151 String user = "admin"; 152 try (Connection conn = ConnectionFactory.createConnection(conf)) { 153 VisibilityClient.setAuths(conn, auths, user); 154 } 155 TableName tableName = TableName.valueOf(TEST_NAME.getMethodName()); 156 final Table table = createTableAndWriteDataWithLabels(tableName, 157 SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE); 158 PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() { 159 @Override 160 public Void run() throws Exception { 161 Scan s = new Scan(); 162 s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); 163 try (Connection connection = ConnectionFactory.createConnection(conf); 164 Table t = connection.getTable(table.getName())) { 165 ResultScanner scanner = t.getScanner(s); 166 Result[] result = scanner.next(5); 167 assertTrue(result.length == 2); 168 } 169 return null; 170 } 171 }; 172 SUPERUSER.runAs(scanAction); 173 } 174 175 @Test 176 public void testGetForSuperUserWithFewerLabelAuths() throws Throwable { 177 String[] auths = { SECRET }; 178 String user = "admin"; 179 VisibilityClient.setAuths(TEST_UTIL.getConnection(), auths, user); 180 TableName tableName = TableName.valueOf(TEST_NAME.getMethodName()); 181 final Table table = createTableAndWriteDataWithLabels(tableName, 182 SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE); 183 PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() { 184 @Override 185 public Void run() throws Exception { 186 Get g = new Get(row1); 187 g.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); 188 try (Connection connection = ConnectionFactory.createConnection(conf); 189 Table t = connection.getTable(table.getName())) { 190 Result result = t.get(g); 191 assertTrue(!result.isEmpty()); 192 } 193 return null; 194 } 195 }; 196 SUPERUSER.runAs(scanAction); 197 } 198 199 @Test 200 public void testVisibilityLabelsForUserWithNoAuths() throws Throwable { 201 String user = "admin"; 202 String[] auths = { SECRET }; 203 try (Connection conn = ConnectionFactory.createConnection(conf)) { 204 VisibilityClient.clearAuths(conn, auths, user); // Removing all auths if any. 205 VisibilityClient.setAuths(conn, auths, "user1"); 206 } 207 TableName tableName = TableName.valueOf(TEST_NAME.getMethodName()); 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}