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

CDRIVER-3665 End-to-end test for OCSP caching #612

Merged
merged 2 commits into from May 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
68 changes: 68 additions & 0 deletions .evergreen/config.yml
Expand Up @@ -13134,6 +13134,74 @@ tasks:
set -o errexit
set -o xtrace
TEST_COLUMN=MALICIOUS_SERVER_TEST_2 CERT_TYPE=rsa sh .evergreen/run-ocsp-test.sh
- name: ocsp-openssl-cache-rsa-nodelegate
tags:
- ocsp-openssl
depends_on:
name: debug-compile-nosasl-openssl
commands:
- func: fetch build
vars:
BUILD_NAME: debug-compile-nosasl-openssl
- command: shell.exec
type: test
params:
working_dir: mongoc
shell: bash
script: |-
set -o errexit
set -o xtrace
TEST_COLUMN=TEST_4 CERT_TYPE=rsa USE_DELEGATE=off sh .evergreen/run-ocsp-responder.sh
- func: bootstrap mongo-orchestration
vars:
OCSP: 'on'
ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling
SSL: ssl
TOPOLOGY: server
VERSION: latest
- command: shell.exec
type: test
params:
working_dir: mongoc
shell: bash
script: |-
set -o errexit
set -o xtrace
CERT_TYPE=rsa .evergreen/run-ocsp-cache-test.sh
- name: ocsp-openssl-cache-ecdsa-nodelegate
tags:
- ocsp-openssl
depends_on:
name: debug-compile-nosasl-openssl
commands:
- func: fetch build
vars:
BUILD_NAME: debug-compile-nosasl-openssl
- command: shell.exec
type: test
params:
working_dir: mongoc
shell: bash
script: |-
set -o errexit
set -o xtrace
TEST_COLUMN=TEST_4 CERT_TYPE=ecdsa USE_DELEGATE=off sh .evergreen/run-ocsp-responder.sh
- func: bootstrap mongo-orchestration
vars:
OCSP: 'on'
ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling
SSL: ssl
TOPOLOGY: server
VERSION: latest
- command: shell.exec
type: test
params:
working_dir: mongoc
shell: bash
script: |-
set -o errexit
set -o xtrace
CERT_TYPE=ecdsa .evergreen/run-ocsp-cache-test.sh
buildvariants:
- name: releng
display_name: '**Release Archive Creator'
Expand Down
70 changes: 70 additions & 0 deletions .evergreen/run-ocsp-cache-test.sh
@@ -0,0 +1,70 @@
#!/bin/bash
#
# End-to-end test runner for OCSP cache.
kevinAlbs marked this conversation as resolved.
Show resolved Hide resolved
#
# Assumptions:
# Mongod:
# The script assumes that a mongod is running with TLS enabled. It also assumes that the server certificate will NOT
# staple a response. This will force the test binary to reach out to an OCSP responder to check the certificates'
# revocation status.
# Mock OCSP Responder:
# This script assumes that a mock OCSP responder is running named "ocsp_mock". It also assumes that the OCSP
# responder will respond with a certificate status of 'revoked'.
#
# Behavior:
# This script first runs the test binary 'test-mongoc-cache' which sends a ping command to the mongod. It then waits for 5
# seconds to give the binary enough time to make the request, and receive and process the response. Since we soft-fail
# if an OCSP responder is not reachable, receiving a certificate status of 'revoked' is the only way we can be certain
# our binary reached out to an OCSP responder. We assert a certificate status of 'revoked' in the test binary for both
# ping commands.
#
# The test binary will hang (it calls 'raise (SIGSTOP)') after the first ping. This gives us enough time to kill the
# mock OCSP responder before sending the second ping command to the server. If the cache is used, the expected behavior,
# then the binary will use the response cached from the first ping command and report a certificate status of 'revoked'.
# However, if the cache is not used, then second ping command will attempt to reach out to an OCSP responder. Since the
# only one available was killed by this script and we soft-fail if we cannot contact an OCSP responder the binary
# will report a certificate status of "good".
#
# The aforementioned behavior is asserted in the test binary, i.e., both ping commands should fail. If they do,
# test-mongoc-cache will return EXIT_SUCCESS, otherwise, it will return EXIT_FAILURE.
#
# Environment variables:
#
# CDRIVER_ROOT
# Optional. The path to mongo-c-driver source (may be same as CDRIVER_BUILD).
# Defaults to $(pwd)
# CDRIVER_BUILD
# Optional. The path to the build of mongo-c-driver (e.g. mongo-c-driver/cmake-build).
# Defaults to $(pwd)
# CERT_TYPE
# Required. Set to either RSA or ECDSA.

