aws-beam / aws-elixir

AWS clients for Elixir
Apache License 2.0
555 stars 132 forks source link

Need help with assuming roles with tags. #143

Closed smashedtoatoms closed 2 years ago

smashedtoatoms commented 2 years ago

When I try to call AWS.STS.assume_role and tag the role, I can't figure out the data structure it wants for the tags. The Go docs say it should be a list of structs with Key and Value as the attributes of the struct. I have no idea what that translates to in elixir.

1) I started with a map

   out =
         AWS.STS.assume_role(client, %{
           "RoleArn" => "arn:aws:iam::000000000000:role/external/MyApp-MyAppRole-D390C2QK3BNF",
           "RoleSessionName" => "TheSessionName",
           "Tags" => %{"Key" => "AccountId", "Value" => "fakeAccountId"}
         })

resulted in

        ** (Protocol.UndefinedError) protocol String.Chars not implemented for %{"Key" => "AccountId", "Value" => "fakeAccountId"} of type Map. This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Decimal, Float, Floki.Selector, Floki.Selector.AttributeSelector, Floki.Selector.Combinator, Floki.Selector.Functional, Floki.Selector.PseudoClass, Integer, List, NaiveDateTime, Phoenix.LiveComponent.CID, Postgrex.Copy, Postgrex.Query, Time, URI, Version, Version.Requirement

2) So I put a map in a list to more closely match the Go SDK

   out =
         AWS.STS.assume_role(client, %{
           "RoleArn" => "arn:aws:iam::000000000000:role/external/MyApp-MyAppRole-D390C2QK3BNF",
           "RoleSessionName" => "TheSessionName",
           "Tags" => [%{Key: "AccountId", Value: "fakeAccountId"}]
         })

resulted in

        ** (ArgumentError) cannot convert the given list to a string.

        To be converted to a string, a list must either be empty or only
        contain the following elements:

          * strings
          * integers representing Unicode code points
          * a list containing one of these three elements

        Please check the given list or call inspect/1 to get the list representation, got:

        [%{Key: "AccountId", Value: "fakeAccountId"}]

3) So I did a list of keyword lists

   out =
         AWS.STS.assume_role(client, %{
           "RoleArn" => "arn:aws:iam::000000000000:role/external/MyApp-MyAppRole-D390C2QK3BNF",
           "RoleSessionName" => "TheSessionName",
           "Tags" => [[Key: "AccountId", Value: "fakeAccountId"]]
         })

resulted in

        ** (ArgumentError) cannot convert the given list to a string.

        To be converted to a string, a list must either be empty or only
        contain the following elements:

          * strings
          * integers representing Unicode code points
          * a list containing one of these three elements

        Please check the given list or call inspect/1 to get the list representation, got:

        [[key: "AccountId", value: "fakeAccountId"]]

4) Running out of ideas

   out =
         AWS.STS.assume_role(client, %{
           "RoleArn" => "arn:aws:iam::000000000000:role/external/MyApp-MyAppRole-D390C2QK3BNF",
           "RoleSessionName" => "TheSessionName",
           "Tags" => [AccountId: "fakeAccountId"]
         })

resulted in

        ** (ArgumentError) cannot convert the given list to a string.

        To be converted to a string, a list must either be empty or only
        contain the following elements:

          * strings
          * integers representing Unicode code points
          * a list containing one of these three elements

        Please check the given list or call inspect/1 to get the list representation, got:

        [AccountId: "fakeAccountId"]

Tuples, you name it, nothing works. I even tried the string Key=AccountId,Value=fakeAccountId from the cli tool. Any help you can provide would be appreciated.

philss commented 2 years ago

