AcraRotate

Why rotate keys

Acra uses a storage keypair to encrypt and decrypt data. When using Zones, AcraWriter uses zoneid_zone.pub public key to encrypt AcraStruct and AcraServer/AcraTranslator use zoneid_zone private key to decrypt AcraStruct.

These keys support rotation process. This makes it possible to change the keys periodically or in response to a potential leak or compromise. We recommend running AcraRotate (at least) once in a while or if you suspect that any parts of the system could have been compromised.

What is AcraRotate

AcraRotate is an easy-to-use utility that generates a new Zone keypair (zoneid_zone.pub and zoneid_zone) for a particular ZoneId and re-encrypts the corresponding AcraStructs with new keys. ZoneId remains the same.

AcraRotate doesn't affect the ACRA_MASTER_KEY or the storage keypair used without Zones (clientid_storage.pub/clientid_storage keys).

AcraRotate rotates only the Zone storage keys and only affects the AcraStructs encrypted with Zones.

Note: AcraRotate currently over-writes the Zone keys completely, supporting only full rotation strategy (partial rotation is coming soon).

AcraRotate works both with AcraStructs stored in database cells (MySQL or PostgreSQL) or in files.

WARNING: For security reasons, we advise you to perform the following procedures on AcraServer/AcraTranslator exclusively!

Building the AcraRotate utility

Installing:

go get github.com/cossacklabs/acra/cmd/acra-rotate

For acra-rotate to work, the key folder has to have the proper permissions (as originally set by acra_genkeys):

  • folder 700,
  • private keys 600.

Before rotating the keys

Make sure that you run AcraRotate only on AcraServer/AcraTranslator. Since AcraRotate has access to decryption keys and actually decrypts the data, it should only run in a trusted environment.

1. Use dry-run

First, use AcraRotate in the "dry-run" mode (available since 0.84.0). In the "dry-run" mode, AcraRotate doesn't rotate keys: it fetches AcraStructs (from files or database), decrypts, rotates in-memory keys, encrypts data with new public keys and prints the resulting JSON with new public keys without actually saving the rotated keys and AcraStructs.

Warning! We strongly advise you to use the "dry-run" first. Key rotation might be tricky so we want users to make sure that AcraRotate has all the required permissions and accesses before actually re-encrypting the data.

2. Backup keys and encrypted data

Backup the keys and the encrypted data. AcraRotate over-writes the existing public and private keys, so AcraServer/AcraTranslator won't be able to decrypt the data encrypted with an old public key. Depending on your backups strategy and security policies, we advise creating a backup of the keys and the encrypted data every time before running AcraRotate.

If AcraRotate is interrupted or finishes with an error (i.e. no access to key files), the private key will be over-written, but the data might be rotated only partially. In this case, restore the keys and the data from backup and run AcraRotate again.

We won't tire of saying this: create a backup of the keys and the data!

Configuration parameters

AcraRotate has the following configuration options:

--config_file
    path to config
--db_connection_string
    Connection string to db
--dry-run
    perform rotation without saving rotated AcraStructs and keys
--dump_config
    dump config
--file_map_config
    Path to file with map of <ZoneId>: <FilePaths> in json format {"zone_id1": ["filepath1", "filepath2"], "zone_id2": ["filepath1", "filepath2"]}
--generate_markdown_args_table
    Generate with yaml config markdown text file with descriptions of all args
--keys_dir
    Folder from which the keys will be loaded (default ".acrakeys")
--mysql_enable
    Handle MySQL connections
--postgresql_enable
    Handle Postgresql connections
--sql_select
    Select query with ? as placeholders where last columns in result must be ClientId/ZoneId and AcraStruct. Other columns will be passed into insert/update query into placeholders
--sql_update
    Insert/Update query with ? as placeholder where into first will be placed rotated AcraStruct

Rotate AcraStruct from files

