Running Your Flask Application Over HTTPS

introduce

During the development of FLASK applications, it is common to run the development Web server, which provides a basic but fully functional WSGI HTTP server. But when deploying an application to a production environment, one of the things to consider is whether the client should be required to use an encrypted connection for added security.

So how do you run the FLASK application over HTTPS? In this article, I’ll look at several options for adding encryption to the Flask application, from A very simple solution that takes only 5 seconds to implement, to A robust A+ rated solution.

How does HTTPS work?

HTTP’s encryption and security functions are implemented through the Transport Layer Security (TLS) protocol. In general, TLS defines a standard way to secure network channels.

The basic idea is that when a client establishes a connection to a server and requests an encrypted connection, the server will respond with its **SSL certificate **. This certificate acts as the identity of the server because it includes the server name and domain. To ensure that the information provided by the server is correct, the certificate is encrypted and signed by the Certificate Authority (CA). If the client knows and trusts the CA, it can confirm that the certificate signature is indeed from this entity, and from this client, the client can determine that the server it is connecting to is legitimate.

After the client validates the certificate, it creates an encryption key to communicate with the server. To ensure that this key is sent securely to the server, it encrypts it using the public key contained in the server certificate. The server has the private key used with the public key in the certificate, so it is the only party that can decrypt it. From the time the encryption key is received by the server, all traffic is encrypted using a key known only to the client and server.

To implement TLS encryption, we need two entries: the server certificate, which includes the public key signed by the CA; The private key along with the public key contained in the certificate.

The easiest way

Flask (more specifically Werkzeug) supports the use of in-the-fly certificates, which are useful for quickly serving applications over HTTPS without the need for certificates.

To use temporary certificates on Flask, you need to install an additional dependency in the virtual environment:

$ pip install pyopenssl
Copy the code

Then add ssl_context =’adhoc’ to the app.run() call:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello() :
    return "Hello World!"

if __name__ == "__main__":
    app.run(ssl_context='adhoc')
Copy the code

If you are using the Flask 1.x distribution, you can also use this option through the Flask CLI:

$ flask run --cert=adhoc
Copy the code

When running this script (or flask Run), you will notice that flask indicates that it is running https://server:

$Python hello.py * Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)Copy the code

The problem is that browsers don’t like this type of certificate, so they display a dire warning that you need to override before you can access the application. Once you allow the browser to connect, you’ll have an encrypted connection, just like you’d get from a server with a valid certificate. Using these temporary certificates is handy for testing, but not for any real purpose.

Self-signed certificate

A so-called self-signed certificate is one that generates a signed certificate using the private key associated with the certificate. I mentioned above that the client needs to “know and trust” the CA that signs the certificate, because this trust relationship allows the client to verify the server certificate. Web browsers and other HTTP clients are pre-configured with a list of known and trusted cas, but obviously, if self-signed certificates are used, the CAS will not be known and verification will fail. This is exactly what happened with the temporary certificate we used in the last section. If the Web browser cannot verify the server certificate, it allows you to proceed and access the site in question, but it will remind you of the risks involved in doing so.

But is there really a risk? With the Flask server on the last part, you obviously believe in yourself, so there’s no risk for you. The problem is that this warning occurs when users connect to sites they do not understand or control. In this case, the user will have no way of knowing if the server is real, because anyone can generate a certificate for any domain.

While self-signed certificates can sometimes be useful, temporary certificates in Flask are not so good because different certificates are generated through pyOpenSSL every time the server runs. When using self-signed certificates, it is best to use the same certificate each time you start the server, as this enables the browser to be configured to trust it and eliminates security warnings.

Self-signed certificates can be generated from the command line simply by installing OpenSSL:

openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365
Copy the code

This command writes a new certificate to cert.pem and the corresponding private key to key.pem. The validity period is 365 days. When this command is run, several questions are asked:

Generating a 4096 bit RSA private key ...................... + +... ++ writing new private key to'key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '. '. the field will be left blank. ----- Country Name (2 letter code) [AU]:US State or Province Name (full name) [Some-State]:Oregon Locality Name (eg, city) []:Portland Organization Name (eg, company) [Internet Widgits Pty Ltd]:Miguel Grinberg Blog Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:localhost Email Address []:Copy the code

