In my previous post, Creating an API with python: Part 5: Authentication, I added oauth2 authentication to the FastAPI API. In this post, I add an nginx proxy. This enables three things:
- The API can be served over HTTPS
- Requests to HTTP will be forwarded to HTTPS
- We can serve the API over a more ‘normal’ URL, without needing the port 8000 part
Prerequisites
These prerequisites are assumed for this post, however the nginx proxy setup steps involved could be applied to any API implementation served over port 8000 (or any other port).
- Creating an API with python: Part 1: GET Endpoints
- Creating an API with python: Part 2: MariaDB Database
- Creating an API with python: Part 3: POST Endpoints
- Creating an API with python: Part 4: DELETE Endpoints
- Creating an API with python: Part 5: Authentication
Step 1: Create Self-Signed SSL Certs
In order to serve the API over HTTPS, we will need SSL certs. For a production environment, we would need to get certificates for the domain over which we are serving the API. We would get them from an official certificate authority (such as a web service provider). However, for the purposes of development, we can create our own self-signed certs. Note that these will result in browsers issuing security warnings, however you can usually choose to ignore the warnings and proceed to the site anyway. Another option is to upload the CA root cert that you create to the browser. There are many ways to create self-signed certs, but I found the article here particularly helpful: https://devopscube.com/create-self-signed-certificates-openssl/. The following instructions are based on that article, with a few minor tweaks.
- Navigate to the home directory of your virtual machine. Create an openssl directory and change directory to it.
$ cd $ mkdir openssl && cd openssl
- Run the following to generate a root cert and key. Be sure to replace <YOUR_IP> with the IP of your virtual machine.
$ openssl req -x509 \ -sha256 -days 356 \ -nodes \ -newkey rsa:2048 \ -subj "/CN=<YOUR_IP>/C=US/L=San Fransisco" \ -keyout rootCA.key -out rootCA.crt
- Generate a server private key:
$ openssl genrsa -out server.key 2048
- Create a CSR (Certificate Signing Request) configuration. Be sure to replace each instance of <YOUR_IP> with the IP of your virtual machine, and the O and OU entries with your project name:
$ cat > csr.conf <<EOF [ req ] default_bits = 2048 prompt = no default_md = sha256 req_extensions = req_ext distinguished_name = dn [ dn ] C = US ST = California L = San Fransisco O = AllTheCoding OU = AllTheCoding Dev CN = <YOUR_IP> [ req_ext ] subjectAltName = @alt_names [ alt_names ] DNS.1 = <YOUR_IP> IP.1 = <YOUR_IP> EOF
- Generate a CSR (Certificate Signing Request):
$ openssl req -new -key server.key -out server.csr -config csr.conf
- Create a
cert.conf
file. Again, replace each instance of <YOUR_IP> with your virtual machine IP:$ cat > cert.conf <<EOF authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = <YOUR_IP> IP.1 = <YOUR_IP> EOF
- Generate the SSL certificate:
$ openssl x509 -req \ -in server.csr \ -CA rootCA.crt -CAkey rootCA.key \ -CAcreateserial -out server.crt \ -days 365 \ -sha256 -extfile cert.conf
- Once you’ve created your certs, copy them to the
/etc/ssl/certs
directory on your server:$ sudo su $ cp server.key /etc/ssl/certs $ cp server.crt /etc/ssl/certs $ cp rootCA.crt /etc/ssl/certs $ cp rootCA.key /etc/ssl/certs
Step 2: Install Nginx
Nginx is open-source web server software. We are going to use it to proxy requests from the server URL https://<YOUR_IP>/api
to the actual location of the API, at http://<YOUR_IP>:8000
. In addition, we will forward any requests over HTTP to HTTPS, to make sure we are always encrypting all traffic.
- Install the
epel-release
repo:$ sudo yum install epel-release
- Install nginx:
$ sudo yum install nginx
- If you have
firewalld
running on your server, you will need to run the following commands to allow traffic over HTTP and HTTPS:$ sudo firewall-cmd --permanent --zone=public --add-service=http $ sudo firewall-cmd --permanent --zone=public --add-service=https $ sudo firewall-cmd --reload
- Start nginx:
$ sudo systemctl start nginx
- Enable nginx to start at boot:
$ sudo systemctl enable nginx
- If you now navigate to
http://<YOUR_IP>/
(replace<YOUR_IP>
with your server IP), you should see the default CentOS7 nginx web page. If so, that means nginx is working.
Step 3: Configure nginx
Now we need to configure nginx to proxy HTTP to HTTPS and forward requests from https://<YOUR_IP>/api
to http://<YOUR_IP>:8000
.
-
Create a new nginx configuration file:
$ cd /etc/nginx/conf.d $ sudo vim api.conf
Inside the file, paste in the following configuration:server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name _; ssl_certificate /etc/ssl/certs/server.crt; ssl_certificate_key /etc/ssl/certs/server.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; access_log /var/log/nginx/nginx.vhost.access.log; error_log /var/log/nginx/nginx.vhost.error.log; location ^~/api/ { rewrite ^/api/(.*)$ /$1 break; proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Request-Id $http_x_request_id; proxy_set_header X-Cert-Issuer-DN $ssl_client_i_dn; proxy_set_header X-Cert-Subject-DN $ssl_client_s_dn; } } server { listen 80; listen [::]:80; server_name _; return 301 https://$host$request_uri; }
- If SELinux is running, you may need to permit httpd to connect over the network. To check if SELinux is running, run:
$ sestatus
If you see this at the top of the output, then SELinux is enabled:SELinux status: enabled
If it’s enabled, run this command to enablehttpd_can_network_connect
:$ setsebool httpd_can_network_connect on -P
- Now restart nginx:
$ sudo systemctl restart nginx
Step 4: Start FastAPI
We have to tell FastAPI that it’s running at a different base path (i.e. /api
). This is so that internal links from the Swagger and ReDoc docs will be correct.
- On your server, change to the code directory (
~/vboxshare/fastapi
should be replaced with the path to your FastAPI python code):$ cd ~/vboxshare/fastapi
-
Run the FastAPI server with a slightly modified command:
$ . ~/.venv-fastapi/bin/activate (.venv-fastapi) $ uvicorn --host 0.0.0.0 main:app --root-path /api --reload
Note the addition of--root-path /api
.
Step 5: Test new URLs
Test that the FastAPI API is served over the new URLs.
-
Navigate to
https://<YOUR_IP>/api/docs
in your browser. Be sure to replace<YOUR_IP>
with your server IP. The swagger page should load. If you try authenticating and making requests, the requests should go to the correct URL (https://<YOUR_IP>/api/
) and return responses correctly. -
Navigate to
http://<YOUR_IP>/api/docs
in your browser. Check that the request is forwarded tohttps://<YOUR_IP>/api/docs
.
Conclusion
You should now have a FastAPI API running with a proxy, which serves the API over a cleaner URL over HTTPS and forwards any requests over HTTP to HTTPS.
If you want to find out how to add CORS (Cross-Origin Resource Scripting) support to the API, see my follow-on post, Creating an API with python: Part 7: CORS.
Thanks for reading!
Recent Comments