From: Antonin Houska Date: Fri, 5 Jul 2019 16:24:02 +0200 Subject: [PATCH 17/17] User and development documentation. --- doc/src/sgml/config.sgml | 14 ++ doc/src/sgml/encryption.sgml | 95 ++++++++++++++ doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/installation.sgml | 14 +- doc/src/sgml/postgres.sgml | 1 + doc/src/sgml/ref/allfiles.sgml | 1 + doc/src/sgml/ref/initdb.sgml | 11 ++ doc/src/sgml/ref/pg_checksums.sgml | 13 +- doc/src/sgml/ref/pg_ctl-ref.sgml | 11 ++ doc/src/sgml/ref/pg_keytool.sgml | 121 +++++++++++++++++ doc/src/sgml/ref/pg_resetwal.sgml | 11 ++ doc/src/sgml/ref/pg_rewind.sgml | 11 ++ doc/src/sgml/ref/pg_waldump.sgml | 11 ++ doc/src/sgml/ref/pgupgrade.sgml | 31 +++++ doc/src/sgml/reference.sgml | 1 + src/backend/storage/file/README.encryption | 202 +++++++++++++++++++++++++++++ 16 files changed, 542 insertions(+), 7 deletions(-) create mode 100644 doc/src/sgml/encryption.sgml create mode 100644 doc/src/sgml/ref/pg_keytool.sgml create mode 100644 src/backend/storage/file/README.encryption diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index c91e3e1550..5d64736a03 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -9004,6 +9004,20 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir' + + data_encryption (boolean) + + data_encryption configuration parameter + + + + + Reports whether data encryption is enabled for this cluster. + See for more information. + + + + data_directory_mode (integer) diff --git a/doc/src/sgml/encryption.sgml b/doc/src/sgml/encryption.sgml new file mode 100644 index 0000000000..5913883bc0 --- /dev/null +++ b/doc/src/sgml/encryption.sgml @@ -0,0 +1,95 @@ + + + + Transparent Cluster Encryption + + + Cluster encryption can be used if DBA cannot or does not + want to rely on the filesystem in terms of data confidentiality. If this + feature is enabled, PostgreSQL encrypts data + (both relations and write-ahead-log) when writing it to disk, and decrypts + when reading it. The encryption is transparent, so applications see no + difference between encrypted and unencrypted cluster. + + + + To create encrypted cluster, use the option to pass the + utility path to the command that will retrieve + the encryption key. For example: + +$ initdb -D /usr/local/pgsql/data -K /usr/local/pgsql/fetch_key_cmd + + Here /usr/local/pgsql/fetch_key_cmd is an executable + file that writes the encryption key to its standard output and returns + zero. The simplest command looks like: + + +echo <your key> + + + + + The encryption key is expected in hexadecimal format, two characters + (hexadecimal digits) per + byte. Since PostgreSQL currently uses key + of length 32 bytes (256 bits), the expected length of the key string + is 64 characters. For example: + + 882fb7c12e80280fd664c69d2d636913e86c381ba487c82f77653c0fac8ffc69 + + + + + If the DBA wants to enter password, he can use + utility to derive ecryption key from + it. For example, if the option looks like in the + following example, the DBA will be asked for password interactively: + + +$ initdb -K 'read -sp "Cluster encryption password: " PGENCRPWD; echo $PGENCRPWD +| pg_keytool -D %D -w' -D data + + + + + + + As long as the application is aware of the data directory, you can + put %D into the command and the application will replace + it with the actual directory path. + + + + Likewise, the same command can be passed to in + order to start the cluster: + + +$ pg_ctl -K 'read -sp "Cluster encryption password: " PGENCRPWD; echo $PGENCRPWD +| pg_keytool -D %D -w' -D data start + + + + + + If encryption is enabled, must be + turned on, otherwise the server refuses to start. This is because the + encryption introduces dependencies between data within a page, and thus + server crash during disk write can result in more serious damage of the page + than it would do without encryption. The whole page needs to be retrieved + from WAL in such a case to ensure reliable recovery. + + + + Once the PostgreSQL server is running, client + applications should recognize no difference from an unencrypted cluster, + except that variable is set. + + + + Since WAL is encrypted, any replication solution based on log shipping + () assume that all standby servers are + encrypted using the same key as their primary server. On the other hand, + allows replication between encrypted + and unencrypted clusters, or between clusters encrypted with different keys. + + diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 3da2365ea9..d21dabf03b 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -48,6 +48,7 @@ + diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index 449386243b..a2daf33e6f 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -245,8 +245,9 @@ su - postgres You need OpenSSL, if you want to support - encrypted client connections. OpenSSL is - also required for random number generation on platforms that do not + on-disk data encryption or encrypted client + connections. OpenSSL is also required for + random number generation on platforms that do not have /dev/urandom (except Windows). The minimum version required is 0.9.8. @@ -835,10 +836,11 @@ su - postgres - Build with support for SSL (encrypted) - connections. This requires the OpenSSL - package to be installed. configure will check - for the required header files and libraries to make sure that + Build with support for on-disk data encryption + or SSL (encrypted) connections. This requires + the OpenSSL package to be + installed. configure will check for the + required header files and libraries to make sure that your OpenSSL installation is sufficient before proceeding. diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index 3e115f1c76..b0d35600b2 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -163,6 +163,7 @@ &wal; &logical-replication; &jit; + &encryption; ®ress; diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 8d91f3529e..a60b02e23f 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -206,6 +206,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml index 7fc3152c6d..ce19151521 100644 --- a/doc/src/sgml/ref/initdb.sgml +++ b/doc/src/sgml/ref/initdb.sgml @@ -209,6 +209,17 @@ PostgreSQL documentation + + + + + + Encrypt the cluster data using a key retrieved from the command + specified here. See for details. + + + + diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml index 33706d1d97..7a2cae5e57 100644 --- a/doc/src/sgml/ref/pg_checksums.sgml +++ b/doc/src/sgml/ref/pg_checksums.sgml @@ -106,7 +106,18 @@ PostgreSQL documentation Only validate checksums in the relation with filenode - filenode. + filenode. + + + + + + + + + + If the cluster is encrypted, run this command to retrieve the + encryption key. See for details. diff --git a/doc/src/sgml/ref/pg_ctl-ref.sgml b/doc/src/sgml/ref/pg_ctl-ref.sgml index e31275a04e..48966eb58e 100644 --- a/doc/src/sgml/ref/pg_ctl-ref.sgml +++ b/doc/src/sgml/ref/pg_ctl-ref.sgml @@ -293,6 +293,17 @@ PostgreSQL documentation + + + + + If the cluster is encrypted, run this command to retrieve the + encryption key. See for details. + + + + + diff --git a/doc/src/sgml/ref/pg_keytool.sgml b/doc/src/sgml/ref/pg_keytool.sgml new file mode 100644 index 0000000000..3c15910885 --- /dev/null +++ b/doc/src/sgml/ref/pg_keytool.sgml @@ -0,0 +1,121 @@ + + + + + pg_keysetup + + + + pg_keytool + 1 + Application + + + + pg_keytool + produce cluster encryption key, possibly deriving it from a + passphrase + + + + + pg_keysetup + option + + + + + Description + + pg_keytool reads encryption key or password from + the standard input. If it received password, it runs key + derivation function on it in order to derive the key. The + encryption key is written either to the standard output or sent to via + frontend/backend protocol to postmaster process which is being started up. + + + + One common use case is that pg_keytool is used + within encryption key command, see the examples in + . In this case it sends the key to the + standard output. + + + + The other use case is that PostgreSQL is + started other way than using , typically + via systemd or a custom script. In this + case pg_keytool can be used to send the key to + specified TCP/IP port or to an Unix socket, on which the server + listens. This is the same port or socket to which applications will + eventually connect, but when the encrypted cluster starts up, it only + accepts the key. + + + + + Options + + + + + + + + Specifies the directory where the database cluster is + stored. Different directory generally means that a different key will + be derived from the same password. + + + + + + + + + + Specifies the host name of the machine on which the server is + listening. If the value begins with a slash, it is used as the + directory for the Unix-domain socket. + + + + + + + + + Specifies the TCP port or the local Unix-domain socket file + extension on which the server is listening for + connections. Defaults to the value of the PGPORT + environment variable or, if not set, to the port specified at + compile time, usually 5432. + + + + + + + + + Send the key to PostgreSQL server rather + than to standard output. + + + + + + + + + Expect password on standard input rather than encryption key. + + + + + + + diff --git a/doc/src/sgml/ref/pg_resetwal.sgml b/doc/src/sgml/ref/pg_resetwal.sgml index 8a9e22d050..613d14e1cc 100644 --- a/doc/src/sgml/ref/pg_resetwal.sgml +++ b/doc/src/sgml/ref/pg_resetwal.sgml @@ -100,6 +100,17 @@ PostgreSQL documentation + + + + + If the cluster is encrypted, run this command to retrieve the + encryption key. See for details. + + + + + diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml index 4d91eeb0ff..0e4f79b652 100644 --- a/doc/src/sgml/ref/pg_rewind.sgml +++ b/doc/src/sgml/ref/pg_rewind.sgml @@ -140,6 +140,17 @@ PostgreSQL documentation + + + + + If the cluster is encrypted, run this command to retrieve the + encryption key. See for details. + + + + + diff --git a/doc/src/sgml/ref/pg_waldump.sgml b/doc/src/sgml/ref/pg_waldump.sgml index 329c10e430..ca1e4e1468 100644 --- a/doc/src/sgml/ref/pg_waldump.sgml +++ b/doc/src/sgml/ref/pg_waldump.sgml @@ -101,6 +101,17 @@ PostgreSQL documentation + + + + + If the cluster is encrypted, run this command to retrieve the + encryption key. See for details. + + + + + diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml index 82886760f1..0c04990d51 100644 --- a/doc/src/sgml/ref/pgupgrade.sgml +++ b/doc/src/sgml/ref/pgupgrade.sgml @@ -120,6 +120,37 @@ + + + + + If the clusters are encrypted, run this command to retrieve the + encryption key. This is the same command that was passed to + for cluster creation. For the upgrade to + be successful, both old and new clusters must use the same key. + Therefore, unless the command contains the %D + string, it does not matter which cluster the command comes from. + + + + + If you are using to derive the + encryption key (see ), + make sure the points to + the old cluster. If it pointed to the new + one then the new cluster would derive a different key from the same + password and thus would fail to start + the new cluster. + + + + + See for details on the command. + + + + + use hard links instead of copying files to the new diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index cef09dd38b..5eed489f27 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -279,6 +279,7 @@ &pgChecksums; &pgControldata; &pgCtl; + &pgKeytool; &pgResetwal; &pgRewind; &pgtestfsync; diff --git a/src/backend/storage/file/README.encryption b/src/backend/storage/file/README.encryption new file mode 100644 index 0000000000..5ffd587cf5 --- /dev/null +++ b/src/backend/storage/file/README.encryption @@ -0,0 +1,202 @@ +src/backend/storage/file/README.encryption + +Transparent Cluster Encryption +============================== + +When creating a new cluster (instance), user can choose to have his data +encrypted on disk. If this feature is active, data is encrypted before it's +written to disk and decrypted after it has been read, however the data is +unencrypted in memory (data-at-rest encryption). The following characteristics +should be considered by anyone who is interested in the feature: + + 1. The encryption is transparent from application's point of view. + + 2. A single key is used to encrypt the whole cluster. + +The full instance encryption feature helps to ensure data confidentiality, +especially when user cannot rely on confidentiality on filesystem level. On +the other hand, it does not ensure data integrity, i.e. it does not help to +detect whether an adversary wrote his cipher data to the disk. The block +cipher methods generally do not protect data integrity, and it'd probably be +hard anyway because we encrypt postgres data pages (typically of size 8 kB) +separate from each other. If the attacker only changes part of the page, this +can be detected if data checksums feature is enabled. And specifically for +XLOG, each XLOG record has its own checksum. + +Since the data is stored on a disk, we naturally base our approach on "Disk +encryption theory" [1]. For each kind of file we use the AES cipher in the +appropriate mode of operation. The AES cipher itself encrypts / decrypts +individual blocks ("encryption blocks") of 16 bytes (128 bits), while the mode +of operation defines the rules how to apply the cipher to data which spans +multiple encryption blocks. + +Relations +--------- + +AES cipher in CBC mode [2] is used to encrypt relation files, one relation +block (page) at a time. The important characteristic of this mode is that, if +a single encryption block changes, the next run of the encryption produces an +encrypted page where not only that block is different, but also all the +following blocks. Furthermore, as the postgres page starts with LSN, even if +only the last encryption block of the page changes, the whole cipher text of +the page will be different after the next encryption. + +This propagation of changes across encryption blocks makes it harder for +adversary to deduce what happens in the database. Without the propagation he +might be able to see data changes with higher granularity. For example, if +only the beginning of index page changes, it gives some information on the +keys inserted. + +On the other hand, the same change propagation can cause problems if only a +part of a new version of encrypted page is written to disk (torn page +write). If such a page is decrypted during crash recovery, part of the plain +text can become garbage. This can affect even those parts of the page that +haven't been modified recently, and therefore XLOG records to fix those parts +are not available. Therefore, if the cluster encryption is enabled, +full_page_writes configuration variable must be set too, otherwise the server +refuses to start. With this setting, the damage described here can be fixed by +retrieving the full-page-image (FPI) from XLOG and applying it. + +However, even if full_page_writes is set, postgres can still avoid writing FPI +XLOG record if only hint bit(s) changed on a page and if wal_log_hints +configuration variable is off. The problem with encryption is that even a +single bit change is propagated to all the following encryption blocks during +the next encryption of the page. Again, if only part of the new cipher text is +written, the remaining portion of the page becomes garbage after decryption, +and we might not be able to fix it during recovery unless we have the FPI in +the XLOG. So if encryption is enabled, postgres behaves as if wal_log_hints +was always set. + +XLOG +---- + +The specific problem of XLOG is that record must not be changed once it has +been flushed to disk. However if we used a block cipher, and if a new XLOG +record started in the same encryption block in which the previous record ends, +that encryption block would become completely different after the next run of +encryption of the containing page. Torn write of such a block (e.g. if memory +page boundary crosses the encryption block) is likely to make decryption +produce garbage, which will also appear in the already-flushed record. + +Therefore we encrypt XLOG using a stream cipher, or rather block cipher in +stream mode of operation. Stream cipher uses XOR operation to combine a "key +stream" with the input stream, and it does not matter if the length of the +input stream is aligned to any value. In particular, the CTR mode [3] was +chosen because it allows for both read and write operations to be +parallelized. + +The XLOG is encrypted / decrypted one XLOG page (typically 8 kB) at a time, so +rather than a single stream, the encrypted XLOG is actually a sequence of +per-page streams. + +Since stream ciphers are susceptible to "reused key attack" [4], we must +ensure that the unused part of the last XLOG page (filled with zeroes) is +never encrypted. + +Temporary files +--------------- + +BufFileWrite() and BufFileRead() functions (see buffile.c) hide the encryption +/ decryption from caller. The encryption / decryption processes one buffer at +a time so that the buffers can be retrieved independent from each other. + +If the encryption is enabled, the following requirements need to be taken into +account: + +1. The file buffer cannot be positioned at arbitrary offset of the file. If +the encryption routine starts at some position of the file, decryption must +not start elsewhere because there's no way to determine which initialization +vector was used internally for the corresponding encryption blocks (of 16 +bytes) during encryption. It makes sense to position the buffer at file offset +that is whole multiple of buffer size. + +2. In general, the useful (written) data does not fill whole multiple of +encryption blocks, but we must write the whole blocks for decryption to +succeed. This implies that we need to fill the unused part of the last block +with zeroes and also remember the amount of useful bytes in the segment file. +(In fact we align the segment file size to file buffers instead of encryption +blocks, which probably makes the implementation a bit simpler.) + +Stream cipher might seem like a solution of the padding problem, but we cannot +use it here because parts of the temporary file can be rewritten. That would +expose the temporary file to "reused key attack" [4]. + +Auxiliary files +--------------- + +To store other kinds of data encrypted than the ones above, developers are +advised to use BufFileWriteTransient() and BufFileReadTransient() functions +(also located in buffile.c). These are especially useful if some data +structure should be written to an encrypted file and user does not want to +care whether the next write position is at encryption block boundary. + +Wherever the write starts, BufFileWriteTransient() first reads the whole +encryption block that contains the start positions, then decrypts it, adds the +new data to it, encrypts the whole block and writes it back to the +file. Likewise, BufFileReadTransient() ensures that only the whole encryption +blocks are read from disk and decrypted, whether the next read position is at +block boundary or not. + +Serialization of data changes during logical decoding (reorderbuffer.c) is the +typical use case for this API. + +Initialization vector (IV), encryption tweak +-------------------------------------------- + +Besides the input data and the key, both block and stream cipher used for the +postgres cluster encryption require an initialization vector. It should make +analysis of the encrypted data more difficult. Also according to [1], the same +data should be encrypted differently if located elsewhere on the disk. Term +"encryption tweak" is more common in the context of disk encryption. + +When encrypting relations, each encryption unit (i.e. page) has an unique +tweak, which consists of RelFileNode structure, buffer number and fork +number. As a consequence, we need to decrypt page using the existing tweak and +encrypt it with a new tweak if copying it from one relation to +another. Typically this happens when a new database is being created from +template database. + +To avoid "reencryption" during pg_upgrade, all the OIDs contained in the +RelFileNode structure (tablespace OID, database OID and relation file OID) +must be preserved. If pg_upgrade had to reencrypt files, it could not be used +with the --link option. + +XLOG encryption tweak consists of timeline, segment number and offset at which +the XLOG page starts in the segment. The "reencryption" takes place when XLOG +page is copied from one timeline to another, typicially at the end of +recovery. + +As for temporary files, PID of the owning backend, file / fileset number and +block number (where block is of the same size as a relation page) within the +file provide sufficient uniqueness, so we use these to generate the tweak. + +Auxiliary files can be closed and reopened by another backend, so there's no +PID strictly associated with them. Therefore we generate the tweak by hashing +the file path, and appending block number to the hash. + +Replication +----------- + +During streaming replication, the walsender process sends the XLOG encrypted +and walreceiver just writes it. Decryption is performed before the slave +cluster tries to apply the changes. That implies that the same encryption key +must be used both master and slave. If it should be possible someday to stream +the XLOG unencrypted. One implication is that pg_basebackup should then also +receive the relation data unencrypted. + +As for logical replication, the only change introduced by this feature is that +the XLOG has to be decrypted before the contained data changes can be +decoded. The data changes are transferred to the subscribing database / +cluster unencrypted, so both master and slave can use different encryption +keys. + +References +---------- + +[1] https://en.wikipedia.org/wiki/Disk_encryption_theory + +[2] https://en.wikipedia.org/wiki/Disk_encryption_theory#Cipher-block_chaining_(CBC) + +[3] https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR) + +[4] https://en.wikipedia.org/wiki/Stream_cipher_attacks#Reused_key_attack -- 2.13.7 --=-=-=--