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.access;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import java.security.PrivilegedExceptionAction;
025import java.util.ArrayList;
026import java.util.List;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
029import org.apache.hadoop.hbase.HBaseTestingUtility;
030import org.apache.hadoop.hbase.HConstants;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.Connection;
033import org.apache.hadoop.hbase.client.ConnectionFactory;
034import org.apache.hadoop.hbase.client.Put;
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.LargeTests;
041import org.apache.hadoop.hbase.testclassification.SecurityTests;
042import org.apache.hadoop.hbase.util.Bytes;
043import org.junit.AfterClass;
044import org.junit.Before;
045import org.junit.BeforeClass;
046import org.junit.ClassRule;
047import org.junit.Rule;
048import org.junit.Test;
049import org.junit.experimental.categories.Category;
050import org.junit.rules.TestName;
051
052@Category({SecurityTests.class, LargeTests.class})
053public class TestAccessControlFilter extends SecureTestUtil {
054
055  @ClassRule
056  public static final HBaseClassTestRule CLASS_RULE =
057      HBaseClassTestRule.forClass(TestAccessControlFilter.class);
058
059  @Rule public TestName name = new TestName();
060  private static HBaseTestingUtility TEST_UTIL;
061
062  private static User READER;
063  private static User LIMITED;
064  private static User DENIED;
065
066  private static TableName TABLE;
067  private static byte[] FAMILY = Bytes.toBytes("f1");
068  private static byte[] PRIVATE_COL = Bytes.toBytes("private");
069  private static byte[] PUBLIC_COL = Bytes.toBytes("public");
070
071  @Before
072  public void setup () {
073    TABLE = TableName.valueOf(name.getMethodName());
074  }
075
076  @BeforeClass
077  public static void setupBeforeClass() throws Exception {
078    TEST_UTIL = new HBaseTestingUtility();
079    Configuration conf = TEST_UTIL.getConfiguration();
080    // Up the handlers; this test needs more than usual.
081    conf.setInt(HConstants.REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT, 10);
082    enableSecurity(conf);
083    verifyConfiguration(conf);
084
085    // We expect 0.98 scanning semantics
086    conf.setBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, false);
087
088    TEST_UTIL.startMiniCluster();
089    TEST_UTIL.waitTableEnabled(AccessControlLists.ACL_TABLE_NAME.getName(), 50000);
090
091    READER = User.createUserForTesting(conf, "reader", new String[0]);
092    LIMITED = User.createUserForTesting(conf, "limited", new String[0]);
093    DENIED = User.createUserForTesting(conf, "denied", new String[0]);
094  }
095
096  @AfterClass
097  public static void tearDownAfterClass() throws Exception {
098    TEST_UTIL.shutdownMiniCluster();
099  }
100
101  @Test
102  public void testQualifierAccess() throws Exception {
103    final Table table = createTable(TEST_UTIL, TABLE, new byte[][] { FAMILY });
104    try {
105      doQualifierAccess(table);
106    } finally {
107      table.close();
108    }
109  }
110
111  private void doQualifierAccess(final Table table) throws Exception {
112    // set permissions
113    SecureTestUtil.grantOnTable(TEST_UTIL, READER.getShortName(), TABLE, null, null,
114      Permission.Action.READ);
115    SecureTestUtil.grantOnTable(TEST_UTIL, LIMITED.getShortName(), TABLE, FAMILY, PUBLIC_COL,
116      Permission.Action.READ);
117
118    // put some test data
119    List<Put> puts = new ArrayList<>(100);
120    for (int i=0; i<100; i++) {
121      Put p = new Put(Bytes.toBytes(i));
122      p.addColumn(FAMILY, PRIVATE_COL, Bytes.toBytes("secret " + i));
123      p.addColumn(FAMILY, PUBLIC_COL, Bytes.toBytes("info " + i));
124      puts.add(p);
125    }
126    table.put(puts);
127
128    // test read
129    READER.runAs(new PrivilegedExceptionAction<Object>() {
130      @Override
131      public Object run() throws Exception {
132        Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
133        // force a new RS connection
134        conf.set("testkey", TEST_UTIL.getRandomUUID().toString());
135        Connection connection = ConnectionFactory.createConnection(conf);
136        Table t = connection.getTable(TABLE);
137        try {
138          ResultScanner rs = t.getScanner(new Scan());
139          int rowcnt = 0;
140          for (Result r : rs) {
141            rowcnt++;
142            int rownum = Bytes.toInt(r.getRow());
143            assertTrue(r.containsColumn(FAMILY, PRIVATE_COL));
144            assertEquals("secret "+rownum, Bytes.toString(r.getValue(FAMILY, PRIVATE_COL)));
145            assertTrue(r.containsColumn(FAMILY, PUBLIC_COL));
146            assertEquals("info "+rownum, Bytes.toString(r.getValue(FAMILY, PUBLIC_COL)));
147          }
148          assertEquals("Expected 100 rows returned", 100, rowcnt);
149          return null;
150        } finally {
151          t.close();
152          connection.close();
153        }
154      }
155    });
156
157    // test read with qualifier filter
158    LIMITED.runAs(new PrivilegedExceptionAction<Object>() {
159      @Override
160      public Object run() throws Exception {
161        Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
162        // force a new RS connection
163        conf.set("testkey", TEST_UTIL.getRandomUUID().toString());
164        Connection connection = ConnectionFactory.createConnection(conf);
165        Table t = connection.getTable(TABLE);
166        try {
167          ResultScanner rs = t.getScanner(new Scan());
168          int rowcnt = 0;
169          for (Result r : rs) {
170            rowcnt++;
171            int rownum = Bytes.toInt(r.getRow());
172            assertFalse(r.containsColumn(FAMILY, PRIVATE_COL));
173            assertTrue(r.containsColumn(FAMILY, PUBLIC_COL));
174            assertEquals("info " + rownum, Bytes.toString(r.getValue(FAMILY, PUBLIC_COL)));
175          }
176          assertEquals("Expected 100 rows returned", 100, rowcnt);
177          return null;
178        } finally {
179          t.close();
180          connection.close();
181        }
182      }
183    });
184
185    // test as user with no permission
186    DENIED.runAs(new PrivilegedExceptionAction<Object>(){
187      @Override
188      public Object run() throws Exception {
189        Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
190        // force a new RS connection
191        conf.set("testkey", TEST_UTIL.getRandomUUID().toString());
192        Connection connection = ConnectionFactory.createConnection(conf);
193        Table t = connection.getTable(TABLE);
194        try {
195          ResultScanner rs = t.getScanner(new Scan());
196          int rowcnt = 0;
197          for (Result r : rs) {
198            rowcnt++;
199            int rownum = Bytes.toInt(r.getRow());
200            assertFalse(r.containsColumn(FAMILY, PRIVATE_COL));
201            assertTrue(r.containsColumn(FAMILY, PUBLIC_COL));
202            assertEquals("info " + rownum, Bytes.toString(r.getValue(FAMILY, PUBLIC_COL)));
203          }
204          assertEquals("Expected 0 rows returned", 0, rowcnt);
205          return null;
206        } finally {
207          t.close();
208          connection.close();
209        }
210      }
211    });
212  }
213}