We can now use this new self-signed certificate in the Flask application by setting the SSL_context parameter in app.run() to a tuple containing the filename of the certificate and private key file.

from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello() :
    return "Hello World!"
if __name__ == "__main__":
    app.run(ssl_context=('cert.pem'.'key.pem'))
Copy the code

If you are using Flask 1.x or later, you can add the –cert and –key options to the Flask run command:

$ flask run --cert=cert.pem --key=key.pem
Copy the code

The browser will still alert you, but if you check the certificate, you will see the information you entered when you created it

Use the production Web server

We all know that the Flask development server is only for development and testing. So, how do we install an SSL certificate on a production server?

If you are using Gunicorn, you can use the command line argument:

$gunicorn --certfile cert.pem --keyfile key.pem -b 0.0.0.0:8000 hello:appCopy the code

If you are using Nginx as a reverse proxy then you can configure the certificate with Nginx and then Nginx can “terminate” the encrypted connection, which means it will accept an encrypted connection from the outside but then communicate with the Flask back end using a regular non-encrypted connection. This is a very useful setting because it frees the application from having to deal with certificates and encryption. The configuration items of Nginx are as follows:

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    #...
}
Copy the code

Another important issue you need to consider is how to handle clients over regular HTTP connections. In my opinion, the best solution is to respond to unencrypted requests by redirecting to the same URL but using HTTPS. For a Flask application, you can do this with the Flask-SSLify extension. With Nginx, you can include another server block in the configuration:

