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