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.apache.hadoop.hbase.security.visibility.VisibilityUtils.SYSTEM_LABEL;
022import static org.junit.jupiter.api.Assertions.assertEquals;
023import static org.junit.jupiter.api.Assertions.assertFalse;
024import static org.junit.jupiter.api.Assertions.assertThrows;
025import static org.junit.jupiter.api.Assertions.assertTrue;
026
027import java.io.IOException;
028import java.security.PrivilegedExceptionAction;
029import java.util.List;
030import java.util.concurrent.atomic.AtomicBoolean;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.TableNameTestExtension;
033import org.apache.hadoop.hbase.client.Connection;
034import org.apache.hadoop.hbase.client.ConnectionFactory;
035import org.apache.hadoop.hbase.client.Result;
036import org.apache.hadoop.hbase.client.ResultScanner;
037import org.apache.hadoop.hbase.client.Scan;
038import org.apache.hadoop.hbase.client.Table;
039import org.apache.hadoop.hbase.security.User;
040import org.apache.hadoop.hbase.testclassification.MediumTests;
041import org.apache.hadoop.hbase.testclassification.SecurityTests;
042import org.apache.hadoop.hbase.util.Bytes;
043import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
044import org.apache.hadoop.hbase.util.Threads;
045import org.junit.jupiter.api.BeforeAll;
046import org.junit.jupiter.api.Tag;
047import org.junit.jupiter.api.Test;
048import org.junit.jupiter.api.TestInfo;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
053
054import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
055import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.RegionActionResult;
056import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.NameBytesPair;
057import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.ListLabelsResponse;
058import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse;
059
060@Tag(SecurityTests.TAG)
061@Tag(MediumTests.TAG)
062public class TestVisibilityLabelsWithDefaultVisLabelService extends VisibilityLabelsTestBase {
063
064  private static final Logger LOG =
065    LoggerFactory.getLogger(TestVisibilityLabelsWithDefaultVisLabelService.class);
066
067  @BeforeAll
068  public static void setupBeforeClass() throws Exception {
069    // setup configuration
070    conf = TEST_UTIL.getConfiguration();
071    VisibilityTestUtil.enableVisiblityLabels(conf);
072    conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class,
073      ScanLabelGenerator.class);
074    conf.set("hbase.superuser", "admin");
075    TEST_UTIL.startMiniCluster(2);
076    SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" });
077    USER1 = User.createUserForTesting(conf, "user1", new String[] {});
078
079    // Wait for the labels table to become available
080    TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000);
081    addLabels();
082  }
083
084  @Test
085  public void testVisibilityLabelsInPutsThatDoesNotMatchAnyDefinedLabels() throws Exception {
086    TableName tableName = name.getTableName();
087    assertThrows(Exception.class,
088      () -> createTableAndWriteDataWithLabels(tableName, "SAMPLE_LABEL", "TEST"),
089      "Should have failed with failed sanity check exception");
090  }
091
092  @Test
093  public void testAddLabels() throws Throwable {
094    PrivilegedExceptionAction<VisibilityLabelsResponse> action =
095      new PrivilegedExceptionAction<VisibilityLabelsResponse>() {
096        @Override
097        public VisibilityLabelsResponse run() throws Exception {
098          String[] labels = { "L1", SECRET, "L2", "invalid~", "L3" };
099          VisibilityLabelsResponse response = null;
100          try (Connection conn = ConnectionFactory.createConnection(conf)) {
101            response = VisibilityClient.addLabels(conn, labels);
102          } catch (Throwable e) {
103            throw new IOException(e);
104          }
105          List<RegionActionResult> resultList = response.getResultList();
106          assertEquals(5, resultList.size());
107          assertTrue(resultList.get(0).getException().getValue().isEmpty());
108          assertEquals("org.apache.hadoop.hbase.DoNotRetryIOException",
109            resultList.get(1).getException().getName());
110          assertTrue(Bytes.toString(resultList.get(1).getException().getValue().toByteArray())
111            .contains("org.apache.hadoop.hbase.security.visibility.LabelAlreadyExistsException: "
112              + "Label 'secret' already exists"));
113          assertTrue(resultList.get(2).getException().getValue().isEmpty());
114          assertTrue(resultList.get(3).getException().getValue().isEmpty());
115          assertTrue(resultList.get(4).getException().getValue().isEmpty());
116          return null;
117        }
118      };
119    SUPERUSER.runAs(action);
120  }
121
122  @Test
123  public void testAddVisibilityLabelsOnRSRestart() throws Exception {
124    List<RegionServerThread> regionServerThreads =
125      TEST_UTIL.getHBaseCluster().getRegionServerThreads();
126    for (RegionServerThread rsThread : regionServerThreads) {
127      rsThread.getRegionServer().abort("Aborting ");
128    }
129    // Start one new RS
130    RegionServerThread rs = TEST_UTIL.getHBaseCluster().startRegionServer();
131    waitForLabelsRegionAvailability(rs.getRegionServer());
132    final AtomicBoolean vcInitialized = new AtomicBoolean(true);
133    do {
134      PrivilegedExceptionAction<VisibilityLabelsResponse> action =
135        new PrivilegedExceptionAction<VisibilityLabelsResponse>() {
136          @Override
137          public VisibilityLabelsResponse run() throws Exception {
138            String[] labels = { SECRET, CONFIDENTIAL, PRIVATE, "ABC", "XYZ" };
139            try (Connection conn = ConnectionFactory.createConnection(conf)) {
140              VisibilityLabelsResponse resp = VisibilityClient.addLabels(conn, labels);
141              List<RegionActionResult> results = resp.getResultList();
142              if (results.get(0).hasException()) {
143                NameBytesPair pair = results.get(0).getException();
144                Throwable t = ProtobufUtil.toException(pair);
145                LOG.debug("Got exception writing labels", t);
146                if (t instanceof VisibilityControllerNotReadyException) {
147                  vcInitialized.set(false);
148                  LOG.warn("VisibilityController was not yet initialized");
149                  Threads.sleep(10);
150                } else {
151                  vcInitialized.set(true);
152                }
153              } else LOG.debug("new labels added: " + resp);
154            } catch (Throwable t) {
155              throw new IOException(t);
156            }
157            return null;
158          }
159        };
160      SUPERUSER.runAs(action);
161    } while (!vcInitialized.get());
162    // Scan the visibility label
163    Scan s = new Scan();
164    s.setAuthorizations(new Authorizations(VisibilityUtils.SYSTEM_LABEL));
165
166    int i = 0;
167    try (Table ht = TEST_UTIL.getConnection().getTable(LABELS_TABLE_NAME);
168      ResultScanner scanner = ht.getScanner(s)) {
169      while (true) {
170        Result next = scanner.next();
171        if (next == null) {
172          break;
173        }
174        i++;
175      }
176    }
177    // One label is the "system" label.
178    assertEquals(13, i, "The count should be 13");
179  }
180
181  @Test
182  public void testListLabels() throws Throwable {
183    PrivilegedExceptionAction<ListLabelsResponse> action =
184      new PrivilegedExceptionAction<ListLabelsResponse>() {
185        @Override
186        public ListLabelsResponse run() throws Exception {
187          ListLabelsResponse response = null;
188          try (Connection conn = ConnectionFactory.createConnection(conf)) {
189            response = VisibilityClient.listLabels(conn, null);
190          } catch (Throwable e) {
191            throw new IOException(e);
192          }
193          // The addLabels() in setup added:
194          // { SECRET, TOPSECRET, CONFIDENTIAL, PUBLIC, PRIVATE, COPYRIGHT, ACCENT,
195          // UNICODE_VIS_TAG, UC1, UC2 };
196          // The previous tests added 2 more labels: ABC, XYZ
197          // The 'system' label is excluded.
198          List<ByteString> labels = response.getLabelList();
199          assertEquals(12, labels.size());
200          assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(SECRET))));
201          assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(TOPSECRET))));
202          assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(CONFIDENTIAL))));
203          assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes("ABC"))));
204          assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes("XYZ"))));
205          assertFalse(labels.contains(ByteString.copyFrom(Bytes.toBytes(SYSTEM_LABEL))));
206          return null;
207        }
208      };
209    SUPERUSER.runAs(action);
210  }
211
212  @Test
213  public void testListLabelsWithRegEx() throws Throwable {
214    PrivilegedExceptionAction<ListLabelsResponse> action =
215      new PrivilegedExceptionAction<ListLabelsResponse>() {
216        @Override
217        public ListLabelsResponse run() throws Exception {
218          ListLabelsResponse response = null;
219          try (Connection conn = ConnectionFactory.createConnection(conf)) {
220            response = VisibilityClient.listLabels(conn, ".*secret");
221          } catch (Throwable e) {
222            throw new IOException(e);
223          }
224          // Only return the labels that end with 'secret'
225          List<ByteString> labels = response.getLabelList();
226          assertEquals(2, labels.size());
227          assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(SECRET))));
228          assertTrue(labels.contains(ByteString.copyFrom(Bytes.toBytes(TOPSECRET))));
229          return null;
230        }
231      };
232    SUPERUSER.runAs(action);
233  }
234
235  @Test
236  public void testVisibilityLabelsOnWALReplay(TestInfo testInfo) throws Exception {
237    final TableName tableName = TableName
238      .valueOf(TableNameTestExtension.cleanUpTestName(testInfo.getTestMethod().get().getName()));
239    try (Table table = createTableAndWriteDataWithLabels(tableName,
240      "(" + SECRET + "|" + CONFIDENTIAL + ")", PRIVATE)) {
241      List<RegionServerThread> regionServerThreads =
242        TEST_UTIL.getHBaseCluster().getRegionServerThreads();
243      for (RegionServerThread rsThread : regionServerThreads) {
244        rsThread.getRegionServer().abort("Aborting ");
245      }
246      // Start one new RS
247      RegionServerThread rs = TEST_UTIL.getHBaseCluster().startRegionServer();
248      waitForLabelsRegionAvailability(rs.getRegionServer());
249      Scan s = new Scan();
250      s.setAuthorizations(new Authorizations(SECRET));
251      ResultScanner scanner = table.getScanner(s);
252      Result[] next = scanner.next(3);
253      assertTrue(next.length == 1);
254    }
255  }
256}