agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedFrom: Antonin Houska <ah@cybertec.at>
Subject: [PATCH 17/17] User and development documentation.
Date: Fri, 5 Jul 2019 16:24:02 +0200
---
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'
</listitem>
</varlistentry>
+ <varlistentry id="guc-data-encryption" xreflabel="data_encryption">
+ <term><varname>data_encryption</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>data_encryption</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Reports whether data encryption is enabled for this cluster.
+ See <xref linkend="encryption"/> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-data-directory-mode" xreflabel="data_directory_mode">
<term><varname>data_directory_mode</varname> (<type>integer</type>)
<indexterm>
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 @@
+<!-- doc/src/sgml/encryption.sgml -->
+
+<chapter id="encryption">
+ <title>Transparent Cluster Encryption</title>
+
+ <para>
+ Cluster encryption can be used if <acronym>DBA</acronym> cannot or does not
+ want to rely on the filesystem in terms of data confidentiality. If this
+ feature is enabled, <productname>PostgreSQL</productname> 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.
+ </para>
+
+ <para>
+ To create encrypted cluster, use the <option>-K</option> option to pass the
+ <xref linkend="app-initdb"/> utility path to the command that will retrieve
+ the encryption key. For example:
+<screen>
+<prompt>$</prompt> <userinput>initdb -D /usr/local/pgsql/data -K /usr/local/pgsql/fetch_key_cmd</userinput>
+</screen>
+ Here <filename>/usr/local/pgsql/fetch_key_cmd</filename> is an executable
+ file that writes the encryption key to its standard output and returns
+ zero. The simplest command looks like:
+
+<programlisting>
+echo <your key>
+</programlisting>
+ </para>
+
+ <para>
+ The encryption key is expected in hexadecimal format, two characters
+ (hexadecimal digits) per
+ byte. Since <productname>PostgreSQL</productname> currently uses key
+ of length 32 bytes (256 bits), the expected length of the key string
+ is 64 characters. For example:
+ <computeroutput>
+ 882fb7c12e80280fd664c69d2d636913e86c381ba487c82f77653c0fac8ffc69
+ </computeroutput>
+ </para>
+
+ <para>
+ If the DBA wants to enter password, he can use
+ <xref linkend="app-pg-keytool"/> utility to derive ecryption key from
+ it. For example, if the <option>-K</option> option looks like in the
+ following example, the DBA will be asked for password interactively:
+
+<screen>
+<prompt>$</prompt> <userinput>initdb -K 'read -sp "Cluster encryption password: " PGENCRPWD; echo $PGENCRPWD
+| pg_keytool -D %D -w' -D data
+</userinput>
+</screen>
+
+ </para>
+
+ <para>
+ As long as the application is aware of the data directory, you can
+ put <literal>%D</literal> into the command and the application will replace
+ it with the actual directory path.
+ </para>
+
+ <para>
+ Likewise, the same command can be passed to <xref linkend="app-pg-ctl"/> in
+ order to start the cluster:
+
+<screen>
+<prompt>$</prompt> <userinput>pg_ctl -K 'read -sp "Cluster encryption password: " PGENCRPWD; echo $PGENCRPWD
+| pg_keytool -D %D -w' -D data start
+</userinput>
+</screen>
+ </para>
+
+ <para>
+ If encryption is enabled, <xref linkend="guc-full-page-writes"/> 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.
+ </para>
+
+ <para>
+ Once the <productname>PostgreSQL</productname> server is running, client
+ applications should recognize no difference from an unencrypted cluster,
+ except that <xref linkend="guc-data-encryption"/> variable is set.
+ </para>
+
+ <para>
+ Since WAL is encrypted, any replication solution based on log shipping
+ (<xref linkend="warm-standby"/>) assume that all standby servers are
+ encrypted using the same key as their primary server. On the other hand,
+ <xref linkend="logical-replication"/> allows replication between encrypted
+ and unencrypted clusters, or between clusters encrypted with different keys.
+ </para>
+</chapter>
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 @@
<!ENTITY wal SYSTEM "wal.sgml">
<!ENTITY logical-replication SYSTEM "logical-replication.sgml">
<!ENTITY jit SYSTEM "jit.sgml">
+<!ENTITY encryption SYSTEM "encryption.sgml">
<!-- programmer's guide -->
<!ENTITY bgworker SYSTEM "bgworker.sgml">
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
<listitem>
<para>
You need <productname>OpenSSL</productname>, if you want to support
- encrypted client connections. <productname>OpenSSL</productname> is
- also required for random number generation on platforms that do not
+ on-disk data encryption or encrypted client
+ connections. <productname>OpenSSL</productname> is also required for
+ random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
version required is 0.9.8.
</para>
@@ -835,10 +836,11 @@ su - postgres
</term>
<listitem>
<para>
- Build with support for <acronym>SSL</acronym> (encrypted)
- connections. This requires the <productname>OpenSSL</productname>
- package to be installed. <filename>configure</filename> will check
- for the required header files and libraries to make sure that
+ Build with support for on-disk data encryption
+ or <acronym>SSL</acronym> (encrypted) connections. This requires
+ the <productname>OpenSSL</productname> package to be
+ installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
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;
</part>
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.
<!ENTITY pgDump SYSTEM "pg_dump.sgml">
<!ENTITY pgDumpall SYSTEM "pg_dumpall.sgml">
<!ENTITY pgIsready SYSTEM "pg_isready.sgml">
+<!ENTITY pgKeytool SYSTEM "pg_keytool.sgml">
<!ENTITY pgReceivewal SYSTEM "pg_receivewal.sgml">
<!ENTITY pgRecvlogical SYSTEM "pg_recvlogical.sgml">
<!ENTITY pgResetwal SYSTEM "pg_resetwal.sgml">
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
</listitem>
</varlistentry>
+ <varlistentry id="app-initdb-data-encr-cmd" xreflabel="data encryption">
+ <term><option>-K</option></term>
+ <term><option>--encryption-key-command=<replaceable class="parameter">command</replaceable></option></term>
+ <listitem>
+ <para>
+ Encrypt the cluster data using a key retrieved from the command
+ specified here. See <xref linkend="encryption"/> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-initdb-data-checksums" xreflabel="data checksums">
<term><option>-k</option></term>
<term><option>--data-checksums</option></term>
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
<listitem>
<para>
Only validate checksums in the relation with filenode
- <replaceable>filenode</replaceable>.
+ <replaceable>filenode</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-K</option></term>
+ <term><option>--encryption-key-command=<replaceable class="parameter">command</replaceable></option></term>
+ <listitem>
+ <para>
+ If the cluster is encrypted, run this command to retrieve the
+ encryption key. See <xref linkend="encryption"/> for details.
</para>
</listitem>
</varlistentry>
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
</varlistentry>
<varlistentry>
+ <term><option>-K</option></term>
+ <term><option>--encryption-key-command=<replaceable class="parameter">command</replaceable></option></term>
+ <listitem>
+ <para>
+ If the cluster is encrypted, run this command to retrieve the
+ encryption key. See <xref linkend="encryption"/> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-l <replaceable class="parameter">filename</replaceable></option></term>
<term><option>--log=<replaceable class="parameter">filename</replaceable></option></term>
<listitem>
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 @@
+<!--
+doc/src/sgml/ref/pg_keytool.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="app-pg-keytool">
+ <indexterm zone="app-pg-keytool">
+ <primary>pg_keysetup</primary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle><application>pg_keytool</application></refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo>Application</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>pg_keytool</refname>
+ <refpurpose>produce cluster encryption key, possibly deriving it from a
+ passphrase</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>pg_keysetup</command>
+ <arg rep="repeat"><replaceable class="parameter">option</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+ <para>
+ <application>pg_keytool</application> reads encryption key or password from
+ the standard input. If it received password, it runs <firstterm>key
+ derivation function</firstterm> 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.
+ </para>
+
+ <para>
+ One common use case is that <application>pg_keytool</application> is used
+ within encryption key command, see the examples in
+ <xref linkend="encryption"/>. In this case it sends the key to the
+ standard output.
+ </para>
+
+ <para>
+ The other use case is that <productname>PostgreSQL</productname> is
+ started other way than using <xref linkend="app-pg-ctl"/>, typically
+ via <firstterm>systemd</firstterm> or a custom script. In this
+ case <application>pg_keytool</application> 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.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-D <replaceable>directory</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the directory where the database cluster is
+ stored. Different directory generally means that a different key will
+ be derived from the same password.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-h <replaceable class="parameter">hostname</replaceable></option></term>
+ <term><option>--host=<replaceable class="parameter">hostname</replaceable></option></term>
+ <listitem>
+ <para>
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-p <replaceable class="parameter">port</replaceable></option></term>
+ <listitem>
+ <para>
+ 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 <envar>PGPORT</envar>
+ environment variable or, if not set, to the port specified at
+ compile time, usually 5432.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-s</option></term>
+ <listitem>
+ <para>
+ Send the key to <productname>PostgreSQL</productname> server rather
+ than to standard output.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-w</option></term>
+ <listitem>
+ <para>
+ Expect password on standard input rather than encryption key.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </refsect1>
+</refentry>
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
</varlistentry>
<varlistentry>
+ <term><option>-K</option></term>
+ <term><option>--encryption-key-command=<replaceable class="parameter">command</replaceable></option></term>
+ <listitem>
+ <para>
+ If the cluster is encrypted, run this command to retrieve the
+ encryption key. See <xref linkend="encryption"/> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-n</option></term>
<term><option>--dry-run</option></term>
<listitem>
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
</varlistentry>
<varlistentry>
+ <term><option>-K</option></term>
+ <term><option>--encryption-key-command=<replaceable class="parameter">command</replaceable></option></term>
+ <listitem>
+ <para>
+ If the cluster is encrypted, run this command to retrieve the
+ encryption key. See <xref linkend="encryption"/> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>--source-pgdata=<replaceable class="parameter">directory</replaceable></option></term>
<listitem>
<para>
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
</varlistentry>
<varlistentry>
+ <term><option>-K</option></term>
+ <term><option>--encryption-key-command=<replaceable class="parameter">command</replaceable></option></term>
+ <listitem>
+ <para>
+ If the cluster is encrypted, run this command to retrieve the
+ encryption key. See <xref linkend="encryption"/> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-n <replaceable>limit</replaceable></option></term>
<term><option>--limit=<replaceable>limit</replaceable></option></term>
<listitem>
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 @@
</varlistentry>
<varlistentry>
+ <term><option>-K</option></term>
+ <term><option>--encryption-key-command=<replaceable class="parameter">command</replaceable></option></term>
+ <listitem>
+ <para>
+ If the clusters are encrypted, run this command to retrieve the
+ encryption key. This is the same command that was passed to
+ <xref linkend="app-initdb"/> 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 <literal>%D</literal>
+ string, it does not matter which cluster the command comes from.
+ </para>
+
+ <note>
+ <para>
+ If you are using <xref linkend="app-pg-keytool"/> to derive the
+ encryption key (see <xref linkend="app-initdb-data-encr-cmd"/>),
+ make sure the <option>-D</option> points to
+ the <emphasis>old</emphasis> cluster. If it pointed to the new
+ one then the new cluster would derive a different key from the same
+ password and thus <xref linkend="pgupgrade"/> would fail to start
+ the new cluster.
+ </para>
+ </note>
+
+ <para>
+ See <xref linkend="encryption"/> for details on the command.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-k</option></term>
<term><option>--link</option></term>
<listitem><para>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
--=-=-=--
view thread (30+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: ah@cybertec.at
Subject: Re: [PATCH 17/17] User and development documentation.
In-Reply-To: <no-message-id-1881973@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox