Skip to content

Commit

Permalink
Merge pull request #149 from JonErickson/support-mutual-tls
Browse files Browse the repository at this point in the history
Adds support for mutual TLS
  • Loading branch information
freekmurze authored Nov 20, 2023
2 parents 275cae5 + 9fd4678 commit cdb00c2
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 4 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,25 @@ WebhookCall::create()
...
```

### Using mutual TLS authentication

To safeguard the integrity of webhook data transmission, it's critical to authenticate the intended recipient of your webhook payload.
Mutual TLS authentication serves as a robust method for this purpose. Contrary to standard TLS, where only the client verifies the server,
mutual TLS requires both the webhook endpoint (acting as the client) and the webhook provider (acting as the server) to authenticate each other.
This is achieved through an exchange of certificates during the TLS handshake, ensuring that both parties confirm each other's identity.

> Note: If you need to include your own certificate authority, pass the certificate path to the `verifySsl()` method.
```php
WebhookCall::create()
->mutualTls(
certPath: storage_path('path/to/cert.pem'),
certPassphrase: 'optional_cert_passphrase',
sslKeyPath: storage_path('path/to/key.pem'),
sslKeyPassphrase: 'optional_key_passphrase'
)
```

The proxy specification follows the [guzzlehttp proxy format](https://docs.guzzlephp.org/en/stable/request-options.html#proxy)

### Verifying the SSL certificate of the receiving app
Expand Down
20 changes: 17 additions & 3 deletions src/CallWebhookJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,21 @@ class CallWebhookJob implements ShouldQueue

public int $requestTimeout;

public ?string $cert = null;

public ?string $certPassphrase = null;

public ?string $sslKey = null;

public ?string $sslKeyPassphrase = null;

public string $backoffStrategyClass;

public ?string $signerClass = null;

public array $headers = [];

public bool $verifySsl;
public string|bool $verifySsl;

public bool $throwExceptionOnFailure;

Expand Down Expand Up @@ -132,14 +140,20 @@ protected function createRequest(array $body): Response
{
$client = $this->getClient();

return $client->request($this->httpVerb, $this->webhookUrl, array_merge([
return $client->request($this->httpVerb, $this->webhookUrl, array_merge(
[
'timeout' => $this->requestTimeout,
'verify' => $this->verifySsl,
'headers' => $this->headers,
'on_stats' => function (TransferStats $stats) {
$this->transferStats = $stats;
},
], $body, is_null($this->proxy) ? [] : ['proxy' => $this->proxy]));
],
$body,
is_null($this->proxy) ? [] : ['proxy' => $this->proxy],
is_null($this->cert) ? [] : ['cert' => [$this->cert, $this->certPassphrase]],
is_null($this->sslKey) ? [] : ['ssl_key' => [$this->sslKey, $this->sslKeyPassphrase]]
));
}

protected function shouldBeRemovedFromQueue(): bool
Expand Down
12 changes: 11 additions & 1 deletion src/WebhookCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ public function maximumTries(int $tries): self
return $this;
}

public function mutualTls(string $certPath, string $sslKeyPath, ?string $certPassphrase = null, ?string $sslKeyPassphrase = null): self
{
$this->callWebhookJob->cert = $certPath;
$this->callWebhookJob->certPassphrase = $certPassphrase;
$this->callWebhookJob->sslKey = $sslKeyPath;
$this->callWebhookJob->sslKeyPassphrase = $sslKeyPassphrase;

return $this;
}

public function useBackoffStrategy(string $backoffStrategyClass): self
{
if (! is_subclass_of($backoffStrategyClass, BackoffStrategy::class)) {
Expand Down Expand Up @@ -160,7 +170,7 @@ public function withHeaders(array $headers): self
return $this;
}

public function verifySsl(bool $verifySsl = true): self
public function verifySsl(bool|string $verifySsl = true): self
{
$this->callWebhookJob->verifySsl = $verifySsl;

Expand Down
53 changes: 53 additions & 0 deletions tests/CallWebhookJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,59 @@ function baseGetRequest(array $overrides = []): array
->assertRequestsMade([$baseRequest]);
});

it('will use mutual TLS without passphrases', function () {
baseWebhook()
->mutualTls('foobar', 'barfoo')
->dispatch();

$baseRequest = baseRequest();

$baseRequest['options']['cert'] = ['foobar', null];
$baseRequest['options']['ssl_key'] = ['barfoo', null];

artisan('queue:work --once');

$this
->testClient
->assertRequestsMade([$baseRequest]);
});

it('will use mutual TLS with passphrases', function () {
baseWebhook()
->mutualTls('foobar', 'barfoo', 'foobarpassword', 'barfoopassword')
->dispatch();

$baseRequest = baseRequest();

$baseRequest['options']['cert'] = ['foobar', 'foobarpassword'];
$baseRequest['options']['ssl_key'] = ['barfoo', 'barfoopassword'];

artisan('queue:work --once');

$this
->testClient
->assertRequestsMade([$baseRequest]);
});

it('will use mutual TLS with certificate authority', function () {
baseWebhook()
->mutualTls('foobar', 'barfoo')
->verifySsl('foofoo')
->dispatch();

$baseRequest = baseRequest();

$baseRequest['options']['cert'] = ['foobar', null];
$baseRequest['options']['ssl_key'] = ['barfoo', null];
$baseRequest['options']['verify'] = 'foofoo';

artisan('queue:work --once');

$this
->testClient
->assertRequestsMade([$baseRequest]);
});

it('will use a proxy', function () {
baseWebhook()
->useProxy('https://proxy.test')
Expand Down

0 comments on commit cdb00c2

Please sign in to comment.