From 250e0416ccf13640823c584938c8fe744735f81a Mon Sep 17 00:00:00 2001 From: Brett Gardner Date: Mon, 8 Apr 2024 12:43:08 +1000 Subject: [PATCH 1/3] fix: Temporary file written with current user read only. Can run test-suite using podman --- core/awscredentials.js | 2 +- tests/docker/docker-compose.yml | 4 +-- tests/test.sh | 44 ++++++++++++++++++-------- tests/unit-test/awscredentials_test.js | 42 +++++++++++++++++++++++- 4 files changed, 74 insertions(+), 18 deletions(-) diff --git a/core/awscredentials.js b/core/awscredentials.js index 1efd9d5..602bdd3 100644 --- a/core/awscredentials.js +++ b/core/awscredentials.js @@ -205,7 +205,7 @@ function _writeCredentialsToKeyValStore(r, credentials) { * @private */ function _writeCredentialsToFile(credentials) { - fs.writeFileSync(_credentialsTempFile(), JSON.stringify(credentials)); + fs.writeFileSync(_credentialsTempFile(), JSON.stringify(credentials),{mode:0x400}); } /** diff --git a/tests/docker/docker-compose.yml b/tests/docker/docker-compose.yml index f5b9bd9..8ee1f30 100644 --- a/tests/docker/docker-compose.yml +++ b/tests/docker/docker-compose.yml @@ -3,10 +3,10 @@ services: nginx_aws_signature_test: hostname: nginx_aws_signature_test container_name: nginx_aws_signature_test - image: nginx_aws_signature_test:${NGINX_TYPE} + image: nginx_aws_signature_test:${nginx_type} build: context: ./ - dockerfile: Dockerfile.${NGINX_TYPE} + dockerfile: Dockerfile.${nginx_type} volumes: - ./build_context/etc/nginx/conf.d:/etc/nginx/conf.d - ../../core:/etc/nginx/serverless diff --git a/tests/test.sh b/tests/test.sh index a4d36c3..a85d480 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -37,7 +37,9 @@ e() { >&2 echo "$1" } -usage() { e "Usage: $0 [--latest-njs ] [--unprivileged ] [--type " 1>&2; exit 1; } +usage() { e "Usage: $0 [--latest-njs ] [--podman] [--unprivileged ] [--type " 1>&2; exit 1; } + +podman=0 for arg in "$@"; do shift @@ -46,6 +48,7 @@ for arg in "$@"; do '--latest-njs') set -- "$@" '-j' ;; '--unprivileged') set -- "$@" '-u' ;; '--type') set -- "$@" '-t' ;; + '--podman') podman=1 ;; *) set -- "$@" "$arg" ;; esac done @@ -73,17 +76,19 @@ startup_message="" if [ -z "${nginx_type}" ]; then nginx_type="oss" startup_message="Starting NGINX ${nginx_type} (default)" -elif ! { [ ${nginx_type} == "oss" ] || [ ${nginx_type} == "plus" ]; }; then +elif ! { [ "${nginx_type}" == "oss" ] || [ "${nginx_type}" == "plus" ]; }; then e "Invalid NGINX type: ${nginx_type} - must be either 'oss' or 'plus'" usage else startup_message="Starting NGINX ${nginx_type}" fi +export nginx_type + if [ -z "${njs_latest}" ]; then njs_latest="0" startup_message="${startup_message} with the release NJS module (default)" -elif [ ${njs_latest} -eq 1 ]; then +elif [ "${njs_latest}" -eq 1 ]; then startup_message="${startup_message} with the latest NJS module" else startup_message="${startup_message} with the release NJS module" @@ -92,7 +97,7 @@ fi if [ -z "${unprivileged}" ]; then unprivileged="0" startup_message="${startup_message} in privileged mode (default)" -elif [ ${unprivileged} -eq 1 ]; then +elif [ "${unprivileged}" -eq 1 ]; then startup_message="${startup_message} in unprivileged mode" else startup_message="${startup_message} in privileged mode" @@ -102,13 +107,22 @@ e "${startup_message}" set -o nounset # abort on unbound variable -docker_cmd="$(command -v docker)" +set +o errexit +if [ "$podman" -eq 1 ]; then + docker_cmd="$(command -v podman)" +else + docker_cmd="$(command -v docker)" +fi if ! [ -x "${docker_cmd}" ]; then e "required dependency not found: docker not found in the path or not executable" exit ${no_dep_exit_code} fi -docker_compose_cmd="$(command -v docker-compose)" +if [ "$podman" -eq 1 ]; then + docker_compose_cmd="$(command -v podman-compose)" +else + docker_compose_cmd="$(command -v docker-compose)" +fi if ! [ -x "${docker_compose_cmd}" ]; then e "required dependency not found: docker-compose not found in the path or not executable" exit ${no_dep_exit_code} @@ -128,6 +142,8 @@ else wait_for_it_installed=0 fi +set -o errexit + if [ "${nginx_type}" = "plus" ]; then if [ ! -f "${ssl_dir}/nginx-repo.crt" ]; then e "NGINX Plus certificate file not found: ${ssl_dir}/nginx-repo.crt" @@ -142,7 +158,7 @@ fi compose() { # Hint to docker-compose the internal port to map for the container - if [ ${unprivileged} -eq 1 ]; then + if [ "${unprivileged}" -eq 1 ]; then export NGINX_INTERNAL_PORT=8080 else export NGINX_INTERNAL_PORT=80 @@ -160,8 +176,8 @@ finish() { fi p "Cleaning up Docker compose environment" - docker kill nginx_aws_signature_test 2> /dev/null || true - docker rmi nginx_aws_signature_test 2> /dev/null || true + "${docker_cmd}" kill nginx_aws_signature_test 2> /dev/null || true + "${docker_cmd}" rmi nginx_aws_signature_test 2> /dev/null || true exit ${result} } @@ -170,13 +186,13 @@ trap finish EXIT ERR SIGTERM SIGINT ### BUILD p "Building NGINX AWS Signature Lib Test Docker image" -docker-compose -f ${test_compose_config} up -d +"${docker_compose_cmd}" -f "${test_compose_config}" up -d ### UNIT TESTS runUnitTestWithOutSessionToken() { test_code="$1" - docker exec \ + "${docker_cmd}" exec \ -e "S3_DEBUG=true" \ -e "S3_STYLE=virtual" \ -e "AWS_ACCESS_KEY_ID=unit_test" \ @@ -187,13 +203,13 @@ runUnitTestWithOutSessionToken() { -e "S3_SERVER_PORT=443" \ -e "S3_REGION=test-1" \ -e "AWS_SIGS_VERSION=4" \ - nginx_aws_signature_test njs /var/tmp/${test_code} + nginx_aws_signature_test njs "/var/tmp/${test_code}" } runUnitTestWithSessionToken() { test_code="$1" - docker exec \ + "${docker_cmd}" exec \ -e "S3_DEBUG=true" \ -e "S3_STYLE=virtual" \ -e "AWS_ACCESS_KEY_ID=unit_test" \ @@ -205,7 +221,7 @@ runUnitTestWithSessionToken() { -e "S3_SERVER_PORT=443" \ -e "S3_REGION=test-1" \ -e "AWS_SIGS_VERSION=4" \ - nginx_aws_signature_test njs /var/tmp/${test_code} + nginx_aws_signature_test njs "/var/tmp/${test_code}" } p "Running unit tests for utils" diff --git a/tests/unit-test/awscredentials_test.js b/tests/unit-test/awscredentials_test.js index 8f357a3..7d761ec 100644 --- a/tests/unit-test/awscredentials_test.js +++ b/tests/unit-test/awscredentials_test.js @@ -75,7 +75,7 @@ function testReadCredentialsFromFilePath() { var tempFile = `${tempDir}/credentials-unit-test-${uniqId}.json`; var testData = '{"accessKeyId":"A","secretAccessKey":"B",' + '"sessionToken":"C","expiration":"2022-02-15T04:49:08Z"}'; - fs.writeFileSync(tempFile, testData); + fs.writeFileSync(tempFile, testData,{mode:0x400}); try { process.env['AWS_CREDENTIALS_TEMP_FILE'] = tempFile; @@ -103,6 +103,45 @@ function testReadCredentialsFromFilePath() { } } +function testWriteCredentialsToFilePath() { + printHeader('testWriteCredentialsToFilePath'); + let r = { + variables: { + cache_instance_credentials_enabled: 0 + } + }; + + var originalCredentialPath = process.env['AWS_CREDENTIALS_TEMP_FILE']; + var tempDir = (process.env['TMPDIR'] ? process.env['TMPDIR'] : '/tmp'); + var uniqId = `${new Date().getTime()}-${Math.floor(Math.random()*101)}`; + var tempFile = `${tempDir}/credentials-unit-test-${uniqId}.json`; + var testData = { + accessKeyId:"A", + secretAccessKey:"B", + sessionToken:"C", + expiration:"2022-02-15T04:49:08Z" + }; + + try { + process.env['AWS_CREDENTIALS_TEMP_FILE'] = tempFile; + awscred.writeCredentials(r, testData); + + var stats = fs.statSync(tempFile); + + if (stats.mode & 0x70 || stats.mode & 0x7) { + throw 'Temporary file has anonymous or group permissions'; + } + + } finally { + if (originalCredentialPath) { + process.env['AWS_CREDENTIALS_TEMP_FILE'] = originalCredentialPath; + } + if (fs.statSync(tempFile, {throwIfNoEntry: false})) { + fs.unlinkSync(tempFile); + } + } +} + function testReadCredentialsFromNonexistentPath() { printHeader('testReadCredentialsFromNonexistentPath'); let r = { @@ -311,6 +350,7 @@ async function test() { testReadCredentialsFromFilePath(); testReadCredentialsFromNonexistentPath(); testReadAndWriteCredentialsFromKeyValStore(); + testWriteCredentialsToFilePath() } function printHeader(testName) { From ac8055f697206fda15761640d9b92ab9cbd80273 Mon Sep 17 00:00:00 2001 From: Brett Gardner Date: Tue, 9 Apr 2024 16:01:43 +1000 Subject: [PATCH 2/3] Sigv4 failed if extra x-amz-* headers were in the request, eg x-amz-expected-bucket-owner. Changed from keyval or filestore, to ngx.shared dictionary. --- CHANGELOG.md | 4 ++ README.md | 5 ++ core/awscredentials.js | 133 +++++++++++------------------------------ core/awssig4.js | 61 +++++++++++++++---- 4 files changed, 93 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7261299..00f40a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,7 @@ - Initial release of the `nginx-aws-signature` repository. - Compatible with [`nginx-s3-gateway`](https://github.com/nginxinc/nginx-s3-gateway) and [`nginx-lambda-gateway`](https://github.com/nginx-serverless/nginx-lambda-gateway). + +## 1.1.0 (9 4, 2024) + +- Removed keyval and filestore, replaced with ngx.shared diff --git a/README.md b/README.md index 6657a07..f8ed23e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ NGINX AWS Signature Library to authenticate AWS services such as S3 and Lambda v This project is to provide the common library for your apps or services. To get this project up and running, the following nginx project can be used prior to implementing your project. +Requires njs 0.8.0+ + - [Getting Started with `nginx-s3-gateway`](https://github.com/nginxinc/nginx-s3-gateway#getting-started) - [Getting Started with `nginx-lambda-gateway`](https://github.com/nginx-serverless/nginx-lambda-gateway#getting-started) @@ -96,6 +98,9 @@ map $request_uri $lambda_url { default https://lambda.us-east-1.amazonaws.com; } +#Create a shared dictionay 'aws' for ngx.shared +js_shared_dict_zone zone=aws:32k type=string; + server { listen 80; # Use SSL/TLS in production diff --git a/core/awscredentials.js b/core/awscredentials.js index 602bdd3..339e259 100644 --- a/core/awscredentials.js +++ b/core/awscredentials.js @@ -89,79 +89,15 @@ function readCredentials(r) { expiration: null }; } - if ("variables" in r && r.variables.cache_instance_credentials_enabled == 1) { - return _readCredentialsFromKeyValStore(r); - } else { - return _readCredentialsFromFile(); - } -} - -/** - * Read credentials from the NGINX Keyval store. If it is not found, then - * return undefined. - * - * @param r {Request} HTTP request object (not used, but required for NGINX configuration) - * @returns {undefined|{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials or undefined - * @private - */ -function _readCredentialsFromKeyValStore(r) { - const cached = r.variables.instance_credential_json; - - if (!cached) { - return undefined; - } - - try { - return JSON.parse(cached); - } catch (e) { - utils.debug_log(r, `Error parsing JSON value from r.variables.instance_credential_json: ${e}`); - return undefined; - } -} -/** - * Read the contents of the credentials file into memory. If it is not - * found, then return undefined. - * - * @returns {undefined|{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials or undefined - * @private - */ -function _readCredentialsFromFile() { - const credsFilePath = _credentialsTempFile(); - - try { - const creds = fs.readFileSync(credsFilePath); - return JSON.parse(creds); - } catch (e) { - /* Do not throw an exception in the case of when the - credentials file path is invalid in order to signal to - the caller that such a file has not been created yet. */ - if (e.code === 'ENOENT') { - return undefined; - } - throw e; - } -} - -/** - * Returns the path to the credentials temporary cache file. - * - * @returns {string} path on the file system to credentials cache file - * @private - */ -function _credentialsTempFile() { - if (process.env['AWS_CREDENTIALS_TEMP_FILE']) { - return process.env['AWS_CREDENTIALS_TEMP_FILE']; - } - if (process.env['TMPDIR']) { - return `${process.env['TMPDIR']}/credentials.json` + if (ngx.shared.aws.has('instance_credential_json')) { + return JSON.parse(ngx.shared.aws.get('instance_credential_json')); } - return '/tmp/credentials.json'; } /** - * Write the instance profile credentials to a caching backend. + * Write the instance profile credentials to ngx.shared.aws * * @param r {Request} HTTP request object (not used, but required for NGINX configuration) * @param credentials {{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials @@ -177,35 +113,7 @@ function writeCredentials(r, credentials) { throw `Cannot write invalid credentials: ${JSON.stringify(credentials)}`; } - if ("variables" in r && r.variables.cache_instance_credentials_enabled == 1) { - _writeCredentialsToKeyValStore(r, credentials); - } else { - _writeCredentialsToFile(credentials); - } -} - -/** - * Write the instance profile credentials to the NGINX Keyval store. - * - * @param r {Request} HTTP request object (not used, but required for NGINX configuration) - * @param credentials {{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials - * @private - */ -function _writeCredentialsToKeyValStore(r, credentials) { - r.variables.instance_credential_json = JSON.stringify(credentials); -} - -/** - * Write the instance profile credentials to a file on the file system. This - * file will be quite small and should end up in the file cache relatively - * quickly if it is repeatedly read. - * - * @param r {Request} HTTP request object (not used, but required for NGINX configuration) - * @param credentials {{accessKeyId: (string), secretAccessKey: (string), sessionToken: (string), expiration: (string)}} AWS instance profile credentials - * @private - */ -function _writeCredentialsToFile(credentials) { - fs.writeFileSync(_credentialsTempFile(), JSON.stringify(credentials),{mode:0x400}); + ngx.shared.aws.set('instance_credential_json', JSON.stringify(credentials)); } /** @@ -223,12 +131,18 @@ function _writeCredentialsToFile(credentials) { * quickly exits. * * @param r {Request} HTTP request object - * @returns {Promise} + * @param inline {bool} If true, returns the result as a boolean, instead of invoking return on "r" + * + * @returns {Promise|bool} */ -async function fetchCredentials(r) { +async function fetchCredentials(r, inline) { + /* If we are not using an AWS instance profile to set our credentials we exit quickly and don't write a credentials file. */ if (utils.areAllEnvVarsSet(['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY'])) { + if (inline) { + return true; + } r.return(200); return; } @@ -239,6 +153,9 @@ async function fetchCredentials(r) { current = readCredentials(r); } catch (e) { utils.debug_log(r, `Could not read credentials: ${e}`); + if (inline) { + return false; + } r.return(500); return; } @@ -249,6 +166,9 @@ async function fetchCredentials(r) { const expireAt = typeof current.expiration == 'number' ? current.expiration * 1000 : current.expiration const exp = new Date(expireAt).getTime() - maxValidityOffsetMs; if (NOW.getTime() < exp) { + if (inline) { + return true; + } r.return(200); return; } @@ -265,6 +185,9 @@ async function fetchCredentials(r) { credentials = await _fetchEcsRoleCredentials(uri); } catch (e) { utils.debug_log(r, 'Could not load ECS task role credentials: ' + JSON.stringify(e)); + if (inline) { + return false; + } r.return(500); return; } @@ -274,6 +197,9 @@ async function fetchCredentials(r) { credentials = await _fetchWebIdentityCredentials(r) } catch (e) { utils.debug_log(r, 'Could not assume role using web identity: ' + JSON.stringify(e)); + if (inline) { + return false; + } r.return(500); return; } @@ -282,6 +208,9 @@ async function fetchCredentials(r) { credentials = await _fetchEC2RoleCredentials(); } catch (e) { utils.debug_log(r, 'Could not load EC2 task role credentials: ' + JSON.stringify(e)); + if (inline) { + return false; + } r.return(500); return; } @@ -290,9 +219,17 @@ async function fetchCredentials(r) { writeCredentials(r, credentials); } catch (e) { utils.debug_log(r, `Could not write credentials: ${e}`); + if (inline) { + return false; + } r.return(500); return; } + + if (inline) { + return true; + } + r.return(200); } diff --git a/core/awssig4.js b/core/awssig4.js index 9888ffc..830d0d4 100644 --- a/core/awssig4.js +++ b/core/awssig4.js @@ -27,9 +27,9 @@ const EMPTY_PAYLOAD_HASH = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495 /** * Constant defining the headers being signed. - * @type {string} + * @type {array} */ -const DEFAULT_SIGNED_HEADERS = 'host;x-amz-date'; +const DEFAULT_SIGNED_HEADERS = ['host','x-amz-date']; /** * Create HTTP Authorization header for authenticating with an AWS compatible @@ -54,7 +54,7 @@ function signatureV4(r, timestamp, region, service, uri, queryParams, host, cred credentials, region, service, canonicalRequest); const authHeader = 'AWS4-HMAC-SHA256 Credential=' .concat(credentials.accessKeyId, '/', eightDigitDate, '/', region, '/', service, '/aws4_request,', - 'SignedHeaders=', _signedHeaders(r, credentials.sessionToken), ',Signature=', signature); + 'SignedHeaders=', _signedHeaders(r, credentials.sessionToken).join(';'), ',Signature=', signature); utils.debug_log(r, 'AWS v4 Auth header: [' + authHeader + ']'); @@ -76,18 +76,40 @@ function signatureV4(r, timestamp, region, service, uri, queryParams, host, cred function _buildCanonicalRequest(r, method, uri, queryParams, host, amzDatetime, sessionToken) { const payloadHash = awsHeaderPayloadHash(r); - let canonicalHeaders = 'host:' + host + '\n' + - 'x-amz-date:' + amzDatetime + '\n'; + + const canonicalHeaders = { + host: host, + 'x-amz-date': amzDatetime + }; if (sessionToken && sessionToken.length > 0) { - canonicalHeaders += 'x-amz-security-token:' + sessionToken + '\n' + canonicalHeaders['x-amz-security-token'] = sessionToken; + } + + //headers must be in alphabetical order + const signedHeaders = _signedHeaders(r, sessionToken).sort(); + + for (let i = 0; i < signedHeaders.length; i++) { + const header = signedHeaders[i]; + if (canonicalHeaders[header] === undefined) { + canonicalHeaders[header] = r.headers.get(header); + } + } + + const orderedCanonicalHeaderKeys = Object.keys(canonicalHeaders).sort(); + + let canonicalHeaderString = ''; + for (let i = 0; i < orderedCanonicalHeaderKeys.length; i++) { + const header = orderedCanonicalHeaderKeys[i]; + + canonicalHeaderString += header + ':' + canonicalHeaders[header] + '\n'; } let canonicalRequest = method + '\n'; canonicalRequest += uri + '\n'; canonicalRequest += queryParams + '\n'; - canonicalRequest += canonicalHeaders + '\n'; - canonicalRequest += _signedHeaders(r, sessionToken) + '\n'; + canonicalRequest += canonicalHeaderString + '\n'; + canonicalRequest += signedHeaders.join(';') + '\n'; canonicalRequest += payloadHash; return canonicalRequest; } @@ -187,19 +209,34 @@ function _buildStringToSign(amzDatetime, eightDigitDate, region, service, canoni } /** - * Creates a string containing the headers that need to be signed as part of v4 + * Returns an array of the headers that need to be signed as part of the v4 * signature authentication. * * @param r {Request} HTTP request object * @param sessionToken {string|undefined} AWS session token if present - * @returns {string} semicolon delimited string of the headers needed for signing + * @returns {array} * @private */ function _signedHeaders(r, sessionToken) { - let headers = DEFAULT_SIGNED_HEADERS; + let headers = []; + + for (let i = 0; i < DEFAULT_SIGNED_HEADERS.length; i++) { + headers.push(DEFAULT_SIGNED_HEADERS[i]); + } + if (sessionToken && sessionToken.length > 0) { - headers += ';x-amz-security-token'; + headers.push('x-amz-security-token'); } + + //Any header that starts with x-amz + //must be included, eg x-amz-expected-bucket-owner + //https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-canonical-request + r.headers.forEach((k) => { + if (! headers.includes(k) && k.startsWith('x-amz-')) { + headers.push(k); + } + }); + return headers; } From e088f065d0993bc5857de6256e41e65235a9ea8d Mon Sep 17 00:00:00 2001 From: Brett Gardner Date: Tue, 9 Apr 2024 16:06:16 +1000 Subject: [PATCH 3/3] Updated tests --- .../docker/build_context/etc/nginx/nginx.conf | 4 + tests/unit-test/awscredentials_test.js | 160 ------------------ 2 files changed, 4 insertions(+), 160 deletions(-) diff --git a/tests/docker/build_context/etc/nginx/nginx.conf b/tests/docker/build_context/etc/nginx/nginx.conf index f7bc8cb..8537fd9 100644 --- a/tests/docker/build_context/etc/nginx/nginx.conf +++ b/tests/docker/build_context/etc/nginx/nginx.conf @@ -37,6 +37,10 @@ http { keepalive_timeout 65; + #Create a shared dictionay 'aws' for ngx.shared + js_shared_dict_zone zone=aws:32k type=string; + #gzip on; include /etc/nginx/conf.d/*.conf; + } diff --git a/tests/unit-test/awscredentials_test.js b/tests/unit-test/awscredentials_test.js index 7d761ec..e916dbe 100644 --- a/tests/unit-test/awscredentials_test.js +++ b/tests/unit-test/awscredentials_test.js @@ -61,162 +61,6 @@ function testReadCredentialsWithAccessSecretKeyAndSessionTokenSet() { } } -function testReadCredentialsFromFilePath() { - printHeader('testReadCredentialsFromFilePath'); - let r = { - variables: { - cache_instance_credentials_enabled: 0 - } - }; - - var originalCredentialPath = process.env['AWS_CREDENTIALS_TEMP_FILE']; - var tempDir = (process.env['TMPDIR'] ? process.env['TMPDIR'] : '/tmp'); - var uniqId = `${new Date().getTime()}-${Math.floor(Math.random()*101)}`; - var tempFile = `${tempDir}/credentials-unit-test-${uniqId}.json`; - var testData = '{"accessKeyId":"A","secretAccessKey":"B",' + - '"sessionToken":"C","expiration":"2022-02-15T04:49:08Z"}'; - fs.writeFileSync(tempFile, testData,{mode:0x400}); - - try { - process.env['AWS_CREDENTIALS_TEMP_FILE'] = tempFile; - var credentials = awscred.readCredentials(r); - var testDataAsJSON = JSON.parse(testData); - if (credentials.accessKeyId !== testDataAsJSON.accessKeyId) { - throw 'JSON test data does not match credentials [accessKeyId]'; - } - if (credentials.secretAccessKey !== testDataAsJSON.secretAccessKey) { - throw 'JSON test data does not match credentials [secretAccessKey]'; - } - if (credentials.sessionToken !== testDataAsJSON.sessionToken) { - throw 'JSON test data does not match credentials [sessionToken]'; - } - if (credentials.expiration !== testDataAsJSON.expiration) { - throw 'JSON test data does not match credentials [expiration]'; - } - } finally { - if (originalCredentialPath) { - process.env['AWS_CREDENTIALS_TEMP_FILE'] = originalCredentialPath; - } - if (fs.statSync(tempFile, {throwIfNoEntry: false})) { - fs.unlinkSync(tempFile); - } - } -} - -function testWriteCredentialsToFilePath() { - printHeader('testWriteCredentialsToFilePath'); - let r = { - variables: { - cache_instance_credentials_enabled: 0 - } - }; - - var originalCredentialPath = process.env['AWS_CREDENTIALS_TEMP_FILE']; - var tempDir = (process.env['TMPDIR'] ? process.env['TMPDIR'] : '/tmp'); - var uniqId = `${new Date().getTime()}-${Math.floor(Math.random()*101)}`; - var tempFile = `${tempDir}/credentials-unit-test-${uniqId}.json`; - var testData = { - accessKeyId:"A", - secretAccessKey:"B", - sessionToken:"C", - expiration:"2022-02-15T04:49:08Z" - }; - - try { - process.env['AWS_CREDENTIALS_TEMP_FILE'] = tempFile; - awscred.writeCredentials(r, testData); - - var stats = fs.statSync(tempFile); - - if (stats.mode & 0x70 || stats.mode & 0x7) { - throw 'Temporary file has anonymous or group permissions'; - } - - } finally { - if (originalCredentialPath) { - process.env['AWS_CREDENTIALS_TEMP_FILE'] = originalCredentialPath; - } - if (fs.statSync(tempFile, {throwIfNoEntry: false})) { - fs.unlinkSync(tempFile); - } - } -} - -function testReadCredentialsFromNonexistentPath() { - printHeader('testReadCredentialsFromNonexistentPath'); - let r = { - variables: { - cache_instance_credentials_enabled: 0 - } - }; - var originalCredentialPath = process.env['AWS_CREDENTIALS_TEMP_FILE']; - var tempDir = (process.env['TMPDIR'] ? process.env['TMPDIR'] : '/tmp'); - var uniqId = `${new Date().getTime()}-${Math.floor(Math.random()*101)}`; - var tempFile = `${tempDir}/credentials-unit-test-${uniqId}.json`; - - try { - process.env['AWS_CREDENTIALS_TEMP_FILE'] = tempFile; - var credentials = awscred.readCredentials(r); - if (credentials !== undefined) { - throw 'Credentials returned when no credentials file should be present'; - } - - } finally { - if (originalCredentialPath) { - process.env['AWS_CREDENTIALS_TEMP_FILE'] = originalCredentialPath; - } - if (fs.statSync(tempFile, {throwIfNoEntry: false})) { - fs.unlinkSync(tempFile); - } - } -} - -function testReadAndWriteCredentialsFromKeyValStore() { - printHeader('testReadAndWriteCredentialsFromKeyValStore'); - - let accessKeyId = process.env['AWS_ACCESS_KEY_ID']; - let secretKey = process.env['AWS_SECRET_ACCESS_KEY']; - let sessionToken = null; - if ('AWS_SESSION_TOKEN' in process.env) { - sessionToken = process.env['AWS_SESSION_TOKEN']; - } - delete process.env.AWS_ACCESS_KEY_ID; - delete process.env.AWS_SECRET_ACCESS_KEY; - if ('AWS_SESSION_TOKEN' in process.env) { - delete process.env.AWS_SESSION_TOKEN - } - try { - let r = { - variables: { - cache_instance_credentials_enabled: 1, - instance_credential_json: null - } - }; - let expectedCredentials = { - AccessKeyId: 'AN_ACCESS_KEY_ID', - Expiration: '2017-05-17T15:09:54Z', - RoleArn: 'TASK_ROLE_ARN', - SecretAccessKey: 'A_SECRET_ACCESS_KEY', - Token: 'A_SECURITY_TOKEN', - }; - - awscred.writeCredentials(r, expectedCredentials); - let credentials = JSON.stringify(awscred.readCredentials(r)); - let expectedJson = JSON.stringify(expectedCredentials); - - if (credentials !== expectedJson) { - console.log(`EXPECTED:\n${expectedJson}\nACTUAL:\n${credentials}`); - throw 'Credentials do not match expected value'; - } - } finally { - process.env['AWS_ACCESS_KEY_ID'] = accessKeyId; - process.env['AWS_SECRET_ACCESS_KEY'] = secretKey; - if ('AWS_SESSION_TOKEN' in process.env) { - process.env['AWS_SESSION_TOKEN'] = sessionToken - } - } -} - async function testEcsCredentialRetrieval() { printHeader('testEcsCredentialRetrieval'); if ('AWS_ACCESS_KEY_ID' in process.env) { @@ -347,10 +191,6 @@ async function test() { await testEc2CredentialRetrieval(); await testEcsCredentialRetrieval(); testReadCredentialsWithAccessSecretKeyAndSessionTokenSet(); - testReadCredentialsFromFilePath(); - testReadCredentialsFromNonexistentPath(); - testReadAndWriteCredentialsFromKeyValStore(); - testWriteCredentialsToFilePath() } function printHeader(testName) {