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}