DEV Community

Cover image for Rust: actix-web get SSL/HTTPS for localhost.
Be Hai Nguyen
Be Hai Nguyen

Posted on

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.

πŸš€ 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
Enter fullscreen mode Exit fullscreen mode

The actix-web learning application mentioned above has been discussed in the following six previous posts:

  1. Rust web application: MySQL server, sqlx, actix-web and tera.
  2. Rust: learning actix-web middleware 01.
  3. Rust: retrofit integration tests to an existing actix-web application.
  4. Rust: adding actix-session and actix-identity to an existing actix-web application.
  5. Rust: actix-web endpoints which accept both application/x-www-form-urlencoded and application/json content types.
  6. 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
Enter fullscreen mode Exit fullscreen mode

-- Note the tag v0.6.0.

Table of contents


❢ To run under HTTPS. That is:

https://localhost:5000/ui/login
https://192.168.0.16:5000/ui/login
Enter fullscreen mode Exit fullscreen mode


❷ Project Layout.

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 β˜…
Enter fullscreen mode Exit fullscreen mode


❸ 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:

097-01.png

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
Enter fullscreen mode Exit fullscreen mode

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 []: 
Enter fullscreen mode Exit fullscreen mode

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 HttpServer object, we call method listen_openssl(...) instead of method listen(...):

...
    .listen_openssl(listener, ssl_builder())?
... 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

We might get a warning of potential security risk... For example, see the Firefox warning in the below screenshot:

097-02.png

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:

097-03-firefox.png
097-03-chrome.png
097-03-opera.png


❻ 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 tests/common.rs. We should set the scheme of app_url to https://:

...
    TestApp {
        app_url: format!("https://127.0.0.1:{}", port)
    }
}   
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)