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.assertFalse;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024import static org.junit.jupiter.api.Assertions.fail;
025
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.Cell;
032import org.apache.hadoop.hbase.CellScanner;
033import org.apache.hadoop.hbase.HBaseTestingUtil;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.TableName;
036import org.apache.hadoop.hbase.client.Connection;
037import org.apache.hadoop.hbase.client.ConnectionFactory;
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.security.User;
044import org.apache.hadoop.hbase.testclassification.MediumTests;
045import org.apache.hadoop.hbase.testclassification.SecurityTests;
046import org.apache.hadoop.hbase.util.Bytes;
047import org.junit.jupiter.api.AfterAll;
048import org.junit.jupiter.api.BeforeAll;
049import org.junit.jupiter.api.Tag;
050import org.junit.jupiter.api.Test;
051import org.junit.jupiter.api.TestInfo;
052
053import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
054
055import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse;
056import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse;
057
058@Tag(SecurityTests.TAG)
059@Tag(MediumTests.TAG)
060public class TestVisibilityLablesWithGroups {
061
062  public static final String CONFIDENTIAL = "confidential";
063  private static final String SECRET = "secret";
064  public static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
065  private static final byte[] ROW_1 = Bytes.toBytes("row1");
066  private final static byte[] CF = Bytes.toBytes("f");
067  private final static byte[] Q1 = Bytes.toBytes("q1");
068  private final static byte[] Q2 = Bytes.toBytes("q2");
069  private final static byte[] Q3 = Bytes.toBytes("q3");
070  private final static byte[] value1 = Bytes.toBytes("value1");
071  private final static byte[] value2 = Bytes.toBytes("value2");
072  private final static byte[] value3 = Bytes.toBytes("value3");
073  public static Configuration conf;
074
075  public static User SUPERUSER;
076  public static User TESTUSER;
077
078  @BeforeAll
079  public static void setupBeforeClass() throws Exception {
080    // setup configuration
081    conf = TEST_UTIL.getConfiguration();
082    VisibilityTestUtil.enableVisiblityLabels(conf);
083    // Not setting any SLG class. This means to use the default behavior.
084    // Use a group as the super user.
085    conf.set("hbase.superuser", "@supergroup");
086    TEST_UTIL.startMiniCluster(1);
087    // 'admin' has super user permission because it is part of the 'supergroup'
088    SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" });
089    // 'test' user will inherit 'testgroup' visibility labels
090    TESTUSER = User.createUserForTesting(conf, "test", new String[] { "testgroup" });
091
092    // Wait for the labels table to become available
093    TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000);
094
095    // Set up for the test
096    SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
097      @Override
098      public Void run() throws Exception {
099        try (Connection conn = ConnectionFactory.createConnection(conf)) {
100          VisibilityClient.addLabels(conn, new String[] { SECRET, CONFIDENTIAL });
101          // set auth for @testgroup
102          VisibilityClient.setAuths(conn, new String[] { CONFIDENTIAL }, "@testgroup");
103        } catch (Throwable t) {
104          throw new IOException(t);
105        }
106        return null;
107      }
108    });
109  }
110
111  @Test
112  public void testGroupAuths(TestInfo testInfo) throws Exception {
113    final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName());
114    // create the table
115    TEST_UTIL.createTable(tableName, CF);
116    // put the data.
117    SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
118      @Override
119      public Void run() throws Exception {
120        try (Connection connection = ConnectionFactory.createConnection(conf);
121          Table table = connection.getTable(tableName)) {
122          Put put = new Put(ROW_1);
123          put.addColumn(CF, Q1, HConstants.LATEST_TIMESTAMP, value1);
124          put.setCellVisibility(new CellVisibility(SECRET));
125          table.put(put);
126          put = new Put(ROW_1);
127          put.addColumn(CF, Q2, HConstants.LATEST_TIMESTAMP, value2);
128          put.setCellVisibility(new CellVisibility(CONFIDENTIAL));
129          table.put(put);
130          put = new Put(ROW_1);
131          put.addColumn(CF, Q3, HConstants.LATEST_TIMESTAMP, value3);
132          table.put(put);
133        }
134        return null;
135      }
136    });
137
138    // 'admin' user is part of 'supergroup', thus can see all the cells.
139    SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
140      @Override
141      public Void run() throws Exception {
142        try (Connection connection = ConnectionFactory.createConnection(conf);
143          Table table = connection.getTable(tableName)) {
144          Scan s = new Scan();
145          ResultScanner scanner = table.getScanner(s);
146          Result[] next = scanner.next(1);
147
148          // Test that super user can see all the cells.
149          assertTrue(next.length == 1);
150          CellScanner cellScanner = next[0].cellScanner();
151          cellScanner.advance();
152          Cell current = cellScanner.current();
153          assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
154            current.getRowLength(), ROW_1, 0, ROW_1.length));
155          assertTrue(Bytes.equals(current.getQualifierArray(), current.getQualifierOffset(),
156            current.getQualifierLength(), Q1, 0, Q1.length));
157          assertTrue(Bytes.equals(current.getValueArray(), current.getValueOffset(),
158            current.getValueLength(), value1, 0, value1.length));
159          cellScanner.advance();
160          current = cellScanner.current();
161          assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
162            current.getRowLength(), ROW_1, 0, ROW_1.length));
163          assertTrue(Bytes.equals(current.getQualifierArray(), current.getQualifierOffset(),
164            current.getQualifierLength(), Q2, 0, Q2.length));
165          assertTrue(Bytes.equals(current.getValueArray(), current.getValueOffset(),
166            current.getValueLength(), value2, 0, value2.length));
167          cellScanner.advance();
168          current = cellScanner.current();
169          assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
170            current.getRowLength(), ROW_1, 0, ROW_1.length));
171          assertTrue(Bytes.equals(current.getQualifierArray(), current.getQualifierOffset(),
172            current.getQualifierLength(), Q3, 0, Q3.length));
173          assertTrue(Bytes.equals(current.getValueArray(), current.getValueOffset(),
174            current.getValueLength(), value3, 0, value3.length));
175        }
176        return null;
177      }
178    });
179
180    // Get testgroup's labels.
181    SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
182      @Override
183      public Void run() throws Exception {
184        GetAuthsResponse authsResponse = null;
185        try (Connection conn = ConnectionFactory.createConnection(conf)) {
186          authsResponse = VisibilityClient.getAuths(conn, "@testgroup");
187        } catch (Throwable e) {
188          fail("Should not have failed");
189        }
190        List<String> authsList = new ArrayList<>(authsResponse.getAuthList().size());
191        for (ByteString authBS : authsResponse.getAuthList()) {
192          authsList.add(Bytes.toString(authBS.toByteArray()));
193        }
194        assertEquals(1, authsList.size());
195        assertTrue(authsList.contains(CONFIDENTIAL));
196        return null;
197      }
198    });
199
200    // Test that test user can see what 'testgroup' has been authorized to.
201    TESTUSER.runAs(new PrivilegedExceptionAction<Void>() {
202      @Override
203      public Void run() throws Exception {
204        try (Connection connection = ConnectionFactory.createConnection(conf);
205          Table table = connection.getTable(tableName)) {
206          // Test scan with no auth attribute
207          Scan s = new Scan();
208          ResultScanner scanner = table.getScanner(s);
209          Result[] next = scanner.next(1);
210
211          assertTrue(next.length == 1);
212          CellScanner cellScanner = next[0].cellScanner();
213          cellScanner.advance();
214          Cell current = cellScanner.current();
215          // test user can see value2 (CONFIDENTIAL) and value3 (no label)
216          assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
217            current.getRowLength(), ROW_1, 0, ROW_1.length));
218          assertTrue(Bytes.equals(current.getQualifierArray(), current.getQualifierOffset(),
219            current.getQualifierLength(), Q2, 0, Q2.length));
220          assertTrue(Bytes.equals(current.getValueArray(), current.getValueOffset(),
221            current.getValueLength(), value2, 0, value2.length));
222          cellScanner.advance();
223          current = cellScanner.current();
224          // test user can see value2 (CONFIDENTIAL) and value3 (no label)
225          assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
226            current.getRowLength(), ROW_1, 0, ROW_1.length));
227          assertTrue(Bytes.equals(current.getQualifierArray(), current.getQualifierOffset(),
228            current.getQualifierLength(), Q3, 0, Q3.length));
229          assertTrue(Bytes.equals(current.getValueArray(), current.getValueOffset(),
230            current.getValueLength(), value3, 0, value3.length));
231
232          // Test scan with correct auth attribute for test user
233          Scan s1 = new Scan();
234          // test user is entitled to 'CONFIDENTIAL'.
235          // If we set both labels in the scan, 'SECRET' will be dropped by the SLGs.
236          s1.setAuthorizations(new Authorizations(new String[] { SECRET, CONFIDENTIAL }));
237          ResultScanner scanner1 = table.getScanner(s1);
238          Result[] next1 = scanner1.next(1);
239
240          assertTrue(next1.length == 1);
241          CellScanner cellScanner1 = next1[0].cellScanner();
242          cellScanner1.advance();
243          Cell current1 = cellScanner1.current();
244          // test user can see value2 (CONFIDENTIAL) and value3 (no label)
245          assertTrue(Bytes.equals(current1.getRowArray(), current1.getRowOffset(),
246            current1.getRowLength(), ROW_1, 0, ROW_1.length));
247          assertTrue(Bytes.equals(current1.getQualifierArray(), current1.getQualifierOffset(),
248            current1.getQualifierLength(), Q2, 0, Q2.length));
249          assertTrue(Bytes.equals(current1.getValueArray(), current1.getValueOffset(),
250            current1.getValueLength(), value2, 0, value2.length));
251          cellScanner1.advance();
252          current1 = cellScanner1.current();
253          // test user can see value2 (CONFIDENTIAL) and value3 (no label)
254          assertTrue(Bytes.equals(current1.getRowArray(), current1.getRowOffset(),
255            current1.getRowLength(), ROW_1, 0, ROW_1.length));
256          assertTrue(Bytes.equals(current1.getQualifierArray(), current1.getQualifierOffset(),
257            current1.getQualifierLength(), Q3, 0, Q3.length));
258          assertTrue(Bytes.equals(current1.getValueArray(), current1.getValueOffset(),
259            current1.getValueLength(), value3, 0, value3.length));
260
261          // Test scan with incorrect auth attribute for test user
262          Scan s2 = new Scan();
263          // test user is entitled to 'CONFIDENTIAL'.
264          // If we set 'SECRET', it will be dropped by the SLGs.
265          s2.setAuthorizations(new Authorizations(new String[] { SECRET }));
266          ResultScanner scanner2 = table.getScanner(s2);
267          Result next2 = scanner2.next();
268          CellScanner cellScanner2 = next2.cellScanner();
269          cellScanner2.advance();
270          Cell current2 = cellScanner2.current();
271          // This scan will only see value3 (no label)
272          assertTrue(Bytes.equals(current2.getRowArray(), current2.getRowOffset(),
273            current2.getRowLength(), ROW_1, 0, ROW_1.length));
274          assertTrue(Bytes.equals(current2.getQualifierArray(), current2.getQualifierOffset(),
275            current2.getQualifierLength(), Q3, 0, Q3.length));
276          assertTrue(Bytes.equals(current2.getValueArray(), current2.getValueOffset(),
277            current2.getValueLength(), value3, 0, value3.length));
278
279          assertFalse(cellScanner2.advance());
280        }
281        return null;
282      }
283    });
284
285    // Clear 'testgroup' of CONFIDENTIAL label.
286    SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
287      @Override
288      public Void run() throws Exception {
289        VisibilityLabelsResponse response = null;
290        try (Connection conn = ConnectionFactory.createConnection(conf)) {
291          response = VisibilityClient.clearAuths(conn, new String[] { CONFIDENTIAL }, "@testgroup");
292        } catch (Throwable e) {
293          fail("Should not have failed");
294        }
295        return null;
296      }
297    });
298
299    // Get testgroup's labels. No label is returned.
300    SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
301      @Override
302      public Void run() throws Exception {
303        GetAuthsResponse authsResponse = null;
304        try (Connection conn = ConnectionFactory.createConnection(conf)) {
305          authsResponse = VisibilityClient.getAuths(conn, "@testgroup");
306        } catch (Throwable e) {
307          fail("Should not have failed");
308        }
309        List<String> authsList = new ArrayList<>(authsResponse.getAuthList().size());
310        for (ByteString authBS : authsResponse.getAuthList()) {
311          authsList.add(Bytes.toString(authBS.toByteArray()));
312        }
313        assertEquals(0, authsList.size());
314        return null;
315      }
316    });
317
318    // Test that test user cannot see the cells with the labels anymore.
319    TESTUSER.runAs(new PrivilegedExceptionAction<Void>() {
320      @Override
321      public Void run() throws Exception {
322        try (Connection connection = ConnectionFactory.createConnection(conf);
323          Table table = connection.getTable(tableName)) {
324          Scan s1 = new Scan();
325          // test user is not entitled to 'CONFIDENTIAL' anymore since we dropped
326          // testgroup's label. test user has no auth labels now.
327          // scan's labels will be dropped on the server side.
328          s1.setAuthorizations(new Authorizations(new String[] { SECRET, CONFIDENTIAL }));
329          ResultScanner scanner1 = table.getScanner(s1);
330          Result[] next1 = scanner1.next(1);
331
332          assertTrue(next1.length == 1);
333          CellScanner cellScanner1 = next1[0].cellScanner();
334          cellScanner1.advance();
335          Cell current1 = cellScanner1.current();
336          // test user can only see value3 (no label)
337          assertTrue(Bytes.equals(current1.getRowArray(), current1.getRowOffset(),
338            current1.getRowLength(), ROW_1, 0, ROW_1.length));
339          assertTrue(Bytes.equals(current1.getQualifierArray(), current1.getQualifierOffset(),
340            current1.getQualifierLength(), Q3, 0, Q3.length));
341          assertTrue(Bytes.equals(current1.getValueArray(), current1.getValueOffset(),
342            current1.getValueLength(), value3, 0, value3.length));
343
344          assertFalse(cellScanner1.advance());
345        }
346        return null;
347      }
348    });
349
350  }
351
352  @AfterAll
353  public static void tearDownAfterClass() throws Exception {
354    TEST_UTIL.shutdownMiniCluster();
355  }
356}