Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public class EncryptionDirectory extends FilterDirectory {

protected final KeySupplier keySupplier;

protected final EncryptionListener encryptionListener;

/** Cache of the latest commit user data. */
protected volatile CommitUserData commitUserData;

Expand All @@ -100,14 +102,20 @@ public class EncryptionDirectory extends FilterDirectory {
* Creates an {@link EncryptionDirectory} which wraps a delegate {@link Directory} to encrypt/decrypt
* files on the fly.
*
* @param encrypterFactory creates {@link AesCtrEncrypter}.
* @param keySupplier provides the key secrets and determines which files should be encrypted.
* @param encrypterFactory creates {@link AesCtrEncrypter}.
* @param keySupplier provides the key secrets and determines which files should be encrypted.
* @param encryptionListener notified when the index is encrypted.
*/
public EncryptionDirectory(Directory delegate, AesCtrEncrypterFactory encrypterFactory, KeySupplier keySupplier)
public EncryptionDirectory(
Directory delegate,
AesCtrEncrypterFactory encrypterFactory,
KeySupplier keySupplier,
EncryptionListener encryptionListener)
throws IOException {
super(delegate);
this.encrypterFactory = encrypterFactory;
this.keySupplier = keySupplier;
this.encryptionListener = encryptionListener;
commitUserData = readLatestCommitUserData();
}

Expand Down Expand Up @@ -148,6 +156,7 @@ protected IndexOutput maybeWrapOutput(IndexOutput indexOutput) throws IOExceptio
try {
String keyRef = getActiveKeyRefFromCommit(getLatestCommitData().data);
if (keyRef != null) {
encryptionListener.onEncryption();
// Get the key secret first. If it fails, we do not write anything.
byte[] keySecret = getKeySecret(keyRef);
// The IndexOutput has to be wrapped to be encrypted with the key.
Expand All @@ -173,10 +182,17 @@ private static void writeEncryptionHeader(String keyRef, DataOutput out) throws
/**
* Forces this {@link EncryptionDirectory} to read the user data of the latest commit, to refresh its cache.
*/
public void forceReadCommitUserData() {
public void clearCachedCommitUserData() {
shouldReadCommitUserData = true;
}

/**
* Clears the cached encryption status for logs.
*/
public void clearCachedEncryptionStatus() {
encryptionListener.clearEncryptionStatus();
}

/**
* Gets the user data from the latest commit, potentially reading the latest commit if the cache is stale.
*/
Expand Down Expand Up @@ -255,6 +271,7 @@ public IndexInput openInput(String fileName, IOContext context) throws IOExcepti
try {
String keyRef = getKeyRefForReading(indexInput);
if (keyRef != null) {
encryptionListener.onEncryption();
// The IndexInput has to be wrapped to be decrypted with the key.
indexInput = new DecryptingIndexInput(indexInput, getKeySecret(keyRef), encrypterFactory);
}
Expand Down Expand Up @@ -371,4 +388,25 @@ protected CommitUserData(String segmentFileName, Map<String, String> data) {
keyCookies = getKeyCookiesFromCommit(data);
}
}

/**
* Notified when the index is encrypted.
*/
public interface EncryptionListener {

EncryptionListener NO_LISTENER = new EncryptionListener() {

@Override
public void onEncryption() {
}

@Override
public void clearEncryptionStatus() {
}
};

void onEncryption();

void clearEncryptionStatus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.solr.core.DirectoryFactory;
import org.apache.solr.core.MMapDirectoryFactory;
import org.apache.solr.core.SolrCore;
import org.apache.solr.encryption.EncryptionDirectory.EncryptionListener;
import org.apache.solr.encryption.crypto.AesCtrEncrypterFactory;
import org.apache.solr.encryption.crypto.CipherAesCtrEncrypter;

Expand Down Expand Up @@ -74,9 +75,20 @@ public class EncryptionDirectoryFactory extends MMapDirectoryFactory {
*/
static final String PROPERTY_INNER_ENCRYPTION_DIRECTORY_FACTORY = "innerEncryptionDirectoryFactory";

private final EncryptionListener encryptionListener = new EncryptionListener() {
@Override
public void onEncryption() {
indexEncrypted = true;
}
@Override
public void clearEncryptionStatus() {
indexEncrypted = false;
}
};
private KeySupplier keySupplier;
private AesCtrEncrypterFactory encrypterFactory;
private InnerFactory innerFactory;
private volatile boolean indexEncrypted;

public EncryptionDirectoryFactory() {}

Expand Down Expand Up @@ -145,6 +157,15 @@ public KeySupplier getKeySupplier() {
return keySupplier;
}

/**
* Returns whether the index is encrypted.
* This flag is set when the {@link EncryptionDirectory} opens an output/input stream that
* requires encryption.
*/
public boolean isIndexEncrypted() {
return indexEncrypted;
}

public static EncryptionDirectoryFactory getFactory(SolrCore core) {
if (!(core.getDirectoryFactory() instanceof EncryptionDirectoryFactory)) {
throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE,
Expand All @@ -157,7 +178,7 @@ public static EncryptionDirectoryFactory getFactory(SolrCore core) {

@Override
protected Directory create(String path, LockFactory lockFactory, DirContext dirContext) throws IOException {
return innerFactory.create(super.create(path, lockFactory, dirContext), getEncrypterFactory(), getKeySupplier());
return innerFactory.create(super.create(path, lockFactory, dirContext), getEncrypterFactory(), getKeySupplier(), encryptionListener);
}

@Override
Expand All @@ -178,7 +199,11 @@ public void close() throws IOException {
* Visible for tests only - Inner factory that creates {@link EncryptionDirectory} instances.
*/
interface InnerFactory {
EncryptionDirectory create(Directory delegate, AesCtrEncrypterFactory encrypterFactory, KeySupplier keySupplier)
EncryptionDirectory create(
Directory delegate,
AesCtrEncrypterFactory encrypterFactory,
KeySupplier keySupplier,
EncryptionListener encryptionListener)
throws IOException;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ public MergeSpecification findForcedMerges(SegmentInfos segmentInfos,
// Make sure the EncryptionDirectory does not keep its cache for the commit user data.
// It must read the latest commit user data to get the latest active key, so below the
// segments with old key (to re-encrypt) are always accurate.
encryptionDir.forceReadCommitUserData();
encryptionDir.clearCachedCommitUserData();
// Also clear the encryption status for logs if the index becomes cleartext after rewriting
// the segments.
if (activeKeyId == null) {
encryptionDir.clearCachedEncryptionStatus();
}
List<SegmentCommitInfo> segmentsWithOldKeyId = encryptionDir.getSegmentsWithOldKeyId(segmentInfos, activeKeyId);
if (segmentsWithOldKeyId.isEmpty()) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public synchronized boolean encryptLogs() throws IOException {
String activeKeyRef;
EncryptionDirectory directory = directorySupplier.get();
try {
directory.forceReadCommitUserData();
directory.clearCachedCommitUserData();
latestCommitData = directory.getLatestCommitData().data;
activeKeyRef = getActiveKeyRefFromCommit(latestCommitData);
for (TransactionLog log : logs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,11 @@ public void testCanDisableChecksumVerification() throws Exception {
AesCtrEncrypterFactory encrypterFactory = random().nextBoolean() ? CipherAesCtrEncrypter.FACTORY : LightAesCtrEncrypter.FACTORY;
KeySupplier keySupplier = new TestingKeySupplier.Factory().create();
try (Directory fsSourceDir = FSDirectory.open(sourcePath);
Directory encSourceDir = new TestEncryptionDirectory(fsSourceDir, encrypterFactory, keySupplier);
Directory encSourceDir = new TestEncryptionDirectory(
fsSourceDir,
encrypterFactory,
keySupplier,
EncryptionDirectory.EncryptionListener.NO_LISTENER);
Directory destinationDir = FSDirectory.open(createTempDir().toAbsolutePath())) {
String fileName = "source-file";
String content = "content";
Expand Down Expand Up @@ -331,9 +335,13 @@ public void setDelegate(BackupRepository delegate) {
*/
private static class TestEncryptionDirectory extends EncryptionDirectory {

TestEncryptionDirectory(Directory delegate, AesCtrEncrypterFactory encrypterFactory, KeySupplier keySupplier)
throws IOException {
super(delegate, encrypterFactory, keySupplier);
TestEncryptionDirectory(
Directory delegate,
AesCtrEncrypterFactory encrypterFactory,
KeySupplier keySupplier,
EncryptionListener encryptionListener)
throws IOException {
super(delegate, encrypterFactory, keySupplier, encryptionListener);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,10 @@ static void setKeysInCommitUserData(String... keyIds) throws IOException {
@Override
public EncryptionDirectory create(Directory delegate,
AesCtrEncrypterFactory encrypterFactory,
KeySupplier keySupplier) throws IOException {
MockEncryptionDirectory mockDir = new MockEncryptionDirectory(delegate, encrypterFactory, keySupplier);
KeySupplier keySupplier,
EncryptionDirectory.EncryptionListener encryptionListener)
throws IOException {
MockEncryptionDirectory mockDir = new MockEncryptionDirectory(delegate, encrypterFactory, keySupplier, encryptionListener);
mockDirs.add(mockDir);
return mockDir;
}
Expand All @@ -273,9 +275,13 @@ private static class MockEncryptionDirectory extends EncryptionDirectory {

final KeySupplier keySupplier;

MockEncryptionDirectory(Directory delegate, AesCtrEncrypterFactory encrypterFactory, KeySupplier keySupplier)
MockEncryptionDirectory(
Directory delegate,
AesCtrEncrypterFactory encrypterFactory,
KeySupplier keySupplier,
EncryptionListener encryptionListener)
throws IOException {
super(delegate, encrypterFactory, keySupplier);
super(delegate, encrypterFactory, keySupplier, encryptionListener);
this.keySupplier = keySupplier;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.encryption.crypto.LightAesCtrEncrypter;
import org.apache.solr.encryption.EncryptionDirectory.EncryptionListener;
import org.apache.solr.index.MergePolicyFactoryArgs;
import org.apache.solr.index.TieredMergePolicyFactory;
import org.junit.Test;
Expand All @@ -38,6 +39,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static org.apache.solr.encryption.TestingEncryptionRequestHandler.MOCK_COOKIE_PARAMS;
Expand Down Expand Up @@ -80,9 +82,22 @@ private MergePolicy createMergePolicy() {
@Test
public void testSegmentReencryption() throws Exception {
KeySupplier keySupplier = new TestingKeySupplier.Factory().create();
try (Directory dir = new EncryptionDirectory(new MMapDirectory(createTempDir(), FSLockFactory.getDefault()),
LightAesCtrEncrypter.FACTORY,
keySupplier)) {
AtomicBoolean indexEncrypted = new AtomicBoolean(false);
EncryptionListener encryptionListener = new EncryptionListener() {
@Override
public void onEncryption() {
indexEncrypted.set(true);
}
@Override
public void clearEncryptionStatus() {
indexEncrypted.set(false);
}
};
try (Directory dir = new EncryptionDirectory(
new MMapDirectory(createTempDir(), FSLockFactory.getDefault()),
LightAesCtrEncrypter.FACTORY,
keySupplier,
encryptionListener)) {
IndexWriterConfig iwc = new IndexWriterConfig(new WhitespaceAnalyzer());
iwc.setMergeScheduler(new ConcurrentMergeScheduler());
iwc.setMergePolicy(createMergePolicy());
Expand Down Expand Up @@ -125,6 +140,7 @@ public void testSegmentReencryption() throws Exception {
assertTrue(segmentNames.isEmpty());
}
}
assertTrue(indexEncrypted.get());
}

private void commit(IndexWriter writer, KeySupplier keySupplier, String... keyIds) throws IOException {
Expand Down
Loading