Repository: serverless/serverless (JavaScript)
Date: 2019-01-23 07:05:21+00:00
Task type: Bug Fix

Issue:
Setting --profile or --aws-profile keeps stack from getting deployed from version 1.36.0
# This is a Bug Report

## Description

* What went wrong?

Using `serverless deploy --aws-profile xyz` or `serverless deploy --profile xyz` keeps stack from getting deployed in version **1.36.0**. (works in version 1.35.1)
Serverless stops just after the sts-assumerole-call was made:

```
Serverless: Invoke aws:deploy:deploy
Serverless: [AWS sts 200 0.546s 0 retries] assumeRole({ RoleArn: 'arn:aws:iam::012345678912:role/xyz',
  RoleSessionName: 'aws-sdk-js-1547158630501' })
```

see details below.

* What did you expect should have happened?

Should still work the same as in version 1.35.1 (i.e. stack deployment to AWS via Cloudformation)

* What was the config you used?

**serverless.yml**
```yml
service: bugs-serverless-issue-5474

provider:
  name: aws
  runtime: python3.6
  # profile: ${opt:profile}
  region: eu-central-1

package:
  exclude:
    - '*/**'
  ínclude:
    - 'handler.py'

functions:
  function-one:
    handler: handler.lambda_handler
    events:
      - http:
          method: GET
          path: info
```

* What stacktrace or error message from your provider did you see?

![serverless_profile](https://user-images.githubusercontent.com/6681272/51000651-0484c600-152e-11e9-8a9f-858ad3c9587f.png)

<details>

Debug output from `./node_modules/serverless/bin/serverless deploy --aws-profile ep_dev_role`:

```
Serverless: Load command config
Serverless: Load command config:credentials
Serverless: Load command create
Serverless: Load command install
Serverless: Load command package
Serverless: Load command deploy
Serverless: Load command deploy:function
Serverless: Load command deploy:list
Serverless: Load command deploy:list:functions
Serverless: Load command invoke
Serverless: Load command invoke:local
Serverless: Load command info
Serverless: Load command logs
Serverless: Load command login
Serverless: Load command logout
Serverless: Load command metrics
Serverless: Load command print
Serverless: Load command remove
Serverless: Load command rollback
Serverless: Load command rollback:function
Serverless: Load command slstats
Serverless: Load command plugin
Serverless: Load command plugin
Serverless: Load command plugin:install
Serverless: Load command plugin
Serverless: Load command plugin:uninstall
Serverless: Load command plugin
Serverless: Load command plugin:list
Serverless: Load command plugin
Serverless: Load command plugin:search
Serverless: Load command config
Serverless: Load command config:credentials
Serverless: Load command rollback
Serverless: Load command rollback:function
Serverless: Invoke deploy
Serverless: Invoke package
Serverless: Invoke aws:common:validate
Serverless: Invoke aws:common:cleanupTempDir
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Invoke aws:package:finalize
Serverless: Invoke aws:common:moveArtifactsToPackage
Serverless: Invoke aws:common:validate
Serverless: Invoke aws:deploy:deploy
Serverless: [AWS sts 200 0.546s 0 retries] assumeRole({ RoleArn: 'arn:aws:iam::012345678912:role/xyz',
  RoleSessionName: 'aws-sdk-js-1547158630501' })
```

</details>

* Similar or dependent issues:

Probably not really related, but these are some issues I wanted to start working on to ease some CI/CD pains we have been having:

*  #5474, #5039, https://github.com/aws/aws-sdk-js/issues/2071

## Additional Data

* ***Serverless Framework Version you're using***:
1.36.0
* ***Operating System***:
Linux XXX 4.15.0-43-generic #46-Ubuntu SMP Thu Dec 6 14:45:28 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux (Ubuntu 18.04)
* ***Stack Trace***:
no stack trace => exit code 0
* ***Provider Error messages***:



Issue discussion:
Just want to add I'm having the same issue in **1.36.1**
Want to add I am having the same issue with 1.36.1 
I am having the same issue, spending the whole day yesterday thinking there was something wrong in my code 

Solution patch:
diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js
index 0db9baf44dd..a5ff03a0220 100644
--- a/lib/plugins/aws/provider/awsProvider.js
+++ b/lib/plugins/aws/provider/awsProvider.js
@@ -212,7 +212,7 @@ class AwsProvider {
    */
   request(service, method, params, options) {
     const that = this;
-    const credentials = _.cloneDeep(that.getCredentials());
+    const credentials = Object.assign({}, that.getCredentials());
     // Make sure options is an object (honors wrong calls of request)
     const requestOptions = _.isObject(options) ? options : {};
     const shouldCache = _.get(requestOptions, 'useCache', false);


Test patch:
diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js
index 48fc28b2d32..fa297e435a7 100644
--- a/lib/plugins/aws/provider/awsProvider.test.js
+++ b/lib/plugins/aws/provider/awsProvider.test.js
@@ -362,6 +362,7 @@ describe('AwsProvider', () => {
           expect(awsProvider.getCredentials().region).to.eql(options.region);
         });
     });
+
     it('should retry if error code is 429', (done) => {
       const error = {
         statusCode: 429,
@@ -767,6 +768,73 @@ describe('AwsProvider', () => {
             requestSpy.restore();
           });
       });
+
+      describe('STS tokens', () => {
+        let newAwsProvider;
+        let originalProviderProfile;
+        let originalEnvironmentVariables;
+        const relevantEnvironment = {
+          AWS_SHARED_CREDENTIALS_FILE: testUtils.getTmpFilePath('credentials'),
+        };
+
+        beforeEach(() => {
+          originalProviderProfile = serverless.service.provider.profile;
+          originalEnvironmentVariables = testUtils.replaceEnv(relevantEnvironment);
+          serverless.utils.writeFileSync(
+            relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE,
+            '[default]\n' +
+            'aws_access_key_id = 1111\n' +
+            'aws_secret_access_key = 22222\n' +
+            '\n' +
+            '[async]\n' +
+            'role_arn = arn:123\n' +
+            'source_profile = default'
+          );
+          newAwsProvider = new AwsProvider(serverless, options);
+        });
+
+        afterEach(() => {
+          testUtils.replaceEnv(originalEnvironmentVariables);
+          serverless.service.provider.profile = originalProviderProfile;
+        });
+
+        it('should retain reference to STS tokens when updated via SDK', () => {
+          const expectedToken = '123';
+
+          serverless.service.provider.profile = 'async';
+          const startToken = newAwsProvider.getCredentials().credentials.sessionToken;
+          expect(startToken).to.not.equal(expectedToken);
+
+          class FakeCloudFormation {
+            constructor(credentials) {
+              // Not sure where the the SDK resolves the STS, so for the test it's here
+              this.credentials = credentials;
+              this.credentials.credentials.sessionToken = expectedToken;
+            }
+
+            describeStacks() {
+              return {
+                send: (cb) => cb(null, {}),
+              };
+            }
+          }
+
+          newAwsProvider.sdk = {
+            CloudFormation: FakeCloudFormation,
+          };
+
+          return newAwsProvider
+            .request('CloudFormation',
+              'describeStacks',
+              { StackName: 'foo' },
+              { region: 'ap-northeast-1' })
+            .then(() => {
+              // STS token is resolved after SDK call
+              const actualToken = newAwsProvider.getCredentials().credentials.sessionToken;
+              expect(expectedToken).to.eql(actualToken);
+            });
+        });
+      });
     });
   });
 
