Skip to content

[th/firewall-cmd-show-overview] show more information with plain firewall-cmd output#1186

Open
thom311 wants to merge 11 commits intofirewalld:mainfrom
thom311:th/firewall-cmd-show-overview
Open

[th/firewall-cmd-show-overview] show more information with plain firewall-cmd output#1186
thom311 wants to merge 11 commits intofirewalld:mainfrom
thom311:th/firewall-cmd-show-overview

Conversation

@thom311
Copy link
Copy Markdown
Collaborator

@thom311 thom311 commented Aug 8, 2023

Let firewall-cmd (without command arguments) show an overview of the current configuration.

The fields that are shown are subject to change and improvement.

Depending on what exactly is requested, this could fix #1062

Example:

ActiveZones:
  public (default)
ActivePolicies:
  allow-host-ipv6
    ingress-zones: ANY
    egress-zones:  HOST

usage: 'firewall-cmd --help' for usage information or see firewall-cmd(1) man page
$ firewall-cmd -v
State: running
Version: 2.0.999
ActiveZones:
  public (default)
Zones:
  FedoraServer
  FedoraWorkstation
  block
  dmz
  drop
  external
  home
  internal
  nm-shared
  trusted
  work
ActivePolicies:
  allow-host-ipv6
    ingress-zones: ANY
    egress-zones:  HOST

usage: 'firewall-cmd --help' for usage information or see firewall-cmd(1) man page

@thom311 thom311 marked this pull request as draft August 8, 2023 13:04
@thom311 thom311 force-pushed the th/firewall-cmd-show-overview branch 4 times, most recently from 9c73298 to 7018d75 Compare August 10, 2023 14:05
Copy link
Copy Markdown
Collaborator

@erig0 erig0 left a comment

Choose a reason for hiding this comment

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

Most of this looks good. I appreciate the clean ups.

Comment thread src/firewall/client.py
Comment thread src/firewall/command.py Outdated
Comment thread src/firewall/client.py Outdated
Comment thread src/firewall/client.py Outdated
@thom311 thom311 force-pushed the th/firewall-cmd-show-overview branch from 7018d75 to fdda506 Compare October 19, 2023 08:53
@thom311 thom311 force-pushed the th/firewall-cmd-show-overview branch from fdda506 to 031d0a2 Compare November 13, 2023 14:30
@thom311 thom311 force-pushed the th/firewall-cmd-show-overview branch from 031d0a2 to 7ae1774 Compare November 21, 2023 15:55
@thom311 thom311 force-pushed the th/firewall-cmd-show-overview branch from 7ae1774 to d4d95e3 Compare December 5, 2023 10:28
The exit() helper (of course) only wraps sys.exit(). However, it's also
*the* blessed way how we can call exit. That means, if you want to add
some printf debugging, this is the place.

There are still many direct calls to sys.exit(). Maybe at the places
where we use FirewallCommand, we should use instead cmd.exit().
@thom311 thom311 force-pushed the th/firewall-cmd-show-overview branch from d4d95e3 to b18da6d Compare February 13, 2024 16:12
@thom311 thom311 marked this pull request as ready for review February 13, 2024 16:12
@thom311 thom311 requested a review from erig0 February 13, 2024 16:12
There are various alternative functions for printing. Some are
more customizable than others. They also have different defaults or
behaviors (e.g. print to stderr instead of stdout).

Add a print() functions, which does it all. This is the basis for all
convenience wrappers.

If you wonder what print_warning() or print_error_msg() does, they all
just call print() with different arguments.

I find this a useful property. Whatever wrapper for the print function
we use, internally they end up at the similar code. The wrappers only
exist for selecting different default values (or internal parameters) to
the print() function.

Also add an "eol" argument to make that configurable. That may be the
main reason for this patch. The "eol" argument is a fundamental tweak,
that is potentially useful to any of the print variants. I wouldn't want
to add the "eol" argument to any convenience wrappers like
print_error_msg(). In particular, I wouldn't want to (re) implement the
logic differently. A user who needs to use a different "eol", can now
call the print() function directly. By choosing the right arguments,
they also can be sure that what they do behaves in a similar manner as
the wrapper functions.

Despite the large number of arguments to print(), they still make sense
and have useful default values.
…nnection_established()

There is no conceivable reason why the creation of the D-Bus proxy
objects should fail, after we successfully got a D-Bus object.

Move the code out of the try-except block, which would hide errors.
This code should not fail, and if it would, we want to notice.
…tate

Only emit the signals that connection was established/lost, if
there is an actual change. In other words, make (some parts of)
_connection_{established,lost}() idempotent.

Also, drop comments like

        # connection established
        self._connection_established()
Clearing "dbus_obj" and "fw_policies" was forgotten, and "fw_helper" is
unused.

