diff --git a/.github/workflows/parameters_aws_auth_tests.json.gpg b/.github/workflows/parameters_aws_auth_tests.json.gpg index 0d3d4ce5b..bea557c13 100644 Binary files a/.github/workflows/parameters_aws_auth_tests.json.gpg and b/.github/workflows/parameters_aws_auth_tests.json.gpg differ diff --git a/.github/workflows/rsa_keys/rsa_encrypted_key.p8.gpg b/.github/workflows/rsa_keys/rsa_encrypted_key.p8.gpg new file mode 100644 index 000000000..3643cdb1f Binary files /dev/null and b/.github/workflows/rsa_keys/rsa_encrypted_key.p8.gpg differ diff --git a/.github/workflows/rsa_keys/rsa_key.p8.gpg b/.github/workflows/rsa_keys/rsa_key.p8.gpg new file mode 100644 index 000000000..e90253cd3 Binary files /dev/null and b/.github/workflows/rsa_keys/rsa_key.p8.gpg differ diff --git a/.github/workflows/rsa_keys/rsa_key_invalid.p8.gpg b/.github/workflows/rsa_keys/rsa_key_invalid.p8.gpg new file mode 100644 index 000000000..3d2442a7c Binary files /dev/null and b/.github/workflows/rsa_keys/rsa_key_invalid.p8.gpg differ diff --git a/.gitignore b/.gitignore index 785a1a3c7..5c1e6c21c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ wss-*-agent.config wss-unified-agent.jar whitesource/ .nyc_output +rsa_*.p8 diff --git a/ci/container/test_authentication.sh b/ci/container/test_authentication.sh index cffe7336e..31e18dde5 100755 --- a/ci/container/test_authentication.sh +++ b/ci/container/test_authentication.sh @@ -5,4 +5,8 @@ set -o pipefail AUTH_PARAMETER_FILE=./.github/workflows/parameters_aws_auth_tests.json eval $(jq -r '.authtestparams | to_entries | map("export \(.key)=\(.value|tostring)")|.[]' $AUTH_PARAMETER_FILE) +export SNOWFLAKE_AUTH_TEST_PRIVATE_KEY_PATH=./.github/workflows/rsa_keys/rsa_key.p8 +export SNOWFLAKE_AUTH_TEST_ENCRYPTED_PRIVATE_KEY_PATH=./.github/workflows/rsa_keys/rsa_encrypted_key.p8 +export SNOWFLAKE_AUTH_TEST_INVALID_PRIVATE_KEY_PATH=./.github/workflows/rsa_keys/rsa_key_invalid.p8 + npm run test:authentication diff --git a/ci/test_authentication.sh b/ci/test_authentication.sh index 725520798..02baada7f 100755 --- a/ci/test_authentication.sh +++ b/ci/test_authentication.sh @@ -5,6 +5,9 @@ THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" export WORKSPACE=${WORKSPACE:-/tmp} gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/parameters_aws_auth_tests.json "$THIS_DIR/../.github/workflows/parameters_aws_auth_tests.json.gpg" +gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/rsa_keys/rsa_encrypted_key.p8 "$THIS_DIR/../.github/workflows/rsa_keys/rsa_encrypted_key.p8.gpg" +gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/rsa_keys/rsa_key.p8 "$THIS_DIR/../.github/workflows/rsa_keys/rsa_key.p8.gpg" +gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/rsa_keys/rsa_key_invalid.p8 "$THIS_DIR/../.github/workflows/rsa_keys/rsa_key_invalid.p8.gpg" docker run \ -v $(cd $THIS_DIR/.. && pwd):/mnt/host \ diff --git a/test/authentication/connectionParameters.js b/test/authentication/connectionParameters.js index 9204f6941..6b98ea5c5 100644 --- a/test/authentication/connectionParameters.js +++ b/test/authentication/connectionParameters.js @@ -1,3 +1,4 @@ +const path = require('path'); const snowflakeAuthTestProtocol = process.env.SNOWFLAKE_AUTH_TEST_PROTOCOL; const snowflakeAuthTestHost = process.env.SNOWFLAKE_AUTH_TEST_HOST; const snowflakeAuthTestPort = process.env.SNOWFLAKE_AUTH_TEST_PORT; @@ -13,6 +14,10 @@ const snowflakeAuthTestOauthClientSecret = process.env.SNOWFLAKE_AUTH_TEST_OAUTH const snowflakeAuthTestDatabase = process.env.SNOWFLAKE_AUTH_TEST_DATABASE; const snowflakeAuthTestWarehouse = process.env.SNOWFLAKE_AUTH_TEST_WAREHOUSE; const snowflakeAuthTestSchema = process.env.SNOWFLAKE_AUTH_TEST_SCHEMA; +const snowflakeAuthTestPrivateKeyPath = process.env.SNOWFLAKE_AUTH_TEST_PRIVATE_KEY_PATH; +const snowflakeAuthTestInvalidPrivateKeyPath = process.env.SNOWFLAKE_AUTH_TEST_INVALID_PRIVATE_KEY_PATH; +const snowflakeAuthTestPrivateKeyPassword = process.env.SNOWFLAKE_AUTH_TEST_PRIVATE_KEY_PASSWORD; +const snowflakeAuthTestEncryptedPrivateKeyPath = process.env.SNOWFLAKE_AUTH_TEST_ENCRYPTED_PRIVATE_KEY_PATH; const accessUrlAuthTests = snowflakeAuthTestProtocol + '://' + snowflakeAuthTestHost + ':' + snowflakeAuthTestPort; @@ -50,9 +55,36 @@ const oauth = authenticator: 'OAUTH' }; +const keypairPrivateKey = + { + ...baseParameters, + username: snowflakeAuthTestOktaUser, + authenticator: 'SNOWFLAKE_JWT' + }; + +const keypairPrivateKeyPath = + { + ...baseParameters, + username: snowflakeAuthTestOktaUser, + privateKeyPath: snowflakeAuthTestPrivateKeyPath, + authenticator: 'SNOWFLAKE_JWT' + }; + +const keypairEncryptedPrivateKeyPath = + { + ...baseParameters, + username: snowflakeAuthTestOktaUser, + privateKeyPass: snowflakeAuthTestPrivateKeyPassword, + privateKeyPath: snowflakeAuthTestEncryptedPrivateKeyPath, + authenticator: 'SNOWFLAKE_JWT' + }; + exports.externalBrowser = externalBrowser; exports.okta = okta; exports.oauth = oauth; +exports.keypairPrivateKey = keypairPrivateKey; +exports.keypairPrivateKeyPath = keypairPrivateKeyPath; +exports.keypairEncryptedPrivateKeyPath = keypairEncryptedPrivateKeyPath; exports.snowflakeTestBrowserUser = snowflakeAuthTestBrowserUser; exports.snowflakeAuthTestOktaUser = snowflakeAuthTestOktaUser; exports.snowflakeAuthTestOktaPass = snowflakeAuthTestOktaPass; @@ -60,3 +92,5 @@ exports.snowflakeAuthTestRole = snowflakeAuthTestRole; exports.snowflakeAuthTestOauthClientId = snowflakeAuthTestOauthClientId; exports.snowflakeAuthTestOauthClientSecret = snowflakeAuthTestOauthClientSecret; exports.snowflakeAuthTestOauthUrl = snowflakeAuthTestOauthUrl; +exports.snowflakeAuthTestPrivateKeyPath = snowflakeAuthTestPrivateKeyPath; +exports.snowflakeAuthTestInvalidPrivateKeyPath = snowflakeAuthTestInvalidPrivateKeyPath; diff --git a/test/authentication/testKeyPair.js b/test/authentication/testKeyPair.js new file mode 100644 index 000000000..a85a1513d --- /dev/null +++ b/test/authentication/testKeyPair.js @@ -0,0 +1,93 @@ +const AuthTest = require('./authTestsBaseClass'); +const connParameters = require('./connectionParameters'); +const snowflake = require('../../lib/snowflake'); +const path = require('path'); +const assert = require('node:assert'); +const fs = require('fs').promises; + + +describe('Key-pair authentication', function () { + let authTest; + + beforeEach(async () => { + authTest = new AuthTest(); + }); + + afterEach(async () => { + await authTest.destroyConnection(); + }); + + describe('Private key', function () { + it('Successful connection', async function () { + const privateKey = await getFileContent(connParameters.snowflakeAuthTestPrivateKeyPath); + const connectionOption = { ... connParameters.keypairPrivateKey, privateKey: privateKey }; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + authTest.verifyNoErrorWasThrown(); + await authTest.verifyConnectionIsUp(); + }); + + it('Invalid private key format', async function () { + const invalidPrivateKeyFormat = 'invalidKey'; + const connectionOption = { ... connParameters.keypairPrivateKey, privateKey: invalidPrivateKeyFormat }; + try { + snowflake.createConnection(connectionOption); + assert.fail('Expected error was not thrown'); + } catch (err) { + assert.strictEqual(err.message, 'Invalid private key. The specified value must be a string in pem format of type pkcs8'); + } + }); + + it('Invalid private key', async function () { + const privateKey = await getFileContent(connParameters.snowflakeAuthTestInvalidPrivateKeyPath); + const connectionOption = { ... connParameters.keypairPrivateKey, privateKey: privateKey }; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + assert.match(authTest.error?.message, /JWT token is invalid./); + await authTest.verifyConnectionIsNotUp('Unable to perform operation using terminated connection.'); + }); + }); + + describe('Private key path', function () { + it('Successful connection', async function () { + const connectionOption = connParameters.keypairPrivateKeyPath; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + authTest.verifyNoErrorWasThrown(); + await authTest.verifyConnectionIsUp(); + }); + + it('Invalid private key', async function () { + const connectionOption = { ...connParameters.keypairPrivateKeyPath, privateKeyPath: connParameters.snowflakeAuthTestInvalidPrivateKeyPath }; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + assert.match(authTest.error?.message, /JWT token is invalid./); + await authTest.verifyConnectionIsNotUp('Unable to perform operation using terminated connection.'); + }); + + it('Successful connection using encrypted private key', async function () { + const connectionOption = connParameters.keypairEncryptedPrivateKeyPath; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + authTest.verifyNoErrorWasThrown(); + await authTest.verifyConnectionIsUp(); + }); + + it('Invalid private key password', async function () { + const connectionOption = { ...connParameters.keypairEncryptedPrivateKeyPath, privateKeyPass: 'invalid' }; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + assert.match(authTest.error?.message, /bad decrypt/); + await authTest.verifyConnectionIsNotUp(); + }); + }); +}); + +async function getFileContent(filePath) { + try { + const absolutePath = path.resolve(filePath); + return await fs.readFile(absolutePath, 'utf8'); + } catch (err) { + throw new Error(`Error reading file: ${err.message}`); + } +} diff --git a/test/integration/connectionOptions.js b/test/integration/connectionOptions.js index 2809dc595..88c56e344 100644 --- a/test/integration/connectionOptions.js +++ b/test/integration/connectionOptions.js @@ -16,11 +16,6 @@ const snowflakeTestRole = process.env.SNOWFLAKE_TEST_ROLE; const snowflakeTestPassword = process.env.SNOWFLAKE_TEST_PASSWORD; const snowflakeTestAdminUser = process.env.SNOWFLAKE_TEST_ADMIN_USER; const snowflakeTestAdminPassword = process.env.SNOWFLAKE_TEST_ADMIN_PASSWORD; -const snowflakeTestPrivateKeyUser = process.env.SNOWFLAKE_JWT_TEST_USER; -const snowflakeTestPrivateKey = process.env.SNOWFLAKE_TEST_PRIVATE_KEY; -const snowflakeTestPrivateKeyPath = process.env.SNOWFLAKE_TEST_PRIVATE_KEY_PATH; -const snowflakeTestPrivateKeyPass = process.env.SNOWFLAKE_TEST_PRIVATE_KEY_PASS; -const snowflakeTestPrivateKeyPathUnencrypted = process.env.SNOWFLAKE_TEST_PRIVATE_KEY_PATH_UNENCRYPTED; const snowflakeTestPasscode = process.env.SNOWFLAKE_TEST_PASSCODE; if (snowflakeTestProtocol === undefined) { @@ -83,55 +78,6 @@ const wrongPwd = account: snowflakeTestAccount }; -const keypairPrivateKey = -{ - accessUrl: accessUrl, - username: snowflakeTestPrivateKeyUser, - account: snowflakeTestAccount, - warehouse: snowflakeTestWarehouse, - database: snowflakeTestDatabase, - schema: snowflakeTestSchema, - role: snowflakeTestRole, - privateKey: snowflakeTestPrivateKey, - authenticator: 'SNOWFLAKE_JWT' -}; - -const keypairPathEncrypted = -{ - accessUrl: accessUrl, - username: snowflakeTestPrivateKeyUser, - account: snowflakeTestAccount, - warehouse: snowflakeTestWarehouse, - database: snowflakeTestDatabase, - schema: snowflakeTestSchema, - role: snowflakeTestRole, - privateKeyPath: snowflakeTestPrivateKeyPath, - privateKeyPass: snowflakeTestPrivateKeyPass, - authenticator: 'SNOWFLAKE_JWT' -}; - -const keypairPathUnencrypted = -{ - accessUrl: accessUrl, - username: snowflakeTestPrivateKeyUser, - account: snowflakeTestAccount, - warehouse: snowflakeTestWarehouse, - database: snowflakeTestDatabase, - schema: snowflakeTestSchema, - role: snowflakeTestRole, - privateKeyPath: snowflakeTestPrivateKeyPathUnencrypted, - authenticator: 'SNOWFLAKE_JWT' -}; - -const keypairWrongToken = -{ - accessUrl: accessUrl, - username: 'node', - account: snowflakeTestAccount, - privateKey: snowflakeTestPrivateKey, - authenticator: 'SNOWFLAKE_JWT' -}; - const MFA = { ...valid, authenticator: 'USER_PWD_MFA_AUTHENTICATOR', @@ -166,10 +112,6 @@ exports.wrongUserName = wrongUserName; exports.wrongPwd = wrongPwd; exports.accessUrl = accessUrl; exports.account = snowflakeTestAccount; -exports.keypairPrivateKey = keypairPrivateKey; -exports.keypairPathEncrypted = keypairPathEncrypted; -exports.keypairPathUnencrypted = keypairPathUnencrypted; -exports.keypairWrongToken = keypairWrongToken; exports.privatelink = privatelink; exports.connectionWithProxy = connectionWithProxy; exports.MFA = MFA; diff --git a/test/integration/testManualConnection.js b/test/integration/testManualConnection.js index 45fa3bbe8..5ae0eb5cc 100644 --- a/test/integration/testManualConnection.js +++ b/test/integration/testManualConnection.js @@ -101,113 +101,6 @@ if (process.env.RUN_MANUAL_TESTS_ONLY === 'true') { }); }); }); - - describe('Connection test - keypair', function () { - it('Simple Connect - specify private key', function (done) { - const connection = snowflake.createConnection( - connOption.keypairPrivateKey - ); - - async.series([ - function (callback) { - connection.connect(function (err) { - done(err); - assert.ok(!err, JSON.stringify(err)); - callback(); - }); - }, - function (callback) { - assert.ok(connection.isUp(), 'not active'); - callback(); - }, - function (callback) { - connection.destroy(function (err) { - assert.ok(!err, JSON.stringify(err)); - callback(); - }); - }, - function (callback) { - assert.ok(!connection.isUp(), 'still active'); - callback(); - }, - ]); - }); - - it('Simple Connect - specify encrypted private key path and passphrase', function (done) { - const connection = snowflake.createConnection( - connOption.keypairPathEncrypted - ); - - async.series([ - function (callback) { - connection.connect(function (err) { - done(err); - assert.ok(!err, JSON.stringify(err)); - callback(); - }); - }, - function (callback) { - assert.ok(connection.isUp(), 'not active'); - callback(); - }, - function (callback) { - connection.destroy(function (err) { - assert.ok(!err, JSON.stringify(err)); - callback(); - }); - }, - function (callback) { - assert.ok(!connection.isUp(), 'still active'); - callback(); - }, - ]); - }); - - it('Simple Connect - specify unencrypted private key path without passphrase', function (done) { - const connection = snowflake.createConnection( - connOption.keypairPathEncrypted - ); - - async.series([ - function (callback) { - connection.connect(function (err) { - done(err); - assert.ok(!err, JSON.stringify(err)); - callback(); - }); - }, - function (callback) { - assert.ok(connection.isUp(), 'not active'); - callback(); - }, - function (callback) { - connection.destroy(function (err) { - assert.ok(!err, JSON.stringify(err)); - callback(); - }); - }, - function (callback) { - assert.ok(!connection.isUp(), 'still active'); - callback(); - }, - ]); - }); - - it('Wrong JWT token', function (done) { - const connection = snowflake.createConnection( - connOption.keypairWrongToken - ); - connection.connect(function (err) { - try { - assert.ok(err, 'Incorrect JWT token is passed.'); - assert.equal('JWT token is invalid.', err['message']); - done(); - } catch (err) { - done(err); - } - }); - }); - }); }); describe('keepAlive test', function () {