Skip to content

Commit

Permalink
CDRIVER-4439 add AWS credential cache (#1207)
Browse files Browse the repository at this point in the history
* add test-awsauth

test-awsauth is intended to replace mongoc-ping in AWS tests.
test-awsauth will include caching tests specific to AWS.

* unconditionally define _mongoc_aws_credentials_cleanup

There is no reason to condition on the presence of ENABLE_MONGODB_AWS_AUTH

Removing reduces duplicate definitions.

* add AWS credential cache

add _mongoc_aws_credentials_copy_to

add _mongoc_aws_credentials_cache_t

* use AWS credential cache

* use test-awsauth in Evergreen

* remove outdated docs of optional variables

* fix docs to note TESTCASE is an env var

* remove unnecessary additions runtime paths

test-awsauth statically links the C driver

* use `mcd_timer` for expiration

* remove unused vars

* simplify bash scripts

* fix comment for `expiration`

* be more explicit about _mongoc_aws_credentials_cache_get behavior

* rename helper to expiration_to_mcd_timer

* compute in int64_t domain

* remove unnecessary expiration vars

* add MONGOC_AWS_CREDENTIALS_INIT macro

* only initialize and cleanup AWS cache if AWS is enabled

* remove unnecessary can_setenv

* fix -Wmissing-braces warning

* fix signature of test_aws_cache

* do not support uninitialized creds in _mongoc_aws_credentials_cache_get

* do not reuse creds after cleanup

* fix -Wmissing-field-initializers warning
  • Loading branch information
kevinAlbs committed Feb 23, 2023
1 parent 822e78e commit 8eaac64
Show file tree
Hide file tree
Showing 10 changed files with 956 additions and 94 deletions.
4 changes: 2 additions & 2 deletions .evergreen/generated_configs/legacy-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1789,11 +1789,11 @@ tasks:
shell: bash
script: |-
set -o errexit
# Compile mongoc-ping. Disable unnecessary dependencies since mongoc-ping is copied to a remote Ubuntu 18.04 ECS cluster for testing, which may not have all dependent libraries.
# Compile test-awsauth. Disable unnecessary dependencies since test-awsauth is copied to a remote Ubuntu 18.04 ECS cluster for testing, which may not have all dependent libraries.
. .evergreen/scripts/find-cmake.sh
export CC='${CC}'
$CMAKE -DENABLE_SASL=OFF -DENABLE_SNAPPY=OFF -DENABLE_ZSTD=OFF -DENABLE_CLIENT_SIDE_ENCRYPTION=OFF .
$CMAKE --build . --target mongoc-ping
$CMAKE --build . --target test-awsauth
- func: upload-build
- name: test-aws-openssl-regular-latest
depends_on:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -815,11 +815,11 @@ def _check_allowed(self):
all_tasks = chain(all_tasks, IPTask.matrix())

aws_compile_task = NamedTask('debug-compile-aws', commands=[shell_mongoc('''
# Compile mongoc-ping. Disable unnecessary dependencies since mongoc-ping is copied to a remote Ubuntu 18.04 ECS cluster for testing, which may not have all dependent libraries.
# Compile test-awsauth. Disable unnecessary dependencies since test-awsauth is copied to a remote Ubuntu 18.04 ECS cluster for testing, which may not have all dependent libraries.
. .evergreen/scripts/find-cmake.sh
export CC='${CC}'
$CMAKE -DENABLE_SASL=OFF -DENABLE_SNAPPY=OFF -DENABLE_ZSTD=OFF -DENABLE_CLIENT_SIDE_ENCRYPTION=OFF .
$CMAKE --build . --target mongoc-ping
$CMAKE --build . --target test-awsauth
'''), func('upload-build')])

all_tasks = chain(all_tasks, [aws_compile_task])
Expand Down
54 changes: 6 additions & 48 deletions .evergreen/scripts/run-aws-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,12 @@
# Test runner for AWS authentication.
#
# This script is meant to be run in parts (so to isolate the AWS tests).
# Pass the desired test first argument (REGULAR, EC2, ECS, ASSUME_ROLE, LAMBDA)
# Pass the desired test as the environment variable TESTCASE: (REGULAR, EC2, ECS, ASSUME_ROLE, LAMBDA)
#
# Example:
# run-aws-tests.sh EC2
# TESTCASE=EC2 run-aws-tests.sh
#
# Optional environment variables:
#
# drivers_tools_dir
# The path to clone of https://github.com/mongodb-labs/drivers-evergreen-tools.
# Defaults to $(pwd)../drivers-evergreen-tools
# mongoc_dir
# The path to the build of mongo-c-driver (e.g. mongo-c-driver/cmake-build).
# Defaults to $(pwd)
# mongoc_dir
# The path to mongo-c-driver source (may be same as mongoc_dir).
# Defaults to $(pwd)
# mongodb_bin_dir
# The path to mongodb binaries.
# Defaults to $(pwd)/mongodb/bin
# iam_auth_ecs_account and iam_auth_ecs_secret_access_key
# Set to access key id/secret access key. Required for some tests.

Expand All @@ -46,49 +33,20 @@ declare drivers_tools_dir
drivers_tools_dir="$(to_absolute "${mongoc_dir}/../drivers-evergreen-tools")"

declare mongodb_bin_dir="${mongoc_dir}/mongodb/bin"
declare mongoc_ping="${mongoc_dir}/src/libmongoc/mongoc-ping"
declare test_awsauth="${mongoc_dir}/src/libmongoc/test-awsauth"

# Add libmongoc-1.0 and libbson-1.0 to library path, so mongoc-ping can find them at runtime.
if [[ "${OSTYPE}" == "cygwin" ]]; then
export PATH
PATH+=":${mongoc_dir}/src/libmongoc/Debug"
PATH+=":${mongoc_dir}/src/libbson/Debug"

chmod -f +x src/libmongoc/Debug/* src/libbson/Debug/* || true

mongoc_ping="${mongoc_dir}/src/libmongoc/Debug/mongoc-ping.exe"
elif [[ "${OSTYPE}" == darwin* ]]; then
export DYLD_LIBRARY_PATH
DYLD_LIBRARY_PATH+=":${mongoc_dir}/src/libmongoc"
DYLD_LIBRARY_PATH+=":${mongoc_dir}/src/libbson"
else
export LD_LIBRARY_PATH
LD_LIBRARY_PATH+=":${mongoc_dir}/src/libmongoc"
LD_LIBRARY_PATH+=":$mongoc_dir/src/libbson"
test_awsauth="${mongoc_dir}/src/libmongoc/Debug/test-awsauth.exe"
fi

expect_success() {
echo "Should succeed:"
if ! "${mongoc_ping}" "${1:?}"; then
echo "Unexpected auth failure" 1>&2
exit 1
fi
"${test_awsauth}" "${1:?}" "EXPECT_SUCCESS" || exit
}

expect_failure() {
echo "Should fail:"
if "${mongoc_ping}" "${1:?}" >output.txt 2>&1; then
echo "Unexpected - authed but it should not have" 1>&2
exit 1
else
echo "auth failed as expected"
fi

if ! grep "Authentication failed" output.txt >/dev/null; then
echo "Unexpected, error was not an authentication failure:" 1>&2
cat output.txt 1>&2
exit 1
fi
"${test_awsauth}" "${1:?}" "EXPECT_FAILURE" || exit
}

url_encode() {
Expand Down
9 changes: 1 addition & 8 deletions .evergreen/scripts/run-mongodb-aws-ecs-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,10 @@

echo "run-mongodb-aws-ecs-test.sh"

# Set paths so mongoc-ping can find dependencies
export LD_LIBRARY_PATH
LD_LIBRARY_PATH+=":/root/mongoc/src/libmongoc"
LD_LIBRARY_PATH+=":/root/mongoc/src/libbson"

expect_success() {
echo "Should succeed:"
if ! /root/mongoc/src/libmongoc/mongoc-ping "${1:?}"; then
echo "Unexpected auth failure" 1>&2
exit 1
fi
/root/mongoc/src/libmongoc/test-awsauth "${1:?}" "EXPECT_SUCCESS"
}

expect_success "${1:?}"
1 change: 1 addition & 0 deletions src/libmongoc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,7 @@ mongoc_add_test (test-mongoc-gssapi FALSE ${PROJECT_SOURCE_DIR}/tests/test-mongo
mongoc_add_test (test-mongoc-cache FALSE ${PROJECT_SOURCE_DIR}/tests/test-mongoc-cache.c)
mongoc_add_test (test-azurekms FALSE ${PROJECT_SOURCE_DIR}/tests/test-azurekms.c)
mongoc_add_test (test-gcpkms FALSE ${PROJECT_SOURCE_DIR}/tests/test-gcpkms.c)
mongoc_add_test (test-awsauth FALSE ${PROJECT_SOURCE_DIR}/tests/test-awsauth.c)

if (ENABLE_TESTS)
# "make test" doesn't compile tests, so we create "make check" which compiles
Expand Down
90 changes: 89 additions & 1 deletion src/libmongoc/src/mongoc/mongoc-cluster-aws-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
#define MONGOC_CLUSTER_AWS_PRIVATE_H

#include "bson/bson.h"
#include "mcd-time.h"
#include "mongoc/mongoc-cluster-private.h"
#include "common-thread-private.h" // bson_mutex_t

bool
_mongoc_cluster_auth_node_aws (mongoc_cluster_t *cluster,
Expand All @@ -29,18 +31,104 @@ _mongoc_cluster_auth_node_aws (mongoc_cluster_t *cluster,
bson_error_t *error);

/* The following are declared in the private header for testing. It is only used
* in test-mongoc-aws.c and mongoc-cluster.aws.c */
* in test-mongoc-aws.c, mongoc-cluster-aws.c, and test-awsauth.c */
typedef struct {
char *access_key_id;
char *secret_access_key;
char *session_token;
// expiration is the time when these credentials expire.
// If expiration.set is false, the credentials do not have a known
// expiration.
struct {
mcd_timer value;
bool set;
} expiration;
} _mongoc_aws_credentials_t;

#define MONGOC_AWS_CREDENTIALS_INIT \
(_mongoc_aws_credentials_t) \
{ \
.access_key_id = NULL, .secret_access_key = NULL, .session_token = NULL, \
.expiration = {.value = {.expire_at = {0}}, .set = false}, \
}

#define MONGOC_AWS_CREDENTIALS_EXPIRATION_WINDOW_MS 60 * 5 * 1000

// _mongoc_aws_credentials_cache_t is a thread-safe global cache of AWS
// credentials.
typedef struct {
struct {
_mongoc_aws_credentials_t value;
bool set;
} cached;
bson_mutex_t mutex; // guards cached.
} _mongoc_aws_credentials_cache_t;

extern _mongoc_aws_credentials_cache_t mongoc_aws_credentials_cache;

// _mongoc_aws_credentials_cache_init initializes the global
// `mongoc_aws_credentials_cache. It is expected to be called by mongoc_init.
void
_mongoc_aws_credentials_cache_init (void);

// _mongoc_aws_credentials_cache_lock exclusively locks the cache.
void
_mongoc_aws_credentials_cache_lock (void);

// _mongoc_aws_credentials_cache_unlock unlocks the cache.
void
_mongoc_aws_credentials_cache_unlock (void);

// _mongoc_aws_credentials_cache_put_nolock is a non-locking variant of
// _mongoc_aws_credentials_cache_put.
void
_mongoc_aws_credentials_cache_put_nolock (
const _mongoc_aws_credentials_t *creds);

// _mongoc_aws_credentials_cache_put adds credentials into the global cache.
void
_mongoc_aws_credentials_cache_put (const _mongoc_aws_credentials_t *creds);

// _mongoc_aws_credentials_cache_get_nolock is a non-locking variant of
// _mongoc_aws_credentials_cache_get.
bool
_mongoc_aws_credentials_cache_get_nolock (_mongoc_aws_credentials_t *creds);

// _mongoc_aws_credentials_cache_get returns true if cached credentials were
// retrieved.
// The passed `creds` is expected to be initialized with
// MONGOC_AWS_CREDENTIALS_INIT. Returns true if there are valid cached
// credentials. Retrieved credentials are copied to `creds`. Callers are
// expected to call
// `_mongoc_aws_credentials_cleanup` on `creds`.
// Returns false and zeroes `creds` if there are no valid cached credentials.
bool
_mongoc_aws_credentials_cache_get (_mongoc_aws_credentials_t *creds);

// _mongoc_aws_credentials_cache_clear_nolock is the non-locking variant of
// _mongoc_aws_credentials_cache_clear
void
_mongoc_aws_credentials_cache_clear_nolock (void);

// _mongoc_aws_credentials_cache_clear clears credentials in the global cache
void
_mongoc_aws_credentials_cache_clear (void);

// _mongoc_aws_credentials_cache_cleanup frees data for the global cache.
// It is expected to be called by mongoc_cleanup.
void
_mongoc_aws_credentials_cache_cleanup (void);


bool
_mongoc_aws_credentials_obtain (mongoc_uri_t *uri,
_mongoc_aws_credentials_t *creds,
bson_error_t *error);

void
_mongoc_aws_credentials_copy_to (const _mongoc_aws_credentials_t *src,
_mongoc_aws_credentials_t *dst);

void
_mongoc_aws_credentials_cleanup (_mongoc_aws_credentials_t *creds);

Expand Down
Loading

0 comments on commit 8eaac64

Please sign in to comment.