pplu / aws-sdk-perl

A community AWS SDK for Perl Programmers
Other
171 stars 94 forks source link

400 Error - AuthorizationHeaderMalformed #185

Open chandywerks opened 7 years ago

chandywerks commented 7 years ago

I am trying to do the equivalent of this command which runs successfully for me,

aws s3 ls s3://<my-bucket> --profile developer

However running that command, for the first time, prompts me for an MFA code so maybe that is a problem?

Anyway this is the code I am trying,

use Paws;
use Paws::Credential::File;

my $paws = Paws->new( config => {
    credentials => Paws::Credential::File->new(
        profile => 'developer'
    )
});

my $s3 = $paws->service('S3', region => 'us-east-1');

my $res = $s3->ListAllObjects(
    Bucket => '<my-bucket>',
);

foreach my $object ( @{ $res->Contents } ) {
    warn $object->Key;
}

However I get this error,

Paws::Net::RestXMLResponse::handle_response('Paws::S3=HASH(0x5086890)', 'Paws::S3::ListObjects=HASH(0x56b1a88)', 400, '<?xml version="1.0" encoding="UTF-8"?>^J<Error><Code>AuthorizationHeaderMalformed</Code><Message>The authorization header is malformed; a non-empty Access Key (AKID) must be provided in the credential.</Message><RequestId>567A6B6D3F48D209</RequestId><HostId>T36ZxDkXOeOqScBFqIBrR4JCVjANn926miRz9wMmMV+9BDtuAgIEgxWaMAwQXaJMrtuR+1NueRM=</HostId></Error>', 'HASH(0x5a46a50)') called at /usr/local/share/perl/5.18.2/Paws/Net/Caller.pm line 40
pplu commented 7 years ago

Hi,

Thanks for your report. I think that the python SDK is automatically detecting that there is MFA enabled. I think it's calling IAM http://docs.aws.amazon.com/STS/latest/APIReference/API_GetSessionToken.html. In Paws, you should do this before invoking the ListAllObjects. So something like:

my $creds = $iam->GetSessionToken(....)

my $paws2 = Paws->new(config => {
  credentials => Paws::Credentials::Explicit->new(
    access_key => $ak_from_getsessiontoken,
    ...
  )
});
my $s3 = $paws2->service('S3'...);
...

Can you post an example of your .aws config file, so we can evaluate how to make Paws handle this correctly?

Hope this helps!

chandywerks commented 7 years ago

My .aws config file looks like this,

[default]
region = us-east-1

[profile developer]
role_arn = arn:aws:iam::############:role/developer
mfa_serial = arn:aws:iam::############:mfa/chandwerker
source_profile = default

Keep in mind these credentials were set up for me so I don't get to change how it works. I really have no idea how to handle this MFA situation with Paws.

I think the way it is supposed to work is that my mfa_serial arn only has the ability to authenticate via MFA and, when authenticated, only has the ability to assume a role which is "developer".

I'm not sure if this is a normal situation. I think once it's in production there will be creds already set up that I don't have to worry about.

This whole AWS thing is pretty new to me so sorry if I'm off base here.

pplu commented 7 years ago

Let's see if I can get you on track. Please bare two things in mind:

1- I don't have access right now to a real computer (on vacation) so the code may contain typos and problems)

2- Paws doesn't support the AWS config file natively, since it is for configuring the CLI.

The AWS CLI is doing some magic behind the scenes:

First it tries to assume the configured role. You can do that with:

my $first = Paws::Credential::AssumeRole->new(
    RoleArn => 'arn:aws:iam::123456789012:role/AdminRole',
    RoleSessionName => 'RoleTest',
  );

Now that object has credentials for calling the next step

my $sts = Paws->service('STS',
  credentials => Paws::Credential::Explicit->new(
    access_key => $first->access_key,
    # same with secret_key and session_token
  )
);
# see http://search.cpan.org/dist/Paws/lib/Paws/STS/GetSessionToken.pm
my $real_creds = $sts->GetSessionToken(
  SerialNumber => ...,
  TokenCode => ...,
);

my $s3 = Paws->service('S3', region => ..., credentials => Explicit->new(access_key => $real_creds->Credentials->AccessKey, ... same for secret key and session token));

$s3->ListAllObjects

Hope it helps. When I get back I'll try to get a credential provider for this to be done sanely in shape :). Please report your findings / a working example

chandywerks commented 7 years ago

Hmm, so does Paws STS support MFA though? I think that is the problem.

This issue only affects my development environment as, of course, as the production environment doesn't require MFA.

Using your suggestion my workaround is to use the aws cli to generate a session json file, after being prompted for my MFA in an interactive shell, and then having my application read that session json file to provide arguments for Paws::Credential::Explicit->new().

After doing that I was able to test uploading my image to S3.

Thanks for your help!

pplu commented 7 years ago

Hi,

Paws does support MFA (via the GetSessionToken command). From your info, it looks like the CLI is caching the credentials returned by GetSessionToken in a json file (which Paws doesn't do). I'd like to support the same behavior in Paws.

Can you pass me the code that you are using to slurp in the credentials from the json file? Also would be helpful if you send me what's in the json file (private info redacted).

Thanks for your feedback!

bennymic commented 5 years ago

I'm currently using this workaround for cross account assumerole with MFA, but it seems like the response from AssumeRole should have a credentials object I can use with the rest of Paws:

use Getopt::Long;
use Term::ReadKey;
use Paws;
use Paws::Credential::File;
use Paws::Credential::Explicit;

## Get required options fromt he comand line
my $aws_profile;
my $roleArn;
my $mfaArn; 
GetOptions('profile=s' => \$aws_profile,
           'role=s' => \$roleArn,
           'mfa=s' => \$mfaArn
) or die "Usage: $0 --profile AWS_PROFILE\n";

# These credentials are the base account for your user
my $profileCreds = Paws::Credential::File->new(
    profile => $aws_profile
);
my $sts = Paws->service('STS', credentials => $profileCreds,region => 'us-gov-west-1');

print "Enter MFA code:\n";
ReadMode('noecho');
my $tokenCode = ReadLine(0);
chomp $tokenCode;
my $duration = 900; # 15 minutes in seconds

my $roleResponse = $sts->AssumeRole(
  RoleArn => $roleArn,
  RoleSessionName => 'test',
  DurationSeconds => $duration,
  SerialNumber => $mfaArn,
  TokenCode => $tokenCode
);

my $mfa_creds = Paws::Credential::Explicit->new(
      access_key => $roleResponse->Credentials()->{AccessKeyId},
      secret_key => $roleResponse->Credentials()->{SecretAccessKey},
      session_token => $roleResponse->Credentials()->{SessionToken}
);

my $ec2 = Paws->service('EC2',
  region => 'us-west-1',
  credentials => $mfa_creds
);

I should be able to just do:

my $ec2 = Paws->service('EC2',
  region => 'us-west-1',
  credentials => $roleResponse->Credentials()
);