TLS: add `{verify, verify_peer}` to enable verification
When the CouchDB custom (couch) distribution is enabled, OTP 24 and 25,
you will get a warning when using `remsh-tls` or `remsh -t`; but in OTP 26,
this is an error. Because the default value of the verify option is `verify_peer`
instead of `verify_none`. We need to provide the appropriate CA certificates to
pass the validation.
```bash
$ ./dev/remsh-tls
=WARNING REPORT==== 4-May-2023::12:43:28.893022 ===
Description: "Server authenticity is not verified since certificate path validation is not enabled"
Reason: "The option {verify, verify_peer} and one of the options 'cacertfile' or 'cacerts' are required to enable this."
```
```bash
$ ./dev/remsh-tls
Could not connect to "node1@127.0.0.1"
```
- Add `{verify, verify_peer}` to enable verification
- Add `certs` in `couch_dist` app to generate proper certificates
- Add `-t`/`--enable-tls` mode in ./dev/run to automatically generate
vm.args, certificates and configuration files
- Add `--no-tls <node>` option to specify node to use TCP only
- Add README and documentation
- Remove TLS certificate generation code from `configure`
Greate thanks to Robert Newson for figuring out how to generate the
correct certificates to pass verification!
Ref:
- https://www.erlang.org/doc/apps/ssl/ssl_distribution.html
- https://www.erlang.org/doc/man/ssl_app.html
- https://www.erlang.org/blog/otp-26-highlights/#ssl-safer-defaults
- https://github.com/rnewson/elixir-certs
diff --git a/.gitignore b/.gitignore
index eaa50a6..30aed77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,8 +30,6 @@
dev/devnode.*
dev/lib/
dev/logs/
-dev/erlserver.pem
-dev/couch_ssl_dist.conf
ebin/
erl_crash.dump
erln8.config
diff --git a/Makefile b/Makefile
index 183afd5..1615cac 100644
--- a/Makefile
+++ b/Makefile
@@ -476,7 +476,7 @@
@rm -f src/couch/priv/couchspawnkillable
@rm -f src/couch/priv/couch_js/config.h
@rm -f dev/*.beam dev/devnode.* dev/pbkdf2.pyc log/crash.log
- @rm -f dev/erlserver.pem dev/couch_ssl_dist.conf
+ @rm -f src/couch_dist/certs/out
ifeq ($(with_nouveau), 1)
@cd nouveau && ./gradlew clean
endif
diff --git a/configure b/configure
index 8b1e43d..f90bfce 100755
--- a/configure
+++ b/configure
@@ -63,31 +63,11 @@
--spidermonkey-version VSN specify the version of SpiderMonkey to use (defaults to $SM_VSN)
--skip-deps do not update erlang dependencies
--rebar=PATH use rebar by specified path (version >=2.6.0 && <3.0 required)
- --generate-tls-dev-cert generate a cert for TLS distribution (To enable TLS, change the vm.args file.)
--rebar3=PATH use rebar3 by specified path
--erlfmt=PATH use erlfmt by specified path
EOF
}
-# This is just an example to generate a certfile for TLS distribution.
-# This is not an endorsement of specific expiration limits, key sizes, or algorithms.
-generate_tls_dev_cert() {
- if [ ! -e "${rootdir}/dev/erlserver.pem" ]; then
- openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
- cat key.pem cert.pem > dev/erlserver.pem && rm key.pem cert.pem
- fi
-
- if [ ! -e "${rootdir}/dev/couch_ssl_dist.conf" ]; then
- cat > "${rootdir}/dev/couch_ssl_dist.conf" << EOF
-[{server,
- [{certfile, "${rootdir}/dev/erlserver.pem"},
- {secure_renegotiate, true}]},
- {client,
- [{secure_renegotiate, true}]}].
-EOF
- fi
-}
-
parse_opts() {
while :; do
case $1 in
@@ -221,13 +201,6 @@
exit 1
;;
- --generate-tls-dev-cert)
- echo "WARNING: To enable TLS distribution, don't forget to customize vm.args file."
- generate_tls_dev_cert
- shift
- continue
- ;;
-
--) # End of options
shift
break
diff --git a/dev/remsh-tls b/dev/remsh-tls
index 089db66..6ac4f1d 100755
--- a/dev/remsh-tls
+++ b/dev/remsh-tls
@@ -23,7 +23,11 @@
HOST="127.0.0.1"
fi
+if [ -z $OPTFILE ]; then
+ rootdir=`dirname "$(cd "${0%/*}" 2>/dev/null; echo "$PWD")"`
+ OPTFILE="$rootdir/src/couch_dist/certs/out/couch_dist.conf"
+fi
+
NAME="remsh$$@$HOST"
NODE="node$NODE@$HOST"
-rootdir="$(cd "${0%/*}" 2>/dev/null; echo "$PWD")"
-erl -name $NAME -remsh $NODE -hidden -proto_dist inet_tls -ssl_dist_optfile "${rootdir}/couch_ssl_dist.conf"
+erl -name $NAME -remsh $NODE -hidden -proto_dist inet_tls -ssl_dist_optfile $OPTFILE
diff --git a/dev/run b/dev/run
index 8ac76b7..3b28bc4 100755
--- a/dev/run
+++ b/dev/run
@@ -117,7 +117,7 @@
def get_args_parser():
- parser = optparse.OptionParser(description="Runs CouchDB 2.0 dev cluster")
+ parser = optparse.OptionParser(description="Runs CouchDB dev cluster")
parser.add_option(
"-a",
"--admin",
@@ -234,6 +234,20 @@
action="store_true",
help="Start Nouveau server",
)
+ parser.add_option(
+ "-t",
+ "--enable-tls",
+ dest="enable_tls",
+ default=False,
+ action="store_true",
+ help="Enable custom TLS distribution -- couch",
+ )
+ parser.add_option(
+ "--no-tls",
+ dest="no_tls",
+ default=None,
+ help="Use TCP for specified node when TLS distribution is enabled",
+ )
return parser
@@ -264,6 +278,8 @@
"auto_ports": opts.auto_ports,
"locald_configs": opts.locald_configs,
"with_nouveau": opts.with_nouveau,
+ "enable_tls": opts.enable_tls,
+ "no_tls": opts.no_tls,
}
@@ -421,6 +437,8 @@
def write_config(ctx, node, env):
etc_src = os.path.join(ctx["rootdir"], "rel", "overlay", "etc")
etc_tgt = ensure_dir_exists(ctx["devdir"], "lib", node, "etc")
+ if ctx["enable_tls"]:
+ sp.call(["./src/couch_dist/gen_certs"], cwd=ctx["rootdir"])
for fname in glob.glob(os.path.join(etc_src, "*")):
base = os.path.basename(fname)
@@ -440,6 +458,8 @@
content = apply_config_overrides(ctx, content)
elif base == "local.ini":
content = hack_local_ini(ctx, content)
+ elif ctx["enable_tls"] and base == "vm.args":
+ content = hack_vm_args(ctx, node, content)
with open(tgt, "w") as handle:
handle.write(content)
@@ -524,6 +544,20 @@
return contents + "\n\n[chttpd_auth]\nsecret = %s\n" % COMMON_SALT
+def hack_vm_args(ctx, node, contents):
+ contents += f"""
+-proto_dist couch
+-couch_dist no_tls '"clouseau{node[-1]}@127.0.0.1"'
+-ssl_dist_optfile {ctx["rootdir"]}/src/couch_dist/certs/out/couch_dist.conf
+ """
+ if ctx["no_tls"]:
+ no_tls_nodes = ctx["no_tls"].split(",")
+ for node_name in no_tls_nodes:
+ node_name = node_name if "@" in node_name else f"{node_name}@127.0.0.1"
+ contents += f"""\n-couch_dist no_tls '"{node_name}"'"""
+ return contents
+
+
def gen_password():
# TODO: figure how to generate something more friendly here
return base64.b64encode(os.urandom(6)).decode()
diff --git a/rel/overlay/bin/remsh b/rel/overlay/bin/remsh
index 1804336..117acaf 100755
--- a/rel/overlay/bin/remsh
+++ b/rel/overlay/bin/remsh
@@ -95,7 +95,7 @@
t)
TLSCONF=$OPTARG
if [ ! -f "$TLSCONF" ]; then
- echo "ERROR: Could't find the file \"$TLSCONF\"." >&2
+ echo "ERROR: Couldn't find the file \"$TLSCONF\"." >&2
exit 1
fi
;;
diff --git a/src/couch_dist/.gitignore b/src/couch_dist/.gitignore
new file mode 100644
index 0000000..d7cb29a
--- /dev/null
+++ b/src/couch_dist/.gitignore
@@ -0,0 +1,4 @@
+.rebar/
+certs/*.pem
+certs/out/
+ebin/
diff --git a/src/couch_dist/README.md b/src/couch_dist/README.md
new file mode 100644
index 0000000..aabf4a5
--- /dev/null
+++ b/src/couch_dist/README.md
@@ -0,0 +1,162 @@
+# couch_dist
+
+Erlang communicates with its own protocol over TCP (Transmission Control Protocol).
+It can also be configured to run its protocol over a TLS (Transport Layer Security)
+connection which itself is over TCP.
+
+`couch_dist` implements a custom distribution protocol -- `couch`, which allows
+using TLS for Erlang distribution between nodes, with the ability to connect to
+some nodes using TCP as well.
+
+`TLS` can provide extra verification and security, but requires proper
+certificates and configuration to set up the environment.
+
+## Set up a custom Erlang distribution
+
+1. Specify the distribution protocol in `vm.args`
+2. Specify some nodes to use TCP only in `vm.args` (optional)
+3. Generate certificates using `certs`
+4. Specify security and other SSL options in `couch_dist.conf`
+
+Examples:
+
+1. `vm.args`:
+
+ ```vm.args
+ -proto_dist couch
+ -couch_dist no_tls '"clouseau@127.0.0.1"'
+ -ssl_dist_optfile </absolute_path/to/couch_dist.conf>
+ ```
+
+2. `couch_dist.conf`:
+
+ - `erlserver.pem`: contains the certificate and its private key.
+ - `{fail_if_no_peer_cert, true}`: In previous OTP versions it could be specified on both server side and client
+ side, but in OTP 26 it can only be used on server side,
+ see [OTP 26 Highlights](https://www.erlang.org/blog/otp-26-highlights/#ssl-improved-checking-of-options).
+
+ ```couch_dist.conf
+ [
+ {server, [
+ {cacertfile, "</absolute_path/to/ca-cert.pem>"},
+ {certfile, "</absolute_path/to/erlserver.pem>"},
+ {secure_renegotiate, true},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true}
+ ]},
+ {client, [
+ {cacertfile, "</absolute_path/to/ca-cert.pem>"},
+ {certfile, "</absolute_path/to/cert.pem>"},
+ {keyfile, "</absolute_path/to/key.pem>"},
+ {secure_renegotiate, true},
+ {verify, verify_peer}
+ ]}
+ ].
+ ```
+
+## Generate Certificate
+
+This is an example of using `elixir-certs` to generate certificates, but it is
+not an endorsement of a specific expiration limit, key size or algorithm.
+
+```bash
+cd src/couch_dist/certs
+
+# Generate CA certificate and key
+./certs self-signed \
+ --out-cert ca-cert.pem --out-key ca-key.pem \
+ --template root-ca \
+ --subject "/CN=CouchDB Root CA"
+
+# Generate node certificate and key
+./certs create-cert \
+ --issuer-cert ca-cert.pem --issuer-key ca-key.pem \
+ --out-cert cert.pem --out-key key.pem \
+ --template server \
+ --subject "/CN=127.0.0.1"
+
+# Generate `erlserver.pem`
+cat key.pem cert.pem >erlserver.pem
+
+# Parse certificate to verify:
+# Certificate needs to match the node's hostname
+./parse_cert.escript cert.pem
+["127.0.0.1"]
+```
+
+Thanks to Roger Lipscombe for creating [`elixir-certs`](https://github.com/rlipscombe/elixir-certs)
+which simplifies the process of generating `X.509` certificates.
+
+Also, thanks to Robert Newson for finding `elixir-certs` and adding the feature
+to [easily pass `host` and `node` parameters to certificates](https://github.com/rnewson/elixir-certs/).
+
+## Development
+
+You can run CouchDB with `--enable-tls` mode, which will automatically generate
+vm.args, certificates, and configuration files.
+
+```bash
+./configure --dev --spidermonkey-version 91 && make && ./dev/run -t
+./configure --dev --spidermonkey-version 91 && make && ./dev/run --enable-tls
+
+./dev/remsh-tls
+(node1@127.0.0.1)1> net_kernel:nodes_info().
+{ok,[{'node3@127.0.0.1',
+ [{owner,<0.679.0>},
+ {state,up},
+ {address,
+ {net_address,{{127,0,0,1},55013},"127.0.0.1",tls,inet}},
+ {type,normal},
+ {in,150},
+ {out,147}]},
+ {'node2@127.0.0.1',
+ [{owner,<0.655.0>},
+ {state,up},
+ {address,
+ {net_address,{{127,0,0,1},55011},"127.0.0.1",tls,inet}},
+ {type,normal},
+ {in,181},
+ {out,196}]},
+ {'remsh14066@127.0.0.1',
+ [{owner,<0.8558.0>},
+ {state,up},
+ {address,
+ {net_address,{{127,0,0,1},55075},"127.0.0.1",tls,inet}},
+ {type,hidden},
+ {in,10},
+ {out,15}]}]}
+```
+
+You can also set specific nodes to use TCP:
+
+```bash
+./configure --dev --spidermonkey-version 91 && make && ./dev/run -t --no-tls node2@127.0.0.1
+./configure --dev --spidermonkey-version 91 && make && ./dev/run -t --no-tls node2,node3
+
+./dev/remsh-tls
+(node1@127.0.0.1)1> net_kernel:nodes_info().
+{ok,[{'node2@127.0.0.1',
+ [{owner,<0.456.0>},
+ {state,up},
+ {address,
+ {net_address,{{127,0,0,1},55170},"127.0.0.1",tcp,inet}},
+ {type,normal},
+ {in,145},
+ {out,164}]},
+ {'node3@127.0.0.1',
+ [{owner,<0.461.0>},
+ {state,up},
+ {address,
+ {net_address,{{127,0,0,1},55172},"127.0.0.1",tcp,inet}},
+ {type,normal},
+ {in,141},
+ {out,169}]},
+ {'remsh17312@127.0.0.1',
+ [{owner,<0.1418.0>},
+ {state,up},
+ {address,
+ {net_address,{{127,0,0,1},55203},"127.0.0.1",tls,inet}},
+ {type,hidden},
+ {in,10},
+ {out,15}]}]}
+```
diff --git a/src/couch_dist/certs/certs b/src/couch_dist/certs/certs
new file mode 100755
index 0000000..b684c7e
--- /dev/null
+++ b/src/couch_dist/certs/certs
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Source code: https://github.com/rnewson/elixir-certs/
+# We want the keys to be u=rw,go=, but there's no way to do that in
+# Elixir without race conditions, afaict, so we use umask:
+umask 077
+
+SCRIPT=$(readlink -f "$0")
+SCRIPTPATH=$(dirname "$SCRIPT")
+
+exec elixir "$SCRIPTPATH/certs.exs" "$@"
diff --git a/src/couch_dist/certs/certs.exs b/src/couch_dist/certs/certs.exs
new file mode 100644
index 0000000..7ce09db
--- /dev/null
+++ b/src/couch_dist/certs/certs.exs
@@ -0,0 +1,209 @@
+# Source code: https://github.com/rnewson/elixir-certs/
+# credo:disable-for-this-file
+# Important: run this with the wrapper script, so that umask is set correctly.
+
+Mix.install([{:x509, "~> 0.8.3"}, {:optimus, "~> 0.2"}])
+
+defmodule Certs do
+ def main(argv) do
+ Certs.Options.new!() |> Optimus.parse!(argv) |> run()
+ end
+
+ defp run(
+ {[:self_signed],
+ %Optimus.ParseResult{
+ options: %{subject: subject, out_cert: out_cert, out_key: out_key, template: template}
+ }}
+ ) do
+ ca_key = X509.PrivateKey.new_ec(:secp256r1)
+
+ ca_crt = X509.Certificate.self_signed(ca_key, subject, template: template(template, subject, "", ""))
+
+ File.write!(out_key, X509.PrivateKey.to_pem(ca_key), [:exclusive])
+ File.chmod!(out_key, 0o400)
+
+ File.write!(out_cert, X509.Certificate.to_pem(ca_crt), [:exclusive])
+ File.chmod!(out_cert, 0o444)
+ end
+
+ defp run(
+ {[:create_cert],
+ %Optimus.ParseResult{
+ options: %{
+ subject: subject,
+ host: host,
+ node: node,
+ issuer_cert: issuer_cert,
+ issuer_key: issuer_key,
+ out_cert: out_cert,
+ out_key: out_key,
+ template: template
+ }
+ }}
+ ) do
+ issuer_cert = File.read!(issuer_cert) |> X509.Certificate.from_pem!()
+ issuer_key = File.read!(issuer_key) |> X509.PrivateKey.from_pem!()
+
+ key = X509.PrivateKey.new_ec(:secp256r1)
+ pub = X509.PublicKey.derive(key)
+
+ crt =
+ X509.Certificate.new(pub, subject, issuer_cert, issuer_key,
+ template: template(template, subject, host, node)
+ )
+
+ File.write!(out_key, X509.PrivateKey.to_pem(key), [:exclusive])
+ File.chmod!(out_key, 0o400)
+
+ File.write!(out_cert, X509.Certificate.to_pem(crt), [:exclusive])
+ File.chmod!(out_cert, 0o444)
+ end
+
+ defp run(_) do
+ Certs.Options.new!() |> Optimus.help() |> IO.puts()
+ end
+
+ defp template("root-ca", _subject, _host, _node), do: :root_ca
+
+ defp template("server", subject, _host, _node) do
+ [commonName] =
+ X509.RDNSequence.new(subject)
+ |> X509.RDNSequence.get_attr(:commonName)
+
+ import X509.Certificate.Extension
+
+ %X509.Certificate.Template{
+ # 1 year, plus a 30 days grace period
+ validity: 365 + 30,
+ hash: :sha256,
+ extensions: [
+ basic_constraints: basic_constraints(false),
+ key_usage: key_usage([:digitalSignature, :keyEncipherment]),
+ ext_key_usage: ext_key_usage([:serverAuth, :clientAuth]),
+ subject_key_identifier: true,
+ authority_key_identifier: true,
+ subject_alt_name: subject_alt_name([commonName])
+ ]
+ }
+ end
+
+ defp template("node", _subject, host, node) do
+ import X509.Certificate.Extension
+
+ %X509.Certificate.Template{
+ # 1 year, plus a 30 days grace period
+ validity: 365 + 30,
+ hash: :sha256,
+ extensions: [
+ basic_constraints: basic_constraints(false),
+ key_usage: key_usage([:digitalSignature, :keyEncipherment]),
+ ext_key_usage: ext_key_usage([:serverAuth, :clientAuth]),
+ subject_key_identifier: true,
+ authority_key_identifier: true,
+ subject_alt_name: subject_alt_name([host]),
+ subject_alt_name: subject_alt_name([{:directoryName, X509.RDNSequence.new("CN=" <> node, :otp)}])
+ ]
+ }
+ end
+end
+
+defmodule Certs.Options do
+ def new!() do
+ Optimus.new!(
+ name: "certs",
+ description: "certs",
+ version: "0.2",
+ allow_unknown_args: false,
+ parse_double_dash: true,
+ subcommands: [
+ self_signed: [
+ name: "self-signed",
+ about: "Create a self-signed certificate",
+ options: [
+ subject: [
+ long: "--subject",
+ value_name: "SUBJECT",
+ required: true,
+ parser: :string
+ ],
+ out_cert: [
+ long: "--out-cert",
+ value_name: "OUT_CRT",
+ required: true,
+ parser: :string
+ ],
+ out_key: [
+ long: "--out-key",
+ value_name: "OUT_KEY",
+ required: true,
+ parser: :string
+ ],
+ template: [
+ long: "--template",
+ value_name: "TEMPLATE",
+ required: true,
+ parser: :string
+ ]
+ ]
+ ],
+ create_cert: [
+ name: "create-cert",
+ options: [
+ subject: [
+ long: "--subject",
+ value_name: "SUBJECT",
+ required: true,
+ parser: :string
+ ],
+ host: [
+ long: "--host",
+ value_name: "HOST",
+ required: false,
+ parser: :string
+ ],
+ node: [
+ long: "--node",
+ value_name: "NODE",
+ required: false,
+ parser: :string
+ ],
+ issuer_cert: [
+ long: "--issuer-cert",
+ value_name: "ISSUER_CRT",
+ required: true,
+ parser: :string
+ ],
+ issuer_key: [
+ long: "--issuer-key",
+ value_name: "ISSUER_KEY",
+ required: true,
+ parser: :string
+ ],
+ out_cert: [
+ long: "--out-cert",
+ value_name: "OUT_CRT",
+ required: true,
+ parser: :string
+ ],
+ out_key: [
+ long: "--out-key",
+ value_name: "OUT_KEY",
+ required: true,
+ parser: :string
+ ],
+ template: [
+ long: "--template",
+ value_name: "TEMPLATE",
+ required: true,
+ parser: :string
+ ]
+ ]
+ ]
+ ]
+ )
+ end
+end
+
+Certs.main(System.argv())
+
+# vim: set ft=elixir
diff --git a/src/couch_dist/certs/parse_cert.escript b/src/couch_dist/certs/parse_cert.escript
new file mode 100755
index 0000000..7d5dbca
--- /dev/null
+++ b/src/couch_dist/certs/parse_cert.escript
@@ -0,0 +1,8 @@
+#!/usr/bin/env escript
+-mode(compile).
+
+main(File) ->
+ {ok, PemBin} = file:read_file(File),
+ [{_, DerCert, _}] = public_key:pem_decode(PemBin),
+ OTPCert = public_key:pkix_decode_cert(DerCert, otp),
+ io:format("~p~n", [inet_tls_dist:cert_nodes(OTPCert)]).
diff --git a/src/couch_dist/gen_certs b/src/couch_dist/gen_certs
new file mode 100755
index 0000000..b7eaf51
--- /dev/null
+++ b/src/couch_dist/gen_certs
@@ -0,0 +1,43 @@
+#!/bin/sh
+set -e
+
+certs_dir="$(cd "${0%/*}" 2>/dev/null; echo "${PWD}")/certs"
+cd "${certs_dir}"
+mkdir -p "${certs_dir}/out"
+
+if [ ! -e "${certs_dir}/out/ca-cert.pem" ]; then
+ ./certs self-signed \
+ --out-cert out/ca-cert.pem --out-key out/ca-key.pem \
+ --template root-ca \
+ --subject "/CN=CouchDB Root CA"
+fi
+
+if [ ! -e "${certs_dir}/out/cert.pem" ]; then
+ ./certs create-cert \
+ --issuer-cert out/ca-cert.pem --issuer-key out/ca-key.pem \
+ --out-cert out/cert.pem --out-key out/key.pem \
+ --template server \
+ --subject "/CN=127.0.0.1"
+fi
+
+if [ ! -e "${certs_dir}/out/couch_dist.conf" ]; then
+ cat <<EOF >"${certs_dir}/out/couch_dist.conf"
+[
+ {server, [
+ {cacertfile, "$(pwd)/out/ca-cert.pem"},
+ {certfile, "$(pwd)/out/cert.pem"},
+ {keyfile, "$(pwd)/out/key.pem"},
+ {secure_renegotiate, true},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true}
+ ]},
+ {client, [
+ {cacertfile, "$(pwd)/out/ca-cert.pem"},
+ {certfile, "$(pwd)/out/cert.pem"},
+ {keyfile, "$(pwd)/out/key.pem"},
+ {secure_renegotiate, true},
+ {verify, verify_peer}
+ ]}
+].
+EOF
+fi
diff --git a/src/docs/src/cluster/tls_erlang_distribution.rst b/src/docs/src/cluster/tls_erlang_distribution.rst
index 07e48ea..5d946fa 100644
--- a/src/docs/src/cluster/tls_erlang_distribution.rst
+++ b/src/docs/src/cluster/tls_erlang_distribution.rst
@@ -29,28 +29,53 @@
Generate Certificate
====================
-For TLS to work properly, at least one public key and one certificate must be
-specified. In the following example (couch_ssl_dist.conf), the PEM file contains
-the ``certificate`` and its ``private key``.
+To distribute using TLS, appropriate certificates need to be provided.
+In the following example (couch_dist.conf), the cert.pem certificate must be
+trusted by a root certificate known to the server, and the erlserver.pem file
+contains the "certificate" and its "private key".
.. code-block:: text
[{server,
- [{certfile, "</path/to/erlserver.pem>"},
- {secure_renegotiate, true}]},
+ [{cacertfile, "</absolute_path/to/ca-cert.pem>"},
+ {certfile, "</absolute_path/to/erlserver.pem>"},
+ {secure_renegotiate, true},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true}]},
{client,
- [{secure_renegotiate, true}]}].
+ [{cacertfile, "</absolute_path/to/ca-cert.pem>"},
+ {keyfile, "</absolute_path/to/key.pem>"},
+ {certfile, "</absolute_path/to/cert.pem>"},
+ {secure_renegotiate, true},
+ {verify, verify_peer}]}].
-The following command is an example of generating a certificate (PEM) file.
+You can use ``{verify, verify_peer}`` to enable verification,
+but it requires appropriate certificates to verify.
+
+This is an example of generating certificates.
.. code-block:: bash
- $ openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
- $ cat key.pem cert.pem > erlserver.pem && rm key.pem cert.pem
+ $ git clone https://github.com/rnewson/elixir-certs
+ $ cd elixir-certs
+ $ ./certs self-signed \
+ --out-cert ca-cert.pem --out-key ca-key.pem \
+ --template root-ca \
+ --subject "/CN=CouchDB Root CA"
+ $./certs create-cert \
+ --issuer-cert ca-cert.pem --issuer-key ca-key.pem \
+ --out-cert cert.pem --out-key key.pem \
+ --template server \
+ --subject "/CN=<hostname>"
+ $ cat key.pem cert.pem >erlserver.pem
.. note::
- This is **not** an endorsement of a specific expiration limit,
- key size or algorithm.
+ * The above examples are **not** an endorsement of specific expiration limits, key sizes, or algorithms.
+ * If option ``verify_peer`` is set, the ``server_name_indication`` option should also be specified.
+ * The option ``{fail_if_no_peer_cert, true}`` should only be used on the server side in OTP 26,
+ for previous versions it can be specified both on the server side and client side.
+ * When generating certificates, make sure Common Name (FQDN) should be different in CA certificate and certificate.
+ Also, FQDN in the certificate should be the same as the hostname.
Config Settings
===============
@@ -62,7 +87,7 @@
-proto_dist couch
-couch_dist no_tls \"clouseau@127.0.0.1\"
- -ssl_dist_optfile <path/to/couch_ssl_dist.conf>
+ -ssl_dist_optfile </absolute_path/to/couch_dist.conf>
.. note::
* The default value of ``no_tls`` is ``false``. If the user does not
@@ -90,7 +115,7 @@
# Specify node1 and node2 to use TCP, others use TLS
- -couch_dist no_tls \"node1@127.0.0.1\"
+ -couch_dist no_tls '"node1@127.0.0.1"'
-couch_dist no_tls \"node2@127.0.0.1\"
.. code-block:: text
@@ -119,4 +144,4 @@
.. code-block:: bash
- $ ./remsh -t <path/to/couch_ssl_dist.conf>
+ $ ./remsh -t </absolute_path/to/couch_dist.conf>