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