AWS AssumeRole with Laravel Filesystem

418 views Asked by At

I'm currently trying to figure something out. I have 2 AWS Accounts. Account A has a bucket called my_awesome_files. Account B has users, that would like to be able to see those documents in my_awesome_files.

I have the following policies and roles setup:

Account A (the one who has the bucket): role: allow-account-b-access-s3-bucket-role policy:

{
    "Statement": [
        {
            "Action": "s3:GetObject",
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::my_awesome_files"
            ]
        }
    ],
    "Version": "2012-10-17"
}

policy: (trusted entities)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<account_id_account_b>:root"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

Account B (the one who wants to have access to the bucket): Has a user with this attached Policy:

{
    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Effect": "Allow",
            "Resource": "arn:aws:iam::<account_id_account_a>:role/allow-account-b-access-s3-bucket-role"
        }
    ],
    "Version": "2012-10-17"
}

If I read the documentation correctly, this should work.

Now to my questions:

  1. Is this indeed correct, or am I missing something?
  2. If it is indeed correct iam-configuration: Am I missing something with my laravel s3 config?

Do I need some special fancy configuration for it to get that it needs to assume that role or to make it work? Currently, the filesystem config looks like this:

'my_awesome_files_bucket_config' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID', 'xxxx'),
    'secret' => env('AWS_SECRET_ACCESS_KEY', 'xxxx'),
    'region' => env('DEFAULT_REGION', 'eu-central-1'),
    'bucket' => env('MY_AWESOME_FILES_BUCKET', 'xxxx')
]
3

There are 3 answers

0
linx On BEST ANSWER

So I kind of figured it out. The policies are correct, but laravel filesystem automatically does not assume roles (obviously) and I didn't find a way to configure it to do so. (This would actually be a nice feature to the Filesystem lib)

So my solution was: using directly the AWS SDK to get the correct credentials, and then fetch the files from s3. This looks like this:

$stsClient = new StsClient([
    'region' => 'eu-central-1',
    'version' => '2011-06-15',
    'credentials' => [
        'key'    => $access_key_id,
        'secret' => $secret_access_key ,
    ],
]);

$sessionName = "s3-access";

$result = $stsClient->AssumeRole([
    'RoleArn' => $ARN,
    'RoleSessionName' => $sessionName,
]);
$access_key_id = $result['Credentials']['AccessKeyId'];
$secret_access_key = $result['Credentials']['SecretAccessKey'];
$session_token = $result['Credentials']['SessionToken'];

Then the fetching of the file is as easy as reading documentation:

$s3Client = new S3Client([
    'version'     => '2006-03-01',
    'region'      => 'eu-central-1',
    'credentials' =>  [
        'key'    => $access_key_id,
        'secret' => $secret_access_key,
        'token'  => $session_token
    ]
]);

$private_file = $s3Client->getObject([
    'Bucket' => env('BUCKET_NAME'),
    'Key' => 'filename.txt',
]);
2
Michael Houlihan On

No nothing about laravel, but at the AWS level the user in Account B has the permission to assume the Role, so it needs to go ahead and call something like sts.AssumeRole()

0
wsamoht On

I just figured out how to do this with Laravel's Filesystem/Storage facade. My scenario is giving our app running on Laravel Vapor permission to an S3 bucket on one of our other accounts. After setting up all the roles and permissions in IAM I did the following to configure a new Filesystem driver - S3 using an assumed role.

Create a helper to get the assumed role credentials

Using CredentialProvider::memoize cache's the credentials so if you need them elsewhere in your app (we are using them for CloudFront too), your app doesn't have to get new credentials every time (within the same process that is).

<?php

namespace App\Domain\Services\Aws;

use Aws\Credentials\AssumeRoleCredentialProvider;
use Aws\Credentials\CredentialProvider;
use Aws\Sts\StsClient;

class AssumedRoleCredentials
{
    public static function create(): ?callable
    {
        $assumedRoleArn = config('services.aws.primary_account_assumed_role_arn');

        if ($assumedRoleArn === null) {
            return null;
        }

        $assumeRoleCredentials = new AssumeRoleCredentialProvider([
            'client' => new StsClient([
                'region' => 'us-east-1',
                'version' => '2011-06-15',
            ]),
            'assume_role_params' => [
                'RoleArn' => $assumedRoleArn,
                'RoleSessionName' => 'vapor-account',
            ],
        ]);

        return CredentialProvider::memoize($assumeRoleCredentials);
    }
}

Reference

Create a S3 storage provider using an assumed role

I followed Laravel's logic to create a S3 driver but simplified the configuration for my specific use case so I didn't have to replicate all of Laravel's logic.

Remember to register this Service Provider in your app.php config.

<?php

namespace App\Providers;

use App\Domain\Services\Aws\AssumedRoleCredentials;
use Aws\S3\S3Client;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Filesystem\AwsS3V3Adapter as LaravelAwsS3V3Adapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
use League\Flysystem\Filesystem;

class AwsS3AssumedRoleStorageProvider extends ServiceProvider
{
    public function boot(): void
    {
        Storage::extend('s3_assumed_role', function (Application $app, array $config) {
            $client = new S3Client([
                'version' => 'latest',
                'region' => $config['region'],
                'bucket' => $config['bucket'],
                'credentials' => AssumedRoleCredentials::create(),
            ]);

            $adapter = new AwsS3V3Adapter(
                $client,
                $config['bucket']
            );

            return  new LaravelAwsS3V3Adapter(
                new Filesystem($adapter),
                $adapter,
                $config,
                $client
            );
        });
    }
}

References

Create a new filesystem disk

In your filesystems.php config, add a new disk.

's3_app_cache' => [
    'driver' => 's3_assumed_role',
    'region' => 'us-east-2',
    'bucket' => env('AWS_APP_CACHE_BUCKET'),
    'throw' => true,
],

Using it

Storage::disk('s3_app_cache')->put('laravel_is_awesome.txt');