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}