My reading of the proposal is that it should work. I've thought about
this edge case a lot and have not come up with a better approach. This
is really a very small window, so definitely pick anything that is
simpler (to understand/and code) over other solutions. The description
is sort of high level now, if you want more feedback before submitting
a patch once you get deeper in the code and can post more detail to
the algorithm I will respond to whatever is posted.
I was sort of expecting old/new named properties in service.properties
vs. different versions of the file. Again if different files are easier
that is fine with me.
As usual with these tiny crash point recovery cases, reproducible
testing is a pain. I am ok with using the "sane" only debug flag
method used in few other places in the store code. The obvious
test points seem to be:
1) right before commit of reencryption (everything should be backed out
and db only accessible with old key)
2) right after commit but before service.properties is updated on disk
with new encryption info.
maybe others - basically make sure to test each new path you describe in
the recovery code described below.
Another good case might be doing the whole thing twice on the same db to
make sure we don't leave stuff around from the first time around. ie.
encrypt unencrypted-> then reencrypt with a new key (with various
crashes the second time).
/mikem
Suresh Thalamati (JIRA) wrote:
[ http://issues.apache.org/jira/browse/DERBY-1156?page=comments#action_12422000 ]
Suresh Thalamati commented on DERBY-1156:
-----------------------------------------
How to handle crash/recovery when switching to new encryption key ?
After re-encrypting all the containers in the database with a new
encryption key. A final switch has to be made for the database to use
the new encryption key to encrypt the transaction log and the data.
Problem is how to perform the switch in such a way that if there is crash,
system will work with the old encryption key or the new encryption key.
Next boot of the database should not require both the keys to recovery
from a crash.
Log is encrypted, so recovery has to know the correct encryption
key to decrypt the transaction log records. Note that even the
checkpoint log record in the transaction log is encrypted.
I think an algorithm using a flag in the log control file
(CHECK_POINT_WITH_NEW_KEY) tha is written on check-point and
another flag (derby.storage.reencryptionLog) in service.properties
to track that re-encryption in progress and also to remember the first
log file that contains the log with the new encryption key.
During the switch to new-encryption key , old copy of service.properties and
the verify.key files saved , so that incase of crash, system will revert back
to the old version of these files.
Incase of a crash during re-encryption ; On recovery reading from
the log that contains log records encrypted with new key
including the commit log record of re-encryption is avoided by
deleting the log file. Because the commit log record is written to
the last log file, re-encryption transaction will be rolled back bringing
the database to state it was before re-encryption started.
Persistent Values/files :
CHECK_POINT_WITH_NEW_KEY to the log.ctrl file
derby.storage.reencryptionLog property to the service.properties.
service.properties.old. verify.key.old to keep track of old version
of the encryption properties.
Following is the description of the re-encryption of the database algorithm:
Re-encryption is performed after the recovery during the RawStore boot:
1) Perform a check point, this will make sure old transaction log
will not be read after the re-encryption (Logged with OLD KEY)
2) Begin Transaction (Logged with OLD Key).
3) Re-encrypt all containers with the new key. (this action is
logged with OLD KEY).
5) Switch to a new Log File and set the encryption engine for the log
to be the one with the new encryption key.
(After this point the transaction log will be encrypted with
the New Encryption Key).
6) update the service.properties with the property
derby.storage.reencryptionLog = (the last log file number)
(this property will help in tracking that re-encryption in progress
and also to delete the last log file in-case of crash)
7) copy service.properties after excluding derby.storage.reencryptionLog property
to service.properties.old and and verify.key into verify.key.old
8) End Transaction (logged with NEW KEY)
9) update the service.properties with new encryption key/password information.
10) update the verify.key file with the new external key incase of external
encryption key.
11) Perform a checkpoint and also mark a flag that checkpoint is done
with a new key in the log.ctrl file. CHECK_POINT_WITH_NEW_KEY = true.
(IF WE REACHED THIS FAR MEANS RE-ENCRYPTION IS SUCCESSFUL).
Now perform the cleanup:
performCleanup ()
{
12) Update the derby.storage.reencryptionLog = 0 ( re-encryption complete ,
only cleanup remaining)
13) Cleanup copies of the container files encrypted with old encryption key.
14) Update the log.control file with CHECK_POINT_WITH_NEW_KEY = false.
15) Remove the service.properties.old and verify.key.old files.
16) Remove derby.storage.reencryptionLog property from the service.properties
file.
}
If all the steps above are successful, database is ready to use the new
encryption key.
On Recovery :
1)
String renEncryptionLogProp = find(derby.storage.ReEncryptionLog );
if( reEncryptioLogProp != null ) {
reEncyrptionLog = Long.valueof(renEncryptionLog);
} else {
// This may not re-encryption recovery case or it crashed
// before the derby.storage.ReEncryptionLog was set.
// database will continue to use the old key and recovery will
// rollback if there were any changed by re-encryption. No special
// handling required.
}
if ( reEncryptionLog > 0 ) {
// renecryption was in progress
if (CHECK_POINT_WITH_NEW_KEY == true)
{
// crashed after the checkpoint with the new key ,
// this is as good as re-encryption is complete.
1) call performCleanup();
2) verify the user entered key against the new verify.key and
proceed with recovery.
}else
{
// re-encryption was not complete, rollback and
// bring the database to the state it was before
// re-encryption started.
1) delete(Blow up) the log file(derby.storage.ReEncryptionLog)
where log records with new encryption key is written if
the file exists. (so that we don't see any log with new key.
2) revert to the the old versions of service.properties and
verify.key, this will also remove the
derby.storage.ReEncryptionLog property.
3) verify the user entered old key/password using the
verify.key/or the key in the service.properties. And then
proceed with recovery.
4) recovery will undo the partially completed re-encryption,
this will bring the containers back to versions of
the old encryption key.
}
} else
{
if (reEncryptionLog == 0)
{
// crash occurred during cleanup; re-encryption was complete.
call performcleanup();
}
}
If the above algorithm works, Other minor issues:
1) instead of having service.properties.old file name the specific properties
as new or old and set them accordingly.
2) instead of storing the log file to delete incase of crash in the
as a property (derby.storage.ReEncryptionLog) in the
service.properties. Store it in the log.ctrl file; There is only one LONG
spare remaining , not sure if worth using for this case.
I would really appreciate if some one can take a look at the above algorithm
and verify if it really works or if there are any suggestions solve in
different way, that will be great too.
Thanks
-suresh
allow the encrypting of an existing unencrypted db and allow the re-encrypting
of an existing encrypted db
----------------------------------------------------------------------------------------------------------
Key: DERBY-1156
URL: http://issues.apache.org/jira/browse/DERBY-1156
Project: Derby
Issue Type: Improvement
Components: Store
Affects Versions: 10.1.2.3
Reporter: Mike Matrigali
Assigned To: Suresh Thalamati
Priority: Minor
Fix For: 10.2.0.0
Attachments: encryptspec.html, reencrypt_1.diff, reencrypt_2.diff,
reencrypt_3.diff, reencrypt_4.diff
encrypted database to be re-encrypted with a new password.
Here are some ideas for an initial implementation.
The easiest way to do this is to make sure we have exclusive access to the
data and that no log is required in the new copy of the db. I want to avoid
the log as it also is encrypted. Here is my VERY high level plan:
1) Force exclusive access by putting all the work in the low level store,
offline boot method. We will do redo recovery as usual, but at the end
there will be an entry point to do the copy/encrypt operation.
copy/encrypt process:
0) The request to encrypt/re-encrypt the db will be handled with a new set
of url flags passed into store at boot time. The new flags will provide
the same inputs as the current encrypt flags. So at high level the
request will be "connect db old_encrypt_url_flags; new_encrypt_url_flags".
TODO - provide exact new flag syntax.
1) Open a transaction do all logged work to do the encryption. All logging
will be done with existing encryption.
2) Copy and encrypt every db file in the database. The target files will
be in the data directory. There will be a new suffix to track the new
files, similar to the current process used for handling drop table in
a transaction consistent manner without logging the entire table to the log.
Entire encrypted destination file is guaranteed synced to disk before
transaction commits. I don't think this part needs to be logged.
Files will be read from the cache using existing mechanism and written
directly into new encrypted files (new encrypted data does not end up in
the cache).
3) Switch encrypted files for old files. Do this under a new log operation
so the process can be correctly rolled back if the encrypt db operation
transaction fails. Rollback will do file at a time switches, no reading
of encrypted data is necessary.
4) log a "change encryption of db" log record, but do not update
system.properties with the change.
5) commit transaction.
6) update system.properties and sync changes.
7) TODO - need someway to handle crash between steps 5 and 6.
6) checkpoint all data, at this point guaranteed that there is no outstanding
transaction, so after checkpoint is done there is no need for the log.
ISSUES:
o there probably should be something that catches a request to encrypt to
whatever db was already encrypted with.