From a0a86d098283833e7f13e6adc86c1f2d7915cf9b Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Wed, 19 Mar 2025 10:23:45 +0000 Subject: [PATCH] Add Info.FirewallBackend Report FirewallBackend in "docker info". It's currently "iptables" or "iptables+firewalld" on Linux, and omitted on Windows. Signed-off-by: Rob Murray --- api/server/router/system/system_routes.go | 4 ++ api/swagger.yaml | 33 +++++++++++++++++ api/types/system/info.go | 9 +++++ daemon/info.go | 8 ++++ docs/api/version-history.md | 2 + integration/networking/firewall_linux_test.go | 37 +++++++++++++++++++ libnetwork/controller_linux.go | 13 +++++++ libnetwork/controller_others.go | 7 ++++ libnetwork/iptables/firewalld.go | 6 ++- testutil/daemon/daemon.go | 7 ++++ 10 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 integration/networking/firewall_linux_test.go diff --git a/api/server/router/system/system_routes.go b/api/server/router/system/system_routes.go index 8213780aac..9324a8e643 100644 --- a/api/server/router/system/system_routes.go +++ b/api/server/router/system/system_routes.go @@ -106,6 +106,10 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht info.RegistryConfig.AllowNondistributableArtifactsCIDRs = []*registry.NetIPNet{} info.RegistryConfig.AllowNondistributableArtifactsHostnames = []string{} } + if versions.LessThan(version, "1.49") { + // FirewallBackend field introduced in API v1.49. + info.FirewallBackend = nil + } // TODO(thaJeztah): Expected commits are deprecated, and should no longer be set in API 1.49. info.ContainerdCommit.Expected = info.ContainerdCommit.ID //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.49. diff --git a/api/swagger.yaml b/api/swagger.yaml index b5f9eaf210..00e2ab5885 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -6856,6 +6856,8 @@ definitions: description: "The network pool size" type: "integer" example: "24" + FirewallBackend: + $ref: "#/definitions/FirewallInfo" Warnings: description: | List of warnings / informational messages about missing features, or @@ -6939,6 +6941,37 @@ definitions: default: "plugins.moby" example: "plugins.moby" + FirewallInfo: + description: | + Information about the daemon's firewalling configuration. + + This field is currently only used on Linux, and omitted on other platforms. + type: "object" + x-nullable: true + properties: + Driver: + description: | + The name of the firewall backend driver. + type: "string" + example: "nftables" + Info: + description: | + Information about the firewall backend, provided as + "label" / "value" pairs. + +


