Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PYTHON-5157 Convert aws tests to use python scripts #624

Merged
merged 20 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 13 additions & 88 deletions .evergreen/auth_aws/aws_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,108 +6,33 @@
# . ./aws_setup.sh <test-name>
#
# Handles AWS credential setup and exports relevant environment variables.
# Assumes you have already set up secrets.
# Sets up secrets if they have not already been set up.
set -eu

SCRIPT_DIR=$(dirname ${BASH_SOURCE[0]})
. $SCRIPT_DIR/../handle-paths.sh
pushd $SCRIPT_DIR

# Ensure that secrets have already been set up.
if [ ! -f "secrets-export.sh" ]; then
echo "ERROR: please run './setup-secrets.sh' in this folder"
fi

# Activate the venv and source the secrets file.
. ./activate-authawsvenv.sh
source secrets-export.sh

if [ "$1" == "web-identity" ]; then
export AWS_WEB_IDENTITY_TOKEN_FILE="./token_file.txt"
fi

# Handle the test setup if not using env variables.
case $1 in
session-creds)
echo "Running aws_tester.py with assume-role"
# Set up credentials with assume-role to create user in MongoDB and write AWS credentials.
python aws_tester.py "assume-role"
;;
env-creds)
echo "Running aws_tester.py with regular"
# Set up credentials with regular to create user in MongoDB and write AWS credentials.
python aws_tester.py "regular"
;;
*)
python aws_tester.py "$1"
;;
esac

# If this is ecs, exit now.
if [ "$1" == "ecs" ]; then
exit 0
# Ensure that secrets have already been set up.
if [ ! -f "./secrets-export.sh" ]; then
bash ./setup-secrets.sh
fi

# Convenience functions.
urlencode () {
python -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote_plus(sys.argv[1]))" "$1"
}
# Remove any AWS creds that might be set in the parent env.
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN

jsonkey () {
python -c "import json,sys;sys.stdout.write(json.load(sys.stdin)[sys.argv[1]])" "$1" < ./creds.json
}
source ./secrets-export.sh

# Handle extra vars based on auth type.
USER=""
case $1 in
assume-role)
USER=$(jsonkey AccessKeyId)
USER=$(urlencode "$USER")
PASS=$(jsonkey SecretAccessKey)
PASS=$(urlencode "$PASS")
SESSION_TOKEN=$(jsonkey SessionToken)
SESSION_TOKEN=$(urlencode "$SESSION_TOKEN")
;;

session-creds)
AWS_ACCESS_KEY_ID=$(jsonkey AccessKeyId)
AWS_SECRET_ACCESS_KEY=$(jsonkey SecretAccessKey)
AWS_SESSION_TOKEN=$(jsonkey SessionToken)

export AWS_ACCESS_KEY_ID
export AWS_SECRET_ACCESS_KEY
export AWS_SESSION_TOKEN
;;

web-identity)
export AWS_ROLE_ARN=$IAM_AUTH_ASSUME_WEB_ROLE_NAME
export AWS_WEB_IDENTITY_TOKEN_FILE="$SCRIPT_DIR/$AWS_WEB_IDENTITY_TOKEN_FILE"
;;

regular)
USER=$(urlencode "${IAM_AUTH_ECS_ACCOUNT}")
PASS=$(urlencode "${IAM_AUTH_ECS_SECRET_ACCESS_KEY}")
;;

env-creds)
export AWS_ACCESS_KEY_ID=$IAM_AUTH_ECS_ACCOUNT
export AWS_SECRET_ACCESS_KEY=$IAM_AUTH_ECS_SECRET_ACCESS_KEY
;;
esac

# Handle the URI.
if [ -n "$USER" ]; then
MONGODB_URI="mongodb://$USER:$PASS@localhost"
export USER
export PASS
else
MONGODB_URI="mongodb://localhost"
fi
MONGODB_URI="${MONGODB_URI}/aws?authMechanism=MONGODB-AWS"
if [[ -n ${SESSION_TOKEN:-} ]]; then
MONGODB_URI="${MONGODB_URI}&authMechanismProperties=AWS_SESSION_TOKEN:${SESSION_TOKEN}"
if [ -f $SCRIPT_DIR/test-env.sh ]; then
rm $SCRIPT_DIR/test-env.sh
fi

export MONGODB_URI="$MONGODB_URI"
python aws_tester.py "$1"
source $SCRIPT_DIR/test-env.sh

popd
84 changes: 75 additions & 9 deletions .evergreen/auth_aws/aws_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,27 @@

