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