engine: resources: Add firewalld resource
This is a simple firewalld resource to make the seamless opening of firewall ports in standalone laptop (and other) environments easy.
This commit is contained in:
651
engine/resources/firewalld.go
Normal file
651
engine/resources/firewalld.go
Normal file
@@ -0,0 +1,651 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
"github.com/purpleidea/mgmt/engine/traits"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/vars"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/google/nftables"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// TODO: Should we split this into firewalld:service and firewalld:port?
|
||||
engine.RegisterResource(KindFirewalld, func() engine.Res { return &FirewalldRes{} })
|
||||
|
||||
// const.res.firewalld.state.exists = "exists"
|
||||
// const.res.firewalld.state.absent = "absent"
|
||||
vars.RegisterResourceParams(KindFirewalld, map[string]map[string]func() interfaces.Var{
|
||||
ParamFileState: {
|
||||
FirewalldStateExists: func() interfaces.Var {
|
||||
return &types.StrValue{
|
||||
V: FirewalldStateExists,
|
||||
}
|
||||
},
|
||||
FirewalldStateAbsent: func() interfaces.Var {
|
||||
return &types.StrValue{
|
||||
V: FirewalldStateAbsent,
|
||||
}
|
||||
},
|
||||
// TODO: consider removing this field entirely
|
||||
"undefined": func() interfaces.Var {
|
||||
return &types.StrValue{
|
||||
V: FirewalldStateUndefined, // empty string
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
// KindFirewalld is the kind string used to identify this resource.
|
||||
KindFirewalld = "firewalld"
|
||||
|
||||
// ParamFirewalldState is the name of the state field parameter.
|
||||
ParamFirewalldState = "state"
|
||||
|
||||
// FirewalldStateExists is the string that represents that the service,
|
||||
// or port, or other setting should be present.
|
||||
FirewalldStateExists = "exists"
|
||||
|
||||
// FirewalldStateAbsent is the string that represents that the service,
|
||||
// or port, or other setting should not exist.
|
||||
FirewalldStateAbsent = "absent"
|
||||
|
||||
// FirewalldStateUndefined means the file state has not been specified.
|
||||
// TODO: consider moving to *string and express this state as a nil.
|
||||
FirewalldStateUndefined = ""
|
||||
|
||||
// ErrInvalidZone is a firewalld error from dbus.
|
||||
ErrInvalidZone = engine.Error("invalid zone")
|
||||
|
||||
// ErrInvalidPort is a firewalld error from dbus.
|
||||
ErrInvalidPort = engine.Error("invalid port")
|
||||
|
||||
// ErrInvalidService is a firewalld error from dbus.
|
||||
ErrInvalidService = engine.Error("invalid service")
|
||||
|
||||
// ErrInvalidProtocol is a firewalld error from dbus.
|
||||
ErrInvalidProtocol = engine.Error("invalid protocol")
|
||||
|
||||
// ErrInvalidCommand is a firewalld error from dbus.
|
||||
ErrInvalidCommand = engine.Error("invalid command")
|
||||
|
||||
// ErrMissingProtocol is a firewalld error from dbus.
|
||||
ErrMissingProtocol = engine.Error("missing protocol")
|
||||
|
||||
// ErrAlreadyEnabled is a firewalld error from dbus.
|
||||
ErrAlreadyEnabled = engine.Error("already enabled")
|
||||
|
||||
// ErrNotEnabled is a firewalld error from dbus.
|
||||
ErrNotEnabled = engine.Error("not enabled")
|
||||
|
||||
firewalld1Path = dbus.ObjectPath("/org/fedoraproject/FirewallD1")
|
||||
firewalld1Iface = "org.fedoraproject.FirewallD1"
|
||||
)
|
||||
|
||||
// FirewalldRes is a simple resource to interact with the firewalld service. It
|
||||
// is not a replacement for a modern, robust tool like `shorewall`, but it has
|
||||
// its uses such as for small, desktop use cases. The API of this resource might
|
||||
// change to either add new features, split this into multiple resources, or to
|
||||
// optimize the execution if it turns out to be too expensive to run large
|
||||
// amounts of these as-is. The name variable currently has no useful purpose.
|
||||
// Keep in mind that this resource requires root permissions to be able change
|
||||
// the firewall settings and to monitor for changes. The change detection uses
|
||||
// the nftables monitor facility.
|
||||
type FirewalldRes struct {
|
||||
traits.Base // add the base methods without re-implementation
|
||||
|
||||
// XXX: add traits.Reversible and make this undo itself on removal
|
||||
|
||||
init *engine.Init
|
||||
|
||||
// Zone is the name of the zone to manage. If unspecified, we will
|
||||
// attempt to get the default zone automatically. In this situation, it
|
||||
// is possible that this default changes over time if it is acted upon
|
||||
// by external tools that use firewalld.
|
||||
Zone string `lang:"zone" yaml:"zone"`
|
||||
|
||||
// State is the desired state.
|
||||
State string `lang:"state" yaml:"state"`
|
||||
|
||||
// Services are the list of services to manage to the desired state.
|
||||
// These are single lower case strings like `dhcp`, and `tftp`.
|
||||
Services []string `lang:"services" yaml:"services"`
|
||||
|
||||
// Ports are the list of port/protocol combinations to manage to the
|
||||
// desired state. These are strings of port number (slash) protocol like
|
||||
// `4280/tcp` and `38/udp`.
|
||||
Ports []string `lang:"ports" yaml:"ports"`
|
||||
|
||||
// TODO: add a boolean setting for persistence (across reboots)
|
||||
// TODO: this is the equivalent of `firewall-cmd --permanent`
|
||||
//Permanent bool `lang:"permanent" yaml:"permanent"`
|
||||
|
||||
zone string // cached name of the zone we're managing
|
||||
call callFunc
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// Default returns some sensible defaults for this resource.
|
||||
func (obj *FirewalldRes) Default() engine.Res {
|
||||
return &FirewalldRes{}
|
||||
}
|
||||
|
||||
// Validate if the params passed in are valid data.
|
||||
func (obj *FirewalldRes) Validate() error {
|
||||
// Check that we're running as root or with enough capabilities.
|
||||
// XXX: check that we have enough capabilities if we're not root
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "error looking up current user")
|
||||
}
|
||||
if currentUser.Uid != "0" {
|
||||
// TODO: Technically we only need perms to watch state and to
|
||||
// change things, so without this we could just error entirely
|
||||
// if this were only used as a change detection tool.
|
||||
return fmt.Errorf("running as root is required for firewalld")
|
||||
}
|
||||
|
||||
if obj.State != "" {
|
||||
if obj.State != FirewalldStateExists && obj.State != FirewalldStateAbsent {
|
||||
return fmt.Errorf("invalid state: %s", obj.State)
|
||||
}
|
||||
}
|
||||
|
||||
for _, x := range obj.Services {
|
||||
// TODO: we could check services from a list
|
||||
if x == "" {
|
||||
return fmt.Errorf("service is empty")
|
||||
}
|
||||
}
|
||||
|
||||
for _, x := range obj.Ports {
|
||||
split := strings.Split(x, "/")
|
||||
if len(split) != 2 {
|
||||
return fmt.Errorf("port/protocol was invalid: %s", x)
|
||||
}
|
||||
|
||||
if num, err := strconv.Atoi(split[0]); err != nil {
|
||||
return err
|
||||
} else if num <= 0 {
|
||||
return fmt.Errorf("invalid number: %d", num)
|
||||
}
|
||||
|
||||
// TODO: we could check protocols from a list
|
||||
if split[1] == "" {
|
||||
return fmt.Errorf("protocol is empty")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init runs some startup code for this resource.
|
||||
func (obj *FirewalldRes) Init(init *engine.Init) error {
|
||||
obj.init = init // save for later
|
||||
|
||||
obj.wg = &sync.WaitGroup{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup is run by the engine to clean up after the resource is done.
|
||||
func (obj *FirewalldRes) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch is the primary listener for this resource and it outputs events. This
|
||||
// could have been implemented with an exec of `/usr/sbin/nft --json monitor`,
|
||||
// but the wrapped polkit and difficulty getting permissions using capabilities
|
||||
// and without using full root, was challenging. This is cleaner too.
|
||||
func (obj *FirewalldRes) Watch(ctx context.Context) error {
|
||||
defer obj.wg.Wait()
|
||||
|
||||
// `sudo setcap CAP_NET_ADMIN=+eip mgmt` seems to let us avoid root
|
||||
opts := []nftables.ConnOption{}
|
||||
conn, err := nftables.New(opts...) // (*nftables.Conn, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// XXX: filter out events we don't care about using WithMonitorObject
|
||||
//opt := nftables.WithMonitorObject(???)
|
||||
mopts := []nftables.MonitorOption{}
|
||||
//mopts = append(mopts, opt)
|
||||
monitor := nftables.NewMonitor(mopts...) // *nftables.Monitor
|
||||
defer monitor.Close()
|
||||
events, err := conn.AddMonitor(monitor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj.init.Running() // when started, notify engine that we're running
|
||||
|
||||
var send = false // send event?
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-events: // &nftables.MonitorEvent
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if event.Error != nil {
|
||||
return errwrap.Wrapf(err, "monitor err")
|
||||
}
|
||||
if obj.init.Debug {
|
||||
//obj.init.Logf("event: %#v", event)
|
||||
obj.init.Logf("event type: %v", event.Type)
|
||||
//obj.init.Logf("event data: %+v", event.Data)
|
||||
}
|
||||
|
||||
send = true
|
||||
|
||||
case <-ctx.Done(): // closed by the engine to signal shutdown
|
||||
return nil
|
||||
}
|
||||
|
||||
// do all our event sending all together to avoid duplicate msgs
|
||||
if send {
|
||||
send = false
|
||||
obj.init.Event() // notify engine of an event (this can block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckApply checks the resource state and applies the resource if the bool
|
||||
// input is true. It returns error info and if the state check passed or not.
|
||||
func (obj *FirewalldRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
|
||||
conn, err := util.SystemBusPrivateUsable()
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf(err, "failed to connect to the private system bus")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
firewalldObject := conn.Object(firewalld1Iface, firewalld1Path) // TODO: can we reuse this?
|
||||
var flags dbus.Flags // none set
|
||||
obj.call = func(ctx context.Context, method string, args ...interface{}) *dbus.Call {
|
||||
fullMethod := firewalld1Iface + method // eg: ".getDefaultZone"
|
||||
return firewalldObject.CallWithContext(ctx, fullMethod, flags, args...)
|
||||
}
|
||||
|
||||
// NOTE: The default zone might change over time by external users...
|
||||
obj.zone = obj.Zone // default to this
|
||||
if obj.Zone == "" {
|
||||
if err := obj.call(ctx, ".getDefaultZone").Store(&obj.zone); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if obj.zone == "" {
|
||||
return false, fmt.Errorf("unexpected empty zone")
|
||||
}
|
||||
obj.init.Logf("zone: %s\n", obj.zone)
|
||||
}
|
||||
|
||||
checkOK := true
|
||||
|
||||
// This ordering doesn't currently matter, but might change if we find
|
||||
// any sort of relevant relationship.
|
||||
for _, x := range obj.Services {
|
||||
if c, err := obj.serviceCheckApply(ctx, apply, x); err != nil {
|
||||
return false, err
|
||||
} else if !c {
|
||||
checkOK = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, x := range obj.Ports {
|
||||
if c, err := obj.portCheckApply(ctx, apply, x); err != nil {
|
||||
return false, err
|
||||
} else if !c {
|
||||
checkOK = false
|
||||
}
|
||||
}
|
||||
|
||||
return checkOK, nil
|
||||
}
|
||||
|
||||
// does the equivalent of: `firewall-cmd --zone=<zone> --list-services` and:
|
||||
// `firewall-cmd --zone=<zone> --add-service=<service>` and: `firewall-cmd
|
||||
// --zone=<zone> --remove-service=<service>`.
|
||||
func (obj *FirewalldRes) serviceCheckApply(ctx context.Context, apply bool, service string) (bool, error) {
|
||||
// .zone.getServices(s: zone) -> as
|
||||
var services []string
|
||||
args := []interface{}{obj.zone}
|
||||
if err := obj.call(ctx, ".zone.getServices", args...).Store(&services); err != nil {
|
||||
if parseError(err) == ErrInvalidZone {
|
||||
obj.init.Logf("did the zone change?") // two managers!
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("services: %+v", services)
|
||||
}
|
||||
|
||||
found := util.StrInList(service, services)
|
||||
exists := obj.State != FirewalldStateAbsent // other states mean "exists"
|
||||
|
||||
if exists && found || !exists && !found {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !apply {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !found {
|
||||
// add it
|
||||
// .zone.addService(s: zone, s: service, i: timeout) -> s
|
||||
timeout := 0 // TODO: what should this be?
|
||||
var output string
|
||||
addArgs := []interface{}{obj.zone, service, timeout}
|
||||
if err := obj.call(ctx, ".zone.addService", addArgs...).Store(&output); err != nil && parseError(err) != ErrAlreadyEnabled {
|
||||
return false, err
|
||||
}
|
||||
obj.init.Logf("service added: %s", service)
|
||||
if output != obj.zone {
|
||||
// programming error
|
||||
return false, fmt.Errorf("dbus API inconsistency")
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// remove it
|
||||
// .zone.removeService(s: zone, s: service) -> s
|
||||
//timeout := 0
|
||||
var output string
|
||||
removeArgs := []interface{}{obj.zone, service}
|
||||
if err := obj.call(ctx, ".zone.removeService", removeArgs...).Store(&output); err != nil && parseError(err) != ErrNotEnabled {
|
||||
return false, err
|
||||
}
|
||||
obj.init.Logf("service removed: %s", service)
|
||||
if output != obj.zone {
|
||||
// programming error
|
||||
return false, fmt.Errorf("dbus API inconsistency")
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// does the equivalent of: `firewall-cmd --zone=<zone> --list-ports` and:
|
||||
// `firewall-cmd --zone=<zone> --add-port=4280/tcp` and: `firewall-cmd
|
||||
// --zone=<zone> --remove-port=4280/tcp`.
|
||||
func (obj *FirewalldRes) portCheckApply(ctx context.Context, apply bool, pp string) (bool, error) {
|
||||
split := strings.Split(pp, "/")
|
||||
if len(split) != 2 { // should already be checked in Validate
|
||||
return false, fmt.Errorf("port/protocol was invalid: %s", pp)
|
||||
}
|
||||
port, err := strconv.Atoi(split[0])
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if port <= 0 {
|
||||
return false, fmt.Errorf("invalid number: %d", port)
|
||||
}
|
||||
protocol := split[1]
|
||||
|
||||
// .zone.getPorts(s: zone) -> aas
|
||||
var ports [][]string
|
||||
args := []interface{}{obj.zone}
|
||||
if err := obj.call(ctx, ".zone.getPorts", args...).Store(&ports); err != nil {
|
||||
if parseError(err) == ErrInvalidZone {
|
||||
obj.init.Logf("did the zone change?") // two managers!
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("ports: %+v", ports)
|
||||
}
|
||||
|
||||
found := false
|
||||
for i, x := range ports {
|
||||
// eg: ["1025-65535", "udp"]
|
||||
if len(x) != 2 {
|
||||
return false, fmt.Errorf("unexpected ports length (%d), got: %v", len(x), x)
|
||||
}
|
||||
|
||||
// x[0] is range or single value, eg: "1025-65535" OR "42"
|
||||
// x[1] is proto like "tcp" or "udp"
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("rule %d: %s/%s", i, x[0], x[1])
|
||||
}
|
||||
|
||||
if protocol != x[1] {
|
||||
continue // not found (not us)
|
||||
}
|
||||
|
||||
// does the port number match?
|
||||
split := strings.Split(x[0], "-") // split the range
|
||||
if len(split) != 1 && len(split) != 2 {
|
||||
return false, fmt.Errorf("unexpected ports format (%d), got: %v", len(split), split)
|
||||
}
|
||||
if len(split) == 1 { // standalone single value
|
||||
if num, err := strconv.Atoi(split[0]); err != nil {
|
||||
return false, err // programming error
|
||||
} else if num != port {
|
||||
continue // not found
|
||||
}
|
||||
found = true // yay!
|
||||
break
|
||||
}
|
||||
//if len(split) == 2
|
||||
lhs, err := strconv.Atoi(split[0])
|
||||
if err != nil {
|
||||
return false, err // programming error
|
||||
}
|
||||
rhs, err := strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
return false, err // programming error
|
||||
}
|
||||
|
||||
if lhs <= port && port <= rhs { // ranges are inclusive on both boundss
|
||||
found = true // yay!
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
exists := obj.State != FirewalldStateAbsent // other states mean "exists"
|
||||
|
||||
if exists && found || !exists && !found {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !apply {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !found {
|
||||
// add it
|
||||
// .zone.addPort(s: zone, s: port, s: protocol, i: timeout) -> s
|
||||
timeout := 0 // TODO: what should this be?
|
||||
var output string
|
||||
addArgs := []interface{}{obj.zone, strconv.Itoa(port), protocol, timeout}
|
||||
if err := obj.call(ctx, ".zone.addPort", addArgs...).Store(&output); err != nil && parseError(err) != ErrAlreadyEnabled {
|
||||
return false, err
|
||||
}
|
||||
obj.init.Logf("port added: %s", pp)
|
||||
if output != obj.zone {
|
||||
// programming error
|
||||
return false, fmt.Errorf("dbus API inconsistency")
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// remove it
|
||||
// .zone.removePort(s: zone, s: port, s: protocol) -> s
|
||||
//timeout := 0
|
||||
var output string
|
||||
removeArgs := []interface{}{obj.zone, strconv.Itoa(port), protocol}
|
||||
if err := obj.call(ctx, ".zone.removePort", removeArgs...).Store(&output); err != nil && parseError(err) != ErrNotEnabled {
|
||||
return false, err
|
||||
}
|
||||
obj.init.Logf("port removed: %s", pp)
|
||||
if output != obj.zone {
|
||||
// programming error
|
||||
return false, fmt.Errorf("dbus API inconsistency")
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Cmp compares two resources and returns an error if they are not equivalent.
|
||||
func (obj *FirewalldRes) Cmp(r engine.Res) error {
|
||||
// we can only compare FirewalldRes to others of the same resource kind
|
||||
res, ok := r.(*FirewalldRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("not a %s", obj.Kind())
|
||||
}
|
||||
|
||||
if obj.Zone != res.Zone {
|
||||
return fmt.Errorf("the Zone differs")
|
||||
}
|
||||
if obj.State != res.State {
|
||||
return fmt.Errorf("the State differs")
|
||||
}
|
||||
|
||||
if len(obj.Services) != len(res.Services) {
|
||||
return fmt.Errorf("the Services differ")
|
||||
}
|
||||
for i, x := range obj.Services {
|
||||
if x != res.Services[i] {
|
||||
return fmt.Errorf("the service at index %d differs", i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(obj.Ports) != len(res.Ports) {
|
||||
return fmt.Errorf("the Ports differ")
|
||||
}
|
||||
for i, x := range obj.Ports {
|
||||
if x != res.Ports[i] {
|
||||
return fmt.Errorf("the port/protocol at index %d differs", i)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consider adding this setting
|
||||
//if obj.Permanent != res.Permanent {
|
||||
// return fmt.Errorf("the Permanent differs")
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
|
||||
// primarily useful for setting the defaults.
|
||||
func (obj *FirewalldRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type rawRes FirewalldRes // indirection to avoid infinite recursion
|
||||
|
||||
def := obj.Default() // get the default
|
||||
res, ok := def.(*FirewalldRes) // put in the right format
|
||||
if !ok {
|
||||
return fmt.Errorf("could not convert to FirewalldRes")
|
||||
}
|
||||
raw := rawRes(*res) // convert; the defaults go here
|
||||
|
||||
if err := unmarshal(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*obj = FirewalldRes(raw) // restore from indirection with type conversion!
|
||||
return nil
|
||||
}
|
||||
|
||||
// callFunc is a helper func for simplifying the making of dbus calls.
|
||||
type callFunc func(ctx context.Context, method string, args ...interface{}) *dbus.Call
|
||||
|
||||
// parseError converts a returned error into one of our error constants if it
|
||||
// matches. If it doesn't match, then it passes the data through. This is a
|
||||
// useful alternative mechanism to inline string matching on the returned error.
|
||||
func parseError(err error) error {
|
||||
if err == nil {
|
||||
return nil // passthrough
|
||||
}
|
||||
dbusError, ok := err.(dbus.Error)
|
||||
if !ok {
|
||||
return err // passthrough
|
||||
}
|
||||
|
||||
if s := firewalld1Iface + ".Exception"; dbusError.Name != s {
|
||||
return err // passthrough
|
||||
}
|
||||
|
||||
// eg:
|
||||
// dbus.Error{
|
||||
// Name:"org.fedoraproject.FirewallD1",
|
||||
// Body:[]interface {}{
|
||||
// "NOT_ENABLED: '4280:tcp' not in 'FedoraWorkstation'",
|
||||
// },
|
||||
//}
|
||||
if len(dbusError.Body) != 1 { // TODO: does this happen?
|
||||
return err // passthrough
|
||||
}
|
||||
|
||||
body, ok := (dbusError.Body[0]).(string)
|
||||
if !ok {
|
||||
return err // passthrough
|
||||
}
|
||||
|
||||
// eg: "NOT_ENABLED: '4280:tcp' not in 'FedoraWorkstation'"
|
||||
sep := ": " // the first colon space after the error name
|
||||
split := strings.Split(body, sep)
|
||||
//other := strings.Join(split[1:], sep) // remaining data (detailed reason)
|
||||
switch split[0] {
|
||||
case "INVALID_ZONE":
|
||||
return ErrInvalidZone
|
||||
case "INVALID_PORT":
|
||||
return ErrInvalidPort
|
||||
case "INVALID_SERVICE":
|
||||
return ErrInvalidService
|
||||
case "INVALID_PROTOCOL":
|
||||
return ErrInvalidProtocol
|
||||
case "INVALID_COMMAND":
|
||||
return ErrInvalidCommand
|
||||
case "MISSING_PROTOCOL":
|
||||
return ErrMissingProtocol
|
||||
case "ALREADY_ENABLED": // we tried to add it but it was already there
|
||||
return ErrAlreadyEnabled
|
||||
case "NOT_ENABLED": // we tried to remove it but it was already gone
|
||||
return ErrNotEnabled
|
||||
}
|
||||
|
||||
return err // passthrough
|
||||
}
|
||||
9
examples/lang/firewalld0.mcl
Normal file
9
examples/lang/firewalld0.mcl
Normal file
@@ -0,0 +1,9 @@
|
||||
firewalld "misc" { # name is irrelevant
|
||||
services => [
|
||||
"dhcp",
|
||||
"tftp",
|
||||
],
|
||||
ports => ["4280/tcp",],
|
||||
|
||||
state => $const.res.firewalld.state.absent,
|
||||
}
|
||||
14
go.mod
14
go.mod
@@ -32,8 +32,8 @@ require (
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.10
|
||||
go.etcd.io/etcd/client/v3 v3.5.10
|
||||
go.etcd.io/etcd/server/v3 v3.5.10
|
||||
golang.org/x/crypto v0.16.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/time v0.5.0
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@@ -65,7 +65,8 @@ require (
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/nftables v0.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/mux v1.7.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
@@ -92,9 +93,10 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/packet v1.0.0 // indirect
|
||||
github.com/mdlayher/raw v0.1.0 // indirect
|
||||
github.com/mdlayher/socket v0.2.3 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
@@ -148,8 +150,8 @@ require (
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.16.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
|
||||
16
go.sum
16
go.sum
@@ -317,11 +317,15 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/nftables v0.2.0 h1:PbJwaBmbVLzpeldoeUKGkE2RjstrjPKMl6oLrfEJ6/8=
|
||||
github.com/google/nftables v0.2.0/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
@@ -554,6 +558,8 @@ github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8=
|
||||
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
@@ -564,6 +570,8 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
|
||||
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
|
||||
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
|
||||
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
@@ -979,6 +987,8 @@ golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1084,6 +1094,8 @@ golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1114,6 +1126,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpi
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1205,6 +1219,8 @@ golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
||||
Reference in New Issue
Block a user