diff --git a/CHANGELOG.md b/CHANGELOG.md index a5e43fb..8d6a408 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 13ae26e..9953ba5 100644 --- a/README.md +++ b/README.md @@ -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. | @@ -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: diff --git a/config/rds-iam-auth.php b/config/rds-iam-auth.php index 5a95831..acf8abf 100644 --- a/config/rds-iam-auth.php +++ b/config/rds-iam-auth.php @@ -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 diff --git a/src/RdsAuthTokenProvider.php b/src/RdsAuthTokenProvider.php index f29beba..785c13c 100644 --- a/src/RdsAuthTokenProvider.php +++ b/src/RdsAuthTokenProvider.php @@ -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." + ), + }; } } diff --git a/tests/RdsAuthTokenProviderTest.php b/tests/RdsAuthTokenProviderTest.php index cd580d0..800324e 100644 --- a/tests/RdsAuthTokenProviderTest.php +++ b/tests/RdsAuthTokenProviderTest.php @@ -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);