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.wal; 019 020import static org.junit.Assert.assertFalse; 021import static org.junit.Assert.assertTrue; 022import java.io.FileNotFoundException; 023import java.io.IOException; 024import java.nio.ByteBuffer; 025import java.util.NavigableMap; 026import java.util.TreeMap; 027import org.apache.commons.io.IOUtils; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.hadoop.fs.FSDataInputStream; 030import org.apache.hadoop.fs.FileStatus; 031import org.apache.hadoop.fs.FileSystem; 032import org.apache.hadoop.fs.Path; 033import org.apache.hadoop.hbase.ByteBufferKeyValue; 034import org.apache.hadoop.hbase.HBaseClassTestRule; 035import org.apache.hadoop.hbase.HBaseTestingUtility; 036import org.apache.hadoop.hbase.HConstants; 037import org.apache.hadoop.hbase.KeyValue; 038import org.apache.hadoop.hbase.ServerName; 039import org.apache.hadoop.hbase.TableName; 040import org.apache.hadoop.hbase.client.RegionInfo; 041import org.apache.hadoop.hbase.client.RegionInfoBuilder; 042import org.apache.hadoop.hbase.io.crypto.KeyProviderForTesting; 043import org.apache.hadoop.hbase.regionserver.MultiVersionConcurrencyControl; 044import org.apache.hadoop.hbase.regionserver.wal.ProtobufLogReader; 045import org.apache.hadoop.hbase.regionserver.wal.ProtobufLogWriter; 046import org.apache.hadoop.hbase.regionserver.wal.SecureAsyncProtobufLogWriter; 047import org.apache.hadoop.hbase.regionserver.wal.SecureProtobufLogReader; 048import org.apache.hadoop.hbase.regionserver.wal.SecureProtobufLogWriter; 049import org.apache.hadoop.hbase.regionserver.wal.SecureWALCellCodec; 050import org.apache.hadoop.hbase.regionserver.wal.WALCellCodec; 051import org.apache.hadoop.hbase.testclassification.RegionServerTests; 052import org.apache.hadoop.hbase.testclassification.SmallTests; 053import org.apache.hadoop.hbase.util.Bytes; 054import org.apache.hadoop.hbase.util.CommonFSUtils; 055import org.apache.hadoop.hbase.zookeeper.ZKSplitLog; 056import org.junit.BeforeClass; 057import org.junit.ClassRule; 058import org.junit.Rule; 059import org.junit.Test; 060import org.junit.experimental.categories.Category; 061import org.junit.rules.TestName; 062 063/** 064 * Test that verifies WAL written by SecureProtobufLogWriter is not readable by ProtobufLogReader 065 */ 066@Category({RegionServerTests.class, SmallTests.class}) 067public class TestWALReaderOnSecureWAL { 068 069 @ClassRule 070 public static final HBaseClassTestRule CLASS_RULE = 071 HBaseClassTestRule.forClass(TestWALReaderOnSecureWAL.class); 072 073 static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 074 final byte[] value = Bytes.toBytes("Test value"); 075 076 private static final String WAL_ENCRYPTION = "hbase.regionserver.wal.encryption"; 077 078 @Rule 079 public TestName currentTest = new TestName(); 080 081 @BeforeClass 082 public static void setUpBeforeClass() throws Exception { 083 Configuration conf = TEST_UTIL.getConfiguration(); 084 conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, KeyProviderForTesting.class.getName()); 085 conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase"); 086 conf.setBoolean(WALSplitter.SPLIT_SKIP_ERRORS_KEY, true); 087 conf.setBoolean(HConstants.ENABLE_WAL_ENCRYPTION, true); 088 CommonFSUtils.setRootDir(conf, TEST_UTIL.getDataTestDir()); 089 } 090 091 private Path writeWAL(final WALFactory wals, final String tblName, boolean offheap) 092 throws IOException { 093 Configuration conf = TEST_UTIL.getConfiguration(); 094 String clsName = conf.get(WALCellCodec.WAL_CELL_CODEC_CLASS_KEY, WALCellCodec.class.getName()); 095 conf.setClass(WALCellCodec.WAL_CELL_CODEC_CLASS_KEY, SecureWALCellCodec.class, 096 WALCellCodec.class); 097 try { 098 TableName tableName = TableName.valueOf(tblName); 099 NavigableMap<byte[], Integer> scopes = new TreeMap<>(Bytes.BYTES_COMPARATOR); 100 scopes.put(tableName.getName(), 0); 101 RegionInfo regionInfo = RegionInfoBuilder.newBuilder(tableName).build(); 102 final int total = 10; 103 final byte[] row = Bytes.toBytes("row"); 104 final byte[] family = Bytes.toBytes("family"); 105 final MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(1); 106 107 // Write the WAL 108 WAL wal = wals.getWAL(regionInfo); 109 for (int i = 0; i < total; i++) { 110 WALEdit kvs = new WALEdit(); 111 KeyValue kv = new KeyValue(row, family, Bytes.toBytes(i), value); 112 if (offheap) { 113 ByteBuffer bb = ByteBuffer.allocateDirect(kv.getBuffer().length); 114 bb.put(kv.getBuffer()); 115 ByteBufferKeyValue offheapKV = new ByteBufferKeyValue(bb, 0, kv.getLength()); 116 kvs.add(offheapKV); 117 } else { 118 kvs.add(kv); 119 } 120 wal.appendData(regionInfo, new WALKeyImpl(regionInfo.getEncodedNameAsBytes(), tableName, 121 System.currentTimeMillis(), mvcc, scopes), kvs); 122 } 123 wal.sync(); 124 final Path walPath = AbstractFSWALProvider.getCurrentFileName(wal); 125 wal.shutdown(); 126 127 return walPath; 128 } finally { 129 // restore the cell codec class 130 conf.set(WALCellCodec.WAL_CELL_CODEC_CLASS_KEY, clsName); 131 } 132 } 133 134 @Test() 135 public void testWALReaderOnSecureWALWithKeyValues() throws Exception { 136 testSecureWALInternal(false); 137 } 138 139 @Test() 140 public void testWALReaderOnSecureWALWithOffheapKeyValues() throws Exception { 141 testSecureWALInternal(true); 142 } 143 144 private void testSecureWALInternal(boolean offheap) throws IOException, FileNotFoundException { 145 Configuration conf = TEST_UTIL.getConfiguration(); 146 conf.setClass("hbase.regionserver.hlog.reader.impl", ProtobufLogReader.class, 147 WAL.Reader.class); 148 conf.setClass("hbase.regionserver.hlog.writer.impl", SecureProtobufLogWriter.class, 149 WALProvider.Writer.class); 150 conf.setClass("hbase.regionserver.hlog.async.writer.impl", SecureAsyncProtobufLogWriter.class, 151 WALProvider.AsyncWriter.class); 152 conf.setBoolean(WAL_ENCRYPTION, true); 153 FileSystem fs = TEST_UTIL.getTestFileSystem(); 154 final WALFactory wals = new WALFactory(conf, currentTest.getMethodName()); 155 Path walPath = writeWAL(wals, currentTest.getMethodName(), offheap); 156 157 // Insure edits are not plaintext 158 long length = fs.getFileStatus(walPath).getLen(); 159 FSDataInputStream in = fs.open(walPath); 160 byte[] fileData = new byte[(int)length]; 161 IOUtils.readFully(in, fileData); 162 in.close(); 163 assertFalse("Cells appear to be plaintext", Bytes.contains(fileData, value)); 164 165 // Confirm the WAL cannot be read back by ProtobufLogReader 166 try { 167 wals.createReader(TEST_UTIL.getTestFileSystem(), walPath); 168 assertFalse(true); 169 } catch (IOException ioe) { 170 System.out.println("Expected ioe " + ioe.getMessage()); 171 } 172 173 FileStatus[] listStatus = fs.listStatus(walPath.getParent()); 174 Path rootdir = CommonFSUtils.getRootDir(conf); 175 WALSplitter s = new WALSplitter(wals, conf, rootdir, fs, rootdir, fs, null, null, null); 176 WALSplitter.SplitWALResult swr = s.splitWAL(listStatus[0], null); 177 assertTrue(swr.isCorrupt()); 178 wals.close(); 179 } 180 181 @Test() 182 public void testSecureWALReaderOnWAL() throws Exception { 183 Configuration conf = TEST_UTIL.getConfiguration(); 184 conf.setClass("hbase.regionserver.hlog.reader.impl", SecureProtobufLogReader.class, 185 WAL.Reader.class); 186 conf.setClass("hbase.regionserver.hlog.writer.impl", ProtobufLogWriter.class, 187 WALProvider.Writer.class); 188 conf.setBoolean(WAL_ENCRYPTION, false); 189 FileSystem fs = TEST_UTIL.getTestFileSystem(); 190 final WALFactory wals = new WALFactory(conf, ServerName 191 .valueOf(currentTest.getMethodName(), 16010, System.currentTimeMillis()).toString()); 192 Path walPath = writeWAL(wals, currentTest.getMethodName(), false); 193 194 // Ensure edits are plaintext 195 long length = fs.getFileStatus(walPath).getLen(); 196 FSDataInputStream in = fs.open(walPath); 197 byte[] fileData = new byte[(int)length]; 198 IOUtils.readFully(in, fileData); 199 in.close(); 200 assertTrue("Cells should be plaintext", Bytes.contains(fileData, value)); 201 202 // Confirm the WAL can be read back by SecureProtobufLogReader 203 try { 204 WAL.Reader reader = wals.createReader(TEST_UTIL.getTestFileSystem(), walPath); 205 reader.close(); 206 } catch (IOException ioe) { 207 assertFalse(true); 208 } 209 210 FileStatus[] listStatus = fs.listStatus(walPath.getParent()); 211 Path rootdir = CommonFSUtils.getRootDir(conf); 212 try { 213 WALSplitter s = new WALSplitter(wals, conf, rootdir, fs, rootdir, fs, null, null, null); 214 s.splitWAL(listStatus[0], null); 215 Path file = new Path(ZKSplitLog.getSplitLogDir(rootdir, listStatus[0].getPath().getName()), 216 "corrupt"); 217 assertTrue(!fs.exists(file)); 218 } catch (IOException ioe) { 219 assertTrue("WAL should have been processed", false); 220 } 221 wals.close(); 222 } 223}