Skip to content

Core, Hive: source encryption keys from table metadata to prevent key… - #17404

Draft
smaheshwar-pltr wants to merge 1 commit into
apache:mainfrom
smaheshwar-pltr:sm/enc-metadata-sourced-manager
Draft

Core, Hive: source encryption keys from table metadata to prevent key…#17404
smaheshwar-pltr wants to merge 1 commit into
apache:mainfrom
smaheshwar-pltr:sm/enc-metadata-sourced-manager

Conversation

@smaheshwar-pltr

Copy link
Copy Markdown
Contributor

… loss under concurrent commits

A single Table (and its shared TableOperations + EncryptionManager, e.g. via a caching catalog) can be used concurrently. On encrypted tables the mutable, shared StandardEncryptionManager could drop a snapshot's manifest-list key from encryption-keys, leaving a committed snapshot undecryptable.

Make encryption keys live only in table metadata and the manager immutable and metadata-sourced:

  • StandardEncryptionManager holds an immutable key snapshot. Minting a manifest-list key is a pure operation (mintManifestListKey) that returns the new key(s) instead of storing them; removes the mutating key methods.
  • SnapshotProducer captures the keys minted while writing the manifest list and persists them into the metadata it commits.
  • HiveTableOperations builds the manager on demand from a metadata's keys (memoized by metadata identity); drops the doCommit/doRefresh key merges and the mutable manager/key fields. temp(uncommittedMetadata) sources keys from the uncommitted metadata, and BaseTransaction re-points temp ops after a rebase, so staged operations resolve earlier ops' (and concurrently committed) keys.

The immutable manager serializes to Spark executors with no concurrent-mutation hazard.

… loss under concurrent commits

A single Table (and its shared TableOperations + EncryptionManager, e.g. via a
caching catalog) can be used concurrently. On encrypted tables the mutable,
shared StandardEncryptionManager could drop a snapshot's manifest-list key from
encryption-keys, leaving a committed snapshot undecryptable.

Make encryption keys live only in table metadata and the manager immutable and
metadata-sourced:

- StandardEncryptionManager holds an immutable key snapshot. Minting a
  manifest-list key is a pure operation (mintManifestListKey) that returns the
  new key(s) instead of storing them; removes the mutating key methods.
- SnapshotProducer captures the keys minted while writing the manifest list and
  persists them into the metadata it commits.
- HiveTableOperations builds the manager on demand from a metadata's keys
  (memoized by metadata identity); drops the doCommit/doRefresh key merges and
  the mutable manager/key fields. temp(uncommittedMetadata) sources keys from
  the uncommitted metadata, and BaseTransaction re-points temp ops after a
  rebase, so staged operations resolve earlier ops' (and concurrently committed)
  keys.

The immutable manager serializes to Spark executors with no concurrent-mutation
hazard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Override
public Snapshot apply() {
refresh();
this.applyManifestListKeys = null;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why

Comment on lines +356 to +357
// Immutable so a built instance's key set is stable, which HiveTableOperations relies on to
// memoize its metadata-sourced encryption manager by metadata identity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? do we rlly need this? surely tablemetadata treats like immutable? wanna avoid large copy (per-snapshot keys)

this.testTimeShift = 0;

this.encryptionKeys = SerializableMap.copyOf(Maps.newLinkedHashMap());
Map<String, EncryptedKey> keyMap = Maps.newLinkedHashMap();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not immutable map builder

}

private SecureRandom workerRNG() {
private synchronized SecureRandom workerRNG() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why

String keyEncryptionKeyTimestamp =
encryptionKeys.get(keyEncryptionKeyID).properties().get(KEY_TIMESTAMP);
/**
* Mints a manifest-list key for the given key metadata without storing it. The returned {@link

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this "mint" wording throughout? is it standard are u sure? i don't love its sound

* Mints a manifest-list key for the given key metadata without storing it. The returned {@link
* MintedKeys} must be persisted into table metadata by the caller to stay decryptable.
*/
public MintedKeys mintManifestListKey(NativeEncryptionKeyMetadata keyMetadata) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this have to be on this class? feels a bit weird and confusing to me to have this method here. is there a better design?

return ByteBuffer.wrap(newKey);
}

/** The keys minted by a single {@link #mintManifestListKey} call, for the caller to persist. */

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, can't help but feel design could be better here

}

