Skip to content

Commit

Permalink
[SWP-5671] Support null AWS credentials for assumed IAM roles (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
clemblanco authored Oct 5, 2022
1 parent ba6ad8a commit 2677288
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 64 deletions.
18 changes: 5 additions & 13 deletions phpunit.xml → phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="vendor/autoload.php"
Expand All @@ -8,20 +10,10 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
stopOnFailure="false">
<testsuites>
<testsuite name="Application Test Suite">
<testsuite name="Package Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
<exclude>
<directory>vendor</directory>
<directory>tests</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
2 changes: 2 additions & 0 deletions src/Contracts/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ interface Client
{
public function configure(array $channelConfig): Client;

public function getAwsConfig(): array;

public function putRecord(array $args = []): \Aws\Result;

public function putRecords(array $args = []): \Aws\Result;
Expand Down
10 changes: 10 additions & 0 deletions src/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,14 @@ public function handleBatch(array $records): void
// Fire and forget
}
}

/**
* Return the configured Client used to write records.
*
* @return Kinesis
*/
public function getClient(): Kinesis
{
return $this->client;
}
}
46 changes: 30 additions & 16 deletions src/Kinesis.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Kinesis implements Client
/** @var Repository */
protected $config;

/** @var array */
protected $awsConfig = [];

public function __construct(Repository $config)
{
$this->config = $config;
Expand All @@ -27,19 +30,6 @@ public function __construct(Repository $config)
* @return $this
*/
public function configure(array $channelConfig): Client
{
$this->kinesis = $this->configureKinesisClient($channelConfig);

return $this;
}

/**
* Configure the Kinesis client for logging records.
*
* @param array $channelConfig
* @return KinesisClient
*/
private function configureKinesisClient(array $channelConfig): KinesisClient
{
$defaultConfig = $this->config->get('services.kinesis');

Expand All @@ -49,13 +39,15 @@ private function configureKinesisClient(array $channelConfig): KinesisClient
'http' => Arr::get($channelConfig, 'http', Arr::get($defaultConfig, 'http', [])),
];

if (Arr::has($channelConfig, ['key', 'secret'])) {
if ($this->configHasCredentials($channelConfig)) {
$config['credentials'] = Arr::only($channelConfig, ['key', 'secret', 'token']);
} elseif (Arr::has($defaultConfig, ['key', 'secret'])) {
} elseif ($this->configHasCredentials($defaultConfig)) {
$config['credentials'] = Arr::only($defaultConfig, ['key', 'secret', 'token']);
}

return new KinesisClient($config);
$this->kinesis = new KinesisClient($this->awsConfig = $config);

return $this;
}

/**
Expand All @@ -79,4 +71,26 @@ public function putRecords(array $args = []): \Aws\Result
{
return $this->kinesis->putRecords($args);
}

/**
* Return the actual array used to configure the AWS Client.
*
* @return array
*/
public function getAwsConfig(): array
{
return $this->awsConfig;
}

/**
* Make sure some AWS credentials were provided to the configuration array.
*
* @return bool
*/
private function configHasCredentials(array $config): bool
{
return Arr::has($config, ['key', 'secret'])
&& is_string(Arr::get($config, 'key'))
&& is_string(Arr::get($config, 'secret'));
}
}
23 changes: 21 additions & 2 deletions tests/InteractsWithKinesis.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,39 @@
namespace PodPoint\MonologKinesis\Tests;

use Closure;
use Illuminate\Foundation\Application;
use PodPoint\MonologKinesis\Contracts\Client;

