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