aws / aws-sdk-js-v3

Modularized AWS SDK for JavaScript.
Apache License 2.0
3.12k stars 578 forks source link

Credentials object returned from sts.assumeRole cannot be used as credentials for clients #3940

Open jonpbbc opened 2 years ago

jonpbbc commented 2 years ago

Checkboxes for prior research

Describe the bug

If you use sts.assumeRole to get session credentials for an assumed role and then pass those credentials to a client the case mis-match means you recieve the TypeError: Cannot read property 'byteLength' of undef described in #2282

This doesn't feel like a documentation error as in #3268, as I am trying to use SDK objects to populate SDK objects.

SDK version number

"@aws-sdk/client-iam": "^3.154.0", "@aws-sdk/client-sts": "^3.168.0"

Which JavaScript Runtime is this issue in?

Node.js

Details of the browser/Node.js/ReactNative version

Node 14 via aws/codebuild/standard:5.0

Reproduction Steps

      const sts = new STS()
      const assumeRoleResponse = await sts.assumeRole({ "RoleArn": `arn:aws:iam::${accountId}:role/${roleName}`, "RoleSessionName": sessionName" })
      const iam = new IAM({ credentials: assumeRoleResponse.Credentials})
      const response = await iam.listPolicies({ "Scope": "AWS, "MaxItems": 1 })

Observed Behavior

TypeError: Cannot read property 'byteLength' of undefined as per #2282

Expected Behavior

I would expect to be able to use the returned Credentials object as the credentials for constructing a subsequent client object.

Possible Solution

No response

Additional Information/Context

Work-around to avoid error:

      const sts = new STS()
      const assumeRoleResponse = await sts.assumeRole({ "RoleArn": `arn:aws:iam::${accountId}:role/${roleName}`, "RoleSessionName": sessionName" })
      const iam = new IAM({ credentials: {
        accessKeyId: assumeRoleResponse.Credentials.AccessKeyId,
        secretAccessKey: assumeRoleResponse.Credentials.SecretAccessKey,
        sessionToken: assumeRoleResponse.Credentials.SessionToken
       }})
      const response = await iam.listPolicies({ "Scope": "AWS, "MaxItems": 1 })
RanVaknin commented 2 years ago

Hi @jonpbbc ,

Thanks for opening the issue and engaging in the community.

I think the reason your first code snippet isn't working as expected is because Client credentials and the Credential object returned from STS are of different type.

Type 'Credentials | undefined' is not assignable to type 'Credentials | Provider | undefined'. Type 'Credentials' is not assignable to type 'Credentials | Provider | undefined'.ts(2322)

I'd say the "work around" is the correct way to make that request. Did you reference an official example or docs for the first code snippet you have provided?

All the best, Ran~

jonpbbc commented 2 years ago

Hi @RanVaknin ,

I was working from the documentation, specifically the IAM client config as part of calling the IAM constructor. That references this Credentials interface and when I saw the assume role output contains a object of type Credentials I have absolutely believed that Credentials and Credentials are the same type.

If Credentials is not supposed to be the same type as Credentials then I would say that the duplicate naming is the root of this issue - but given the purpose of both Credentials types is to return or supply the same logical data, with the same names, I completely it expect it to be compatible within the same overall API. If they are not supposed to be compatible, I propose the renaming of the types so Credentials is not confused with Credentials - but as a user of the API I would much rather they be made compatible.

My route to the work around was searching for the TypeError in relation to the SDK and finding #2282 - not really an intuitive path.

Hope that helps,

Jon

RanVaknin commented 2 years ago

Hi Jon,

Thanks for your response! I totally see what you are saying and it makes a lot of sense. I'll speak with the development team regarding this issue and will raise a backlog item.

In the spirit of transparency I'll share that the team is slammed, and since this has a workaround it will get lower priority, but will get addressed later on.

Thanks again for being an active member in the community, we hope to hear back from you! All the best, Ran~

github-actions[bot] commented 6 months ago

Greetings! We’re closing this issue because it has been open a long time and hasn’t been updated in a while and may not be getting the attention it deserves. We encourage you to check if this is still an issue in the latest release and if you find that this is still a problem, please feel free to comment or open a new issue.

jonpbbc commented 6 months ago

The behaviour has changed slightly, but use of a returned credentials object as an input credentials object to the next call still fails:

Script:

const { IAM } = require("@aws-sdk/client-iam");
const { STS } = require("@aws-sdk/client-sts");

async function demoWorkAround(accountId, roleName, sessionName) { 
      const sts = new STS();
      const assumeRoleResponse = await sts.assumeRole({ "RoleArn": `arn:aws:iam::${accountId}:role/${roleName}`, "RoleSessionName": sessionName });
      const iam = new IAM({ credentials: {
        accessKeyId: assumeRoleResponse.Credentials.AccessKeyId,
        secretAccessKey: assumeRoleResponse.Credentials.SecretAccessKey,
        sessionToken: assumeRoleResponse.Credentials.SessionToken
       }})
      console.log("Error NOT caused here:");
      const response = await iam.listPolicies({ "Scope": "AWS", "MaxItems": 1 });
      console.log("Success");
}

async function demoIssue(accountId, roleName, sessionName) { 
      const sts = new STS();
      const assumeRoleResponse = await sts.assumeRole({ "RoleArn": `arn:aws:iam::${accountId}:role/${roleName}`, "RoleSessionName": sessionName });
      const iam = new IAM({ credentials: assumeRoleResponse.Credentials});
      console.log("Error caused here:");
      const response = await iam.listPolicies({ "Scope": "AWS", "MaxItems": 1 });
      console.log("This line is never reached");
}

const acc = '123456789012';
const role = 'my-role';
const sess = 'my-session';

demoWorkAround(acc, role, sess).then(() => {
    demoIssue(acc, role, sess).then(() => console.log("done"));
});

Script output:

Error NOT caused here:
Success
Error caused here:
/path/to/node_modules/@smithy/signature-v4/dist-cjs/index.js:562
      throw new Error("Resolved credential object is not valid");
            ^

Error: Resolved credential object is not valid
    at _SignatureV4.validateResolvedCredentials (/path/to/node_modules/@smithy/signature-v4/dist-cjs/index.js:562:13)
    at _SignatureV4.signRequest (/path/to/node_modules/@smithy/signature-v4/dist-cjs/index.js:487:10)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  '$metadata': { attempts: 1, totalRetryDelay: 0 }
}
gvgage commented 1 month ago

You could try using the @aws-sdk/credentials-provider library. It has a method called fromTemporaryCredentials() that returns AwsCredentialIdentityProvider. I was trying to use the Kinesis client and pass in credentials returned by the STS client (AWS SDK v3) but the types didn't align because the STS client returned a Credentials object and the Kinesis client was expecting AwsCredentialIdentityProvider. Bonus is that fromTemporaryCredentials() handles credential expiration for you!