@smashedtoatoms This is presumably fixed on master (by https://github.com/aws-beam/aws-elixir/pull/142). Can you try to use that version?

I'm going to release a new version if that works for you.

smashedtoatoms commented 2 years ago

I just tried it and got a 400 from STS. This is likely self-inflicted though. What should the data format for the tags be? Should it be a list of Key-Value maps since the underlying Go would be a slice of Key-Value structs?

{:error,
 {:unexpected_response,
  %{
    body: "<ErrorResponse xmlns=\"https://sts.amazonaws.com/doc/2011-06-15/\">\n  <Error>\n    <Type>Sender</Type>\n    <Code>MalformedInput</Code>\n    <Message>Unexpected complex element termination</Message>\n  </Error>\n  <RequestId>9e7d41f8-0b43-4b43-9769-c2171d0c44d6</RequestId>\n</ErrorResponse>\n",
    headers: [
      {"x-amzn-RequestId", "9e7d41f8-0b43-4b43-9769-c2171d0c44d6"},
      {"Content-Type", "text/xml"},
      {"Content-Length", "284"},
      {"Date", "Mon, 18 Jul 2022 13:29:03 GMT"},
      {"Connection", "close"}
    ],
    status_code: 400
  }}}
smashedtoatoms commented 2 years ago

Here is the error if I use a list of structs:

{:error,
 {:unexpected_response,
  %{
    body: "<ErrorResponse xmlns=\"https://sts.amazonaws.com/doc/2011-06-15/\">\n  <Error>\n    <Type>Sender</Type>\n    <Code>MalformedInput</Code>\n    <Message>Top level element may not be treated as a list</Message>\n  </Error>\n  <RequestId>81d89cf2-f142-4a14-ac82-742a31bca1fb</RequestId>\n</ErrorResponse>\n",
    headers: [
      {"x-amzn-RequestId", "81d89cf2-f142-4a14-ac82-742a31bca1fb"},
      {"Content-Type", "text/xml"},
      {"Content-Length", "292"},
      {"Date", "Mon, 18 Jul 2022 13:31:54 GMT"},
      {"Connection", "close"}
    ],
    status_code: 400
  }}}
smashedtoatoms commented 2 years ago

I tried list of keyword lists as well:

** (Protocol.UndefinedError) protocol String.Chars not implemented for {:Key, "AccountId"} of type Tuple. This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Decimal, Float, Floki.Selector, Floki.Selector.AttributeSelector, Floki.Selector.Combinator, Floki.Selector.Functional, Floki.Selector.PseudoClass, Integer, List, NaiveDateTime, Phoenix.LiveComponent.CID, Postgrex.Copy, Postgrex.Query, Time, URI, Version, Version.Requirement
onno-vos-dev commented 2 years ago

Disclaimer: I do not use aws-elixir and there may be some slight differences between aws-elixir and aws-erlang (which I do use) but could you try passing tags as follows:

<<"Tags.member.1.Key">> => <<"Team">>,
<<"Tags.member.1.Value">> => <<"smashed">>,
<<"Tags.member.2.Key">> => <<"Application">>,
<<"Tags.member.2.Value">> => <<"ToAtoms">>,
<<"Tags.member.3.Key">> => <<"Library">>,
<<"Tags.member.3.Value">> => <<"Aws-Elixir">>},

So forgive me for butchering potentially as I don't know if this even compiles but something along the lines of:

%{ "RoleArn" => "arn:aws:iam::000000000000:role/external/MyApp-MyAppRole-D390C2QK3BNF",
   "RoleSessionName" => "TheSessionName",
   "Tags.member.1.Key" => "Team",
   "Tags.member.1.Value" => "Smashed",
   "Tags.member.2.Key" => "Application",
   "Tags.member.2.Value" => "ToAtoms"
 }

@smashedtoatoms ^

philss commented 2 years ago

@smashedtoatoms sorry, I'm not sure how this supposed to be. Maybe something like you mentioned that is accepted in the command line? Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_adding-assume-role

I would try:

AWS.STS.assume_role(client, %{
  "RoleArn" => "arn:aws:iam::000000000000:role/external/MyApp-MyAppRole-D390C2QK3BNF",
  "RoleSessionName" => "TheSessionName",
  "Tags" => ["Key=AccountId,Value=fakeAccountId"]
})
smashedtoatoms commented 2 years ago

Hahahaha, you're totally on the right track. The STS API is absolutely bonkers. This works.

out =
      AWS.STS.assume_role(client, %{
        "RoleArn" => "arn:aws:iam::000000000000:role/external/MyApp-MyRole-D390C2QK3BNF",
        "RoleSessionName" => "nightmare",
        "Tags.member.1.Key" => "AccountId",
        "Tags.member.1.Value" => "fakeAccountId"
      })
onno-vos-dev commented 2 years ago

It's the whole AWS API when it comes to tagging which is utter crazyness if you ask me... But glad to have helped! I'll document it in the README tonight as this is not the first time it came up and I know I struggled with it as well when getting these damn tags to work :-)

onno-vos-dev commented 2 years ago

Are you ok with closing this @smashedtoatoms ?

smashedtoatoms commented 2 years ago

Man, hidden magic: https://github.com/aws/aws-sdk-go-v2/blob/6fb212bd975211ea75825ba1e83e2df756538818/aws/protocol/query/map.go#L60

smashedtoatoms commented 2 years ago

Yep, let's close it. When I get assuming a tagged role done, I will do a PR for some example code.