Allow editing of the CSP trusted image sources.#5337
Allow editing of the CSP trusted image sources.#5337w1ll-i-code wants to merge 5 commits intoIcinga:mainfrom
Conversation
For reference: We discussed a use case where an Icinga Web module depends on features from an external provider, such as OpenStreetMap. Without the ability to modify the CSP header, every user of the module would need to adjust or override the web server configuration. A more effective approach we considered is to implement specific functionality in Icinga Web to modify only certain parts of the header. Additionally, I think such functionality would also be necessary for https://github.com/nbuchwitz/icingaweb2-module-map for example. |
168628c to
43c748e
Compare
nilmerg
left a comment
There was a problem hiding this comment.
There are some style issues as well. Please rebase with main and the actions will run and point this out.
4ffe492 to
d31bc33
Compare
|
I just noticed this breaks our own font loading, so all our icons are invisible since In addition, I've already talked about this with you in person, I'd rather like to make this an automatic whitelisting. With the recent security release, we sandboxed iframes and automatically open them up in case a user configures a navigation item or dashboard with an external link. I'm thinking about the same with So, I'm afraid, we need to re-think that. I also fear complications with |
d31bc33 to
bbf35b5
Compare
bbf35b5 to
2a9adc0
Compare
nilmerg
left a comment
There was a problem hiding this comment.
Thanks for keeping at it!
I didn't look at it entirely yet. Only one thing for now:
The way you load navigation items is not correct yet. You forgot dashlets and you only load shared items the user is an owner of.
Take a look at \Icinga\Web\Navigation\Navigation::load for example. Though, please note that for dashlets a more complex solution is needed. Dashlets by modules can be loaded there (and should! Also modules may provide external URLs this way), but not user dashlets. This is done by \Icinga\Web\Widget\Dashboard::load instead.
Also, please remember that there are additional types of navigation items. A module can provide its own as well using \Icinga\Application\Modules\Module::provideNavigationItem. Monitoring's and Icinga DB Web's host and service actions are an example which may also result in an iframe.
So please restore the NavigationController and think about an alternative to the NavigationItemHelper.
|
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Davide Zeni.
|
|
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Davide Zeni.
|
3e68e02 to
47c9736
Compare
|
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Davide Zeni.
|
47c9736 to
78bfc90
Compare
|
Thank you for your pull request. Before we can look at it, you'll need to sign a Contributor License Agreement (CLA). Please follow instructions at https://icinga.com/company/contributor-agreement to sign the CLA. After that, please reply here with a comment and we'll verify. Contributors that have not signed yet: @zenosaaur Details
|
|
hello @nilmerg can you please give us a feedback on the changes we made? Thanks!! |
|
Thank you for your pull request. Before we can look at it, you'll need to sign a Contributor License Agreement (CLA). Please follow instructions at https://icinga.com/company/contributor-agreement to sign the CLA. After that, please reply here with a comment and we'll verify. Contributors that have not signed yet: @zenosaaur Details
|
|
Thank you for your pull request. Before we can look at it, you'll need to sign a Contributor License Agreement (CLA). Please follow instructions at https://icinga.com/company/contributor-agreement to sign the CLA. After that, please reply here with a comment and we'll verify. Contributors that have not signed yet: @zenosaaur Details
|
|
|
||
| namespace Icinga\Application\Hook; | ||
|
|
||
| abstract class CspDirectiveHook |
There was a problem hiding this comment.
As this is a new hook, add the new methods just like in #5433:
- all()
- register()
nilmerg
left a comment
There was a problem hiding this comment.
Please rebase with main as the CI configuration changed.
Didn't test yet but it looks promising already!
v2.13 is on the horizon, so ensure to get back to it soon if you want this being part of it. Or drop a comment to let me delegate this further if you don't have the time. (But then allow collaborator access for us)
| $this->view->title = $navigation->getLabel(); | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
Please restore the empty line at the end of the file as well.
There was a problem hiding this comment.
Changes to NavigationControllerhave been reverted in #5477
| @@ -0,0 +1,18 @@ | |||
| <?php | |||
| /* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */ | |||
There was a problem hiding this comment.
| /* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */ | |
| // SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com> | |
| // SPDX-License-Identifier: GPL-3.0-or-later |
There was a problem hiding this comment.
Neither I'd add GPLv3 files in a (yet) GPLv2 project (our corporate lawyer has left the chat 🙈), nor I'd mix license header formats in one and the same project. Latter especially because the GPLv3 PR (which doesn't even exist yet, by the way!) will likely use xargs to apply the same command to all files. The more files have the same header format, the better.
| $user = Auth::getInstance()->getUser(); | ||
| $menuItems = []; | ||
| if ($user === null) { | ||
| return $menuItems; | ||
| } | ||
| $navigationType = Navigation::getItemTypeConfiguration(); | ||
| foreach ($navigationType as $type => $_) { | ||
| $config = Config::navigation($type, $user->getUsername()); | ||
| $config->getConfigObject()->setKeyColumn('name'); | ||
| foreach ($config->select() as $itemConfig) { | ||
| if ($itemConfig->get("target", "") !== "_blank") { | ||
| $menuItems[] = ["name" => $itemConfig->get('name'), "url" => $itemConfig->get('url')]; | ||
| } | ||
| } | ||
| $configShared = Config::navigation($type); | ||
| $configShared->getConfigObject()->setKeyColumn('name'); | ||
| foreach ($configShared->select() as $itemConfig) { | ||
| if (Icinga::app()->hasAccessToSharedNavigationItem($itemConfig, $config) && $itemConfig->get("target", "") !== "_blank") { | ||
| $menuItems[] = ["name" => $itemConfig->get('name'), "url" => $itemConfig->get('url')]; | ||
| } | ||
| } | ||
| } | ||
| return $menuItems; | ||
| } |
There was a problem hiding this comment.
I was rather thinking about a solution like this: (Untested)
I didn't ensure this fits with your remaining code, so adjust it accordingly as you see fit.
| $user = Auth::getInstance()->getUser(); | |
| $menuItems = []; | |
| if ($user === null) { | |
| return $menuItems; | |
| } | |
| $navigationType = Navigation::getItemTypeConfiguration(); | |
| foreach ($navigationType as $type => $_) { | |
| $config = Config::navigation($type, $user->getUsername()); | |
| $config->getConfigObject()->setKeyColumn('name'); | |
| foreach ($config->select() as $itemConfig) { | |
| if ($itemConfig->get("target", "") !== "_blank") { | |
| $menuItems[] = ["name" => $itemConfig->get('name'), "url" => $itemConfig->get('url')]; | |
| } | |
| } | |
| $configShared = Config::navigation($type); | |
| $configShared->getConfigObject()->setKeyColumn('name'); | |
| foreach ($configShared->select() as $itemConfig) { | |
| if (Icinga::app()->hasAccessToSharedNavigationItem($itemConfig, $config) && $itemConfig->get("target", "") !== "_blank") { | |
| $menuItems[] = ["name" => $itemConfig->get('name'), "url" => $itemConfig->get('url')]; | |
| } | |
| } | |
| } | |
| return $menuItems; | |
| } | |
| if (! Auth::isAuthenticated()) { | |
| return []; | |
| } | |
| $origins = []; | |
| foreach (Navigation::load($type) as $navItem) { | |
| foreach (self::yieldNavigation($navItem) as $name => $url) { | |
| $origins[] = $url->getScheme() . '://' . $url->getHost(); | |
| } | |
| } | |
| return $origins; | |
| } | |
| protected static function yieldNavigation(NavigationItem $item): Generator | |
| { | |
| if ($item->hasChildren()) { | |
| foreach ($item as $child) { | |
| yield from self::yieldNavigation($child); | |
| } | |
| } else { | |
| if ($item->getTarget() !== '_blank' && $item->getUrl()->isExternal()) { | |
| yield $item->getName() => $item->getUrl(); | |
| } | |
| } | |
| } | |
There was a problem hiding this comment.
Implemented your suggestion in #5477
| } | ||
|
|
||
| private function hasAccessToSharedNavigationItem(&$config, Config $navConfig) | ||
| public function hasAccessToSharedNavigationItem(&$config, Config $navConfig) |
There was a problem hiding this comment.
I don't think that's needed if you use my proposal.
| $errorSource = sprintf("Navigation item %s", $navigationItem['name']); | ||
|
|
||
| $host = parse_url($navigationItem["url"], PHP_URL_HOST); | ||
| // Make sure $url is actually valid; | ||
| if (filter_var($navigationItem["url"], FILTER_VALIDATE_URL) === false) { | ||
| Logger::debug("$errorSource: Skipping invalid url: $host"); | ||
| continue; | ||
| } | ||
|
|
||
| $scheme = parse_url($navigationItem["url"], PHP_URL_SCHEME); | ||
|
|
||
|
|
||
| if ($host === null) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
The validation is already performed at least partially in the other methods. Please make sure to do this only once. Preferably during the fetch logic, as in my proposal.
|
Please excuse me for not informing everyone, but I discussed with WP that we will take over this PR. |
Hi, as discussed with @lippserd, we improved the CSP header and added the ability to whitelist certain trusted domains for the image sources. This closes #5333.