Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement exporterfactory #4

Merged
merged 11 commits into from
Jun 9, 2021
Merged

Implement exporterfactory #4

merged 11 commits into from
Jun 9, 2021

Conversation

alexperez52
Copy link

Description

This PR addresses issue 184. With this update, the exporter can be selected via configuration string. The implementation was done by creating a factory class called ExporterFactory that takes in a string through env variable (config string) and creates the correct Exporter. Prior to this change, exporters needed to be injected to the processors manually as instances.
Currently this update only support Jaeger and Zipkin exporters.

Type of change

New feature

Testing

Implemented Unit test that ensure that the exporter is being chosen dynamically based on env variable.
Testing verified through make test

Documentation

Documentation was added for the ExporterFactory file.

@alexperez52
Copy link
Author

Got some clarification that the factory should also handle the other exporters. Will update to reflect that

public function fromConnectionString(string $configurationString)
{
$strArr = explode('+', $configurationString);
$contribName = $strArr[0];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be validated against an allowlist. This is untrusted user input and needs to be validated. Since we know the allowable values here an allowlist should be used to reject any non-allowed values. My PHP is incredibly rusty, but something like this should work:

private $_allowedExporters = array("Jaeger" => true, "Zipkin" => true);

private function _isAllowed(string $exporter)
{
  return array_key_exists($this->_allowedExporters, $exporter) && $this->_allowedExporters{$exporter};
}

...
$contribName = ucfirst(lower($strArr[0]));
if !$this->_isAllowed($contribName) {
  return null // or some way to indicate an error here
}
$dynamicExporter = ...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Aneurysm9, you must always sanitize any user input to protect from attacks. If we do have an allowed exporters list, then definitely use that as suggested.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also look into the htmlentities() function which could serve as a way to sanitize input. Make sure it is relevant to your issue, usually it is used mostly to protect from front end malicious input.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these reviews. Will soon have an update to reflect this

/**
* @test
*/
public function testIfExporterHasCorrectEndpoint()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to add a test for invalid input as well. Perhaps even malicious input if you can construct one.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When writing you tests, make sure to use inputs that are malicious like certain html entities. There are examples out there that give examples of malicious attacks (especially with sql injections and other such attacks) although I don't know how relevant they will be to an exporter used for tracing.

public function fromConnectionString(string $configurationString)
{
$strArr = explode('+', $configurationString);
$contribName = $strArr[0];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Aneurysm9, you must always sanitize any user input to protect from attacks. If we do have an allowed exporters list, then definitely use that as suggested.

/**
* @test
*/
public function testIfExporterHasCorrectEndpoint()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When writing you tests, make sure to use inputs that are malicious like certain html entities. There are examples out there that give examples of malicious attacks (especially with sql injections and other such attacks) although I don't know how relevant they will be to an exporter used for tracing.

$factory = new ExporterFactory('test.jaeger');
$exporter = $factory->fromConnectionString($input);
$this->assertInstanceOf(Path\Jaeger\Exporter::class, $exporter);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of code structure these tests look good

public function fromConnectionString(string $configurationString)
{
$strArr = explode('+', $configurationString);
$contribName = $strArr[0];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also look into the htmlentities() function which could serve as a way to sanitize input. Make sure it is relevant to your issue, usually it is used mostly to protect from front end malicious input.

@alexperez52
Copy link
Author

Some of the generator functions seem redundant. I still wrote them in case of future changes in the implementation of the 6 different Exporters.

@alexperez52 alexperez52 requested review from Aneurysm9 and ohamuy June 5, 2021 00:54
Copy link

@ohamuy ohamuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything else seems really good with good code coverage. Just the htmlentities mistake I told you about

*/
public function fromConnectionString(string $configurationString)
{
$strArr = htmlentities($configurationString);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so I made a mistake here, we should never escape input, its bad practice because it permanently ties that string to its final output format. So from what I understand you are usually only expecting some type of identifier with a url at the end. So what you want to do is filter the input to only be in the format you expect. Look at these filters here and here. You can also make your own filter through a regular expression which might be what you want. This will make sure that you are getting input only in the format that you desire.

From what I see, I don't believe these input values ever are on some type of html page so you can exclude the use of html entities. If it actually does end up on an html page you would use htmlentities() when your outputting it, not on input. So I think you can safely exclude htmlentities() since its just a url but definitely add the format filter you want instead

Used Nyholm Dsn parser for input string parsing.
Modified unit tests and added testing for invalid licenseKey
sdk/Trace/ExporterFactory.php Show resolved Hide resolved
Comment on lines 50 to 55
$licenseKey = '';
if ($endpointUrl != false) {
$dsn = DsnParser::parse($endpointUrl);
$endpointUrl = (string) ($dsn->withoutParameter('licenseKey'));
$licenseKey = (string) ($dsn->getParameter('licenseKey'));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is overly-specific. The handling of licenseKey seems to only be applicable to the New Relic-flavored exporters. When new exporters are added that may have their own options this handling logic will need to change. Instead, if URL query string parameters are to be the mechanism for conveying configuration to exporters then they should be pulled into an array that is passed to the relevant constructor.

return null;
}

// endpointUrl should only be null with otlp and otlpgrpc

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the case then it should be validated/enforced in some manner. Not really relevant to this PR, but out of curiosity how does the OTLP exporter get its target configuration? The exporter should definitely be able to be pointed at different destinations.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that otlp- is constructed with no args and gets its params fron env variables

sdk/Trace/ExporterFactory.php Outdated Show resolved Hide resolved
return $exporter;
}

private function _generateOtlp()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might this underscore have been forgotten to be removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. thank you good catch

return array_key_exists($exporter, $this->allowedExporters) && $this->allowedExporters[$exporter];
}

// constructs the baseUrl from
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(also another nit, but you may want to finish this comment)

@alexperez52 alexperez52 merged commit 95c2dee into master Jun 9, 2021
@alexperez52 alexperez52 deleted the implement-exporterfactory branch June 18, 2021 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants