Skip to content

Commit cea14e7

Browse files
committed
Explicitly inject desired AWS.Config instance
Resolves issues caused by @elastic/elasticsearch not passing through custom options to the underlying Connection and Transport instances. Working with multiple sets of credentials is easier now, and working with async credential sources should be more future-proof. This change breaks completely with the previous version and the original http-aws-es library. h/t to @kelyvin for inspiration on this.
1 parent 39d870e commit cea14e7

9 files changed

+120
-267
lines changed

Diff for: README.md

+23-60
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
A tiny [Amazon Signature Version 4](https://www.npmjs.com/package/aws4) connection class for the official [Elasticsearch Node.js client](https://www.npmjs.com/package/elasticsearch), for compatibility with AWS Elasticsearch and IAM authentication.
1010

11-
> For legacy [Elasticsearch.js 16.x](https://www.npmjs.com/package/elasticsearch) support, use version 7.x of this library.
11+
Supports AWS SDK global or specific configuration instances (AWS.Config), including asyncronous credentials from IAM roles and credential refreshing.
1212

1313
## Installation
1414

@@ -18,78 +18,41 @@ npm install --save aws-elasticsearch-connector @elastic/elasticsearch aws-sdk
1818

1919
## Example usage
2020

21-
### With static credentials
21+
### Using global configuration
2222

2323
```javascript
24-
const { Client } = require('@elastic/elasticsearch');
25-
const { AmazonConnection } = require('aws-elasticsearch-connector');
24+
const { Client } = require('@elastic/elasticsearch')
25+
const AWS = require('aws-sdk')
26+
const createAwsElasticsearchConnector = require('aws-elasticsearch-connector')
2627

27-
const client = new Client({
28-
node: 'my-elasticsearch-cluster.us-east-1.es.amazonaws.com',
29-
Connection: AmazonConnection,
30-
awsConfig: {
31-
credentials: {
32-
accessKeyId: 'foo',
33-
secretAccessKey: 'bar',
34-
sessionToken: 'baz' // optional
35-
}
36-
}
37-
});
38-
```
39-
40-
### With static credentials from AWS.Config
41-
42-
```javascript
43-
const AWS = require('aws-sdk');
44-
const { Client } = require('@elastic/elasticsearch');
45-
const { AmazonConnection } = require('aws-elasticsearch-connector');
46-
47-
// Load AWS profile credentials
28+
// (Optional) load profile credentials from file
4829
AWS.config.update({
4930
profile: 'my-profile'
50-
});
31+
})
5132

5233
const client = new Client({
53-
node: 'my-elasticsearch-cluster.us-east-1.es.amazonaws.com',
54-
Connection: AmazonConnection
55-
});
34+
...createAwsElasticsearchConnector(AWS.config),
35+
node: 'https://my-elasticsearch-cluster.us-east-1.es.amazonaws.com'
36+
})
5637
```
5738

58-
### With static credentials from the environment
59-
60-
```env
61-
AWS_ACCESS_KEY_ID=foo # alias: AWS_ACCESS_KEY
62-
AWS_SECRET_ACCESS_KEY=bar # alias: AWS_SECRET_KEY
63-
AWS_SESSION_TOKEN=baz
64-
```
39+
### Using specific configuration
6540

6641
```javascript
67-
const { Client } = require('@elastic/elasticsearch');
68-
const { AmazonConnection } = require('aws-elasticsearch-connector');
42+
const { Client } = require('@elastic/elasticsearch')
43+
const AWS = require('aws-sdk')
44+
const createAwsElasticsearchConnector = require('aws-elasticsearch-connector')
6945

70-
const client = new Client({
71-
node: 'my-elasticsearch-cluster.us-east-1.es.amazonaws.com',
72-
Connection: AmazonConnection,
73-
});
74-
```
75-
76-
### With asynchronous or refreshing credentials from AWS
77-
78-
When reading AWS credentials from an IAM role or an EC2/ECS profile, the credentials
79-
will be retrieved and refreshed automatically. In this case you'll need to use the
80-
bundled `AmazonTransport` transport which will call AWS.Config.getCredentials()
81-
before each ElasticSearch request to ensure that the latest credentials are used.
82-
83-
```javascript
84-
const { Client } = require('@elastic/elasticsearch');
85-
const { AmazonConnection, AmazonTransport } = require('aws-elasticsearch-connector');
46+
const awsConfig = new AWS.Config({
47+
// Your credentials and settings here, see
48+
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#constructor-property
49+
})
8650

8751
const client = new Client({
88-
node: 'my-elasticsearch-cluster.us-east-1.es.amazonaws.com',
89-
Connection: AmazonConnection,
90-
Transport: AmazonTransport
91-
});
92-
```
52+
...createAwsElasticsearchConnector(awsConfig),
53+
node: 'https://my-elasticsearch-cluster.us-east-1.es.amazonaws.com'
54+
})
55+
````
9356