If AcraStructs are stored as files at a KV-storage/file storage, it's possible to run AcraRotate to re-encrypt the files. To do this, run AcraRotate in AcraTranslator container.

The process is as follows:

1. Backup the keys and the encrypted data as described above.

2. Generate a json config file that contains ZoneId as key and a list with paths to the encrypted files with that ZoneId:

{
   "<ZONE ID 1>": ["/path/to/file/acrastruct1", "/path/to/file/acrastruct2", "/path/to/file/acrastruct3"],
   "<ZONE ID 2>": ["/path/to/file/acrastruct1", "/path/to/file/acrastruct2", "/path/to/file/acrastruct3"]
}

3. Run rotation, setting path to key directory and json config file:

$GOPATH/bin/acra-rotate --keys_dir=/path/to/zone-private-keys --file_map_config=/path/to/config.json

AcraRotate utility takes encrypted data from config.json file, decrypts data, rotates keys, and encrypts data with new keys.

4. The result of processing is a json file with encrypted files, Zone Ids and Zone public keys with the following structure:

{ "<ZONE ID 1>": { 
    "new_public_key": "<ZONE PUBLIC KEY 1>",
    "file_paths": ["/path/to/encrypted/file/acrastruct1", "/path/to/encrypted/file/acrastruct2"]
   },
  "<ZONE ID 2>": { 
    "new_public_key": "<ZONE PUBLIC KEY 2>",
    "file_paths": ["/path/to/encrypted/file/acrastruct3", "/path/to/encrypted/file/acrastruct4"]
   }
}

An example output of such json file:

{
    "DDDDDDDDFSckOyJqVmENXawn": {
        "new_public_key": "VUVDMgAAAC1Vq8RnA4Q1BJnUye29lkj0rbSImHiKRPzzPxp1use2BGPYCug+",
        "file_paths": [
            "/tmp/tmpchz_kgy5/DDDDDDDDFSckOyJqVmENXawn_0.acrastruct",
            "/tmp/tmpchz_kgy5/DDDDDDDDFSckOyJqVmENXawn_1.acrastruct",
            "/tmp/tmpchz_kgy5/DDDDDDDDFSckOyJqVmENXawn_2.acrastruct"
        ]
    },
    "DDDDDDDDNWuqISbEdXtokBfu": {
        "new_public_key": "VUVDMgAAAC10hAftA3sOEp9qhe5h0yWugZb1DHEpj0BUK+5WE1/h9FDvKEsO",
        "file_paths": [
            "/tmp/tmpchz_kgy5/DDDDDDDDNWuqISbEdXtokBfu_0.acrastruct"
        ]
    },
    "DDDDDDDDUIUNdhWUSIXlTczw": {
        "new_public_key": "VUVDMgAAAC0hvsSPA78cxoxHOeEVZtwJT6bhK1SWNLj0J+GjRD7j5kZwb8Be",
        "file_paths": [
            "/tmp/tmpchz_kgy5/DDDDDDDDUIUNdhWUSIXlTczw_0.acrastruct",
            "/tmp/tmpchz_kgy5/DDDDDDDDUIUNdhWUSIXlTczw_1.acrastruct"
        ]
    }
}

5. Propagate the updated AcraStructs into a file storage and Zone public keys to the client applications/AcraWriter.

6. If AcraTranslator uses key cache (launched with the option keystore_cache_size > 0), reload the keystore to use the new keys by stopping and launching the AcraTranslator again.

Rotate AcraStruct from database

If AcraStructs are stored as database cells, you should run AcraRotate with SQL statements indicating how to fetch and put back the data. Run AcraRotate in an AcraServer container.

1. Backup the keys and the encrypted data as noted above.

2. Set the SQL queries to fetch and update the data (see examples below).

  • sql_select – to fetch the data from the database where the last 2 columns (other columns will be passed to "update query") in the result are ZoneId and AcraStruct.
  • sql_update – to update/insert data using at least one placeholder for the rotated AcraStruct. All the other placeholders can contain other data returned by sql_select in the same order.

