we currently deploy our prototype on PythonAnywhere. We work with Django and use mongoengine for connections to MongoDB Atlas.
The problem is, that Atlas requires to whitelist the IP but PythonAnywhere does not give me a static IP address - it depends on the time the code of the Django application runs.
Whitelisting 0.0.0.0/0 is too risky as we don’t want to open connection from anywhere.
PythonAnywhere recommends to use some sort of atlas_api_key to request a whitelist of the actual IP but I don’t know where I can generate this API key. Is this with a service like Realm? Or might this code example just be out of date? Is this approach even more secure?
Here is the link to the code example I found on PythonAnywhere
import requests
from requests.auth import HTTPDigestAuth
from ipify import get_ip
atlas_group_id = "<your group ID aka project ID -- check the Project / Settings section inside Atlas>"
atlas_username = "<your atlas username/email, eg. jane@example.com>"
atlas_api_key = "<your atlas API key>"
ip = get_ip()
resp = requests.post(
"https://cloud.mongodb.com/api/atlas/v1.0/groups/{atlas_group_id}/whitelist".format(atlas_group_id=atlas_group_id),
auth=HTTPDigestAuth(atlas_username, atlas_api_key),
json=[{'ipAddress': ip, 'comment': 'From PythonAnywhere'}] # the comment is optional
)
if resp.status_code in (200, 201):
print("MongoDB Atlas whitelist request successful", flush=True)
else:
print(
"MongoDB Atlas whitelist request problem: status code was {status_code}, content was {content}".format(
status_code=resp.status_code, content=resp.content
),
flush=True
)
To make this thread complete:
The package ipify is still not updated and will not work for Python Versions newer than 3.8.
I posted an updated version of the code which will work on Python 3.8 and used request to ident.me to receive my external ip-adress. I also updated the naming of the variables necessary for authentication as we use private - public key pair and not username - api key. This ressource from the documentation helped me to understand a little bit better what is going on:
I still have a question though:
As PythonAnywhere IP adress is not static I can’t whitelist with the standard way. Allowing “access from anywhere” is not good practise and to handle this I use an API Key to request a whitelist entry from application level. Still I need to add the IP adress from the instance on PythonAnywhere to the IP Access List of the generated API Key. This again only works if I allow “access from anywhere” or at least a range of all IP adresses my PythonAnywhere instance might get as the IP adress is not static.
Maybe I get this wrong and I am curious about the advantage here. I basically only allow access via dbuser if my application with the api key was able to add it’s external IP to the whitelist.
The “hacker” now needs to steal private and public key value pair to open database network access and than also needs to steal dbuser - password pair to finally gain access to my db. Did I get this right?
@Philipp_Wuerfel, yea this workaround they offer is not super secure. Therefore, if you could run this application in a cloud container VPC peered to your cloud region it will be much better.
Btw you can outsource the code that whitelist Ips to a realm webhook and keep your keys as secret.
Thank you! I will take a look at it!
For now I wrote a script that regulary checks the ip ranges of aws and it notifies me in case of any changes.
It compares an update on https://ip-ranges.amazonaws.com/ip-ranges.json
PythonAnywhere uses only us-east-1 or eu-central-1 so I limited the range of allowed ip adresses on the access list based on all possible ip ranges from these two server locations. If it needs an update I can run an update script to update my ip access list. It is not the most secure solution but at least better than allowing access from anywhere. In future I might move away to a service provider allowing me to set up a static external ip adress from which I can run my application from. I wanted to start my project on PythonAnywhere to have things simple in setup and costs.
Thanks again! I used your blog and now I think I came up with a solid solution.
The application on PythonAnywhere connects to a webhook created with the Realm service and secured by payload signature. On the webhook I have the api keys as secrets and this requests an ip whitelist for network access to my database for my application.
Now I am independent from ip changes on PythonAnywhere while still having a solid security on my database as I only allow access from a single ip. This setup should be enough for prototyping.