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.assertArrayEquals;
022import static org.junit.jupiter.api.Assertions.assertEquals;
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 java.util.Optional;
030import java.util.concurrent.atomic.AtomicInteger;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.hbase.ArrayBackedTag;
033import org.apache.hadoop.hbase.Cell;
034import org.apache.hadoop.hbase.CellScanner;
035import org.apache.hadoop.hbase.CellUtil;
036import org.apache.hadoop.hbase.ExtendedCell;
037import org.apache.hadoop.hbase.HBaseConfiguration;
038import org.apache.hadoop.hbase.HBaseTestingUtil;
039import org.apache.hadoop.hbase.HConstants;
040import org.apache.hadoop.hbase.PrivateCellUtil;
041import org.apache.hadoop.hbase.TableName;
042import org.apache.hadoop.hbase.Tag;
043import org.apache.hadoop.hbase.TagType;
044import org.apache.hadoop.hbase.client.Admin;
045import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
046import org.apache.hadoop.hbase.client.Connection;
047import org.apache.hadoop.hbase.client.ConnectionFactory;
048import org.apache.hadoop.hbase.client.Durability;
049import org.apache.hadoop.hbase.client.Get;
050import org.apache.hadoop.hbase.client.Put;
051import org.apache.hadoop.hbase.client.Result;
052import org.apache.hadoop.hbase.client.ResultScanner;
053import org.apache.hadoop.hbase.client.Scan;
054import org.apache.hadoop.hbase.client.Table;
055import org.apache.hadoop.hbase.client.TableDescriptor;
056import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
057import org.apache.hadoop.hbase.codec.KeyValueCodecWithTags;
058import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
059import org.apache.hadoop.hbase.coprocessor.ObserverContext;
060import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;
061import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
062import org.apache.hadoop.hbase.coprocessor.RegionObserver;
063import org.apache.hadoop.hbase.replication.ReplicationEndpoint;
064import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
065import org.apache.hadoop.hbase.security.User;
066import org.apache.hadoop.hbase.testclassification.MediumTests;
067import org.apache.hadoop.hbase.testclassification.SecurityTests;
068import org.apache.hadoop.hbase.util.Bytes;
069import org.apache.hadoop.hbase.wal.WAL.Entry;
070import org.apache.hadoop.hbase.wal.WALEdit;
071import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster;
072import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
073import org.junit.jupiter.api.BeforeEach;
074import org.junit.jupiter.api.Test;
075import org.slf4j.Logger;
076import org.slf4j.LoggerFactory;
077
078import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse;
079
080@org.junit.jupiter.api.Tag(SecurityTests.TAG)
081@org.junit.jupiter.api.Tag(MediumTests.TAG)
082public class TestVisibilityLabelsReplication {
083
084  private static final Logger LOG = LoggerFactory.getLogger(TestVisibilityLabelsReplication.class);
085  protected static final int NON_VIS_TAG_TYPE = 100;
086  protected static final String TEMP = "temp";
087  protected static Configuration conf;
088  protected static Configuration conf1;
089  protected static TableName TABLE_NAME = TableName.valueOf("TABLE_NAME");
090  protected static Admin admin;
091  public static final String TOPSECRET = "topsecret";
092  public static final String PUBLIC = "public";
093  public static final String PRIVATE = "private";
094  public static final String CONFIDENTIAL = "confidential";
095  public static final String COPYRIGHT = "\u00A9ABC";
096  public static final String ACCENT = "\u0941";
097  public static final String SECRET = "secret";
098  public static final String UNICODE_VIS_TAG =
099    COPYRIGHT + "\"" + ACCENT + "\\" + SECRET + "\"" + "\u0027&\\";
100  public static HBaseTestingUtil TEST_UTIL;
101  public static HBaseTestingUtil TEST_UTIL1;
102  public static final byte[] row1 = Bytes.toBytes("row1");
103  public static final byte[] row2 = Bytes.toBytes("row2");
104  public static final byte[] row3 = Bytes.toBytes("row3");
105  public static final byte[] row4 = Bytes.toBytes("row4");
106  public final static byte[] fam = Bytes.toBytes("info");
107  public final static byte[] qual = Bytes.toBytes("qual");
108  public final static byte[] value = Bytes.toBytes("value");
109  protected static ZKWatcher zkw1;
110  protected static ZKWatcher zkw2;
111  protected static int expected[] = { 4, 6, 4, 0, 3 };
112  private static final String NON_VISIBILITY = "non-visibility";
113  protected static String[] expectedVisString =
114    { "(\"secret\"&\"topsecret\"&\"public\")|(\"topsecret\"&\"confidential\")",
115      "(\"public\"&\"private\")|(\"topsecret\"&\"private\")|"
116        + "(\"confidential\"&\"public\")|(\"topsecret\"&\"confidential\")",
117      "(!\"topsecret\"&\"secret\")|(!\"topsecret\"&\"confidential\")", "(\"secret\"&\"" + COPYRIGHT
118        + "\\\"" + ACCENT + "\\\\" + SECRET + "\\\"" + "\u0027&\\\\" + "\")" };
119
120  public static User SUPERUSER, USER1;
121
122  protected void setUpTest() throws Exception {
123    // setup configuration
124    conf = HBaseConfiguration.create();
125    conf.setInt("hfile.format.version", 3);
126    conf.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/1");
127    conf.setInt("replication.source.size.capacity", 10240);
128    conf.setLong("replication.source.sleepforretries", 100);
129    conf.setInt("hbase.regionserver.maxlogs", 10);
130    conf.setLong("hbase.master.logcleaner.ttl", 10);
131    conf.setInt("zookeeper.recovery.retry", 1);
132    conf.setInt("zookeeper.recovery.retry.intervalmill", 10);
133    conf.setLong(HConstants.THREAD_WAKE_FREQUENCY, 100);
134    conf.setInt("replication.stats.thread.period.seconds", 5);
135    conf.setBoolean("hbase.tests.use.shortcircuit.reads", false);
136    setVisibilityLabelServiceImpl(conf);
137    conf.setStrings(HConstants.REPLICATION_CODEC_CONF_KEY, KeyValueCodecWithTags.class.getName());
138    VisibilityTestUtil.enableVisiblityLabels(conf);
139    conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
140      VisibilityReplication.class.getName());
141    conf.setStrings(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY, SimpleCP.class.getName());
142    // Have to reset conf1 in case zk cluster location different
143    // than default
144    conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class,
145      ScanLabelGenerator.class);
146    conf.set("hbase.superuser", User.getCurrent().getShortName());
147    SUPERUSER = User.createUserForTesting(conf, User.getCurrent().getShortName(),
148      new String[] { "supergroup" });
149    // User.createUserForTesting(conf, User.getCurrent().getShortName(), new
150    // String[] { "supergroup" });
151    USER1 = User.createUserForTesting(conf, "user1", new String[] {});
152    TEST_UTIL = new HBaseTestingUtil(conf);
153    TEST_UTIL.startMiniZKCluster();
154    MiniZooKeeperCluster miniZK = TEST_UTIL.getZkCluster();
155    zkw1 = new ZKWatcher(conf, "cluster1", null, true);
156
157    // Base conf2 on conf1 so it gets the right zk cluster.
158    conf1 = HBaseConfiguration.create(conf);
159    conf1.setInt("hfile.format.version", 3);
160    conf1.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/2");
161    conf1.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6);
162    conf1.setBoolean("hbase.tests.use.shortcircuit.reads", false);
163    conf1.setStrings(HConstants.REPLICATION_CODEC_CONF_KEY, KeyValueCodecWithTags.class.getName());
164    conf1.setStrings(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY,
165      TestCoprocessorForTagsAtSink.class.getName());
166    // setVisibilityLabelServiceImpl(conf1);
167    USER1 = User.createUserForTesting(conf1, "user1", new String[] {});
168    TEST_UTIL1 = new HBaseTestingUtil(conf1);
169    TEST_UTIL1.setZkCluster(miniZK);
170    zkw2 = new ZKWatcher(conf1, "cluster2", null, true);
171
172    TEST_UTIL.startMiniCluster(1);
173    // Wait for the labels table to become available
174    TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000);
175    TEST_UTIL1.startMiniCluster(1);
176
177    admin = TEST_UTIL.getAdmin();
178    ReplicationPeerConfig rpc =
179      ReplicationPeerConfig.newBuilder().setClusterKey(TEST_UTIL1.getRpcConnnectionURI()).build();
180    admin.addReplicationPeer("2", rpc);
181
182    Admin hBaseAdmin = TEST_UTIL.getAdmin();
183    TableDescriptor tableDescriptor =
184      TableDescriptorBuilder.newBuilder(TABLE_NAME).setColumnFamily(ColumnFamilyDescriptorBuilder
185        .newBuilder(fam).setScope(HConstants.REPLICATION_SCOPE_GLOBAL).build()).build();
186    try {
187      hBaseAdmin.createTable(tableDescriptor);
188    } finally {
189      if (hBaseAdmin != null) {
190        hBaseAdmin.close();
191      }
192    }
193    Admin hBaseAdmin1 = TEST_UTIL1.getAdmin();
194    try {
195      hBaseAdmin1.createTable(tableDescriptor);
196    } finally {
197      if (hBaseAdmin1 != null) {
198        hBaseAdmin1.close();
199      }
200    }
201    addLabels();
202    setAuths(conf);
203    setAuths(conf1);
204  }
205
206  @BeforeEach
207  public void setUp() throws Exception {
208    setUpTest();
209  }
210
211  protected static void setVisibilityLabelServiceImpl(Configuration conf) {
212    conf.setClass(VisibilityLabelServiceManager.VISIBILITY_LABEL_SERVICE_CLASS,
213      DefaultVisibilityLabelServiceImpl.class, VisibilityLabelService.class);
214  }
215
216  @Test
217  public void testVisibilityReplication() throws Exception {
218    int retry = 0;
219    try (Table table = writeData(TABLE_NAME,
220      "(" + SECRET + "&" + PUBLIC + ")" + "|(" + CONFIDENTIAL + ")&(" + TOPSECRET + ")",
221      "(" + PRIVATE + "|" + CONFIDENTIAL + ")&(" + PUBLIC + "|" + TOPSECRET + ")",
222      "(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET,
223      CellVisibility.quote(UNICODE_VIS_TAG) + "&" + SECRET)) {
224      Scan s = new Scan();
225      s.setAuthorizations(
226        new Authorizations(SECRET, CONFIDENTIAL, PRIVATE, TOPSECRET, UNICODE_VIS_TAG));
227      ResultScanner scanner = table.getScanner(s);
228      Result[] next = scanner.next(4);
229
230      assertTrue(next.length == 4);
231      CellScanner cellScanner = next[0].cellScanner();
232      cellScanner.advance();
233      Cell current = cellScanner.current();
234      assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), current.getRowLength(),
235        row1, 0, row1.length));
236      cellScanner = next[1].cellScanner();
237      cellScanner.advance();
238      current = cellScanner.current();
239      assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), current.getRowLength(),
240        row2, 0, row2.length));
241      cellScanner = next[2].cellScanner();
242      cellScanner.advance();
243      current = cellScanner.current();
244      assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), current.getRowLength(),
245        row3, 0, row3.length));
246      cellScanner = next[3].cellScanner();
247      cellScanner.advance();
248      current = cellScanner.current();
249      assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), current.getRowLength(),
250        row4, 0, row4.length));
251      try (Table table2 = TEST_UTIL1.getConnection().getTable(TABLE_NAME)) {
252        s = new Scan();
253        // Ensure both rows are replicated
254        scanner = table2.getScanner(s);
255        next = scanner.next(4);
256        while (next.length == 0 && retry <= 10) {
257          scanner = table2.getScanner(s);
258          next = scanner.next(4);
259          Thread.sleep(2000);
260          retry++;
261        }
262        assertTrue(next.length == 4);
263        verifyGet(row1, expectedVisString[0], expected[0], false, TOPSECRET, CONFIDENTIAL);
264        TestCoprocessorForTagsAtSink.tags.clear();
265        verifyGet(row2, expectedVisString[1], expected[1], false, CONFIDENTIAL, PUBLIC);
266        TestCoprocessorForTagsAtSink.tags.clear();
267        verifyGet(row3, expectedVisString[2], expected[2], false, PRIVATE, SECRET);
268        verifyGet(row3, "", expected[3], true, TOPSECRET, SECRET);
269        verifyGet(row4, expectedVisString[3], expected[4], false, UNICODE_VIS_TAG, SECRET);
270      }
271    }
272  }
273
274  protected static void doAssert(byte[] row, String visTag) throws Exception {
275    if (VisibilityReplicationEndPointForTest.lastEntries == null) {
276      return; // first call
277    }
278    assertEquals(1, VisibilityReplicationEndPointForTest.lastEntries.size());
279    List<Cell> cells = VisibilityReplicationEndPointForTest.lastEntries.get(0).getEdit().getCells();
280    assertEquals(4, cells.size());
281    boolean tagFound = false;
282    for (Cell cell : cells) {
283      if (
284        (Bytes.equals(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength(), row, 0,
285          row.length))
286      ) {
287        List<Tag> tags = PrivateCellUtil.getTags((ExtendedCell) cell);
288        for (Tag tag : tags) {
289          if (tag.getType() == TagType.STRING_VIS_TAG_TYPE) {
290            assertEquals(visTag, Tag.getValueAsString(tag));
291            tagFound = true;
292            break;
293          }
294        }
295      }
296    }
297    assertTrue(tagFound);
298  }
299
300  protected void verifyGet(final byte[] row, final String visString, final int expected,
301    final boolean nullExpected, final String... auths) throws IOException, InterruptedException {
302    PrivilegedExceptionAction<Void> scanAction = new PrivilegedExceptionAction<Void>() {
303      @Override
304      public Void run() throws Exception {
305        try (Connection connection = ConnectionFactory.createConnection(conf1);
306          Table table2 = connection.getTable(TABLE_NAME)) {
307          CellScanner cellScanner;
308          Cell current;
309          Get get = new Get(row);
310          get.setAuthorizations(new Authorizations(auths));
311          Result result = table2.get(get);
312          cellScanner = result.cellScanner();
313          boolean advance = cellScanner.advance();
314          if (nullExpected) {
315            assertTrue(!advance);
316            return null;
317          }
318          current = cellScanner.current();
319          assertArrayEquals(CellUtil.cloneRow(current), row);
320          for (Tag tag : TestCoprocessorForTagsAtSink.tags) {
321            LOG.info("The tag type is " + tag.getType());
322          }
323          assertEquals(expected, TestCoprocessorForTagsAtSink.tags.size());
324          Tag tag = TestCoprocessorForTagsAtSink.tags.get(1);
325          if (tag.getType() != NON_VIS_TAG_TYPE) {
326            assertEquals(TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE, tag.getType());
327          }
328          tag = TestCoprocessorForTagsAtSink.tags.get(0);
329          boolean foundNonVisTag = false;
330          for (Tag t : TestCoprocessorForTagsAtSink.tags) {
331            if (t.getType() == NON_VIS_TAG_TYPE) {
332              assertEquals(TEMP, Tag.getValueAsString(t));
333              foundNonVisTag = true;
334              break;
335            }
336          }
337          doAssert(row, visString);
338          assertTrue(foundNonVisTag);
339          return null;
340        }
341      }
342    };
343    USER1.runAs(scanAction);
344  }
345
346  public static void addLabels() throws Exception {
347    PrivilegedExceptionAction<VisibilityLabelsResponse> action =
348      new PrivilegedExceptionAction<VisibilityLabelsResponse>() {
349        @Override
350        public VisibilityLabelsResponse run() throws Exception {
351          String[] labels = { SECRET, TOPSECRET, CONFIDENTIAL, PUBLIC, PRIVATE, UNICODE_VIS_TAG };
352          try (Connection conn = ConnectionFactory.createConnection(conf)) {
353            VisibilityClient.addLabels(conn, labels);
354          } catch (Throwable t) {
355            throw new IOException(t);
356          }
357          return null;
358        }
359      };
360    SUPERUSER.runAs(action);
361  }
362
363  public static void setAuths(final Configuration conf) throws Exception {
364    PrivilegedExceptionAction<VisibilityLabelsResponse> action =
365      new PrivilegedExceptionAction<VisibilityLabelsResponse>() {
366        @Override
367        public VisibilityLabelsResponse run() throws Exception {
368          try (Connection conn = ConnectionFactory.createConnection(conf)) {
369            return VisibilityClient.setAuths(conn,
370              new String[] { SECRET, CONFIDENTIAL, PRIVATE, TOPSECRET, UNICODE_VIS_TAG }, "user1");
371          } catch (Throwable e) {
372            throw new Exception(e);
373          }
374        }
375      };
376    SUPERUSER.runAs(action);
377  }
378
379  static Table writeData(TableName tableName, String... labelExps) throws Exception {
380    Table table = TEST_UTIL.getConnection().getTable(TABLE_NAME);
381    int i = 1;
382    List<Put> puts = new ArrayList<>(labelExps.length);
383    for (String labelExp : labelExps) {
384      Put put = new Put(Bytes.toBytes("row" + i));
385      put.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value);
386      put.setCellVisibility(new CellVisibility(labelExp));
387      put.setAttribute(NON_VISIBILITY, Bytes.toBytes(TEMP));
388      puts.add(put);
389      i++;
390    }
391    table.put(puts);
392    return table;
393  }
394
395  // A simple BaseRegionbserver impl that allows to add a non-visibility tag from the
396  // attributes of the Put mutation. The existing cells in the put mutation is overwritten
397  // with a new cell that has the visibility tags and the non visibility tag
398  public static class SimpleCP implements RegionCoprocessor, RegionObserver {
399    @Override
400    public Optional<RegionObserver> getRegionObserver() {
401      return Optional.of(this);
402    }
403
404    @Override
405    public void prePut(ObserverContext<? extends RegionCoprocessorEnvironment> e, Put m,
406      WALEdit edit, Durability durability) throws IOException {
407      byte[] attribute = m.getAttribute(NON_VISIBILITY);
408      byte[] cf = null;
409      List<Cell> updatedCells = new ArrayList<>();
410      if (attribute != null) {
411        for (List<? extends Cell> edits : m.getFamilyCellMap().values()) {
412          for (Cell cell : edits) {
413            if (cf == null) {
414              cf = CellUtil.cloneFamily(cell);
415            }
416            Tag tag = new ArrayBackedTag((byte) NON_VIS_TAG_TYPE, attribute);
417            List<Tag> tagList =
418              new ArrayList<>(PrivateCellUtil.getTags((ExtendedCell) cell).size() + 1);
419            tagList.add(tag);
420            tagList.addAll(PrivateCellUtil.getTags((ExtendedCell) cell));
421            Cell newcell = PrivateCellUtil.createCell((ExtendedCell) cell, tagList);
422            ((List<Cell>) updatedCells).add(newcell);
423          }
424        }
425        m.getFamilyCellMap().remove(cf);
426        // Update the family map
427        m.getFamilyCellMap().put(cf, updatedCells);
428      }
429    }
430  }
431
432  public static class TestCoprocessorForTagsAtSink implements RegionCoprocessor, RegionObserver {
433    public static List<Tag> tags = null;
434
435    @Override
436    public Optional<RegionObserver> getRegionObserver() {
437      return Optional.of(this);
438    }
439
440    @Override
441    public void postGetOp(ObserverContext<? extends RegionCoprocessorEnvironment> e, Get get,
442      List<Cell> results) throws IOException {
443      if (results.size() > 0) {
444        // Check tag presence in the 1st cell in 1st Result
445        if (!results.isEmpty()) {
446          Cell cell = results.get(0);
447          tags = PrivateCellUtil.getTags((ExtendedCell) cell);
448        }
449      }
450    }
451  }
452
453  /**
454   * An extn of VisibilityReplicationEndpoint to verify the tags that are replicated
455   */
456  public static class VisibilityReplicationEndPointForTest extends VisibilityReplicationEndpoint {
457    static AtomicInteger replicateCount = new AtomicInteger();
458    static volatile List<Entry> lastEntries = null;
459
460    public VisibilityReplicationEndPointForTest(ReplicationEndpoint endpoint,
461      VisibilityLabelService visibilityLabelsService) {
462      super(endpoint, visibilityLabelsService);
463    }
464
465    @Override
466    public boolean replicate(ReplicateContext replicateContext) {
467      boolean ret = super.replicate(replicateContext);
468      lastEntries = replicateContext.getEntries();
469      replicateCount.incrementAndGet();
470      return ret;
471    }
472  }
473}