1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.regionserver;
19
20 import static org.junit.Assert.*;
21
22 import java.security.Key;
23 import java.security.SecureRandom;
24 import java.util.ArrayList;
25 import java.util.List;
26
27 import javax.crypto.spec.SecretKeySpec;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.hadoop.conf.Configuration;
32 import org.apache.hadoop.fs.Path;
33 import org.apache.hadoop.hbase.HBaseTestingUtility;
34 import org.apache.hadoop.hbase.HColumnDescriptor;
35 import org.apache.hadoop.hbase.HConstants;
36 import org.apache.hadoop.hbase.HTableDescriptor;
37 import org.apache.hadoop.hbase.testclassification.MediumTests;
38 import org.apache.hadoop.hbase.TableName;
39 import org.apache.hadoop.hbase.Waiter.Predicate;
40 import org.apache.hadoop.hbase.client.HTable;
41 import org.apache.hadoop.hbase.client.Put;
42 import org.apache.hadoop.hbase.io.crypto.Encryption;
43 import org.apache.hadoop.hbase.io.crypto.KeyProviderForTesting;
44 import org.apache.hadoop.hbase.io.crypto.aes.AES;
45 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
46 import org.apache.hadoop.hbase.io.hfile.HFile;
47 import org.apache.hadoop.hbase.security.EncryptionUtil;
48 import org.apache.hadoop.hbase.security.User;
49 import org.apache.hadoop.hbase.util.Bytes;
50
51 import org.junit.AfterClass;
52 import org.junit.BeforeClass;
53 import org.junit.Test;
54 import org.junit.experimental.categories.Category;
55
56 @Category(MediumTests.class)
57 public class TestEncryptionKeyRotation {
58 private static final Log LOG = LogFactory.getLog(TestEncryptionKeyRotation.class);
59 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
60 private static final Configuration conf = TEST_UTIL.getConfiguration();
61 private static final Key initialCFKey;
62 private static final Key secondCFKey;
63 static {
64
65 SecureRandom rng = new SecureRandom();
66 byte[] keyBytes = new byte[AES.KEY_LENGTH];
67 rng.nextBytes(keyBytes);
68 String algorithm =
69 conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
70 initialCFKey = new SecretKeySpec(keyBytes, algorithm);
71 rng.nextBytes(keyBytes);
72 secondCFKey = new SecretKeySpec(keyBytes, algorithm);
73 }
74
75 @BeforeClass
76 public static void setUp() throws Exception {
77 conf.setInt("hfile.format.version", 3);
78 conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, KeyProviderForTesting.class.getName());
79 conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase");
80
81 conf.setBoolean("hbase.online.schema.update.enable", true);
82
83
84 TEST_UTIL.startMiniCluster(1);
85 }
86
87 @AfterClass
88 public static void tearDown() throws Exception {
89 TEST_UTIL.shutdownMiniCluster();
90 }
91
92 @Test
93 public void testCFKeyRotation() throws Exception {
94
95 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("default",
96 "testCFKeyRotation"));
97 HColumnDescriptor hcd = new HColumnDescriptor("cf");
98 String algorithm =
99 conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
100 hcd.setEncryptionType(algorithm);
101 hcd.setEncryptionKey(EncryptionUtil.wrapKey(conf, "hbase", initialCFKey));
102 htd.addFamily(hcd);
103
104
105 createTableAndFlush(htd);
106
107
108 final List<Path> initialPaths = findStorefilePaths(htd.getTableName());
109 assertTrue(initialPaths.size() > 0);
110 for (Path path: initialPaths) {
111 assertTrue("Store file " + path + " has incorrect key",
112 Bytes.equals(initialCFKey.getEncoded(), extractHFileKey(path)));
113 }
114
115
116 hcd = htd.getFamily(Bytes.toBytes("cf"));
117 hcd.setEncryptionKey(EncryptionUtil.wrapKey(conf,
118 conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()),
119 secondCFKey));
120 TEST_UTIL.getHBaseAdmin().modifyColumn(htd.getName(), hcd);
121 Thread.sleep(5000);
122
123
124 TEST_UTIL.getHBaseAdmin().majorCompact(htd.getName());
125 TEST_UTIL.waitFor(30000, 1000, true, new Predicate<Exception>() {
126 @Override
127 public boolean evaluate() throws Exception {
128
129
130 boolean found = false;
131 for (Path path: initialPaths) {
132 found = TEST_UTIL.getTestFileSystem().exists(path);
133 if (found) {
134 LOG.info("Found " + path);
135 break;
136 }
137 }
138 return !found;
139 }
140 });
141
142
143 List<Path> pathsAfterCompaction = findStorefilePaths(htd.getTableName());
144 assertTrue(pathsAfterCompaction.size() > 0);
145 for (Path path: pathsAfterCompaction) {
146 assertFalse("Store file " + path + " retains initial key",
147 Bytes.equals(initialCFKey.getEncoded(), extractHFileKey(path)));
148 assertTrue("Store file " + path + " has incorrect key",
149 Bytes.equals(secondCFKey.getEncoded(), extractHFileKey(path)));
150 }
151 }
152
153 @Test
154 public void testMasterKeyRotation() throws Exception {
155
156 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("default",
157 "testMasterKeyRotation"));
158 HColumnDescriptor hcd = new HColumnDescriptor("cf");
159 String algorithm =
160 conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
161 hcd.setEncryptionType(algorithm);
162 hcd.setEncryptionKey(EncryptionUtil.wrapKey(conf, "hbase", initialCFKey));
163 htd.addFamily(hcd);
164
165
166 createTableAndFlush(htd);
167
168
169 List<Path> storeFilePaths = findStorefilePaths(htd.getTableName());
170 assertTrue(storeFilePaths.size() > 0);
171 for (Path path: storeFilePaths) {
172 assertTrue("Store file " + path + " has incorrect key",
173 Bytes.equals(initialCFKey.getEncoded(), extractHFileKey(path)));
174 }
175
176
177 TEST_UTIL.shutdownMiniHBaseCluster();
178
179
180 conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "other");
181 conf.set(HConstants.CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY, "hbase");
182
183
184 TEST_UTIL.startMiniHBaseCluster(1, 1);
185
186 TEST_UTIL.waitTableAvailable(htd.getName(), 5000);
187
188 storeFilePaths = findStorefilePaths(htd.getTableName());
189 assertTrue(storeFilePaths.size() > 0);
190 for (Path path: storeFilePaths) {
191 assertTrue("Store file " + path + " has incorrect key",
192 Bytes.equals(initialCFKey.getEncoded(), extractHFileKey(path)));
193 }
194 }
195
196 private static List<Path> findStorefilePaths(TableName tableName) throws Exception {
197 List<Path> paths = new ArrayList<Path>();
198 for (HRegion region:
199 TEST_UTIL.getRSForFirstRegionInTable(tableName).getOnlineRegions(tableName)) {
200 for (Store store: region.getStores().values()) {
201 for (StoreFile storefile: store.getStorefiles()) {
202 paths.add(storefile.getPath());
203 }
204 }
205 }
206 return paths;
207 }
208
209 private void createTableAndFlush(HTableDescriptor htd) throws Exception {
210 HColumnDescriptor hcd = htd.getFamilies().iterator().next();
211
212 TEST_UTIL.getHBaseAdmin().createTable(htd);
213 TEST_UTIL.waitTableAvailable(htd.getName(), 5000);
214
215 HTable table = new HTable(conf, htd.getName());
216 try {
217 table.put(new Put(Bytes.toBytes("testrow"))
218 .add(hcd.getName(), Bytes.toBytes("q"), Bytes.toBytes("value")));
219 } finally {
220 table.close();
221 }
222 TEST_UTIL.getHBaseAdmin().flush(htd.getName());
223 }
224
225 private static byte[] extractHFileKey(Path path) throws Exception {
226 HFile.Reader reader = HFile.createReader(TEST_UTIL.getTestFileSystem(), path,
227 new CacheConfig(conf), conf);
228 try {
229 reader.loadFileInfo();
230 Encryption.Context cryptoContext = reader.getFileContext().getEncryptionContext();
231 assertNotNull("Reader has a null crypto context", cryptoContext);
232 Key key = cryptoContext.getKey();
233 assertNotNull("Crypto context has no key", key);
234 return key.getEncoded();
235 } finally {
236 reader.close();
237 }
238 }
239
240 }