Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
35 changes: 35 additions & 0 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,28 @@ functions:
AUTH=${AUTH} SSL=${SSL} TOPOLOGY=${TOPOLOGY} RVM_RUBY="${RVM_RUBY}" \
.evergreen/run-tests-atlas.sh

"export SFP credentials":
- command: subprocess.exec
type: test
params:
binary: bash
working_dir: "src"
include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, DRIVERS_TOOLS]
args:
- "${DRIVERS_TOOLS}/.evergreen/secrets_handling/setup-secrets.sh"
- "drivers/sfp"

"run SFP tests":
- command: shell.exec
type: test
params:
shell: bash
working_dir: "src"
script: |
${PREPARE_SHELL}
RVM_RUBY="${RVM_RUBY}" \
.evergreen/run-tests-sfp.sh

pre:
- func: assume-test-secrets-ec2-role
- func: "fetch source"
Expand Down Expand Up @@ -550,6 +572,10 @@ tasks:
commands:
- func: "export Atlas credentials"
- func: "run Atlas tests"
- name: "test-sfp"
commands:
- func: "export SFP credentials"
- func: "run SFP tests"
- name: "test"
commands:
- func: "run tests"
Expand Down Expand Up @@ -1726,6 +1752,15 @@ buildvariants:
tasks:
- name: test-atlas

- matrix_name: "sfp"
matrix_spec:
ruby: ["ruby-4.0", "ruby-3.4", "ruby-3.3", "ruby-3.2", "ruby-3.1"]
os: ubuntu2204
display_name: "Atlas SFP tests ${ruby}"
tags: ["pr"]
tasks:
- name: test-sfp

# https://jira.mongodb.org/browse/RUBY-3311
# - matrix_name: "aws-lambda"
# matrix_spec:
Expand Down
26 changes: 26 additions & 0 deletions .evergreen/config/common.yml.erb
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,28 @@ functions:
AUTH=${AUTH} SSL=${SSL} TOPOLOGY=${TOPOLOGY} RVM_RUBY="${RVM_RUBY}" \
.evergreen/run-tests-atlas.sh

"export SFP credentials":
- command: subprocess.exec
type: test
params:
binary: bash
working_dir: "src"
include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, DRIVERS_TOOLS]
args:
- "${DRIVERS_TOOLS}/.evergreen/secrets_handling/setup-secrets.sh"
- "drivers/sfp"

"run SFP tests":
- command: shell.exec
type: test
params:
shell: bash
working_dir: "src"
script: |
${PREPARE_SHELL}
RVM_RUBY="${RVM_RUBY}" \
.evergreen/run-tests-sfp.sh

pre:
- func: assume-test-secrets-ec2-role
- func: "fetch source"
Expand Down Expand Up @@ -547,6 +569,10 @@ tasks:
commands:
- func: "export Atlas credentials"
- func: "run Atlas tests"
- name: "test-sfp"
commands:
- func: "export SFP credentials"
- func: "run SFP tests"
- name: "test"
commands:
- func: "run tests"
Expand Down
9 changes: 9 additions & 0 deletions .evergreen/config/standard.yml.erb
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,15 @@ buildvariants:
tasks:
- name: test-atlas

- matrix_name: "sfp"
matrix_spec:
ruby: <%= supported_mri_rubies_3_ubuntu %>
os: ubuntu2204
display_name: "Atlas SFP tests ${ruby}"
tags: ["pr"]
tasks:
- name: test-sfp

# https://jira.mongodb.org/browse/RUBY-3311
# - matrix_name: "aws-lambda"
# matrix_spec:
Expand Down
37 changes: 37 additions & 0 deletions .evergreen/run-tests-sfp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash

set -ex

. `dirname "$0"`/../spec/shared/shlib/distro.sh
. `dirname "$0"`/../spec/shared/shlib/set_env.sh
. `dirname "$0"`/functions.sh

set_env_vars
set_env_python

# Install rbenv and download the requested ruby version
rm -rf ~/.rbenv
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
rm -rf ~/.rbenv/versions/
curl --retry 3 -fL http://boxes.10gen.com/build/toolchain-drivers/mongo-ruby-toolchain/library/`host_distro`/$RVM_RUBY.tar.xz |tar -xC $HOME/.rbenv/ -Jf -
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init - bash)"
export FULL_RUBY_VERSION=$(ls ~/.rbenv/versions | head -n1)
rbenv global $FULL_RUBY_VERSION

export JAVA_HOME=/opt/java/jdk21
export JAVACMD=$JAVA_HOME/bin/java

