Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Configurable AWS credential provider via `credential_provider` config / `RDS_IAM_CREDENTIAL_PROVIDER` env var — supports `default`, `environment`, `ecs` (Pod Identity / ECS task role), `web_identity` (IRSA), `instance_profile`, `sso`, and `ini`

## [1.0.2] - 2026-03-06

### Fixed
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ The package config (`config/rds-iam-auth.php`):
| Key | Default | Description |
|---|---|---|
| `region` | `AWS_DEFAULT_REGION` / `AWS_REGION` env | Fallback region when not set on connection |
| `credential_provider` | `default` | AWS credential provider for token signing. Override with `RDS_IAM_CREDENTIAL_PROVIDER` env. Supported: `default`, `environment`, `ecs`, `web_identity`, `instance_profile`, `sso`, `ini`. |
| `cache_store` | `null` | Laravel cache store for token caching when APCu is unavailable. Use `file`, `redis`, `memcached`, etc. **Never** `database` or `dynamodb`. |
| `cache_ttl` | `600` (10 min) | Cache TTL in seconds (APCu and Laravel cache). Tokens are valid for 15 min. |
| `pgsql_sslmode` | `verify-full` | SSL mode for PostgreSQL IAM connections. Overrides connection-level `sslmode` to prevent accidental downgrade. Override with `RDS_IAM_PGSQL_SSLMODE` env. |
Expand Down Expand Up @@ -130,6 +131,8 @@ GRANT rds_iam TO app_user;

The AWS SDK default credential chain picks up Pod Identity credentials automatically. No code changes needed beyond enabling `use_iam_auth`.

*The option to force a specific credential provider exists via the `credential_provider` config option.*

## Token Caching

IAM auth tokens are valid for 15 minutes. The package caches them to avoid per-request STS calls:
Expand Down
17 changes: 17 additions & 0 deletions config/rds-iam-auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@

'region' => env('AWS_DEFAULT_REGION', env('AWS_REGION', 'us-east-1')),

/*
|--------------------------------------------------------------------------
| AWS Credential Provider
|--------------------------------------------------------------------------
|
| The AWS credential provider used to sign IAM auth tokens. The default
| uses the full SDK credential chain. Override to force a specific
| provider when multiple credential sources exist (e.g. Pod Identity
| over env vars).
|
| Supported: 'default', 'environment', 'ecs', 'web_identity',
| 'instance_profile', 'sso', 'ini'
|
*/

'credential_provider' => env('RDS_IAM_CREDENTIAL_PROVIDER', 'default'),

/*
|--------------------------------------------------------------------------
| Token Cache Store
Expand Down
21 changes: 20 additions & 1 deletion src/RdsAuthTokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,25 @@ private function generateToken(string $host, int $port, string $username, string

protected function createTokenGenerator(): AuthTokenGenerator
{
return new AuthTokenGenerator(CredentialProvider::defaultProvider());
return new AuthTokenGenerator($this->resolveCredentialProvider());
}

protected function resolveCredentialProvider(): callable
{
$name = config('rds-iam-auth.credential_provider', 'default');

return match ($name) {
'default' => CredentialProvider::defaultProvider(),
'environment' => CredentialProvider::env(),
'ecs' => CredentialProvider::ecsCredentials(),
'web_identity' => CredentialProvider::assumeRoleWithWebIdentityCredentialProvider(),
'instance_profile' => CredentialProvider::instanceProfile(),
'sso' => CredentialProvider::sso(),
'ini' => CredentialProvider::ini(),
default => throw new RuntimeException(
"Unsupported RDS IAM credential provider '{$name}'. "
."Supported values: default, environment, ecs, web_identity, instance_profile, sso, ini."
),
};
}
}
45 changes: 45 additions & 0 deletions tests/RdsAuthTokenProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,51 @@ public function test_throws_on_dynamodb_cache_store(): void
$provider->getToken('my-rds.cluster.us-east-1.rds.amazonaws.com', 3306, 'app_user', 'us-east-1');
}

private function resolveCredentialProviderFor(string $name): callable
{
config(['rds-iam-auth.credential_provider' => $name]);

$provider = new class extends RdsAuthTokenProvider
{
public function exposeCredentialProvider(): callable
{
return $this->resolveCredentialProvider();
}
};

return $provider->exposeCredentialProvider();
}

/**
* @dataProvider validCredentialProviderNames
*/
public function test_resolves_all_supported_credential_providers(string $name): void
{
$result = $this->resolveCredentialProviderFor($name);
$this->assertIsCallable($result);
}

public static function validCredentialProviderNames(): array
{
return [
'default' => ['default'],
'environment' => ['environment'],
'ecs' => ['ecs'],
'web_identity' => ['web_identity'],
'instance_profile' => ['instance_profile'],
'sso' => ['sso'],
'ini' => ['ini'],
];
}

public function test_throws_on_unsupported_credential_provider(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage("Unsupported RDS IAM credential provider 'banana'");

$this->resolveCredentialProviderFor('banana');
}

public function test_wraps_token_generation_failure_with_context(): void
{
$generator = Mockery::mock(AuthTokenGenerator::class);
Expand Down
Loading