@Test
public void testConcurrentMintingIsSafeAndDoesNotMutateKeys() throws InterruptedException {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? why would it ever mint keys?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test reads silly to me wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we not be using standard testing patterns on the SEM to test meaningfully

@Override
public FileIO io() {
// Avoid current() (which may refresh) on the unencrypted fast path.
return tableKeyId == null ? fileIO : io(current());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u sure it's fine to call current() in io / encryption? just feels like design smell here

Comment on lines +220 to +221
// Use plain fileIO, not io(): metadata isn't envelope-encrypted and io() would re-enter
// refresh.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm don't love this


tableKeyId = tableKeyIdFromHMS;
encryptionDekLength =
// Keys aren't cached here; encryption() rebuilds the manager from metadata on demand.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this comment give us

Comment on lines +246 to +247
// Encryption keys are already persisted into the metadata by SnapshotProducer.
TableMetadata tableMetadata = metadata;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is silly both can be tiched

Comment on lines +478 to +479
// Source keys from the uncommitted metadata so staged transaction operations can read
// snapshots produced by earlier operations before the transaction commits.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe mention encryption here. also, justify encryptionPropsFromMetadata which feels mutating-y above

encryptedKeys, encryptionProperties, keyManagementClient);
} else {
return PlaintextEncryptionManager.instance();
synchronized (managerCacheLock) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u sure we need locking here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember smaheshwar-pltr#10 PR description where there's no sync in table operations but that's fine. also think abt how that pr desc applies to this pr in general

}

@TestTemplate
public void testSharedTableTransactionInterleavedWithDirectCommit() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes in this file read weird to me, shouldn't this be the tests in smaheshwar-pltr#14 smaheshwar-pltr#15 instead? i feel like those tests are very useful, maybe this new one is too or the same as one of the prev one. but concurrent append test w encryption is almost essential to me

Comment on lines +168 to +170
// A single shared Table (and its EncryptionManager) drives both a staged transaction and a
// direct commit. With a mutable shared manager, the direct commit's key could be dropped from
// metadata, leaving its snapshot undecryptable. The metadata-sourced manager keeps every key.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slop


transaction.commitTransaction();

// Reading forces decryption of every snapshot's manifest list, including the direct commit's.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just completely wrong. reading only reads the current manifest list. this is unacceptable correctness bug in your code. i've lost trust in your iteration process so you must now review everything fact checking everything. that's what happens when i catch a huge oversight like this. not acceptable at all

}

/**
* Creates a manager whose immutable key set is exactly {@code keys}, as if sourced from metadata.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"as if sourced from metadata." is silly

@Override
public EncryptionManager encryption() {
return encryptionManager;
// Metadata-sourced: keys live in metadata, persisted by SnapshotProducer at commit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Comment on lines +2680 to +2681
List<EncryptedKey> keys = current() == null ? List.of() : current().encryptionKeys();
return EncryptionTestHelpers.createEncryptionManager(keys);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional of nullable + or else -> one line?

Comment on lines +2004 to +2005
// Reading the staged snapshot's manifest list decrypts it, proving its key was persisted into
// metadata even though the snapshot is not a branch head.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slop comment, improve it.

}

/** The keys minted by a single {@link #mintManifestListKey} call, for the caller to persist. */
public static class MintedKeys {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the verboseness in these classes btw? we don't have records available

Comment on lines +448 to +449
// Re-point temp ops at the refreshed metadata so a metadata-sourced encryption manager can
// resolve keys for the concurrently-committed snapshots the refresh pulled in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't like this wording, too AI

Comment on lines +40 to +45
/**
* An immutable {@link EncryptionManager} for standard (envelope) encryption whose keys are sourced
* from table metadata. Minting a manifest-list key is a pure operation ({@link
* #mintManifestListKey}) that returns the new key(s) for the caller to persist into metadata rather
* than storing them, so instances are safe to share across concurrent commits and to serialize.
*/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slop

Comment thread .palantir/revapi.yml
Comment on lines +567 to +570
- code: "java.method.removed"
old: "method java.lang.String org.apache.iceberg.encryption.StandardEncryptionManager::addManifestListKeyMetadata(org.apache.iceberg.encryption.NativeEncryptionKeyMetadata)"
justification: "Replaced by pure StandardEncryptionManager.mintManifestListKey;\
\ the immutable, metadata-sourced manager no longer stores minted keys"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is significant. what should we actually do here. i didn't realise StandardEncryptionManager was public so this whole PR might be infeasible....

can u look at github history to work out what the standard procedure is for this stuff? do we need to go through deprecation or cycle or are we just screwed here given we're changing a public class? can u multiple precedences for this with for / against?

i'm unconvinced we can "just do this"


private ManifestFile writeAndReadEncryptedManifestList(EncryptionManager em) throws IOException {
/**
* Writes an encrypted manifest list with a manager built from {@code metadataKeys}, persists the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've not audited these tests but having seen ur other tests, i'm highly skeptical your tests in this PR are good. please do an audit for software quality with good-testing-patterns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant