Skip to content

Commit

Permalink
[FEATURE] Allow overriding the aws service identifier in AwsSigv4Sign…
Browse files Browse the repository at this point in the history
…er (opensearch-project#333)

* [FEATURE] Allow overriding the aws service identifier in AwsSigv4Signer

Currently the service identifier is hard coded to es with no way to
override, this change allows for a user provided `service` option to
override the default.

defaulted the `opts` parameter to an empty object literal which allowed
for some type checking cleanup.

Signed-off-by: Lee Shepherd <[email protected]>

* Update changelog for opensearch-project#333

Signed-off-by: Lee Shepherd <[email protected]>

* Update planned test assertions

I swear I ran this test...

Signed-off-by: Lee Shepherd <[email protected]>

Signed-off-by: Lee Shepherd <[email protected]>
(cherry picked from commit 2f840e5)
  • Loading branch information
bwg authored and harshavamsi committed Feb 21, 2023
1 parent aa64b9e commit ba2a2f6
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 27 deletions.
71 changes: 59 additions & 12 deletions lib/aws/AwsSigv4Signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,70 @@ const Transport = require('../Transport')
const aws4 = require('aws4')
const AwsSigv4SignerError = require('./errors')

function AwsSigv4Signer (opts) {
const getAwsSDKCredentialsProvider = async () => {
// First try V3
try {
const awsV3 = await import('@aws-sdk/credential-provider-node');
if (typeof awsV3.defaultProvider === 'function') {
return awsV3.defaultProvider();
}
} catch (err) {
// Ignore
}
try {
const awsV2 = await import('aws-sdk');
if (awsV2.default && typeof awsV2.default.config.getCredentials === 'function') {
return () =>
new Promise((resolve, reject) => {
awsV2.default.config.getCredentials((err, credentials) => {
if (err) {
reject(err);
} else {
resolve(credentials);
}
});
});
}
} catch (err) {
// Ignore
}

throw new AwsSigv4SignerError(
'Unable to find a valid AWS SDK, please provide a valid getCredentials function to AwsSigv4Signer options.'
);
};

const awsDefaultCredentialsProvider = () =>
new Promise((resolve, reject) => {
getAwsSDKCredentialsProvider()
.then((provider) => {
provider().then(resolve).catch(reject);
})
.catch((err) => {
reject(err);
});
});

function AwsSigv4Signer(opts = {}) {
const credentialsState = {
credentials: null
credentials: null,
};
if (!opts.region) {
throw new AwsSigv4SignerError('Region cannot be empty');
}
if (opts && (!opts.region || opts.region === null || opts.region === '')) {
throw new AwsSigv4SignerError('Region cannot be empty')
if (!opts.service) {
opts.service = 'es';
}
if (opts && typeof opts.getCredentials !== 'function') {
throw new AwsSigv4SignerError('getCredentials function is required')
if (typeof opts.getCredentials !== 'function') {
opts.getCredentials = awsDefaultCredentialsProvider;
}

function buildSignedRequestObject (request = {}) {
request.service = 'es'
request.region = opts.region
request.headers = request.headers || {}
request.headers.host = request.hostname
return aws4.sign(request, credentialsState.credentials)
function buildSignedRequestObject(request = {}) {
request.service = opts.service;
request.region = opts.region;
request.headers = request.headers || {};
request.headers['host'] = request.hostname;
return aws4.sign(request, credentialsState.credentials);
}

class AwsSigv4SignerConnection extends Connection {
Expand Down
70 changes: 55 additions & 15 deletions test/unit/lib/aws/awssigv4signer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const { Connection } = require('../../../../index')
const { Client, buildServer } = require('../../../utils')

test('Sign with SigV4', (t) => {
t.plan(2)
t.plan(3);

const mockCreds = {
accessKeyId: uuidv4(),
Expand Down Expand Up @@ -48,34 +48,74 @@ test('Sign with SigV4', (t) => {
}
})

const signedRequest = auth.buildSignedRequestObject(request)
t.hasProp(signedRequest.headers, 'X-Amz-Date')
t.hasProp(signedRequest.headers, 'Authorization')
})
const signedRequest = auth.buildSignedRequestObject(request);
t.hasProp(signedRequest.headers, 'X-Amz-Date');
t.hasProp(signedRequest.headers, 'Authorization');
t.same(signedRequest.service, 'es');
});

test('Sign with SigV4 failure (with empty region)', (t) => {
t.plan(2)

const mockCreds = {
accessKeyId: uuidv4(),
secretAccessKey: uuidv4()
}

const mockRegions = [{ region: undefined }, { region: null }, { region: '' }, {}];

const AwsSigv4SignerOptions = {
getCredentials: () =>
new Promise((resolve) => {
setTimeout(() => resolve(mockCreds), 100)
})
}

try {
AwsSigv4Signer(AwsSigv4SignerOptions)
t.fail('Should fail')
} catch (err) {
t.ok(err instanceof AwsSigv4SignerError)
t.same(err.message, 'Region cannot be empty')
}
})
mockRegions.forEach((region) => {
try {
AwsSigv4Signer(Object.assign({}, AwsSigv4SignerOptions, region));
t.fail('Should fail');
} catch (err) {
t.ok(err instanceof AwsSigv4SignerError);
t.same(err.message, 'Region cannot be empty');
}
});

t.end();
});

test('Sign with SigV4 and provided service', (t) => {
t.plan(1);

const mockCreds = {
accessKeyId: uuidv4(),
secretAccessKey: uuidv4(),
};

const mockRegion = 'us-west-2';
const mockService = 'foo';

const AwsSigv4SignerOptions = {
getCredentials: () =>
new Promise((resolve) => {
setTimeout(() => resolve(mockCreds), 100);
}),
region: mockRegion,
service: mockService,
};

const auth = AwsSigv4Signer(AwsSigv4SignerOptions);

const connection = new Connection({
url: new URL('https://localhost:9200'),
});

const request = connection.buildRequestObject({
path: '/hello',
method: 'GET',
});

const signedRequest = auth.buildSignedRequestObject(request);
t.same(signedRequest.service, mockService);
});

test('Sign with SigV4 failure (without getCredentials function)', (t) => {
t.plan(2)
Expand Down

0 comments on commit ba2a2f6

Please sign in to comment.