mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
Check for the roleArn attribute on ini file loaded credentials. If that exists, then asynchronous (assume role) credentials are being loaded but they may not yet be received. Accept these as valid credentials and use the containing credentials object as the current credentials object. Clean up the test data (fakeCredentials didn't need so much) Create a test that verifies the async credential loading functionality Create a test that ensures a non-existent profile one attempts to load does not load any credentials Bump the version of the aws-sdk so that the modified aws-sdk will be demanded for proper handling of this feature.
246 lines
8.1 KiB
JavaScript
246 lines
8.1 KiB
JavaScript
'use strict';
|
|
|
|
const AWS = require('aws-sdk');
|
|
const BbPromise = require('bluebird');
|
|
const HttpsProxyAgent = require('https-proxy-agent');
|
|
const url = require('url');
|
|
|
|
const naming = require('../lib/naming.js');
|
|
|
|
const constants = {
|
|
providerName: 'aws',
|
|
};
|
|
|
|
const impl = {
|
|
/**
|
|
* Determine whether the given credentials are valid. It turned out that detecting invalid
|
|
* credentials was more difficult than detecting the positive cases we know about. Hooray for
|
|
* whak-a-mole!
|
|
* @param credentials The credentials to test for validity
|
|
* @return {boolean} Whether the given credentials were valid
|
|
*/
|
|
validCredentials: (credentials) => {
|
|
let result = false;
|
|
if (credentials) {
|
|
if (
|
|
( // valid credentials loaded
|
|
credentials.accessKeyId && credentials.accessKeyId !== 'undefined' &&
|
|
credentials.secretAccessKey && credentials.secretAccessKey !== 'undefined'
|
|
) || (
|
|
// a role to assume has been successfully loaded, the associated STS request has been
|
|
// sent, and the temporary credentials will be asynchronously delivered.
|
|
credentials.roleArn
|
|
)
|
|
) {
|
|
result = true;
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
/**
|
|
* Add credentials, if present, to the given results
|
|
* @param results The results to add the given credentials to if they are valid
|
|
* @param credentials The credentials to validate and add to the results if valid
|
|
*/
|
|
addCredentials: (results, credentials) => {
|
|
if (impl.validCredentials(credentials)) {
|
|
results.credentials = credentials; // eslint-disable-line no-param-reassign
|
|
}
|
|
},
|
|
/**
|
|
* Add credentials, if present, from the environment
|
|
* @param results The results to add environment credentials to
|
|
* @param prefix The environment variable prefix to use in extracting credentials
|
|
*/
|
|
addEnvironmentCredentials: (results, prefix) => {
|
|
if (prefix) {
|
|
const environmentCredentials = new AWS.EnvironmentCredentials(prefix);
|
|
impl.addCredentials(results, environmentCredentials);
|
|
}
|
|
},
|
|
/**
|
|
* Add credentials from a profile, if the profile and credentials for it exists
|
|
* @param results The results to add profile credentials to
|
|
* @param profile The profile to load credentials from
|
|
*/
|
|
addProfileCredentials: (results, profile) => {
|
|
if (profile) {
|
|
const params = { profile };
|
|
if (process.env.AWS_SHARED_CREDENTIALS_FILE) {
|
|
params.filename = process.env.AWS_SHARED_CREDENTIALS_FILE;
|
|
}
|
|
const profileCredentials = new AWS.SharedIniFileCredentials(params);
|
|
impl.addCredentials(results, profileCredentials);
|
|
}
|
|
},
|
|
/**
|
|
* Add credentials, if present, from a profile that is specified within the environment
|
|
* @param results The prefix of the profile's declaration in the environment
|
|
* @param prefix The prefix for the environment variable
|
|
*/
|
|
addEnvironmentProfile: (results, prefix) => {
|
|
if (prefix) {
|
|
const profile = process.env[`${prefix}_PROFILE`];
|
|
impl.addProfileCredentials(results, profile);
|
|
}
|
|
},
|
|
};
|
|
|
|
class AwsProvider {
|
|
static getProviderName() {
|
|
return constants.providerName;
|
|
}
|
|
|
|
constructor(serverless, options) {
|
|
this.naming = { provider: this };
|
|
this.options = options;
|
|
this.provider = this; // only load plugin in an AWS service context
|
|
this.serverless = serverless;
|
|
this.sdk = AWS;
|
|
this.serverless.setProvider(constants.providerName, this);
|
|
|
|
Object.assign(this.naming, naming);
|
|
|
|
// Use HTTPS Proxy (Optional)
|
|
const proxy = process.env.proxy
|
|
|| process.env.HTTP_PROXY
|
|
|| process.env.http_proxy
|
|
|| process.env.HTTPS_PROXY
|
|
|| process.env.https_proxy;
|
|
|
|
if (proxy) {
|
|
const proxyOptions = url.parse(proxy);
|
|
proxyOptions.secureEndpoint = true;
|
|
AWS.config.httpOptions.agent = new HttpsProxyAgent(proxyOptions);
|
|
}
|
|
|
|
// Configure the AWS Client timeout (Optional). The default is 120000 (2 minutes)
|
|
const timeout = process.env.AWS_CLIENT_TIMEOUT || process.env.aws_client_timeout;
|
|
if (timeout) {
|
|
AWS.config.httpOptions.timeout = parseInt(timeout, 10);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deprecated method. Moved to naming.js
|
|
* Kept here for backward compatibility
|
|
*/
|
|
getStackName(stage) {
|
|
const warningMessage = [
|
|
'Deprecation Notice: provider.getStackName() method is deprecated.',
|
|
' Please reference the naming.js file instead: ',
|
|
' ex. naming.getStackName();',
|
|
].join('');
|
|
|
|
this.serverless.cli.log(warningMessage);
|
|
return `${this.serverless.service.service}-${stage}`;
|
|
}
|
|
|
|
request(service, method, params) {
|
|
const that = this;
|
|
const credentials = that.getCredentials();
|
|
const persistentRequest = (f) => new BbPromise((resolve, reject) => {
|
|
const doCall = () => {
|
|
f()
|
|
.then(resolve)
|
|
.catch((e) => {
|
|
if (e.statusCode === 429) {
|
|
that.serverless.cli.log("'Too many requests' received, sleeping 5 seconds");
|
|
setTimeout(doCall, 5000);
|
|
} else {
|
|
reject(e);
|
|
}
|
|
});
|
|
};
|
|
return doCall();
|
|
});
|
|
|
|
return persistentRequest(() => {
|
|
const awsService = new that.sdk[service](credentials);
|
|
const req = awsService[method](params);
|
|
|
|
// TODO: Add listeners, put Debug statments here...
|
|
// req.on('send', function (r) {console.log(r)});
|
|
|
|
return new BbPromise((resolve, reject) => {
|
|
req.send((errParam, data) => {
|
|
const err = errParam;
|
|
if (err) {
|
|
if (err.message === 'Missing credentials in config') {
|
|
const errorMessage = [
|
|
'AWS provider credentials not found.',
|
|
' You can find more info on how to set up provider',
|
|
' credentials in our docs here: https://git.io/vXsdd',
|
|
].join('');
|
|
err.message = errorMessage;
|
|
}
|
|
reject(new this.serverless.classes.Error(err.message, err.statusCode));
|
|
} else {
|
|
resolve(data);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch credentials directly or using a profile from serverless yml configuration or from the
|
|
* well known environment variables
|
|
* @returns {{region: *}}
|
|
*/
|
|
getCredentials() {
|
|
const result = {};
|
|
const stageUpper = this.getStage() ? this.getStage().toUpperCase() : null;
|
|
|
|
// add specified credentials, overriding with more specific declarations
|
|
impl.addCredentials(result, this.serverless.service.provider.credentials); // config creds
|
|
impl.addProfileCredentials(result, this.serverless.service.provider.profile);
|
|
impl.addEnvironmentCredentials(result, 'AWS'); // creds for all stages
|
|
impl.addEnvironmentProfile(result, 'AWS');
|
|
impl.addEnvironmentCredentials(result, `AWS_${stageUpper}`); // stage specific creds
|
|
impl.addEnvironmentProfile(result, `AWS_${stageUpper}`);
|
|
|
|
result.region = this.getRegion();
|
|
return result;
|
|
}
|
|
|
|
getRegion() {
|
|
let returnValue = 'us-east-1';
|
|
if (this.options && this.options.region) {
|
|
returnValue = this.options.region;
|
|
} else if (this.serverless.config.region) {
|
|
returnValue = this.serverless.config.region;
|
|
} else if (this.serverless.service.provider.region) {
|
|
returnValue = this.serverless.service.provider.region;
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
getServerlessDeploymentBucketName() {
|
|
if (this.serverless.service.provider.deploymentBucket) {
|
|
return BbPromise.resolve(this.serverless.service.provider.deploymentBucket);
|
|
}
|
|
return this.request('CloudFormation',
|
|
'describeStackResource',
|
|
{
|
|
StackName: this.naming.getStackName(),
|
|
LogicalResourceId: this.naming.getDeploymentBucketLogicalId(),
|
|
}
|
|
).then((result) => result.StackResourceDetail.PhysicalResourceId);
|
|
}
|
|
|
|
getStage() {
|
|
let returnValue = 'dev';
|
|
if (this.options && this.options.stage) {
|
|
returnValue = this.options.stage;
|
|
} else if (this.serverless.config.stage) {
|
|
returnValue = this.serverless.config.stage;
|
|
} else if (this.serverless.service.provider.stage) {
|
|
returnValue = this.serverless.service.provider.stage;
|
|
}
|
|
return returnValue;
|
|
}
|
|
}
|
|
|
|
module.exports = AwsProvider;
|