import argparse
import json
import logging
import os
import subprocess
import sys
from functools import partial
from pathlib import Path
from urllib.parse import quote_plus

from pymongo import MongoClient
from pymongo.errors import OperationFailure

HERE = os.path.abspath(os.path.dirname(__file__))
HERE = Path(__file__).absolute().parent
LOGGER = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")


def join(*parts):
return os.path.join(*parts).replace(os.sep, "/")


sys.path.insert(0, join(HERE, "lib"))
sys.path.insert(0, str(HERE / "lib"))
from aws_assign_instance_profile import _assign_instance_policy
from aws_assume_role import _assume_role
from aws_assume_web_role import _assume_role_with_web_identity
Expand All @@ -35,7 +39,7 @@ def join(*parts):
_USE_AWS_SECRETS = False

try:
with open(join(HERE, "aws_e2e_setup.json")) as fid:
with (HERE / "aws_e2e_setup.json").open() as fid:
CONFIG = json.load(fid)
get_key = partial(_get_key, uppercase=False)
except FileNotFoundError:
Expand All @@ -51,7 +55,7 @@ def run(args, env):

def create_user(user, kwargs):
"""Create a user and verify access."""
print("Creating user", user)
LOGGER.info("Creating user %s", user)
client = MongoClient(username="bob", password="pwd123")
db = client["$external"]
try:
Expand All @@ -76,7 +80,7 @@ def setup_assume_role():

role_name = CONFIG[get_key("iam_auth_assume_role_name")]
creds = _assume_role(role_name, quiet=True)
with open(join(HERE, "creds.json"), "w") as fid:
with (HERE / "creds.json").open("w") as fid:
json.dump(creds, fid)

# Create the user.
Expand All @@ -87,6 +91,11 @@ def setup_assume_role():
authmechanismproperties=f"AWS_SESSION_TOKEN:{token}",
)
create_user(ASSUMED_ROLE, kwargs)
return dict(
USER=kwargs["username"],
PASS=kwargs["password"],
SESSION_TOKEN=creds["SessionToken"],
)


def setup_ec2():
Expand All @@ -95,6 +104,7 @@ def setup_ec2():
os.environ.pop("AWS_ACCESS_KEY_ID", None)
os.environ.pop("AWS_SECRET_ACCESS_KEY", None)
create_user(AWS_ACCOUNT_ARN, dict())
return dict()


def setup_ecs():
Expand Down Expand Up @@ -138,6 +148,18 @@ def setup_ecs():
# Run the test in a container
subprocess.check_call(["/bin/sh", "-c", run_test_command], env=env)

return dict()


def setup_session_creds():
# Set up the assume role user, and export the aws vars.
creds = setup_assume_role()
return dict(
AWS_ACCESS_KEY_ID=creds["USER"],
AWS_SECRET_ACCESS_KEY=creds["PASS"],
AWS_SESSION_TOKEN=creds["SESSION_TOKEN"],
)


def setup_regular():
# Create the user.
Expand All @@ -147,6 +169,14 @@ def setup_regular():
)
create_user(CONFIG[get_key("iam_auth_ecs_account_arn")], kwargs)

return dict(USER=kwargs["username"], PASS=kwargs["password"])


def setup_env_creds():
# Set up the regular user, but export the creds as environment vars.
creds = setup_regular()
return dict(AWS_ACCESS_KEY_ID=creds["USER"], AWS_SECRET_ACCESS_KEY=creds["PASS"])


def setup_web_identity():
# Unassign the instance profile.
Expand All @@ -161,7 +191,7 @@ def setup_web_identity():
raise RuntimeError("Request limit exceeded for AWS API")

if ret != 0:
print("ret was", ret)
LOGGER.debug("return code was %s", ret)
raise RuntimeError(
"Failed to unassign an instance profile from the current machine"
)
Expand All @@ -186,10 +216,11 @@ def setup_web_identity():

# Assume the web role to get temp credentials.
os.environ["AWS_WEB_IDENTITY_TOKEN_FILE"] = token_file
os.environ["AWS_ROLE_ARN"] = CONFIG[get_key("iam_auth_assume_web_role_name")]
role_arn = CONFIG[get_key("iam_auth_assume_web_role_name")]
os.environ["AWS_ROLE_ARN"] = role_arn

creds = _assume_role_with_web_identity(True)
with open(join(HERE, "creds.json"), "w") as fid:
with (HERE / "creds.json").open("w") as fid:
json.dump(creds, fid)

