Secure way to connect to MongoDB Atlas from PythonAnywhere

Hey there,

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
https://help.pythonanywhere.com/pages/MongoDB/

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
    )

Hi @Philipp_Wuerfel,

The following to guide covers how to create the api key for your projects:

https://docs.atlas.mongodb.com/configure-api-access

Please let me know if that works.

Thanks
Pavel

2 Likes

Thank you so much @Pavel_Duchovny - it worked! :tada:

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:

https://docs.atlas.mongodb.com/reference/api/whitelist-add-one

The API Key can be created on the Project Level only - Organization Level is not necessary:
Scroll down to Create One API Key for One Project in https://docs.atlas.mongodb.com/configure-api-access

import os
import mongoengine
from dotenv import load_dotenv

# IP-Handling on MongoDB Atlas
import requests
from requests.auth import HTTPDigestAuth

load_dotenv()  # initialize / load env variables
DB_URI = os.getenv("TEST_DB_URI")
db = mongoengine.connect(host=DB_URI)

# whitelist current IP adress via API Key on MongoDB Atlas
# source: https://help.pythonanywhere.com/pages/MongoDB/
print("Whitelisting...")
atlas_group_id = os.getenv("ATLAS_GROUP_ID")
atlas_public_key = os.getenv("ATLAS_PUBLIC_KEY")
atlas_private_key = os.getenv("ATLAS_PRIVATE_KEY")

# alternative to receive external ip-adress: https://checkip.amazonaws.com, https://ident.me 
ip = requests.get('https://ident.me').text.strip()

print(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_public_key, atlas_private_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
    )
1 Like

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.

If you want to explore this way I recommend looking into my blog
https://www.mongodb.com/article/building-service-based-atlas-management

The idea is highlighted there for a different problem but its similar. So once you have this webhook your python app can run it providing the IP.

Thanks
Pavel

1 Like

@Pavel_Duchovny

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.

@Pavel_Duchovny

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.

You have been a great help! :clap:

2 Likes

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.