Skip to content

Instantly share code, notes, and snippets.

@the-vampiire
Last active March 26, 2024 08:26
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save the-vampiire/0747eecf19dfaecf0d8fdf07eb3924e5 to your computer and use it in GitHub Desktop.
Save the-vampiire/0747eecf19dfaecf0d8fdf07eb3924e5 to your computer and use it in GitHub Desktop.
docker entrypoint to automatically whitelist mongo atlas IP

why

mongo atlas provides a reasonably priced access to a managed mongo DB. CSPs where containers are hosted charge too much for their managed mongo DB. they all suggest setting an insecure CIDR (0.0.0.0/0) to allow the container to access the cluster. this is obviously ridiculous.

this entrypoint script is surgical to maintain least privileged access. only the current hosted IP address of the service is whitelisted.

related searches, hope this shows up for you

  • "How to connect to mongodb atlas cluster to container app service EKS ECS cloudrun compute engine"
  • "mongodb atlas cluster access without using whitelist 0.0.0.0"
  • "secure mongodb atlas cluster access on digital ocean GCP AWS vultr heroku"

usage

  • set as the entrypoint for the Dockerfile (after executing it will exec the CMD on the last line)
  • or use as cloud init / startup script in VM (remove the last line, exec "$@")

behavior

uses the mongo atlas project IP access list endpoints

  • will detect the hosted IP address of the container and whitelist it with the cluster using the mongo atlas API
  • if the service has no whitelist entry it is created
  • if the service has an existing whitelist entry that matches current IP no change
  • if the service IP has changed the old entry is deleted and new one is created

when a whitelist entry is created the service sleeps for 60s to wait for atlas to propagate access to the cluster

env

setup

  1. create an API key for the project
  • for name set whitelist management: <project name> so it is easily identifiable across your projects
  • for Project Permissions select Project Owner role
  • do not define an IP access list for the key - it must be usable from any IP for the script to work

note: elsewhere in the docs it is claimed you have to first create an org key then "invite" it to the project. this is not necessary. just creating the project API key will automatically create an org key with the correct role scope of Organization Member.

if you go the long route make sure you set the correct scope for the org key. it should be Organization Member NOT Organization Owner. the latter is effectively a "root" scope which is far too much privilege. The Organization Member combined with a project key role of Project Owner will give the least access necessary for the script to operate on the cluster associated with the project

  1. copy the public key (MONGO_ATLAS_API_PK) and secret key (MONGO_ATLAS_API_SK)
  2. go to project settings page (Settings in the left nav-bar) and copy the project ID (MONGO_ATLAS_API_PROJECT_ID)

confirm you have the correct configuration for the key

  • the org key should have Organization Member
    • you can view this at the top Access Manager > Organization Access > API Keys tab
  • the project key (really is just the org key scoped to this project) should have Project Owner
  • neither key should have any access list restriction

provide the following values in the runtime env of the service executing the script

  • SERVICE_NAME: unique name used for creating / updating (deleting old) whitelist entry
  • MONGO_ATLAS_API_PK: step 3
  • MONGO_ATLAS_API_SK: step 3
  • MONGO_ATLAS_API_PROJECT_ID: step 4

deps

# alpine / apk
apk update \
  && apk add --no-cache \
     bash \
     curl \
     jq
     
# ubuntu / apt
export DEBIAN_FRONTEND=noninteractive \
  && apt-get update  \
  && apt-get -y install \
     bash \
     curl \
     jq
#!/usr/bin/env bash
# -- ENV -- #
# SERVICE_NAME
# MONGO_ATLAS_API_PK
# MONGO_ATLAS_API_SK
# MONGO_ATLAS_API_PROJECT_ID
# -- ENV -- #
set -e
mongo_api_base_url='https://cloud.mongodb.com/api/atlas/v1.0'
check_for_deps() {
deps=(
bash
curl
jq
)
for dep in "${deps[@]}"; do
if [ ! "$(command -v $dep)" ]
then
echo "dependency [$dep] not found. exiting"
exit 1
fi
done
}
make_mongo_api_request() {
local request_method="$1"
local request_url="$2"
local data="$3"
curl \
--silent \
--user "$MONGO_ATLAS_API_PK:$MONGO_ATLAS_API_SK" --digest \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--request "$request_method" "$request_url" \
--data "$data"
}
get_access_list_endpoint() {
echo -n "$mongo_api_base_url/groups/$MONGO_ATLAS_API_PROJECT_ID/accessList"
}
get_service_ip() {
echo -n "$(curl https://ipinfo.io/ip -s)"
}
get_previous_service_ip() {
local access_list_endpoint=$(get_access_list_endpoint)
local previous_ip=$(make_mongo_api_request 'GET' "$access_list_endpoint" \
| jq --arg SERVICE_NAME "$SERVICE_NAME" -r \
'.results[]? as $results | $results.comment | if test("\\[\($SERVICE_NAME)\\]") then $results.ipAddress else empty end'
)
echo "$previous_ip"
}
whitelist_service_ip() {
local current_service_ip="$1"
local comment="Hosted IP of [$SERVICE_NAME] [set@$(date +%s)]"
if (( "${#comment}" > 80 )); then
echo "comment field value will be above 80 char limit: \"$comment\""
echo "comment would be too long due to length of service name [$SERVICE_NAME] [${#SERVICE_NAME}]"
echo "change comment format or service name then retry. exiting to avoid mongo API failure"
exit 1
fi
echo "whitelisting service IP [$current_service_ip] with comment value: \"$comment\""
response=$(make_mongo_api_request \
'POST' \
"$(get_access_list_endpoint)?pretty=true" \
"[
{
\"comment\" : \"$comment\",
\"ipAddress\": \"$current_service_ip\"
}
]" \
| jq -r 'if .error then . else empty end'
)
if [[ -n "$response" ]];
then
echo 'API error whitelisting service'
echo "$response"
exit 1
else
echo "whitelist request successful"
echo "waiting 60s for whitelist to propagate to cluster"
sleep 60
fi
}
delete_previous_service_ip() {
local previous_service_ip="$1"
echo "deleting previous service IP address of [$SERVICE_NAME]"
make_mongo_api_request \
'DELETE' \
"$(get_access_list_endpoint)/$previous_service_ip"
}
set_mongo_whitelist_for_service_ip() {
local current_service_ip=$(get_service_ip)
local previous_service_ip=$(get_previous_service_ip)
if [[ -z "$previous_service_ip" ]]; then
echo "service [$SERVICE_NAME] has not yet been whitelisted"
whitelist_service_ip "$current_service_ip"
elif [[ "$current_service_ip" == "$previous_service_ip" ]]; then
echo "service [$SERVICE_NAME] IP has not changed"
else
echo "service [$SERVICE_NAME] IP has changed from [$previous_service_ip] to [$current_service_ip]"
delete_previous_service_ip "$previous_service_ip"
whitelist_service_ip "$current_service_ip"
fi
}
check_for_deps
set_mongo_whitelist_for_service_ip
# run CMD
exec "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment