/*
* 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.nifi.provenance;
import java.io.IOException;
import java.security.KeyManagementException;
import java.util.Map;
import java.util.stream.Collectors;
import javax.crypto.SecretKey;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.events.EventReporter;
import org.apache.nifi.provenance.serialization.RecordReaders;
import org.apache.nifi.provenance.store.EventFileManager;
import org.apache.nifi.provenance.store.RecordReaderFactory;
import org.apache.nifi.provenance.store.RecordWriterFactory;
import org.apache.nifi.provenance.toc.StandardTocWriter;
import org.apache.nifi.provenance.toc.TocUtil;
import org.apache.nifi.provenance.toc.TocWriter;
import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EncryptedWriteAheadProvenanceRepository extends WriteAheadProvenanceRepository {
private static final Logger logger = LoggerFactory.getLogger(EncryptedWriteAheadProvenanceRepository.class);
/**
* This constructor exists solely for the use of the Java Service Loader mechanism and should not be used.
*/
public EncryptedWriteAheadProvenanceRepository() {
super();
}
public EncryptedWriteAheadProvenanceRepository(final NiFiProperties nifiProperties) {
super(RepositoryConfiguration.create(nifiProperties));
}
public EncryptedWriteAheadProvenanceRepository(final RepositoryConfiguration config) {
super(config);
}
/**
* This method initializes the repository. It first builds the key provider and event encryptor
* from the config values, then creates the encrypted record writer and reader, then delegates
* back to the superclass for the common implementation.
*
* @param eventReporter the event reporter
* @param authorizer the authorizer
* @param resourceFactory the authorizable factory
* @param idLookup the lookup provider
* @throws IOException if there is an error initializing this repository
*/
@Override
public synchronized void initialize(final EventReporter eventReporter, final Authorizer authorizer, final ProvenanceAuthorizableFactory resourceFactory,
final IdentifierLookup idLookup) throws IOException {
// Initialize the encryption-specific fields
ProvenanceEventEncryptor provenanceEventEncryptor;
if (getConfig().supportsEncryption()) {
try {
KeyProvider keyProvider = buildKeyProvider();
provenanceEventEncryptor = new AESProvenanceEventEncryptor();
provenanceEventEncryptor.initialize(keyProvider);
} catch (KeyManagementException e) {
String msg = "Encountered an error building the key provider";
logger.error(msg, e);
throw new IOException(msg, e);
}
} else {
throw new IOException("The provided configuration does not support a encrypted repository");
}
// Build a factory using lambda which injects the encryptor
final RecordWriterFactory recordWriterFactory = (file, idGenerator, compressed, createToc) -> {
try {
final TocWriter tocWriter = createToc ? new StandardTocWriter(TocUtil.getTocFile(file), false, false) : null;
return new EncryptedSchemaRecordWriter(file, idGenerator, tocWriter, compressed, BLOCK_SIZE, idLookup, provenanceEventEncryptor, getConfig().getDebugFrequency());
} catch (EncryptionException e) {
logger.error("Encountered an error building the schema record writer factory: ", e);
throw new IOException(e);
}
};
// Build a factory using lambda which injects the encryptor
final EventFileManager fileManager = new EventFileManager();
final RecordReaderFactory recordReaderFactory = (file, logs, maxChars) -> {
fileManager.obtainReadLock(file);
try {
EncryptedSchemaRecordReader tempReader = (EncryptedSchemaRecordReader) RecordReaders.newRecordReader(file, logs, maxChars);
tempReader.setProvenanceEventEncryptor(provenanceEventEncryptor);
return tempReader;
} finally {
fileManager.releaseReadLock(file);
}
};
// Delegate the init to the parent impl
super.init(recordWriterFactory, recordReaderFactory, eventReporter, authorizer, resourceFactory);
}
private KeyProvider buildKeyProvider() throws KeyManagementException {
RepositoryConfiguration config = super.getConfig();
if (config == null) {
throw new KeyManagementException("The repository configuration is missing");
}
final String implementationClassName = config.getKeyProviderImplementation();
if (implementationClassName == null) {
throw new KeyManagementException("Cannot create Key Provider because the NiFi Properties is missing the following property: "
+ NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS);
}
// TODO: Extract to factory
KeyProvider keyProvider;
if (StaticKeyProvider.class.getName().equals(implementationClassName)) {
// Get all the keys (map) from config
if (CryptoUtils.isValidKeyProvider(implementationClassName, config.getKeyProviderLocation(), config.getKeyId(), config.getEncryptionKeys())) {
Map<String, SecretKey> formedKeys = config.getEncryptionKeys().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> {
try {
return CryptoUtils.formKeyFromHex(e.getValue());
} catch (KeyManagementException e1) {
// This should never happen because the hex has already been validated
logger.error("Encountered an error: ", e1);
return null;
}
}));
keyProvider = new StaticKeyProvider(formedKeys);
} else {
final String msg = "The StaticKeyProvider definition is not valid";
logger.error(msg);
throw new KeyManagementException(msg);
}
} else if (FileBasedKeyProvider.class.getName().equals(implementationClassName)) {
keyProvider = new FileBasedKeyProvider(config.getKeyProviderLocation());
if (!keyProvider.keyExists(config.getKeyId())) {
throw new KeyManagementException("The specified key ID " + config.getKeyId() + " is not in the key definition file");
}
} else {
throw new KeyManagementException("Invalid key provider implementation provided: " + implementationClassName);
}
return keyProvider;
}
}