signer.js
8.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
var AWS = require('../core');
/**
* @api private
*/
var service = null;
/**
* @api private
*/
var api = {
signatureVersion: 'v4',
signingName: 'rds-db',
operations: {}
};
/**
* @api private
*/
var requiredAuthTokenOptions = {
region: 'string',
hostname: 'string',
port: 'number',
username: 'string'
};
/**
* A signer object can be used to generate an auth token to a database.
*/
AWS.RDS.Signer = AWS.util.inherit({
/**
* Creates a signer object can be used to generate an auth token.
*
* @option options credentials [AWS.Credentials] the AWS credentials
* to sign requests with. Uses the default credential provider chain
* if not specified.
* @option options hostname [String] the hostname of the database to connect to.
* @option options port [Number] the port number the database is listening on.
* @option options region [String] the region the database is located in.
* @option options username [String] the username to login as.
* @example Passing in options to constructor
* var signer = new AWS.RDS.Signer({
* credentials: new AWS.SharedIniFileCredentials({profile: 'default'}),
* region: 'us-east-1',
* hostname: 'db.us-east-1.rds.amazonaws.com',
* port: 8000,
* username: 'name'
* });
*/
constructor: function Signer(options) {
this.options = options || {};
},
/**
* @api private
* Strips the protocol from a url.
*/
convertUrlToAuthToken: function convertUrlToAuthToken(url) {
// we are always using https as the protocol
var protocol = 'https://';
if (url.indexOf(protocol) === 0) {
return url.substring(protocol.length);
}
},
/**
* @overload getAuthToken(options = {}, [callback])
* Generate an auth token to a database.
* @note You must ensure that you have static or previously resolved
* credentials if you call this method synchronously (with no callback),
* otherwise it may not properly sign the request. If you cannot guarantee
* this (you are using an asynchronous credential provider, i.e., EC2
* IAM roles), you should always call this method with an asynchronous
* callback.
*
* @param options [map] The fields to use when generating an auth token.
* Any options specified here will be merged on top of any options passed
* to AWS.RDS.Signer:
*
* * **credentials** (AWS.Credentials) — the AWS credentials
* to sign requests with. Uses the default credential provider chain
* if not specified.
* * **hostname** (String) — the hostname of the database to connect to.
* * **port** (Number) — the port number the database is listening on.
* * **region** (String) — the region the database is located in.
* * **username** (String) — the username to login as.
* @return [String] if called synchronously (with no callback), returns the
* auth token.
* @return [null] nothing is returned if a callback is provided.
* @callback callback function (err, token)
* If a callback is supplied, it is called when an auth token has been generated.
* @param err [Error] the error object returned from the signer.
* @param token [String] the auth token.
*
* @example Generating an auth token synchronously
* var signer = new AWS.RDS.Signer({
* // configure options
* region: 'us-east-1',
* username: 'default',
* hostname: 'db.us-east-1.amazonaws.com',
* port: 8000
* });
* var token = signer.getAuthToken({
* // these options are merged with those defined when creating the signer, overriding in the case of a duplicate option
* // credentials are not specified here or when creating the signer, so default credential provider will be used
* username: 'test' // overriding username
* });
* @example Generating an auth token asynchronously
* var signer = new AWS.RDS.Signer({
* // configure options
* region: 'us-east-1',
* username: 'default',
* hostname: 'db.us-east-1.amazonaws.com',
* port: 8000
* });
* signer.getAuthToken({
* // these options are merged with those defined when creating the signer, overriding in the case of a duplicate option
* // credentials are not specified here or when creating the signer, so default credential provider will be used
* username: 'test' // overriding username
* }, function(err, token) {
* if (err) {
* // handle error
* } else {
* // use token
* }
* });
*
*/
getAuthToken: function getAuthToken(options, callback) {
if (typeof options === 'function' && callback === undefined) {
callback = options;
options = {};
}
var self = this;
var hasCallback = typeof callback === 'function';
// merge options with existing options
options = AWS.util.merge(this.options, options);
// validate options
var optionsValidation = this.validateAuthTokenOptions(options);
if (optionsValidation !== true) {
if (hasCallback) {
return callback(optionsValidation, null);
}
throw optionsValidation;
}
// 15 minutes
var expires = 900;
// create service to generate a request from
var serviceOptions = {
region: options.region,
endpoint: new AWS.Endpoint(options.hostname + ':' + options.port),
paramValidation: false,
signatureVersion: 'v4'
};
if (options.credentials) {
serviceOptions.credentials = options.credentials;
}
service = new AWS.Service(serviceOptions);
// ensure the SDK is using sigv4 signing (config is not enough)
service.api = api;
var request = service.makeRequest();
// add listeners to request to properly build auth token
this.modifyRequestForAuthToken(request, options);
if (hasCallback) {
request.presign(expires, function(err, url) {
if (url) {
url = self.convertUrlToAuthToken(url);
}
callback(err, url);
});
} else {
var url = request.presign(expires);
return this.convertUrlToAuthToken(url);
}
},
/**
* @api private
* Modifies a request to allow the presigner to generate an auth token.
*/
modifyRequestForAuthToken: function modifyRequestForAuthToken(request, options) {
request.on('build', request.buildAsGet);
var httpRequest = request.httpRequest;
httpRequest.body = AWS.util.queryParamsToString({
Action: 'connect',
DBUser: options.username
});
},
/**
* @api private
* Validates that the options passed in contain all the keys with values of the correct type that
* are needed to generate an auth token.
*/
validateAuthTokenOptions: function validateAuthTokenOptions(options) {
// iterate over all keys in options
var message = '';
options = options || {};
for (var key in requiredAuthTokenOptions) {
if (!Object.prototype.hasOwnProperty.call(requiredAuthTokenOptions, key)) {
continue;
}
if (typeof options[key] !== requiredAuthTokenOptions[key]) {
message += 'option \'' + key + '\' should have been type \'' + requiredAuthTokenOptions[key] + '\', was \'' + typeof options[key] + '\'.\n';
}
}
if (message.length) {
return AWS.util.error(new Error(), {
code: 'InvalidParameter',
message: message
});
}
return true;
}
});