bundle_install

echo "Running specs"

export SFP_TESTING=1

if test -f secrets-export.sh; then
# shellcheck disable=SC1091
. ./secrets-export.sh
fi

bundle exec rspec spec/sfp \
--format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml
142 changes: 142 additions & 0 deletions spec/sfp/sfp_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# frozen_string_literal: true

require 'lite_spec_helper'
require 'base64'
require 'tempfile'
require 'uri'

# Connectivity tests for an Atlas Secure Frontend Processor (SFP), a proxy that
# sits in front of Atlas clusters to provide additional security capabilities.
# See the atlas-sfp-testing specification for the full list of required tests.
#
# These tests run against a preconfigured Atlas cluster and are driven entirely
# by environment variables. They are skipped unless SFP_TESTING is set.
describe 'Atlas SFP connectivity' do
before do
skip 'Set SFP_TESTING to run tests against a live Atlas SFP cluster' unless ENV['SFP_TESTING']
end

# Each authenticated test runs under three variations: a baseline, one with a
# compressor enabled, and one with Server API version 1.
variations = {
'baseline' => {},
'with compression' => { compressors: %w[ zlib ] },
'with server API version 1' => { server_api: { version: '1' } },
}.freeze

# DIAGNOSTIC (RUBY-3800): the SFP proxy closes certless TLS handshakes on
# SFP_ATLAS_URI ("SSL_read: unexpected eof"), while connections presenting the
# X.509 client certificate succeed. Present the certificate at the TLS layer
# for every connection (auth mechanism unchanged) to test whether the proxy
# requires mutual TLS universally.
let(:client_certificate) do
decoded = Base64.strict_decode64(ENV.fetch('SFP_ATLAS_X509_BASE64'))
file = Tempfile.new([ 'sfp-x509-cert', '.pem' ])
file.write(decoded)
File.chmod(0o600, file.path)
file.close
file
end

# tlsCertificateKeyFile maps to both :ssl_cert and :ssl_key in the driver.
let(:tls_certificate_options) do
{ ssl_cert: client_certificate.path, ssl_key: client_certificate.path }
end

after do
begin
client.close
rescue StandardError
# no-op
end

begin
client_certificate.unlink
rescue StandardError
# no-op
end
end

shared_examples 'connects successfully' do
it 'succeeds at ping' do
result = client.use(:admin).database.command(ping: 1).documents.first
expect(result['ok']).to eq(1)
end

it 'reports the expected authentication state' do
info = client.use(:admin).database.command(connectionStatus: 1).documents.first
expect(info['ok']).to eq(1)
authenticated_users = info[:authInfo][:authenticatedUsers]
if authenticated
expect(authenticated_users).not_to be_empty
else
expect(authenticated_users).to be_empty
end
end
end

shared_examples 'performs CRUD operations' do
# Drivers MUST use a unique collection name for each test run.
let(:collection) { client.use('db')["sfp_test_#{BSON::ObjectId.new}"] }

# Drivers MUST drop the test collection after the test completes,
# regardless of success or failure.
after do
collection.drop
rescue StandardError
# no-op
end

it 'inserts and reads back a document' do
collection.insert_one(_id: 0)
expect(collection.find(_id: 0).to_a).to eq([ { '_id' => 0 } ])
end
end

context 'when unauthenticated' do
let(:authenticated) { false }
let(:client) { Mongo::Client.new(ENV.fetch('SFP_ATLAS_URI'), tls_certificate_options) }

include_examples 'connects successfully'
end

context 'when using SCRAM-SHA-256 authentication' do
let(:authenticated) { true }

variations.each do |description, options|
context description do
let(:client) do
Mongo::Client.new(
ENV.fetch('SFP_ATLAS_URI'),
{
user: ENV.fetch('SFP_ATLAS_USER'),
password: ENV.fetch('SFP_ATLAS_PASSWORD'),
auth_mech: :scram256,
}.merge(tls_certificate_options).merge(options)
)
end

include_examples 'connects successfully'
include_examples 'performs CRUD operations'
end
end
end

context 'when using X.509 authentication' do
let(:authenticated) { true }

let(:uri) do
"#{ENV.fetch('SFP_ATLAS_X509_URI')}&tlsCertificateKeyFile=" \
"#{URI::DEFAULT_PARSER.escape(client_certificate.path)}"
end

variations.each do |description, options|
context description do
let(:client) { Mongo::Client.new(uri, options) }

include_examples 'connects successfully'
include_examples 'performs CRUD operations'
end
end
end
end
Loading