Files
api/services/nginx/app/tests/Unit/Bird/BirdControlPlaneSecurityTest.php
T
Jeppe B a442e70744 Add secure Bird gateway for Pleno Control Plane (#332)
Add the Bird Control Plane gateway, signed webhook ingestion, policy-gated writes, fail-closed production auto-activation, and RSA-OAEP bootstrap credential flow.
2026-07-29 19:59:20 +02:00

107 lines
3.5 KiB
PHP

<?php
app_require('modules/bird/classes/bird_control_plane_security.php');
use bird\classes\bird_control_plane_security;
it('verifies the documented Bird webhook signature without changing the signed URL or body', function (): void {
$key = 'test-signing-key';
$timestamp = '1785312000';
$url = 'https://api.example.test/bird/webhooks/notifications?source=bird';
$body = '{"event":"conversation.updated","data":{"id":"c-1"}}';
$checksum = hash('sha256', $body, true);
$signature = base64_encode(hash_hmac(
'sha256',
$timestamp . "\n" . $url . "\n" . $checksum,
$key,
true
));
expect(bird_control_plane_security::verifyBirdWebhookSignature(
$key,
$timestamp,
$url,
$body,
$signature
))->toBeTrue()
->and(bird_control_plane_security::verifyBirdWebhookSignature(
$key,
$timestamp,
$url,
$body . "\n",
$signature
))->toBeFalse()
->and(bird_control_plane_security::verifyBirdWebhookSignature(
$key,
$timestamp,
'https://internal.example.test/bird/webhooks/notifications',
$body,
$signature
))->toBeFalse();
});
it('fails closed for malformed signatures and stale webhook timestamps', function (): void {
expect(bird_control_plane_security::verifyBirdWebhookSignature(
'key',
'1785312000',
'https://api.example.test/bird/webhooks/notifications',
'{}',
'not-base64!'
))->toBeFalse()
->and(bird_control_plane_security::timestampWithinReplayWindow(
'1785312000',
300,
1785312200
))->toBeTrue()
->and(bird_control_plane_security::timestampWithinReplayWindow(
'1785312000000',
300,
1785312200
))->toBeTrue()
->and(bird_control_plane_security::timestampWithinReplayWindow(
'1785312000',
300,
1785312401
))->toBeFalse()
->and(bird_control_plane_security::timestampWithinReplayWindow(
'tomorrow',
300,
1785312000
))->toBeFalse();
});
it('accepts only an exact configured bearer token', function (): void {
expect(bird_control_plane_security::bearerToken(
['HTTP_AUTHORIZATION' => 'Bearer expected-token']
))->toBe('expected-token')
->and(bird_control_plane_security::verifyBearer('expected-token', 'expected-token'))->toBeTrue()
->and(bird_control_plane_security::verifyBearer('expected-token', 'Expected-token'))->toBeFalse()
->and(bird_control_plane_security::verifyBearer('', 'anything'))->toBeFalse();
});
it('verifies timestamp-bound Bird Flow request signatures', function (): void {
$secret = 'flow-secret';
$timestamp = '1785312000';
$body = '{"event":{"platform":"sms"}}';
$signature = 'sha256=' . hash_hmac('sha256', $timestamp . "\n" . $body, $secret);
expect(bird_control_plane_security::verifyFlowSignature(
$secret,
$timestamp,
$body,
$signature
))->toBeTrue()
->and(bird_control_plane_security::verifyFlowSignature(
$secret,
$timestamp,
$body . ' ',
$signature
))->toBeFalse()
->and(bird_control_plane_security::verifyFlowSignature(
'',
$timestamp,
$body,
$signature
))->toBeFalse();
});