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.regionserver;
019
020import static org.junit.jupiter.api.Assertions.assertNotNull;
021import static org.junit.jupiter.api.Assertions.assertTrue;
022
023import java.security.Key;
024import java.util.ArrayList;
025import java.util.List;
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.fs.Path;
028import org.apache.hadoop.hbase.HBaseTestingUtil;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
032import org.apache.hadoop.hbase.client.Put;
033import org.apache.hadoop.hbase.client.Table;
034import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
035import org.apache.hadoop.hbase.io.crypto.Encryption;
036import org.apache.hadoop.hbase.io.crypto.MockAesKeyProvider;
037import org.apache.hadoop.hbase.io.hfile.CacheConfig;
038import org.apache.hadoop.hbase.io.hfile.HFile;
039import org.apache.hadoop.hbase.testclassification.MediumTests;
040import org.apache.hadoop.hbase.testclassification.RegionServerTests;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.junit.jupiter.api.AfterAll;
043import org.junit.jupiter.api.BeforeAll;
044import org.junit.jupiter.api.Tag;
045import org.junit.jupiter.api.Test;
046
047@Tag(RegionServerTests.TAG)
048@Tag(MediumTests.TAG)
049public class TestEncryptionRandomKeying {
050
051  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
052  private static Configuration conf = TEST_UTIL.getConfiguration();
053  private static TableDescriptorBuilder tdb;
054
055  private static List<Path> findStorefilePaths(TableName tableName) throws Exception {
056    List<Path> paths = new ArrayList<>();
057    for (Region region : TEST_UTIL.getRSForFirstRegionInTable(tableName)
058      .getRegions(tdb.build().getTableName())) {
059      for (HStore store : ((HRegion) region).getStores()) {
060        for (HStoreFile storefile : store.getStorefiles()) {
061          paths.add(storefile.getPath());
062        }
063      }
064    }
065    return paths;
066  }
067
068  private static byte[] extractHFileKey(Path path) throws Exception {
069    HFile.Reader reader =
070      HFile.createReader(TEST_UTIL.getTestFileSystem(), path, new CacheConfig(conf), true, conf);
071    try {
072      Encryption.Context cryptoContext = reader.getFileContext().getEncryptionContext();
073      assertNotNull(cryptoContext, "Reader has a null crypto context");
074      Key key = cryptoContext.getKey();
075      if (key == null) {
076        return null;
077      }
078      return key.getEncoded();
079    } finally {
080      reader.close();
081    }
082  }
083
084  @BeforeAll
085  public static void setUp() throws Exception {
086    conf.setInt("hfile.format.version", 3);
087    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName());
088    conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase");
089
090    // Create the table schema
091    // Specify an encryption algorithm without a key
092    tdb =
093      TableDescriptorBuilder.newBuilder(TableName.valueOf("default", "TestEncryptionRandomKeying"));
094    ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder =
095      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf"));
096    String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
097    columnFamilyDescriptorBuilder.setEncryptionType(algorithm);
098    tdb.setColumnFamily(columnFamilyDescriptorBuilder.build());
099
100    // Start the minicluster
101    TEST_UTIL.startMiniCluster(1);
102
103    // Create the test table
104    TEST_UTIL.getAdmin().createTable(tdb.build());
105    TEST_UTIL.waitTableAvailable(tdb.build().getTableName(), 5000);
106
107    // Create a store file
108    Table table = TEST_UTIL.getConnection().getTable(tdb.build().getTableName());
109    try {
110      table.put(
111        new Put(Bytes.toBytes("testrow")).addColumn(columnFamilyDescriptorBuilder.build().getName(),
112          Bytes.toBytes("q"), Bytes.toBytes("value")));
113    } finally {
114      table.close();
115    }
116    TEST_UTIL.getAdmin().flush(tdb.build().getTableName());
117  }
118
119  @AfterAll
120  public static void tearDown() throws Exception {
121    TEST_UTIL.shutdownMiniCluster();
122  }
123
124  @Test
125  public void testRandomKeying() throws Exception {
126    // Verify we have store file(s) with a random key
127    final List<Path> initialPaths = findStorefilePaths(tdb.build().getTableName());
128    assertTrue(initialPaths.size() > 0);
129    for (Path path : initialPaths) {
130      assertNotNull(extractHFileKey(path), "Store file " + path + " is not encrypted");
131    }
132  }
133
134}