@@ -776,22 +844,6 @@ describe('AwsProvider', () => {
       'aws-sdk': awsStub,
     });
 
-    function replaceEnv(values) {
-      const originals = {};
-      for (const key of Object.keys(values)) {
-        if (process.env[key]) {
-          originals[key] = process.env[key];
-        } else {
-          originals[key] = 'undefined';
-        }
-        if (values[key] === 'undefined') {
-          delete process.env[key];
-        } else {
-          process.env[key] = values[key];
-        }
-      }
-      return originals;
-    }
 
     // add environment variables here if you want them cleared prior to your test and restored
     // after it has completed.  Any environment variable that might alter credentials loading
@@ -828,7 +880,7 @@ describe('AwsProvider', () => {
     beforeEach(() => {
       originalProviderCredentials = serverless.service.provider.credentials;
       originalProviderProfile = serverless.service.provider.profile;
-      originalEnvironmentVariables = replaceEnv(relevantEnvironment);
+      originalEnvironmentVariables = testUtils.replaceEnv(relevantEnvironment);
       // make temporary credentials file
       serverless.utils.writeFileSync(
         relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE,
@@ -849,7 +901,7 @@ describe('AwsProvider', () => {
     });
 
     afterEach(() => {
-      replaceEnv(originalEnvironmentVariables);
+      testUtils.replaceEnv(originalEnvironmentVariables);
       serverless.service.provider.profile = originalProviderProfile;
       serverless.service.provider.credentials = originalProviderCredentials;
     });
diff --git a/tests/utils/index.js b/tests/utils/index.js
index d1377a012c0..68776075925 100644
--- a/tests/utils/index.js
+++ b/tests/utils/index.js
@@ -219,4 +219,21 @@ module.exports = {
   removeService() {
     execSync(`${serverlessExec} remove`, { stdio: 'inherit' });
   },
+
+  replaceEnv(values) {
+    const originals = {};
+    for (const key of Object.keys(values)) {
+      if (process.env[key]) {
+        originals[key] = process.env[key];
+      } else {
+        originals[key] = 'undefined';
+      }
+      if (values[key] === 'undefined') {
+        delete process.env[key];
+      } else {
+        process.env[key] = values[key];
+      }
+    }
+    return originals;
+  },
 };