Rust: actix-web get SSL/HTTPS for localhost.
We are going to enable our actix-web learning application to run under HTTPS
. As a result, we need to do some minor refactoring to existing integration tests. We also move and rename an existing module for better code organisation.
π¦ Index of the Complete Series.
Rust: actix-web get SSL/HTTPS for localhost. |
π Please note, complete code for this post can be downloaded from GitHub with:
git clone -b v0.7.0 https://github.com/behai-nguyen/rust_web_01.git
The actix-web learning application mentioned above has been discussed in the following six previous posts:
- Rust web application: MySQL server, sqlx, actix-web and tera.
- Rust: learning actix-web middleware 01.
- Rust: retrofit integration tests to an existing actix-web application.
- Rust: adding actix-session and actix-identity to an existing actix-web application.
- Rust: actix-web endpoints which accept both
application/x-www-form-urlencoded
andapplication/json
content types. - Rust: simple actix-web email-password login and request authentication using middleware.
The code weβre developing in this post is a continuation of the code from the sixth post above. π To get the code of this sixth post, please use the following command:
git clone -b v0.6.0 https://github.com/behai-nguyen/rust_web_01.git
β Note the tag v0.6.0
.
Table of contents
- Define βto run under
HTTPS
β - Project Layout
- The OpenSSL Toolkit
- Generate a Self-Signed Encrypted Private Key and a Certificate
- Code Refactoring to Enable
HTTPS
- Refactor Integration Tests
- Moving src/utils.rs to src/bh_libs/australian_date.rs
- Concluding Remarks
βΆ To run under HTTPS
. That is:
https://localhost:5000/ui/login
https://192.168.0.16:5000/ui/login
This post introduces a self-signed encrypted private key file and a certificate file. The updated directory layout for the project is listed below.
β Please note, those marked with β are updated, and those marked with β are new.
.
βββ Cargo.toml β
βββ cert
β βββ cert-pass.pem β -- Self-signed encrypted private key
β βββ key-pass.pem β -- Certificate
βββ .env
βββ migrations
β βββ mysql
β β βββ migrations
β β βββ 20231128234321_emp_email_pwd.down.sql
β β βββ 20231128234321_emp_email_pwd.up.sql
β βββ postgres
β βββ migrations
β βββ 20231130023147_emp_email_pwd.down.sql
β βββ 20231130023147_emp_email_pwd.up.sql
βββ README.md β
βββ src
β βββ auth_handlers.rs
β βββ auth_middleware.rs
β βββ bh_libs
β β βββ api_status.rs
β β βββ australian_date.rs β
β βββ bh_libs.rs β
β βββ config.rs
β βββ database.rs
β βββ handlers.rs
β βββ helper
β β βββ app_utils.rs β
β β βββ constants.rs
β β βββ endpoint.rs
β β βββ messages.rs
β βββ helper.rs
β βββ lib.rs β
β βββ main.rs
β βββ middleware.rs
β βββ models.rs β
βββ templates
β βββ auth
β β βββ home.html
β β βββ login.html
β βββ employees.html
βββ tests
βββ common.rs β
βββ test_auth_handlers.rs β
βββ test_handlers.rs β
βΈ In this post, we are using the OpenSSL Cryptography and SSL/TLS Toolkit to generate the self-signed encrypted private key and the certificate files.
β΅ We have previously discussed its installation on both Windows 10 Pro and Ubuntu 22.10 in this section of another post.
βΆ π₯ On Windows 10 Pro, I have observed that, once we include the openssl crate, we should set the environment variable OPENSSL_DIR
at the system level, otherwise the Rust Analyzer Visual Studio Code plug-in would run into trouble.
The environment variable OPENSSL_DIR
indicates where OpenSSL has been installed. For example, C:\Program Files\OpenSSL-Win64
. Following are the steps to access Windows 10 Pro environment variable setting dialog.
Right click on This PC β Properties β Advance system settings (right hand side) β Advanced tab β Environment Variablesβ¦ button β under System variables β Newβ¦ β enter variable name and value in the dialog β OK button.
The screenshot below is a brief visual representation of the above steps, including the environment variable OPENSSL_DIR
in place:
We might need to restart Visual Studio Code to get the new setting recognised.
βΉ Generate the self-signed encrypted private key and the certificate files using the OpenSSL Toolkit.
The OpenSSL command to generate the files will prompt a series of questions. One important question is the Common Name
which is the server name or FQDN
where the certificate is going to be used. If we are not yet familiar with this process, this FQDN (Fully Qualified Domain Name): What It Is, Examples, and More article would be an essential reading, in my humble opnion.
I did seek help working on this issue, please see Actix-web OpenSSL SSL/HTTPS for Localhost, is it possible please? And I was pointed to examples/https-tls/openssl/ β and this is my primary source of reference for getting this learning application to run under HTTPS
.
The command I choose to use is:
$ openssl req -x509 -newkey rsa:4096 -keyout key-pass.pem -out cert-pass.pem -sha256 -days 365
Be prepared, we will be asked the following questions:
Enter PEM pass pharse:
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []: Melbourne
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Both key-pass.pem
and cert-pass.pem
are in the cert/
sub-directory as seen in the Project Layout section.
π₯ Please note I also use these two files on Windows 10 Pro to run the application. It works, I am not sure why yet. I need to keep an eye out for this.
βΊ Code refactoring to enable HTTPS
.
We are also taking the code from examples/https-tls/openssl/. In src/lib.rs, we add two private functions fn load_encrypted_private_key() -> PKey<Private>
and fn ssl_builder() -> SslAcceptorBuilder
. They are basically the code copied from the above example re-arranged into two separate functions.
And for the actual HttpServer object, we call method listen_openssl(β¦) instead of method listen(β¦):
...
.listen_openssl(listener, ssl_builder())?
...
I have tested with the latest version of the following browsers: Firefox, Chrome, Edge, Opera, Brave and Vivaldi, for both:
https://localhost:5000/ui/login
https://192.168.0.16:5000/ui/login
We might get a warning of potential security risk...
For example, see the Firefox warning in the below screenshot:
I just ignore the warning and choose to go ahead. Even though https://
works, but all mentioned browsers state that the connection is not secure. Please see Firefox, Chrome and Opera sample screenshots below:
β» We have to make changes to both integration tests common code and actual test code.
β΅ Itβs quite obvious that we should access the routes via HTTPS
. The first change would be pub async fn spawn_app() -> TestApp
in module tests/common.rs. We should set the scheme of app_url
to https://
:
...
TestApp {
app_url: format!("https://127.0.0.1:{}", port)
}
}
I did run integration tests after making this change. They failed. Base on the error messages, it seems that reqwest::Client should βhaveβ the certificate as well (?).
Looking through the reqwest crate documentation, pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder
seems like a good candidateβ¦
Base on the example given in reqwest::tls::Certificate, I come up with pub fn load_certificate() -> Certificate
and pub fn reqwest_client() -> reqwest::Client
.
I have tried to document all my observations during developing these two helper functions. They are short and simple, I think the inline documentation explains the code quite sufficiently.
β Initially, reqwest_client()
does not include .danger_accept_invalid_certs(true)
, resulting in a certificate error. This solution, provided in the following Stack Overflow thread titled How to resolve a Rust Reqwest Error: Invalid Certificate suggests adding .danger_accept_invalid_certs(true)
, which appears to resolve the issue.
π₯ Base on all evidences presented so far, including the connection not secure
warnings reported by browsers and the need to call .danger_accept_invalid_certs(true)
when creating a reqwest::Client instance, it seems to suggest that there may still be an issue with this implementation. Or is it common for a self-signed certificate, which is not issued by a trusted certificate authority, to encounter such problems? However, having the application run under https://
addresses issues I have had with cookies. For now, I will leave it as is. We will discuss cookie in another new post.
βΆ In both integration test modules, tests/test_handlers.rs and tests/test_auth_handlers.rs, we use the pub fn reqwest_client() -> reqwest::Client
function to create instances of reqwest::Client for testing purposes, instead of creating instances directly in each test method.
βΌ The final task of this post involves moving src/utils.rs to src/bh_libs/australian_date.rs, as it is a generic module, even though it depends on other third-party crates. It is possible that this module will be moved elsewhere again.
The module src/bh_libs/australian_date.rs is generic enough to used as-is in other projects.
As a result, the src/models.rs module is updated.
β½ Weβve reached the end of this post. Iβd like to mention that I also followed the tutorial How to Get SSL/HTTPS for Localhost. I completed it successfully on Ubuntu 22.10, but browsers still warn about the connection not being secure. Perhaps this is to be expected with a self-signed certificate?
Overall, itβs been an interesting exercise. I hope you find the information in this post useful. Thank you for reading. And stay safe, as always.
βΏβΏβΏ
Feature image source:
- https://www.omgubuntu.co.uk/2022/09/ubuntu-2210-kinetic-kudu-default-wallpaper
- https://in.pinterest.com/pin/337277459600111737/
- https://www.rust-lang.org/
- https://www.pngitem.com/download/ibmJoR_rust-language-hd-png-download/