set -o xtrace # Write all commands first to stderr
set -o errexit # Exit the script with error if any of the commands fail

CDRIVER_ROOT=${CDRIVER_ROOT:-$(pwd)}
CDRIVER_BUILD=${CDRIVER_BUILD:-$(pwd)}

if [ -z "$CERT_TYPE" ]; then
echo "Required environment variable 'CERT_TYPE' unset. See file comments for help."
exit 1;
fi

if [ ! `pgrep -nf mongod` ]; then
kevinAlbs marked this conversation as resolved.
Show resolved Hide resolved
echo "Cannot find mongod. See file comments for help."
exit 1;
fi

if [ ! `pgrep -nf ocsp_mock` ]; then
echo "Cannot find mock OCSP responder. See file comments for help."
exit 1;
fi

# This test will hang after the first ping.
${CDRIVER_BUILD}/src/libmongoc/test-mongoc-cache ${CDRIVER_ROOT}/.evergreen/ocsp/${CERT_TYPE}/ca.pem &
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how errexit treats background processes. To check, I created two files:

  • fail.sh containing just exit 1
  • run.sh containing:
set -o errexit
echo "Start"
./fail.sh &
echo "End (should fail sooner)" 

But run.sh prints both messages.

IIUC, calling wait on the background process PID may be all that's needed. That returns the exit code of that process. Modifying run.sh as follows seemed to give the desired behavior:

set -o errexit
echo "Start"
./fail.sh &
child_pid=$!
wait $child_pid
echo "End (should fail sooner)" 

With that, it terminated before printing the final message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand. I think the first example is more in line with the expected behavior since this line doesn't terminate, i.e., return an exit code, in the background. It just hangs while the rest of the scripts continues. The sleep is just there to make sure that it can contact the OCSP responder before we kill the OCSP responder. But the program is still hanging around. Finally, kill will continue the program and return its exit code. If the test program does exit in the background for whatever reason this program still fails. Although I'm not exactly sure how I'd capture the exit status of a background that may either halt indefinitely or return.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I was misunderstanding. I was thinking that if it terminated quickly, the script may quietly pass. But in that case the kill would fail

sleep 5 # Give the program time to contact the OCSP responder

pkill -nf "ocsp_mock" # We assume that the mock OCSP responder is named "ocsp_mock"

# Resume the test binary. This will cause it to send the second ping command.
kill -s SIGCONT `pgrep -nf test-mongoc-cache`
20 changes: 13 additions & 7 deletions build/evergreen_config_lib/tasks.py
Expand Up @@ -897,7 +897,7 @@ def name(self):
all_tasks = chain(all_tasks, AWSTestTask.matrix())