# Create the user.
Expand All @@ -201,6 +232,31 @@ def setup_web_identity():
)
create_user(ASSUMED_WEB_ROLE, kwargs)

return dict(AWS_WEB_IDENTITY_TOKEN_FILE=token_file, AWS_ROLE_ARN=role_arn)


def handle_creds(creds: dict):
if "USER" in creds:
USER = quote_plus(creds["USER"])
PASS = quote_plus(creds["PASS"])
MONGODB_URI = f"mongodb://{USER}:{PASS}@localhost"
else:
MONGODB_URI = "mongodb://localhost"
MONGODB_URI = f"{MONGODB_URI}/aws?authMechanism=MONGODB-AWS"
if "SESSION_TOKEN" in creds:
SESSION_TOKEN = quote_plus(creds["SESSION_TOKEN"])
MONGODB_URI = (
f"{MONGODB_URI}&authMechanismProperties=AWS_SESSION_TOKEN:{SESSION_TOKEN}"
)
with (HERE / "test-env.sh").open("w", newline="\n") as fid:
fid.write("#!/usr/bin/env bash\n\n")
fid.write("set +x\n")
for key, value in creds.items():
if key in ["USER", "PASS", "SESSION_TOKEN"]:
value = quote_plus(value) # noqa: PLW2901
fid.write(f"export {key}={value}\n")
fid.write(f"export MONGODB_URI={MONGODB_URI}\n")


def main():
parser = argparse.ArgumentParser(description="MONGODB-AWS tester.")
Expand All @@ -218,11 +274,21 @@ def main():
run_regular_cmd = sub.add_parser("regular", help="Regular credentials test")
run_regular_cmd.set_defaults(func=setup_regular)

run_session_creds_cmd = sub.add_parser("session-creds", help="Session credentials")
run_session_creds_cmd.set_defaults(func=setup_session_creds)

run_env_creds_cmd = sub.add_parser("env-creds", help="Environment credentials")
run_env_creds_cmd.set_defaults(func=setup_env_creds)

run_web_identity_cmd = sub.add_parser("web-identity", help="Web identity test")
run_web_identity_cmd.set_defaults(func=setup_web_identity)

args = parser.parse_args()
args.func()
func_name = args.func.__name__.replace("setup_", "").replace("_", "-")
LOGGER.info("Running aws_tester.py with %s...", func_name)
creds = args.func()
handle_creds(creds)
LOGGER.info("Running aws_tester.py with %s... done.", func_name)


if __name__ == "__main__":
Expand Down
18 changes: 9 additions & 9 deletions .evergreen/auth_aws/lib/aws_assign_instance_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,23 @@ def _get_local_instance_id():
def _has_instance_profile():
base_url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
try:
print("Reading: " + base_url)
LOGGER.info("Reading: " + base_url)
iam_role = urllib.request.urlopen(base_url).read().decode()
except urllib.error.HTTPError as e:
print(e)
if e.code == 404:
return False
LOGGER.error(e)
raise e

try:
url = base_url + iam_role
print("Reading: " + url)
LOGGER.info("Reading: " + url)
_ = urllib.request.urlopen(url)
print("Assigned " + iam_role)
LOGGER.info("Assigned " + iam_role)
except urllib.error.HTTPError as e:
print(e)
if e.code == 404:
return False
LOGGER.error(e)
raise e

return True
Expand Down Expand Up @@ -85,7 +85,7 @@ def _handle_config():
)
return CONFIG[get_key("iam_auth_ec2_instance_profile")]
except Exception as e:
print(e)
LOGGER.error(e)
return ""


Expand All @@ -94,7 +94,7 @@ def _handle_config():

def _assign_instance_policy(iam_instance_arn=DEFAULT_ARN):
if _has_instance_profile():
print(
LOGGER.warning(
"IMPORTANT: Found machine already has instance profile, skipping the assignment"
)
return
Expand All @@ -112,14 +112,14 @@ def _assign_instance_policy(iam_instance_arn=DEFAULT_ARN):
InstanceId=instance_id,
)

print(response)
LOGGER.debug(response)

# Wait for the instance profile to be assigned by polling the local instance metadata service
_wait_instance_profile()

except botocore.exceptions.ClientError as ce:
if ce.response["Error"]["Code"] == "RequestLimitExceeded":
print("WARNING: RequestLimitExceeded, exiting with error code 2")
LOGGER.warning("WARNING: RequestLimitExceeded, exiting with error code 2")
sys.exit(2)
raise

Expand Down
Loading
Loading