-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add firewall section to the networking page #8032
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
Conversation
Related links: |
Ei, there's any still-alpha firewall module available to download for trying it? Thanks! |
First update towards @garrett's design: move the page over to the network page. Small bug (in addition to all the stuff missing): |
Quick update: make the breadcrumb work correctly, hook up the switch to starting/stopping |
Update with a refactor, because my initial approach of talking to firewalld turned out to make too many D-Bus calls and cause too many re-renders. Removing services is now supported. Also lots of tiny fixes to get it close to the design. |
pkg/networkmanager/firewall.jsx
Outdated
return ( | ||
<div className="container-fluid page-ct"> | ||
<ol className="breadcrumb"> | ||
<li><a onClick={go_up}>{_("Network")}</a></li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a flyby comment that this is called "Networking" in the menu and on the breadcrumb on the other sub-pages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. Thanks for the catch! It'll be fixed with the next push.
Update with minor fixes pointed out by @martinpitt and @andreasn. Also add a first version of the "Add Services" dialog. It's still missing the search box and some layout fixes to qualify for this PR, so leaving WIP. Depends on #8794 now. |
Fix a couple of things we discussed on IRC:
|
Another update, which I think brings it to a state for a first round of review (removed WIP):
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this! The code layout and structure is very clean and easy to follow (kudos! it's a nice read), and the scope is just right for the initial landing. I have lots of nitpicks, but the vast majority should be trivial to fix.
@@ -46,6 +46,7 @@ Vagrant.configure(2) do |config| | |||
atomic \ | |||
docker \ | |||
etcd \ | |||
firewalld \ |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
cockpit.event_target(firewall); | ||
|
||
var firewalld_service = service.proxy('firewalld'); | ||
var firewalld_dbus = cockpit.dbus('org.fedoraproject.FirewallD1', { superuser: 'try' }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two should be const
.
superuser "try" might work because firewalld has polkit rules? But the default policy (rightfully) does not allow users to change the firewall rules without authenticating as admin. Does reading the state work as user? Then the page would be useful to non-admins, provided that permission errors are handled sanely (e. g. by not showing the "modify" buttons for non-admins).
var firewalld_service = service.proxy('firewalld'); | ||
var firewalld_dbus = cockpit.dbus('org.fedoraproject.FirewallD1', { superuser: 'try' }); | ||
|
||
firewalld_service.addEventListener('changed', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: you mix arrow functions and classic function
s for this kind of "inline" event handlers. Arrow functions are a bit more ES6-y I think. (totally not a merge blocker, of course)
var firewalld_dbus = cockpit.dbus('org.fedoraproject.FirewallD1', { superuser: 'try' }); | ||
|
||
firewalld_service.addEventListener('changed', function () { | ||
var installed = !!firewalld_service.exists; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't exists
already a boolean property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's set to null
before the first update. I've been bitten by JavaScript's weird type coercion too many times, so I've left the !!
.
.then(reply => { | ||
var [ , name, description, ports ] = reply[0]; | ||
|
||
var info = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use let
in this kind of arrow functions for proper scoping.
raise Exception("timed out waiting for {} to become {}".format(unit, state)) | ||
|
||
|
||
class TestNetworking(MachineCase): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename to TestFirewall
, to avoid confusion.
These tests need to be blacklisted on at least rhel-7-4 (where networking is in base, and thus don't get this feature). There are also some images (like the Debians) which don't have firewalld, I suggest the test skips them for now.
wait_unit_state(m, "firewalld", "active") | ||
|
||
active_rules = m.execute("firewallctl zone '' list services").split() | ||
b.wait_in_text("#networking-firewall-summary", "{} Active Rules".format(len(active_rules))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corner case: Use "Rule" here instead of "Rules", for the corner case that there is really just one. However, our images should have at least ssh and cockpit, so I guess it's fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, awesome catch!
b.wait_present("#networking-firewall-switch label.active:not(.disabled)") | ||
b.wait_in_text("#networking-firewall-switch label.active", "Off") | ||
wait_unit_state(m, "firewalld", "inactive") | ||
b.wait_in_text("#networking-firewall-summary", "0 Active Rules") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behaviour feels a bit odd. If the firewall is disabled, then "0 active rules" feels like "enabled, but unconfigured". Shouldn't this be hidden completely, as the firewall state is already shown as "off"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is as designed. I agree that we might want something better here. I'll ask @garrett.
b.enter_page("/network/firewall") | ||
|
||
b.click(".breadcrumb li:first a") | ||
b.enter_page("/network") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest stopping and removing firewalld.service from disk (don't forget systemctl daemon-reload), and verify the expected behaviour of "not installed".
|
||
self.login_and_go("/network/firewall") | ||
|
||
# Add a service that's probably not enabled by default anywhere |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... so do the equivalent wait_not_present()?
Thanks very much for the detailed review. Lots of good stuff! To not spam you too much, I haven't commented "Fixed" on the suggestions that I accepted exactly as you wrote them. The one thing I haven't fixed is showing the UI read-only when we're not superuser, as that's a bit more involved. How do we usually find this out? Ask polkit directly? |
@larskarlitski : We spoke about this in IRC, but for the record: You can query https://www.freedesktop.org/software/polkit/docs/latest/eggdbus-interface-org.freedesktop.PolicyKit1.Authority.html#eggdbus-method-org.freedesktop.PolicyKit1.Authority.CheckAuthorization to see if your process is allowed to authorize. Of course you still need to know whether the user ticked the "use my password for admin tasks", I hope we store that somewhere? |
Added a commit that disables buttons when the current login doesn't want their password remembered. This removes I ended up calling This is a bit weird, because I couldn't find a way to get a signal when the "Lock" button in the menubar is clicked, in which case we'd have to rerender the UI. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works well enough, thanks for that! The "On/Off" button on the /network/firewall page still needs the same "disable" treatment as "Add services..". I can't actually operate it in unprivileged mode - it stays at "off" even though the firewall is really on.
I don't see the firewall section on /network at all in unprivileged mode. I think this is right - the /firewall page is next to useless then anyway.
@@ -120,6 +121,12 @@ firewalld_dbus.subscribe({ | |||
firewall.dispatchEvent('changed'); | |||
}); | |||
|
|||
cockpit.spawn(['sh', '-c', 'pkcheck --action-id org.fedoraproject.FirewallD1.all --process $$ -u 2>&1']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit ugly indeed, but I can't think of a better way. If we encounter this more often in the future, we might instead query/store the bridge pid as a global variable somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. If we start doing this in more places, we probably also want (internal) library functions for that.
pkg/networkmanager/firewall.jsx
Outdated
var addServiceAction; | ||
var classes = "btn btn-primary pull-right"; | ||
if (this.state.firewall.readonly) | ||
addServiceAction = <button className={classes} disabled title={_("You are not authorized to modify the firewall.")}>{_("Add Services…")}</button>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is using the standard browser tooltips (white on black) which are "wrong". I suggest using update_privileged()
for that, or if that somehow is impractical, use .tooltip('fixTitle')
(but that requires jQuery) or our Tooltip
react component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update_privileged()
also depends on jQuery, which I don't want to pull into firewall.jsx
. I've used the React component, which unfortunately needed some minor tweaking. I've added the two commits on here as well.
pkg/networkmanager/firewall.jsx
Outdated
@@ -65,6 +65,8 @@ function ServiceRow(props) { | |||
{ udp.map(p => p.port).join(', ') } | |||
</div>, | |||
<button className="btn btn-danger pficon pficon-delete" | |||
title={props.readonly ? _("You are not authorized to modify the firewall.") : null} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the wrong tooltip. Here it seems mostly irrelevant as an unprivileged user cannot even see the current firewall rules. But there is the theoretical possibility that an admin changes the polkit rules to allow that to users, but not modification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see the firewall rules as an unprivileged user on fedora. Fixed as above.
As discussed on IRC: I can operate the button even in unprivileged mode. I guess there's something wrong with
I do, but I've left it the way it is for the reason above. I've added two more commits to fix our |
Actually now I see that too, not sure what was different. However, does that actually work for you? What happens here is that the firewall is shown as "off", then I can click it to "on" (but does not actually start firewalld.service), and then it moves to the disabled state. From then on it stays disabled. Another issue is that in privileged mode the On/Off switch is a bit confusing. It takes about two seconds after clicking to actually take effect, but during that time the button stays enabled and does not change state. This feels like "I mis-clicked", and when clicking again it interferes with the first click. Compare this with the "Automatic Updates" on/off switch on software updates. This immediately switches state, and gets disabled until the action takes place. This was the result of some involved discussions with our designers, so I think we should have the same behaviour here. Both are minor issues, so I would be happy if these become follow-ups if you prefer. The rest looks good, great job with figuring out the tooltip issues! I confirm that the changes work well, also on the kdump/playground pages. |
The firewall tests fail on several OSes. |
I've changed the switch on the firewall page to behave in the same way as the other one: it is disabled while the state change is pending. The tests had two issues: one was a logic mixup ( Also folded the read-only mode commit into the main one. Sorry for the delay. |
Still some test failures. |
Finally got around to look at the failing tests. Sorry for the delay. Don't use I tried to make this work on fedora-atomic and rhel-atomic. They do have firewalld installed, but it's not enabled by default. The tests fail as soon as its enabled, because there's no hole for cockpit in their firewall. I've tried adding that but ran into more issues and decided this is ok for a follow-up. |
Hmm, this collided with #9027. Maybe this was too aggressive and we should revert the "indent" stuff.. |
@larskarlitski : Force-pushed with running |
Ah, I rebased and fixed a conflict, it didn’t build again, because I had to run. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests look good now, nice! There are a few outstanding issues/questions from the previous review round, and this still needs a feature page and a video or some screen shots. Getting close!
rowId={props.service.id} | ||
columns={columns} | ||
tabRenderers={tabs}/>; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine for me, just wanted to point it out.
pkg/networkmanager/firewall.jsx
Outdated
enabled: firewall.enabled, | ||
services: firewall.services, | ||
enabledServices: firewall.enabledServices | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still like to discuss the above.
firewall.dispatchEvent('changed'); | ||
}); | ||
|
||
cockpit.spawn(['sh', '-c', 'pkcheck --action-id org.fedoraproject.FirewallD1.all --process $$ -u 2>&1']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe replace -u
with --allow-user-interaction
-- short options are nice for users to type; but not really in code imo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a good point. Changed. Thanks!
Added a feature page. |
eslint spotted a bug and has another complaint:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! I found a few small bugs and nitpicks, but this is really close now :)
doc/guide/feature-firewall.xml
Outdated
services in the default zone.</para> | ||
|
||
<para>To perform similar tasks from the command line, use | ||
<ulink url="http://www.firewalld.org/documentation/man-pages/firewall-cmd.html">firewalld-cmd</ulink>: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please use https:// URLs? (Same above) I checked that they work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had tried https://firewalld.org
and that didn't work. It does with the www
though. Good catch!
<programlisting> | ||
$ <command>sudo firewall-cmd --list-services</command> | ||
dhcpv6-client samba-client mdns ssh cockpit | ||
</programlisting> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe another command to enable a service? That's a very common use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good point.
*/ | ||
.then(function () { | ||
return Array.prototype.slice.call(arguments); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine like that if you prefer; but you use arrow functions everywhere else, and they are more concise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a comment above these lines which explains why this is: cockpit.all
returns results as individual arguments, so we need access to the arguments
object, which doesn't exist in arrow functions.
Also, eslint gets the indentation wrong here. Do you know why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't, I'm afraid. You mean the alignment of the });
, right? If some rule gets it wrong, feel free to disable it for the time being.
|
||
if (!firewall.enabled) { | ||
firewall.services = {}; | ||
firewall.enabledServices = new Set(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if these two lines of initialization shouldn't happen always. Say you get an owner
event while the firewall is enabled, then the code below would only add to the current state, leaving potentially obsolete old state. As long as you don't dispatch a changed
in between initialization and finishing the getServices()
call below, there shouldn't be any actual re-rendering (i. e. screen flicker)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed! Very nice catch, thanks.
|
||
firewall.enable = () => { | ||
return cockpit.all(firewalld_service.enable(), firewalld_service.start()); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arrow function with a single-line return
can be simplified.
if (!this.state.firewall.installed) { | ||
return ( | ||
<EmptyState title={_("Firewall is not available")} icon="fa fa-exclamation-circle"> | ||
<p>{cockpit.format(_("Please install the {0} package"), "firewalld")}</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hah, another nice candidate for on-demand installation :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed!
pkg/networkmanager/interfaces.js
Outdated
$('#networking-firewall-switch').onoff('value', firewall.enabled); | ||
|
||
var n = firewall.enabledServices.size; | ||
var summary = cockpit.ngettext('1 Active Rule', n + ' Active Rules', n); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These strings cannot be translated. You need to use ngettext("$0 Active Rule", "$0 Active Rules", n)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oooooooops
def wait_unit_state(machine, unit, state): | ||
|
||
def active_state(unit): | ||
# don't use `systemctl is-active` here because of https://bugzilla.redhat.com/show_bug.cgi?id=1073481 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please prepend this with HACK:
, so that our hacks stay easily greppable?
def active_state(unit): | ||
# don't use `systemctl is-active` here because of https://bugzilla.redhat.com/show_bug.cgi?id=1073481 | ||
# outputs "ActiveState=state\n" | ||
line = machine.execute("systemctl show -p ActiveState {}".format(unit)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: --value
does not yet exist in current CentOS 7.4. Once that moves to 7.5, we can use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I didn't know about that. Thank you!
wait(lambda: active_state(unit) == state, delay=0.2) | ||
|
||
|
||
@skipImage("firewalld not installed", "rhel-7-5", "centos-7", "fedora-atomic", "rhel-atomic", "debian-stable", "debian-testing", "ubuntu-stable", "ubuntu-1604") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow-up: We should definitively install it on rhel-7-5 and centos-7 at least. So on rhel-8 it's already present apparently, good!
Pass through the `className` property to the outermost div of the Tooltip component. This avoids wrapping the tooltip when using it with positioning classes (such as `pull-right`).
Use onmouseleave instead of onmouseout events, because the latter has different bubbling behavior on Chrome, which doesn't work when the containing element is floating. Also, use onmouseenter instead of onmouseover events to avoid unnecessary re-renders and make capitalization more consistent.
Thanks very much for the review! The ones I've left uncommented are fixed as well. Didn't want to spam you too much ;) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice, great work!
Oops, the test still fails: The screenshot shows that it's literally displayed |
pkg/networkmanager/interfaces.js
Outdated
$('#networking-firewall-switch').onoff('value', firewall.enabled); | ||
|
||
var n = firewall.enabledServices.size; | ||
var summary = cockpit.ngettext('$0 Active Rule', '$0 Active Rules', n); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, this needs to be wrapped in cockpit.format(cockpit.ngettext(...), n)
to actually resolve the $0
.
Introduce a panel on the networking page, which allows toggling the firewall and shows some quick information. Also add a firewall page that shows currently enabled services (as defined by firewalld) and allows adding and removing these services. Custom ports are not yet supported. Fixes #1094 Closes #8032
Added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, thanks!
Corresponding semaphore run: https://semaphoreci.com/cockpit/cockpit/branches/pull-request-8032/builds/20 (wasn't added as test here for some reason) |
First look at a what a firewall module might look like.
It operates on firewalld's default zone and there are no plans to expose zones, as they don't seem to be used very often on servers.
The delete buttons don't work yet, but the UI updates itself when adding and removing services from the default zone with one of:
This will implement the minimal version of the design and iterate in later pull requests.