001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.regionserver.wal; 019 020import java.io.IOException; 021import java.security.Key; 022import java.security.KeyException; 023import java.util.ArrayList; 024import java.util.List; 025import org.apache.hadoop.fs.FSDataInputStream; 026import org.apache.hadoop.hbase.HBaseInterfaceAudience; 027import org.apache.hadoop.hbase.HConstants; 028import org.apache.hadoop.hbase.io.crypto.Cipher; 029import org.apache.hadoop.hbase.io.crypto.Decryptor; 030import org.apache.hadoop.hbase.io.crypto.Encryption; 031import org.apache.hadoop.hbase.security.EncryptionUtil; 032import org.apache.hadoop.hbase.security.User; 033import org.apache.hadoop.hbase.util.EncryptionTest; 034import org.apache.yetus.audience.InterfaceAudience; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.WALHeader; 039 040@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) 041public class SecureProtobufLogReader extends ProtobufLogReader { 042 043 private static final Logger LOG = LoggerFactory.getLogger(SecureProtobufLogReader.class); 044 045 private Decryptor decryptor = null; 046 private static List<String> writerClsNames = new ArrayList<>(); 047 static { 048 writerClsNames.add(ProtobufLogWriter.class.getSimpleName()); 049 writerClsNames.add(SecureProtobufLogWriter.class.getSimpleName()); 050 writerClsNames.add(AsyncProtobufLogWriter.class.getSimpleName()); 051 writerClsNames.add(SecureAsyncProtobufLogWriter.class.getSimpleName()); 052 } 053 054 @Override 055 public List<String> getWriterClsNames() { 056 return writerClsNames; 057 } 058 059 @Override 060 protected WALHdrContext readHeader(WALHeader.Builder builder, FSDataInputStream stream) 061 throws IOException { 062 WALHdrContext hdrCtxt = super.readHeader(builder, stream); 063 WALHdrResult result = hdrCtxt.getResult(); 064 // We need to unconditionally handle the case where the WAL has a key in 065 // the header, meaning it is encrypted, even if ENABLE_WAL_ENCRYPTION is 066 // no longer set in the site configuration. 067 if (result == WALHdrResult.SUCCESS && builder.hasEncryptionKey()) { 068 // Serialized header data has been merged into the builder from the 069 // stream. 070 071 EncryptionTest.testKeyProvider(conf); 072 EncryptionTest.testCipherProvider(conf); 073 074 // Retrieve a usable key 075 076 byte[] keyBytes = builder.getEncryptionKey().toByteArray(); 077 Key key = null; 078 String walKeyName = conf.get(HConstants.CRYPTO_WAL_KEY_NAME_CONF_KEY); 079 // First try the WAL key, if one is configured 080 if (walKeyName != null) { 081 try { 082 key = EncryptionUtil.unwrapWALKey(conf, walKeyName, keyBytes); 083 } catch (KeyException e) { 084 if (LOG.isDebugEnabled()) { 085 LOG.debug("Unable to unwrap key with WAL key '" + walKeyName + "'"); 086 } 087 key = null; 088 } 089 } 090 if (key == null) { 091 String masterKeyName = 092 conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()); 093 try { 094 // Then, try the cluster master key 095 key = EncryptionUtil.unwrapWALKey(conf, masterKeyName, keyBytes); 096 } catch (KeyException e) { 097 // If the current master key fails to unwrap, try the alternate, if 098 // one is configured 099 if (LOG.isDebugEnabled()) { 100 LOG.debug("Unable to unwrap key with current master key '" + masterKeyName + "'"); 101 } 102 String alternateKeyName = conf.get(HConstants.CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY); 103 if (alternateKeyName != null) { 104 try { 105 key = EncryptionUtil.unwrapWALKey(conf, alternateKeyName, keyBytes); 106 } catch (KeyException ex) { 107 throw new IOException(ex); 108 } 109 } else { 110 throw new IOException(e); 111 } 112 } 113 } 114 115 // Use the algorithm the key wants 116 117 Cipher cipher = Encryption.getCipher(conf, key.getAlgorithm()); 118 if (cipher == null) { 119 throw new IOException("Cipher '" + key.getAlgorithm() + "' is not available"); 120 } 121 122 // Set up the decryptor for this WAL 123 124 decryptor = cipher.getDecryptor(); 125 decryptor.setKey(key); 126 127 if (LOG.isTraceEnabled()) { 128 LOG.trace("Initialized secure protobuf WAL: cipher=" + cipher.getName()); 129 } 130 } 131 132 return hdrCtxt; 133 } 134 135 @Override 136 protected void initAfterCompression(String cellCodecClsName) throws IOException { 137 if (decryptor != null && cellCodecClsName.equals(SecureWALCellCodec.class.getName())) { 138 WALCellCodec codec = SecureWALCellCodec.getCodec(this.conf, decryptor); 139 this.cellDecoder = codec.getDecoder(this.inputStream); 140 // We do not support compression with WAL encryption 141 this.compressionContext = null; 142 this.byteStringUncompressor = WALCellCodec.getNoneUncompressor(); 143 this.hasCompression = false; 144 } else { 145 super.initAfterCompression(cellCodecClsName); 146 } 147 } 148 149}