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

Introduce SOCKS5 proxy support #1180

Merged
merged 27 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
311515e
Introduce SOCKS5 proxy support, allowing seamless connections to Mong…
vbabanin Aug 3, 2023
07a567d
Include CSFLE test suites to run via Socks5 proxy.
vbabanin Aug 17, 2023
0231ae6
Remove extra MONGODB_URI variable.
vbabanin Aug 17, 2023
53c7973
Remove unused imports.
vbabanin Aug 17, 2023
81020fb
Update driver-core/src/main/com/mongodb/connection/SocketSettings.java
vbabanin Aug 24, 2023
1313753
Update driver-core/src/main/com/mongodb/ClientEncryptionSettings.java
vbabanin Aug 24, 2023
37e0874
Update driver-core/src/main/com/mongodb/AutoEncryptionSettings.java
vbabanin Aug 24, 2023
3790094
Update driver-core/src/main/com/mongodb/connection/ProxySettings.java
vbabanin Aug 25, 2023
494e293
Apply suggestions from code review
vbabanin Aug 29, 2023
de7697d
Update driver-core/src/main/com/mongodb/internal/connection/SocksSock…
vbabanin Aug 29, 2023
3e84f5c
Revert proxy settings for key Management Service.
vbabanin Aug 31, 2023
08d3e30
Remove KMIP config.
vbabanin Sep 1, 2023
ba111ca
Restructuring testing:
vbabanin Sep 2, 2023
db8d91b
Fix checkstyle errors.
vbabanin Sep 2, 2023
ea1060c
Update driver-reactive-streams/src/main/com/mongodb/reactivestreams/c…
vbabanin Sep 5, 2023
7b93565
Update driver-core/src/main/com/mongodb/connection/ProxySettings.java
vbabanin Sep 5, 2023
e7218a8
Update driver-core/src/main/com/mongodb/connection/ProxySettings.java
vbabanin Sep 5, 2023
a56d56c
Update driver-core/src/main/com/mongodb/connection/ProxySettings.java
vbabanin Sep 5, 2023
dcac4f6
Apply suggestions from code review
vbabanin Sep 5, 2023
7eb1c11
- Change javadoc.
vbabanin Sep 5, 2023
19f2dca
Add javadoc.
vbabanin Sep 5, 2023
735e81e
Remove redundant code.
vbabanin Sep 5, 2023
34a26ce
Add @EnabledIf annotation for tests to remove skipped tests from a re…
vbabanin Sep 5, 2023
ede85de
Apply suggestions from code review
vbabanin Sep 6, 2023
840d12b
Remove redundant code.
vbabanin Sep 6, 2023
7d461a0
Add size check in domain regex.
vbabanin Sep 6, 2023
42aca98
Address checkstyle warnings.
vbabanin Sep 6, 2023
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
37 changes: 37 additions & 0 deletions .evergreen/.evg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,18 @@ functions:
MONGODB_URIS="${atlas_free_tier_uri}|${atlas_replica_set_uri}|${atlas_sharded_uri}|${atlas_tls_v11_uri}|${atlas_tls_v12_uri}|${atlas_free_tier_uri_srv}|${atlas_replica_set_uri_srv}|${atlas_sharded_uri_srv}|${atlas_tls_v11_uri_srv}|${atlas_tls_v12_uri_srv}|${atlas_serverless_uri}|${atlas_serverless_uri_srv}" \
.evergreen/run-connectivity-tests.sh

run socks5 tests:
- command: shell.exec
type: test
params:
working_dir: src
script: |
${PREPARE_SHELL}
SOCKS_AUTH="${SOCKS_AUTH}" \
SSL="${SSL}" MONGODB_URI="${MONGODB_URI}" \
JAVA_VERSION="${JAVA_VERSION}" \
.evergreen/run-socks5-tests.sh

start-kms-mock-server:
- command: shell.exec
params:
Expand Down Expand Up @@ -1605,6 +1617,14 @@ tasks:
export AZUREKMS_VMNAME=${AZUREKMS_VMNAME}
export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey
AZUREKMS_CMD="MONGODB_URI=mongodb://localhost:27017 PROVIDER=azure AZUREKMS_KEY_VAULT_ENDPOINT=${testazurekms_keyvaultendpoint} AZUREKMS_KEY_NAME=${testazurekms_keyname} ./.evergreen/run-fle-on-demand-credential-test.sh" $DRIVERS_TOOLS/.evergreen/csfle/azurekms/run-command.sh
- name: test-socks5
tags: []
commands:
- func: bootstrap mongo-orchestration
vars:
VERSION: latest
TOPOLOGY: replica_set
- func: run socks5 tests
axes:
- id: version
display_name: MongoDB Version
Expand Down Expand Up @@ -1695,6 +1715,17 @@ axes:
display_name: NoAuth
variables:
AUTH: "noauth"
- id: socks_auth
display_name: Socks Proxy Authentication
values:
- id: "auth"
display_name: Auth
variables:
SOCKS_AUTH: "auth"
- id: "noauth"
display_name: NoAuth
variables:
SOCKS_AUTH: "noauth"
- id: ssl
display_name: SSL
values:
Expand Down Expand Up @@ -2136,6 +2167,12 @@ buildvariants:
tasks:
- name: "csfle-tests-with-mongocryptd"