class OCSPTask(MatrixTask):
axes = OD([('test', ['test_1', 'test_2', 'test_3', 'test_4', 'soft_fail_test', 'malicious_server_test_1', 'malicious_server_test_2']),
axes = OD([('test', ['test_1', 'test_2', 'test_3', 'test_4', 'soft_fail_test', 'malicious_server_test_1', 'malicious_server_test_2', 'cache']),
('delegate', ['delegate', 'nodelegate']),
('cert', ['rsa', 'ecdsa']),
('ssl', ['openssl', 'darwinssl', 'winssl'])])
Expand All @@ -920,17 +920,23 @@ def to_dict(self):
func('fetch build', BUILD_NAME=self.depends_on['name']))

stapling = 'mustStaple'
if self.test in [ 'test_3', 'test_4', 'soft_fail_test']:
if self.test in [ 'test_3', 'test_4', 'soft_fail_test', 'cache']:
stapling = 'disableStapling'
if self.test in [ 'malicious_server_test_1', 'malicious_server_test_2' ]:
stapling = 'mustStaple-disableStapling'

orchestration_file = '%s-basic-tls-ocsp-%s' % (self.cert, stapling)
orchestration = bootstrap(TOPOLOGY='server', SSL='ssl', OCSP='on', ORCHESTRATION_FILE=orchestration_file)

commands.append(shell_mongoc('TEST_COLUMN=%s CERT_TYPE=%s USE_DELEGATE=%s sh .evergreen/run-ocsp-responder.sh' % (self.test.upper(), self.cert, 'on' if self.delegate == 'delegate' else 'off')))
# The cache test expects a revoked response from an OCSP responder, exactly like TEST_4.
test_column = 'TEST_4' if self.test == 'cache' else self.test.upper()

commands.append(shell_mongoc('TEST_COLUMN=%s CERT_TYPE=%s USE_DELEGATE=%s sh .evergreen/run-ocsp-responder.sh' % (test_column, self.cert, 'on' if self.delegate == 'delegate' else 'off')))
commands.append(orchestration)
commands.append(shell_mongoc('TEST_COLUMN=%s CERT_TYPE=%s sh .evergreen/run-ocsp-test.sh' % (self.test.upper(), self.cert)))
if self.test == 'cache':
commands.append(shell_mongoc('CERT_TYPE=%s .evergreen/run-ocsp-cache-test.sh' % self.cert))
else:
commands.append(shell_mongoc('TEST_COLUMN=%s CERT_TYPE=%s sh .evergreen/run-ocsp-test.sh' % (self.test.upper(), self.cert)))

return task

Expand All @@ -948,11 +954,11 @@ def _check_allowed(self):

# OCSP stapling is not supported on macOS or Windows.
if self.ssl == 'darwinssl' or self.ssl == 'winssl':
prohibit (self.test in ['test_1', 'test_2'])
prohibit (self.test in ['test_1', 'test_2', 'cache'])

if self.test == 'soft_fail_test' or self.test == 'malicious_server_test_2':
if self.test == 'soft_fail_test' or self.test == 'malicious_server_test_2' or self.test == 'cache':
prohibit(self.delegate == 'delegate')

# Until OCSP is supported in OpenSSL, skip tests that expect to be revoked.
if self.ssl == 'openssl':
prohibit (self.test in ['test_2', 'test_4', 'malicious_server_test_1', 'malicious_server_test_2'])
Expand Down
1 change: 1 addition & 0 deletions src/libmongoc/.gitignore
Expand Up @@ -21,3 +21,4 @@ mongoc-stat
mongoc-tail
test-libmongoc
test-mongoc-gssapi
test-mongoc-cache
1 change: 1 addition & 0 deletions src/libmongoc/CMakeLists.txt
Expand Up @@ -959,6 +959,7 @@ endif ()

mongoc_add_test (test-libmongoc FALSE ${test-libmongoc-sources})
mongoc_add_test (test-mongoc-gssapi FALSE ${PROJECT_SOURCE_DIR}/tests/test-mongoc-gssapi.c)
mongoc_add_test (test-mongoc-cache FALSE ${PROJECT_SOURCE_DIR}/tests/test-mongoc-cache.c)

if (ENABLE_TESTS)
enable_testing ()
Expand Down
90 changes: 90 additions & 0 deletions src/libmongoc/tests/test-mongoc-cache.c
@@ -0,0 +1,90 @@
/*
* Copyright 2020-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <stdlib.h>

#if defined(__linux__)

#include <mongoc/mongoc.h>
#include <stdio.h>
#include <signal.h>
#include <bits/signum.h>
#include "TestSuite.h"

static char *ca_file;

static int
ping ()
{
mongoc_client_t *client;
mongoc_database_t *database;
bson_t reply;
bson_error_t error;
bson_t ping;
char *uri;
int ret = EXIT_FAILURE;

uri = bson_strdup_printf ("mongodb://localhost/?tls=true&tlsCAFile=%s",
ca_file);
ASSERT ((client = mongoc_client_new (uri)));

bson_init (&ping);
bson_append_int32 (&ping, "ping", 4, 1);
database = mongoc_client_get_database (client, "cache");

if (mongoc_database_command_with_opts (
database, &ping, NULL, NULL, &reply, &error)) {
MONGOC_DEBUG ("Ping success\n");
ret = EXIT_SUCCESS;
} else {
MONGOC_DEBUG ("Ping failure: %s\n", error.message);
ASSERT_ERROR_CONTAINS (error,
MONGOC_ERROR_SERVER_SELECTION,
MONGOC_ERROR_SERVER_SELECTION_FAILURE,
"TLS handshake failed");
}

bson_free (uri);
bson_destroy (&ping);
bson_destroy (&reply);
mongoc_database_destroy (database);
mongoc_client_destroy (client);

return ret;
}
#endif

int
main (int argc, char *argv[])
{
#if defined(__linux__)
if (argc != 2) {
fprintf (stderr, "usage: %s CA_FILE_PATH\n", argv[0]);
return EXIT_FAILURE;
}

ca_file = argv[1];

mongoc_init ();

ASSERT (ping () == EXIT_FAILURE);
raise (SIGSTOP);
ASSERT (ping () == EXIT_FAILURE);

mongoc_cleanup ();
#endif
return EXIT_SUCCESS;
kevinAlbs marked this conversation as resolved.
Show resolved Hide resolved
}