server {
    listen 80;
    server_name example.com;
    location / {
        return 301https://$host$request_uri; }}Copy the code

Use “real” certificates

We’ve now explored all the options for self-signed certificates, but in all these cases, the limitation remains that web browsers won’t trust these certificates unless you tell them.

So for production sites, the best choice for server certificates is to get them from one of these Well-Known cas that are automatically trusted by all Web browsers.

When you request a certificate from a CA, the entity will verify that you are under the control of the server and domain, but how you do that depends on the CA. If the server passes this verification, the CA issues it a certificate with its own signature and gives it to you to install. Certificates are usually valid for less than one year. Most cas charge for these certificates, but some offer them for free. The most popular free CA is called Let’s Encrypt.

Getting a certificate from Let’s Encrypt is fairly easy because the whole process is automated. Assuming you are using an Ubuntu-based server, you need to install their open source Certbot tool on your server:

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install certbot
Copy the code

Now, you can request certificates using the Certbot tool. Certbot can validate your site in a number of ways.

In general, the “webroot” approach is the easiest to implement. Using this approach, Certbot adds files to a directory that the Web server exposes as static files, and then tries to access these files over HTTP using the domain for which the certificate is to be generated. If this test is successful, CertBot knows that the server running it is associated with the correct domain, matches it, and issues the certificate. The command to request a certificate using this method is as follows:

$ sudo certbot certonly --webroot -w /var/www/example -d example.com
Copy the code

In this example, we try to generate a certificate for the example.com domain that uses the directory in /var/www/example as the static file root. Unfortunately, Flask based websites don’t have a static file root, at least with the default configuration, which uses the /static prefix to access all static files in the application, so more planning is required.

In this example, we try to generate a certificate for the example.com domain that uses the directory in /var/www/example as the static file root. Unfortunately, Flask based websites don’t have a static file root (at least with the default configuration), and you need to use the /static prefix to access all static files in your application.

What Certbot does to the static root directory is to add a.well-known subdirectory and store some files in it. It then uses an HTTP client to [http://example.com/.well-known/… (http://example.com/.well-known/…) Retrieve these files in the form of If these files can be retrieved, your server is in full control of the domain name. For Flask and other applications that do not have a static file root, it is necessary to define a root directory.

If you use Nginx as a reverse proxy, you can take advantage of the powerful mapping that can be created in the configuration to provide certBot with a private directory where its validation files can be written. In the following example, I extend the HTTP server block shown in the previous section to place all encryption-related requests (always known as /.well-known /… To the specific directory of your choice:

If you use Nginx as a reverse proxy, a powerful map can be created in the configuration to provide certBot with a private directory where it can write its validation files

server {
    listen 80;
    server_name example.com;
    location ~ /.well-known {
        root /path/to/letsencrypt/verification/directory;
    }
    location / {
        return 301https://$host$request_uri; }}Copy the code

You can then hand this directory to Certbot:

$ sudo certbot certonly --webroot -w /path/to/letsencrypt/verification/directory -d example.com
Copy the code

If certbot to verify the domain name, it will be written in the certificate file/etc/letsencrypt/live/example.com/fullchain.pem, The private key is written as /etc/letsencrypt/live/ example.com/privkey.pem and is valid for 90 days.

To use this newly obtained certificate, you can enter the two file names mentioned above to replace the self-signed file we used earlier, which should apply to any configuration described above. Of course, you also need to make your application available through your registered domain name, as this is the only way the browser will accept the certificate as valid.

You can also use Certbot when you need to update certificates:

$ sudo certbot renew
Copy the code

If any certificates are about to expire on the system, the above command will update them and leave new certificates in the same place. If you want to get an updated certificate, you may need to restart your Web server.

Earn SSL level A+

If you use a certificate from Let’s Encrypt or another known CA on your production site, and you are running a newly maintained operating system on this server, you are likely to have a server with the highest rating for SSL security. You can go to the Qualys SSL Labs Labs website to get a report.

The report will point out where you need to improve, but in general, I hope you’ll be told that the encrypted communication options that the server exposes are either too wide or too weak, making you vulnerable to known vulnerabilities.

One area where it is easy to improve is how to generate the coefficients used in cryptographic key exchange, which usually have fairly weak defaults. In particular, the Diffie-Hellman coefficient takes a lot of time to generate, so by default the server uses a smaller number to save time. But we can generate the strong coefficients up front and store them in a file, and then Nginx can use them. Using the OpenSSL tool, you can run the following command:

openssl dhparam -out /path/to/dhparam.pem 2048
Copy the code

If you want stronger coefficients, you can change the above from 2048 to 4096. This command takes some time to run, especially if your server doesn’t have a lot of CPU power, but when it runs, you will have a strong dhparam.pem file that you can insert into the Nginx SSL server block

ssl_dhparam /path/to/dhparam.pem;
Copy the code

You may need to configure a password for the server to allow encrypted communications. Here’s the list on my server:

ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE -RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA -AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AE S256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA -AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:! DES-CBC3-SHA:! aNULL:! eNULL:! EXPORT:! DES:! RC4:! MD5:! PSK:! aECDH:! EDH-DSS-DES-CBC3-SHA:! EDH-RSA-DES-CBC3-SHA:! KRB5-DES-CBC3-SHA';
Copy the code

In this list, use! Prefix disables ciphers. The SSL report will tell you if there are any passwords that are not recommended. You must check from time to time to see if you have found new vulnerabilities that need to be modified.

Below you can find my current Nginx SSL configuration, including the Settings above, as well as some warnings I added to the SSL report:

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_dhparam /path/to/dhparam.pem;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE -RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA -AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AE S256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA -AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:! DES-CBC3-SHA:! aNULL:! eNULL:! EXPORT:! DES:! RC4:! MD5:! PSK:! aECDH:! EDH-DSS-DES-CBC3-SHA:! EDH-RSA-DES-CBC3-SHA:! KRB5-DES-CBC3-SHA';
    ssl_protocols TLSv1.2;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;
    #...
}
Copy the code

You can see the SSL security report obtained from my website. If you do not score 100% in any of the categories, you must add additional restrictions to the configuration, but this will limit the number of clients that can connect to your site. Typically, older browsers and HTTP clients use Ciphers that are not considered the strongest, but if Ciphers is disabled, these clients will not be able to connect. So, you basically need to compromise, and you also need to check your security reports regularly and update them as the situation changes.

Unfortunately, for the complexity of these recent SSL improvements, you’ll need to use a professional-grade Web server, so if you don’t want to use Nginx, you’ll need to find a server that supports these Settings, and the list is pretty small. I know Apache does, but beyond that, I don’t know anything else.