- matrix_name: "socks5-tests"
matrix_spec: { os: "linux", ssl: ["nossl", "ssl"], version: [ "latest" ], topology: ["replicaset"], socks_auth: ["auth", "noauth"] }
display_name: "${socks_auth} SOCKS5 proxy: ${version} ${topology} ${ssl} ${jdk} ${os}"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's move socks_auth after SOCKS5 proxy so that they sort together in the display

tasks:
- name: test-socks5

- name: testgcpkms-variant
display_name: "GCP KMS"
run_on:
Expand Down
87 changes: 87 additions & 0 deletions .evergreen/run-socks5-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/bash

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

SSL=${SSL:-nossl}
SOCKS_AUTH=${SOCKS_AUTH:-noauth}
MONGODB_URI=${MONGODB_URI:-}
SOCKS5_SERVER_SCRIPT="$DRIVERS_TOOLS/.evergreen/socks5srv.py"
PYTHON_BINARY=${PYTHON_BINARY:-python3}
# Grab a connection string that only refers to *one* of the hosts in MONGODB_URI
FIRST_HOST=$(echo "$MONGODB_URI" | awk -F[/:,] '{print $4":"$5}')
# Use 127.0.0.1:12345 as the URL for the single host that we connect to,
# we configure the Socks5 proxy server script to redirect from this to FIRST_HOST
export MONGODB_URI_SINGLEHOST="mongodb://127.0.0.1:12345"

if [ "${SSL}" = "ssl" ]; then
MONGODB_URI="${MONGODB_URI}&ssl=true&sslInvalidHostNameAllowed=true"
MONGODB_URI_SINGLEHOST="${MONGODB_URI_SINGLEHOST}/?ssl=true&sslInvalidHostNameAllowed=true"
fi

# Compute path to socks5 fake server script in a way that works on Windows
if [ "Windows_NT" == "$OS" ]; then
SOCKS5_SERVER_SCRIPT=$(cygpath -m $DRIVERS_TOOLS)
fi

RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE:-$0}")"
. "${RELATIVE_DIR_PATH}/javaConfig.bash"

############################################
# Functions #
############################################

provision_ssl () {
# We generate the keystore and truststore on every run with the certs in the drivers-tools repo
if [ ! -f client.pkc ]; then
openssl pkcs12 -CAfile ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -export -in ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem -out client.pkc -password pass:bithere
fi

cp ${JAVA_HOME}/lib/security/cacerts mongo-truststore
${JAVA_HOME}/bin/keytool -importcert -trustcacerts -file ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -keystore mongo-truststore -storepass changeit -storetype JKS -noprompt

# We add extra gradle arguments for SSL
export GRADLE_SSL_VARS="-Pssl.enabled=true -Pssl.keyStoreType=pkcs12 -Pssl.keyStore=`pwd`/client.pkc -Pssl.keyStorePassword=bithere -Pssl.trustStoreType=jks -Pssl.trustStore=`pwd`/mongo-truststore -Pssl.trustStorePassword=changeit"
}


run_socks5_proxy () {
if [ "$SOCKS_AUTH" == "auth" ]; then
"$PYTHON_BINARY" "$SOCKS5_SERVER_SCRIPT" --port 1080 --auth username:p4ssw0rd --map "127.0.0.1:12345 to $FIRST_HOST" &
SOCKS5_SERVER_PID_1=$!
trap "kill $SOCKS5_SERVER_PID_1" EXIT
else
"$PYTHON_BINARY" "$SOCKS5_SERVER_SCRIPT" --port 1080 --map "127.0.0.1:12345 to $FIRST_HOST" &
SOCKS5_SERVER_PID_1=$!
trap "kill $SOCKS5_SERVER_PID_1" EXIT
fi
}

