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.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertNull;
023import static org.junit.jupiter.api.Assertions.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.HBaseTestingUtil;
031import org.apache.hadoop.hbase.HConstants;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.TableNameTestExtension;
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.jupiter.api.AfterAll;
051import org.junit.jupiter.api.BeforeAll;
052import org.junit.jupiter.api.Tag;
053import org.junit.jupiter.api.Test;
054import org.junit.jupiter.api.TestInfo;
055
056import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
057
058import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse;
059import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse;
060
061@Tag(SecurityTests.TAG)
062@Tag(MediumTests.TAG)
063public class TestVisibilityLabelsWithACL {
064
065  private static final String PRIVATE = "private";
066  private static final String CONFIDENTIAL = "confidential";
067  private static final String SECRET = "secret";
068  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
069  private static final byte[] row1 = Bytes.toBytes("row1");
070  private final static byte[] fam = Bytes.toBytes("info");
071  private final static byte[] qual = Bytes.toBytes("qual");
072  private final static byte[] value = Bytes.toBytes("value");
073  private static Configuration conf;
074
075  private static User SUPERUSER;
076  private static User NORMAL_USER1;
077  private static User NORMAL_USER2;
078
079  @BeforeAll
080  public static void setupBeforeClass() throws Exception {
081    // setup configuration
082    conf = TEST_UTIL.getConfiguration();
083    SecureTestUtil.enableSecurity(conf);
084    conf.set("hbase.coprocessor.master.classes",
085      AccessController.class.getName() + "," + VisibilityController.class.getName());
086    conf.set("hbase.coprocessor.region.classes",
087      AccessController.class.getName() + "," + VisibilityController.class.getName());
088    TEST_UTIL.startMiniCluster(2);
089
090    TEST_UTIL.waitTableEnabled(PermissionStorage.ACL_TABLE_NAME.getName(), 50000);
091    // Wait for the labels table to become available
092    TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000);
093    addLabels();
094
095    // Create users for testing
096    SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" });
097    NORMAL_USER1 = User.createUserForTesting(conf, "user1", new String[] {});
098    NORMAL_USER2 = User.createUserForTesting(conf, "user2", new String[] {});
099    // Grant users EXEC privilege on the labels table. For the purposes of this
100    // test, we want to insure that access is denied even with the ability to access
101    // the endpoint.
102    SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER1.getShortName(), LABELS_TABLE_NAME, null,
103      null, Permission.Action.EXEC);
104    SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), LABELS_TABLE_NAME, null,
105      null, Permission.Action.EXEC);
106  }
107
108  @AfterAll
109  public static void tearDownAfterClass() throws Exception {
110    TEST_UTIL.shutdownMiniCluster();
111  }
112
113  @Test
114  public void testScanForUserWithFewerLabelAuthsThanLabelsInScanAuthorizations(TestInfo testInfo)
115    throws Throwable {
116    String[] auths = { SECRET };
117    String user = "user2";
118    VisibilityClient.setAuths(TEST_UTIL.getConnection(), auths, user);
119    TableName tableName = TableName
120      .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName()));
121    final Table table = createTableAndWriteDataWithLabels(tableName,
122      SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE);
123    SecureTestUtil.grantOnTable(TEST_UTIL, NORMAL_USER2.getShortName(), tableName, null, null,
124      Permission.Action.READ);
125    PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() {
126      @Override
127      public Void run() throws Exception {
128        Scan s = new Scan();
129        s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL));
130        try (Connection connection = ConnectionFactory.createConnection(conf);
131          Table t = connection.getTable(table.getName())) {
132          ResultScanner scanner = t.getScanner(s);
133          Result result = scanner.next();
134          assertTrue(!result.isEmpty());
135          assertTrue(Bytes.equals(Bytes.toBytes("row2"), result.getRow()));
136          result = scanner.next();
137          assertNull(result);
138        }
139        return null;
140      }
141    };
142    NORMAL_USER2.runAs(scanAction);
143  }
144
145  @Test
146  public void testScanForSuperUserWithFewerLabelAuths(TestInfo testInfo) throws Throwable {
147    String[] auths = { SECRET };
148    String user = "admin";
149    try (Connection conn = ConnectionFactory.createConnection(conf)) {
150      VisibilityClient.setAuths(conn, auths, user);
151    }
152    TableName tableName = TableName
153      .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName()));
154    final Table table = createTableAndWriteDataWithLabels(tableName,
155      SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE);
156    PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() {
157      @Override
158      public Void run() throws Exception {
159        Scan s = new Scan();
160        s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL));
161        try (Connection connection = ConnectionFactory.createConnection(conf);
162          Table t = connection.getTable(table.getName())) {
163          ResultScanner scanner = t.getScanner(s);
164          Result[] result = scanner.next(5);
165          assertTrue(result.length == 2);
166        }
167        return null;
168      }
169    };
170    SUPERUSER.runAs(scanAction);
171  }
172
173  @Test
174  public void testGetForSuperUserWithFewerLabelAuths(TestInfo testInfo) throws Throwable {
175    String[] auths = { SECRET };
176    String user = "admin";
177    VisibilityClient.setAuths(TEST_UTIL.getConnection(), auths, user);
178    TableName tableName = TableName
179      .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName()));
180    final Table table = createTableAndWriteDataWithLabels(tableName,
181      SECRET + "&" + CONFIDENTIAL + "&!" + PRIVATE, SECRET + "&!" + PRIVATE);
182    PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() {
183      @Override
184      public Void run() throws Exception {
185        Get g = new Get(row1);
186        g.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL));
187        try (Connection connection = ConnectionFactory.createConnection(conf);
188          Table t = connection.getTable(table.getName())) {
189          Result result = t.get(g);
190          assertTrue(!result.isEmpty());
191        }
192        return null;
193      }
194    };
195    SUPERUSER.runAs(scanAction);
196  }
197
198  @Test
199  public void testVisibilityLabelsForUserWithNoAuths(TestInfo testInfo) throws Throwable {
200    String user = "admin";
201    String[] auths = { SECRET };
202    try (Connection conn = ConnectionFactory.createConnection(conf)) {
203      VisibilityClient.clearAuths(conn, auths, user); // Removing all auths if any.
204      VisibilityClient.setAuths(conn, auths, "user1");
205    }
206    TableName tableName = TableName
207      .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName()));
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}