You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by vj...@apache.org on 2025/09/24 04:09:37 UTC
(hbase) branch HBASE-29368-key-management-feature updated: HBASE-29495: Integrate key management with existing encryption (#7297)
This is an automated email from the ASF dual-hosted git repository.
vjasani pushed a commit to branch HBASE-29368-key-management-feature
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/HBASE-29368-key-management-feature by this push:
new 4d4d167f230 HBASE-29495: Integrate key management with existing encryption (#7297)
4d4d167f230 is described below
commit 4d4d167f2309e4df355f3da23e1d5c67a27695b3
Author: Hari Krishna Dara <ha...@gmail.com>
AuthorDate: Wed Sep 24 09:39:30 2025 +0530
HBASE-29495: Integrate key management with existing encryption (#7297)
---
.gitignore | 1 +
.../hadoop/hbase/security/EncryptionUtil.java | 52 --
.../java/org/apache/hadoop/hbase/HConstants.java | 6 +
.../org/apache/hadoop/hbase/io/crypto/Context.java | 20 +
.../apache/hadoop/hbase/io/crypto/Encryption.java | 6 -
.../hadoop/hbase/io/crypto/ManagedKeyData.java | 8 +-
.../hadoop/hbase/io/crypto/MockAesKeyProvider.java | 16 +-
.../apache/hadoop/hbase/util/CommonFSUtils.java | 35 +-
.../hadoop/hbase/io/crypto/KeymetaTestUtils.java | 198 ++++++
.../hbase/io/crypto/TestKeyStoreKeyProvider.java | 56 +-
.../hbase/io/crypto/TestManagedKeyProvider.java | 125 ++--
.../src/main/protobuf/server/io/HFile.proto | 3 +
.../org/apache/hadoop/hbase/HBaseServerBase.java | 8 +-
.../hadoop/hbase/MockRegionServerServices.java | 6 +
.../main/java/org/apache/hadoop/hbase/Server.java | 16 +-
.../hbase/client/ClientSideRegionScanner.java | 2 +-
.../java/org/apache/hadoop/hbase/io/HFileLink.java | 9 +
.../hadoop/hbase/io/hfile/FixedFileTrailer.java | 60 ++
.../org/apache/hadoop/hbase/io/hfile/HFile.java | 14 +-
.../apache/hadoop/hbase/io/hfile/HFileInfo.java | 37 +-
.../hadoop/hbase/io/hfile/HFileWriterImpl.java | 31 +-
.../hadoop/hbase/io/hfile/ReaderContext.java | 17 +-
.../hbase/io/hfile/ReaderContextBuilder.java | 19 +-
.../hadoop/hbase/keymeta/KeyManagementBase.java | 16 +-
.../hadoop/hbase/keymeta/KeyManagementService.java | 78 +++
.../hadoop/hbase/keymeta/KeyNamespaceUtil.java | 90 +++
.../hbase/keymeta/KeymetaServiceEndpoint.java | 10 +-
.../hadoop/hbase/keymeta/KeymetaTableAccessor.java | 42 +-
.../hadoop/hbase/keymeta/SystemKeyAccessor.java | 29 +-
.../hadoop/hbase/keymeta/SystemKeyCache.java | 15 +
.../apache/hadoop/hbase/master/MasterServices.java | 3 +-
.../hadoop/hbase/master/SystemKeyManager.java | 6 +-
.../master/procedure/CreateTableProcedure.java | 3 +-
.../hbase/master/procedure/InitMetaProcedure.java | 13 +-
.../hadoop/hbase/master/region/MasterRegion.java | 42 +-
.../hbase/master/region/MasterRegionFactory.java | 4 +-
.../hbase/master/region/MasterRegionParams.java | 8 +-
.../apache/hadoop/hbase/regionserver/HRegion.java | 290 ++++++--
.../apache/hadoop/hbase/regionserver/HStore.java | 7 +-
.../hadoop/hbase/regionserver/HStoreFile.java | 42 +-
.../hbase/regionserver/RegionServerServices.java | 4 +-
.../regionserver/RegionServicesForStores.java | 10 +
.../hadoop/hbase/regionserver/StoreEngine.java | 12 +-
.../hadoop/hbase/regionserver/StoreFileInfo.java | 14 +-
.../storefiletracker/StoreFileTracker.java | 7 +
.../storefiletracker/StoreFileTrackerBase.java | 5 +
.../regionserver/ReplicationSyncUp.java | 24 +-
.../apache/hadoop/hbase/security/SecurityUtil.java | 189 +++++-
.../hadoop/hbase/util/ModifyRegionUtils.java | 30 +-
.../hadoop/hbase/keymeta/ManagedKeyTestBase.java | 7 +-
.../hbase/keymeta/TestKeyManagementBase.java | 25 +-
.../hbase/keymeta/TestKeyManagementService.java | 102 +++
.../hadoop/hbase/keymeta/TestKeyNamespaceUtil.java | 126 ++++
.../hadoop/hbase/keymeta/TestKeymetaEndpoint.java | 10 +-
.../hbase/keymeta/TestKeymetaTableAccessor.java | 5 +-
.../hbase/keymeta/TestManagedKeyDataCache.java | 4 +-
.../hadoop/hbase/keymeta/TestSystemKeyCache.java | 3 +-
.../hbase/master/MasterStateStoreTestBase.java | 3 +-
.../hbase/master/MockNoopMasterServices.java | 7 +-
.../hadoop/hbase/master/MockRegionServer.java | 6 +
.../hbase/master/TestActiveMasterManager.java | 16 +-
.../hadoop/hbase/master/TestKeymetaAdminImpl.java | 9 +-
.../master/TestSystemKeyAccessorAndManager.java | 39 +-
.../cleaner/TestReplicationHFileCleaner.java | 24 +-
.../hbase/master/region/MasterRegionTestBase.java | 6 +-
.../region/TestMasterRegionOnTwoFileSystems.java | 4 +-
.../RegionProcedureStorePerformanceEvaluation.java | 31 +-
.../store/region/RegionProcedureStoreTestBase.java | 4 +-
.../region/RegionProcedureStoreTestHelper.java | 5 +-
.../region/TestRegionProcedureStoreMigration.java | 4 +-
.../hbase/regionserver/TestHeapMemoryManager.java | 24 +-
.../TestRecoveredEditsReplayAndAbort.java | 3 +-
.../hbase/regionserver/TestStoreFileInfo.java | 3 +-
.../hadoop/hbase/security/TestSecurityUtil.java | 751 +++++++++++++++++++++
.../security/token/TestTokenAuthentication.java | 20 +-
.../hbase/snapshot/TestRestoreSnapshotHelper.java | 4 +-
.../org/apache/hadoop/hbase/util/MockServer.java | 24 +-
hbase-shell/pom.xml | 6 +
hbase-shell/src/main/ruby/hbase/hbase.rb | 26 +-
.../ruby/shell/commands/keymeta_command_base.rb | 2 +-
.../hadoop/hbase/client/TestKeymetaAdminShell.java | 127 ++++
.../src/test/ruby/shell/admin_keymeta_test.rb | 61 ++
.../ruby/shell/encrypted_table_keymeta_test.rb | 143 ++++
83 files changed, 2787 insertions(+), 585 deletions(-)
diff --git a/.gitignore b/.gitignore
index fc93b1447ba..274a0740c85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@ linklint/
**/*.log
tmp
**/.flattened-pom.xml
+.sw*
.*.sw*
ID
filenametags
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java
index b06ca9ce0d1..05a1a4b0b66 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java
@@ -27,7 +27,6 @@ import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.crypto.cipher.CryptoCipherFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.io.crypto.Cipher;
import org.apache.hadoop.hbase.io.crypto.Encryption;
import org.apache.hadoop.hbase.io.crypto.aes.CryptoAES;
@@ -219,57 +218,6 @@ public final class EncryptionUtil {
return getUnwrapKey(conf, subject, wrappedKey, cipher, null);
}
- /**
- * Helper to create an encyption context.
- * @param conf The current configuration.
- * @param family The current column descriptor.
- * @return The created encryption context.
- * @throws IOException if an encryption key for the column cannot be unwrapped
- * @throws IllegalStateException in case of encryption related configuration errors
- */
- public static Encryption.Context createEncryptionContext(Configuration conf,
- ColumnFamilyDescriptor family) throws IOException {
- Encryption.Context cryptoContext = Encryption.Context.NONE;
- String cipherName = family.getEncryptionType();
- if (cipherName != null) {
- if (!Encryption.isEncryptionEnabled(conf)) {
- throw new IllegalStateException("Encryption for family '" + family.getNameAsString()
- + "' configured with type '" + cipherName + "' but the encryption feature is disabled");
- }
- Cipher cipher;
- Key key;
- byte[] keyBytes = family.getEncryptionKey();
- if (keyBytes != null) {
- // Family provides specific key material
- key = unwrapKey(conf, keyBytes);
- // Use the algorithm the key wants
- cipher = Encryption.getCipher(conf, key.getAlgorithm());
- if (cipher == null) {
- throw new IllegalStateException("Cipher '" + key.getAlgorithm() + "' is not available");
- }
- // Fail if misconfigured
- // We use the encryption type specified in the column schema as a sanity check on
- // what the wrapped key is telling us
- if (!cipher.getName().equalsIgnoreCase(cipherName)) {
- throw new IllegalStateException(
- "Encryption for family '" + family.getNameAsString() + "' configured with type '"
- + cipherName + "' but key specifies algorithm '" + cipher.getName() + "'");
- }
- } else {
- // Family does not provide key material, create a random key
- cipher = Encryption.getCipher(conf, cipherName);
- if (cipher == null) {
- throw new IllegalStateException("Cipher '" + cipherName + "' is not available");
- }
- key = cipher.getRandomKey();
- }
- cryptoContext = Encryption.newContext(conf);
- cryptoContext.setCipher(cipher);
- cryptoContext.setKey(key);
- }
- return cryptoContext;
- }
-
/**
* Helper for {@link #unwrapKey(Configuration, String, byte[])} which automatically uses the
* configured master and alternative keys, rather than having to specify a key type to unwrap
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
index b9dfa9afc5d..2dca4f7e452 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
@@ -343,6 +343,7 @@ public final class HConstants {
/** Parameter name for HBase instance root directory */
public static final String HBASE_DIR = "hbase.rootdir";
+ public static final String HBASE_ORIGINAL_DIR = "hbase.originalRootdir";
/** Parameter name for HBase client IPC pool type */
public static final String HBASE_CLIENT_IPC_POOL_TYPE = "hbase.client.ipc.pool.type";
@@ -1342,6 +1343,11 @@ public final class HConstants {
"hbase.crypto.managed_keys.l1_active_cache.max_ns_entries";
public static final int CRYPTO_MANAGED_KEYS_L1_ACTIVE_CACHE_MAX_NS_ENTRIES_DEFAULT = 100;
+ /** Enables or disables local key generation per file. */
+ public static final String CRYPTO_MANAGED_KEYS_LOCAL_KEY_GEN_PER_FILE_ENABLED_CONF_KEY =
+ "hbase.crypto.managed_keys.local_key_gen_per_file.enabled";
+ public static final boolean CRYPTO_MANAGED_KEYS_LOCAL_KEY_GEN_PER_FILE_DEFAULT_ENABLED = false;
+
/** Configuration key for setting RPC codec class name */
public static final String RPC_CODEC_CONF_KEY = "hbase.client.rpc.codec";
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Context.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Context.java
index ce32351fecd..7e816b91762 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Context.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Context.java
@@ -34,6 +34,8 @@ public class Context implements Configurable {
private Configuration conf;
private Cipher cipher;
private Key key;
+ private ManagedKeyData kekData;
+ private String keyNamespace;
private String keyHash;
Context(Configuration conf) {
@@ -97,4 +99,22 @@ public class Context implements Configurable {
this.keyHash = new String(Hex.encodeHex(Encryption.computeCryptoKeyHash(conf, encoded)));
return this;
}
+
+ public Context setKeyNamespace(String keyNamespace) {
+ this.keyNamespace = keyNamespace;
+ return this;
+ }
+
+ public String getKeyNamespace() {
+ return keyNamespace;
+ }
+
+ public Context setKEKData(ManagedKeyData kekData) {
+ this.kekData = kekData;
+ return this;
+ }
+
+ public ManagedKeyData getKEKData() {
+ return kekData;
+ }
}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java
index a176a432942..91af77361a0 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java
@@ -117,12 +117,6 @@ public final class Encryption {
return this;
}
- @Override
- public Context setKey(Key key) {
- super.setKey(key);
- return this;
- }
-
public Context setKey(byte[] key) {
super.setKey(new SecretKeySpec(key, getCipher().getName()));
return this;
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/ManagedKeyData.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/ManagedKeyData.java
index e9c00935d38..ffd5dbb7b57 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/ManagedKeyData.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/ManagedKeyData.java
@@ -54,11 +54,17 @@ public class ManagedKeyData {
*/
public static final String KEY_SPACE_GLOBAL = "*";
+ /**
+ * Special value to be used for custodian to indicate that it is global, meaning it is not
+ * associated with a specific custodian.
+ */
+ public static final byte[] KEY_GLOBAL_CUSTODIAN_BYTES = KEY_SPACE_GLOBAL.getBytes();
+
/**
* Encoded form of global custodian.
*/
public static final String KEY_GLOBAL_CUSTODIAN =
- ManagedKeyProvider.encodeToStr(KEY_SPACE_GLOBAL.getBytes());
+ ManagedKeyProvider.encodeToStr(KEY_GLOBAL_CUSTODIAN_BYTES);
private final byte[] keyCustodian;
private final String keyNamespace;
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/MockAesKeyProvider.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/MockAesKeyProvider.java
index 0bb2aef7d99..39f460e062a 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/MockAesKeyProvider.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/MockAesKeyProvider.java
@@ -18,6 +18,8 @@
package org.apache.hadoop.hbase.io.crypto;
import java.security.Key;
+import java.util.HashMap;
+import java.util.Map;
import javax.crypto.spec.SecretKeySpec;
import org.apache.yetus.audience.InterfaceAudience;
@@ -27,8 +29,13 @@ import org.apache.yetus.audience.InterfaceAudience;
@InterfaceAudience.Private
public class MockAesKeyProvider implements KeyProvider {
+ private Map<String, Key> keys = new HashMap<>();
+
+ private boolean cacheKeys = false;
+
@Override
public void init(String parameters) {
+ cacheKeys = Boolean.parseBoolean(parameters);
}
@Override
@@ -40,7 +47,14 @@ public class MockAesKeyProvider implements KeyProvider {
public Key[] getKeys(String[] aliases) {
Key[] result = new Key[aliases.length];
for (int i = 0; i < aliases.length; i++) {
- result[i] = new SecretKeySpec(Encryption.hash128(aliases[i]), "AES");
+ if (keys.containsKey(aliases[i])) {
+ result[i] = keys.get(aliases[i]);
+ } else {
+ result[i] = new SecretKeySpec(Encryption.hash128(aliases[i]), "AES");
+ if (cacheKeys) {
+ keys.put(aliases[i], result[i]);
+ }
+ }
}
return result;
}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/CommonFSUtils.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/CommonFSUtils.java
index da4662d2c8a..d79cb6f3887 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/CommonFSUtils.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/CommonFSUtils.java
@@ -288,17 +288,48 @@ public final class CommonFSUtils {
* @throws IOException e
*/
public static Path getRootDir(final Configuration c) throws IOException {
- Path p = new Path(c.get(HConstants.HBASE_DIR));
+ return getRootDir(c, HConstants.HBASE_DIR);
+ }
+
+ /**
+ * Get the path for the original root data directory, which could be different from the current
+ * root directory, in case it was changed.
+ * @param c configuration
+ * @return {@link Path} to hbase original root directory from configuration as a qualified Path.
+ * @throws IOException e
+ */
+ public static Path getOriginalRootDir(final Configuration c) throws IOException {
+ return getRootDir(c,
+ c.get(HConstants.HBASE_ORIGINAL_DIR) == null
+ ? HConstants.HBASE_DIR
+ : HConstants.HBASE_ORIGINAL_DIR);
+ }
+
+ /**
+ * Get the path for the root data directory
+ * @param c configuration
+ * @param rootDirProp the property name for the root directory
+ * @return {@link Path} to hbase root directory from configuration as a qualified Path.
+ * @throws IOException e
+ */
+ public static Path getRootDir(final Configuration c, final String rootDirProp)
+ throws IOException {
+ Path p = new Path(c.get(rootDirProp));
FileSystem fs = p.getFileSystem(c);
return p.makeQualified(fs.getUri(), fs.getWorkingDirectory());
}
public static void setRootDir(final Configuration c, final Path root) {
+ // Keep track of the original root dir.
+ if (c.get(HConstants.HBASE_ORIGINAL_DIR) == null && c.get(HConstants.HBASE_DIR) != null) {
+ c.set(HConstants.HBASE_ORIGINAL_DIR, c.get(HConstants.HBASE_DIR));
+ }
c.set(HConstants.HBASE_DIR, root.toString());
}
public static Path getSystemKeyDir(final Configuration c) throws IOException {
- return new Path(getRootDir(c), HConstants.SYSTEM_KEYS_DIRECTORY);
+ // Always use the original root dir for system key dir, in case it was changed..
+ return new Path(getOriginalRootDir(c), HConstants.SYSTEM_KEYS_DIRECTORY);
}
public static void setFsDefault(final Configuration c, final Path root) {
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/KeymetaTestUtils.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/KeymetaTestUtils.java
new file mode 100644
index 00000000000..3a8fb3d3246
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/KeymetaTestUtils.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.io.crypto;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.util.Base64;
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Function;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.PositionedReadable;
+import org.apache.hadoop.fs.Seekable;
+import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.util.Bytes;
+
+import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
+
+public class KeymetaTestUtils {
+
+ /**
+ * A ByteArrayInputStream that implements Seekable and PositionedReadable to work with
+ * FSDataInputStream.
+ */
+ public static class SeekableByteArrayInputStream extends ByteArrayInputStream
+ implements Seekable, PositionedReadable {
+
+ public SeekableByteArrayInputStream(byte[] buf) {
+ super(buf);
+ }
+
+ @Override
+ public void seek(long pos) throws IOException {
+ if (pos < this.mark || pos > buf.length) {
+ throw new IOException("Seek position out of bounds: " + pos);
+ }
+ this.pos = (int) pos;
+ this.mark = (int) pos;
+ }
+
+ @Override
+ public long getPos() throws IOException {
+ return pos;
+ }
+
+ @Override
+ public boolean seekToNewSource(long targetPos) throws IOException {
+ return false; // No alternate sources
+ }
+
+ @Override
+ public int read(long position, byte[] buffer, int offset, int length) throws IOException {
+ if (position < 0 || position >= buf.length) {
+ return -1;
+ }
+ int currentPos = pos;
+ seek(position);
+ int bytesRead = read(buffer, offset, length);
+ pos = currentPos; // Restore original position
+ return bytesRead;
+ }
+
+ @Override
+ public void readFully(long position, byte[] buffer, int offset, int length) throws IOException {
+ int totalBytesRead = 0;
+ while (totalBytesRead < length) {
+ int bytesRead =
+ read(position + totalBytesRead, buffer, offset + totalBytesRead, length - totalBytesRead);
+ if (bytesRead == -1) {
+ throw new IOException("Reached end of stream before reading fully");
+ }
+ totalBytesRead += bytesRead;
+ }
+ }
+
+ @Override
+ public void readFully(long position, byte[] buffer) throws IOException {
+ readFully(position, buffer, 0, buffer.length);
+ }
+ }
+
+ private KeymetaTestUtils() {
+ // Utility class
+ }
+
+ public static final String ALIAS = "test";
+ public static final String PASSWORD = "password";
+
+ public static void addEntry(Configuration conf, int keyLen, KeyStore store, String alias,
+ String custodian, boolean withPasswordOnAlias, Map<Bytes, Bytes> cust2key,
+ Map<Bytes, String> cust2alias, Properties passwordFileProps) throws Exception {
+ Preconditions.checkArgument(keyLen == 256 || keyLen == 128, "Key length must be 256 or 128");
+ byte[] key =
+ MessageDigest.getInstance(keyLen == 256 ? "SHA-256" : "MD5").digest(Bytes.toBytes(alias));
+ cust2alias.put(new Bytes(custodian.getBytes()), alias);
+ cust2key.put(new Bytes(custodian.getBytes()), new Bytes(key));
+ store.setEntry(alias, new KeyStore.SecretKeyEntry(new SecretKeySpec(key, "AES")),
+ new KeyStore.PasswordProtection(withPasswordOnAlias ? PASSWORD.toCharArray() : new char[0]));
+ String encCust = Base64.getEncoder().encodeToString(custodian.getBytes());
+ String confKey = HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + encCust + "." + "alias";
+ conf.set(confKey, alias);
+ if (passwordFileProps != null) {
+ passwordFileProps.setProperty(alias, PASSWORD);
+ }
+ }
+
+ public static String setupTestKeyStore(HBaseCommonTestingUtil testUtil,
+ boolean withPasswordOnAlias, boolean withPasswordFile,
+ Function<KeyStore, Properties> customEntriesAdder) throws Exception {
+ KeyStore store = KeyStore.getInstance("JCEKS");
+ store.load(null, PASSWORD.toCharArray());
+ Properties passwordProps = null;
+ if (customEntriesAdder != null) {
+ passwordProps = customEntriesAdder.apply(store);
+ }
+ // Create the test directory
+ String dataDir = testUtil.getDataTestDir().toString();
+ new File(dataDir).mkdirs();
+ // Write the keystore file
+ File storeFile = new File(dataDir, "keystore.jks");
+ FileOutputStream os = new FileOutputStream(storeFile);
+ try {
+ store.store(os, PASSWORD.toCharArray());
+ } finally {
+ os.close();
+ }
+ File passwordFile = null;
+ if (withPasswordFile) {
+ passwordFile = new File(dataDir, "keystore.pw");
+ os = new FileOutputStream(passwordFile);
+ try {
+ passwordProps.store(os, "");
+ } finally {
+ os.close();
+ }
+ }
+ String providerParams;
+ if (withPasswordFile) {
+ providerParams = "jceks://" + storeFile.toURI().getPath() + "?passwordFile="
+ + URLEncoder.encode(passwordFile.getAbsolutePath(), "UTF-8");
+ } else {
+ providerParams = "jceks://" + storeFile.toURI().getPath() + "?password=" + PASSWORD;
+ }
+ return providerParams;
+ }
+
+ public static FileStatus createMockFile(String fileName) {
+ Path mockPath = mock(Path.class);
+ when(mockPath.getName()).thenReturn(fileName);
+ FileStatus mockFileStatus = mock(FileStatus.class);
+ when(mockFileStatus.getPath()).thenReturn(mockPath);
+ return mockFileStatus;
+ }
+
+ public static Path createMockPath(String tableName, String family) {
+ Path mockPath = mock(Path.class);
+ Path mockRegionDir = mock(Path.class);
+ Path mockTableDir = mock(Path.class);
+ Path mockNamespaceDir = mock(Path.class);
+ Path mockFamilyDir = mock(Path.class);
+ Path mockDataDir = mock(Path.class);
+ when(mockPath.getParent()).thenReturn(mockFamilyDir);
+ when(mockFamilyDir.getParent()).thenReturn(mockRegionDir);
+ when(mockRegionDir.getParent()).thenReturn(mockTableDir);
+ when(mockTableDir.getParent()).thenReturn(mockNamespaceDir);
+ when(mockNamespaceDir.getParent()).thenReturn(mockDataDir);
+ when(mockTableDir.getName()).thenReturn(tableName);
+ when(mockFamilyDir.getName()).thenReturn(family);
+ return mockPath;
+ }
+}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestKeyStoreKeyProvider.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestKeyStoreKeyProvider.java
index a0304e6337f..bb19d422200 100644
--- a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestKeyStoreKeyProvider.java
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestKeyStoreKeyProvider.java
@@ -17,12 +17,11 @@
*/
package org.apache.hadoop.hbase.io.crypto;
+import static org.apache.hadoop.hbase.io.crypto.KeymetaTestUtils.ALIAS;
+import static org.apache.hadoop.hbase.io.crypto.KeymetaTestUtils.PASSWORD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.net.URLEncoder;
import java.security.Key;
import java.security.KeyStore;
import java.security.MessageDigest;
@@ -51,12 +50,8 @@ public class TestKeyStoreKeyProvider {
HBaseClassTestRule.forClass(TestKeyStoreKeyProvider.class);
static final HBaseCommonTestingUtil TEST_UTIL = new HBaseCommonTestingUtil();
- static final String ALIAS = "test";
- static final String PASSWORD = "password";
static byte[] KEY;
- static File storeFile;
- static File passwordFile;
protected KeyProvider provider;
@@ -75,40 +70,21 @@ public class TestKeyStoreKeyProvider {
@Before
public void setUp() throws Exception {
KEY = MessageDigest.getInstance("SHA-256").digest(Bytes.toBytes(ALIAS));
- // Create a JKECS store containing a test secret key
- KeyStore store = KeyStore.getInstance("JCEKS");
- store.load(null, PASSWORD.toCharArray());
- store.setEntry(ALIAS, new KeyStore.SecretKeyEntry(new SecretKeySpec(KEY, "AES")),
- new KeyStore.PasswordProtection(withPasswordOnAlias ? PASSWORD.toCharArray() : new char[0]));
- Properties p = new Properties();
- addCustomEntries(store, p);
- // Create the test directory
- String dataDir = TEST_UTIL.getDataTestDir().toString();
- new File(dataDir).mkdirs();
- // Write the keystore file
- storeFile = new File(dataDir, "keystore.jks");
- FileOutputStream os = new FileOutputStream(storeFile);
- try {
- store.store(os, PASSWORD.toCharArray());
- } finally {
- os.close();
- }
- // Write the password file
- passwordFile = new File(dataDir, "keystore.pw");
- os = new FileOutputStream(passwordFile);
- try {
- p.store(os, "");
- } finally {
- os.close();
- }
-
+ String providerParams = KeymetaTestUtils.setupTestKeyStore(TEST_UTIL, withPasswordOnAlias,
+ withPasswordFile, store -> {
+ Properties p = new Properties();
+ try {
+ store.setEntry(ALIAS, new KeyStore.SecretKeyEntry(new SecretKeySpec(KEY, "AES")),
+ new KeyStore.PasswordProtection(
+ withPasswordOnAlias ? PASSWORD.toCharArray() : new char[0]));
+ addCustomEntries(store, p);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return p;
+ });
provider = createProvider();
- if (withPasswordFile) {
- provider.init("jceks://" + storeFile.toURI().getPath() + "?passwordFile="
- + URLEncoder.encode(passwordFile.getAbsolutePath(), "UTF-8"));
- } else {
- provider.init("jceks://" + storeFile.toURI().getPath() + "?password=" + PASSWORD);
- }
+ provider.init(providerParams);
}
protected KeyProvider createProvider() {
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestManagedKeyProvider.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestManagedKeyProvider.java
index 472ce56405a..405c5731be9 100644
--- a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestManagedKeyProvider.java
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestManagedKeyProvider.java
@@ -17,6 +17,7 @@
*/
package org.apache.hadoop.hbase.io.crypto;
+import static org.apache.hadoop.hbase.io.crypto.ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES;
import static org.apache.hadoop.hbase.io.crypto.ManagedKeyStoreKeyProvider.KEY_METADATA_ALIAS;
import static org.apache.hadoop.hbase.io.crypto.ManagedKeyStoreKeyProvider.KEY_METADATA_CUST;
import static org.junit.Assert.assertEquals;
@@ -26,14 +27,12 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import java.security.KeyStore;
-import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
-import javax.crypto.spec.SecretKeySpec;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseConfiguration;
@@ -66,10 +65,10 @@ public class TestManagedKeyProvider {
private static final String SYSTEM_KEY_ALIAS = "system-alias";
private Configuration conf = HBaseConfiguration.create();
- private int nPrefixes = 2;
+ private int nCustodians = 2;
private ManagedKeyProvider managedKeyProvider;
- private Map<Bytes, Bytes> prefix2key = new HashMap<>();
- private Map<Bytes, String> prefix2alias = new HashMap<>();
+ private Map<Bytes, Bytes> cust2key = new HashMap<>();
+ private Map<Bytes, String> cust2alias = new HashMap<>();
private String clusterId;
private byte[] systemKey;
@@ -86,41 +85,21 @@ public class TestManagedKeyProvider {
protected void addCustomEntries(KeyStore store, Properties passwdProps) throws Exception {
super.addCustomEntries(store, passwdProps);
- for (int i = 0; i < nPrefixes; ++i) {
- String prefix = "prefix+ " + i;
- String alias = prefix + "-alias";
- byte[] key = MessageDigest.getInstance("SHA-256").digest(Bytes.toBytes(alias));
- prefix2alias.put(new Bytes(prefix.getBytes()), alias);
- prefix2key.put(new Bytes(prefix.getBytes()), new Bytes(key));
- store.setEntry(alias, new KeyStore.SecretKeyEntry(new SecretKeySpec(key, "AES")),
- new KeyStore.PasswordProtection(
- withPasswordOnAlias ? PASSWORD.toCharArray() : new char[0]));
-
- String encPrefix = Base64.getEncoder().encodeToString(prefix.getBytes());
- String confKey =
- HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + encPrefix + "." + "alias";
- conf.set(confKey, alias);
-
- passwdProps.setProperty(alias, PASSWORD);
-
- clusterId = UUID.randomUUID().toString();
- systemKey = MessageDigest.getInstance("SHA-256").digest(Bytes.toBytes(SYSTEM_KEY_ALIAS));
- store.setEntry(SYSTEM_KEY_ALIAS,
- new KeyStore.SecretKeyEntry(new SecretKeySpec(systemKey, "AES")),
- new KeyStore.PasswordProtection(
- withPasswordOnAlias ? PASSWORD.toCharArray() : new char[0]));
-
- conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_SYSTEM_KEY_NAME_CONF_KEY, SYSTEM_KEY_ALIAS);
-
- passwdProps.setProperty(SYSTEM_KEY_ALIAS, PASSWORD);
+ for (int i = 0; i < nCustodians; ++i) {
+ String custodian = "custodian+ " + i;
+ String alias = custodian + "-alias";
+ KeymetaTestUtils.addEntry(conf, 256, store, alias, custodian, withPasswordOnAlias, cust2key,
+ cust2alias, passwdProps);
}
- }
- private void addEntry(String alias, String prefix) {
- String encPrefix = Base64.getEncoder().encodeToString(prefix.getBytes());
- String confKey =
- HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + encPrefix + "." + "alias";
- conf.set(confKey, alias);
+ clusterId = UUID.randomUUID().toString();
+ KeymetaTestUtils.addEntry(conf, 256, store, SYSTEM_KEY_ALIAS, clusterId, withPasswordOnAlias,
+ cust2key, cust2alias, passwdProps);
+ systemKey = cust2key.get(new Bytes(clusterId.getBytes())).get();
+ conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_SYSTEM_KEY_NAME_CONF_KEY, SYSTEM_KEY_ALIAS);
+
+ KeymetaTestUtils.addEntry(conf, 256, store, "global-cust-alias", "*", withPasswordOnAlias,
+ cust2key, cust2alias, passwdProps);
}
@Test
@@ -133,46 +112,54 @@ public class TestManagedKeyProvider {
@Test
public void testGetManagedKey() throws Exception {
- for (Bytes prefix : prefix2key.keySet()) {
+ for (Bytes cust : cust2key.keySet()) {
ManagedKeyData keyData =
- managedKeyProvider.getManagedKey(prefix.get(), ManagedKeyData.KEY_SPACE_GLOBAL);
- assertKeyData(keyData, ManagedKeyState.ACTIVE, prefix2key.get(prefix).get(), prefix.get(),
- prefix2alias.get(prefix));
+ managedKeyProvider.getManagedKey(cust.get(), ManagedKeyData.KEY_SPACE_GLOBAL);
+ assertKeyData(keyData, ManagedKeyState.ACTIVE, cust2key.get(cust).get(), cust.get(),
+ cust2alias.get(cust));
}
}
+ @Test
+ public void testGetGlobalCustodianKey() throws Exception {
+ byte[] globalCustodianKey = cust2key.get(new Bytes(KEY_GLOBAL_CUSTODIAN_BYTES)).get();
+ ManagedKeyData keyData = managedKeyProvider.getManagedKey(KEY_GLOBAL_CUSTODIAN_BYTES,
+ ManagedKeyData.KEY_SPACE_GLOBAL);
+ assertKeyData(keyData, ManagedKeyState.ACTIVE, globalCustodianKey, KEY_GLOBAL_CUSTODIAN_BYTES,
+ "global-cust-alias");
+ }
+
@Test
public void testGetInactiveKey() throws Exception {
- Bytes firstPrefix = prefix2key.keySet().iterator().next();
- String encPrefix = Base64.getEncoder().encodeToString(firstPrefix.get());
- conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + encPrefix + ".active",
- "false");
+ Bytes firstCust = cust2key.keySet().iterator().next();
+ String encCust = Base64.getEncoder().encodeToString(firstCust.get());
+ conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + encCust + ".active", "false");
ManagedKeyData keyData =
- managedKeyProvider.getManagedKey(firstPrefix.get(), ManagedKeyData.KEY_SPACE_GLOBAL);
+ managedKeyProvider.getManagedKey(firstCust.get(), ManagedKeyData.KEY_SPACE_GLOBAL);
assertNotNull(keyData);
- assertKeyData(keyData, ManagedKeyState.INACTIVE, prefix2key.get(firstPrefix).get(),
- firstPrefix.get(), prefix2alias.get(firstPrefix));
+ assertKeyData(keyData, ManagedKeyState.INACTIVE, cust2key.get(firstCust).get(),
+ firstCust.get(), cust2alias.get(firstCust));
}
@Test
public void testGetInvalidKey() throws Exception {
- byte[] invalidPrefixBytes = "invalid".getBytes();
+ byte[] invalidCustBytes = "invalid".getBytes();
ManagedKeyData keyData =
- managedKeyProvider.getManagedKey(invalidPrefixBytes, ManagedKeyData.KEY_SPACE_GLOBAL);
+ managedKeyProvider.getManagedKey(invalidCustBytes, ManagedKeyData.KEY_SPACE_GLOBAL);
assertNotNull(keyData);
- assertKeyData(keyData, ManagedKeyState.FAILED, null, invalidPrefixBytes, null);
+ assertKeyData(keyData, ManagedKeyState.FAILED, null, invalidCustBytes, null);
}
@Test
public void testGetDisabledKey() throws Exception {
- byte[] invalidPrefix = new byte[] { 1, 2, 3 };
- String invalidPrefixEnc = ManagedKeyProvider.encodeToStr(invalidPrefix);
- conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + invalidPrefixEnc + ".active",
+ byte[] invalidCust = new byte[] { 1, 2, 3 };
+ String invalidCustEnc = ManagedKeyProvider.encodeToStr(invalidCust);
+ conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + invalidCustEnc + ".active",
"false");
ManagedKeyData keyData =
- managedKeyProvider.getManagedKey(invalidPrefix, ManagedKeyData.KEY_SPACE_GLOBAL);
+ managedKeyProvider.getManagedKey(invalidCust, ManagedKeyData.KEY_SPACE_GLOBAL);
assertNotNull(keyData);
- assertKeyData(keyData, ManagedKeyState.DISABLED, null, invalidPrefix, null);
+ assertKeyData(keyData, ManagedKeyState.DISABLED, null, invalidCust, null);
}
@Test
@@ -192,31 +179,31 @@ public class TestManagedKeyProvider {
@Test
public void testUnwrapInvalidKey() throws Exception {
String invalidAlias = "invalidAlias";
- byte[] invalidPrefix = new byte[] { 1, 2, 3 };
- String invalidPrefixEnc = ManagedKeyProvider.encodeToStr(invalidPrefix);
+ byte[] invalidCust = new byte[] { 1, 2, 3 };
+ String invalidCustEnc = ManagedKeyProvider.encodeToStr(invalidCust);
String invalidMetadata =
- ManagedKeyStoreKeyProvider.generateKeyMetadata(invalidAlias, invalidPrefixEnc);
+ ManagedKeyStoreKeyProvider.generateKeyMetadata(invalidAlias, invalidCustEnc);
ManagedKeyData keyData = managedKeyProvider.unwrapKey(invalidMetadata, null);
assertNotNull(keyData);
- assertKeyData(keyData, ManagedKeyState.FAILED, null, invalidPrefix, invalidAlias);
+ assertKeyData(keyData, ManagedKeyState.FAILED, null, invalidCust, invalidAlias);
}
@Test
public void testUnwrapDisabledKey() throws Exception {
String invalidAlias = "invalidAlias";
- byte[] invalidPrefix = new byte[] { 1, 2, 3 };
- String invalidPrefixEnc = ManagedKeyProvider.encodeToStr(invalidPrefix);
- conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + invalidPrefixEnc + ".active",
+ byte[] invalidCust = new byte[] { 1, 2, 3 };
+ String invalidCustEnc = ManagedKeyProvider.encodeToStr(invalidCust);
+ conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_CONF_KEY_PREFIX + invalidCustEnc + ".active",
"false");
String invalidMetadata =
- ManagedKeyStoreKeyProvider.generateKeyMetadata(invalidAlias, invalidPrefixEnc);
+ ManagedKeyStoreKeyProvider.generateKeyMetadata(invalidAlias, invalidCustEnc);
ManagedKeyData keyData = managedKeyProvider.unwrapKey(invalidMetadata, null);
assertNotNull(keyData);
- assertKeyData(keyData, ManagedKeyState.DISABLED, null, invalidPrefix, invalidAlias);
+ assertKeyData(keyData, ManagedKeyState.DISABLED, null, invalidCust, invalidAlias);
}
private void assertKeyData(ManagedKeyData keyData, ManagedKeyState expKeyState, byte[] key,
- byte[] prefixBytes, String alias) throws Exception {
+ byte[] custBytes, String alias) throws Exception {
assertNotNull(keyData);
assertEquals(expKeyState, keyData.getKeyState());
if (key == null) {
@@ -229,9 +216,9 @@ public class TestManagedKeyProvider {
Map keyMetadata =
GsonUtil.getDefaultInstance().fromJson(keyData.getKeyMetadata(), HashMap.class);
assertNotNull(keyMetadata);
- assertEquals(new Bytes(prefixBytes), keyData.getKeyCustodian());
+ assertEquals(new Bytes(custBytes), keyData.getKeyCustodian());
assertEquals(alias, keyMetadata.get(KEY_METADATA_ALIAS));
- assertEquals(Base64.getEncoder().encodeToString(prefixBytes),
+ assertEquals(Base64.getEncoder().encodeToString(custBytes),
keyMetadata.get(KEY_METADATA_CUST));
assertEquals(keyData, managedKeyProvider.unwrapKey(keyData.getKeyMetadata(), null));
}
diff --git a/hbase-protocol-shaded/src/main/protobuf/server/io/HFile.proto b/hbase-protocol-shaded/src/main/protobuf/server/io/HFile.proto
index fd1b9b3680d..26a343a5d04 100644
--- a/hbase-protocol-shaded/src/main/protobuf/server/io/HFile.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/server/io/HFile.proto
@@ -51,4 +51,7 @@ message FileTrailerProto {
optional string comparator_class_name = 11;
optional uint32 compression_codec = 12;
optional bytes encryption_key = 13;
+ optional string key_namespace = 14;
+ optional string kek_metadata = 15;
+ optional uint64 kek_checksum = 16;
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/HBaseServerBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/HBaseServerBase.java
index 12cc7433e7b..0993fc0f09d 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/HBaseServerBase.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/HBaseServerBase.java
@@ -52,6 +52,7 @@ import org.apache.hadoop.hbase.fs.HFileSystem;
import org.apache.hadoop.hbase.http.InfoServer;
import org.apache.hadoop.hbase.io.util.MemorySizeUtil;
import org.apache.hadoop.hbase.ipc.RpcServerInterface;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
import org.apache.hadoop.hbase.keymeta.KeymetaAdminImpl;
import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
@@ -92,7 +93,7 @@ import org.slf4j.LoggerFactory;
*/
@InterfaceAudience.Private
public abstract class HBaseServerBase<R extends HBaseRpcServicesBase<?>> extends Thread
- implements Server, ConfigurationObserver, ConnectionRegistryEndpoint {
+ implements Server, ConfigurationObserver, ConnectionRegistryEndpoint, KeyManagementService {
private static final Logger LOG = LoggerFactory.getLogger(HBaseServerBase.class);
@@ -661,6 +662,11 @@ public abstract class HBaseServerBase<R extends HBaseRpcServicesBase<?>> extends
postUpdateConfiguration();
}
+ @Override
+ public KeyManagementService getKeyManagementService() {
+ return this;
+ }
+
private void preUpdateConfiguration() throws IOException {
CoprocessorHost<?, ?> coprocessorHost = getCoprocessorHost();
if (coprocessorHost instanceof RegionServerCoprocessorHost) {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/MockRegionServerServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/MockRegionServerServices.java
index 39d09ab170f..4b5d36382ef 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/MockRegionServerServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/MockRegionServerServices.java
@@ -38,6 +38,7 @@ import org.apache.hadoop.hbase.executor.ExecutorService;
import org.apache.hadoop.hbase.fs.HFileSystem;
import org.apache.hadoop.hbase.io.hfile.BlockCache;
import org.apache.hadoop.hbase.ipc.RpcServerInterface;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
@@ -404,4 +405,9 @@ public class MockRegionServerServices implements RegionServerServices {
public RegionReplicationBufferManager getRegionReplicationBufferManager() {
return null;
}
+
+ @Override
+ public KeyManagementService getKeyManagementService() {
+ return this;
+ }
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/Server.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/Server.java
index c1a6d7dc9ec..ba258d14add 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/Server.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/Server.java
@@ -23,9 +23,7 @@ import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.hbase.client.AsyncClusterConnection;
import org.apache.hadoop.hbase.client.AsyncConnection;
import org.apache.hadoop.hbase.client.Connection;
-import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
-import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
-import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
import org.apache.yetus.audience.InterfaceAudience;
@@ -86,15 +84,6 @@ public interface Server extends Abortable, Stoppable {
/** Returns The {@link ChoreService} instance for this server */
ChoreService getChoreService();
- /** Returns the cache for cluster keys. */
- public SystemKeyCache getSystemKeyCache();
-
- /** Returns the cache for managed keys. */
- public ManagedKeyDataCache getManagedKeyDataCache();
-
- /** Returns the admin for keymeta. */
- public KeymetaAdmin getKeymetaAdmin();
-
/** Returns Return the FileSystem object used (can return null!). */
// TODO: Distinguish between "dataFs" and "walFs".
default FileSystem getFileSystem() {
@@ -116,4 +105,7 @@ public interface Server extends Abortable, Stoppable {
default boolean isStopping() {
return false;
}
+
+ /** Returns the KeyManagementService instance for this server. */
+ KeyManagementService getKeyManagementService();
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/client/ClientSideRegionScanner.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/client/ClientSideRegionScanner.java
index df99fd40338..eb7c77554b0 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/client/ClientSideRegionScanner.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/client/ClientSideRegionScanner.java
@@ -63,7 +63,7 @@ public class ClientSideRegionScanner extends AbstractClientScanner {
// open region from the snapshot directory
region = HRegion.newHRegion(CommonFSUtils.getTableDir(rootDir, htd.getTableName()), null, fs,
- conf, hri, htd, null);
+ conf, hri, htd, null, null);
region.setRestoredRegion(true);
// non RS process does not have a block cache, and this a client side scanner,
// create one for MapReduce jobs to cache the INDEX block by setting to use
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/HFileLink.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/HFileLink.java
index bd5fac1c3c4..85201ccd8bd 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/HFileLink.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/HFileLink.java
@@ -174,6 +174,15 @@ public class HFileLink extends FileLink {
return this.mobPath;
}
+ /**
+ * Get the table name and family name from the origin path.
+ * @return the table name and family name
+ */
+ public Pair<String, String> getTableNameAndFamilyName() {
+ return new Pair<>(this.originPath.getParent().getName(),
+ this.originPath.getParent().getParent().getParent().getName());
+ }
+
/**
* @param path Path to check.
* @return True if the path is a HFileLink.
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/FixedFileTrailer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/FixedFileTrailer.java
index 74b560022a8..d3337d24712 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/FixedFileTrailer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/FixedFileTrailer.java
@@ -130,6 +130,21 @@ public class FixedFileTrailer {
*/
private byte[] encryptionKey;
+ /**
+ * The key namespace
+ */
+ private String keyNamespace;
+
+ /**
+ * The KEK checksum
+ */
+ private long kekChecksum;
+
+ /**
+ * The KEK metadata
+ */
+ private String kekMetadata;
+
/**
* The {@link HFile} format major version.
*/
@@ -211,6 +226,15 @@ public class FixedFileTrailer {
if (encryptionKey != null) {
builder.setEncryptionKey(UnsafeByteOperations.unsafeWrap(encryptionKey));
}
+ if (keyNamespace != null) {
+ builder.setKeyNamespace(keyNamespace);
+ }
+ if (kekMetadata != null) {
+ builder.setKekMetadata(kekMetadata);
+ }
+ if (kekChecksum != 0) {
+ builder.setKekChecksum(kekChecksum);
+ }
return builder.build();
}
@@ -313,6 +337,15 @@ public class FixedFileTrailer {
if (trailerProto.hasEncryptionKey()) {
encryptionKey = trailerProto.getEncryptionKey().toByteArray();
}
+ if (trailerProto.hasKeyNamespace()) {
+ keyNamespace = trailerProto.getKeyNamespace();
+ }
+ if (trailerProto.hasKekMetadata()) {
+ kekMetadata = trailerProto.getKekMetadata();
+ }
+ if (trailerProto.hasKekChecksum()) {
+ kekChecksum = trailerProto.getKekChecksum();
+ }
}
/**
@@ -362,6 +395,9 @@ public class FixedFileTrailer {
if (majorVersion >= 3) {
append(sb, "encryptionKey=" + (encryptionKey != null ? "PRESENT" : "NONE"));
}
+ if (keyNamespace != null) {
+ append(sb, "keyNamespace=" + keyNamespace);
+ }
append(sb, "majorVersion=" + majorVersion);
append(sb, "minorVersion=" + minorVersion);
@@ -641,10 +677,34 @@ public class FixedFileTrailer {
return encryptionKey;
}
+ public String getKeyNamespace() {
+ return keyNamespace;
+ }
+
+ public void setKeyNamespace(String keyNamespace) {
+ this.keyNamespace = keyNamespace;
+ }
+
+ public void setKEKChecksum(long kekChecksum) {
+ this.kekChecksum = kekChecksum;
+ }
+
+ public long getKEKChecksum() {
+ return kekChecksum;
+ }
+
public void setEncryptionKey(byte[] keyBytes) {
this.encryptionKey = keyBytes;
}
+ public String getKEKMetadata() {
+ return kekMetadata;
+ }
+
+ public void setKEKMetadata(String kekMetadata) {
+ this.kekMetadata = kekMetadata;
+ }
+
/**
* Extracts the major version for a 4-byte serialized version data. The major version is the 3
* least significant bytes
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFile.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFile.java
index a99eac4085e..6392b36ef12 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFile.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFile.java
@@ -41,9 +41,13 @@ import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
import org.apache.hadoop.hbase.io.hfile.ReaderContext.ReaderType;
import org.apache.hadoop.hbase.ipc.RpcServer;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.hadoop.hbase.regionserver.CellSink;
import org.apache.hadoop.hbase.regionserver.ShipperListener;
import org.apache.hadoop.hbase.regionserver.TimeRangeTracker;
+import org.apache.hadoop.hbase.security.SecurityUtil;
import org.apache.hadoop.hbase.util.BloomFilterWriter;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.FSUtils;
@@ -554,10 +558,18 @@ public final class HFile {
boolean primaryReplicaReader, Configuration conf) throws IOException {
Preconditions.checkNotNull(cacheConf, "Cannot create Reader with null CacheConf");
FSDataInputStreamWrapper stream = new FSDataInputStreamWrapper(fs, path);
+ KeyManagementService keyManagementService = SecurityUtil.isKeyManagementEnabled(conf)
+ ? KeyManagementService.createDefault(conf, fs)
+ : null;
+ ManagedKeyDataCache managedKeyDataCache =
+ keyManagementService != null ? keyManagementService.getManagedKeyDataCache() : null;
+ SystemKeyCache systemKeyCache =
+ keyManagementService != null ? keyManagementService.getSystemKeyCache() : null;
ReaderContext context =
new ReaderContextBuilder().withFilePath(path).withInputStreamWrapper(stream)
.withFileSize(fs.getFileStatus(path).getLen()).withFileSystem(stream.getHfs())
- .withPrimaryReplicaReader(primaryReplicaReader).withReaderType(ReaderType.PREAD).build();
+ .withPrimaryReplicaReader(primaryReplicaReader).withReaderType(ReaderType.PREAD)
+ .withManagedKeyDataCache(managedKeyDataCache).withSystemKeyCache(systemKeyCache).build();
HFileInfo fileInfo = new HFileInfo(context, conf);
Reader reader = createReader(context, fileInfo, cacheConf, conf);
fileInfo.initMetaAndIndex(reader);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileInfo.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileInfo.java
index 2386e8d82a5..b3da98f1343 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileInfo.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileInfo.java
@@ -22,7 +22,6 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
-import java.security.Key;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
@@ -39,10 +38,8 @@ import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.ExtendedCell;
import org.apache.hadoop.hbase.KeyValue;
-import org.apache.hadoop.hbase.io.crypto.Cipher;
-import org.apache.hadoop.hbase.io.crypto.Encryption;
import org.apache.hadoop.hbase.protobuf.ProtobufMagic;
-import org.apache.hadoop.hbase.security.EncryptionUtil;
+import org.apache.hadoop.hbase.security.SecurityUtil;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
@@ -351,7 +348,7 @@ public class HFileInfo implements SortedMap<byte[], byte[]> {
context.getInputStreamWrapper().getStream(isHBaseChecksum), context.getFileSize());
Path path = context.getFilePath();
checkFileVersion(path);
- this.hfileContext = createHFileContext(path, trailer, conf);
+ this.hfileContext = createHFileContext(context, path, trailer, conf);
context.getInputStreamWrapper().unbuffer();
} catch (Throwable t) {
IOUtils.closeQuietly(context.getInputStreamWrapper(),
@@ -409,30 +406,16 @@ public class HFileInfo implements SortedMap<byte[], byte[]> {
initialized = true;
}
- private HFileContext createHFileContext(Path path, FixedFileTrailer trailer, Configuration conf)
- throws IOException {
- HFileContextBuilder builder = new HFileContextBuilder().withHBaseCheckSum(true)
- .withHFileName(path.getName()).withCompression(trailer.getCompressionCodec())
+ private HFileContext createHFileContext(ReaderContext readerContext, Path path,
+ FixedFileTrailer trailer, Configuration conf) throws IOException {
+ return new HFileContextBuilder().withHBaseCheckSum(true).withHFileName(path.getName())
+ .withCompression(trailer.getCompressionCodec())
.withDecompressionContext(
trailer.getCompressionCodec().getHFileDecompressionContextForConfiguration(conf))
- .withCellComparator(FixedFileTrailer.createComparator(trailer.getComparatorClassName()));
- // Check for any key material available
- byte[] keyBytes = trailer.getEncryptionKey();
- if (keyBytes != null) {
- Encryption.Context cryptoContext = Encryption.newContext(conf);
- Key key = EncryptionUtil.unwrapKey(conf, keyBytes);
- // Use the algorithm the key wants
- Cipher cipher = Encryption.getCipher(conf, key.getAlgorithm());
- if (cipher == null) {
- throw new IOException(
- "Cipher '" + key.getAlgorithm() + "' is not available" + ", path=" + path);
- }
- cryptoContext.setCipher(cipher);
- cryptoContext.setKey(key);
- builder.withEncryptionContext(cryptoContext);
- }
- HFileContext context = builder.build();
- return context;
+ .withCellComparator(FixedFileTrailer.createComparator(trailer.getComparatorClassName()))
+ .withEncryptionContext(SecurityUtil.createEncryptionContext(conf, path, trailer,
+ readerContext.getManagedKeyDataCache(), readerContext.getSystemKeyCache()))
+ .build();
}
private void loadMetaInfo(HFileBlock.BlockIterator blockIter, HFileContext hfileContext)
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileWriterImpl.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileWriterImpl.java
index 684aee3beac..2b74d177a4f 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileWriterImpl.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileWriterImpl.java
@@ -27,6 +27,7 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
+import java.security.Key;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -48,6 +49,7 @@ import org.apache.hadoop.hbase.MetaCellComparator;
import org.apache.hadoop.hbase.PrivateCellUtil;
import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.io.crypto.Encryption;
+import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
import org.apache.hadoop.hbase.io.encoding.IndexBlockEncoding;
import org.apache.hadoop.hbase.io.hfile.HFileBlock.BlockWritable;
@@ -877,12 +879,33 @@ public class HFileWriterImpl implements HFile.Writer {
// Write out encryption metadata before finalizing if we have a valid crypto context
Encryption.Context cryptoContext = hFileContext.getEncryptionContext();
if (cryptoContext != Encryption.Context.NONE) {
+ String wrapperSubject = null;
+ Key encKey = null;
+ Key wrapperKey = null;
+ ManagedKeyData kekData = cryptoContext.getKEKData();
+ String keyNamespace = cryptoContext.getKeyNamespace();
+ String kekMetadata = null;
+ long kekChecksum = 0;
+ if (kekData != null) {
+ kekMetadata = kekData.getKeyMetadata();
+ kekChecksum = kekData.getKeyChecksum();
+ wrapperKey = kekData.getTheKey();
+ encKey = cryptoContext.getKey();
+ } else {
+ wrapperSubject = cryptoContext.getConf().get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY,
+ User.getCurrent().getShortName());
+ encKey = cryptoContext.getKey();
+ }
// Wrap the context's key and write it as the encryption metadata, the wrapper includes
// all information needed for decryption
- trailer.setEncryptionKey(EncryptionUtil.wrapKey(
- cryptoContext.getConf(), cryptoContext.getConf()
- .get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()),
- cryptoContext.getKey()));
+ if (encKey != null) {
+ byte[] wrappedKey =
+ EncryptionUtil.wrapKey(cryptoContext.getConf(), wrapperSubject, encKey, wrapperKey);
+ trailer.setEncryptionKey(wrappedKey);
+ }
+ trailer.setKeyNamespace(keyNamespace);
+ trailer.setKEKMetadata(kekMetadata);
+ trailer.setKEKChecksum(kekChecksum);
}
// Now we can finish the close
trailer.setMetaIndexCount(metaNames.size());
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/ReaderContext.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/ReaderContext.java
index d6f711d866e..ac2031b723a 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/ReaderContext.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/ReaderContext.java
@@ -21,6 +21,8 @@ import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.fs.HFileSystem;
import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.yetus.audience.InterfaceAudience;
/**
@@ -41,9 +43,12 @@ public class ReaderContext {
private final boolean primaryReplicaReader;
private final ReaderType type;
private final boolean preadAllBytes;
+ private final SystemKeyCache systemKeyCache;
+ private final ManagedKeyDataCache managedKeyDataCache;
public ReaderContext(Path filePath, FSDataInputStreamWrapper fsdis, long fileSize,
- HFileSystem hfs, boolean primaryReplicaReader, ReaderType type) {
+ HFileSystem hfs, boolean primaryReplicaReader, ReaderType type, SystemKeyCache systemKeyCache,
+ ManagedKeyDataCache managedKeyDataCache) {
this.filePath = filePath;
this.fsdis = fsdis;
this.fileSize = fileSize;
@@ -52,6 +57,8 @@ public class ReaderContext {
this.type = type;
this.preadAllBytes = hfs.getConf().getBoolean(HConstants.HFILE_PREAD_ALL_BYTES_ENABLED_KEY,
HConstants.HFILE_PREAD_ALL_BYTES_ENABLED_DEFAULT);
+ this.systemKeyCache = systemKeyCache;
+ this.managedKeyDataCache = managedKeyDataCache;
}
public Path getFilePath() {
@@ -81,4 +88,12 @@ public class ReaderContext {
public boolean isPreadAllBytes() {
return preadAllBytes;
}
+
+ public SystemKeyCache getSystemKeyCache() {
+ return this.systemKeyCache;
+ }
+
+ public ManagedKeyDataCache getManagedKeyDataCache() {
+ return this.managedKeyDataCache;
+ }
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/ReaderContextBuilder.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/ReaderContextBuilder.java
index 718f7fcb78a..1490299ab1f 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/ReaderContextBuilder.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/ReaderContextBuilder.java
@@ -26,6 +26,8 @@ import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.fs.HFileSystem;
import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
import org.apache.hadoop.hbase.io.hfile.ReaderContext.ReaderType;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.yetus.audience.InterfaceAudience;
/**
@@ -39,6 +41,8 @@ public class ReaderContextBuilder {
private HFileSystem hfs;
private boolean primaryReplicaReader = true;
private ReaderType type = ReaderType.PREAD;
+ private SystemKeyCache systemKeyCache;
+ private ManagedKeyDataCache managedKeyDataCache;
public ReaderContextBuilder() {
}
@@ -53,6 +57,8 @@ public class ReaderContextBuilder {
this.fileSize = readerContext.getFileSize();
this.hfs = readerContext.getFileSystem();
this.type = readerContext.getReaderType();
+ this.systemKeyCache = readerContext.getSystemKeyCache();
+ this.managedKeyDataCache = readerContext.getManagedKeyDataCache();
}
public ReaderContextBuilder withFilePath(Path filePath) {
@@ -101,9 +107,20 @@ public class ReaderContextBuilder {
return this;
}
+ public ReaderContextBuilder withManagedKeyDataCache(ManagedKeyDataCache managedKeyDataCache) {
+ this.managedKeyDataCache = managedKeyDataCache;
+ return this;
+ }
+
+ public ReaderContextBuilder withSystemKeyCache(SystemKeyCache systemKeyCache) {
+ this.systemKeyCache = systemKeyCache;
+ return this;
+ }
+
public ReaderContext build() {
validateFields();
- return new ReaderContext(filePath, fsdis, fileSize, hfs, primaryReplicaReader, type);
+ return new ReaderContext(filePath, fsdis, fileSize, hfs, primaryReplicaReader, type,
+ systemKeyCache, managedKeyDataCache);
}
private void validateFields() throws IllegalArgumentException {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyManagementBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyManagementBase.java
index 1e4ee2a3e79..957c3c8f726 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyManagementBase.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyManagementBase.java
@@ -21,7 +21,6 @@ import java.io.IOException;
import java.security.KeyException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.io.crypto.Encryption;
import org.apache.hadoop.hbase.io.crypto.KeyProvider;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
@@ -38,20 +37,19 @@ import org.slf4j.LoggerFactory;
public abstract class KeyManagementBase {
protected static final Logger LOG = LoggerFactory.getLogger(KeyManagementBase.class);
- private Server server;
+ private KeyManagementService keyManagementService;
private final Configuration configuration;
private Boolean isDynamicLookupEnabled;
private Boolean isKeyManagementEnabled;
- private Integer perCustNamespaceActiveKeyCount;
/**
* Construct with a server instance. Configuration is derived from the server.
* @param server the server instance
*/
- public KeyManagementBase(Server server) {
- this(server.getConfiguration());
- this.server = server;
+ public KeyManagementBase(KeyManagementService keyManagementService) {
+ this(keyManagementService.getConfiguration());
+ this.keyManagementService = keyManagementService;
}
/**
@@ -65,8 +63,8 @@ public abstract class KeyManagementBase {
this.configuration = configuration;
}
- protected Server getServer() {
- return server;
+ protected KeyManagementService getKeyManagementService() {
+ return keyManagementService;
}
protected Configuration getConfiguration() {
@@ -150,7 +148,7 @@ public abstract class KeyManagementBase {
LOG.info(
"retrieveManagedKey: got managed key with status: {} and metadata: {} for "
+ "(custodian: {}, namespace: {})",
- pbeKey.getKeyState(), pbeKey.getKeyMetadata(), encKeyCust, keyNamespace);
+ pbeKey.getKeyState(), pbeKey.getKeyMetadata(), encKeyCust, pbeKey.getKeyNamespace());
if (accessor != null) {
accessor.addKey(pbeKey);
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyManagementService.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyManagementService.java
new file mode 100644
index 00000000000..bdb76f5bbe6
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyManagementService.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.keymeta;
+
+import java.io.IOException;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.yetus.audience.InterfaceAudience;
+
+@InterfaceAudience.Private
+public interface KeyManagementService {
+ class DefaultKeyManagementService implements KeyManagementService {
+ private final Configuration configuration;
+ private final ManagedKeyDataCache managedKeyDataCache;
+ private final SystemKeyCache systemKeyCache;
+
+ public DefaultKeyManagementService(Configuration configuration, FileSystem fs) {
+ this.configuration = configuration;
+ this.managedKeyDataCache = new ManagedKeyDataCache(configuration, null);
+ try {
+ this.systemKeyCache = SystemKeyCache.createCache(configuration, fs);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create system key cache", e);
+ }
+ }
+
+ @Override
+ public SystemKeyCache getSystemKeyCache() {
+ return systemKeyCache;
+ }
+
+ @Override
+ public ManagedKeyDataCache getManagedKeyDataCache() {
+ return managedKeyDataCache;
+ }
+
+ @Override
+ public KeymetaAdmin getKeymetaAdmin() {
+ throw new UnsupportedOperationException("KeymetaAdmin is not supported");
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+ }
+
+ static KeyManagementService createDefault(Configuration configuration, FileSystem fs) {
+ return new DefaultKeyManagementService(configuration, fs);
+ }
+
+ /** Returns the cache for cluster keys. */
+ public SystemKeyCache getSystemKeyCache();
+
+ /** Returns the cache for managed keys. */
+ public ManagedKeyDataCache getManagedKeyDataCache();
+
+ /** Returns the admin for keymeta. */
+ public KeymetaAdmin getKeymetaAdmin();
+
+ /** Returns the configuration. */
+ public Configuration getConfiguration();
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyNamespaceUtil.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyNamespaceUtil.java
new file mode 100644
index 00000000000..52b6adddc6f
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeyNamespaceUtil.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.keymeta;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.regionserver.StoreContext;
+import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.yetus.audience.InterfaceStability;
+
+import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
+
+/**
+ * Utility class for constructing key namespaces used in key management operations.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public class KeyNamespaceUtil {
+
+ /**
+ * Construct a key namespace from a table descriptor and column family descriptor.
+ * @param tableDescriptor The table descriptor
+ * @param family The column family descriptor
+ * @return The constructed key namespace
+ */
+ public static String constructKeyNamespace(TableDescriptor tableDescriptor,
+ ColumnFamilyDescriptor family) {
+ return tableDescriptor.getTableName().getNameAsString() + "/" + family.getNameAsString();
+ }
+
+ /**
+ * Construct a key namespace from a store context.
+ * @param storeContext The store context
+ * @return The constructed key namespace
+ */
+ public static String constructKeyNamespace(StoreContext storeContext) {
+ return storeContext.getTableName().getNameAsString() + "/"
+ + storeContext.getFamily().getNameAsString();
+ }
+
+ /**
+ * Construct a key namespace by deriving table name and family name from a store file info.
+ * @param fileInfo The store file info
+ * @return The constructed key namespace
+ */
+ public static String constructKeyNamespace(StoreFileInfo fileInfo) {
+ return constructKeyNamespace(
+ fileInfo.isLink() ? fileInfo.getLink().getOriginPath() : fileInfo.getPath());
+ }
+
+ /**
+ * Construct a key namespace by deriving table name and family name from a store file path.
+ * @param path The path
+ * @return The constructed key namespace
+ */
+ public static String constructKeyNamespace(Path path) {
+ return constructKeyNamespace(path.getParent().getParent().getParent().getName(),
+ path.getParent().getName());
+ }
+
+ /**
+ * Construct a key namespace from a table name and family name.
+ * @param tableName The table name
+ * @param family The family name
+ * @return The constructed key namespace
+ */
+ public static String constructKeyNamespace(String tableName, String family) {
+ // Add precoditions for null check
+ Preconditions.checkNotNull(tableName, "tableName should not be null");
+ Preconditions.checkNotNull(family, "family should not be null");
+ return tableName + "/" + family;
+ }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeymetaServiceEndpoint.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeymetaServiceEndpoint.java
index 07b73376fa5..4eb19a602cc 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeymetaServiceEndpoint.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeymetaServiceEndpoint.java
@@ -137,8 +137,7 @@ public class KeymetaServiceEndpoint implements MasterCoprocessor {
@InterfaceAudience.Private
public static ManagedKeysResponse.Builder getResponseBuilder(RpcController controller,
ManagedKeysRequest request) {
- ManagedKeysResponse.Builder builder =
- ManagedKeysResponse.newBuilder().setKeyNamespace(request.getKeyNamespace());
+ ManagedKeysResponse.Builder builder = ManagedKeysResponse.newBuilder();
byte[] key_cust = convertToKeyCustBytes(controller, request, builder);
if (key_cust != null) {
builder.setKeyCustBytes(ByteString.copyFrom(key_cust));
@@ -152,9 +151,10 @@ public class KeymetaServiceEndpoint implements MasterCoprocessor {
List<ManagedKeyData> managedKeyStates, ManagedKeysResponse.Builder builder) {
GetManagedKeysResponse.Builder responseBuilder = GetManagedKeysResponse.newBuilder();
for (ManagedKeyData keyData : managedKeyStates) {
- builder.setKeyState(ManagedKeysProtos.ManagedKeyState.valueOf(keyData.getKeyState().getVal()))
- .setKeyMetadata(keyData.getKeyMetadata())
- .setRefreshTimestamp(keyData.getRefreshTimestamp());
+ builder
+ .setKeyState(ManagedKeysProtos.ManagedKeyState.forNumber(keyData.getKeyState().getVal()))
+ .setKeyMetadata(keyData.getKeyMetadata()).setRefreshTimestamp(keyData.getRefreshTimestamp())
+ .setKeyNamespace(keyData.getKeyNamespace());
responseBuilder.addState(builder.build());
}
return responseBuilder.build();
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeymetaTableAccessor.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeymetaTableAccessor.java
index 6862e35ddf1..8e2a7095cfc 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeymetaTableAccessor.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/KeymetaTableAccessor.java
@@ -21,7 +21,7 @@ import java.io.IOException;
import java.security.Key;
import java.security.KeyException;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.hadoop.hbase.HBaseInterfaceAudience;
@@ -76,8 +76,15 @@ public class KeymetaTableAccessor extends KeyManagementBase {
public static final String KEY_STATE_QUAL_NAME = "k";
public static final byte[] KEY_STATE_QUAL_BYTES = Bytes.toBytes(KEY_STATE_QUAL_NAME);
+ private Server server;
+
public KeymetaTableAccessor(Server server) {
- super(server);
+ super(server.getKeyManagementService());
+ this.server = server;
+ }
+
+ public Server getServer() {
+ return server;
}
/**
@@ -113,8 +120,7 @@ public class KeymetaTableAccessor extends KeyManagementBase {
throws IOException, KeyException {
assertKeyManagementEnabled();
Connection connection = getServer().getConnection();
- byte[] prefixForScan =
- Bytes.add(Bytes.toBytes(key_cust.length), key_cust, Bytes.toBytes(keyNamespace));
+ byte[] prefixForScan = constructRowKeyForCustNamespace(key_cust, keyNamespace);
PrefixFilter prefixFilter = new PrefixFilter(prefixForScan);
Scan scan = new Scan();
scan.setFilter(prefixFilter);
@@ -122,9 +128,10 @@ public class KeymetaTableAccessor extends KeyManagementBase {
try (Table table = connection.getTable(KEY_META_TABLE_NAME)) {
ResultScanner scanner = table.getScanner(scan);
- Set<ManagedKeyData> allKeys = new HashSet<>();
+ Set<ManagedKeyData> allKeys = new LinkedHashSet<>();
for (Result result : scanner) {
- ManagedKeyData keyData = parseFromResult(getServer(), key_cust, keyNamespace, result);
+ ManagedKeyData keyData =
+ parseFromResult(getKeyManagementService(), key_cust, keyNamespace, result);
if (keyData != null) {
allKeys.add(keyData);
}
@@ -147,11 +154,10 @@ public class KeymetaTableAccessor extends KeyManagementBase {
Connection connection = getServer().getConnection();
byte[] rowkeyForGet = constructRowKeyForCustNamespace(key_cust, keyNamespace);
Get get = new Get(rowkeyForGet);
- get.addColumn(KEY_META_INFO_FAMILY, KEY_STATE_QUAL_BYTES);
try (Table table = connection.getTable(KEY_META_TABLE_NAME)) {
Result result = table.get(get);
- return parseFromResult(getServer(), key_cust, keyNamespace, result);
+ return parseFromResult(getKeyManagementService(), key_cust, keyNamespace, result);
}
}
@@ -200,7 +206,7 @@ public class KeymetaTableAccessor extends KeyManagementBase {
try (Table table = connection.getTable(KEY_META_TABLE_NAME)) {
byte[] rowKey = constructRowKeyForMetadata(key_cust, keyNamespace, keyMetadataHash);
Result result = table.get(new Get(rowKey));
- return parseFromResult(getServer(), key_cust, keyNamespace, result);
+ return parseFromResult(getKeyManagementService(), key_cust, keyNamespace, result);
}
}
@@ -208,10 +214,11 @@ public class KeymetaTableAccessor extends KeyManagementBase {
* Add the mutation columns to the given Put that are derived from the keyData.
*/
private Put addMutationColumns(Put put, ManagedKeyData keyData) throws IOException {
- ManagedKeyData latestSystemKey = getServer().getSystemKeyCache().getLatestSystemKey();
+ ManagedKeyData latestSystemKey =
+ getKeyManagementService().getSystemKeyCache().getLatestSystemKey();
if (keyData.getTheKey() != null) {
- byte[] dekWrappedBySTK = EncryptionUtil.wrapKey(getServer().getConfiguration(), null,
- keyData.getTheKey(), latestSystemKey.getTheKey());
+ byte[] dekWrappedBySTK = EncryptionUtil.wrapKey(getConfiguration(), null, keyData.getTheKey(),
+ latestSystemKey.getTheKey());
put
.addColumn(KEY_META_INFO_FAMILY, DEK_CHECKSUM_QUAL_BYTES,
Bytes.toBytes(keyData.getKeyChecksum()))
@@ -261,12 +268,12 @@ public class KeymetaTableAccessor extends KeyManagementBase {
@InterfaceAudience.Private
public static byte[] constructRowKeyForCustNamespace(byte[] key_cust, String keyNamespace) {
int custLength = key_cust.length;
- return Bytes.add(Bytes.toBytes(custLength), key_cust, Bytes.toBytesBinary(keyNamespace));
+ return Bytes.add(Bytes.toBytes(custLength), key_cust, Bytes.toBytes(keyNamespace));
}
@InterfaceAudience.Private
- public static ManagedKeyData parseFromResult(Server server, byte[] key_cust, String keyNamespace,
- Result result) throws IOException, KeyException {
+ public static ManagedKeyData parseFromResult(KeyManagementService keyManagementService,
+ byte[] key_cust, String keyNamespace, Result result) throws IOException, KeyException {
if (result == null || result.isEmpty()) {
return null;
}
@@ -285,13 +292,14 @@ public class KeymetaTableAccessor extends KeyManagementBase {
if (dekWrappedByStk != null) {
long stkChecksum =
Bytes.toLong(result.getValue(KEY_META_INFO_FAMILY, STK_CHECKSUM_QUAL_BYTES));
- ManagedKeyData clusterKey = server.getSystemKeyCache().getSystemKeyByChecksum(stkChecksum);
+ ManagedKeyData clusterKey =
+ keyManagementService.getSystemKeyCache().getSystemKeyByChecksum(stkChecksum);
if (clusterKey == null) {
LOG.error("Dropping key with metadata: {} as STK with checksum: {} is unavailable",
dekMetadata, stkChecksum);
return null;
}
- dek = EncryptionUtil.unwrapKey(server.getConfiguration(), null, dekWrappedByStk,
+ dek = EncryptionUtil.unwrapKey(keyManagementService.getConfiguration(), null, dekWrappedByStk,
clusterKey.getTheKey());
}
long refreshedTimestamp =
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/SystemKeyAccessor.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/SystemKeyAccessor.java
index ecac8e1a285..8de01319e25 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/SystemKeyAccessor.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/SystemKeyAccessor.java
@@ -25,6 +25,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
@@ -35,14 +36,24 @@ import org.apache.hadoop.hbase.io.crypto.ManagedKeyProvider;
import org.apache.hadoop.hbase.util.CommonFSUtils;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@InterfaceAudience.Private
public class SystemKeyAccessor extends KeyManagementBase {
+ private static final Logger LOG = LoggerFactory.getLogger(SystemKeyAccessor.class);
+
+ private final FileSystem fs;
protected final Path systemKeyDir;
public SystemKeyAccessor(Server server) throws IOException {
- super(server);
- this.systemKeyDir = CommonFSUtils.getSystemKeyDir(server.getConfiguration());
+ this(server.getConfiguration(), server.getFileSystem());
+ }
+
+ public SystemKeyAccessor(Configuration configuration, FileSystem fs) throws IOException {
+ super(configuration);
+ this.systemKeyDir = CommonFSUtils.getSystemKeyDir(configuration);
+ this.fs = fs;
}
/**
@@ -52,9 +63,7 @@ public class SystemKeyAccessor extends KeyManagementBase {
* is initialized yet.
*/
public Pair<Path, List<Path>> getLatestSystemKeyFile() throws IOException {
- if (!isKeyManagementEnabled()) {
- return new Pair<>(null, null);
- }
+ assertKeyManagementEnabled();
List<Path> allClusterKeyFiles = getAllSystemKeyFiles();
if (allClusterKeyFiles.isEmpty()) {
throw new RuntimeException("No cluster key initialized yet");
@@ -72,17 +81,15 @@ public class SystemKeyAccessor extends KeyManagementBase {
* @throws IOException if there is an error getting the cluster key files
*/
public List<Path> getAllSystemKeyFiles() throws IOException {
- if (!isKeyManagementEnabled()) {
- return null;
- }
- FileSystem fs = getServer().getFileSystem();
+ assertKeyManagementEnabled();
+ LOG.info("Getting all system key files from: {} matching prefix: {}", systemKeyDir,
+ SYSTEM_KEY_FILE_PREFIX + "*");
Map<Integer, Path> clusterKeys = new TreeMap<>(Comparator.reverseOrder());
for (FileStatus st : fs.globStatus(new Path(systemKeyDir, SYSTEM_KEY_FILE_PREFIX + "*"))) {
Path keyPath = st.getPath();
int seqNum = extractSystemKeySeqNum(keyPath);
clusterKeys.put(seqNum, keyPath);
}
-
return new ArrayList<>(clusterKeys.values());
}
@@ -130,7 +137,7 @@ public class SystemKeyAccessor extends KeyManagementBase {
}
protected String loadKeyMetadata(Path keyPath) throws IOException {
- try (FSDataInputStream fin = getServer().getFileSystem().open(keyPath)) {
+ try (FSDataInputStream fin = fs.open(keyPath)) {
return fin.readUTF();
}
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/SystemKeyCache.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/SystemKeyCache.java
index bb7a6e3f693..bcdf2ae11cf 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/SystemKeyCache.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/keymeta/SystemKeyCache.java
@@ -21,6 +21,8 @@ import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
import org.apache.yetus.audience.InterfaceAudience;
@@ -35,6 +37,19 @@ public class SystemKeyCache {
private final ManagedKeyData latestSystemKey;
private final Map<Long, ManagedKeyData> systemKeys;
+ /**
+ * Create a SystemKeyCache from the specified configuration and file system.
+ * @param configuration the configuration to use
+ * @param fs the file system to use
+ * @return the cache or {@code null} if no keys are found.
+ * @throws IOException if there is an error loading the system keys
+ */
+ public static SystemKeyCache createCache(Configuration configuration, FileSystem fs)
+ throws IOException {
+ SystemKeyAccessor accessor = new SystemKeyAccessor(configuration, fs);
+ return createCache(accessor);
+ }
+
/**
* Construct the System Key cache from the specified accessor.
* @param accessor the accessor to use to load the system keys
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
index 0573b1a7562..dee9b48f9ea 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
@@ -33,6 +33,7 @@ import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.executor.ExecutorService;
import org.apache.hadoop.hbase.favored.FavoredNodesManager;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
import org.apache.hadoop.hbase.master.hbck.HbckChore;
import org.apache.hadoop.hbase.master.janitor.CatalogJanitor;
@@ -67,7 +68,7 @@ import org.apache.hbase.thirdparty.com.google.protobuf.Service;
* adding API. Changes cause ripples through the code base.
*/
@InterfaceAudience.Private
-public interface MasterServices extends Server {
+public interface MasterServices extends Server, KeyManagementService {
/** Returns the underlying snapshot manager */
SnapshotManager getSnapshotManager();
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/SystemKeyManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/SystemKeyManager.java
index 2ca423bad8e..de0e37dde27 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/SystemKeyManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/SystemKeyManager.java
@@ -30,9 +30,13 @@ import org.apache.hadoop.hbase.io.crypto.ManagedKeyState;
import org.apache.hadoop.hbase.keymeta.SystemKeyAccessor;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@InterfaceAudience.Private
public class SystemKeyManager extends SystemKeyAccessor {
+ private static final Logger LOG = LoggerFactory.getLogger(SystemKeyManager.class);
+
private final MasterServices master;
public SystemKeyManager(MasterServices master) throws IOException {
@@ -63,7 +67,7 @@ public class SystemKeyManager extends SystemKeyAccessor {
return null;
}
Pair<Path, List<Path>> latestFileResult = getLatestSystemKeyFile();
- Path latestFile = getLatestSystemKeyFile().getFirst();
+ Path latestFile = latestFileResult.getFirst();
String latestKeyMetadata = loadKeyMetadata(latestFile);
return rotateSystemKey(latestKeyMetadata, latestFileResult.getSecond());
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java
index 423297f667d..59a8285b2f6 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java
@@ -360,8 +360,7 @@ public class CreateTableProcedure extends AbstractStateMachineTableProcedure<Cre
throws IOException {
RegionInfo[] regions =
newRegions != null ? newRegions.toArray(new RegionInfo[newRegions.size()]) : null;
- return ModifyRegionUtils.createRegions(env.getMasterConfiguration(), tableRootDir,
- tableDescriptor, regions, null);
+ return ModifyRegionUtils.createRegions(env, tableRootDir, tableDescriptor, regions, null);
}
});
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/InitMetaProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/InitMetaProcedure.java
index 8b4901e90e8..2d54eaf6c58 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/InitMetaProcedure.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/InitMetaProcedure.java
@@ -75,10 +75,10 @@ public class InitMetaProcedure extends AbstractStateMachineTableProcedure<InitMe
return TableOperationType.CREATE;
}
- private static TableDescriptor writeFsLayout(Path rootDir, Configuration conf)
+ private static TableDescriptor writeFsLayout(Path rootDir, MasterProcedureEnv env)
throws IOException {
LOG.info("BOOTSTRAP: creating hbase:meta region");
- FileSystem fs = rootDir.getFileSystem(conf);
+ FileSystem fs = rootDir.getFileSystem(env.getMasterConfiguration());
Path tableDir = CommonFSUtils.getTableDir(rootDir, TableName.META_TABLE_NAME);
if (fs.exists(tableDir) && !deleteMetaTableDirectoryIfPartial(fs, tableDir)) {
LOG.warn("Can not delete partial created meta table, continue...");
@@ -87,10 +87,11 @@ public class InitMetaProcedure extends AbstractStateMachineTableProcedure<InitMe
// created here in bootstrap and it'll need to be cleaned up. Better to
// not make it in first place. Turn off block caching for bootstrap.
// Enable after.
- TableDescriptor metaDescriptor =
- FSTableDescriptors.tryUpdateAndGetMetaTableDescriptor(conf, fs, rootDir);
+ TableDescriptor metaDescriptor = FSTableDescriptors
+ .tryUpdateAndGetMetaTableDescriptor(env.getMasterConfiguration(), fs, rootDir);
HRegion
- .createHRegion(RegionInfoBuilder.FIRST_META_REGIONINFO, rootDir, conf, metaDescriptor, null)
+ .createHRegion(RegionInfoBuilder.FIRST_META_REGIONINFO, rootDir, env.getMasterConfiguration(),
+ metaDescriptor, null, env.getMasterServices().getKeyManagementService())
.close();
return metaDescriptor;
}
@@ -104,7 +105,7 @@ public class InitMetaProcedure extends AbstractStateMachineTableProcedure<InitMe
case INIT_META_WRITE_FS_LAYOUT:
Configuration conf = env.getMasterConfiguration();
Path rootDir = CommonFSUtils.getRootDir(conf);
- TableDescriptor td = writeFsLayout(rootDir, conf);
+ TableDescriptor td = writeFsLayout(rootDir, env);
env.getMasterServices().getTableDescriptors().update(td, true);
setNextState(InitMetaState.INIT_META_ASSIGN_META);
return Flow.HAS_MORE_STATE;
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegion.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegion.java
index 97447e37b7c..0539fb6250a 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegion.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegion.java
@@ -29,7 +29,6 @@ import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseIOException;
import org.apache.hadoop.hbase.RegionTooBusyException;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.ConnectionUtils;
@@ -44,6 +43,7 @@ import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.ipc.RpcCall;
import org.apache.hadoop.hbase.ipc.RpcServer;
import org.apache.hadoop.hbase.log.HBaseMarkers;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.HRegion.FlushResult;
import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
@@ -114,7 +114,7 @@ public final class MasterRegion {
private static final int REGION_ID = 1;
- private final Server server;
+ private final MasterServices server;
private final WALFactory walFactory;
@@ -128,7 +128,7 @@ public final class MasterRegion {
private final long regionUpdateRetryPauseTime;
- private MasterRegion(Server server, HRegion region, WALFactory walFactory,
+ private MasterRegion(MasterServices server, HRegion region, WALFactory walFactory,
MasterRegionFlusherAndCompactor flusherAndCompactor, MasterRegionWALRoller walRoller) {
this.server = server;
this.region = region;
@@ -301,14 +301,15 @@ public final class MasterRegion {
private static HRegion bootstrap(Configuration conf, TableDescriptor td, FileSystem fs,
Path rootDir, FileSystem walFs, Path walRootDir, WALFactory walFactory,
- MasterRegionWALRoller walRoller, String serverName, boolean touchInitializingFlag)
+ MasterRegionWALRoller walRoller, MasterServices server, boolean touchInitializingFlag)
throws IOException {
TableName tn = td.getTableName();
RegionInfo regionInfo = RegionInfoBuilder.newBuilder(tn).setRegionId(REGION_ID).build();
Path tableDir = CommonFSUtils.getTableDir(rootDir, tn);
// persist table descriptor
FSTableDescriptors.createTableDescriptorForTableDirectory(fs, tableDir, td, true);
- HRegion.createHRegion(conf, regionInfo, fs, tableDir, td).close();
+ HRegion.createHRegion(conf, regionInfo, fs, tableDir, td, server.getKeyManagementService())
+ .close();
Path initializedFlag = new Path(tableDir, INITIALIZED_FLAG);
if (!fs.mkdirs(initializedFlag)) {
throw new IOException("Can not touch initialized flag: " + initializedFlag);
@@ -317,8 +318,10 @@ public final class MasterRegion {
if (!fs.delete(initializingFlag, true)) {
LOG.warn("failed to clean up initializing flag: " + initializingFlag);
}
- WAL wal = createWAL(walFactory, walRoller, serverName, walFs, walRootDir, regionInfo);
- return HRegion.openHRegionFromTableDir(conf, fs, tableDir, regionInfo, td, wal, null, null);
+ WAL wal = createWAL(walFactory, walRoller, server.getServerName().toString(), walFs, walRootDir,
+ regionInfo);
+ return HRegion.openHRegionFromTableDir(conf, fs, tableDir, regionInfo, td, wal, null, null,
+ server.getKeyManagementService());
}
private static RegionInfo loadRegionInfo(FileSystem fs, Path tableDir) throws IOException {
@@ -330,7 +333,7 @@ public final class MasterRegion {
private static HRegion open(Configuration conf, TableDescriptor td, RegionInfo regionInfo,
FileSystem fs, Path rootDir, FileSystem walFs, Path walRootDir, WALFactory walFactory,
- MasterRegionWALRoller walRoller, String serverName) throws IOException {
+ MasterRegionWALRoller walRoller, MasterServices server) throws IOException {
Path tableDir = CommonFSUtils.getTableDir(rootDir, td.getTableName());
Path walRegionDir = FSUtils.getRegionDirFromRootDir(walRootDir, regionInfo);
Path replayEditsDir = new Path(walRegionDir, REPLAY_EDITS_DIR);
@@ -346,7 +349,8 @@ public final class MasterRegion {
// to always exist in normal situations, but we should guard against users changing the
// filesystem outside of HBase's line of sight.
if (walFs.exists(walsDir)) {
- replayWALs(conf, walFs, walRootDir, walsDir, regionInfo, serverName, replayEditsDir);
+ replayWALs(conf, walFs, walRootDir, walsDir, regionInfo, server.getServerName().toString(),
+ replayEditsDir);
} else {
LOG.error(
"UNEXPECTED: WAL directory for MasterRegion is missing." + " {} is unexpectedly missing.",
@@ -354,13 +358,15 @@ public final class MasterRegion {
}
// Create a new WAL
- WAL wal = createWAL(walFactory, walRoller, serverName, walFs, walRootDir, regionInfo);
+ WAL wal = createWAL(walFactory, walRoller, server.getServerName().toString(), walFs, walRootDir,
+ regionInfo);
conf.set(HRegion.SPECIAL_RECOVERED_EDITS_DIR,
replayEditsDir.makeQualified(walFs.getUri(), walFs.getWorkingDirectory()).toString());
// we do not do WAL splitting here so it is possible to have uncleanly closed WAL files, so we
// need to ignore EOFException.
conf.setBoolean(HRegion.RECOVERED_EDITS_IGNORE_EOF, true);
- return HRegion.openHRegionFromTableDir(conf, fs, tableDir, regionInfo, td, wal, null, null);
+ return HRegion.openHRegionFromTableDir(conf, fs, tableDir, regionInfo, td, wal, null, null,
+ server);
}
private static void replayWALs(Configuration conf, FileSystem walFs, Path walRootDir,
@@ -437,7 +443,7 @@ public final class MasterRegion {
public static MasterRegion create(MasterRegionParams params) throws IOException {
TableDescriptor td = params.tableDescriptor();
LOG.info("Create or load local region for table " + td);
- Server server = params.server();
+ MasterServices server = params.server();
Configuration baseConf = server.getConfiguration();
FileSystem fs = CommonFSUtils.getRootDirFileSystem(baseConf);
FileSystem walFs = CommonFSUtils.getWALFileSystem(baseConf);
@@ -476,8 +482,8 @@ public final class MasterRegion {
if (!fs.mkdirs(initializedFlag)) {
throw new IOException("Can not touch initialized flag");
}
- region = bootstrap(conf, td, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
- server.getServerName().toString(), true);
+ region =
+ bootstrap(conf, td, fs, rootDir, walFs, walRootDir, walFactory, walRoller, server, true);
} else {
if (!fs.exists(initializedFlag)) {
if (!fs.exists(initializingFlag)) {
@@ -495,7 +501,7 @@ public final class MasterRegion {
RegionInfo regionInfo = loadRegionInfo(fs, tableDir);
tryMigrate(conf, fs, tableDir, regionInfo, oldTd, td);
region = open(conf, td, regionInfo, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
- server.getServerName().toString());
+ server);
} else {
// delete all contents besides the initializing flag, here we can make sure tableDir
// exists(unless someone delete it manually...), so we do not do null check here.
@@ -505,7 +511,7 @@ public final class MasterRegion {
}
}
region = bootstrap(conf, td, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
- server.getServerName().toString(), false);
+ server, false);
}
} else {
if (fs.exists(initializingFlag) && !fs.delete(initializingFlag, true)) {
@@ -515,8 +521,8 @@ public final class MasterRegion {
TableDescriptor oldTd = FSTableDescriptors.getTableDescriptorFromFs(fs, tableDir);
RegionInfo regionInfo = loadRegionInfo(fs, tableDir);
tryMigrate(conf, fs, tableDir, regionInfo, oldTd, td);
- region = open(conf, td, regionInfo, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
- server.getServerName().toString());
+ region =
+ open(conf, td, regionInfo, fs, rootDir, walFs, walRootDir, walFactory, walRoller, server);
}
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionFactory.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionFactory.java
index 71fb76bd0f1..878f8dc17a1 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionFactory.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionFactory.java
@@ -21,12 +21,12 @@ import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.regionserver.BloomType;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
@@ -113,7 +113,7 @@ public final class MasterRegionFactory {
return tracker.updateWithTrackerConfigs(TableDescriptorBuilder.newBuilder(TABLE_DESC)).build();
}
- public static MasterRegion create(Server server) throws IOException {
+ public static MasterRegion create(MasterServices server) throws IOException {
Configuration conf = server.getConfiguration();
MasterRegionParams params = new MasterRegionParams().server(server)
.regionDirName(MASTER_STORE_DIR).tableDescriptor(withTrackerConfigs(conf));
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionParams.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionParams.java
index b9065747b66..443bca9f8c9 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionParams.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionParams.java
@@ -17,8 +17,8 @@
*/
package org.apache.hadoop.hbase.master.region;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.yetus.audience.InterfaceAudience;
/**
@@ -27,7 +27,7 @@ import org.apache.yetus.audience.InterfaceAudience;
@InterfaceAudience.Private
public class MasterRegionParams {
- private Server server;
+ private MasterServices server;
private String regionDirName;
@@ -55,7 +55,7 @@ public class MasterRegionParams {
private Boolean useMetaCellComparator;
- public MasterRegionParams server(Server server) {
+ public MasterRegionParams server(MasterServices server) {
this.server = server;
return this;
}
@@ -125,7 +125,7 @@ public class MasterRegionParams {
return this;
}
- public Server server() {
+ public MasterServices server() {
return server;
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
index 7936197ff8d..99aca4f6abd 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
@@ -90,6 +90,7 @@ import org.apache.hadoop.hbase.DoNotRetryIOException;
import org.apache.hadoop.hbase.DroppedSnapshotException;
import org.apache.hadoop.hbase.ExtendedCell;
import org.apache.hadoop.hbase.ExtendedCellBuilderFactory;
+import org.apache.hadoop.hbase.HBaseInterfaceAudience;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
import org.apache.hadoop.hbase.HDFSBlocksDistribution;
@@ -146,6 +147,9 @@ import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
import org.apache.hadoop.hbase.ipc.RpcCall;
import org.apache.hadoop.hbase.ipc.RpcServer;
import org.apache.hadoop.hbase.ipc.ServerCall;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.hadoop.hbase.mob.MobFileCache;
import org.apache.hadoop.hbase.monitoring.MonitoredTask;
import org.apache.hadoop.hbase.monitoring.TaskMonitor;
@@ -166,6 +170,7 @@ import org.apache.hadoop.hbase.regionserver.wal.WALSyncTimeoutIOException;
import org.apache.hadoop.hbase.regionserver.wal.WALUtil;
import org.apache.hadoop.hbase.replication.ReplicationUtils;
import org.apache.hadoop.hbase.replication.regionserver.ReplicationObserver;
+import org.apache.hadoop.hbase.security.SecurityUtil;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
import org.apache.hadoop.hbase.snapshot.SnapshotManifest;
@@ -382,6 +387,8 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
private final Configuration baseConf;
private final int rowLockWaitDuration;
static final int DEFAULT_ROWLOCK_WAIT_DURATION = 30000;
+ private final ManagedKeyDataCache managedKeyDataCache;
+ private final SystemKeyCache systemKeyCache;
private Path regionWalDir;
private FileSystem walFS;
@@ -769,8 +776,36 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
public HRegion(final Path tableDir, final WAL wal, final FileSystem fs,
final Configuration confParam, final RegionInfo regionInfo, final TableDescriptor htd,
final RegionServerServices rsServices) {
+ this(tableDir, wal, fs, confParam, regionInfo, htd, rsServices, null);
+ }
+
+ /**
+ * HRegion constructor. This constructor should only be used for testing and extensions. Instances
+ * of HRegion should be instantiated with the {@link HRegion#createHRegion} or
+ * {@link HRegion#openHRegion} method.
+ * @param tableDir qualified path of directory where region should be located, usually
+ * the table directory.
+ * @param wal The WAL is the outbound log for any updates to the HRegion The wal
+ * file is a logfile from the previous execution that's
+ * custom-computed for this HRegion. The HRegionServer computes and
+ * sorts the appropriate wal info for this HRegion. If there is a
+ * previous wal file (implying that the HRegion has been written-to
+ * before), then read it from the supplied path.
+ * @param fs is the filesystem.
+ * @param confParam is global configuration settings.
+ * @param regionInfo - RegionInfo that describes the region is new), then read them from
+ * the supplied path.
+ * @param htd the table descriptor
+ * @param rsServices reference to {@link RegionServerServices} or null
+ * @param keyManagementService reference to {@link KeyManagementService} or null
+ * @deprecated Use other constructors.
+ */
+ @Deprecated
+ public HRegion(final Path tableDir, final WAL wal, final FileSystem fs,
+ final Configuration confParam, final RegionInfo regionInfo, final TableDescriptor htd,
+ final RegionServerServices rsServices, final KeyManagementService keyManagementService) {
this(new HRegionFileSystem(confParam, fs, tableDir, regionInfo), wal, confParam, htd,
- rsServices);
+ rsServices, keyManagementService);
}
/**
@@ -789,6 +824,28 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
*/
public HRegion(final HRegionFileSystem fs, final WAL wal, final Configuration confParam,
final TableDescriptor htd, final RegionServerServices rsServices) {
+ this(fs, wal, confParam, htd, rsServices, null);
+ }
+
+ /**
+ * HRegion constructor. This constructor should only be used for testing and extensions. Instances
+ * of HRegion should be instantiated with the {@link HRegion#createHRegion} or
+ * {@link HRegion#openHRegion} method.
+ * @param fs is the filesystem.
+ * @param wal The WAL is the outbound log for any updates to the HRegion The wal
+ * file is a logfile from the previous execution that's
+ * custom-computed for this HRegion. The HRegionServer computes and
+ * sorts the appropriate wal info for this HRegion. If there is a
+ * previous wal file (implying that the HRegion has been written-to
+ * before), then read it from the supplied path.
+ * @param confParam is global configuration settings.
+ * @param htd the table descriptor
+ * @param rsServices reference to {@link RegionServerServices} or null
+ * @param keyManagementService reference to {@link KeyManagementService} or null
+ */
+ public HRegion(final HRegionFileSystem fs, final WAL wal, final Configuration confParam,
+ final TableDescriptor htd, final RegionServerServices rsServices,
+ KeyManagementService keyManagementService) {
if (htd == null) {
throw new IllegalArgumentException("Need table descriptor");
}
@@ -929,6 +986,17 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
minBlockSizeBytes = Arrays.stream(this.htableDescriptor.getColumnFamilies())
.mapToInt(ColumnFamilyDescriptor::getBlocksize).min().orElse(HConstants.DEFAULT_BLOCKSIZE);
+
+ if (SecurityUtil.isKeyManagementEnabled(conf)) {
+ if (keyManagementService == null) {
+ keyManagementService = KeyManagementService.createDefault(conf, fs.getFileSystem());
+ }
+ this.managedKeyDataCache = keyManagementService.getManagedKeyDataCache();
+ this.systemKeyCache = keyManagementService.getSystemKeyCache();
+ } else {
+ this.managedKeyDataCache = null;
+ this.systemKeyCache = null;
+ }
}
private void setHTableSpecificConf() {
@@ -2122,6 +2190,14 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
return this.blockCache;
}
+ public ManagedKeyDataCache getManagedKeyDataCache() {
+ return this.managedKeyDataCache;
+ }
+
+ public SystemKeyCache getSystemKeyCache() {
+ return this.systemKeyCache;
+ }
+
/**
* Only used for unit test which doesn't start region server.
*/
@@ -7579,37 +7655,60 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
}
// Utility methods
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
+ public static HRegion newHRegion(Path tableDir, WAL wal, FileSystem fs, Configuration conf,
+ RegionInfo regionInfo, final TableDescriptor htd, RegionServerServices rsServices) {
+ return newHRegion(tableDir, wal, fs, conf, regionInfo, htd, rsServices, null);
+ }
+
/**
* A utility method to create new instances of HRegion based on the {@link HConstants#REGION_IMPL}
* configuration property.
- * @param tableDir qualified path of directory where region should be located, usually the table
- * directory.
- * @param wal The WAL is the outbound log for any updates to the HRegion The wal file is a
- * logfile from the previous execution that's custom-computed for this HRegion.
- * The HRegionServer computes and sorts the appropriate wal info for this
- * HRegion. If there is a previous file (implying that the HRegion has been
- * written-to before), then read it from the supplied path.
- * @param fs is the filesystem.
- * @param conf is global configuration settings.
- * @param regionInfo - RegionInfo that describes the region is new), then read them from the
- * supplied path.
- * @param htd the table descriptor
+ * @param tableDir qualified path of directory where region should be located, usually
+ * the table directory.
+ * @param wal The WAL is the outbound log for any updates to the HRegion The wal
+ * file is a logfile from the previous execution that's
+ * custom-computed for this HRegion. The HRegionServer computes and
+ * sorts the appropriate wal info for this HRegion. If there is a
+ * previous file (implying that the HRegion has been written-to
+ * before), then read it from the supplied path.
+ * @param fs is the filesystem.
+ * @param conf is global configuration settings.
+ * @param regionInfo - RegionInfo that describes the region is new), then read them from
+ * the supplied path.
+ * @param htd the table descriptor
+ * @param keyManagementService reference to {@link KeyManagementService} or null
* @return the new instance
*/
public static HRegion newHRegion(Path tableDir, WAL wal, FileSystem fs, Configuration conf,
- RegionInfo regionInfo, final TableDescriptor htd, RegionServerServices rsServices) {
+ RegionInfo regionInfo, final TableDescriptor htd, RegionServerServices rsServices,
+ final KeyManagementService keyManagementService) {
+ List<Class<?>> ctorArgTypes =
+ Arrays.asList(Path.class, WAL.class, FileSystem.class, Configuration.class, RegionInfo.class,
+ TableDescriptor.class, RegionServerServices.class, KeyManagementService.class);
+ List<Object> ctorArgs =
+ Arrays.asList(tableDir, wal, fs, conf, regionInfo, htd, rsServices, keyManagementService);
+
+ try {
+ return createInstance(conf, ctorArgTypes, ctorArgs);
+ } catch (Throwable e) {
+ // Try the old signature for the sake of test code.
+ return createInstance(conf, ctorArgTypes.subList(0, ctorArgTypes.size() - 1),
+ ctorArgs.subList(0, ctorArgs.size() - 1));
+ }
+ }
+
+ private static HRegion createInstance(Configuration conf, List<Class<?>> ctorArgTypes,
+ List<Object> ctorArgs) {
try {
@SuppressWarnings("unchecked")
Class<? extends HRegion> regionClass =
(Class<? extends HRegion>) conf.getClass(HConstants.REGION_IMPL, HRegion.class);
Constructor<? extends HRegion> c =
- regionClass.getConstructor(Path.class, WAL.class, FileSystem.class, Configuration.class,
- RegionInfo.class, TableDescriptor.class, RegionServerServices.class);
-
- return c.newInstance(tableDir, wal, fs, conf, regionInfo, htd, rsServices);
+ regionClass.getConstructor(ctorArgTypes.toArray(new Class<?>[ctorArgTypes.size()]));
+ return c.newInstance(ctorArgs.toArray(new Object[ctorArgs.size()]));
} catch (Throwable e) {
- // todo: what should I throw here?
throw new IllegalStateException("Could not instantiate a region instance.", e);
}
}
@@ -7622,6 +7721,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
* @param initialize - true to initialize the region
* @return new HRegion
*/
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
public static HRegion createHRegion(final RegionInfo info, final Path rootDir,
final Configuration conf, final TableDescriptor hTableDescriptor, final WAL wal,
final boolean initialize) throws IOException {
@@ -7637,16 +7737,35 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
* @param rsRpcServices An interface we can request flushes against.
* @return new HRegion
*/
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
public static HRegion createHRegion(final RegionInfo info, final Path rootDir,
final Configuration conf, final TableDescriptor hTableDescriptor, final WAL wal,
final boolean initialize, RegionServerServices rsRpcServices) throws IOException {
+ return createHRegion(info, rootDir, conf, hTableDescriptor, wal, initialize, rsRpcServices,
+ null);
+ }
+
+ /**
+ * Convenience method creating new HRegions. Used by createTable.
+ * @param info Info for region to create.
+ * @param rootDir Root directory for HBase instance
+ * @param wal shared WAL
+ * @param initialize - true to initialize the region
+ * @param rsRpcServices An interface we can request flushes against.
+ * @param keyManagementService reference to {@link KeyManagementService} or null
+ * @return new HRegion
+ */
+ public static HRegion createHRegion(final RegionInfo info, final Path rootDir,
+ final Configuration conf, final TableDescriptor hTableDescriptor, final WAL wal,
+ final boolean initialize, RegionServerServices rsRpcServices,
+ final KeyManagementService keyManagementService) throws IOException {
LOG.info("creating " + info + ", tableDescriptor="
+ (hTableDescriptor == null ? "null" : hTableDescriptor) + ", regionDir=" + rootDir);
createRegionDir(conf, info, rootDir);
FileSystem fs = rootDir.getFileSystem(conf);
Path tableDir = CommonFSUtils.getTableDir(rootDir, info.getTable());
- HRegion region =
- HRegion.newHRegion(tableDir, wal, fs, conf, info, hTableDescriptor, rsRpcServices);
+ HRegion region = HRegion.newHRegion(tableDir, wal, fs, conf, info, hTableDescriptor,
+ rsRpcServices, keyManagementService);
if (initialize) {
region.initialize(null);
}
@@ -7657,11 +7776,13 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
* Create a region under the given table directory.
*/
public static HRegion createHRegion(Configuration conf, RegionInfo regionInfo, FileSystem fs,
- Path tableDir, TableDescriptor tableDesc) throws IOException {
+ Path tableDir, TableDescriptor tableDesc, KeyManagementService keyManagementService)
+ throws IOException {
LOG.info("Creating {}, tableDescriptor={}, under table dir {}", regionInfo, tableDesc,
tableDir);
HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, regionInfo);
- HRegion region = HRegion.newHRegion(tableDir, null, fs, conf, regionInfo, tableDesc, null);
+ HRegion region = HRegion.newHRegion(tableDir, null, fs, conf, regionInfo, tableDesc, null,
+ keyManagementService);
return region;
}
@@ -7680,7 +7801,14 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
public static HRegion createHRegion(final RegionInfo info, final Path rootDir,
final Configuration conf, final TableDescriptor hTableDescriptor, final WAL wal)
throws IOException {
- return createHRegion(info, rootDir, conf, hTableDescriptor, wal, true);
+ return createHRegion(info, rootDir, conf, hTableDescriptor, wal, null);
+ }
+
+ public static HRegion createHRegion(final RegionInfo info, final Path rootDir,
+ final Configuration conf, final TableDescriptor hTableDescriptor, final WAL wal,
+ final KeyManagementService keyManagementService) throws IOException {
+ return createHRegion(info, rootDir, conf, hTableDescriptor, wal, true, null,
+ keyManagementService);
}
/**
@@ -7691,6 +7819,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
* properly kept up. HRegionStore does this every time it opens a new region.
* @return new HRegion
*/
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
public static HRegion openHRegion(final RegionInfo info, final TableDescriptor htd, final WAL wal,
final Configuration conf) throws IOException {
return openHRegion(info, htd, wal, conf, null, null);
@@ -7712,7 +7841,8 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
public static HRegion openHRegion(final RegionInfo info, final TableDescriptor htd, final WAL wal,
final Configuration conf, final RegionServerServices rsServices,
final CancelableProgressable reporter) throws IOException {
- return openHRegion(CommonFSUtils.getRootDir(conf), info, htd, wal, conf, rsServices, reporter);
+ return openHRegion(CommonFSUtils.getRootDir(conf), info, htd, wal, conf, rsServices, reporter,
+ rsServices);
}
/**
@@ -7726,9 +7856,10 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
* @param conf The Configuration object to use.
* @return new HRegion
*/
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
public static HRegion openHRegion(Path rootDir, final RegionInfo info, final TableDescriptor htd,
final WAL wal, final Configuration conf) throws IOException {
- return openHRegion(rootDir, info, htd, wal, conf, null, null);
+ return openHRegion(rootDir, info, htd, wal, conf, null, null, null);
}
/**
@@ -7745,10 +7876,33 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
* @param reporter An interface we can report progress against.
* @return new HRegion
*/
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
public static HRegion openHRegion(final Path rootDir, final RegionInfo info,
final TableDescriptor htd, final WAL wal, final Configuration conf,
final RegionServerServices rsServices, final CancelableProgressable reporter)
throws IOException {
+ return openHRegion(rootDir, info, htd, wal, conf, rsServices, reporter, null);
+ }
+
+ /**
+ * Open a Region.
+ * @param rootDir Root directory for HBase instance
+ * @param info Info for region to be opened.
+ * @param htd the table descriptor
+ * @param wal WAL for region to use. This method will call
+ * WAL#setSequenceNumber(long) passing the result of the call to
+ * HRegion#getMinSequenceId() to ensure the wal id is properly kept
+ * up. HRegionStore does this every time it opens a new region.
+ * @param conf The Configuration object to use.
+ * @param rsServices An interface we can request flushes against.
+ * @param reporter An interface we can report progress against.
+ * @param keyManagementService reference to {@link KeyManagementService} or null
+ * @return new HRegion
+ */
+ public static HRegion openHRegion(final Path rootDir, final RegionInfo info,
+ final TableDescriptor htd, final WAL wal, final Configuration conf,
+ final RegionServerServices rsServices, final CancelableProgressable reporter,
+ final KeyManagementService keyManagementService) throws IOException {
FileSystem fs = null;
if (rsServices != null) {
fs = rsServices.getFileSystem();
@@ -7756,7 +7910,8 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
if (fs == null) {
fs = rootDir.getFileSystem(conf);
}
- return openHRegion(conf, fs, rootDir, info, htd, wal, rsServices, reporter);
+ return openHRegion(conf, fs, rootDir, info, htd, wal, rsServices, reporter,
+ keyManagementService);
}
/**
@@ -7771,57 +7926,70 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
* properly kept up. HRegionStore does this every time it opens a new region.
* @return new HRegion
*/
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
public static HRegion openHRegion(final Configuration conf, final FileSystem fs,
final Path rootDir, final RegionInfo info, final TableDescriptor htd, final WAL wal)
throws IOException {
- return openHRegion(conf, fs, rootDir, info, htd, wal, null, null);
+ return openHRegion(conf, fs, rootDir, info, htd, wal, null, null, null);
+ }
+
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
+ public static HRegion openHRegion(final Configuration conf, final FileSystem fs,
+ final Path rootDir, final RegionInfo info, final TableDescriptor htd, final WAL wal,
+ final RegionServerServices rsServices, final CancelableProgressable reporter)
+ throws IOException {
+ return openHRegion(conf, fs, rootDir, info, htd, wal, rsServices, reporter, null);
}
/**
* Open a Region.
- * @param conf The Configuration object to use.
- * @param fs Filesystem to use
- * @param rootDir Root directory for HBase instance
- * @param info Info for region to be opened.
- * @param htd the table descriptor
- * @param wal WAL for region to use. This method will call WAL#setSequenceNumber(long)
- * passing the result of the call to HRegion#getMinSequenceId() to ensure the
- * wal id is properly kept up. HRegionStore does this every time it opens a new
- * region.
- * @param rsServices An interface we can request flushes against.
- * @param reporter An interface we can report progress against.
+ * @param conf The Configuration object to use.
+ * @param fs Filesystem to use
+ * @param rootDir Root directory for HBase instance
+ * @param info Info for region to be opened.
+ * @param htd the table descriptor
+ * @param wal WAL for region to use. This method will call
+ * WAL#setSequenceNumber(long) passing the result of the call to
+ * HRegion#getMinSequenceId() to ensure the wal id is properly kept
+ * up. HRegionStore does this every time it opens a new region.
+ * @param rsServices An interface we can request flushes against.
+ * @param reporter An interface we can report progress against.
+ * @param keyManagementService reference to {@link KeyManagementService} or null
* @return new HRegion
*/
public static HRegion openHRegion(final Configuration conf, final FileSystem fs,
final Path rootDir, final RegionInfo info, final TableDescriptor htd, final WAL wal,
- final RegionServerServices rsServices, final CancelableProgressable reporter)
- throws IOException {
+ final RegionServerServices rsServices, final CancelableProgressable reporter,
+ final KeyManagementService keyManagementService) throws IOException {
Path tableDir = CommonFSUtils.getTableDir(rootDir, info.getTable());
- return openHRegionFromTableDir(conf, fs, tableDir, info, htd, wal, rsServices, reporter);
+ return openHRegionFromTableDir(conf, fs, tableDir, info, htd, wal, rsServices, reporter,
+ keyManagementService);
}
/**
* Open a Region.
- * @param conf The Configuration object to use.
- * @param fs Filesystem to use
- * @param info Info for region to be opened.
- * @param htd the table descriptor
- * @param wal WAL for region to use. This method will call WAL#setSequenceNumber(long)
- * passing the result of the call to HRegion#getMinSequenceId() to ensure the
- * wal id is properly kept up. HRegionStore does this every time it opens a new
- * region.
- * @param rsServices An interface we can request flushes against.
- * @param reporter An interface we can report progress against.
+ * @param conf The Configuration object to use.
+ * @param fs Filesystem to use
+ * @param info Info for region to be opened.
+ * @param htd the table descriptor
+ * @param wal WAL for region to use. This method will call
+ * WAL#setSequenceNumber(long) passing the result of the call to
+ * HRegion#getMinSequenceId() to ensure the wal id is properly kept
+ * up. HRegionStore does this every time it opens a new region.
+ * @param rsServices An interface we can request flushes against.
+ * @param reporter An interface we can report progress against.
+ * @param keyManagementService reference to {@link KeyManagementService} or null
* @return new HRegion
* @throws NullPointerException if {@code info} is {@code null}
*/
public static HRegion openHRegionFromTableDir(final Configuration conf, final FileSystem fs,
final Path tableDir, final RegionInfo info, final TableDescriptor htd, final WAL wal,
- final RegionServerServices rsServices, final CancelableProgressable reporter)
- throws IOException {
+ final RegionServerServices rsServices, final CancelableProgressable reporter,
+ final KeyManagementService keyManagementService) throws IOException {
Objects.requireNonNull(info, "RegionInfo cannot be null");
LOG.debug("Opening region: {}", info);
- HRegion r = HRegion.newHRegion(tableDir, wal, fs, conf, info, htd, rsServices);
+ HRegion r =
+ HRegion.newHRegion(tableDir, wal, fs, conf, info, htd, rsServices, keyManagementService);
return r.openHRegion(reporter);
}
@@ -7835,19 +8003,15 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
* @param reporter An interface we can report progress against.
* @return new HRegion
*/
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.UNITTEST)
public static HRegion openHRegion(final HRegion other, final CancelableProgressable reporter)
throws IOException {
HRegionFileSystem regionFs = other.getRegionFileSystem();
HRegion r = newHRegion(regionFs.getTableDir(), other.getWAL(), regionFs.getFileSystem(),
- other.baseConf, other.getRegionInfo(), other.getTableDescriptor(), null);
+ other.baseConf, other.getRegionInfo(), other.getTableDescriptor(), null, null);
return r.openHRegion(reporter);
}
- public static Region openHRegion(final Region other, final CancelableProgressable reporter)
- throws IOException {
- return openHRegion((HRegion) other, reporter);
- }
-
/**
* Open HRegion.
* <p/>
@@ -7913,7 +8077,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
if (info.getReplicaId() <= 0) {
info = RegionReplicaUtil.getRegionInfoForReplica(info, 1);
}
- HRegion r = HRegion.newHRegion(tableDir, null, fs, conf, info, htd, null);
+ HRegion r = HRegion.newHRegion(tableDir, null, fs, conf, info, htd, null, null);
r.writestate.setReadOnly(true);
return r.openHRegion(null);
}
@@ -7933,7 +8097,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
if (fs == null) {
fs = rootDir.getFileSystem(conf);
}
- HRegion r = HRegion.newHRegion(tableDir, wal, fs, conf, info, htd, null);
+ HRegion r = HRegion.newHRegion(tableDir, wal, fs, conf, info, htd, null, null);
r.initializeWarmup(reporter);
r.close();
return r;
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java
index 98299c47302..995f7fa6c47 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java
@@ -81,6 +81,7 @@ import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder;
import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoderImpl;
import org.apache.hadoop.hbase.io.hfile.HFileScanner;
import org.apache.hadoop.hbase.io.hfile.InvalidHFileException;
+import org.apache.hadoop.hbase.keymeta.KeyNamespaceUtil;
import org.apache.hadoop.hbase.monitoring.MonitoredTask;
import org.apache.hadoop.hbase.quotas.RegionSizeStore;
import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
@@ -93,7 +94,7 @@ import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
import org.apache.hadoop.hbase.regionserver.throttle.ThroughputController;
import org.apache.hadoop.hbase.regionserver.wal.WALUtil;
-import org.apache.hadoop.hbase.security.EncryptionUtil;
+import org.apache.hadoop.hbase.security.SecurityUtil;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.ClassSize;
@@ -336,7 +337,9 @@ public class HStore
private StoreContext initializeStoreContext(ColumnFamilyDescriptor family) throws IOException {
return new StoreContext.Builder().withBlockSize(family.getBlocksize())
- .withEncryptionContext(EncryptionUtil.createEncryptionContext(conf, family))
+ .withEncryptionContext(SecurityUtil.createEncryptionContext(conf, family,
+ region.getManagedKeyDataCache(), region.getSystemKeyCache(),
+ KeyNamespaceUtil.constructKeyNamespace(region.getTableDescriptor(), family)))
.withBloomType(family.getBloomFilterType()).withCacheConfig(createCacheConf(family))
.withCellComparator(region.getTableDescriptor().isMetaTable() || conf
.getBoolean(HRegion.USE_META_CELL_COMPARATOR, HRegion.DEFAULT_USE_META_CELL_COMPARATOR)
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStoreFile.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStoreFile.java
index a7df71f460e..0fb5c2e5f94 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStoreFile.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStoreFile.java
@@ -17,6 +17,8 @@
*/
package org.apache.hadoop.hbase.regionserver;
+import static org.apache.hadoop.hbase.io.crypto.ManagedKeyData.KEY_SPACE_GLOBAL;
+
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@@ -43,7 +45,11 @@ import org.apache.hadoop.hbase.io.hfile.CacheConfig;
import org.apache.hadoop.hbase.io.hfile.HFile;
import org.apache.hadoop.hbase.io.hfile.ReaderContext;
import org.apache.hadoop.hbase.io.hfile.ReaderContext.ReaderType;
+import org.apache.hadoop.hbase.keymeta.KeyNamespaceUtil;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
+import org.apache.hadoop.hbase.security.SecurityUtil;
import org.apache.hadoop.hbase.util.BloomFilterFactory;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.yetus.audience.InterfaceAudience;
@@ -213,9 +219,16 @@ public class HStoreFile implements StoreFile {
*/
private final BloomType cfBloomType;
+ private String keyNamespace;
+
+ private SystemKeyCache systemKeyCache;
+
+ private final ManagedKeyDataCache managedKeyDataCache;
+
/**
* Constructor, loads a reader and it's indices, etc. May allocate a substantial amount of ram
- * depending on the underlying files (10-20MB?).
+ * depending on the underlying files (10-20MB?). Since this is used only in read path, key
+ * namespace is not needed.
* @param fs The current file system to use.
* @param p The path of the file.
* @param conf The current configuration.
@@ -229,7 +242,9 @@ public class HStoreFile implements StoreFile {
*/
public HStoreFile(FileSystem fs, Path p, Configuration conf, CacheConfig cacheConf,
BloomType cfBloomType, boolean primaryReplica, StoreFileTracker sft) throws IOException {
- this(sft.getStoreFileInfo(p, primaryReplica), cfBloomType, cacheConf);
+ this(sft.getStoreFileInfo(p, primaryReplica), cfBloomType, cacheConf, null, null,
+ SecurityUtil.isKeyManagementEnabled(conf) ? SystemKeyCache.createCache(conf, fs) : null,
+ SecurityUtil.isKeyManagementEnabled(conf) ? new ManagedKeyDataCache(conf, null) : null);
}
/**
@@ -243,8 +258,15 @@ public class HStoreFile implements StoreFile {
* ignored.
* @param cacheConf The cache configuration and block cache reference.
*/
- public HStoreFile(StoreFileInfo fileInfo, BloomType cfBloomType, CacheConfig cacheConf) {
- this(fileInfo, cfBloomType, cacheConf, null);
+ public HStoreFile(StoreFileInfo fileInfo, BloomType cfBloomType, CacheConfig cacheConf)
+ throws IOException {
+ this(fileInfo, cfBloomType, cacheConf, null, KeyNamespaceUtil.constructKeyNamespace(fileInfo),
+ SecurityUtil.isKeyManagementEnabled(fileInfo.getConf())
+ ? SystemKeyCache.createCache(fileInfo.getConf(), fileInfo.getFileSystem())
+ : null,
+ SecurityUtil.isKeyManagementEnabled(fileInfo.getConf())
+ ? new ManagedKeyDataCache(fileInfo.getConf(), null)
+ : null);
}
/**
@@ -260,10 +282,14 @@ public class HStoreFile implements StoreFile {
* @param metrics Tracks bloom filter requests and results. May be null.
*/
public HStoreFile(StoreFileInfo fileInfo, BloomType cfBloomType, CacheConfig cacheConf,
- BloomFilterMetrics metrics) {
+ BloomFilterMetrics metrics, String keyNamespace, SystemKeyCache systemKeyCache,
+ ManagedKeyDataCache managedKeyDataCache) {
this.fileInfo = fileInfo;
this.cacheConf = cacheConf;
this.metrics = metrics;
+ this.keyNamespace = keyNamespace != null ? keyNamespace : KEY_SPACE_GLOBAL;
+ this.systemKeyCache = systemKeyCache;
+ this.managedKeyDataCache = managedKeyDataCache;
if (BloomFilterFactory.isGeneralBloomEnabled(fileInfo.getConf())) {
this.cfBloomType = cfBloomType;
} else {
@@ -392,7 +418,8 @@ public class HStoreFile implements StoreFile {
private void open() throws IOException {
fileInfo.initHDFSBlocksDistribution();
long readahead = fileInfo.isNoReadahead() ? 0L : -1L;
- ReaderContext context = fileInfo.createReaderContext(false, readahead, ReaderType.PREAD);
+ ReaderContext context = fileInfo.createReaderContext(false, readahead, ReaderType.PREAD,
+ keyNamespace, systemKeyCache, managedKeyDataCache);
fileInfo.initHFileInfo(context);
StoreFileReader reader = fileInfo.preStoreFileReaderOpen(context, cacheConf);
if (reader == null) {
@@ -540,7 +567,8 @@ public class HStoreFile implements StoreFile {
private StoreFileReader createStreamReader(boolean canUseDropBehind) throws IOException {
initReader();
final boolean doDropBehind = canUseDropBehind && cacheConf.shouldDropBehindCompaction();
- ReaderContext context = fileInfo.createReaderContext(doDropBehind, -1, ReaderType.STREAM);
+ ReaderContext context = fileInfo.createReaderContext(doDropBehind, -1, ReaderType.STREAM,
+ keyNamespace, systemKeyCache, managedKeyDataCache);
StoreFileReader reader = fileInfo.preStoreFileReaderOpen(context, cacheConf);
if (reader == null) {
reader = fileInfo.createReader(context, cacheConf);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java
index a46e2dae695..db5cec9f322 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java
@@ -32,6 +32,7 @@ import org.apache.hadoop.hbase.client.locking.EntityLock;
import org.apache.hadoop.hbase.executor.ExecutorService;
import org.apache.hadoop.hbase.io.hfile.BlockCache;
import org.apache.hadoop.hbase.ipc.RpcServerInterface;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.mob.MobFileCache;
import org.apache.hadoop.hbase.quotas.RegionServerRpcQuotaManager;
import org.apache.hadoop.hbase.quotas.RegionServerSpaceQuotaManager;
@@ -54,7 +55,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProto
* judicious adding API. Changes cause ripples through the code base.
*/
@InterfaceAudience.Private
-public interface RegionServerServices extends Server, MutableOnlineRegions, FavoredNodesForRegion {
+public interface RegionServerServices
+ extends Server, MutableOnlineRegions, FavoredNodesForRegion, KeyManagementService {
/** Returns the WAL for a particular region. Pass null for getting the default (common) WAL */
WAL getWAL(RegionInfo regionInfo) throws IOException;
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServicesForStores.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServicesForStores.java
index 1c837d216f3..99833263737 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServicesForStores.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServicesForStores.java
@@ -25,6 +25,8 @@ import org.apache.hadoop.hbase.executor.ExecutorService;
import org.apache.hadoop.hbase.executor.ExecutorService.ExecutorConfig;
import org.apache.hadoop.hbase.executor.ExecutorType;
import org.apache.hadoop.hbase.io.ByteBuffAllocator;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.hadoop.hbase.wal.WAL;
import org.apache.yetus.audience.InterfaceAudience;
@@ -117,4 +119,12 @@ public class RegionServicesForStores {
long getMemStoreSize() {
return region.getMemStoreDataSize();
}
+
+ public ManagedKeyDataCache getManagedKeyDataCache() {
+ return rsServices.getManagedKeyDataCache();
+ }
+
+ public SystemKeyCache getSystemKeyCache() {
+ return rsServices.getSystemKeyCache();
+ }
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreEngine.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreEngine.java
index 30cf5e2a92f..08e71082635 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreEngine.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreEngine.java
@@ -41,6 +41,9 @@ import org.apache.hadoop.hbase.ExtendedCell;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.conf.ConfigKey;
import org.apache.hadoop.hbase.io.hfile.BloomFilterMetrics;
+import org.apache.hadoop.hbase.keymeta.KeyNamespaceUtil;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.hadoop.hbase.log.HBaseMarkers;
import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
import org.apache.hadoop.hbase.regionserver.compactions.CompactionPolicy;
@@ -116,6 +119,10 @@ public abstract class StoreEngine<SF extends StoreFlusher, CP extends Compaction
private final ReadWriteLock storeLock = new ReentrantReadWriteLock();
+ private ManagedKeyDataCache managedKeyDataCache;
+
+ private SystemKeyCache systemKeyCache;
+
/**
* The name of the configuration parameter that specifies the class of a store engine that is used
* to manage and compact HBase store files.
@@ -208,6 +215,8 @@ public abstract class StoreEngine<SF extends StoreFlusher, CP extends Compaction
this.coprocessorHost = store.getHRegion().getCoprocessorHost();
this.openStoreFileThreadPoolCreator = store.getHRegion()::getStoreFileOpenAndCloseThreadPool;
this.storeFileTracker = createStoreFileTracker(conf, store);
+ this.managedKeyDataCache = store.getHRegion().getManagedKeyDataCache();
+ this.systemKeyCache = store.getHRegion().getSystemKeyCache();
assert compactor != null && compactionPolicy != null && storeFileManager != null
&& storeFlusher != null;
}
@@ -228,7 +237,8 @@ public abstract class StoreEngine<SF extends StoreFlusher, CP extends Compaction
public HStoreFile createStoreFileAndReader(StoreFileInfo info) throws IOException {
info.setRegionCoprocessorHost(coprocessorHost);
HStoreFile storeFile = new HStoreFile(info, ctx.getFamily().getBloomFilterType(),
- ctx.getCacheConf(), bloomFilterMetrics);
+ ctx.getCacheConf(), bloomFilterMetrics, KeyNamespaceUtil.constructKeyNamespace(ctx),
+ systemKeyCache, managedKeyDataCache);
storeFile.initReader();
return storeFile;
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFileInfo.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFileInfo.java
index cd748bb353a..1184f39da66 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFileInfo.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFileInfo.java
@@ -39,6 +39,8 @@ import org.apache.hadoop.hbase.io.hfile.InvalidHFileException;
import org.apache.hadoop.hbase.io.hfile.ReaderContext;
import org.apache.hadoop.hbase.io.hfile.ReaderContext.ReaderType;
import org.apache.hadoop.hbase.io.hfile.ReaderContextBuilder;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.hadoop.hbase.mob.MobUtils;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
import org.apache.hadoop.hbase.util.FSUtils;
@@ -227,6 +229,10 @@ public class StoreFileInfo implements Configurable {
this.conf.getBoolean(STORE_FILE_READER_NO_READAHEAD, DEFAULT_STORE_FILE_READER_NO_READAHEAD);
}
+ public HFileLink getLink() {
+ return link;
+ }
+
@Override
public Configuration getConf() {
return conf;
@@ -290,7 +296,8 @@ public class StoreFileInfo implements Configurable {
return reader;
}
- ReaderContext createReaderContext(boolean doDropBehind, long readahead, ReaderType type)
+ ReaderContext createReaderContext(boolean doDropBehind, long readahead, ReaderType type,
+ String keyNamespace, SystemKeyCache systemKeyCache, ManagedKeyDataCache managedKeyDataCache)
throws IOException {
FSDataInputStreamWrapper in;
FileStatus status;
@@ -319,7 +326,8 @@ public class StoreFileInfo implements Configurable {
long length = status.getLen();
ReaderContextBuilder contextBuilder =
new ReaderContextBuilder().withInputStreamWrapper(in).withFileSize(length)
- .withPrimaryReplicaReader(this.primaryReplica).withReaderType(type).withFileSystem(fs);
+ .withPrimaryReplicaReader(this.primaryReplica).withReaderType(type).withFileSystem(fs)
+ .withSystemKeyCache(systemKeyCache).withManagedKeyDataCache(managedKeyDataCache);
if (this.reference != null) {
contextBuilder.withFilePath(this.getPath());
} else {
@@ -733,7 +741,7 @@ public class StoreFileInfo implements Configurable {
}
}
- FileSystem getFileSystem() {
+ public FileSystem getFileSystem() {
return this.fs;
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTracker.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTracker.java
index 595d5f4d1fc..7e6980e854e 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTracker.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTracker.java
@@ -27,6 +27,7 @@ import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.io.Reference;
import org.apache.hadoop.hbase.regionserver.CreateStoreFileWriterParams;
import org.apache.hadoop.hbase.regionserver.HStoreFile;
+import org.apache.hadoop.hbase.regionserver.StoreContext;
import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
import org.apache.hadoop.hbase.regionserver.StoreFileWriter;
import org.apache.yetus.audience.InterfaceAudience;
@@ -153,4 +154,10 @@ public interface StoreFileTracker {
* @throws IOException if the archiving fails
*/
void removeStoreFiles(List<HStoreFile> storeFiles) throws IOException;
+
+ /**
+ * Get the store context. Get the store context.
+ * @return the store context.
+ */
+ StoreContext getStoreContext();
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTrackerBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTrackerBase.java
index 779a114af59..87eca7b93c9 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTrackerBase.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTrackerBase.java
@@ -375,6 +375,11 @@ abstract class StoreFileTrackerBase implements StoreFileTracker {
createBackRef);
}
+ @Override
+ public StoreContext getStoreContext() {
+ return ctx;
+ }
+
public void removeStoreFiles(List<HStoreFile> storeFiles) throws IOException {
archiveStoreFiles(storeFiles);
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSyncUp.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSyncUp.java
index 9bbaf8cd72d..1ca3f68ee99 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSyncUp.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSyncUp.java
@@ -41,9 +41,7 @@ import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.client.AsyncClusterConnection;
import org.apache.hadoop.hbase.client.Connection;
-import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
-import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
-import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.master.replication.OfflineTableReplicationQueueStorage;
import org.apache.hadoop.hbase.replication.ReplicationException;
import org.apache.hadoop.hbase.replication.ReplicationGroupOffset;
@@ -369,21 +367,6 @@ public class ReplicationSyncUp extends Configured implements Tool {
return null;
}
- @Override
- public SystemKeyCache getSystemKeyCache() {
- return null;
- }
-
- @Override
- public ManagedKeyDataCache getManagedKeyDataCache() {
- return null;
- }
-
- @Override
- public KeymetaAdmin getKeymetaAdmin() {
- return null;
- }
-
@Override
public FileSystem getFileSystem() {
return null;
@@ -403,5 +386,10 @@ public class ReplicationSyncUp extends Configured implements Tool {
public AsyncClusterConnection getAsyncClusterConnection() {
return null;
}
+
+ @Override
+ public KeyManagementService getKeyManagementService() {
+ return null;
+ }
}
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/SecurityUtil.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/SecurityUtil.java
index 92b5f340a61..2e6e4cb4f93 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/SecurityUtil.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/SecurityUtil.java
@@ -17,8 +17,19 @@
*/
package org.apache.hadoop.hbase.security;
+import java.io.IOException;
+import java.security.Key;
+import java.security.KeyException;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
+import org.apache.hadoop.hbase.io.crypto.Cipher;
+import org.apache.hadoop.hbase.io.crypto.Encryption;
+import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
+import org.apache.hadoop.hbase.io.hfile.FixedFileTrailer;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.yetus.audience.InterfaceStability;
@@ -28,7 +39,6 @@ import org.apache.yetus.audience.InterfaceStability;
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class SecurityUtil {
-
/**
* Get the user name from a principal
*/
@@ -48,6 +58,183 @@ public class SecurityUtil {
return (i > -1) ? principal.substring(0, i) : principal;
}
+ /**
+ * Helper to create an encyption context with current encryption key, suitable for writes.
+ * @param conf The current configuration.
+ * @param family The current column descriptor.
+ * @param managedKeyDataCache The managed key data cache.
+ * @param systemKeyCache The system key cache.
+ * @param keyNamespace The key namespace.
+ * @return The created encryption context.
+ * @throws IOException if an encryption key for the column cannot be unwrapped
+ * @throws IllegalStateException in case of encryption related configuration errors
+ */
+ public static Encryption.Context createEncryptionContext(Configuration conf,
+ ColumnFamilyDescriptor family, ManagedKeyDataCache managedKeyDataCache,
+ SystemKeyCache systemKeyCache, String keyNamespace) throws IOException {
+ Encryption.Context cryptoContext = Encryption.Context.NONE;
+ String cipherName = family.getEncryptionType();
+ if (cipherName != null) {
+ if (!Encryption.isEncryptionEnabled(conf)) {
+ throw new IllegalStateException("Encryption for family '" + family.getNameAsString()
+ + "' configured with type '" + cipherName + "' but the encryption feature is disabled");
+ }
+ Cipher cipher = null;
+ Key key = null;
+ ManagedKeyData kekKeyData = null;
+ if (isKeyManagementEnabled(conf)) {
+ kekKeyData = managedKeyDataCache.getActiveEntry(ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES,
+ keyNamespace);
+ // If no active key found in the specific namespace, try the global namespace
+ if (kekKeyData == null) {
+ kekKeyData = managedKeyDataCache.getActiveEntry(ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES,
+ ManagedKeyData.KEY_SPACE_GLOBAL);
+ keyNamespace = ManagedKeyData.KEY_SPACE_GLOBAL;
+ }
+ if (kekKeyData == null) {
+ throw new IOException(
+ "No active key found for custodian: " + ManagedKeyData.KEY_GLOBAL_CUSTODIAN
+ + " in namespaces: " + keyNamespace + " and " + ManagedKeyData.KEY_SPACE_GLOBAL);
+ }
+ if (
+ conf.getBoolean(HConstants.CRYPTO_MANAGED_KEYS_LOCAL_KEY_GEN_PER_FILE_ENABLED_CONF_KEY,
+ HConstants.CRYPTO_MANAGED_KEYS_LOCAL_KEY_GEN_PER_FILE_DEFAULT_ENABLED)
+ ) {
+ cipher =
+ getCipherIfValid(conf, cipherName, kekKeyData.getTheKey(), family.getNameAsString());
+ } else {
+ key = kekKeyData.getTheKey();
+ kekKeyData = systemKeyCache.getLatestSystemKey();
+ }
+ } else {
+ byte[] keyBytes = family.getEncryptionKey();
+ if (keyBytes != null) {
+ // Family provides specific key material
+ key = EncryptionUtil.unwrapKey(conf, keyBytes);
+ } else {
+ cipher = getCipherIfValid(conf, cipherName, null, null);
+ }
+ }
+ if (key != null || cipher != null) {
+ if (key == null) {
+ // Family does not provide key material, create a random key
+ key = cipher.getRandomKey();
+ }
+ if (cipher == null) {
+ cipher = getCipherIfValid(conf, cipherName, key, family.getNameAsString());
+ }
+ cryptoContext = Encryption.newContext(conf);
+ cryptoContext.setCipher(cipher);
+ cryptoContext.setKey(key);
+ cryptoContext.setKeyNamespace(keyNamespace);
+ cryptoContext.setKEKData(kekKeyData);
+ }
+ }
+ return cryptoContext;
+ }
+
+ /**
+ * Create an encryption context from encryption key found in a file trailer, suitable for read.
+ * @param conf The current configuration.
+ * @param path The path of the file.
+ * @param trailer The file trailer.
+ * @param managedKeyDataCache The managed key data cache.
+ * @param systemKeyCache The system key cache.
+ * @return The created encryption context or null if no key material is available.
+ * @throws IOException if an encryption key for the file cannot be unwrapped
+ */
+ public static Encryption.Context createEncryptionContext(Configuration conf, Path path,
+ FixedFileTrailer trailer, ManagedKeyDataCache managedKeyDataCache,
+ SystemKeyCache systemKeyCache) throws IOException {
+ ManagedKeyData kekKeyData = null;
+ byte[] keyBytes = trailer.getEncryptionKey();
+ Encryption.Context cryptoContext = Encryption.Context.NONE;
+ // Check for any key material available
+ if (keyBytes != null) {
+ cryptoContext = Encryption.newContext(conf);
+ Key kek = null;
+ // When the KEK medata is available, we will try to unwrap the encrypted key using the KEK,
+ // otherwise we will use the system keys starting from the latest to the oldest.
+ if (trailer.getKEKMetadata() != null) {
+ if (managedKeyDataCache == null) {
+ throw new IOException("Key management is enabled, but ManagedKeyDataCache is null");
+ }
+ Throwable cause = null;
+ try {
+ kekKeyData = managedKeyDataCache.getEntry(ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES,
+ trailer.getKeyNamespace(), trailer.getKEKMetadata(), keyBytes);
+ } catch (KeyException | IOException e) {
+ cause = e;
+ }
+ // When getEntry returns null we treat it the same as exception case.
+ if (kekKeyData == null) {
+ throw new IOException(
+ "Failed to get key data for KEK metadata: " + trailer.getKEKMetadata(), cause);
+ }
+ kek = kekKeyData.getTheKey();
+ } else {
+ if (SecurityUtil.isKeyManagementEnabled(conf)) {
+ if (systemKeyCache == null) {
+ throw new IOException("Key management is enabled, but SystemKeyCache is null");
+ }
+ ManagedKeyData systemKeyData =
+ systemKeyCache.getSystemKeyByChecksum(trailer.getKEKChecksum());
+ if (systemKeyData == null) {
+ throw new IOException(
+ "Failed to get system key by checksum: " + trailer.getKEKChecksum());
+ }
+ kek = systemKeyData.getTheKey();
+ kekKeyData = systemKeyData;
+ }
+ }
+ Key key;
+ if (kek != null) {
+ try {
+ key = EncryptionUtil.unwrapKey(conf, null, keyBytes, kek);
+ } catch (KeyException | IOException e) {
+ throw new IOException("Failed to unwrap key with KEK checksum: "
+ + trailer.getKEKChecksum() + ", metadata: " + trailer.getKEKMetadata(), e);
+ }
+ } else {
+ key = EncryptionUtil.unwrapKey(conf, keyBytes);
+ }
+ // Use the algorithm the key wants
+ Cipher cipher = getCipherIfValid(conf, key.getAlgorithm(), key, null);
+ cryptoContext.setCipher(cipher);
+ cryptoContext.setKey(key);
+ cryptoContext.setKEKData(kekKeyData);
+ }
+ return cryptoContext;
+ }
+
+ /**
+ * Get the cipher if the cipher name is valid, otherwise throw an exception.
+ * @param conf the configuration
+ * @param cipherName the cipher name to check
+ * @param key the key to check
+ * @param familyName the family name
+ * @return the cipher if the cipher name is valid
+ * @throws IllegalStateException if the cipher name is not valid
+ */
+ private static Cipher getCipherIfValid(Configuration conf, String cipherName, Key key,
+ String familyName) {
+ // Fail if misconfigured
+ // We use the encryption type specified in the column schema as a sanity check
+ // on
+ // what the wrapped key is telling us
+ if (key != null && !key.getAlgorithm().equalsIgnoreCase(cipherName)) {
+ throw new IllegalStateException(
+ "Encryption for family '" + familyName + "' configured with type '" + cipherName
+ + "' but key specifies algorithm '" + key.getAlgorithm() + "'");
+ }
+ // Use the algorithm the key wants
+ Cipher cipher = Encryption.getCipher(conf, cipherName);
+ if (cipher == null) {
+ throw new IllegalStateException("Cipher '" + cipherName + "' is not available");
+ }
+ return cipher;
+ }
+
/**
* From the given configuration, determine if key management is enabled.
* @param conf the configuration to check
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ModifyRegionUtils.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ModifyRegionUtils.java
index 564c46ad5bf..db7a9422b75 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ModifyRegionUtils.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ModifyRegionUtils.java
@@ -34,6 +34,7 @@ import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionInfoBuilder;
import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
@@ -90,20 +91,27 @@ public abstract class ModifyRegionUtils {
* @param newRegions {@link RegionInfo} that describes the regions to create
* @param task {@link RegionFillTask} custom code to populate region after creation
*/
- public static List<RegionInfo> createRegions(final Configuration conf, final Path rootDir,
+ public static List<RegionInfo> createRegions(final MasterProcedureEnv env, final Path rootDir,
final TableDescriptor tableDescriptor, final RegionInfo[] newRegions, final RegionFillTask task)
throws IOException {
if (newRegions == null) return null;
int regionNumber = newRegions.length;
- ThreadPoolExecutor exec = getRegionOpenAndInitThreadPool(conf,
+ ThreadPoolExecutor exec = getRegionOpenAndInitThreadPool(env.getMasterConfiguration(),
"RegionOpenAndInit-" + tableDescriptor.getTableName(), regionNumber);
try {
- return createRegions(exec, conf, rootDir, tableDescriptor, newRegions, task);
+ return createRegions(exec, env.getMasterConfiguration(), env, rootDir, tableDescriptor,
+ newRegions, task);
} finally {
exec.shutdownNow();
}
}
+ public static List<RegionInfo> createRegions(final ThreadPoolExecutor exec,
+ final Configuration conf, final Path rootDir, final TableDescriptor tableDescriptor,
+ final RegionInfo[] newRegions, final RegionFillTask task) throws IOException {
+ return createRegions(exec, conf, null, rootDir, tableDescriptor, newRegions, task);
+ }
+
/**
* Create new set of regions on the specified file-system. NOTE: that you should add the regions
* to hbase:meta after this operation.
@@ -115,8 +123,9 @@ public abstract class ModifyRegionUtils {
* @param task {@link RegionFillTask} custom code to populate region after creation
*/
public static List<RegionInfo> createRegions(final ThreadPoolExecutor exec,
- final Configuration conf, final Path rootDir, final TableDescriptor tableDescriptor,
- final RegionInfo[] newRegions, final RegionFillTask task) throws IOException {
+ final Configuration conf, final MasterProcedureEnv env, final Path rootDir,
+ final TableDescriptor tableDescriptor, final RegionInfo[] newRegions, final RegionFillTask task)
+ throws IOException {
if (newRegions == null) return null;
int regionNumber = newRegions.length;
CompletionService<RegionInfo> completionService = new ExecutorCompletionService<>(exec);
@@ -125,7 +134,7 @@ public abstract class ModifyRegionUtils {
completionService.submit(new Callable<RegionInfo>() {
@Override
public RegionInfo call() throws IOException {
- return createRegion(conf, rootDir, tableDescriptor, newRegion, task);
+ return createRegion(conf, env, rootDir, tableDescriptor, newRegion, task);
}
});
}
@@ -151,15 +160,16 @@ public abstract class ModifyRegionUtils {
* @param newRegion {@link RegionInfo} that describes the region to create
* @param task {@link RegionFillTask} custom code to populate region after creation
*/
- public static RegionInfo createRegion(final Configuration conf, final Path rootDir,
- final TableDescriptor tableDescriptor, final RegionInfo newRegion, final RegionFillTask task)
- throws IOException {
+ public static RegionInfo createRegion(final Configuration conf, final MasterProcedureEnv env,
+ final Path rootDir, final TableDescriptor tableDescriptor, final RegionInfo newRegion,
+ final RegionFillTask task) throws IOException {
// 1. Create HRegion
// The WAL subsystem will use the default rootDir rather than the passed in rootDir
// unless I pass along via the conf.
Configuration confForWAL = new Configuration(conf);
confForWAL.set(HConstants.HBASE_DIR, rootDir.toString());
- HRegion region = HRegion.createHRegion(newRegion, rootDir, conf, tableDescriptor, null, false);
+ HRegion region = HRegion.createHRegion(newRegion, rootDir, conf, tableDescriptor, null, false,
+ null, env == null ? null : env.getMasterServices());
try {
// 2. Custom user code to interact with the created region
if (task != null) {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/ManagedKeyTestBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/ManagedKeyTestBase.java
index caccf816c8a..f3b2e2ca1ad 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/ManagedKeyTestBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/ManagedKeyTestBase.java
@@ -19,6 +19,7 @@ package org.apache.hadoop.hbase.keymeta;
import org.apache.hadoop.hbase.HBaseTestingUtil;
import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.io.crypto.ManagedKeyProvider;
import org.apache.hadoop.hbase.io.crypto.MockManagedKeyProvider;
import org.junit.After;
import org.junit.Before;
@@ -29,7 +30,7 @@ public class ManagedKeyTestBase {
@Before
public void setUp() throws Exception {
TEST_UTIL.getConfiguration().set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY,
- MockManagedKeyProvider.class.getName());
+ getKeyProviderClass().getName());
TEST_UTIL.getConfiguration().set(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, "true");
TEST_UTIL.getConfiguration().set("hbase.coprocessor.master.classes",
KeymetaServiceEndpoint.class.getName());
@@ -44,4 +45,8 @@ public class ManagedKeyTestBase {
public void tearDown() throws Exception {
TEST_UTIL.shutdownMiniCluster();
}
+
+ protected Class<? extends ManagedKeyProvider> getKeyProviderClass() {
+ return MockManagedKeyProvider.class;
+ }
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyManagementBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyManagementBase.java
index d476e0619ca..8ae91de6588 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyManagementBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyManagementBase.java
@@ -17,6 +17,7 @@
*/
package org.apache.hadoop.hbase.keymeta;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -25,7 +26,7 @@ import static org.mockito.Mockito.when;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.Server;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.junit.ClassRule;
@@ -45,22 +46,36 @@ public class TestKeyManagementBase {
conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY,
"org.apache.hadoop.hbase.keymeta.DummyKeyProvider");
- Server mockServer = mock(Server.class);
+ MasterServices mockServer = mock(MasterServices.class);
when(mockServer.getConfiguration()).thenReturn(conf);
- KeyManagementBase keyMgmt = new TestKeyManagement(mockServer);
+ final KeyManagementBase keyMgmt = new TestKeyManagement(mockServer);
+ assertEquals(mockServer, keyMgmt.getKeyManagementService());
// Should throw RuntimeException when provider is not ManagedKeyProvider
RuntimeException exception = assertThrows(RuntimeException.class, () -> {
keyMgmt.getKeyProvider();
});
-
assertTrue(exception.getMessage().contains("expected to be of type ManagedKeyProvider"));
+ exception = assertThrows(RuntimeException.class, () -> {
+ KeyManagementBase keyMgmt2 = new TestKeyManagement(conf);
+ keyMgmt2.getKeyProvider();
+ });
+ assertTrue(exception.getMessage().contains("expected to be of type ManagedKeyProvider"));
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ Configuration configuration = null;
+ new TestKeyManagement(configuration);
+ });
}
private static class TestKeyManagement extends KeyManagementBase {
- public TestKeyManagement(Server server) {
+ public TestKeyManagement(MasterServices server) {
super(server);
}
+
+ public TestKeyManagement(Configuration configuration) {
+ super(configuration);
+ }
}
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyManagementService.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyManagementService.java
new file mode 100644
index 00000000000..3fe669f90d8
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyManagementService.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.keymeta;
+
+import static org.apache.hadoop.hbase.HConstants.SYSTEM_KEY_FILE_PREFIX;
+import static org.apache.hadoop.hbase.io.crypto.KeymetaTestUtils.SeekableByteArrayInputStream;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.io.crypto.Encryption;
+import org.apache.hadoop.hbase.io.crypto.KeymetaTestUtils;
+import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
+import org.apache.hadoop.hbase.io.crypto.MockManagedKeyProvider;
+import org.apache.hadoop.hbase.testclassification.MiscTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.apache.hadoop.hbase.util.CommonFSUtils;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TestName;
+
+@Category({ MiscTests.class, SmallTests.class })
+public class TestKeyManagementService {
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestKeyManagementService.class);
+
+ @Rule
+ public TestName name = new TestName();
+
+ protected Configuration conf = new Configuration();
+ protected FileSystem mockFileSystem = mock(FileSystem.class);
+
+ @Before
+ public void setUp() throws Exception {
+ conf.set(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, "true");
+ conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockManagedKeyProvider.class.getName());
+ conf.set(HConstants.HBASE_ORIGINAL_DIR, "/tmp/hbase");
+ }
+
+ @Test
+ public void testDefaultKeyManagementServiceCreation() throws IOException {
+ // SystemKeyCache needs at least one valid key to be created, so setting up a mock FS that
+ // returns a mock file that returns a known mocked key metadata.
+ MockManagedKeyProvider provider = (MockManagedKeyProvider) Encryption.getKeyProvider(conf);
+ ManagedKeyData keyData =
+ provider.getManagedKey("system".getBytes(), ManagedKeyData.KEY_SPACE_GLOBAL);
+ String fileName = SYSTEM_KEY_FILE_PREFIX + "1";
+ Path systemKeyDir = CommonFSUtils.getSystemKeyDir(conf);
+ FileStatus mockFileStatus = KeymetaTestUtils.createMockFile(fileName);
+
+ // Create a real FSDataInputStream that contains the key metadata in UTF format
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeUTF(keyData.getKeyMetadata());
+ dos.close();
+
+ SeekableByteArrayInputStream seekableStream =
+ new SeekableByteArrayInputStream(baos.toByteArray());
+ FSDataInputStream realStream = new FSDataInputStream(seekableStream);
+
+ when(mockFileSystem.open(eq(mockFileStatus.getPath()))).thenReturn(realStream);
+ when(mockFileSystem.globStatus(eq(new Path(systemKeyDir, SYSTEM_KEY_FILE_PREFIX + "*"))))
+ .thenReturn(new FileStatus[] { mockFileStatus });
+
+ KeyManagementService service = KeyManagementService.createDefault(conf, mockFileSystem);
+ assertNotNull(service);
+ assertNotNull(service.getSystemKeyCache());
+ assertNotNull(service.getManagedKeyDataCache());
+ assertThrows(UnsupportedOperationException.class, () -> service.getKeymetaAdmin());
+ }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyNamespaceUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyNamespaceUtil.java
new file mode 100644
index 00000000000..1012d2b5a08
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeyNamespaceUtil.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.keymeta;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.RegionInfoBuilder;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.io.HFileLink;
+import org.apache.hadoop.hbase.io.crypto.KeymetaTestUtils;
+import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
+import org.apache.hadoop.hbase.regionserver.StoreContext;
+import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
+import org.apache.hadoop.hbase.testclassification.MiscTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({ MiscTests.class, SmallTests.class })
+public class TestKeyNamespaceUtil {
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestKeyNamespaceUtil.class);
+
+ @Test
+ public void testConstructKeyNamespace_FromTableDescriptorAndFamilyDescriptor() {
+ TableDescriptor tableDescriptor = mock(TableDescriptor.class);
+ ColumnFamilyDescriptor familyDescriptor = mock(ColumnFamilyDescriptor.class);
+ when(tableDescriptor.getTableName()).thenReturn(TableName.valueOf("test"));
+ when(familyDescriptor.getNameAsString()).thenReturn("family");
+ String keyNamespace = KeyNamespaceUtil.constructKeyNamespace(tableDescriptor, familyDescriptor);
+ assertEquals("test/family", keyNamespace);
+ }
+
+ @Test
+ public void testConstructKeyNamespace_FromStoreContext() {
+ // Test store context path construction
+ TableName tableName = TableName.valueOf("test");
+ RegionInfo regionInfo = RegionInfoBuilder.newBuilder(tableName).build();
+ HRegionFileSystem regionFileSystem = mock(HRegionFileSystem.class);
+ when(regionFileSystem.getRegionInfo()).thenReturn(regionInfo);
+
+ ColumnFamilyDescriptor familyDescriptor = ColumnFamilyDescriptorBuilder.of("family");
+
+ StoreContext storeContext = StoreContext.getBuilder().withRegionFileSystem(regionFileSystem)
+ .withColumnFamilyDescriptor(familyDescriptor).build();
+
+ String keyNamespace = KeyNamespaceUtil.constructKeyNamespace(storeContext);
+ assertEquals("test/family", keyNamespace);
+ }
+
+ @Test
+ public void testConstructKeyNamespace_FromStoreFileInfo_RegularFile() {
+ // Test both regular files and linked files
+ StoreFileInfo storeFileInfo = mock(StoreFileInfo.class);
+ when(storeFileInfo.isLink()).thenReturn(false);
+ Path path = KeymetaTestUtils.createMockPath("test", "family");
+ when(storeFileInfo.getPath()).thenReturn(path);
+ String keyNamespace = KeyNamespaceUtil.constructKeyNamespace(storeFileInfo);
+ assertEquals("test/family", keyNamespace);
+ }
+
+ @Test
+ public void testConstructKeyNamespace_FromStoreFileInfo_LinkedFile() {
+ // Test both regular files and linked files
+ StoreFileInfo storeFileInfo = mock(StoreFileInfo.class);
+ HFileLink link = mock(HFileLink.class);
+ when(storeFileInfo.isLink()).thenReturn(true);
+ Path path = KeymetaTestUtils.createMockPath("test", "family");
+ when(link.getOriginPath()).thenReturn(path);
+ when(storeFileInfo.getLink()).thenReturn(link);
+ String keyNamespace = KeyNamespaceUtil.constructKeyNamespace(storeFileInfo);
+ assertEquals("test/family", keyNamespace);
+ }
+
+ @Test
+ public void testConstructKeyNamespace_FromPath() {
+ // Test path parsing with different HBase directory structures
+ Path path = KeymetaTestUtils.createMockPath("test", "family");
+ String keyNamespace = KeyNamespaceUtil.constructKeyNamespace(path);
+ assertEquals("test/family", keyNamespace);
+ }
+
+ @Test
+ public void testConstructKeyNamespace_FromStrings() {
+ // Test string-based construction
+ String tableName = "test";
+ String family = "family";
+ String keyNamespace = KeyNamespaceUtil.constructKeyNamespace(tableName, family);
+ assertEquals("test/family", keyNamespace);
+ }
+
+ @Test
+ public void testConstructKeyNamespace_NullChecks() {
+ // Test null inputs for both table name and family
+ assertThrows(NullPointerException.class,
+ () -> KeyNamespaceUtil.constructKeyNamespace(null, "family"));
+ assertThrows(NullPointerException.class,
+ () -> KeyNamespaceUtil.constructKeyNamespace("test", null));
+ }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeymetaEndpoint.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeymetaEndpoint.java
index bc8e14fe4b3..7c884bdd27e 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeymetaEndpoint.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeymetaEndpoint.java
@@ -148,9 +148,7 @@ public class TestKeymetaEndpoint {
public void testGetResponseBuilder() {
// Arrange
String keyCust = Base64.getEncoder().encodeToString("testKey".getBytes());
- String keyNamespace = "testNamespace";
- ManagedKeysRequest request =
- requestBuilder.setKeyCust(keyCust).setKeyNamespace(keyNamespace).build();
+ ManagedKeysRequest request = requestBuilder.setKeyCust(keyCust).build();
// Act
ManagedKeysResponse.Builder result =
@@ -158,7 +156,6 @@ public class TestKeymetaEndpoint {
// Assert
assertNotNull(result);
- assertEquals(keyNamespace, result.getKeyNamespace());
assertArrayEquals("testKey".getBytes(), result.getKeyCustBytes().toByteArray());
verify(controller, never()).setFailed(anyString());
}
@@ -167,9 +164,7 @@ public class TestKeymetaEndpoint {
public void testGetResponseBuilderWithInvalidBase64() {
// Arrange
String keyCust = "invalidBase64!";
- String keyNamespace = "testNamespace";
- ManagedKeysRequest request =
- requestBuilder.setKeyCust(keyCust).setKeyNamespace(keyNamespace).build();
+ ManagedKeysRequest request = requestBuilder.setKeyCust(keyCust).build();
// Act
ManagedKeysResponse.Builder result =
@@ -177,7 +172,6 @@ public class TestKeymetaEndpoint {
// Assert
assertNotNull(result);
- assertEquals(keyNamespace, result.getKeyNamespace());
assertEquals(KEY_FAILED, result.getKeyState());
verify(controller).setFailed(contains("Failed to decode specified prefix as Base64 string"));
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeymetaTableAccessor.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeymetaTableAccessor.java
index 12b74e1c3bc..b695dedcdf9 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeymetaTableAccessor.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestKeymetaTableAccessor.java
@@ -57,7 +57,6 @@ import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.Durability;
import org.apache.hadoop.hbase.client.Get;
@@ -69,6 +68,7 @@ import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyState;
import org.apache.hadoop.hbase.io.crypto.MockManagedKeyProvider;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.security.EncryptionUtil;
import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
@@ -99,7 +99,7 @@ public class TestKeymetaTableAccessor {
protected static String KEY_METADATA = "metadata1";
@Mock
- protected Server server;
+ protected MasterServices server;
@Mock
protected Connection connection;
@Mock
@@ -127,6 +127,7 @@ public class TestKeymetaTableAccessor {
when(connection.getTable(KeymetaTableAccessor.KEY_META_TABLE_NAME)).thenReturn(table);
when(server.getSystemKeyCache()).thenReturn(systemKeyCache);
when(server.getConfiguration()).thenReturn(conf);
+ when(server.getKeyManagementService()).thenReturn(server);
accessor = new KeymetaTableAccessor(server);
managedKeyProvider = new MockManagedKeyProvider();
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestManagedKeyDataCache.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestManagedKeyDataCache.java
index 61678e316ce..807586a9a47 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestManagedKeyDataCache.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestManagedKeyDataCache.java
@@ -425,7 +425,7 @@ public class TestManagedKeyDataCache {
@Test
public void testGenericCacheWithKeymetaAccessorException() throws Exception {
when(mockL2.getKey(CUST_ID, KEY_SPACE_GLOBAL, "test-metadata"))
- .thenThrow(new IOException("Test exception"));
+ .thenThrow(new IOException("Test exception"));
assertNull(cache.getEntry(CUST_ID, KEY_SPACE_GLOBAL, "test-metadata", null));
verify(mockL2).getKey(any(), any(String.class), any(String.class));
clearInvocations(mockL2);
@@ -436,7 +436,7 @@ public class TestManagedKeyDataCache {
@Test
public void testGetActiveEntryWithKeymetaAccessorException() throws Exception {
when(mockL2.getActiveKey(CUST_ID, KEY_SPACE_GLOBAL))
- .thenThrow(new IOException("Test exception"));
+ .thenThrow(new IOException("Test exception"));
assertNull(cache.getActiveEntry(CUST_ID, KEY_SPACE_GLOBAL));
verify(mockL2).getActiveKey(any(), any(String.class));
clearInvocations(mockL2);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestSystemKeyCache.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestSystemKeyCache.java
index 9882b823da8..f541d4bac18 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestSystemKeyCache.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/keymeta/TestSystemKeyCache.java
@@ -47,7 +47,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
- * Tests for SystemKeyCache class
+ * Tests for SystemKeyCache class. NOTE: The createCache() method is tested in
+ * TestKeyManagementService.
*/
@Category({ MasterTests.class, SmallTests.class })
public class TestSystemKeyCache {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MasterStateStoreTestBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MasterStateStoreTestBase.java
index f5c25992747..9cf69775a30 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MasterStateStoreTestBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MasterStateStoreTestBase.java
@@ -26,7 +26,6 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.ChoreService;
import org.apache.hadoop.hbase.HBaseZKTestingUtil;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
@@ -72,7 +71,7 @@ public abstract class MasterStateStoreTestBase {
CHORE_SERVICE = new ChoreService("TestMasterStateStore");
HFILE_CLEANER_POOL = DirScanPool.getHFileCleanerScanPool(conf);
LOG_CLEANER_POOL = DirScanPool.getLogCleanerScanPool(conf);
- Server server = mock(Server.class);
+ MasterServices server = mock(MasterServices.class);
when(server.getConfiguration()).thenReturn(conf);
when(server.getServerName())
.thenReturn(ServerName.valueOf("localhost", 12345, EnvironmentEdgeManager.currentTime()));
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
index 6af4dcec1ad..5b522dc9107 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
@@ -38,6 +38,7 @@ import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.executor.ExecutorService;
import org.apache.hadoop.hbase.favored.FavoredNodesManager;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
@@ -587,8 +588,12 @@ public class MockNoopMasterServices implements MasterServices {
return 0;
}
- @Override
public long rollAllWALWriters(long nonceGroup, long nonce) throws IOException {
return 0;
}
+
+ @Override
+ public KeyManagementService getKeyManagementService() {
+ return this;
+ }
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java
index ba3387b8ad1..81977c24b29 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java
@@ -52,6 +52,7 @@ import org.apache.hadoop.hbase.executor.ExecutorService;
import org.apache.hadoop.hbase.io.hfile.BlockCache;
import org.apache.hadoop.hbase.ipc.HBaseRpcController;
import org.apache.hadoop.hbase.ipc.RpcServerInterface;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
@@ -775,4 +776,9 @@ class MockRegionServer implements AdminProtos.AdminService.BlockingInterface,
ReplicateWALEntryRequest request) throws ServiceException {
return null;
}
+
+ @Override
+ public KeyManagementService getKeyManagementService() {
+ return null;
+ }
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestActiveMasterManager.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestActiveMasterManager.java
index 02d76a9af3a..ac6d754a839 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestActiveMasterManager.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestActiveMasterManager.java
@@ -33,9 +33,7 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtil;
import org.apache.hadoop.hbase.ServerName;
-import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
-import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
-import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.monitoring.MonitoredTask;
import org.apache.hadoop.hbase.monitoring.TaskGroup;
import org.apache.hadoop.hbase.testclassification.MasterTests;
@@ -332,17 +330,7 @@ public class TestActiveMasterManager {
}
@Override
- public SystemKeyCache getSystemKeyCache() {
- return null;
- }
-
- @Override
- public ManagedKeyDataCache getManagedKeyDataCache() {
- return null;
- }
-
- @Override
- public KeymetaAdmin getKeymetaAdmin() {
+ public KeyManagementService getKeyManagementService() {
return null;
}
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestKeymetaAdminImpl.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestKeymetaAdminImpl.java
index 08ffef8e0e9..6592238add5 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestKeymetaAdminImpl.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestKeymetaAdminImpl.java
@@ -43,7 +43,6 @@ import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtil;
import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.io.crypto.Encryption;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyProvider;
@@ -73,11 +72,12 @@ import org.junit.runners.Suite;
TestKeymetaAdminImpl.TestForKeyProviderNullReturn.class, })
@Category({ MasterTests.class, SmallTests.class })
public class TestKeymetaAdminImpl {
- private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
private static final String CUST = "cust1";
private static final String ENCODED_CUST = ManagedKeyProvider.encodeToStr(CUST.getBytes());
+ private final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
+
@Rule
public TestName name = new TestName();
@@ -86,7 +86,7 @@ public class TestKeymetaAdminImpl {
protected FileSystem fs;
protected FileSystem mockFileSystem = mock(FileSystem.class);
- protected Server mockServer = mock(Server.class);
+ protected MasterServices mockServer = mock(MasterServices.class);
protected KeymetaAdminImplForTest keymetaAdmin;
KeymetaTableAccessor keymetaAccessor = mock(KeymetaTableAccessor.class);
@@ -99,6 +99,7 @@ public class TestKeymetaAdminImpl {
conf.set(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, "true");
conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockManagedKeyProvider.class.getName());
+ when(mockServer.getKeyManagementService()).thenReturn(mockServer);
when(mockServer.getFileSystem()).thenReturn(mockFileSystem);
when(mockServer.getConfiguration()).thenReturn(conf);
keymetaAdmin = new KeymetaAdminImplForTest(mockServer, keymetaAccessor);
@@ -221,7 +222,7 @@ public class TestKeymetaAdminImpl {
}
private class KeymetaAdminImplForTest extends KeymetaAdminImpl {
- public KeymetaAdminImplForTest(Server mockServer, KeymetaTableAccessor mockAccessor) {
+ public KeymetaAdminImplForTest(MasterServices mockServer, KeymetaTableAccessor mockAccessor) {
super(mockServer);
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestSystemKeyAccessorAndManager.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestSystemKeyAccessorAndManager.java
index 1c4ad60a8da..0dc765ba729 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestSystemKeyAccessorAndManager.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestSystemKeyAccessorAndManager.java
@@ -45,6 +45,7 @@ import org.apache.hadoop.hbase.ClusterId;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtil;
import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.io.crypto.KeymetaTestUtils;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyProvider;
import org.apache.hadoop.hbase.io.crypto.ManagedKeyState;
@@ -105,14 +106,6 @@ public class TestSystemKeyAccessorAndManager {
systemKeyManager = new SystemKeyManager(mockMaster);
}
- private static FileStatus createMockFile(String fileName) {
- Path mockPath = mock(Path.class);
- when(mockPath.getName()).thenReturn(fileName);
- FileStatus mockFileStatus = mock(FileStatus.class);
- when(mockFileStatus.getPath()).thenReturn(mockPath);
- return mockFileStatus;
- }
-
@RunWith(BlockJUnit4ClassRunner.class)
@Category({ MasterTests.class, SmallTests.class })
public static class TestAccessorWhenDisabled extends TestSystemKeyAccessorAndManager {
@@ -128,8 +121,8 @@ public class TestSystemKeyAccessorAndManager {
@Test
public void test() throws Exception {
- assertNull(systemKeyManager.getAllSystemKeyFiles());
- assertNull(systemKeyManager.getLatestSystemKeyFile().getFirst());
+ assertThrows(IOException.class, () -> systemKeyManager.getAllSystemKeyFiles());
+ assertThrows(IOException.class, () -> systemKeyManager.getLatestSystemKeyFile().getFirst());
}
}
@@ -164,15 +157,15 @@ public class TestSystemKeyAccessorAndManager {
public void testGetLatestWithNone() throws Exception {
when(mockFileSystem.globStatus(any())).thenReturn(new FileStatus[0]);
- RuntimeException ex = assertThrows(RuntimeException.class,
- () -> systemKeyManager.getLatestSystemKeyFile());
+ RuntimeException ex =
+ assertThrows(RuntimeException.class, () -> systemKeyManager.getLatestSystemKeyFile());
assertEquals("No cluster key initialized yet", ex.getMessage());
}
@Test
public void testGetWithSingle() throws Exception {
String fileName = SYSTEM_KEY_FILE_PREFIX + "1";
- FileStatus mockFileStatus = createMockFile(fileName);
+ FileStatus mockFileStatus = KeymetaTestUtils.createMockFile(fileName);
Path systemKeyDir = CommonFSUtils.getSystemKeyDir(conf);
when(mockFileSystem.globStatus(eq(new Path(systemKeyDir, SYSTEM_KEY_FILE_PREFIX + "*"))))
@@ -192,7 +185,8 @@ public class TestSystemKeyAccessorAndManager {
@Test
public void testGetWithMultiple() throws Exception {
FileStatus[] mockFileStatuses = IntStream.rangeClosed(1, 3)
- .mapToObj(i -> createMockFile(SYSTEM_KEY_FILE_PREFIX + i)).toArray(FileStatus[]::new);
+ .mapToObj(i -> KeymetaTestUtils.createMockFile(SYSTEM_KEY_FILE_PREFIX + i))
+ .toArray(FileStatus[]::new);
Path systemKeyDir = CommonFSUtils.getSystemKeyDir(conf);
when(mockFileSystem.globStatus(eq(new Path(systemKeyDir, SYSTEM_KEY_FILE_PREFIX + "*"))))
@@ -208,7 +202,8 @@ public class TestSystemKeyAccessorAndManager {
@Test
public void testExtractKeySequenceForInvalidFilename() throws Exception {
- assertEquals(-1, SystemKeyAccessor.extractKeySequence(createMockFile("abcd").getPath()));
+ assertEquals(-1,
+ SystemKeyAccessor.extractKeySequence(KeymetaTestUtils.createMockFile("abcd").getPath()));
}
}
@@ -235,7 +230,7 @@ public class TestSystemKeyAccessorAndManager {
@Test
public void test() throws Exception {
- FileStatus mockFileStatus = createMockFile(fileName);
+ FileStatus mockFileStatus = KeymetaTestUtils.createMockFile(fileName);
IOException ex = assertThrows(IOException.class,
() -> SystemKeyAccessor.extractSystemKeySeqNum(mockFileStatus.getPath()));
@@ -345,7 +340,7 @@ public class TestSystemKeyAccessorAndManager {
when(mockFileSystem.create(any())).thenReturn(mockStream);
when(mockFileSystem.rename(any(), any())).thenReturn(false);
String fileName = SYSTEM_KEY_FILE_PREFIX + "1";
- FileStatus mockFileStatus = createMockFile(fileName);
+ FileStatus mockFileStatus = KeymetaTestUtils.createMockFile(fileName);
when(mockFileSystem.globStatus(any())).thenReturn(new FileStatus[0],
new FileStatus[] { mockFileStatus });
@@ -498,6 +493,16 @@ public class TestSystemKeyAccessorAndManager {
assertEquals(0, SystemKeyAccessor.extractKeySequence(validZero));
assertEquals(-1, SystemKeyAccessor.extractKeySequence(validNegative));
}
+
+ @Test
+ public void testCreateCacheFactoryMethod() {
+ // Test static factory method
+ }
+
+ @Test
+ public void testCreateCacheWithNoKeys() {
+ // Test behavior when no system keys are available
+ }
}
private static class MockSystemKeyManager extends SystemKeyManager {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/cleaner/TestReplicationHFileCleaner.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/cleaner/TestReplicationHFileCleaner.java
index a5e3dd1a5b8..ab99c55e625 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/cleaner/TestReplicationHFileCleaner.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/cleaner/TestReplicationHFileCleaner.java
@@ -38,9 +38,7 @@ import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.TableDescriptor;
-import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
-import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
-import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.master.HMaster;
import org.apache.hadoop.hbase.replication.ReplicationException;
import org.apache.hadoop.hbase.replication.ReplicationFactory;
@@ -218,21 +216,6 @@ public class TestReplicationHFileCleaner {
}
}
- @Override
- public SystemKeyCache getSystemKeyCache() {
- return null;
- }
-
- @Override
- public ManagedKeyDataCache getManagedKeyDataCache() {
- return null;
- }
-
- @Override
- public KeymetaAdmin getKeymetaAdmin() {
- return null;
- }
-
@Override
public FileSystem getFileSystem() {
try {
@@ -241,5 +224,10 @@ public class TestReplicationHFileCleaner {
throw new UncheckedIOException(e);
}
}
+
+ @Override
+ public KeyManagementService getKeyManagementService() {
+ return null;
+ }
}
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/MasterRegionTestBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/MasterRegionTestBase.java
index 0526fd3ba70..9ea11f73231 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/MasterRegionTestBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/MasterRegionTestBase.java
@@ -26,12 +26,12 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.ChoreService;
import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.master.cleaner.DirScanPool;
import org.apache.hadoop.hbase.regionserver.MemStoreLAB;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
@@ -53,7 +53,7 @@ public class MasterRegionTestBase {
protected DirScanPool logCleanerPool;
- protected Server server;
+ protected MasterServices server;
protected static byte[] CF1 = Bytes.toBytes("f1");
@@ -96,7 +96,7 @@ public class MasterRegionTestBase {
choreService = new ChoreService(getClass().getSimpleName());
hfileCleanerPool = DirScanPool.getHFileCleanerScanPool(conf);
logCleanerPool = DirScanPool.getLogCleanerScanPool(conf);
- server = mock(Server.class);
+ server = mock(MasterServices.class);
when(server.getConfiguration()).thenReturn(conf);
when(server.getServerName())
.thenReturn(ServerName.valueOf("localhost", 12345, EnvironmentEdgeManager.currentTime()));
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionOnTwoFileSystems.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionOnTwoFileSystems.java
index 8f11cc41505..80792d4b276 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionOnTwoFileSystems.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionOnTwoFileSystems.java
@@ -40,7 +40,6 @@ import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
import org.apache.hadoop.hbase.HBaseTestingUtil;
import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
@@ -49,6 +48,7 @@ import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.regionserver.MemStoreLAB;
import org.apache.hadoop.hbase.regionserver.RegionScanner;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
@@ -119,7 +119,7 @@ public class TestMasterRegionOnTwoFileSystems {
}
private MasterRegion createMasterRegion(ServerName serverName) throws IOException {
- Server server = mock(Server.class);
+ MasterServices server = mock(MasterServices.class);
when(server.getConfiguration()).thenReturn(HFILE_UTIL.getConfiguration());
when(server.getServerName()).thenReturn(serverName);
MasterRegionParams params = new MasterRegionParams();
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStorePerformanceEvaluation.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStorePerformanceEvaluation.java
index 790435f6a47..779ca4dac6c 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStorePerformanceEvaluation.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStorePerformanceEvaluation.java
@@ -24,9 +24,7 @@ import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.io.util.MemorySizeUtil;
-import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
-import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
-import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.master.MockNoopMasterServices;
import org.apache.hadoop.hbase.master.region.MasterRegion;
import org.apache.hadoop.hbase.master.region.MasterRegionFactory;
import org.apache.hadoop.hbase.procedure2.store.ProcedureStorePerformanceEvaluation;
@@ -34,47 +32,24 @@ import org.apache.hadoop.hbase.regionserver.ChunkCreator;
import org.apache.hadoop.hbase.regionserver.MemStoreLAB;
import org.apache.hadoop.hbase.util.CommonFSUtils;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
-import org.apache.hadoop.hbase.util.MockServer;
import org.apache.hadoop.hbase.util.Pair;
public class RegionProcedureStorePerformanceEvaluation
extends ProcedureStorePerformanceEvaluation<RegionProcedureStore> {
- private static final class DummyServer extends MockServer {
-
- private final Configuration conf;
+ private static final class DummyServer extends MockNoopMasterServices {
private final ServerName serverName =
ServerName.valueOf("localhost", 12345, EnvironmentEdgeManager.currentTime());
public DummyServer(Configuration conf) {
- this.conf = conf;
- }
-
- @Override
- public Configuration getConfiguration() {
- return conf;
+ super(conf);
}
@Override
public ServerName getServerName() {
return serverName;
}
-
- @Override
- public SystemKeyCache getSystemKeyCache() {
- return null;
- }
-
- @Override
- public ManagedKeyDataCache getManagedKeyDataCache() {
- return null;
- }
-
- @Override
- public KeymetaAdmin getKeymetaAdmin() {
- return null;
- }
}
private MasterRegion region;
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStoreTestBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStoreTestBase.java
index c05eb9a8ce3..dac4cc1e0e7 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStoreTestBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStoreTestBase.java
@@ -21,7 +21,7 @@ import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
-import org.apache.hadoop.hbase.Server;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.master.region.MasterRegion;
import org.apache.hadoop.hbase.master.region.MasterRegionFactory;
import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.LoadCounter;
@@ -51,7 +51,7 @@ public class RegionProcedureStoreTestBase {
conf.setBoolean(CommonFSUtils.UNSAFE_STREAM_CAPABILITY_ENFORCE, false);
Path testDir = htu.getDataTestDir();
CommonFSUtils.setRootDir(htu.getConfiguration(), testDir);
- Server server = RegionProcedureStoreTestHelper.mockServer(conf);
+ MasterServices server = RegionProcedureStoreTestHelper.mockServer(conf);
region = MasterRegionFactory.create(server);
store = RegionProcedureStoreTestHelper.createStore(server, region, new LoadCounter());
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStoreTestHelper.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStoreTestHelper.java
index 0607d9d3e92..cc90d6e22b6 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStoreTestHelper.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/RegionProcedureStoreTestHelper.java
@@ -26,6 +26,7 @@ import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.master.region.MasterRegion;
import org.apache.hadoop.hbase.procedure2.store.LeaseRecovery;
import org.apache.hadoop.hbase.procedure2.store.ProcedureStore.ProcedureLoader;
@@ -36,8 +37,8 @@ final class RegionProcedureStoreTestHelper {
private RegionProcedureStoreTestHelper() {
}
- static Server mockServer(Configuration conf) {
- Server server = mock(Server.class);
+ static MasterServices mockServer(Configuration conf) {
+ MasterServices server = mock(MasterServices.class);
when(server.getConfiguration()).thenReturn(conf);
when(server.getServerName())
.thenReturn(ServerName.valueOf("localhost", 12345, EnvironmentEdgeManager.currentTime()));
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/TestRegionProcedureStoreMigration.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/TestRegionProcedureStoreMigration.java
index 7a6fee5f314..70b93487c12 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/TestRegionProcedureStoreMigration.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/TestRegionProcedureStoreMigration.java
@@ -35,9 +35,9 @@ import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
import org.apache.hadoop.hbase.HBaseIOException;
-import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.RegionInfoBuilder;
+import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.master.assignment.AssignProcedure;
import org.apache.hadoop.hbase.master.region.MasterRegion;
import org.apache.hadoop.hbase.master.region.MasterRegionFactory;
@@ -66,7 +66,7 @@ public class TestRegionProcedureStoreMigration {
private HBaseCommonTestingUtil htu;
- private Server server;
+ private MasterServices server;
private MasterRegion region;
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHeapMemoryManager.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHeapMemoryManager.java
index 22be2181195..9b6a5d80c9a 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHeapMemoryManager.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHeapMemoryManager.java
@@ -46,9 +46,7 @@ import org.apache.hadoop.hbase.io.hfile.Cacheable;
import org.apache.hadoop.hbase.io.hfile.CachedBlock;
import org.apache.hadoop.hbase.io.hfile.ResizableBlockCache;
import org.apache.hadoop.hbase.io.util.MemorySizeUtil;
-import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
-import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
-import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.regionserver.HeapMemoryManager.TunerContext;
import org.apache.hadoop.hbase.regionserver.HeapMemoryManager.TunerResult;
import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -840,21 +838,6 @@ public class TestHeapMemoryManager {
return null;
}
- @Override
- public SystemKeyCache getSystemKeyCache() {
- return null;
- }
-
- @Override
- public ManagedKeyDataCache getManagedKeyDataCache() {
- return null;
- }
-
- @Override
- public KeymetaAdmin getKeymetaAdmin() {
- return null;
- }
-
@Override
public FileSystem getFileSystem() {
return null;
@@ -874,6 +857,11 @@ public class TestHeapMemoryManager {
public AsyncClusterConnection getAsyncClusterConnection() {
return null;
}
+
+ @Override
+ public KeyManagementService getKeyManagementService() {
+ return null;
+ }
}
static class CustomHeapMemoryTuner implements HeapMemoryTuner {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRecoveredEditsReplayAndAbort.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRecoveredEditsReplayAndAbort.java
index 6b372fa9935..1a4ba7ac99c 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRecoveredEditsReplayAndAbort.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRecoveredEditsReplayAndAbort.java
@@ -125,7 +125,8 @@ public class TestRecoveredEditsReplayAndAbort {
Path rootDir = TEST_UTIL.getDataTestDir();
Path tableDir = CommonFSUtils.getTableDir(rootDir, info.getTable());
HRegionFileSystem.createRegionOnFileSystem(CONF, TEST_UTIL.getTestFileSystem(), tableDir, info);
- region = HRegion.newHRegion(tableDir, wal, TEST_UTIL.getTestFileSystem(), CONF, info, htd, rs);
+ region = HRegion.newHRegion(tableDir, wal, TEST_UTIL.getTestFileSystem(), CONF, info, htd, rs,
+ rs.getKeyManagementService());
// create some recovered.edits
final WALFactory wals = new WALFactory(CONF, method);
try {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestStoreFileInfo.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestStoreFileInfo.java
index ffc4e17f6f8..29040ad58be 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestStoreFileInfo.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestStoreFileInfo.java
@@ -122,7 +122,8 @@ public class TestStoreFileInfo {
storeFileTrackerForTest.createReference(r, p);
StoreFileInfo sfi = storeFileTrackerForTest.getStoreFileInfo(p, true);
try {
- ReaderContext context = sfi.createReaderContext(false, 1000, ReaderType.PREAD);
+ ReaderContext context =
+ sfi.createReaderContext(false, 1000, ReaderType.PREAD, null, null, null);
sfi.createReader(context, null);
throw new IllegalStateException();
} catch (FileNotFoundException fnfe) {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecurityUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecurityUtil.java
new file mode 100644
index 00000000000..ca2f8088a78
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecurityUtil.java
@@ -0,0 +1,751 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.security;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.security.Key;
+import java.security.KeyException;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HBaseTestingUtil;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
+import org.apache.hadoop.hbase.io.crypto.Cipher;
+import org.apache.hadoop.hbase.io.crypto.CipherProvider;
+import org.apache.hadoop.hbase.io.crypto.Encryption;
+import org.apache.hadoop.hbase.io.crypto.KeyProvider;
+import org.apache.hadoop.hbase.io.crypto.ManagedKeyData;
+import org.apache.hadoop.hbase.io.crypto.MockAesKeyProvider;
+import org.apache.hadoop.hbase.io.hfile.FixedFileTrailer;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
+import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({ TestSecurityUtil.TestBasic.class,
+ TestSecurityUtil.TestCreateEncryptionContext_ForWrites.class,
+ TestSecurityUtil.TestCreateEncryptionContext_ForReads.class,
+ TestSecurityUtil.TestCreateEncryptionContext_WithoutKeyManagement_UnwrapKeyException.class, })
+@Category({ SecurityTests.class, SmallTests.class })
+public class TestSecurityUtil {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestSecurityUtil.class);
+
+ // Test constants to eliminate magic strings and improve maintainability
+ protected static final String TEST_NAMESPACE = "test-namespace";
+ protected static final String TEST_FAMILY = "test-family";
+ protected static final String HBASE_KEY = "hbase";
+ protected static final String TEST_KEK_METADATA = "test-kek-metadata";
+ protected static final long TEST_KEK_CHECKSUM = 12345L;
+ protected static final String TEST_KEY_16_BYTE = "test-key-16-byte";
+ protected static final String TEST_DEK_16_BYTE = "test-dek-16-byte";
+ protected static final String INVALID_KEY_DATA = "invalid-key-data";
+ protected static final String INVALID_WRAPPED_KEY_DATA = "invalid-wrapped-key-data";
+ protected static final String INVALID_SYSTEM_KEY_DATA = "invalid-system-key-data";
+ protected static final String UNKNOWN_CIPHER = "UNKNOWN_CIPHER";
+ protected static final String AES_CIPHER = "AES";
+ protected static final String DES_CIPHER = "DES";
+
+ protected Configuration conf;
+ protected HBaseTestingUtil testUtil;
+ protected Path testPath;
+ protected ColumnFamilyDescriptor mockFamily;
+ protected ManagedKeyDataCache mockManagedKeyDataCache;
+ protected SystemKeyCache mockSystemKeyCache;
+ protected FixedFileTrailer mockTrailer;
+ protected ManagedKeyData mockManagedKeyData;
+ protected Key testKey;
+ protected byte[] testWrappedKey;
+ protected Key kekKey;
+
+ /**
+ * Configuration builder for setting up different encryption test scenarios.
+ */
+ protected static class TestConfigBuilder {
+ private boolean encryptionEnabled = true;
+ private boolean keyManagementEnabled = false;
+ private boolean localKeyGenEnabled = false;
+ private String cipherProvider = "org.apache.hadoop.hbase.io.crypto.DefaultCipherProvider";
+ private String keyProvider = MockAesKeyProvider.class.getName();
+ private String masterKeyName = HBASE_KEY;
+
+ public TestConfigBuilder withEncryptionEnabled(boolean enabled) {
+ this.encryptionEnabled = enabled;
+ return this;
+ }
+
+ public TestConfigBuilder withKeyManagement(boolean enabled, boolean localKeyGen) {
+ this.keyManagementEnabled = enabled;
+ this.localKeyGenEnabled = localKeyGen;
+ return this;
+ }
+
+ public TestConfigBuilder withNullCipherProvider() {
+ this.cipherProvider = NullCipherProvider.class.getName();
+ return this;
+ }
+
+ public void apply(Configuration conf) {
+ conf.setBoolean(Encryption.CRYPTO_ENABLED_CONF_KEY, encryptionEnabled);
+ conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, keyProvider);
+ conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, masterKeyName);
+ conf.set(HConstants.CRYPTO_KEYPROVIDER_PARAMETERS_KEY, "true");
+ conf.set(HConstants.CRYPTO_CIPHERPROVIDER_CONF_KEY, cipherProvider);
+
+ if (keyManagementEnabled) {
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, true);
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_LOCAL_KEY_GEN_PER_FILE_ENABLED_CONF_KEY,
+ localKeyGenEnabled);
+ } else {
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, false);
+ }
+ }
+ }
+
+ protected static TestConfigBuilder configBuilder() {
+ return new TestConfigBuilder();
+ }
+
+ protected void setUpEncryptionConfig() {
+ // Set up real encryption configuration using default AES cipher
+ conf.setBoolean(Encryption.CRYPTO_ENABLED_CONF_KEY, true);
+ conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName());
+ conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase");
+ // Enable key caching
+ conf.set(HConstants.CRYPTO_KEYPROVIDER_PARAMETERS_KEY, "true");
+ // Use DefaultCipherProvider for real AES encryption functionality
+ conf.set(HConstants.CRYPTO_CIPHERPROVIDER_CONF_KEY,
+ "org.apache.hadoop.hbase.io.crypto.DefaultCipherProvider");
+ }
+
+ protected void setUpEncryptionConfigWithNullCipher() {
+ configBuilder().withNullCipherProvider().apply(conf);
+ }
+
+ // ==== Mock Setup Helpers ====
+
+ protected void setupManagedKeyDataCache(String namespace, ManagedKeyData keyData) {
+ when(mockManagedKeyDataCache.getActiveEntry(eq(ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES),
+ eq(namespace))).thenReturn(keyData);
+ }
+
+ protected void setupManagedKeyDataCache(String namespace, String globalSpace,
+ ManagedKeyData keyData) {
+ when(mockManagedKeyDataCache.getActiveEntry(eq(ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES),
+ eq(namespace))).thenReturn(null);
+ when(mockManagedKeyDataCache.getActiveEntry(eq(ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES),
+ eq(globalSpace))).thenReturn(keyData);
+ }
+
+ protected void setupTrailerMocks(byte[] keyBytes, String metadata, Long checksum,
+ String namespace) {
+ when(mockTrailer.getEncryptionKey()).thenReturn(keyBytes);
+ when(mockTrailer.getKEKMetadata()).thenReturn(metadata);
+ if (checksum != null) {
+ when(mockTrailer.getKEKChecksum()).thenReturn(checksum);
+ }
+ when(mockTrailer.getKeyNamespace()).thenReturn(namespace);
+ }
+
+ protected void setupSystemKeyCache(Long checksum, ManagedKeyData keyData) {
+ when(mockSystemKeyCache.getSystemKeyByChecksum(checksum)).thenReturn(keyData);
+ }
+
+ protected void setupSystemKeyCache(ManagedKeyData latestKey) {
+ when(mockSystemKeyCache.getLatestSystemKey()).thenReturn(latestKey);
+ }
+
+ protected void setupManagedKeyDataCacheEntry(String namespace, String metadata, byte[] keyBytes,
+ ManagedKeyData keyData) throws IOException, KeyException {
+ when(mockManagedKeyDataCache.getEntry(eq(ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES),
+ eq(namespace), eq(metadata), eq(keyBytes))).thenReturn(keyData);
+ }
+
+ // ==== Exception Testing Helpers ====
+
+ protected <T extends Exception> void assertExceptionContains(Class<T> expectedType,
+ String expectedMessage, Runnable testCode) {
+ T exception = assertThrows(expectedType, () -> testCode.run());
+ assertTrue("Exception message should contain: " + expectedMessage,
+ exception.getMessage().contains(expectedMessage));
+ }
+
+ protected void assertEncryptionContextThrowsForWrites(Class<? extends Exception> expectedType,
+ String expectedMessage) {
+ Exception exception = assertThrows(Exception.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, mockFamily, mockManagedKeyDataCache,
+ mockSystemKeyCache, TEST_NAMESPACE);
+ });
+ assertTrue("Expected exception type: " + expectedType.getName() + ", but got: "
+ + exception.getClass().getName(), expectedType.isInstance(exception));
+ assertTrue("Exception message should contain: " + expectedMessage,
+ exception.getMessage().contains(expectedMessage));
+ }
+
+ protected void assertEncryptionContextThrowsForReads(Class<? extends Exception> expectedType,
+ String expectedMessage) {
+ Exception exception = assertThrows(Exception.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, mockManagedKeyDataCache,
+ mockSystemKeyCache);
+ });
+ assertTrue("Expected exception type: " + expectedType.getName() + ", but got: "
+ + exception.getClass().getName(), expectedType.isInstance(exception));
+ assertTrue("Exception message should contain: " + expectedMessage,
+ exception.getMessage().contains(expectedMessage));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ conf = HBaseConfiguration.create();
+ testUtil = new HBaseTestingUtil(conf);
+ testPath = testUtil.getDataTestDir("test-file");
+
+ // Setup mocks (only for objects that don't have encryption logic)
+ mockFamily = mock(ColumnFamilyDescriptor.class);
+ mockManagedKeyDataCache = mock(ManagedKeyDataCache.class);
+ mockSystemKeyCache = mock(SystemKeyCache.class);
+ mockTrailer = mock(FixedFileTrailer.class);
+ mockManagedKeyData = mock(ManagedKeyData.class);
+
+ // Use a real test key with exactly 16 bytes for AES-128
+ testKey = new SecretKeySpec(TEST_KEY_16_BYTE.getBytes(), AES_CIPHER);
+
+ // Configure mocks
+ when(mockFamily.getEncryptionType()).thenReturn(AES_CIPHER);
+ when(mockFamily.getNameAsString()).thenReturn(TEST_FAMILY);
+ when(mockManagedKeyData.getTheKey()).thenReturn(testKey);
+
+ // Set up default encryption config
+ setUpEncryptionConfig();
+
+ // Create test wrapped key
+ KeyProvider keyProvider = Encryption.getKeyProvider(conf);
+ kekKey = keyProvider.getKey(HBASE_KEY);
+ Key key = keyProvider.getKey(TEST_DEK_16_BYTE);
+ testWrappedKey = EncryptionUtil.wrapKey(conf, null, key, kekKey);
+ }
+
+ @RunWith(BlockJUnit4ClassRunner.class)
+ @Category({ SecurityTests.class, SmallTests.class })
+ public static class TestBasic extends TestSecurityUtil {
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestBasic.class);
+
+ @Test
+ public void testGetUserFromPrincipal() {
+ // Test with slash separator
+ assertEquals("user1", SecurityUtil.getUserFromPrincipal("user1/host@REALM"));
+ assertEquals("user2", SecurityUtil.getUserFromPrincipal("user2@REALM"));
+
+ // Test with no realm
+ assertEquals("user3", SecurityUtil.getUserFromPrincipal("user3"));
+
+ // Test with multiple slashes
+ assertEquals("user4", SecurityUtil.getUserFromPrincipal("user4/host1/host2@REALM"));
+ }
+
+ @Test
+ public void testGetPrincipalWithoutRealm() {
+ // Test with realm
+ assertEquals("user1/host", SecurityUtil.getPrincipalWithoutRealm("user1/host@REALM"));
+ assertEquals("user2", SecurityUtil.getPrincipalWithoutRealm("user2@REALM"));
+
+ // Test without realm
+ assertEquals("user3", SecurityUtil.getPrincipalWithoutRealm("user3"));
+ assertEquals("user4/host", SecurityUtil.getPrincipalWithoutRealm("user4/host"));
+ }
+
+ @Test
+ public void testIsKeyManagementEnabled() {
+ Configuration conf = HBaseConfiguration.create();
+
+ // Test default behavior (should be false)
+ assertFalse(SecurityUtil.isKeyManagementEnabled(conf));
+
+ // Test with key management enabled
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, true);
+ assertTrue(SecurityUtil.isKeyManagementEnabled(conf));
+
+ // Test with key management disabled
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, false);
+ assertFalse(SecurityUtil.isKeyManagementEnabled(conf));
+ }
+ }
+
+ // Tests for the first createEncryptionContext method (for ColumnFamilyDescriptor)
+ @RunWith(BlockJUnit4ClassRunner.class)
+ @Category({ SecurityTests.class, SmallTests.class })
+ public static class TestCreateEncryptionContext_ForWrites extends TestSecurityUtil {
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestCreateEncryptionContext_ForWrites.class);
+
+ @Test
+ public void testWithNoEncryptionOnFamily() throws IOException {
+ when(mockFamily.getEncryptionType()).thenReturn(null);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, mockFamily,
+ mockManagedKeyDataCache, mockSystemKeyCache, "test-namespace");
+
+ assertEquals(Encryption.Context.NONE, result);
+ }
+
+ @Test
+ public void testWithEncryptionDisabled() throws IOException {
+ configBuilder().withEncryptionEnabled(false).apply(conf);
+ assertEncryptionContextThrowsForWrites(IllegalStateException.class,
+ "encryption feature is disabled");
+ }
+
+ @Test
+ public void testWithKeyManagement_LocalKeyGen() throws IOException {
+ configBuilder().withKeyManagement(true, true).apply(conf);
+ setupManagedKeyDataCache(TEST_NAMESPACE, mockManagedKeyData);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, mockFamily,
+ mockManagedKeyDataCache, mockSystemKeyCache, TEST_NAMESPACE);
+
+ verifyContext(result);
+ }
+
+ @Test
+ public void testWithKeyManagement_NoActiveKey() throws IOException {
+ configBuilder().withKeyManagement(true, false).apply(conf);
+ setupManagedKeyDataCache(TEST_NAMESPACE, ManagedKeyData.KEY_SPACE_GLOBAL, null);
+ assertEncryptionContextThrowsForWrites(IOException.class, "No active key found");
+ }
+
+ @Test
+ public void testWithKeyManagement_LocalKeyGen_WithUnknownKeyCipher() throws IOException {
+ when(mockFamily.getEncryptionType()).thenReturn(UNKNOWN_CIPHER);
+ Key unknownKey = mock(Key.class);
+ when(unknownKey.getAlgorithm()).thenReturn(UNKNOWN_CIPHER);
+ when(mockManagedKeyData.getTheKey()).thenReturn(unknownKey);
+
+ configBuilder().withKeyManagement(true, true).apply(conf);
+ setupManagedKeyDataCache(TEST_NAMESPACE, mockManagedKeyData);
+ assertEncryptionContextThrowsForWrites(RuntimeException.class,
+ "Cipher 'UNKNOWN_CIPHER' is not");
+ }
+
+ @Test
+ public void testWithKeyManagement_LocalKeyGen_WithKeyAlgorithmMismatch() throws IOException {
+ Key desKey = mock(Key.class);
+ when(desKey.getAlgorithm()).thenReturn(DES_CIPHER);
+ when(mockManagedKeyData.getTheKey()).thenReturn(desKey);
+
+ configBuilder().withKeyManagement(true, true).apply(conf);
+ setupManagedKeyDataCache(TEST_NAMESPACE, mockManagedKeyData);
+ assertEncryptionContextThrowsForWrites(IllegalStateException.class,
+ "Encryption for family 'test-family' configured with type 'AES' but key specifies algorithm 'DES'");
+ }
+
+ @Test
+ public void testWithKeyManagement_UseSystemKeyWithNSSpecificActiveKey() throws IOException {
+ configBuilder().withKeyManagement(true, false).apply(conf);
+ setupManagedKeyDataCache(TEST_NAMESPACE, mockManagedKeyData);
+ setupSystemKeyCache(mockManagedKeyData);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, mockFamily,
+ mockManagedKeyDataCache, mockSystemKeyCache, TEST_NAMESPACE);
+
+ verifyContext(result);
+ }
+
+ @Test
+ public void testWithKeyManagement_UseSystemKeyWithoutNSSpecificActiveKey() throws IOException {
+ configBuilder().withKeyManagement(true, false).apply(conf);
+ setupManagedKeyDataCache(TEST_NAMESPACE, ManagedKeyData.KEY_SPACE_GLOBAL, mockManagedKeyData);
+ setupSystemKeyCache(mockManagedKeyData);
+ when(mockManagedKeyData.getTheKey()).thenReturn(kekKey);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, mockFamily,
+ mockManagedKeyDataCache, mockSystemKeyCache, TEST_NAMESPACE);
+
+ verifyContext(result);
+ }
+
+ @Test
+ public void testWithoutKeyManagement_WithFamilyProvidedKey() throws Exception {
+ when(mockFamily.getEncryptionKey()).thenReturn(testWrappedKey);
+ configBuilder().withKeyManagement(false, false).apply(conf);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, mockFamily,
+ mockManagedKeyDataCache, mockSystemKeyCache, TEST_NAMESPACE);
+
+ verifyContext(result, false);
+ }
+
+ @Test
+ public void testWithoutKeyManagement_KeyAlgorithmMismatch() throws Exception {
+ // Create a key with different algorithm and wrap it
+ Key differentKey = new SecretKeySpec(TEST_KEY_16_BYTE.getBytes(), DES_CIPHER);
+ byte[] wrappedDESKey = EncryptionUtil.wrapKey(conf, HBASE_KEY, differentKey);
+ when(mockFamily.getEncryptionKey()).thenReturn(wrappedDESKey);
+
+ configBuilder().withKeyManagement(false, false).apply(conf);
+ assertEncryptionContextThrowsForWrites(IllegalStateException.class,
+ "Encryption for family 'test-family' configured with type 'AES' but key specifies algorithm 'DES'");
+ }
+
+ @Test
+ public void testWithoutKeyManagement_WithRandomKeyGeneration() throws IOException {
+ when(mockFamily.getEncryptionKey()).thenReturn(null);
+ configBuilder().withKeyManagement(false, false).apply(conf);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, mockFamily,
+ mockManagedKeyDataCache, mockSystemKeyCache, TEST_NAMESPACE);
+
+ verifyContext(result, false);
+ }
+
+ @Test
+ public void testWithUnavailableCipher() throws IOException {
+ when(mockFamily.getEncryptionType()).thenReturn(UNKNOWN_CIPHER);
+ setUpEncryptionConfigWithNullCipher();
+ assertEncryptionContextThrowsForWrites(IllegalStateException.class,
+ "Cipher 'UNKNOWN_CIPHER' is not available");
+ }
+
+ // Tests for the second createEncryptionContext method (for reading files)
+
+ @Test
+ public void testWithNoKeyMaterial() throws IOException {
+ when(mockTrailer.getEncryptionKey()).thenReturn(null);
+ when(mockTrailer.getKeyNamespace()).thenReturn(TEST_NAMESPACE);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer,
+ mockManagedKeyDataCache, mockSystemKeyCache);
+
+ assertEquals(Encryption.Context.NONE, result);
+ }
+ }
+
+ // Tests for the second createEncryptionContext method (for reading files)
+ @RunWith(BlockJUnit4ClassRunner.class)
+ @Category({ SecurityTests.class, SmallTests.class })
+ public static class TestCreateEncryptionContext_ForReads extends TestSecurityUtil {
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestCreateEncryptionContext_ForReads.class);
+
+ @Test
+ public void testWithKEKMetadata() throws Exception {
+ setupTrailerMocks(testWrappedKey, TEST_KEK_METADATA, TEST_KEK_CHECKSUM, TEST_NAMESPACE);
+ setupManagedKeyDataCacheEntry(TEST_NAMESPACE, TEST_KEK_METADATA, testWrappedKey,
+ mockManagedKeyData);
+ when(mockManagedKeyData.getTheKey()).thenReturn(kekKey);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer,
+ mockManagedKeyDataCache, mockSystemKeyCache);
+
+ verifyContext(result);
+ }
+
+ @Test
+ public void testWithKeyManagement_KEKMetadataFailure() throws IOException, KeyException {
+ byte[] keyBytes = "test-encrypted-key".getBytes();
+ String kekMetadata = "test-kek-metadata";
+
+ when(mockTrailer.getEncryptionKey()).thenReturn(keyBytes);
+ when(mockTrailer.getKEKMetadata()).thenReturn(kekMetadata);
+ when(mockTrailer.getKeyNamespace()).thenReturn("test-namespace");
+
+ when(mockManagedKeyDataCache.getEntry(eq(ManagedKeyData.KEY_GLOBAL_CUSTODIAN_BYTES),
+ eq("test-namespace"), eq(kekMetadata), eq(keyBytes)))
+ .thenThrow(new IOException("Key not found"));
+
+ IOException exception = assertThrows(IOException.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, mockManagedKeyDataCache,
+ mockSystemKeyCache);
+ });
+
+ assertTrue(exception.getMessage().contains("Failed to get key data"));
+ }
+
+ @Test
+ public void testWithKeyManagement_UseSystemKey() throws IOException {
+ setupTrailerMocks(testWrappedKey, null, TEST_KEK_CHECKSUM, TEST_NAMESPACE);
+ configBuilder().withKeyManagement(true, false).apply(conf);
+ setupSystemKeyCache(TEST_KEK_CHECKSUM, mockManagedKeyData);
+ when(mockManagedKeyData.getTheKey()).thenReturn(kekKey);
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer,
+ mockManagedKeyDataCache, mockSystemKeyCache);
+
+ verifyContext(result);
+ }
+
+ @Test
+ public void testWithKeyManagement_SystemKeyNotFound() throws IOException {
+ byte[] keyBytes = "test-encrypted-key".getBytes();
+ long kekChecksum = 12345L;
+
+ when(mockTrailer.getEncryptionKey()).thenReturn(keyBytes);
+ when(mockTrailer.getKEKMetadata()).thenReturn(null);
+ when(mockTrailer.getKEKChecksum()).thenReturn(kekChecksum);
+ when(mockTrailer.getKeyNamespace()).thenReturn("test-namespace");
+
+ // Enable key management
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, true);
+
+ when(mockSystemKeyCache.getSystemKeyByChecksum(kekChecksum)).thenReturn(null);
+
+ IOException exception = assertThrows(IOException.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, mockManagedKeyDataCache,
+ mockSystemKeyCache);
+ });
+
+ assertTrue(exception.getMessage().contains("Failed to get system key"));
+ }
+
+ @Test
+ public void testWithoutKeyManagemntEnabled() throws IOException {
+ when(mockTrailer.getEncryptionKey()).thenReturn(testWrappedKey);
+ when(mockTrailer.getKEKMetadata()).thenReturn(null);
+ when(mockTrailer.getKeyNamespace()).thenReturn(TEST_NAMESPACE);
+ configBuilder().withKeyManagement(false, false).apply(conf);
+ // TODO: Get the key provider to return kek when getKeys() is called.
+
+ Encryption.Context result = SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer,
+ mockManagedKeyDataCache, mockSystemKeyCache);
+
+ verifyContext(result, false);
+ }
+
+ @Test
+ public void testWithoutKeyManagement_UnwrapFailure() throws IOException {
+ byte[] invalidKeyBytes = INVALID_KEY_DATA.getBytes();
+ when(mockTrailer.getEncryptionKey()).thenReturn(invalidKeyBytes);
+ when(mockTrailer.getKEKMetadata()).thenReturn(null);
+ when(mockTrailer.getKeyNamespace()).thenReturn(TEST_NAMESPACE);
+ configBuilder().withKeyManagement(false, false).apply(conf);
+
+ Exception exception = assertThrows(Exception.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, mockManagedKeyDataCache,
+ mockSystemKeyCache);
+ });
+
+ // The exception should indicate that unwrapping failed - could be IOException or
+ // RuntimeException
+ assertNotNull(exception);
+ }
+
+ @Test
+ public void testCreateEncryptionContext_WithoutKeyManagement_UnavailableCipher()
+ throws Exception {
+ // Create a DES key and wrap it first with working configuration
+ Key desKey = new SecretKeySpec("test-key-16-byte".getBytes(), "DES");
+ byte[] wrappedDESKey = EncryptionUtil.wrapKey(conf, "hbase", desKey);
+
+ when(mockTrailer.getEncryptionKey()).thenReturn(wrappedDESKey);
+ when(mockTrailer.getKEKMetadata()).thenReturn(null);
+ when(mockTrailer.getKeyNamespace()).thenReturn("test-namespace");
+
+ // Disable key management and use null cipher provider
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, false);
+ setUpEncryptionConfigWithNullCipher();
+
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, mockManagedKeyDataCache,
+ mockSystemKeyCache);
+ });
+
+ assertTrue(exception.getMessage().contains("Cipher 'AES' not available"));
+ }
+
+ @Test
+ public void testCreateEncryptionContext_WithKeyManagement_NullKeyManagementCache()
+ throws IOException {
+ byte[] keyBytes = "test-encrypted-key".getBytes();
+ String kekMetadata = "test-kek-metadata";
+
+ when(mockTrailer.getEncryptionKey()).thenReturn(keyBytes);
+ when(mockTrailer.getKEKMetadata()).thenReturn(kekMetadata);
+ when(mockTrailer.getKeyNamespace()).thenReturn("test-namespace");
+
+ // Enable key management
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, true);
+
+ IOException exception = assertThrows(IOException.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, null, mockSystemKeyCache);
+ });
+
+ assertTrue(exception.getMessage().contains("ManagedKeyDataCache is null"));
+ }
+
+ @Test
+ public void testCreateEncryptionContext_WithKeyManagement_NullSystemKeyCache()
+ throws IOException {
+ byte[] keyBytes = "test-encrypted-key".getBytes();
+
+ when(mockTrailer.getEncryptionKey()).thenReturn(keyBytes);
+ when(mockTrailer.getKEKMetadata()).thenReturn(null);
+ when(mockTrailer.getKeyNamespace()).thenReturn("test-namespace");
+
+ // Enable key management
+ conf.setBoolean(HConstants.CRYPTO_MANAGED_KEYS_ENABLED_CONF_KEY, true);
+
+ IOException exception = assertThrows(IOException.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, mockManagedKeyDataCache,
+ null);
+ });
+
+ assertTrue(exception.getMessage().contains("SystemKeyCache is null"));
+ }
+ }
+
+ @RunWith(Parameterized.class)
+ @Category({ SecurityTests.class, SmallTests.class })
+ public static class TestCreateEncryptionContext_WithoutKeyManagement_UnwrapKeyException
+ extends TestSecurityUtil {
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule
+ .forClass(TestCreateEncryptionContext_WithoutKeyManagement_UnwrapKeyException.class);
+
+ @Parameter(0)
+ public boolean isKeyException;
+
+ @Parameterized.Parameters(name = "{index},isKeyException={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { { true }, { false }, });
+ }
+
+ @Test
+ public void test() throws IOException {
+ }
+
+ @Test
+ public void testWithDEK() throws IOException, KeyException {
+ // This test is challenging because we need to create a scenario where unwrapping fails
+ // with either KeyException or IOException. We'll create invalid wrapped data.
+ byte[] invalidKeyBytes = INVALID_WRAPPED_KEY_DATA.getBytes();
+
+ setupTrailerMocks(invalidKeyBytes, TEST_KEK_METADATA, TEST_KEK_CHECKSUM, TEST_NAMESPACE);
+ setupManagedKeyDataCacheEntry(TEST_NAMESPACE, TEST_KEK_METADATA, invalidKeyBytes,
+ mockManagedKeyData);
+
+ IOException exception = assertThrows(IOException.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, mockManagedKeyDataCache,
+ mockSystemKeyCache);
+ });
+
+ assertTrue(exception.getMessage().contains("Failed to unwrap key with KEK checksum: "
+ + TEST_KEK_CHECKSUM + ", metadata: " + TEST_KEK_METADATA));
+ // The root cause should be some kind of parsing/unwrapping exception
+ assertNotNull(exception.getCause());
+ }
+
+ @Test
+ public void testWithSystemKey() throws IOException {
+ // Use invalid key bytes to trigger unwrapping failure
+ byte[] invalidKeyBytes = INVALID_SYSTEM_KEY_DATA.getBytes();
+
+ setupTrailerMocks(invalidKeyBytes, null, TEST_KEK_CHECKSUM, TEST_NAMESPACE);
+ configBuilder().withKeyManagement(true, false).apply(conf);
+ setupSystemKeyCache(TEST_KEK_CHECKSUM, mockManagedKeyData);
+
+ IOException exception = assertThrows(IOException.class, () -> {
+ SecurityUtil.createEncryptionContext(conf, testPath, mockTrailer, mockManagedKeyDataCache,
+ mockSystemKeyCache);
+ });
+
+ assertTrue(exception.getMessage().contains(
+ "Failed to unwrap key with KEK checksum: " + TEST_KEK_CHECKSUM + ", metadata: null"));
+ // The root cause should be some kind of parsing/unwrapping exception
+ assertNotNull(exception.getCause());
+ }
+ }
+
+ protected void verifyContext(Encryption.Context context) {
+ verifyContext(context, true);
+ }
+
+ protected void verifyContext(Encryption.Context context, boolean withKeyManagement) {
+ assertNotNull(context);
+ assertNotNull("Context should have a cipher", context.getCipher());
+ assertNotNull("Context should have a key", context.getKey());
+ if (withKeyManagement) {
+ assertNotNull("Context should have KEK data when key management is enabled",
+ context.getKEKData());
+ } else {
+ assertNull("Context should not have KEK data when key management is disabled",
+ context.getKEKData());
+ }
+ }
+
+ /**
+ * Null cipher provider for testing error cases.
+ */
+ public static class NullCipherProvider implements CipherProvider {
+ private Configuration conf;
+
+ @Override
+ public Configuration getConf() {
+ return conf;
+ }
+
+ @Override
+ public void setConf(Configuration conf) {
+ this.conf = conf;
+ }
+
+ @Override
+ public String getName() {
+ return "null";
+ }
+
+ @Override
+ public String[] getSupportedCiphers() {
+ return new String[0];
+ }
+
+ @Override
+ public Cipher getCipher(String name) {
+ return null; // Always return null to simulate unavailable cipher
+ }
+ }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/token/TestTokenAuthentication.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/token/TestTokenAuthentication.java
index 5d62d6a908c..273385ec9c8 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/token/TestTokenAuthentication.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/token/TestTokenAuthentication.java
@@ -55,9 +55,7 @@ import org.apache.hadoop.hbase.ipc.RpcServerFactory;
import org.apache.hadoop.hbase.ipc.RpcServerInterface;
import org.apache.hadoop.hbase.ipc.ServerRpcController;
import org.apache.hadoop.hbase.ipc.SimpleRpcServer;
-import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
-import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
-import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.log.HBaseMarkers;
import org.apache.hadoop.hbase.regionserver.RegionServerServices;
import org.apache.hadoop.hbase.security.SecurityInfo;
@@ -354,27 +352,17 @@ public class TestTokenAuthentication {
}
@Override
- public SystemKeyCache getSystemKeyCache() {
- return null;
- }
-
- @Override
- public ManagedKeyDataCache getManagedKeyDataCache() {
- return null;
- }
-
- @Override
- public KeymetaAdmin getKeymetaAdmin() {
+ public Connection createConnection(Configuration conf) throws IOException {
return null;
}
@Override
- public Connection createConnection(Configuration conf) throws IOException {
+ public AsyncClusterConnection getAsyncClusterConnection() {
return null;
}
@Override
- public AsyncClusterConnection getAsyncClusterConnection() {
+ public KeyManagementService getKeyManagementService() {
return null;
}
}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/snapshot/TestRestoreSnapshotHelper.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/snapshot/TestRestoreSnapshotHelper.java
index 7b274917788..a0246fee295 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/snapshot/TestRestoreSnapshotHelper.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/snapshot/TestRestoreSnapshotHelper.java
@@ -178,7 +178,7 @@ public class TestRestoreSnapshotHelper {
for (RegionInfo restoredRegion : restoredRegions) {
// open restored region
HRegion region = HRegion.newHRegion(CommonFSUtils.getTableDir(restoreDir, tableName), null,
- fs, conf, restoredRegion, htd, null);
+ fs, conf, restoredRegion, htd, null, null);
// set restore flag
region.setRestoredRegion(true);
region.initialize();
@@ -188,7 +188,7 @@ public class TestRestoreSnapshotHelper {
// open restored region without set restored flag
HRegion region2 = HRegion.newHRegion(CommonFSUtils.getTableDir(restoreDir, tableName), null,
- fs, conf, restoredRegion, htd, null);
+ fs, conf, restoredRegion, htd, null, null);
region2.initialize();
long maxSeqId2 = WALSplitUtil.getMaxRegionSequenceId(fs, recoveredEdit);
Assert.assertTrue(maxSeqId2 > maxSeqId);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/MockServer.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/MockServer.java
index 98283db1914..77b6ceffe7c 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/MockServer.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/MockServer.java
@@ -26,9 +26,7 @@ import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.client.AsyncClusterConnection;
import org.apache.hadoop.hbase.client.Connection;
-import org.apache.hadoop.hbase.keymeta.KeymetaAdmin;
-import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
-import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.keymeta.KeyManagementService;
import org.apache.hadoop.hbase.log.HBaseMarkers;
import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
import org.slf4j.Logger;
@@ -103,21 +101,6 @@ public class MockServer implements Server {
throw new UnsupportedOperationException();
}
- @Override
- public SystemKeyCache getSystemKeyCache() {
- return null;
- }
-
- @Override
- public ManagedKeyDataCache getManagedKeyDataCache() {
- return null;
- }
-
- @Override
- public KeymetaAdmin getKeymetaAdmin() {
- return null;
- }
-
@Override
public FileSystem getFileSystem() {
throw new UnsupportedOperationException();
@@ -137,4 +120,9 @@ public class MockServer implements Server {
public AsyncClusterConnection getAsyncClusterConnection() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public KeyManagementService getKeyManagementService() {
+ return null;
+ }
}
diff --git a/hbase-shell/pom.xml b/hbase-shell/pom.xml
index fb699554fc0..d595b2dd219 100644
--- a/hbase-shell/pom.xml
+++ b/hbase-shell/pom.xml
@@ -41,6 +41,12 @@
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.hbase</groupId>
+ <artifactId>hbase-common</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-annotations</artifactId>
diff --git a/hbase-shell/src/main/ruby/hbase/hbase.rb b/hbase-shell/src/main/ruby/hbase/hbase.rb
index 9b24e5caa97..a7e531806cf 100644
--- a/hbase-shell/src/main/ruby/hbase/hbase.rb
+++ b/hbase-shell/src/main/ruby/hbase/hbase.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#
#
# Licensed to the Apache Software Foundation (ASF) under one
@@ -29,6 +31,7 @@ require 'hbase/security'
require 'hbase/visibility_labels'
module Hbase
+ # Main HBase class for connection and admin operations
class Hbase
attr_accessor :configuration
@@ -45,22 +48,21 @@ module Hbase
end
def connection
- if @connection.nil?
- @connection = ConnectionFactory.createConnection(configuration)
- end
+ @connection = ConnectionFactory.createConnection(configuration) if @connection.nil?
@connection
end
+
# Returns ruby's Admin class from admin.rb
def admin
- ::Hbase::Admin.new(self.connection)
+ ::Hbase::Admin.new(connection)
end
def rsgroup_admin
- ::Hbase::RSGroupAdmin.new(self.connection)
+ ::Hbase::RSGroupAdmin.new(connection)
end
def keymeta_admin
- ::Hbase::KeymetaAdmin.new(@connection)
+ ::Hbase::KeymetaAdmin.new(connection)
end
def taskmonitor
@@ -69,7 +71,7 @@ module Hbase
# Create new one each time
def table(table, shell)
- ::Hbase::Table.new(self.connection.getTable(TableName.valueOf(table)), shell)
+ ::Hbase::Table.new(connection.getTable(TableName.valueOf(table)), shell)
end
def replication_admin
@@ -77,21 +79,19 @@ module Hbase
end
def security_admin
- ::Hbase::SecurityAdmin.new(self.connection.getAdmin)
+ ::Hbase::SecurityAdmin.new(connection.getAdmin)
end
def visibility_labels_admin
- ::Hbase::VisibilityLabelsAdmin.new(self.connection.getAdmin)
+ ::Hbase::VisibilityLabelsAdmin.new(connection.getAdmin)
end
def quotas_admin
- ::Hbase::QuotasAdmin.new(self.connection.getAdmin)
+ ::Hbase::QuotasAdmin.new(connection.getAdmin)
end
def shutdown
- if @connection != nil
- @connection.close
- end
+ @connection&.close
end
end
end
diff --git a/hbase-shell/src/main/ruby/shell/commands/keymeta_command_base.rb b/hbase-shell/src/main/ruby/shell/commands/keymeta_command_base.rb
index e2af5f524cc..98a57766831 100644
--- a/hbase-shell/src/main/ruby/shell/commands/keymeta_command_base.rb
+++ b/hbase-shell/src/main/ruby/shell/commands/keymeta_command_base.rb
@@ -34,7 +34,7 @@ module Shell
[
status.getKeyCustodianEncoded,
status.getKeyNamespace,
- status.getKeyStatus.toString,
+ status.getKeyState.toString,
status.getKeyMetadata,
status.getKeyMetadataHashEncoded,
status.getRefreshTimestamp
diff --git a/hbase-shell/src/test/java/org/apache/hadoop/hbase/client/TestKeymetaAdminShell.java b/hbase-shell/src/test/java/org/apache/hadoop/hbase/client/TestKeymetaAdminShell.java
new file mode 100644
index 00000000000..b67fbc69f3c
--- /dev/null
+++ b/hbase-shell/src/test/java/org/apache/hadoop/hbase/client/TestKeymetaAdminShell.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.client;
+
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtil;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.io.crypto.KeymetaTestUtils;
+import org.apache.hadoop.hbase.io.crypto.ManagedKeyProvider;
+import org.apache.hadoop.hbase.io.crypto.ManagedKeyStoreKeyProvider;
+import org.apache.hadoop.hbase.keymeta.ManagedKeyTestBase;
+import org.apache.hadoop.hbase.testclassification.ClientTests;
+import org.apache.hadoop.hbase.testclassification.IntegrationTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.jruby.embed.ScriptingContainer;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({ ClientTests.class, IntegrationTests.class })
+public class TestKeymetaAdminShell extends ManagedKeyTestBase implements RubyShellTest {
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestKeymetaAdminShell.class);
+
+ private final ScriptingContainer jruby = new ScriptingContainer();
+
+ @Before
+ public void setUp() throws Exception {
+ final Configuration conf = TEST_UTIL.getConfiguration();
+ conf.set("zookeeper.session.timeout", "6000000");
+ conf.set("hbase.rpc.timeout", "6000000");
+ conf.set("hbase.rpc.read.timeout", "6000000");
+ conf.set("hbase.rpc.write.timeout", "6000000");
+ conf.set("hbase.client.operation.timeout", "6000000");
+ conf.set("hbase.client.scanner.timeout.period", "6000000");
+ conf.set("hbase.ipc.client.socket.timeout.connect", "6000000");
+ conf.set("hbase.ipc.client.socket.timeout.read", "6000000");
+ conf.set("hbase.ipc.client.socket.timeout.write", "6000000");
+ conf.set("hbase.master.start.timeout.localHBaseCluster", "6000000");
+ conf.set("hbase.master.init.timeout.localHBaseCluster", "6000000");
+ conf.set("hbase.client.sync.wait.timeout.msec", "6000000");
+ Map<Bytes, Bytes> cust2key = new HashMap<>();
+ Map<Bytes, String> cust2alias = new HashMap<>();
+ String clusterId = UUID.randomUUID().toString();
+ String SYSTEM_KEY_ALIAS = "system-key-alias";
+ String CUST1 = "cust1";
+ String CUST1_ALIAS = "cust1-alias";
+ String GLOB_CUST_ALIAS = "glob-cust-alias";
+ String providerParams = KeymetaTestUtils.setupTestKeyStore(TEST_UTIL, true, true, store -> {
+ Properties p = new Properties();
+ try {
+ KeymetaTestUtils.addEntry(conf, 128, store, CUST1_ALIAS, CUST1, true, cust2key, cust2alias,
+ p);
+ KeymetaTestUtils.addEntry(conf, 128, store, GLOB_CUST_ALIAS, "*", true, cust2key,
+ cust2alias, p);
+ KeymetaTestUtils.addEntry(conf, 128, store, SYSTEM_KEY_ALIAS, clusterId, true, cust2key,
+ cust2alias, p);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return p;
+ });
+ // byte[] systemKey = cust2key.get(new Bytes(clusterId.getBytes())).get();
+ conf.set(HConstants.CRYPTO_MANAGED_KEY_STORE_SYSTEM_KEY_NAME_CONF_KEY, SYSTEM_KEY_ALIAS);
+ conf.set(HConstants.CRYPTO_KEYPROVIDER_PARAMETERS_KEY, providerParams);
+ RubyShellTest.setUpConfig(this);
+ super.setUp();
+ RubyShellTest.setUpJRubyRuntime(this);
+ RubyShellTest.doTestSetup(this);
+ addCustodianRubyEnvVars(jruby, "CUST1", CUST1);
+ }
+
+ @Override
+ public HBaseTestingUtil getTEST_UTIL() {
+ return TEST_UTIL;
+ }
+
+ @Override
+ public ScriptingContainer getJRuby() {
+ return jruby;
+ }
+
+ @Override
+ public String getSuitePattern() {
+ return "**/*_keymeta_test.rb";
+ }
+
+ @Test
+ public void testRunShellTests() throws Exception {
+ RubyShellTest.testRunShellTests(this);
+ }
+
+ @Override
+ protected Class<? extends ManagedKeyProvider> getKeyProviderClass() {
+ return ManagedKeyStoreKeyProvider.class;
+ }
+
+ public static void addCustodianRubyEnvVars(ScriptingContainer jruby, String custId,
+ String custodian) {
+ jruby.put("$" + custId, custodian);
+ jruby.put("$" + custId + "_ALIAS", custodian + "-alias");
+ jruby.put("$" + custId + "_ENCODED", Base64.getEncoder().encodeToString(custodian.getBytes()));
+ }
+}
diff --git a/hbase-shell/src/test/ruby/shell/admin_keymeta_test.rb b/hbase-shell/src/test/ruby/shell/admin_keymeta_test.rb
new file mode 100644
index 00000000000..c1108d0fc7d
--- /dev/null
+++ b/hbase-shell/src/test/ruby/shell/admin_keymeta_test.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'hbase_shell'
+require 'stringio'
+require 'hbase_constants'
+require 'hbase/hbase'
+require 'hbase/table'
+
+module Hbase
+ # Test class for keymeta admin functionality
+ class KeymetaAdminTest < Test::Unit::TestCase
+ include TestHelpers
+
+ def setup
+ setup_hbase
+ end
+
+ define_test 'Test enable key management' do
+ cust_and_namespace = "#{$CUST1_ENCODED}:*"
+ # Repeat the enable twice in a loop and ensure multiple enables succeed and return the
+ # same output.
+ 2.times do |i|
+ output = capture_stdout { @shell.command('enable_key_management', cust_and_namespace) }
+ puts "enable_key_management #{i} output: #{output}"
+ assert(output.include?("#{$CUST1_ENCODED} * ACTIVE"))
+ end
+ output = capture_stdout { @shell.command('show_key_status', cust_and_namespace) }
+ puts "show_key_status output: #{output}"
+ assert(output.include?("#{$CUST1_ENCODED} * ACTIVE"))
+
+ # The ManagedKeyStoreKeyProvider doesn't support specific namespaces, so it will return the
+ # global key.
+ cust_and_namespace = "#{$CUST1_ENCODED}:test_table/f"
+ output = capture_stdout { @shell.command('enable_key_management', cust_and_namespace) }
+ puts "enable_key_management output: #{output}"
+ assert(output.include?("#{$CUST1_ENCODED} * ACTIVE"))
+ output = capture_stdout { @shell.command('show_key_status', cust_and_namespace) }
+ puts "show_key_status output: #{output}"
+ assert(output.include?('0 row(s)'))
+ end
+ end
+end
diff --git a/hbase-shell/src/test/ruby/shell/encrypted_table_keymeta_test.rb b/hbase-shell/src/test/ruby/shell/encrypted_table_keymeta_test.rb
new file mode 100644
index 00000000000..be52a2524e4
--- /dev/null
+++ b/hbase-shell/src/test/ruby/shell/encrypted_table_keymeta_test.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'hbase_shell'
+require 'stringio'
+require 'hbase_constants'
+require 'hbase/hbase'
+require 'hbase/table'
+
+java_import org.apache.hadoop.conf.Configuration
+java_import org.apache.hadoop.fs.FSDataInputStream
+java_import org.apache.hadoop.hbase.CellUtil
+java_import org.apache.hadoop.hbase.HConstants
+java_import org.apache.hadoop.hbase.client.Get
+java_import org.apache.hadoop.hbase.io.crypto.Encryption
+java_import org.apache.hadoop.hbase.io.crypto.MockManagedKeyProvider
+java_import org.apache.hadoop.hbase.io.hfile.CorruptHFileException
+java_import org.apache.hadoop.hbase.io.hfile.FixedFileTrailer
+java_import org.apache.hadoop.hbase.io.hfile.HFile
+java_import org.apache.hadoop.hbase.io.hfile.CacheConfig
+java_import org.apache.hadoop.hbase.util.Bytes
+
+module Hbase
+ # Test class for encrypted table keymeta functionality
+ class EncryptedTableKeymetaTest < Test::Unit::TestCase
+ include TestHelpers
+
+ def setup
+ setup_hbase
+ @test_table = 'enctest'
+ @connection = $TEST_CLUSTER.connection
+ end
+
+ define_test 'Test table put/get with encryption' do
+ cust_and_namespace = "#{$CUST1_ENCODED}:*"
+ @shell.command(:enable_key_management, cust_and_namespace)
+ @shell.command(:create, @test_table, { 'NAME' => 'f', 'ENCRYPTION' => 'AES' })
+ test_table = table(@test_table)
+ test_table.put('1', 'f:a', '2')
+ puts "Added a row, now flushing table #{@test_table}"
+ command(:flush, @test_table)
+
+ table_name = TableName.valueOf(@test_table)
+ store_file_info = nil
+ $TEST_CLUSTER.getRSForFirstRegionInTable(table_name).getRegions(table_name).each do |region|
+ region.getStores.each do |store|
+ store.getStorefiles.each do |storefile|
+ store_file_info = storefile.getFileInfo
+ end
+ end
+ end
+ assert_not_nil(store_file_info)
+ hfile_info = store_file_info.getHFileInfo
+ assert_not_nil(hfile_info)
+ live_trailer = hfile_info.getTrailer
+ assert_trailer(live_trailer)
+
+ ## Disable table to ensure that the stores are not cached.
+ command(:disable, @test_table)
+ assert(!command(:is_enabled, @test_table))
+
+ # Open FSDataInputStream to the path pointed to by the store_file_info
+ fs = store_file_info.getFileSystem
+ fio = fs.open(store_file_info.getPath)
+ assert_not_nil(fio)
+ # Read trailer using FiledFileTrailer
+ offline_trailer = FixedFileTrailer.readFromStream(
+ fio, fs.getFileStatus(store_file_info.getPath).getLen
+ )
+ fio.close
+ assert_trailer(offline_trailer, live_trailer)
+
+ # Test for the ability to read HFile with encryption in an offline offline
+ reader = HFile.createReader(fs, store_file_info.getPath, CacheConfig::DISABLED, true,
+ $TEST_CLUSTER.getConfiguration)
+ assert_not_nil(reader)
+ offline_trailer = reader.getTrailer
+ assert_trailer(offline_trailer, live_trailer)
+ scanner = reader.getScanner($TEST_CLUSTER.getConfiguration, false, false)
+ assert_true(scanner.seekTo)
+ cell = scanner.getCell
+ assert_equal('1', Bytes.toString(CellUtil.cloneRow(cell)))
+ assert_equal('2', Bytes.toString(CellUtil.cloneValue(cell)))
+ assert_false(scanner.next)
+
+ # Confirm that the offline reading will fail with no config related to encryption
+ Encryption.clearKeyProviderCache
+ conf = Configuration.new($TEST_CLUSTER.getConfiguration)
+ conf.set(HConstants::CRYPTO_KEYPROVIDER_CONF_KEY, MockManagedKeyProvider.java_class.getName)
+ # This is expected to fail with CorruptHFileException.
+ assert_raises(CorruptHFileException) do |e|
+ reader = HFile.createReader(fs, store_file_info.getPath, CacheConfig::DISABLED, true, conf)
+ assert_true(e.message.include?(
+ "Problem reading HFile Trailer from file #{store_file_info.getPath}"
+ ))
+ end
+ Encryption.clearKeyProviderCache
+
+ ## Enable back the table to be able to query.
+ command(:enable, @test_table)
+ assert(command(:is_enabled, @test_table))
+
+ get = Get.new(Bytes.toBytes('1'))
+ res = test_table.table.get(get)
+ puts "res for row '1' and column f:a: #{res}"
+ assert_false(res.isEmpty)
+ assert_equal('2', Bytes.toString(res.getValue(Bytes.toBytes('f'), Bytes.toBytes('a'))))
+ end
+
+ def assert_trailer(offline_trailer, live_trailer = nil)
+ assert_not_nil(offline_trailer)
+ assert_not_nil(offline_trailer.getEncryptionKey)
+ assert_not_nil(offline_trailer.getKEKMetadata)
+ assert_not_nil(offline_trailer.getKEKChecksum)
+ assert_not_nil(offline_trailer.getKeyNamespace)
+
+ return unless live_trailer
+
+ assert_equal(live_trailer.getEncryptionKey, offline_trailer.getEncryptionKey)
+ assert_equal(live_trailer.getKEKMetadata, offline_trailer.getKEKMetadata)
+ assert_equal(live_trailer.getKEKChecksum, offline_trailer.getKEKChecksum)
+ assert_equal(live_trailer.getKeyNamespace, offline_trailer.getKeyNamespace)
+ end
+ end
+end