run_socks5_prose_tests () {
if [ "$SOCKS_AUTH" == "auth" ]; then
local AUTH_ENABLED="true"
else
local AUTH_ENABLED="false"
fi

echo "Running Socks5 tests with Java ${JAVA_VERSION} over $SSL for $TOPOLOGY and connecting to $MONGODB_URI with socks auth enabled: $AUTH_ENABLED"
./gradlew -PjavaVersion=${JAVA_VERSION} -Dorg.mongodb.test.uri=${MONGODB_URI} \
-Dorg.mongodb.test.uri.singleHost=${MONGODB_URI_SINGLEHOST} \
-Dorg.mongodb.test.uri.proxyHost="127.0.0.1" \
-Dorg.mongodb.test.uri.proxyPort="1080" \
-Dorg.mongodb.test.uri.socks.auth.enabled=${AUTH_ENABLED} \
${GRADLE_SSL_VARS} \
--stacktrace --info --continue \
driver-sync:test \
--tests "com.mongodb.client.Socks5ProseTest*"
}

############################################
# Main Program #
############################################

# Set up keystore/truststore
provision_ssl
./gradlew -version
run_socks5_proxy
run_socks5_prose_tests
21 changes: 21 additions & 0 deletions THIRD-PARTY-NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,24 @@ https://github.com/mongodb/mongo-java-driver.
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

8) The following files (originally from https://github.com/google/guava):

InetAddressUtils.java (formerly InetAddresses.java)
InetAddressUtilsTest.java (formerly InetAddressesTest.java)

Copyright 2008-present MongoDB, Inc.
Copyright (C) 2008 The Guava Authors

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.

2 changes: 1 addition & 1 deletion config/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@

<!-- Spotbugs assumes that SSLSocket#getSSLParameters never returns null, when that is not the case for all JDKs -->
<Match>
<Class name="com.mongodb.internal.connection.SocketStreamHelper"/>
<Class name="com.mongodb.internal.connection.SslHelper"/>
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
</Match>

Expand Down
118 changes: 117 additions & 1 deletion driver-core/src/main/com/mongodb/ConnectionString.java
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@
* <li>{@code maxIdleTimeMS=ms}: Maximum idle time of a pooled connection. A connection that exceeds this limit will be closed</li>
* <li>{@code maxLifeTimeMS=ms}: Maximum life time of a pooled connection. A connection that exceeds this limit will be closed</li>
* </ul>
* <p>Proxy Configuration:</p>
* <ul>
* <li>{@code proxyHost=string}: The SOCKS5 proxy host to establish a connection through.
* It can be provided as a valid IPv4 address, IPv6 address, or a domain name. Required if either proxyPassword, proxyUsername or
* proxyPort are specified</li>
* <li>{@code proxyPort=n}: The port number for the SOCKS5 proxy server. Must be a non-negative integer.</li>
* <li>{@code proxyUsername=string}: Username for authenticating with the proxy server. Required if proxyPassword is specified.</li>
* <li>{@code proxyPassword=string}: Password for authenticating with the proxy server. Required if proxyUsername is specified.</li>
* </ul>
* <p>Connection pool configuration:</p>
* <ul>
* <li>{@code maxPoolSize=n}: The maximum number of connections in the connection pool.</li>
Expand Down Expand Up @@ -283,6 +292,10 @@ public class ConnectionString {
private Integer socketTimeout;
private Boolean sslEnabled;
private Boolean sslInvalidHostnameAllowed;
private String proxyHost;
private Integer proxyPort;
private String proxyUsername;
private String proxyPassword;
private String requiredReplicaSetName;
private Integer serverSelectionTimeout;
private Integer localThreshold;
Expand Down Expand Up @@ -461,6 +474,8 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient
throw new IllegalArgumentException("srvMaxHosts can not be specified with replica set name");
}

validateProxyParameters();

credential = createCredentials(combinedOptionsMaps, userName, password);
warnOnUnsupportedOptions(combinedOptionsMaps);
}
Expand Down Expand Up @@ -495,6 +510,12 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient
GENERAL_OPTIONS_KEYS.add("sslinvalidhostnameallowed");
GENERAL_OPTIONS_KEYS.add("tlsallowinvalidhostnames");

//Socks5 proxy settings
GENERAL_OPTIONS_KEYS.add("proxyhost");
GENERAL_OPTIONS_KEYS.add("proxyport");
GENERAL_OPTIONS_KEYS.add("proxyusername");
GENERAL_OPTIONS_KEYS.add("proxypassword");
stIncMale marked this conversation as resolved.
Show resolved Hide resolved

GENERAL_OPTIONS_KEYS.add("replicaset");
GENERAL_OPTIONS_KEYS.add("readconcernlevel");

Expand Down Expand Up @@ -592,6 +613,18 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
case "sockettimeoutms":
socketTimeout = parseInteger(value, "sockettimeoutms");
break;
case "proxyhost":
proxyHost = value;
break;
case "proxyport":
proxyPort = parseInteger(value, "proxyPort");
break;
case "proxyusername":
proxyUsername = value;
break;
case "proxypassword":
proxyPassword = value;
break;
case "tlsallowinvalidhostnames":
sslInvalidHostnameAllowed = parseBoolean(value, "tlsAllowInvalidHostnames");
tlsAllowInvalidHostnamesSet = true;
Expand Down Expand Up @@ -1151,6 +1184,41 @@ private void validatePort(final String host, final String port) {
}
}

