Problem/Motivation

Splitting off from #3035979-40: [DISCUSSION] Should a facility be added to JSON:API which limits automatic exposure of all entities.

We'd like to have an "in case of emergency, break glass" way to lock down routes to arbitrary entities and fields (e.g. to mitigate a security flaw that would be easily exploitable via API or UI).

Proposed resolution

Rather than intervening during routing, it intervenes in Entity/Field/TypedData isInternal(). This provides more security, because it prevents undesired routes from even existing, and it also allows for the emergency removal of vulnerable field and property types without needing to remove the entire entity type.

For example:

$settings['typed_data_internalize'] = [
  'internalize' => [
    \Drupal\Core\Config\Entity\ConfigEntityBase::class,
    \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::class,
  ],
  'except' => [
    \Drupal\Core\Datetime\Entity\DateFormat::class,
    \Drupal\image\Plugin\Field\FieldType\ImageItem::class,
  ],
];

Initial proof-of-concept patch by @effulgentsia.

Remaining tasks

  1. Finish polish + improvements.
  2. Add test coverage.
  3. Security team sign-off that this is valuable mitigation functionality for REST, JSON:API and GraphQL.
  4. Reviews / RTBC.
  5. Commit.

User interface changes

None.

API changes

Adds a new Trait for TypedData to override isInternal() via settings.php changes.

Data model changes

New options in settings.php.

Release notes snippet

TBD.

CommentFileSizeAuthor
#2 3037534-2.typed-data-mitigation.patch5.58 KBdww

Comments

dww created an issue. See original summary.

dww’s picture

StatusFileSize
new5.58 KB

(Uploading @effulgentsia's patch and copying my review into here)

#40 looks like a very elegant solution to this! Bravo.

A few nits, then let's see a full testbot run on it. I can't imagine it'll fail, but it'd be nice to know for sure.

  1. +++ b/core/lib/Drupal/Core/Entity/EntityType.php
    @@ -370,6 +372,11 @@ public function set($property, $value) {
    +    if ($this->internalizeViaSettings($this->getClass())) {
    

    Wonder if the method wants to be called "isInternalViaSettings()" to be a bit more clear that we're basically testing a bool. "internalize" as a verb sounds like it might take some action or have a side effect or something, while "is" is very clear. ;)

  2. +++ b/core/lib/Drupal/Core/TypedData/InternalizeViaSettingsTrait.php
    @@ -0,0 +1,33 @@
    +/**
    + *
    + */
    

    todo

  3. +++ b/core/lib/Drupal/Core/TypedData/InternalizeViaSettingsTrait.php
    @@ -0,0 +1,33 @@
    +  private function internalizeViaSettings($class) {
    

    This also needs a docblock.

  4. +++ b/core/lib/Drupal/Core/TypedData/InternalizeViaSettingsTrait.php
    @@ -0,0 +1,33 @@
    +      foreach ($setting['internalize'] as $test_class) {
    +        if (is_a($class, $test_class, TRUE)) {
    

    "test_class" might be misunderstood on a quick glance, or via grep, since we have a metric crapton of "test classes" in core. ;) Perhaps "settings_class"? Or maybe "internal_class" here?

  5. +++ b/core/lib/Drupal/Core/TypedData/InternalizeViaSettingsTrait.php
    @@ -0,0 +1,33 @@
    +      foreach ($setting['except'] as $test_class) {
    

    ... and "except_class" here? Or stick with "settings_class" in both?

  6. Needs tests (if possible). I don't know if/how we could mock the Settings class since it's so low-level. :/ A functional test of some kind to verify that the thing we said is internal is no longer accessible might also be nice for peace-of-mind that this actually locks down something (although I'm sure we've got other access tests for things where isInternal() returns true. But it'd be nice to make sure we don't accidentally forget to call the "internalize" method in the right spots, or refactor/remove it without noticing, etc.

Otherwise, I love it. ;) Meanwhile, I think this could be a follow-up security hardening "task" that could be done after alpha1 (and perhaps back-ported to 8.6.x), so I don't think we should block jsonapi itself on this. Perhaps we should submit that separately now so as not to bog down this discussion with nitpicky reviews like I've just done. ;)

Otherwise, some kind of agreement/commitment on #3037039: Create a public API for indicating resource types should not be exposed would be nice before we commit #2843147: Add JSON:API to core as a stable module but I don't think we need to *fix* #3037039 first, just agree on the plan.

Thanks!
-Derek

dww’s picture

Status: Needs work » Active
Issue tags: -blocker

This isn't blocking anything, now that #3037942: Add facility to allow security advisories to advise to disable certain routes to mitigate security risk temporarily seems like the way forward.
In fact, I think this probably is now "won't fix" based on how overloaded and confusing "internal" is in core.
See discussion upstream.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

xjm’s picture

As far as I know this already exists? So maybe this issue can be closed as duplicate. See also #3040354: Introduce entity type annotation key for indicating that an entity type is *safe* for HTTP API usage.

wim leers’s picture

Title: Add a mechanism to mark TypedData (entities and fields) internal via settings » Add a mechanism to mark TypedData (entities and fields) internal via settings.php

#7: this does not yet exist. \Drupal\Core\TypedData\DataDefinitionInterface::isInternal() exists, but this issue about allowing the boolean that that returns to be overridden via settings.php.

Clarified issue title.

wim leers’s picture

Issue summary: View changes
Issue tags: +API-First Initiative
wim leers’s picture

I am concerned about the performance impact of this proposal. Because overridability via settings.php implies it needs to be evaluated on every request, especially if the purpose is to be a security improvement.

Is my assumption correct that this would need to be evaluated on every request?

I guess we could hash $settings['typed_data_internalize'] and make it a cache context that all normalizations must vary by. That'd solve the performance impact.

Thoughts?

wim leers’s picture

@gabesullice in #3040025-8: [meta] Decide on and implement additional security hardenings for JSON:API and other HTTP APIs:

  • #3037534 would be tremendously complicated to implement, complicated to configure and does not make JSON:API safer by default. While it is the proposal that offers the most granular tool for securing JSON:API, that pro doesn't seem to outweigh the costs IMO.
gabesullice’s picture

In #3040025-8: [meta] Decide on and implement additional security hardenings for JSON:API and other HTTP APIs I proposed we close this issue because of the concerns I expressed (copied to #12 above). @Wim Leers agreed with me about my concerns. His comment in #10 adds more concerns.

I think we should give this 2 weeks from today for @dww, @xjm or @effulgentsia (or others) to weigh in otherwise. At the end of that time we can act accordingly.

dww’s picture

My perspective from #4 still stands:

In fact, I think this probably is now "won't fix" based on how overloaded and confusing "internal" is in core.
See discussion upstream.

*shrug*

wim leers’s picture

Status: Active » Closed (won't fix)

@gabesullice in #13:

I think we should give this 2 weeks from today for @dww, @xjm or @effulgentsia (or others) to weigh in otherwise. At the end of that time we can act accordingly.

Tomorrow it'll have been 10 weeks. I think it's time to close this.