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
3 changes: 3 additions & 0 deletions .github/workflows/test-monorepo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ jobs:
- name: Test PHPStan
run: vendor/bin/phpstan

- name: Create random file for encryption tests
run: (cd packages/DataProtection && tests/before-tests.sh)

- name: Test PHPUnit on Postgres
run: vendor/bin/phpunit --no-coverage
env:
Expand Down
12 changes: 9 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,19 @@
},
"require": {
"php": "^8.2",
"ext-amqp": "*",
"ext-openssl": "*",
"doctrine/dbal": "^3.9|^4.0",
"doctrine/persistence": "^2.5|^3.4",
"defuse/php-encryption": "^2.4",
"enqueue/amqp-lib": "^0.10.25",
"enqueue/redis": "^0.10.9",
"enqueue/sqs": "^0.10.15",
"enqueue/enqueue": "^0.10.0",
"ext-amqp": "*",
"laminas/laminas-code": "^4",
"jms/serializer": "^3.32",
"laravel/framework": "^9.5.2|^10.0|^11.0|^12.0|^13.0",
"paragonie/random_compat": "^2.0",
"prooph/pdo-event-store": "^1.16.3",
"psr/log": "^2.0|^3.0",
"queue-interop/queue-interop": "^0.8",
Expand Down Expand Up @@ -172,7 +174,8 @@
"symfony/monolog-bundle": "^3.10",
"kwn/php-rdkafka-stubs": "^2.2",
"symfony/var-exporter": "^6.4|^7.0|^8.0",
"enqueue/dsn": "^0.10.27"
"enqueue/dsn": "^0.10.27",
"yoast/phpunit-polyfills": "^4.0.0"
},
"conflict": {
"symfony/doctrine-messenger": ">7.0.5 < 7.1.0",
Expand Down Expand Up @@ -212,7 +215,10 @@
},
"scripts": {
"tests:phpstan": "vendor/bin/phpstan",
"tests:phpunit": "vendor/bin/phpunit --no-coverage",
"tests:phpunit": [
"(cd packages/DataProtection && tests/before-tests.sh)",
"vendor/bin/phpunit --no-coverage"
],
"tests:behat": "vendor/bin/behat -vvv",
"tests:ci": [
"@tests:phpstan",
Expand Down
2 changes: 2 additions & 0 deletions packages/DataProtection/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ file
.phpunit.result.cache
composer.lock
phpunit.xml

tests/Fixture/files/big-generated-file
19 changes: 11 additions & 8 deletions packages/DataProtection/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
"ecotone",
"Encryption",
"OpenSSL",
"Data Protection",
"Data Obfuscation"
"Sensitive Data Protection",
"Secure Messages"
],
"description": "Extends Ecotone with Data Protection features allowing to obfuscate messages with sensitive data.",
"description": "Extends Ecotone with Data Protection features allowing to secure sensitive data.",
"autoload": {
"psr-4": {
"Ecotone\\DataProtection\\": "src"
Expand All @@ -36,20 +36,23 @@
}
},
"require": {
"php": "^8.2",
"ext-openssl": "*",
"ecotone/ecotone": "~1.299.2",
"ecotone/jms-converter": "~1.299.2",
"defuse/php-encryption": "^2.4"
"paragonie/random_compat": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5|^10.5|^11.0",
"phpstan/phpstan": "^1.8",
"psr/container": "^2.0",
"phpunit/phpunit": "^11.0",
"phpstan/phpstan": "^2.1",
"wikimedia/composer-merge-plugin": "^2.1"
},
"scripts": {
"tests:phpstan": "vendor/bin/phpstan",
"tests:phpunit": "vendor/bin/phpunit --no-coverage --testdox",
"tests:phpunit": [
"tests/before-tests.sh",
"vendor/bin/phpunit --no-coverage"
],
"tests:ci": [
"@tests:phpstan",
"@tests:phpunit"
Expand Down
8 changes: 5 additions & 3 deletions packages/DataProtection/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="true"
>
<coverage processUncoveredFiles="true">
<source>
<include>
<directory suffix=".php">./src</directory>
<directory>./src</directory>
</include>
</source>
<coverage>
<report>
<text outputFile="php://stdout" showOnlySummary="true" />
</report>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public function channelName(): string
return $this->channelName;
}

public function obfuscatorConfig(): ObfuscatorConfig
public function messageEncryptionConfig(): MessageEncryptionConfig
{
return new ObfuscatorConfig($this->encryptionKey, $this->isPayloadSensitive, $this->sensitiveHeaders);
return new MessageEncryptionConfig($this->encryptionKey, $this->isPayloadSensitive, $this->sensitiveHeaders);
}

public function withSensitivePayload(bool $isPayloadSensitive): self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Ecotone\DataProtection\Configuration;

use Defuse\Crypto\Key;
use Ecotone\DataProtection\Encryption\Key;
use Ecotone\Messaging\Support\Assert;

class DataProtectionConfiguration
Expand Down
74 changes: 39 additions & 35 deletions packages/DataProtection/src/Configuration/DataProtectionModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

namespace Ecotone\DataProtection\Configuration;

use Defuse\Crypto\Key;
use Ecotone\AnnotationFinder\AnnotatedMethod;
use Ecotone\AnnotationFinder\AnnotationFinder;
use Ecotone\DataProtection\Attribute\Sensitive;
use Ecotone\DataProtection\Attribute\WithEncryptionKey;
use Ecotone\DataProtection\Attribute\WithSensitiveHeader;
use Ecotone\DataProtection\Obfuscator\Obfuscator;
use Ecotone\DataProtection\Encryption\Key;
use Ecotone\DataProtection\MessageEncryption\MessageEncryptor;
use Ecotone\DataProtection\OutboundDecryptionChannelBuilder;
use Ecotone\DataProtection\OutboundEncryptionChannelBuilder;
use Ecotone\JMSConverter\JMSConverterConfiguration;
Expand All @@ -39,20 +39,23 @@
#[ModuleAnnotation]
final class DataProtectionModule extends NoExternalConfigurationModule
{
final public const ENCRYPTOR_SERVICE_ID_FORMAT = 'ecotone.data-protection.encryptor.%s';
final public const KEY_SERVICE_ID_FORMAT = 'ecotone.encryption.key.%s';

/**
* @param array<ObfuscatorConfig> $obfuscatorConfigs
* @param array<MessageEncryptionConfig> $encryptionConfigs
*/
public function __construct(private array $obfuscatorConfigs)
public function __construct(private array $encryptionConfigs)
{
}

public static function create(AnnotationFinder $annotationRegistrationService, InterfaceToCallRegistry $interfaceToCallRegistry): static
{
$obfuscatorConfigs = self::resolveObfuscatorConfigsFromAnnotatedClasses($annotationRegistrationService->findAnnotatedClasses(Sensitive::class), [], $interfaceToCallRegistry);
$obfuscatorConfigs = self::resolveObfuscatorConfigsFromAnnotatedMethods($annotationRegistrationService->findAnnotatedMethods(CommandHandler::class), $obfuscatorConfigs, $interfaceToCallRegistry);
$obfuscatorConfigs = self::resolveObfuscatorConfigsFromAnnotatedMethods($annotationRegistrationService->findAnnotatedMethods(EventHandler::class), $obfuscatorConfigs, $interfaceToCallRegistry);
$encryptionConfigs = self::resolveEncryptionConfigsFromAnnotatedClasses($annotationRegistrationService->findAnnotatedClasses(Sensitive::class), $interfaceToCallRegistry);
$encryptionConfigs = self::resolveEncryptionConfigsFromAnnotatedMethods($annotationRegistrationService->findAnnotatedMethods(CommandHandler::class), $encryptionConfigs, $interfaceToCallRegistry);
$encryptionConfigs = self::resolveEncryptionConfigsFromAnnotatedMethods($annotationRegistrationService->findAnnotatedMethods(EventHandler::class), $encryptionConfigs, $interfaceToCallRegistry);

return new self($obfuscatorConfigs);
return new self($encryptionConfigs);
}

public function prepare(Configuration $messagingConfiguration, array $extensionObjects, ModuleReferenceSearchService $moduleReferenceSearchService, InterfaceToCallRegistry $interfaceToCallRegistry): void
Expand All @@ -70,7 +73,7 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO

foreach ($dataProtectionConfiguration->keys() as $encryptionKeyName => $key) {
$messagingConfiguration->registerServiceDefinition(
id: sprintf('ecotone.encryption.key.%s', $encryptionKeyName),
id: sprintf(self::KEY_SERVICE_ID_FORMAT, $encryptionKeyName),
definition: new Definition(
Key::class,
[$key->saveToAsciiSafeString()],
Expand All @@ -79,39 +82,39 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO
);
}

$channelObfuscatorReferences = $messageObfuscatorReferences = [];
$channelEncryptorReferences = $messageEncryptorReferences = [];
foreach ($channelProtectionConfigurations as $channelProtectionConfiguration) {
Assert::isTrue($messagingConfiguration->isPollableChannel($channelProtectionConfiguration->channelName()), sprintf('`%s` channel must be pollable channel to use Data Protection.', $channelProtectionConfiguration->channelName()));

$obfuscatorConfig = $channelProtectionConfiguration->obfuscatorConfig();
$encryptionConfig = $channelProtectionConfiguration->messageEncryptionConfig();
$messagingConfiguration->registerServiceDefinition(
id: $id = sprintf('ecotone.encryption.obfuscator.%s', $channelProtectionConfiguration->channelName()),
id: $id = sprintf(self::ENCRYPTOR_SERVICE_ID_FORMAT, $channelProtectionConfiguration->channelName()),
definition: new Definition(
Obfuscator::class,
MessageEncryptor::class,
[
Reference::to(sprintf('ecotone.encryption.key.%s', $obfuscatorConfig->encryptionKeyName($dataProtectionConfiguration))),
$obfuscatorConfig->isPayloadSensitive,
$obfuscatorConfig->sensitiveHeaders,
Reference::to(sprintf(self::KEY_SERVICE_ID_FORMAT, $encryptionConfig->encryptionKeyName($dataProtectionConfiguration))),
$encryptionConfig->isPayloadSensitive,
$encryptionConfig->sensitiveHeaders,
],
)
);

$channelObfuscatorReferences[$channelProtectionConfiguration->channelName()] = Reference::to($id);
$channelEncryptorReferences[$channelProtectionConfiguration->channelName()] = Reference::to($id);
}

foreach ($this->obfuscatorConfigs as $messageClass => $obfuscatorConfig) {
foreach ($this->encryptionConfigs as $messageClass => $encryptionConfig) {
$messagingConfiguration->registerServiceDefinition(
id: $id = sprintf('ecotone.encryption.obfuscator.%s', $messageClass),
id: $id = sprintf(self::ENCRYPTOR_SERVICE_ID_FORMAT, $messageClass),
definition: new Definition(
Obfuscator::class,
MessageEncryptor::class,
[
Reference::to(sprintf('ecotone.encryption.key.%s', $obfuscatorConfig->encryptionKeyName($dataProtectionConfiguration))),
$obfuscatorConfig->isPayloadSensitive,
$obfuscatorConfig->sensitiveHeaders,
Reference::to(sprintf(self::KEY_SERVICE_ID_FORMAT, $encryptionConfig->encryptionKeyName($dataProtectionConfiguration))),
$encryptionConfig->isPayloadSensitive,
$encryptionConfig->sensitiveHeaders,
],
)
);
$messageObfuscatorReferences[$messageClass] = Reference::to($id);
$messageEncryptorReferences[$messageClass] = Reference::to($id);
}

foreach (ExtensionObjectResolver::resolve(MessageChannelWithSerializationBuilder::class, $extensionObjects) as $pollableMessageChannel) {
Expand All @@ -122,15 +125,15 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO
$messagingConfiguration->registerChannelInterceptor(
new OutboundEncryptionChannelBuilder(
relatedChannel: $pollableMessageChannel->getMessageChannelName(),
channelObfuscatorReference: $channelObfuscatorReferences[$pollableMessageChannel->getMessageChannelName()] ?? null,
messageObfuscatorReferences: $messageObfuscatorReferences,
channelEncryptorReference: $channelEncryptorReferences[$pollableMessageChannel->getMessageChannelName()] ?? null,
messageEncryptorReferences: $messageEncryptorReferences,
)
);
$messagingConfiguration->registerChannelInterceptor(
new OutboundDecryptionChannelBuilder(
relatedChannel: $pollableMessageChannel->getMessageChannelName(),
channelObfuscatorReference: $channelObfuscatorReferences[$pollableMessageChannel->getMessageChannelName()] ?? null,
messageObfuscatorReferences: $messageObfuscatorReferences,
channelEncryptionReference: $channelEncryptorReferences[$pollableMessageChannel->getMessageChannelName()] ?? null,
messageEncryptionReferences: $messageEncryptorReferences,
)
);
}
Expand All @@ -151,20 +154,21 @@ public function getModulePackageName(): string
return ModulePackageList::DATA_PROTECTION_PACKAGE;
}

private static function resolveObfuscatorConfigsFromAnnotatedClasses(array $sensitiveMessages, array $obfuscatorConfigs, InterfaceToCallRegistry $interfaceToCallRegistry): array
private static function resolveEncryptionConfigsFromAnnotatedClasses(array $sensitiveMessages, InterfaceToCallRegistry $interfaceToCallRegistry): array
{
$encryptionConfigs = [];
foreach ($sensitiveMessages as $message) {
$classDefinition = $interfaceToCallRegistry->getClassDefinitionFor(Type::create($message));
$encryptionKey = $classDefinition->findSingleClassAnnotation(Type::create(WithEncryptionKey::class))?->encryptionKey();
$sensitiveHeaders = array_map(static fn (WithSensitiveHeader $annotation) => $annotation->header, $classDefinition->getClassAnnotations(Type::create(WithSensitiveHeader::class)) ?? []);

$obfuscatorConfigs[$message] = new ObfuscatorConfig(encryptionKey: $encryptionKey, isPayloadSensitive: true, sensitiveHeaders: $sensitiveHeaders);
$encryptionConfigs[$message] = new MessageEncryptionConfig(encryptionKey: $encryptionKey, isPayloadSensitive: true, sensitiveHeaders: $sensitiveHeaders);
}

return $obfuscatorConfigs;
return $encryptionConfigs;
}

private static function resolveObfuscatorConfigsFromAnnotatedMethods(array $annotatedMethods, array $obfuscatorConfigs, InterfaceToCallRegistry $interfaceToCallRegistry): array
private static function resolveEncryptionConfigsFromAnnotatedMethods(array $annotatedMethods, array $encryptionConfigs, InterfaceToCallRegistry $interfaceToCallRegistry): array
{
/** @var AnnotatedMethod $method */
foreach ($annotatedMethods as $method) {
Expand All @@ -175,7 +179,7 @@ private static function resolveObfuscatorConfigsFromAnnotatedMethods(array $anno
$payload->hasAnnotation(Header::class)
|| $payload->hasAnnotation(Headers::class)
|| $payload->hasAnnotation(Reference::class)
|| array_key_exists($payload->getTypeHint(), $obfuscatorConfigs)
|| array_key_exists($payload->getTypeHint(), $encryptionConfigs)
) {
continue;
}
Expand All @@ -193,10 +197,10 @@ private static function resolveObfuscatorConfigsFromAnnotatedMethods(array $anno
}
}

$obfuscatorConfigs[$payload->getTypeHint()] = new ObfuscatorConfig(encryptionKey: $encryptionKey, isPayloadSensitive: true, sensitiveHeaders: $sensitiveHeaders);
$encryptionConfigs[$payload->getTypeHint()] = new MessageEncryptionConfig(encryptionKey: $encryptionKey, isPayloadSensitive: true, sensitiveHeaders: $sensitiveHeaders);
}

return $obfuscatorConfigs;
return $encryptionConfigs;
}

private function verifyLicense(Configuration $messagingConfiguration): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use Ecotone\Messaging\Support\Assert;

final readonly class ObfuscatorConfig
final readonly class MessageEncryptionConfig
{
/**
* @param array<string> $sensitiveHeaders
Expand Down
Loading