Skip to content

Commit

Permalink
IGNITE-21013 Support encryption of cache dumps (#11078)
Browse files Browse the repository at this point in the history
  • Loading branch information
nizhikov authored Dec 8, 2023
1 parent d053cb8 commit 668939a
Show file tree
Hide file tree
Showing 16 changed files with 516 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import org.apache.ignite.internal.util.typedef.internal.CU;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteExperimental;
import org.apache.ignite.spi.IgniteSpiAdapter;
import org.apache.ignite.spi.encryption.EncryptionSpi;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.apache.ignite.configuration.DataStorageConfiguration.DFLT_MARSHALLER_PATH;
Expand Down Expand Up @@ -75,7 +77,7 @@ public DumpReader(DumpReaderConfiguration cfg, IgniteLogger log) {
@Override public void run() {
ackAsciiLogo();

try (Dump dump = new Dump(cfg.dumpRoot(), cfg.keepBinary(), false, log)) {
try (Dump dump = new Dump(cfg.dumpRoot(), null, cfg.keepBinary(), false, encryptionSpi(), log)) {
DumpConsumer cnsmr = cfg.consumer();

cnsmr.start();
Expand Down Expand Up @@ -231,4 +233,19 @@ private void ackAsciiLogo() {
"");
}
}

/** */
private EncryptionSpi encryptionSpi() {
EncryptionSpi encSpi = cfg.encryptionSpi();

if (encSpi == null)
return null;

if (encSpi instanceof IgniteSpiAdapter)
((IgniteSpiAdapter)encSpi).onBeforeStart();

encSpi.spiStart("dump-reader");

return encSpi;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.ignite.internal.processors.cache.CacheObject;
import org.apache.ignite.internal.processors.cache.KeyCacheObject;
import org.apache.ignite.lang.IgniteExperimental;
import org.apache.ignite.spi.encryption.EncryptionSpi;

/**
* Configuration class of {@link DumpReader}.
Expand Down Expand Up @@ -61,12 +62,15 @@ public class DumpReaderConfiguration {
/** Skip copies. */
private final boolean skipCopies;

/** Encryption SPI. */
private final EncryptionSpi encSpi;

/**
* @param dir Root dump directory.
* @param cnsmr Dump consumer.
*/
public DumpReaderConfiguration(File dir, DumpConsumer cnsmr) {
this(dir, cnsmr, DFLT_THREAD_CNT, DFLT_TIMEOUT, true, true, null, false);
this(dir, cnsmr, DFLT_THREAD_CNT, DFLT_TIMEOUT, true, true, null, false, null);
}

/**
Expand All @@ -78,15 +82,18 @@ public DumpReaderConfiguration(File dir, DumpConsumer cnsmr) {
* @param keepBinary If {@code true} then don't deserialize {@link KeyCacheObject} and {@link CacheObject}.
* @param cacheGroupNames Cache group names.
* @param skipCopies Skip copies.
* @param encSpi Encryption SPI.
*/
public DumpReaderConfiguration(File dir,
public DumpReaderConfiguration(
File dir,
DumpConsumer cnsmr,
int thCnt,
Duration timeout,
boolean failFast,
boolean keepBinary,
String[] cacheGroupNames,
boolean skipCopies
boolean skipCopies,
EncryptionSpi encSpi
) {
this.dir = dir;
this.cnsmr = cnsmr;
Expand All @@ -96,6 +103,7 @@ public DumpReaderConfiguration(File dir,
this.keepBinary = keepBinary;
this.cacheGroupNames = cacheGroupNames;
this.skipCopies = skipCopies;
this.encSpi = encSpi;
}

/** @return Root dump directiory. */
Expand Down Expand Up @@ -137,4 +145,9 @@ public String[] cacheGroupNames() {
public boolean skipCopies() {
return skipCopies;
}

/** @return Encryption SPI */
public EncryptionSpi encryptionSpi() {
return encSpi;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@ private IgniteInternalFuture<SnapshotOperationResponse> initLocalFullSnapshot(
withMetaStorage,
req.dump(),
req.compress(),
req.encrypt(),
locSndrFactory.apply(req.snapshotName(), req.snapshotPath())
);

Expand All @@ -1218,6 +1219,10 @@ private IgniteInternalFuture<SnapshotOperationResponse> initLocalFullSnapshot(

SnapshotFutureTaskResult res = (SnapshotFutureTaskResult)task0.result();

Serializable encKey = req.encrypt() ? ((CreateDumpFutureTask)task0).encryptionKey() : null;

EncryptionSpi encSpi = cctx.gridConfig().getEncryptionSpi();

SnapshotMetadata meta = new SnapshotMetadata(req.requestId(),
req.snapshotName(),
cctx.localNode().consistentId().toString(),
Expand All @@ -1229,9 +1234,10 @@ private IgniteInternalFuture<SnapshotOperationResponse> initLocalFullSnapshot(
blts,
res.parts(),
res.snapshotPointer(),
cctx.gridConfig().getEncryptionSpi().masterKeyDigest(),
encSpi.masterKeyDigest(),
req.onlyPrimary(),
req.dump()
req.dump(),
encKey == null ? null : encSpi.encryptKey(encKey)
);

SnapshotHandlerContext ctx = new SnapshotHandlerContext(meta, req.groups(), cctx.localNode(), snpDir,
Expand Down Expand Up @@ -1808,7 +1814,7 @@ private boolean cancelLocalSnapshotTask0(Function<AbstractSnapshotFutureTask<?>,

/** {@inheritDoc} */
@Override public IgniteFuture<Void> createDump(String name, @Nullable Collection<String> cacheGroupNames) {
return createSnapshot(name, null, cacheGroupNames, false, false, true, false);
return createSnapshot(name, null, cacheGroupNames, false, false, true, false, false);
}

/**
Expand Down Expand Up @@ -2196,11 +2202,16 @@ public IgniteFutureImpl<Void> createSnapshot(
boolean incremental,
boolean onlyPrimary
) {
return createSnapshot(name, snpPath, null, incremental, onlyPrimary, false, false);
return createSnapshot(name, snpPath, null, incremental, onlyPrimary, false, false, false);
}

/**
* Create a consistent copy of all persistence cache groups from the whole cluster.
* Note, {@code encrypt} flag can be used only for cache dump.
* Full snapshots store partition files itself.
* So if cache is encrypted ({@link CacheConfiguration#isEncryptionEnabled()}{@code = true}) then snapshot files will be encrypted.
* On the other hand, dumps stores only entry data and can be used fo in-memory caches.
* So we provide an ability to encrypt dump content to protect data on the disk.
*
* @param name Snapshot unique name which satisfies the following name pattern [a-zA-Z0-9_].
* @param snpPath Snapshot directory path.
Expand All @@ -2209,6 +2220,7 @@ public IgniteFutureImpl<Void> createSnapshot(
* @param onlyPrimary If {@code true} snapshot only primary copies of partitions.
* @param dump If {@code true} cache dump must be created.
* @param compress If {@code true} then compress partition files.
* @param encrypt If {@code true} then content of dump encrypted.
* @return Future which will be completed when a process ends.
*/
public IgniteFutureImpl<Void> createSnapshot(
Expand All @@ -2218,7 +2230,8 @@ public IgniteFutureImpl<Void> createSnapshot(
boolean incremental,
boolean onlyPrimary,
boolean dump,
boolean compress
boolean compress,
boolean encrypt
) {
A.notNullOrEmpty(name, "Snapshot name cannot be null or empty.");
A.ensure(U.alphanumericUnderscore(name), "Snapshot name must satisfy the following name pattern: a-zA-Z0-9_");
Expand Down Expand Up @@ -2250,11 +2263,17 @@ public IgniteFutureImpl<Void> createSnapshot(
return new IgniteSnapshotFutureImpl(cctx.kernalContext().closure()
.callAsync(
BALANCE,
new CreateSnapshotCallable(name, cacheGroupNames, incremental, onlyPrimary, dump, compress),
new CreateSnapshotCallable(name, cacheGroupNames, incremental, onlyPrimary, dump, compress, encrypt),
options(Collections.singletonList(crd)).withFailoverDisabled()
));
}

A.ensure(!encrypt || dump, "Encryption key is supported only for dumps");
A.ensure(
!encrypt || cctx.gridConfig().getEncryptionSpi() != null,
"Encryption SPI must be set to encrypt dump"
);

if (!CU.isPersistenceEnabled(cctx.gridConfig()) && !dump) {
throw new IgniteException("Create snapshot request has been rejected. " +
"Snapshots on an in-memory clusters are not allowed.");
Expand Down Expand Up @@ -2353,7 +2372,8 @@ else if (grps.isEmpty())
incIdx,
onlyPrimary,
dump,
compress
compress,
encrypt
));

String msg =
Expand Down Expand Up @@ -2733,6 +2753,7 @@ public GridCloseableIterator<CacheDataRow> partitionRowIterator(String snpName,
* @param withMetaStorage {@code true} if all metastorage data must be also included into snapshot.
* @param dump {@code true} if cache group dump must be created.
* @param compress If {@code true} then compress partition files.
* @param encrypt If {@code true} then content of dump encrypted.
* @param snpSndr Factory which produces snapshot receiver instance.
* @return Snapshot operation task which should be registered on checkpoint to run.
*/
Expand All @@ -2745,6 +2766,7 @@ AbstractSnapshotFutureTask<?> registerSnapshotTask(
boolean withMetaStorage,
boolean dump,
boolean compress,
boolean encrypt,
SnapshotSender snpSndr
) {
AbstractSnapshotFutureTask<?> task = registerTask(snpName, dump
Expand All @@ -2757,7 +2779,8 @@ AbstractSnapshotFutureTask<?> registerSnapshotTask(
transferRateLimiter,
snpSndr,
parts,
compress
compress,
encrypt
)
: new SnapshotFutureTask(cctx, srcNodeId, requestId, snpName, tmpWorkDir, ioFactory, snpSndr, parts, withMetaStorage, locBuff));

Expand Down Expand Up @@ -4681,6 +4704,9 @@ private static class CreateSnapshotCallable implements IgniteCallable<Void> {
/** If {@code true} then compress partition files. */
private final boolean comprParts;

/** If {@code true} then content of dump encrypted. */
private final boolean encrypt;

/** Auto-injected grid instance. */
@IgniteInstanceResource
private transient IgniteEx ignite;
Expand All @@ -4692,21 +4718,24 @@ private static class CreateSnapshotCallable implements IgniteCallable<Void> {
* @param onlyPrimary If {@code true} then only copy of primary partitions will be created.
* @param dump If {@code true} then cache dump must be created.
* @param comprParts If {@code true} then compress partition files.
* @param encrypt If {@code true} then content of dump encrypted.
*/
public CreateSnapshotCallable(
String snpName,
@Nullable Collection<String> cacheGroupNames,
boolean incremental,
boolean onlyPrimary,
boolean dump,
boolean comprParts
boolean comprParts,
boolean encrypt
) {
this.snpName = snpName;
this.cacheGroupNames = cacheGroupNames;
this.incremental = incremental;
this.onlyPrimary = onlyPrimary;
this.dump = dump;
this.comprParts = comprParts;
this.encrypt = encrypt;
}

/** {@inheritDoc} */
Expand All @@ -4721,7 +4750,8 @@ public CreateSnapshotCallable(
false,
onlyPrimary,
dump,
comprParts
comprParts,
encrypt
).get();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ public class SnapshotMetadata implements Serializable {
/** If {@code true} cache group dump stored. */
private boolean dump;

/** Encryption key. */
private @Nullable byte[] encKey;

/**
* @param rqId Unique request id.
* @param snpName Snapshot name.
Expand All @@ -124,6 +127,7 @@ public class SnapshotMetadata implements Serializable {
* @param masterKeyDigest Master key digest for encrypted caches.
* @param onlyPrimary If {@code true} snapshot only primary copies of partitions.
* @param dump If {@code true} cache group dump stored.
* @param encKey Encryption key. For dumps, only.
*/
public SnapshotMetadata(
UUID rqId,
Expand All @@ -139,7 +143,8 @@ public SnapshotMetadata(
@Nullable WALPointer snpRecPtr,
@Nullable byte[] masterKeyDigest,
boolean onlyPrimary,
boolean dump
boolean dump,
@Nullable byte[] encKey
) {
this.rqId = rqId;
this.snpName = snpName;
Expand All @@ -153,6 +158,7 @@ public SnapshotMetadata(
this.masterKeyDigest = masterKeyDigest;
this.onlyPrimary = onlyPrimary;
this.dump = dump;
this.encKey = encKey;

if (!F.isEmpty(compGrpIds)) {
hasComprGrps = true;
Expand Down Expand Up @@ -332,6 +338,11 @@ public byte[] masterKeyDigest() {
return masterKeyDigest;
}

/** @return Encryption key. */
public byte[] encryptionKey() {
return encKey;
}

/**
* @param warnings Snapshot creation warnings.
*/
Expand Down Expand Up @@ -364,6 +375,7 @@ public List<String> warnings() {
Objects.equals(grpIds, meta.grpIds) &&
Objects.equals(bltNodes, meta.bltNodes) &&
Arrays.equals(masterKeyDigest, meta.masterKeyDigest) &&
Arrays.equals(encKey, meta.encKey) &&
Objects.equals(warnings, meta.warnings) &&
Objects.equals(hasComprGrps, meta.hasComprGrps) &&
Objects.equals(comprGrpIds, meta.comprGrpIds) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public class SnapshotOperationRequest implements Serializable {
/** If {@code true} then compress partition files. */
private final boolean compress;

/** If {@code true} then content of dump encrypted. */
private final boolean encrypt;

/**
* @param reqId Request ID.
* @param opNodeId Operational node ID.
Expand All @@ -109,6 +112,7 @@ public class SnapshotOperationRequest implements Serializable {
* @param onlyPrimary If {@code true} snapshot only primary copies of partitions.
* @param dump If {@code true} then create dump.
* @param compress If {@code true} then compress partition files.
* @param encrypt If {@code true} then content of dump encrypted.
*/
public SnapshotOperationRequest(
UUID reqId,
Expand All @@ -121,7 +125,8 @@ public SnapshotOperationRequest(
int incIdx,
boolean onlyPrimary,
boolean dump,
boolean compress
boolean compress,
boolean encrypt
) {
this.reqId = reqId;
this.opNodeId = opNodeId;
Expand All @@ -134,6 +139,7 @@ public SnapshotOperationRequest(
this.onlyPrimary = onlyPrimary;
this.dump = dump;
this.compress = compress;
this.encrypt = encrypt;
startTime = U.currentTimeMillis();
}

Expand Down Expand Up @@ -218,6 +224,11 @@ public boolean compress() {
return compress;
}

/** @return If {@code true} then content of dump encrypted. */
public boolean encrypt() {
return encrypt;
}

/** @return Start time. */
public long startTime() {
return startTime;
Expand Down
Loading

0 comments on commit 668939a

Please sign in to comment.