private void validateProxyParameters() {
if (proxyHost == null) {
if (proxyPort != null) {
throw new IllegalArgumentException("proxyPort can only be specified with proxyHost");
} else if (proxyUsername != null) {
throw new IllegalArgumentException("proxyUsername can only be specified with proxyHost");
} else if (proxyPassword != null) {
throw new IllegalArgumentException("proxyPassword can only be specified with proxyHost");
}
}
if (proxyPort != null && (proxyPort < 0 || proxyPort > 65535)) {
throw new IllegalArgumentException("proxyPort should be within the valid range (0 to 65535)");
}
if (proxyUsername != null) {
if (proxyUsername.isEmpty()) {
throw new IllegalArgumentException("proxyUsername cannot be empty");
}
if (proxyUsername.getBytes(StandardCharsets.UTF_8).length >= 255) {
throw new IllegalArgumentException("username's length in bytes cannot be greater than 255");
}
}
if (proxyPassword != null) {
if (proxyPassword.isEmpty()) {
throw new IllegalArgumentException("proxyPassword cannot be empty");
}
if (proxyPassword.getBytes(StandardCharsets.UTF_8).length >= 255) {
throw new IllegalArgumentException("password's length in bytes cannot be greater than 255");
}
}
if (proxyUsername == null ^ proxyPassword == null) {
throw new IllegalArgumentException(
"Both proxyUsername and proxyPassword must be set together. They cannot be set individually");
}
}

private int countOccurrences(final String haystack, final String needle) {
return haystack.length() - haystack.replace(needle, "").length();
}
Expand Down Expand Up @@ -1439,6 +1507,49 @@ public Boolean getSslEnabled() {
return sslEnabled;
}

/**
* Gets the SOCKS5 proxy host specified in the connection string.
*
* @return the proxy host value.
* @since 4.11
*/
@Nullable
public String getProxyHost() {
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
return proxyHost;
}

/**
* Gets the SOCKS5 proxy port specified in the connection string.
*
* @return the proxy port value.
* @since 4.11
*/
@Nullable
public Integer getProxyPort() {
return proxyPort;
}

/**
* Gets the SOCKS5 proxy username specified in the connection string.
*
* @return the proxy username value.
* @since 4.11
*/
@Nullable
public String getProxyUsername() {
return proxyUsername;
}

/**
* Gets the SOCKS5 proxy password specified in the connection string.
*
* @return the proxy password value.
* @since 4.11
*/
@Nullable
public String getProxyPassword() {
return proxyPassword;
}
/**
* Gets the SSL invalidHostnameAllowed value specified in the connection string.
*
Expand Down Expand Up @@ -1560,6 +1671,10 @@ public boolean equals(final Object o) {
&& Objects.equals(maxConnecting, that.maxConnecting)
&& Objects.equals(connectTimeout, that.connectTimeout)
&& Objects.equals(socketTimeout, that.socketTimeout)
&& Objects.equals(proxyHost, that.proxyHost)
&& Objects.equals(proxyPort, that.proxyPort)
&& Objects.equals(proxyUsername, that.proxyUsername)
&& Objects.equals(proxyPassword, that.proxyPassword)
&& Objects.equals(sslEnabled, that.sslEnabled)
&& Objects.equals(sslInvalidHostnameAllowed, that.sslInvalidHostnameAllowed)
&& Objects.equals(requiredReplicaSetName, that.requiredReplicaSetName)
Expand All @@ -1579,6 +1694,7 @@ public int hashCode() {
writeConcern, retryWrites, retryReads, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime,
maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, socketTimeout, sslEnabled,
sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency,
applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts);
applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts, proxyHost, proxyPort,
proxyUsername, proxyPassword);
}
}
1 change: 1 addition & 0 deletions driver-core/src/main/com/mongodb/MongoClientSettings.java
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,7 @@ private MongoClientSettings(final Builder builder) {
.connectTimeout(builder.heartbeatConnectTimeoutMS == 0
? socketSettings.getConnectTimeout(MILLISECONDS) : builder.heartbeatConnectTimeoutMS,
MILLISECONDS)
.applyToProxySettings(proxyBuilder -> proxyBuilder.applySettings(socketSettings.getProxySettings()))
.build();
heartbeatSocketTimeoutSetExplicitly = builder.heartbeatSocketTimeoutMS != 0;
heartbeatConnectTimeoutSetExplicitly = builder.heartbeatConnectTimeoutMS != 0;
Expand Down
Loading