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.AccessControlLists;
047import org.apache.hadoop.hbase.security.access.AccessController;
048import org.apache.hadoop.hbase.security.access.Permission;
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", AccessController.class.getName() + ","
090        + VisibilityController.class.getName());
091    conf.set("hbase.coprocessor.region.classes", AccessController.class.getName() + ","
092        + VisibilityController.class.getName());
093    TEST_UTIL.startMiniCluster(2);
094
095    TEST_UTIL.waitTableEnabled(AccessControlLists.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,
108      null, null, Permission.Action.EXEC);
109    SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), LABELS_TABLE_NAME,
110      null, 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, SECRET + "&" + CONFIDENTIAL
125        + "&!" + PRIVATE, SECRET + "&!" + PRIVATE);
126    SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), tableName,
127      null, null, 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, SECRET + "&" + CONFIDENTIAL
157        + "&!" + 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, SECRET + "&" + CONFIDENTIAL
182        + "&!" + 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,
210      null, null, Permission.Action.READ);
211    SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), tableName,
212      null, null, 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", response
244        .getResult(0).getException().getName());
245    assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", response
246        .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", response
260        .getResult(0).getException().getName());
261    assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", response
262        .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[] {
283              CONFIDENTIAL, PRIVATE }, "user1");
284        } catch (Throwable e) {
285        }
286        return null;
287      }
288    };
289    response = NORMAL_USER1.runAs(action);
290    assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", response.getResult(0)
291        .getException().getName());
292    assertEquals("org.apache.hadoop.hbase.security.AccessDeniedException", response.getResult(1)
293        .getException().getName());
294
295    response = VisibilityClient.clearAuths(TEST_UTIL.getConnection(), new String[] { CONFIDENTIAL,
296      PRIVATE }, "user1");
297    assertTrue(response.getResult(0).getException().getValue().isEmpty());
298    assertTrue(response.getResult(1).getException().getValue().isEmpty());
299
300    VisibilityClient.setAuths(TEST_UTIL.getConnection(), new String[] { CONFIDENTIAL, PRIVATE },
301      "user3");
302    PrivilegedExceptionAction<GetAuthsResponse> action1 =
303        new PrivilegedExceptionAction<GetAuthsResponse>() {
304      @Override
305      public GetAuthsResponse run() throws Exception {
306        try (Connection conn = ConnectionFactory.createConnection(conf)) {
307          return VisibilityClient.getAuths(conn, "user3");
308        } catch (Throwable e) {
309        }
310        return null;
311      }
312    };
313    GetAuthsResponse authsResponse = NORMAL_USER1.runAs(action1);
314    assertNull(authsResponse);
315    authsResponse = SUPERUSER.runAs(action1);
316    List<String> authsList = new ArrayList<>(authsResponse.getAuthList().size());
317    for (ByteString authBS : authsResponse.getAuthList()) {
318      authsList.add(Bytes.toString(authBS.toByteArray()));
319    }
320    assertEquals(2, authsList.size());
321    assertTrue(authsList.contains(CONFIDENTIAL));
322    assertTrue(authsList.contains(PRIVATE));
323  }
324
325  private static Table createTableAndWriteDataWithLabels(TableName tableName, String... labelExps)
326      throws Exception {
327    Table table = null;
328    try {
329      table = TEST_UTIL.createTable(tableName, fam);
330      int i = 1;
331      List<Put> puts = new ArrayList<>(labelExps.length);
332      for (String labelExp : labelExps) {
333        Put put = new Put(Bytes.toBytes("row" + i));
334        put.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value);
335        put.setCellVisibility(new CellVisibility(labelExp));
336        puts.add(put);
337        i++;
338      }
339      table.put(puts);
340    } finally {
341      if (table != null) {
342        table.close();
343      }
344    }
345    return table;
346  }
347
348  private static void addLabels() throws IOException {
349    String[] labels = { SECRET, CONFIDENTIAL, PRIVATE };
350    try {
351      VisibilityClient.addLabels(TEST_UTIL.getConnection(), labels);
352    } catch (Throwable t) {
353      throw new IOException(t);
354    }
355  }
356}