9457
## Test
9558

@@ -98,5 +61,5 @@ npm test
9861
9962
# Run integration tests against a real endpoint
10063
AWS_PROFILE=your-profile npm run test:integration -- \
101-
--endpoint https://amazon-es-host.us-east-1.es.amazonaws.com
64+
--endpoint https://my-elasticsearch-cluster.us-east-1.es.amazonaws.com
10265
```

Diff for: package-lock.json

-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
{
22
"name": "aws-elasticsearch-connector",
33
"version": "8.3.0",
4-
"description": "A tiny Amazon Signature Version 4 connection class for Elasticsearch.js 16.x, for compatibility with AWS Elasticsearch and IAM authentication. NOTE: This library is drop-in replacement for http-aws-es, which is no longer actively maintained.",
4+
"description": "A tiny Amazon Signature Version 4 connection class for @elastic/elasticsearch, for compatibility with AWS Elasticsearch and IAM authentication.",
55
"repository": "https://github.com/compwright/aws-elasticsearch-connector",
66
"bugs": "https://github.com/compwright/aws-elasticsearch-connector/issues",
77
"keywords": [
88
"elasticsearch",
9-
"elasticsearch-js",
10-
"elasticsearch-js-legacy",
119
"aws-es",
1210
"aws",
1311
"aws4",
@@ -21,7 +19,7 @@
2119
"HttpConnection"
2220
],
2321
"engines": {
24-
"node": ">= 10.0.0"
22+
"node": ">= 10"
2523
},
2624
"main": "src/index.js",
2725
"scripts": {
@@ -53,11 +51,10 @@
5351
]
5452
},
5553
"dependencies": {
56-
"aws4": "^1.10.0",
57-
"lodash.get": "^4.4.2"
54+
"aws4": "^1.10.0"
5855
},
5956
"peerDependencies": {
60-
"@elastic/elasticsearch": ">=5",
57+
"@elastic/elasticsearch": ">=7.8.0",
6158
"aws-sdk": "^2.709.0"
6259
},
6360
"devDependencies": {

Diff for: src/AmazonConnection.js

+20-38
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,31 @@
11
const { Connection } = require('@elastic/elasticsearch')
22
const aws4 = require('aws4')
3-
const AWS = require('aws-sdk')
4-
const get = require('lodash.get')
53

6-
class AmazonConnection extends Connection {
7-
constructor (options) {
8-
super(options)
9-
this.awsConfig = options.awsConfig || AWS.config
10-
}
11-
12-
get credentials () {
13-
const credentials = {
14-
accessKeyId: get(this.awsConfig, 'credentials.accessKeyId', process.env.AWS_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY || false),
15-
secretAccessKey: get(this.awsConfig, 'credentials.secretAccessKey', process.env.AWS_SECRET_ACCESS_KEY || process.env.AWS_SECRET_KEY || false),
16-
sessionToken: get(this.awsConfig, 'credentials.sessionToken', process.env.AWS_SESSION_TOKEN)
17-
}
18-
19-
if (!credentials.accessKeyId || !credentials.secretAccessKey) {
20-
throw new Error('Missing AWS credentials')
21-
}
4+
module.exports = awsConfig => {
5+
class AmazonConnection extends Connection {
6+
buildRequestObject (params) {
7+
const req = super.buildRequestObject(params)
228

23-
return credentials
24-
}
9+
req.service = 'es'
2510

26-
buildRequestObject (params) {
27-
const req = super.buildRequestObject(params)
28-
req.service = 'es'
11+
if (!req.headers) {
12+
req.headers = {}
13+
}
2914

30-
if (!req.headers) {
31-
req.headers = {}
32-
}
15+
// Fix the Host header, since HttpConnector.makeReqParams() appends
16+
// the port number which will cause signature verification to fail
17+
req.headers.host = req.hostname
3318

34-
// Fix the Host header, since HttpConnector.makeReqParams() appends
35-
// the port number which will cause signature verification to fail
36-
req.headers.host = req.hostname
19+
if (params.body) {
20+
req.headers['content-length'] = Buffer.byteLength(params.body, 'utf8')
21+
req.body = params.body
22+
} else {
23+
req.headers['content-length'] = 0
24+
}
3725

38-
if (params.body) {
39-
req.headers['content-length'] = Buffer.byteLength(params.body, 'utf8')
40-
req.body = params.body
41-
} else {
42-
req.headers['content-length'] = 0
26+
return aws4.sign(req, awsConfig.credentials)
4327
}
44-
45-
return aws4.sign(req, this.credentials)
4628
}
47-
}
4829

49-
module.exports = AmazonConnection
30+
return AmazonConnection
31+
}

Diff for: src/AmazonTransport.js

+26-27
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
const { Transport } = require('@elastic/elasticsearch')
2-
const AWS = require('aws-sdk')
32

4-
class AmazonTransport extends Transport {
5-
awaitAwsCredentials () {
6-
return new Promise((resolve, reject) => {
7-
AWS.config.getCredentials((err) => {
8-
err ? reject(err) : resolve()
9-
})
3+
function awaitAwsCredentials (awsConfig) {
4+
return new Promise((resolve, reject) => {
5+
awsConfig.getCredentials((err) => {
6+
err ? reject(err) : resolve()
107
})
11-
}
8+
})
9+
}
1210

13-
request (params, options = {}, callback = undefined) {
14-
// options is optional, so if it is omitted, options will be the callback
15-
if (typeof options === 'function') {
16-
callback = options
17-
options = {}
18-
}
11+
module.exports = awsConfig => {
12+
class AmazonTransport extends Transport {
13+
request (params, options = {}, callback = undefined) {
14+
// options is optional, so if it is omitted, options will be the callback
15+
if (typeof options === 'function') {
16+
callback = options
17+
options = {}
18+
}
1919

20-
// Promise support
21-
if (typeof callback === 'undefined') {
22-
return this.awaitAwsCredentials()
23-
.then(() => super.request(params, options))
24-
}
20+
// Promise support
21+
if (typeof callback === 'undefined') {
22+
return awaitAwsCredentials(awsConfig)
23+
.then(() => super.request(params, options))
24+
}
2525

26-
// Callback support
27-
this.awaitAwsCredentials()
28-
.then(() => {
29-
super.request(params, options, callback)
30-
})
31-
.catch(callback)
26+
// Callback support
27+
awaitAwsCredentials(awsConfig)
28+
.then(() => super.request(params, options, callback))
29+
.catch(callback)
30+
}
3231
}
33-
}
3432

35-
module.exports = AmazonTransport
33+
return AmazonTransport
34+
}

Diff for: src/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = {
2-
AmazonConnection: require('./AmazonConnection'),
3-
AmazonTransport: require('./AmazonTransport')
4-
}
1+
module.exports = (awsConfig) => ({
2+
Connection: require('./AmazonConnection')(awsConfig),
3+
Transport: require('./AmazonTransport')(awsConfig)
4+
})

0 commit comments

Comments
 (0)