+ + > **Note**: The information returned in this field, including the + > formatting of values and labels, should not be considered stable, + > and may change without notice. + type: "array" + items: + type: "array" + items: + type: "string" + example: + - ["ReloadedAt", "2025-01-01T00:00:00Z"] + # PluginsInfo is a temp struct holding Plugins name # registered with docker daemon. It is used by Info struct PluginsInfo: diff --git a/api/types/system/info.go b/api/types/system/info.go index 8a2444da28..7320582950 100644 --- a/api/types/system/info.go +++ b/api/types/system/info.go @@ -73,6 +73,7 @@ type Info struct { SecurityOptions []string ProductLicense string `json:",omitempty"` DefaultAddressPools []NetworkAddressPool `json:",omitempty"` + FirewallBackend *FirewallInfo `json:"FirewallBackend,omitempty"` CDISpecDirs []string Containerd *ContainerdInfo `json:",omitempty"` @@ -151,3 +152,11 @@ type NetworkAddressPool struct { Base string Size int } + +// FirewallInfo describes the firewall backend. +type FirewallInfo struct { + // Driver is the name of the firewall backend driver. + Driver string `json:"Driver"` + // Info is a list of label/value pairs, containing information related to the firewall. + Info [][2]string `json:"Info,omitempty"` +} diff --git a/daemon/info.go b/daemon/info.go index ebc3fa280c..3c38c49afb 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -93,6 +93,7 @@ func (daemon *Daemon) SystemInfo(ctx context.Context) (*system.Info, error) { daemon.fillSecurityOptions(v, sysInfo, &cfg.Config) daemon.fillLicense(v) daemon.fillDefaultAddressPools(ctx, v, &cfg.Config) + daemon.fillFirewallInfo(v) return v, nil } @@ -284,6 +285,13 @@ func (daemon *Daemon) fillDefaultAddressPools(ctx context.Context, v *system.Inf } } +func (daemon *Daemon) fillFirewallInfo(v *system.Info) { + if daemon.netController == nil { + return + } + v.FirewallBackend = daemon.netController.FirewallBackend() +} + func hostName(ctx context.Context) string { ctx, span := tracing.StartSpan(ctx, "hostName") defer span.End() diff --git a/docs/api/version-history.md b/docs/api/version-history.md index a78bba88ee..45cc07e10a 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -21,6 +21,8 @@ keywords: "API, Docker, rcli, REST, documentation" encoded OCI Platform type) allowing to specify a platform of the multi-platform image to inspect. This option is mutually exclusive with the `manifests` option. +* `GET /info` now returns a `FirewallBackend` containing information about + the daemon's firewalling configuration. ## v1.48 API changes diff --git a/integration/networking/firewall_linux_test.go b/integration/networking/firewall_linux_test.go new file mode 100644 index 0000000000..9820a5ffbc --- /dev/null +++ b/integration/networking/firewall_linux_test.go @@ -0,0 +1,37 @@ +package networking + +import ( + "testing" + + "github.com/docker/docker/client" + "github.com/docker/docker/internal/testutils/networking" + "github.com/docker/docker/testutil/request" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestInfoFirewallBackend(t *testing.T) { + ctx := setupTest(t) + c := testEnv.APIClient() + + expDriver := "iptables" + if !testEnv.IsRootless() && networking.FirewalldRunning() { + expDriver = "iptables+firewalld" + } + info, err := c.Info(ctx) + assert.NilError(t, err) + assert.Assert(t, info.FirewallBackend != nil, "expected firewall backend in info response") + t.Log("FirewallBackend: Driver:", info.FirewallBackend.Driver) + for _, kv := range info.FirewallBackend.Info { + t.Logf("FirewallBackend: %s: %s", kv[0], kv[1]) + } + assert.Check(t, is.Equal(info.FirewallBackend.Driver, expDriver)) + + // Check FirewallBackend is omitted for API <= 1.48. + t.Run("api 1.48", func(t *testing.T) { + c148 := request.NewAPIClient(t, client.WithVersion("1.48")) + info148, err := c148.Info(ctx) + assert.NilError(t, err) + assert.Check(t, is.Nil(info148.FirewallBackend)) + }) +} diff --git a/libnetwork/controller_linux.go b/libnetwork/controller_linux.go index 502c9c22d1..45c5dcd9c3 100644 --- a/libnetwork/controller_linux.go +++ b/libnetwork/controller_linux.go @@ -6,12 +6,25 @@ import ( "sync" "github.com/containerd/log" + "github.com/docker/docker/api/types/system" "github.com/docker/docker/libnetwork/iptables" "github.com/docker/docker/libnetwork/netlabel" "github.com/docker/docker/libnetwork/options" "github.com/docker/docker/libnetwork/osl" ) +// FirewallBackend returns the name of the firewall backend for "docker info". +func (c *Controller) FirewallBackend() *system.FirewallInfo { + usingFirewalld, err := iptables.UsingFirewalld() + if err != nil { + return nil + } + if usingFirewalld { + return &system.FirewallInfo{Driver: "iptables+firewalld"} + } + return &system.FirewallInfo{Driver: "iptables"} +} + // enabledIptablesVersions returns the iptables versions that are enabled // for the controller. func (c *Controller) enabledIptablesVersions() []iptables.IPVersion { diff --git a/libnetwork/controller_others.go b/libnetwork/controller_others.go index d837c7cf3d..c8c8cc9ed2 100644 --- a/libnetwork/controller_others.go +++ b/libnetwork/controller_others.go @@ -2,6 +2,13 @@ package libnetwork +import "github.com/docker/docker/api/types/system" + +// FirewallBackend returns the name of the firewall backend for "docker info". +func (c *Controller) FirewallBackend() *system.FirewallInfo { + return nil +} + // enabledIptablesVersions is a no-op on non-Linux systems. func (c *Controller) enabledIptablesVersions() []any { return nil diff --git a/libnetwork/iptables/firewalld.go b/libnetwork/iptables/firewalld.go index 54ee975ddd..aa11a34897 100644 --- a/libnetwork/iptables/firewalld.go +++ b/libnetwork/iptables/firewalld.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/containerd/log" + "github.com/docker/docker/pkg/rootless" dbus "github.com/godbus/dbus/v5" "github.com/pkg/errors" ) @@ -40,7 +41,10 @@ var ( // passthrough interface. The error return is non-nil if the status cannot be // determined because the initialisation function has not been called. func UsingFirewalld() (bool, error) { - if !firewalldInitCalled { + // If called before startup has completed, the firewall backend is unknown. + // But, if running rootless, the init function is not called because + // firewalld will be running in the host's netns, not in rootlesskit's. + if !firewalldInitCalled && !rootless.RunningWithRootlessKit() { return false, fmt.Errorf("iptables.firewalld is not initialised") } return firewalldRunning, nil diff --git a/testutil/daemon/daemon.go b/testutil/daemon/daemon.go index d24e4b329f..d83f62556c 100644 --- a/testutil/daemon/daemon.go +++ b/testutil/daemon/daemon.go @@ -982,6 +982,13 @@ func (d *Daemon) Info(t testing.TB) system.Info { return info } +func (d *Daemon) FirewallBackendDriver(t testing.TB) string { + t.Helper() + info := d.Info(t) + assert.Assert(t, info.FirewallBackend != nil, "no firewall backend reported") + return info.FirewallBackend.Driver +} + // TamperWithContainerConfig modifies the on-disk config of a container. func (d *Daemon) TamperWithContainerConfig(t testing.TB, containerID string, tamper func(*container.Container)) { t.Helper()