trait InteractsWithKinesis
{
public function mockKinesis(): \Mockery\MockInterface
protected function mockKinesis(): \Mockery\MockInterface
{
$mock = $this->mock(Client::class);

$mock->shouldReceive('configure')->andReturn($mock);

return $mock;
}

public function mockKinesisWith(Closure $mock): \Mockery\MockInterface
protected function mockKinesisWith(Closure $mock): \Mockery\MockInterface
{
return $this->mock(Client::class, $mock);
}

protected function withDefaultCredentials(Application $app): void
{
$app->config->set('services.kinesis.key', 'dummy-key');
$app->config->set('services.kinesis.secret', 'dummy-secret');
}

protected function withNullDefaultCredentials(Application $app): void
{
$app->config->set('services.kinesis.key', null);
$app->config->set('services.kinesis.secret', null);
}

protected function withoutDefaultCredentials(Application $app): void
{
// ...
}
}
112 changes: 86 additions & 26 deletions tests/MonologKinesisTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,81 @@ class MonologKinesisTest extends TestCase
{
use InteractsWithKinesis;

/** @define-env withDefaultCredentials */
public function test_aws_credentials_can_be_configured_at_the_service_level()
{
$kinesisHandler = collect(logger()->channel('some_channel')->getHandlers())->first();

$this->assertInstanceOf(\PodPoint\MonologKinesis\Handler::class, $kinesisHandler);

/** @var \PodPoint\MonologKinesis\Handler $kinesisHandler */
$this->assertEquals([
'region' => 'eu-west-1',
'version' => 'latest',
'http' => [],
'credentials' => [
'key' => 'dummy-key',
'secret' => 'dummy-secret',
],
], $kinesisHandler->getClient()->getAwsConfig());
}

/** @define-env withoutDefaultCredentials */
public function test_aws_credentials_can_be_completely_optional()
{
$kinesisHandler = collect(logger()->channel('some_channel')->getHandlers())->first();

$this->assertInstanceOf(\PodPoint\MonologKinesis\Handler::class, $kinesisHandler);

/** @var \PodPoint\MonologKinesis\Handler $kinesisHandler */
$this->assertEquals([
'region' => 'eu-west-1',
'version' => 'latest',
'http' => [],
], $kinesisHandler->getClient()->getAwsConfig());
}

/** @define-env withNullDefaultCredentials */
public function test_aws_credentials_can_be_null()
{
$kinesisHandler = collect(logger()->channel('some_channel')->getHandlers())->first();

$this->assertInstanceOf(\PodPoint\MonologKinesis\Handler::class, $kinesisHandler);

/** @var \PodPoint\MonologKinesis\Handler $kinesisHandler */
$this->assertEquals([
'region' => 'eu-west-1',
'version' => 'latest',
'http' => [],
], $kinesisHandler->getClient()->getAwsConfig());
}

/** @define-env withDefaultCredentials */
public function test_aws_credentials_can_be_configured_at_the_channel_level_overriding_default_credentials()
{
$this->mockKinesisWith(function ($mock) {
$mock->shouldReceive('configure')->once()->with(m::on(function ($argument) {
return $argument['key'] === 'some_other_key'
&& $argument['secret'] === 'some_other_secret'
&& $argument['region'] === 'another_region';
}))->andReturn($mock);

$mock->shouldReceive('putRecord')->once();
});

config()->set('logging.default', 'another_channel');
config()->set('logging.channels.another_channel', [
'driver' => 'kinesis',
'key' => 'some_other_key',
'secret' => 'some_other_secret',
'region' => 'another_region',
'stream' => 'logging',
'level' => 'debug',
]);

logger()->warning('Something went wrong.');
}

/**
* @return array
*/
Expand All @@ -26,7 +101,10 @@ public function loggerLevelTestProvider(): array
];
}

/** @dataProvider loggerLevelTestProvider $logLevel */
/**
* @dataProvider loggerLevelTestProvider
* @define-env withoutDefaultCredentials
*/
public function test_standard_log_levels_are_supported($logLevel)
{
$this->mockKinesis()
Expand All @@ -41,6 +119,7 @@ public function test_standard_log_levels_are_supported($logLevel)
logger()->$logLevel("Test {$logLevel} message");
}

