Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/ai.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@
'secret_access_key' => env('AWS_SECRET_ACCESS_KEY'),
'session_token' => env('AWS_SESSION_TOKEN'),
'use_default_credential_provider' => env('AWS_USE_DEFAULT_CREDENTIALS', true),
'assume_role_arn' => env('AWS_BEDROCK_ASSUME_ROLE_ARN'),
'assume_role_session_name' => env('AWS_BEDROCK_ASSUME_ROLE_SESSION_NAME'),
'assume_role_duration_seconds' => env('AWS_BEDROCK_ASSUME_ROLE_DURATION_SECONDS', 3600),
'assume_role_external_id' => env('AWS_BEDROCK_ASSUME_ROLE_EXTERNAL_ID'),
],

'cohere' => [
Expand Down
38 changes: 38 additions & 0 deletions src/Gateway/Bedrock/Concerns/CreatesBedrockClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
namespace Laravel\Ai\Gateway\Bedrock\Concerns;

use Aws\BedrockRuntime\BedrockRuntimeClient;
use Aws\Credentials\AssumeRoleCredentialProvider;
use Aws\Credentials\CredentialProvider;
use Aws\Sts\StsClient;
use Laravel\Ai\Providers\Provider;

trait CreatesBedrockClient
Expand Down Expand Up @@ -56,10 +59,45 @@ protected function resolveAuthConfig(array $credentials, array $config): array
return ['credentials' => $awsCredentials];
}

if (! empty($config['assume_role_arn'])) {
return ['credentials' => $this->assumeRoleCredentialProvider($config)];
}

if (! ($config['use_default_credential_provider'] ?? true)) {
return ['credentials' => false];
}

return [];
}

/**
* Create a memoized assume-role credential provider.
*
* @param array<string, mixed> $config
*/
protected function assumeRoleCredentialProvider(array $config): callable
{
$region = $config['region'] ?? 'us-east-1';

$stsClient = new StsClient([
'region' => $region,
'version' => 'latest',
]);

$assumeRoleParams = [
'RoleArn' => $config['assume_role_arn'],
'RoleSessionName' => $config['assume_role_session_name']
?? 'laravel-ai-bedrock-'.bin2hex(random_bytes(4)),
'DurationSeconds' => (int) ($config['assume_role_duration_seconds'] ?? 3600),
];

if (! empty($config['assume_role_external_id'])) {
$assumeRoleParams['ExternalId'] = $config['assume_role_external_id'];
}

return CredentialProvider::memoize(new AssumeRoleCredentialProvider([
'client' => $stsClient,
'assume_role_params' => $assumeRoleParams,
]));
}
}
4 changes: 4 additions & 0 deletions src/Providers/BedrockProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public function additionalConfiguration(): array
return [
'region' => $this->config['region'] ?? 'us-east-1',
'use_default_credential_provider' => $this->config['use_default_credential_provider'] ?? true,
'assume_role_arn' => $this->config['assume_role_arn'] ?? null,
'assume_role_session_name' => $this->config['assume_role_session_name'] ?? null,
'assume_role_duration_seconds' => $this->config['assume_role_duration_seconds'] ?? null,
'assume_role_external_id' => $this->config['assume_role_external_id'] ?? null,
];
}

Expand Down
72 changes: 72 additions & 0 deletions tests/Unit/Gateway/Bedrock/CreatesBedrockClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,75 @@ public function resolve(array $credentials, array $config): array

expect($config)->toEqual([]);
});

test('assume role arn produces a callable credential provider', function () {
$config = bedrockClientTrait()->resolve(
[],
[
'assume_role_arn' => 'arn:aws:iam::123456789012:role/test-role',
'region' => 'us-west-2',
],
);

expect($config)->toHaveKey('credentials')
->and($config['credentials'])->toBeCallable();
});

test('assume role is not used when arn is empty', function () {
$config = bedrockClientTrait()->resolve(
[],
['assume_role_arn' => null],
);

expect($config)->toEqual([]);
});

test('assume role is not used when arn is empty string', function () {
$config = bedrockClientTrait()->resolve(
[],
['assume_role_arn' => ''],
);

expect($config)->toEqual([]);
});

test('static iam credentials take priority over assume role', function () {
$config = bedrockClientTrait()->resolve(
[
'access_key_id' => 'AKIA123',
'secret_access_key' => 'secret',
],
['assume_role_arn' => 'arn:aws:iam::123456789012:role/test-role'],
);

expect($config)->toEqual([
'credentials' => [
'key' => 'AKIA123',
'secret' => 'secret',
],
]);
});

test('bearer token takes priority over assume role', function () {
$config = bedrockClientTrait()->resolve(
['key' => 'bedrock-token'],
['assume_role_arn' => 'arn:aws:iam::123456789012:role/test-role'],
);

expect($config)->toHaveKey('token')
->and($config)->not->toHaveKey('credentials');
});

test('assume role takes priority over disabled default provider', function () {
$config = bedrockClientTrait()->resolve(
[],
[
'use_default_credential_provider' => false,
'assume_role_arn' => 'arn:aws:iam::123456789012:role/test-role',
'region' => 'us-east-1',
],
);

expect($config)->toHaveKey('credentials')
->and($config['credentials'])->toBeCallable();
});
28 changes: 28 additions & 0 deletions tests/Unit/Providers/BedrockProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,31 @@

expect($gateway1)->toBe($gateway2);
});

test('returns assume role configuration when provided', function () {
$config = [
'region' => 'us-west-2',
'assume_role_arn' => 'arn:aws:iam::123456789012:role/test-role',
'assume_role_session_name' => 'my-session',
'assume_role_duration_seconds' => 900,
'assume_role_external_id' => 'ext-123',
];

$provider = new BedrockProvider($config, $this->dispatcher);
$additionalConfig = $provider->additionalConfiguration();

expect($additionalConfig['assume_role_arn'])->toBe('arn:aws:iam::123456789012:role/test-role')
->and($additionalConfig['assume_role_session_name'])->toBe('my-session')
->and($additionalConfig['assume_role_duration_seconds'])->toBe(900)
->and($additionalConfig['assume_role_external_id'])->toBe('ext-123');
});

test('assume role configuration defaults to null when not set', function () {
$provider = new BedrockProvider([], $this->dispatcher);
$additionalConfig = $provider->additionalConfiguration();

expect($additionalConfig['assume_role_arn'])->toBeNull()
->and($additionalConfig['assume_role_session_name'])->toBeNull()
->and($additionalConfig['assume_role_duration_seconds'])->toBeNull()
->and($additionalConfig['assume_role_external_id'])->toBeNull();
});