Mutual TLS (mTLS) with FastAPI and Uvicorn
The Challenge:
- Help others who may want to configure mTLS with FastAPI
- Clarify some of the lacking documentation (or things people know but may not have explained)
- Document the approach (here)
The Approach
We Need two CAs:
- A public CA (trusted) to create a secure channel
- A validation CA which can issue client certs to be validated
- A validation CA can be based on a public CA (however issuing client certs may be expensive) and Internal CA Based on Something Like Active Directory or a Self Manage CA.
- I am going to use a Self Managed CA.
Nuts and Bolts
- To create a Self Managed CA for Validation I used CFSSL (you can use OpenSSL or others)
- The Article I followed is here: https://medium.com/@rob.blackbourn/how-to-use-cfssl-to-create-self-signed-certificates-d55f76ba5781
- As I am using a local environment I need to add a Fully Qualified Domain Name (FQDN) so my certificate is trusted. As I am using windows I need to edit the hosts file. Below is how I set it up for a domain I called fastapi-mtls.cryptoroo.xyz
- Create a validation_fullachain.pem for your validation CA. Below is my fullchain which has the Cryptoroo Root CA and the FastAPI MTLS Proofs Intermediate.
- Create a Leaf Certificate Signed by the FastAPI MTLS Proofs Intermediate. Note: usually the Common Name is all that is needed for a MTLS Client Cert but you can add other extensions (e.g. OU, etc). Make sure that you add the Client Authentication Extended key usage.
Testing and Implementing
Phase 1: Ensure TLS/SSL is working
- Run the command below. This should be the path to your Public CA Certificates.
- For Let’s Encrypt it is the fullchain.pem and the privkey.pem files. Note: I use windows but linux clients should be /live/{domain}/ as well.
- Note: I am using port 443 for a specific reason to make the testing consistent.
- Note: You will need to run this as administrator on windows in order to be able to open the Certbot certificates.
uvicorn main:app — ssl-certfile <fullchain certificate in PEM> — ssl-keyfile <RSA / ECC Private Key> — port 443
- Browse to your domain name (mine is fastapi-mtls.cryptoroo.xyz) and inspect the certificate. If you see Let’s Encrypt and a green padlock you are good to go to the next phase.
Phase 2: For Testing CERT_OPTIONAL Configuration
- Review the functioning of the CERT_OPTIONAL flag here. https://docs.python.org/3/library/ssl.html#ssl.CERT_OPTIONAL
- Essentially the browser will ask for a certificate but if one is not presented it will allow users through.
- Note: I can’t seem to be able to get the browser to show my self signed certificate so in the next phase I will use Postman and OpenSSL for verification
uvicorn main:app
— ssl-certfile <fullchain certificate in PEM> — ssl-keyfile <RSA / ECC Private Key>
— ssl-cert-reqs 1 --ssl-ca-certs <fullchain certificates in PEM>
— port 443
Browser Asking for a Certificate.
Clicking Cancel
- Not sending a certificate still allows you to use the service.
Phase 3: For Testing CERT_REQUIRED Configuration
- Review the functioning of the CERT_REQUIRED flag here. https://docs.python.org/3/library/ssl.html#ssl.CERT_REQUIRED
- We will use Postman and OpenSSL instead of the browser for these tests (see above that my personal certificates aren’t all showing so we can’t pass them in the browser)
uvicorn main:app
— ssl-certfile C:\Certbot\live\cryptoroo.xyz\fullchain.pem — ssl-keyfile C:\Certbot\live\cryptoroo.xyz\privkey.pem
— ssl-cert-reqs 2 — ssl-ca-certs E:\GITHUB\cryptoroo_ca\complete.crt
— port 443
Postman Call with no Client Cert:
Configuring Postman to Use SSL Certificates for https://fastapi-mtls.cryptoroo.xyz/docs
Refer to here for more details: https://learning.postman.com/docs/sending-requests/certificates/
Success!!:
Verify that the certificate is in fact passed in:
Testing With OpenSSL:
openssl s_client -connect fastapi-mtls.cryptoroo.xyz:443 -cert <path to your mtls cert> -key <path to your mtls key>
We then Should get a command Prompt at the bottom:
Type In the Following to test a GET to the Root (/)
GET / HTTP/1.1
Host: fastapi-mtls.cryptoroo.xyz
Summary:
- mTLS is quite possible and easy to configure with FastAPI
- Uvicorn provides some tips on how to set this up but it does require knowledge of Python TLS/SSL library
- There is an assumed knowledge of TLS and which arguments perform which function
- Testing in Browser is hard as the personal keys don’t show up
To Do:
- Demo using CURL
- Fix Browser SSL Certificate Issue
- Video Demos
- Demo of how to create a Validation CA