Also drop @handle_exceptions. This method is not supposed to throw
exceptions, and there is nothing to handle.
dbus.exceptions.DBusException does not always have a valid dbus_name. Check
for that, otherwise we get another exception while handling the exception.
… not running

When the daemon is not running, then bus.get_object() in
_connection_established() fails with a D-Bus exception
"org.freedesktop.DBus.Error.ServiceUnknown" and message "The name is not
activatable" (when using d-bus broker).

Consequently, self.dbus_obj and attributes like self.fw_properties are
left at None.

That means, if we call `firewall-cmd`, we get an attribute error and an
ugly backtrace:

  $ firewall-cmd
  Exception 'NoneType' object has no attribute 'Get'
  Error: Traceback (most recent call last):
    File "/usr/lib/python3.11/site-packages/firewall/client.py", line 50, in _impl
      return func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
    File "/usr/lib/python3.11/site-packages/firewall/client.py", line 3150, in get_property
      return dbus_to_python(self.fw_properties.Get(
                            ^^^^^^^^^^^^^^^^^^^^^^
  AttributeError: 'NoneType' object has no attribute 'Get'

Try to handle that case better by raising a better exception.

What we instead could do, is calling

  self.dbus_obj = self.bus.get_object(config.dbus.DBUS_INTERFACE,
                                      config.dbus.DBUS_PATH,
                                      follow_name_owner_changes=True)

then we would always be able to create D-Bus proxy objects.  However,
that affects the tracking of the well-known name, and I think we would
need to adjust our _connection_established() handling. It's not clear to
me, that that would really be preferable, at least not without further
changes.

Instead, add for attributes like `self.fw_properties` a getter
`self._get_fw_properties()`, which will raise a DBusException when the
proxy is None. Update most places that accessed the attribute to use the
new getter functions.

This gives:

  $ firewall-cmd
  Error: org.freedesktop.DBus.Error.NameHasNoOwner: firewalld is not running

Another benefit is, that you can catch DBusException when calling those
D-Bus methods, and don't need to consider that they might fail with an
AttributeError (which would indicate some internal bug).
This essentially allows to assert that what we request, has the expected
type.
The methods like FirewallClient.get_property() are decorated by @handle_exceptions.
Users (like `firewall-cmd`) hook into those exceptions and will abort the process.

Add FirewallClient.raw_*() helpers to access the underlying functionality.
When calling these, we can handle the exceptions ourselves.
@thom311 thom311 force-pushed the th/firewall-cmd-show-overview branch from b18da6d to bf67676 Compare February 13, 2024 18:51
…ll-cmd`

Make calling `firewall-cmd` without arguments show some more useful information.
Similar to `nmcli`, `networkctl`, and `systemctl`, which give an overview.

The shown data is subject to change and further improvements.

- drop the "No options specified" line, it's not interesting and wastes
  space.
- don't exit with error code 2 but with success (zero). Unless firewalld is not
  running, in which case exit with 252 (NOT_RUNNING) or 253 (NOT_AUTHORIZED).
- previously, errors we handled by @handle_exceptions decorator. In
  particular, that handled the not-running and not-authorized cases.
  Now, that handling is bypassed, aiming to do something more useful.

Honors "--verbose" to show more information. It also overrides "--quiet"
handling. Currently passing "--quiet" has no additional effect, but we
could imagine to show even less information (but still more than none).

Example:

  $ firewall-cmd -v
  State: running
  Version: 2.0.999
  ActiveZones:
    public (default)
  Zones:
    FedoraServer
    FedoraWorkstation
    block
    dmz
    drop
    external
    home
    internal
    nm-shared
    trusted
    work
  ActivePolicies:
    allow-host-ipv6
      ingress-zones: ANY
      egress-zones:  HOST

  usage: 'firewall-cmd --help' for usage information or see firewall-cmd(1) man page
@thom311 thom311 force-pushed the th/firewall-cmd-show-overview branch from bf67676 to 5989748 Compare February 14, 2024 09:04
Comment thread src/firewall/client.py
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I like this idea, but instead of adding things like _get_fw_ipset() I'd rather override __getattribute__(). I think it's more likely that new code written may do self.fw and not realize there are these helper functions. It also avoids changing all these call sites.

def __getattribute__(self, attr):
    value = super().__getattribute__(attr)
    if attr in ["fw", "fw_ipset" ... ] and value is None:
        raise dbus.exceptions.DBusException(
            "org.freedesktop.DBus.Error.NameHasNoOwner: firewalld is not running"
        )
    return value

Comment thread src/firewall/client.py
def getZoneNames(self):
return self.raw_getZoneNames()

def raw_getZoneNames(self):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These are only used for show_state_and_exit(). If one fails I would expect them all to fail. So in show_state_and_exit() couldn't we have a single failure scenario that prints "unknown" for all things. i.e. fetch all the days in try, and if you get an exception set all the printed info to "unknown".

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.

List of firewall-cmd query commands is incomplete (in 0.9.3)

2 participants