/** @define-env withoutDefaultCredentials */
public function test_driver_does_not_log_below_default_log_level()
{
config()->set('logging.channels.some_channel.level', 'warning');
Expand All @@ -51,6 +130,7 @@ public function test_driver_does_not_log_below_default_log_level()
logger()->debug('Test debug message');
}

/** @define-env withoutDefaultCredentials */
public function test_driver_does_log_greater_than_or_equal_to_default_log_level()
{
config()->set('logging.channels.some_channel.level', 'warning');
Expand All @@ -61,6 +141,7 @@ public function test_driver_does_log_greater_than_or_equal_to_default_log_level(
logger()->error('Test error message');
}

/** @define-env withoutDefaultCredentials */
public function test_data_pushed_to_kinesis_is_properly_formatted_using_the_default_formatter()
{
$this->mockKinesis()->shouldReceive('putRecord')->once()->with(m::on(function ($argument) {
Expand All @@ -81,6 +162,7 @@ public function test_data_pushed_to_kinesis_is_properly_formatted_using_the_defa
logger()->info('Test info message');
}

/** @define-env withoutDefaultCredentials */
public function test_data_pushed_to_kinesis_can_be_formatted_using_a_custom_formatter()
{
$this->mockKinesis()->shouldReceive('putRecord')->once()->with(m::on(function ($argument) {
Expand All @@ -93,6 +175,7 @@ public function test_data_pushed_to_kinesis_can_be_formatted_using_a_custom_form
logger()->info('Test info message');
}

/** @define-env withoutDefaultCredentials */
public function test_a_custom_formatter_has_to_implement_the_required_interface_to_be_usable()
{
$this->mockKinesis()->shouldNotReceive('putRecord');
Expand All @@ -105,6 +188,7 @@ public function test_a_custom_formatter_has_to_implement_the_required_interface_
$this->assertStringContainsString('Unable to create configured logger', $logfile);
}

/** @define-env withoutDefaultCredentials */
public function test_data_pushed_to_kinesis_can_also_forward_some_context()
{
$this->mockKinesis()->shouldReceive('putRecord')->once()->with(m::on(function ($argument) {
Expand All @@ -116,31 +200,7 @@ public function test_data_pushed_to_kinesis_can_also_forward_some_context()
logger()->info('Test info message', ['some_context' => ['key' => 'value']]);
}

public function test_channel_specific_aws_credentials_can_be_given()
{
$this->mockKinesisWith(function ($mock) {
$mock->shouldReceive('configure')->once()->with(m::on(function ($argument) {
return $argument['key'] === 'some_other_key'
&& $argument['secret'] === 'some_other_secret'
&& $argument['region'] === 'another_region';
}))->andReturn($mock);

$mock->shouldReceive('putRecord')->once();
});

config()->set('logging.default', 'another_channel');
config()->set('logging.channels.another_channel', [
'driver' => 'kinesis',
'key' => 'some_other_key',
'secret' => 'some_other_secret',
'region' => 'another_region',
'stream' => 'logging',
'level' => 'debug',
]);

logger()->warning('Something went wrong.');
}

/** @define-env withoutDefaultCredentials */
public function test_channel_specific_http_client_options_can_be_given()
{
$this->mockKinesisWith(function ($mock) {
Expand Down
10 changes: 3 additions & 7 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,11 @@ protected function getPackageProviders($app): array
*/
protected function defineEnvironment($app): void
{
$app['config']->set('services.kinesis', [
'key' => 'dummy-key',
'secret' => 'dummy-secret',
'region' => 'eu-west-1',
]);
$app->config->set('services.kinesis.region', 'eu-west-1');

$app['config']->set('logging.default', 'some_channel');
$app->config->set('logging.default', 'some_channel');

$app['config']->set('logging.channels.some_channel', [
$app->config->set('logging.channels.some_channel', [
'driver' => 'kinesis',
'stream' => 'logging',
'level' => 'debug',
Expand Down

0 comments on commit 2677288

Please sign in to comment.