Return-Path: X-Original-To: apmail-hbase-commits-archive@www.apache.org Delivered-To: apmail-hbase-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B0B5210B19 for ; Thu, 1 May 2014 01:05:00 +0000 (UTC) Received: (qmail 72998 invoked by uid 500); 1 May 2014 01:04:59 -0000 Delivered-To: apmail-hbase-commits-archive@hbase.apache.org Received: (qmail 72896 invoked by uid 500); 1 May 2014 01:04:59 -0000 Mailing-List: contact commits-help@hbase.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@hbase.apache.org Delivered-To: mailing list commits@hbase.apache.org Received: (qmail 72889 invoked by uid 99); 1 May 2014 01:04:59 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 May 2014 01:04:59 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 May 2014 01:04:57 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 48A272388994; Thu, 1 May 2014 01:04:37 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1591524 [2/2] - in /hbase/trunk: hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/ hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/ hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/ Date: Thu, 01 May 2014 01:04:36 -0000 To: commits@hbase.apache.org From: apurtell@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20140501010437.48A272388994@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: hbase/trunk/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestScanEarlyTermination.java URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestScanEarlyTermination.java?rev=1591524&view=auto ============================================================================== --- hbase/trunk/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestScanEarlyTermination.java (added) +++ hbase/trunk/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestScanEarlyTermination.java Thu May 1 01:04:36 2014 @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.access; + +import static org.junit.Assert.*; + +import java.util.UUID; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.Coprocessor; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.MediumTests; +import org.apache.hadoop.hbase.TableNotFoundException; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.master.MasterCoprocessorHost; +import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.security.access.Permission.Action; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.TestTableName; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(MediumTests.class) +public class TestScanEarlyTermination extends SecureTestUtil { + private static final Log LOG = LogFactory.getLog(TestScanEarlyTermination.class); + + static { + Logger.getLogger(AccessController.class).setLevel(Level.TRACE); + Logger.getLogger(AccessControlFilter.class).setLevel(Level.TRACE); + Logger.getLogger(TableAuthManager.class).setLevel(Level.TRACE); + } + + @Rule + public TestTableName TEST_TABLE = new TestTableName(); + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + private static final byte[] TEST_FAMILY1 = Bytes.toBytes("f1"); + private static final byte[] TEST_FAMILY2 = Bytes.toBytes("f2"); + private static final byte[] TEST_ROW = Bytes.toBytes("testrow"); + private static final byte[] TEST_Q1 = Bytes.toBytes("q1"); + private static final byte[] TEST_Q2 = Bytes.toBytes("q2"); + private static final byte[] ZERO = Bytes.toBytes(0L); + + private static Configuration conf; + + private static User USER_OWNER; + private static User USER_OTHER; + + @BeforeClass + public static void setupBeforeClass() throws Exception { + // setup configuration + conf = TEST_UTIL.getConfiguration(); + // Enable security + enableSecurity(conf); + // Verify enableSecurity sets up what we require + verifyConfiguration(conf); + + TEST_UTIL.startMiniCluster(); + MasterCoprocessorHost cpHost = TEST_UTIL.getMiniHBaseCluster().getMaster() + .getMasterCoprocessorHost(); + cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf); + AccessController ac = (AccessController) + cpHost.findCoprocessor(AccessController.class.getName()); + cpHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf); + RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0) + .getRegionServerCoprocessorHost(); + rsHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf); + + // Wait for the ACL table to become available + TEST_UTIL.waitTableEnabled(AccessControlLists.ACL_TABLE_NAME.getName()); + + // create a set of test users + USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]); + USER_OTHER = User.createUserForTesting(conf, "other", new String[0]); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + TEST_UTIL.shutdownMiniCluster(); + } + + @Before + public void setUp() throws Exception { + HBaseAdmin admin = TEST_UTIL.getHBaseAdmin(); + HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); + htd.setOwner(USER_OWNER); + HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY1); + hcd.setMaxVersions(10); + htd.addFamily(hcd); + hcd = new HColumnDescriptor(TEST_FAMILY2); + hcd.setMaxVersions(10); + htd.addFamily(hcd); + + // Enable backwards compatible early termination behavior in the HTD. We + // want to confirm that the per-table configuration is properly picked up. + htd.setConfiguration(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, "true"); + + admin.createTable(htd); + + TEST_UTIL.waitTableEnabled(TEST_TABLE.getTableName().getName()); + } + + @After + public void tearDown() throws Exception { + // Clean the _acl_ table + try { + TEST_UTIL.deleteTable(TEST_TABLE.getTableName()); + } catch (TableNotFoundException ex) { + // Test deleted the table, no problem + LOG.info("Test deleted table " + TEST_TABLE.getTableName()); + } + assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE.getTableName()).size()); + } + + @Test + public void testEarlyScanTermination() throws Exception { + // Grant USER_OTHER access to TEST_FAMILY1 only + grantOnTable(TEST_UTIL, USER_OTHER.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY1, + null, Action.READ); + + // Set up test data + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + // force a new RS connection + conf.set("testkey", UUID.randomUUID().toString()); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + Put put = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO); + t.put(put); + // Set a READ cell ACL for USER_OTHER on this value in FAMILY2 + put = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q1, ZERO); + put.setACL(USER_OTHER.getShortName(), new Permission(Action.READ)); + t.put(put); + // Set an empty cell ACL for USER_OTHER on this other value in FAMILY2 + put = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q2, ZERO); + put.setACL(USER_OTHER.getShortName(), new Permission()); + t.put(put); + } finally { + t.close(); + } + return null; + } + }, USER_OWNER); + + // A scan of FAMILY1 will be allowed + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + // force a new RS connection + conf.set("testkey", UUID.randomUUID().toString()); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + Scan scan = new Scan().addFamily(TEST_FAMILY1); + Result result = t.getScanner(scan).next(); + if (result != null) { + assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1)); + assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1)); + return result.listCells(); + } + return null; + } finally { + t.close(); + } + } + }, USER_OTHER); + + // A scan of FAMILY1 and FAMILY2 will produce results for FAMILY1 without + // throwing an exception, however no cells from FAMILY2 will be returned + // because we early out checks at the CF level. + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + // force a new RS connection + conf.set("testkey", UUID.randomUUID().toString()); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + Scan scan = new Scan(); + Result result = t.getScanner(scan).next(); + if (result != null) { + assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1)); + assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1)); + return result.listCells(); + } + return null; + } finally { + t.close(); + } + } + }, USER_OTHER); + + // A scan of FAMILY2 will throw an AccessDeniedException + verifyDeniedWithException(new AccessTestAction() { + @Override + public Object run() throws Exception { + // force a new RS connection + conf.set("testkey", UUID.randomUUID().toString()); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + Scan scan = new Scan().addFamily(TEST_FAMILY2); + Result result = t.getScanner(scan).next(); + if (result != null) { + return result.listCells(); + } + return null; + } finally { + t.close(); + } + } + }, USER_OTHER); + + // Now grant USER_OTHER access to TEST_FAMILY2:TEST_Q2 + grantOnTable(TEST_UTIL, USER_OTHER.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY2, + TEST_Q2, Action.READ); + + // A scan of FAMILY1 and FAMILY2 will produce combined results. In FAMILY2 + // we have access granted to Q2 at the CF level. Because we early out + // checks at the CF level the cell ACL on Q1 also granting access is ignored. + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + // force a new RS connection + conf.set("testkey", UUID.randomUUID().toString()); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + Scan scan = new Scan(); + Result result = t.getScanner(scan).next(); + if (result != null) { + assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1)); + assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1)); + assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY2, TEST_Q2)); + return result.listCells(); + } + return null; + } finally { + t.close(); + } + } + }, USER_OTHER); + + // A scan of FAMILY1 and FAMILY2 will produce combined results. If we use + // a cell first strategy then cell ACLs come into effect. In FAMILY2, that + // cell ACL on Q1 now grants access and the empty permission set on Q2 now + // denies access. + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + // force a new RS connection + conf.set("testkey", UUID.randomUUID().toString()); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + Scan scan = new Scan(); + scan.setACLStrategy(true); + Result result = t.getScanner(scan).next(); + if (result != null) { + assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1)); + assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1)); + assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q2)); + return result.listCells(); + } + return null; + } finally { + t.close(); + } + } + }, USER_OTHER); + + } +}