diff --git a/Dockerfile b/Dockerfile index a5e36b1..1d4ab2e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,18 +44,21 @@ ENV KERL_DOC_TARGETS="" ENV KERL_INSTALL_HTMLDOCS="no" ENV KERL_INSTALL_MANPAGES="no" -RUN git clone https://github.com/asdf-vm/asdf.git --branch v0.6.3 "$HOME"/.asdf && \ - echo '. $HOME/.asdf/asdf.sh' >> "$HOME"/.bashrc && \ - echo '. $HOME/.asdf/asdf.sh' >> "$HOME"/.profile +# Install asdf and add it to PATH +RUN git clone https://github.com/asdf-vm/asdf.git --branch v0.6.3 /root/.asdf && \ + echo '. /root/.asdf/asdf.sh' >> /root/.bashrc && \ + echo '. /root/.asdf/asdf.sh' >> /root/.profile -ENV PATH="${PATH}:/root/.asdf/shims:/root/.asdf/bin" +ENV PATH="/root/.asdf/shims:/root/.asdf/bin:${PATH}" RUN mkdir -p /opt/erlang/epp_proxy WORKDIR /opt/erlang/epp_proxy COPY .tool-versions ./ + +# Install plugins and tools with explicit sourcing of asdf.sh RUN asdf plugin-add erlang -RUN . $HOME/.asdf/asdf.sh && asdf install +RUN source /root/.asdf/asdf.sh && asdf install RUN asdf global erlang $(grep erlang .tool-versions | cut -d' ' -f2) RUN asdf plugin-add ruby RUN asdf plugin-add rebar diff --git a/README.md b/README.md index 0a55f2a..c7832a0 100644 --- a/README.md +++ b/README.md @@ -105,20 +105,33 @@ Configuration for the application tries to emulate the mod_epp configuration as to make migration easier. The configuration is placed in `config/sys.config` file, it takes a format of Erlang property list. +There are three example configuration files in `config/`: + +* `sys.config` – default configuration used for real deployments. Values such as `tls_port`, + `epp_session_url` and certificate paths are typically provided via environment variables (eg. + `${TLS_PORT}`, `${EPP_SESSION_URL}`), so the same file can be reused across environments. +* `docker.config` – configuration tuned for running inside Docker. It uses hardcoded ports, + certificate paths under `/opt/ca/...` and EPP endpoints pointing to the `epp` container + (eg. `http://epp:3000/epp/…`). +* `test.config` – local development/test configuration. It enables `dev_mode`, uses local ports + and points EPP endpoints to `http://localhost:9292/...`, with test CA material under + `test_ca/`. + *Configuration variables* -| Variable name | Expected values | Apache equivalent | Definition ------------------------|------------------------------------|-----------------------|-------------------------------------------- -| `dev_mode` | `true`, `false` | None | Enables TCP access without TLS. -| `tls_port` | `700` | Listen | At which port should we open a TLS socket. Default is 700. -| `tcp_port` | `70000` | Listen | At which port should we open a TCP socket. Only in `dev_mode`. -| `epp_session_url` | `https://example.com/epp/session` | EppSessionRoot | HTTP address of the session endpoints including schema and port. -| `epp_command_url` | `https://example.com/epp/command` | EppCommandRoot | HTTP address of the command endpoints including schema and port. -| `epp_error_url` | `https://example.com/epp/error` | EppErrorRoot | HTTP address of the error endpoints including schema and port. -| `cacertfile_path` | `/opt/ca/ca.crt.pem` | SSLCACertificateFile | Where is the client root CA located. Can be inside apps/epp_proxy/priv or absolute path. -| `certfile_path` | `/opt/ca/server.crt.pem` | SSLCertificateFile | Where is the server certificate located. Can be inside apps/epp_proxy/priv or absolute path. -| `keyfile_path` | `/opt/ca/server.key.pem` | SSLCertificateKeyFile | Where is the server key located. Can be inside apps/epp_proxy/priv or absolute path. -| `crlfile_path` | `/opt/ca/crl.pem` | SSLCARevocationFile | Where is the CRL file located. Can be inside apps/epp_proxy/priv or absolute path. When not set, not CRL check is performed. +| Variable name | Expected values | Apache equivalent | Definition +-------------------------|------------------------------------|-----------------------|-------------------------------------------- +| `dev_mode` | `true`, `false` | None | Enables TCP access without TLS. +| `tls_port` | `700` | Listen | At which port should we open a TLS socket. Default is 700. +| `tcp_port` | `70000` | Listen | At which port should we open a TCP socket. Only in `dev_mode`. +| `epp_session_url` | `https://example.com/epp/session` | EppSessionRoot | HTTP address of the session endpoints including schema and port. +| `epp_command_url` | `https://example.com/epp/command` | EppCommandRoot | HTTP address of the command endpoints including schema and port. +| `epp_error_url` | `https://example.com/epp/error` | EppErrorRoot | HTTP address of the error endpoints including schema and port. +| `require_client_certs` | `true`, `false` | None | Allows client to connect to epp_proxy without client certificate using TLS. +| `cacertfile_path` | `/opt/ca/ca.crt.pem` | SSLCACertificateFile | Where is the client root CA located. Can be inside apps/epp_proxy/priv or absolute path. +| `certfile_path` | `/opt/ca/server.crt.pem` | SSLCertificateFile | Where is the server certificate located. Can be inside apps/epp_proxy/priv or absolute path. +| `keyfile_path` | `/opt/ca/server.key.pem` | SSLCertificateKeyFile | Where is the server key located. Can be inside apps/epp_proxy/priv or absolute path. +| `crlfile_path` | `/opt/ca/crl.pem` | SSLCARevocationFile | Where is the CRL file located. Can be inside apps/epp_proxy/priv or absolute path. When not set, not CRL check is performed. Migrating from mod_epp diff --git a/apps/epp_proxy/src/epp_tls_acceptor.erl b/apps/epp_proxy/src/epp_tls_acceptor.erl index a3c0080..c41a4e8 100644 --- a/apps/epp_proxy/src/epp_tls_acceptor.erl +++ b/apps/epp_proxy/src/epp_tls_acceptor.erl @@ -21,9 +21,10 @@ start_link(Port) -> []). init(Port) -> + RequireClientCerts = require_client_certs(), DefaultOptions = [binary, {packet, raw}, {active, false}, {reuseaddr, true}, - {verify, verify_peer}, {depth, 1}, + {verify, verify_peer}, {fail_if_no_peer_cert, RequireClientCerts}, {depth, 1}, {cacertfile, ca_cert_file()}, {certfile, cert_file()}, {keyfile, key_file()}], Options = handle_crl_check_options(DefaultOptions), @@ -82,6 +83,14 @@ key_file() -> {ok, KeyFile} -> epp_util:path_for_file(KeyFile) end. +%% Whether client certificates are required. +%% If not configured, default to true to preserve existing behavior. +require_client_certs() -> + case application:get_env(epp_proxy, require_client_certs) of + undefined -> true; + {ok, Bool} -> Bool + end. + crl_file() -> case application:get_env(epp_proxy, crlfile_path) of undefined -> undefined; diff --git a/apps/epp_proxy/test/tls_client_optional_cert_SUITE.erl b/apps/epp_proxy/test/tls_client_optional_cert_SUITE.erl new file mode 100644 index 0000000..bc49961 --- /dev/null +++ b/apps/epp_proxy/test/tls_client_optional_cert_SUITE.erl @@ -0,0 +1,36 @@ +-module(tls_client_optional_cert_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-export([all/0, init_per_suite/1, end_per_suite/1, + connect_without_client_cert_test/1, connect_with_client_cert_test/1]). + +all() -> [connect_without_client_cert_test, connect_with_client_cert_test]. + +init_per_suite(Config) -> + application:set_env(epp_proxy, require_client_certs, false), + application:ensure_all_started(epp_proxy), + application:ensure_all_started(hackney), + CWD = code:priv_dir(epp_proxy), + WithCert = [binary, + {certfile, filename:join(CWD, "test_ca/certs/client.crt.pem")}, + {keyfile, filename:join(CWD, "test_ca/private/client.key.pem")}, + {active, false}], + [{with_cert, WithCert} | Config]. + +end_per_suite(Config) -> + application:unset_env(epp_proxy, require_client_certs), + application:stop(epp_proxy), + application:stop(hackney), + Config. + +connect_without_client_cert_test(_Config) -> + {ok, Socket} = ssl:connect("localhost", 1443, [binary, {active, false}], 2000), + {ok, _Data} = ssl:recv(Socket, 0, 1200), + ok. + +connect_with_client_cert_test(Config) -> + Options = proplists:get_value(with_cert, Config), + {ok, Socket} = ssl:connect("localhost", 1443, Options, 2000), + {ok, _Data} = ssl:recv(Socket, 0, 1200), + ok. \ No newline at end of file diff --git a/config/docker.config b/config/docker.config index a8f8935..06efb97 100644 --- a/config/docker.config +++ b/config/docker.config @@ -8,10 +8,12 @@ {epp_session_url, "http://epp:3000/epp/session/"}, {epp_command_url, "http://epp:3000/epp/command/"}, {epp_error_url, "http://epp:3000/epp/error/"}, + %% Allows client to connect to epp_proxy without client certificate using TLS + {require_client_certs, true}, {cacertfile_path, "/opt/ca/certs/ca.crt.pem"}, {certfile_path, "/opt/ca/certs/apache.crt"}, - {keyfile_path, "/opt/ca/private/apache.key"}, - {crlfile_path, "/opt/ca/crl/crl.pem"} + {keyfile_path, "/opt/ca/private/apache.key"} + %% {crlfile_path, "/opt/ca/crl/crl.pem"} ]}, {lager, [ {handlers, [ diff --git a/config/sys.config b/config/sys.config index 2ee9a24..ebc44a3 100644 --- a/config/sys.config +++ b/config/sys.config @@ -17,6 +17,8 @@ {epp_session_url, "${EPP_SESSION_URL}"}, {epp_command_url, "${EPP_COMMAND_URL}"}, {epp_error_url, "${EPP_ERROR_URL}"}, + %% Allows client to connect to epp_proxy without client certificate using TLS + {require_client_certs, true}, %% Path to root CA that should check the client certificates. {cacertfile_path, "${CACERT_PATH}"}, %% Path to server's certficate file. diff --git a/config/test.config b/config/test.config index 90cecb2..697715a 100644 --- a/config/test.config +++ b/config/test.config @@ -7,6 +7,8 @@ {epp_session_url, "http://localhost:9292/session/"}, {epp_command_url, "http://localhost:9292/command/"}, {epp_error_url, "http://localhost:9292/error/"}, + %% Allows client to connect to epp_proxy without client certificate using TLS + {require_client_certs, true}, %% Path to root CA that should check the client certificates. {cacertfile_path, "test_ca/certs/ca.crt.pem"}, {certfile_path, "test_ca/certs/apache.crt"},