3. Set the database connection string in the following formats:

PostgreSQL:

"postgres://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<DATABASE>?<param1>=<param1-value>"

(format used in github.com/lib/pq)

MySQL:

"[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]"

(format used in github.com/go-sql-driver/mysql)

4. Run rotation: AcraRotate connects to the database, fetches the data, decrypts the data, rotates the keys, encrypts the data with new keys, and updates the database with the new data.

PostgreSQL:

$GOPATH/bin/acra-rotate --keys_dir=/path/to/zone-private-keys --postgresql_enable --sql_select="select id, data from Test" --sql_update="insert into Test (data, id) values ($1, $2);" --connection_string="postgres://test:test@127.0.0.1:5432/test"

MySQL:

$GOPATH/bin/acra-rotate --keys_dir=/path/to/zone-private-keys --mysql_enable --sql_select="select id, data from Test" --sql_update="insert into Test values (?, ?);" --connection_string="test:test@tcp(127.0.0.1:3306)/test"

AcraRotate returns a list of ZoneIds and zone public keys:

{
    "DDDDDDDDDROGqDEAapfPhSrD": {
        "new_public_key": "VUVDMgAAAC2S4KHmAmQrS33ij6nUcSEZo4HCh/Cw7zkoqtdofTPjAhUeNSjS"
    },
    "DDDDDDDDLJPIbFYZuqCAgUiv": {
        "new_public_key": "VUVDMgAAAC3mlU5YArGGhn15qnw9A+a/rHt3M/tPZ/S5OCQB1GgzUn+oJc3o"
    },
    "DDDDDDDDNbxERTXcVVJSmTcx": {
        "new_public_key": "VUVDMgAAAC1bbs0CA7GHfwFkIkoQm/dWlm92g26C7NEOR/LWsjk8OSEBTNQ7"
    }
}

5. Propagate the zone public keys to the client applications/AcraWriter.

6. If AcraServer uses the key cache (was launched with option keystore_cache_size > 0), then reload the keystore to use the new keys by stopping and launching the AcraServer again.

7. For a manual check: use AcraConnector's host:port to see the decrypted data and the database's host:port to see the encrypted data.

Building SQL query

Imagine the following table:

create table Test (
 id INTEGER
 superuser BOOLEAN
 zone_id BINARY
 data BINARY
)

Let's say, you want to rotate all the data for the superusers (superuser field is TRUE):

  1. Build queries to update by id:

    • sql_select: select id, zone_id, data from Test where superuser=True
    • sql_update: update Test set data=$1 where id=$2
  2. Build queries to update by zone_id:

    • sql_select: select zone_id, zone_id, data from Test where superuser=True
    • sql_update: update Test set data=$1 where zone_id=$2

    We fetch zone_id twice to pass it as placeholder value to sql_update query.

  3. Insert the rotated data into another table:

create table TestDataBackup (
 id INTEGER
 data BINARY
)
  • sql_select: select id, data from Test
  • sql_update: insert into Test values ($2, $1); – we place the rotated AcraStruct as placeholder $1 and set it as the second ($2) values value.

Or we can just declare another order of columns in a query:

  • sql_update: insert into Test (data, ID) values ($1, $2); – we declare that the order for insertion is (data, id) instead of the order declared upon creation of the table.

Value placeholders in SQL queries

You should use the following placeholder values:

PostgreSQL: $n, $n+1, $n+2
insert into table values($1, $2, $3)

MySQL: ?, ?, ?
insert into table value(?, ?, ?)

After rotating keys

After a successful key rotation, it's important to propagate a new Zone public key (zoneid_zone.pub) to all the client applications/AcraWriter services.

Examples

A Python example for rotating AcraStructs in MySQL/PostgreSQL databases and files can be found in tests/test.py.