arkaitzgarro / elastic-apm-laravel

Laravel APM agent for Elastic v2 intake API
MIT License
79 stars 17 forks source link

Problems with self-signed certificates #181

Closed ElvisIsKing666 closed 7 months ago

ElvisIsKing666 commented 7 months ago

Hey - thanks for all your work. I was able to get the APM integration up and running on an ELK stack that was based on sherifabdlnaby/elastdocker. The problem I had was that the certificates it generates are self-signed and when the Guzzle7 adapter attempts to contact the APM server using https: there was no configuration option that I could find that provided either:

  1. a way to specify a custom CA certificate
  2. turn of verification of the certificate

In the end I had to subclass the ServiceProvider with my own implementation that created a hand-rolled client and fed that to AgentBuilder.

It works, but for such a simple use-case I'm wondering if I missed something? I found this issue for the official PHP APM agent: https://github.com/elastic/apm-agent-php/issues/414 - and they supported a verify_server_cert=false in the php.ini file or a ELASTIC_APM_VERIFY_SERVER_CERT env var. As far as I could tell this project doesn't.

I'm not used to using Psr18ClientDiscovery::find() which is what nipwaayoni/Middleware/Connector uses under the hood to create the default http client - so I don't know if it's possible to hook into that process and feed it config options.

I'll post my subclass here in case others find it helpful. I'd be curious to know if there's a simpler solution.

class CustomElasticApmServiceProvider extends ElasticApmServiceProvider
{
    protected function registerAgent(): void
    {
        $this->app->singleton(Agent::class, function ($app) {
            // Use the original configuration method
            $agentConfig = $this->getAgentConfig();

            // Configure Guzzle to not verify SSL
            $guzzleClient = new GuzzleClient(['verify' => false]);

            // Wrap the Guzzle client with a PSR-18 adapter
            $psr18Client = new GuzzleAdapter($guzzleClient);

            $builder = $this->app->make(AgentBuilder::class);

            // Building the agent with custom HTTP client and existing configurations
            return $builder
                ->withConfig(new Config($agentConfig))
                ->withHttpClient($psr18Client) // Inject the custom-configured HTTP client
                ->withEnvData(config('elastic-apm-laravel.env.env'))
                ->withAppConfig($this->app->make(Repository::class))
                ->withEventCollectors(collect($this->app->tagged(self::COLLECTOR_TAG)))
                ->build();
        });
    }
}
dstepe commented 7 months ago

Enabling the HTTP client configuration would need to start in underlying agent rather than this package. I'm not exactly sure how support client auto-discovery while enabling client configuration (which may vary by client). The 'bring your own client" approach is the compromise. If you want to open an issue we can further explore options.

For this package, we've used a slightly different approach to configure the client. In the standard AppServiceProvider::register() method, you can bind a configured AgentBuilder and provide only the http client:

        $this->app->bind(AgentBuilder::class, function () {
            $builder = new AgentBuilder();

            $builder->withHttpClient(new \Http\Adapter\Guzzle7\Client(new Client(['verify' => false])));

            return $builder;
        });

Do you want to give that a try and see if it works for you? If so, we can suggest this be added to the documentation of this project.

arkaitzgarro commented 7 months ago

Hi @ElvisIsKing666,

As mentioned by @dstepe, this is the way to go. We actually use this approach to reduce the timeout when connecting to Elastic:

$this->app->bind(AgentBuilder::class, function (): AgentBuilder {
    $builder = new AgentBuilder();
    $builder
        ->withHttpClient(new GuzzleClient([
            'timeout' => 1.0,
        ]));

    return $builder;
});
ElvisIsKing666 commented 7 months ago

Ah, right, I see now that the original service provider is calling app()->make() to get the AgentBuilder. So all I need to do is just bind a new one with the client configured the way I need it. Thanks for pointing that out.