252 lines
7.9 KiB
Go
252 lines
7.9 KiB
Go
package firewalld
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
)
|
|
|
|
// PolicySettings is the transport-neutral representation of a zone-to-zone policy
|
|
// (firewalld >= 0.9). Policies have no v1 tuple form; they are exchanged only as
|
|
// an a{sv} dict. Ingress and egress zones define the traffic the policy governs;
|
|
// the remaining fields mirror a zone's filtering elements.
|
|
type PolicySettings struct {
|
|
Version string
|
|
Short string
|
|
Description string
|
|
Target string // CONTINUE, ACCEPT, DROP, REJECT
|
|
Priority int32
|
|
IngressZones []string
|
|
EgressZones []string
|
|
Services []string
|
|
Ports []Port
|
|
SourcePorts []Port
|
|
ICMPBlocks []string
|
|
Masquerade bool
|
|
ForwardPorts []ForwardPort
|
|
RichRules []string
|
|
Protocols []string
|
|
}
|
|
|
|
// toDict builds the a{sv} policy dict with concrete typed values so godbus emits
|
|
// the tuple signatures firewalld requires.
|
|
func (s *PolicySettings) toDict() map[string]dbus.Variant {
|
|
target := s.Target
|
|
if target == "" {
|
|
target = "CONTINUE"
|
|
}
|
|
return map[string]dbus.Variant{
|
|
"version": dbus.MakeVariant(s.Version),
|
|
"short": dbus.MakeVariant(s.Short),
|
|
"description": dbus.MakeVariant(s.Description),
|
|
"target": dbus.MakeVariant(target),
|
|
"priority": dbus.MakeVariant(s.Priority),
|
|
"ingress_zones": dbus.MakeVariant(nonNilStrings(s.IngressZones)),
|
|
"egress_zones": dbus.MakeVariant(nonNilStrings(s.EgressZones)),
|
|
"services": dbus.MakeVariant(nonNilStrings(s.Services)),
|
|
"ports": dbus.MakeVariant(nonNilPorts(s.Ports)),
|
|
"source_ports": dbus.MakeVariant(nonNilPorts(s.SourcePorts)),
|
|
"icmp_blocks": dbus.MakeVariant(nonNilStrings(s.ICMPBlocks)),
|
|
"masquerade": dbus.MakeVariant(s.Masquerade),
|
|
"forward_ports": dbus.MakeVariant(nonNilForwardPorts(s.ForwardPorts)),
|
|
// Policies name rich rules "rich_rules" in their dict (zones use
|
|
// "rules_str"); firewalld rejects the wrong key with INVALID_OPTION.
|
|
"rich_rules": dbus.MakeVariant(nonNilStrings(s.RichRules)),
|
|
"protocols": dbus.MakeVariant(nonNilStrings(s.Protocols)),
|
|
}
|
|
}
|
|
|
|
// policySettingsFromDict decodes an a{sv} policy dict, tolerating omitted keys.
|
|
func policySettingsFromDict(d map[string]dbus.Variant) PolicySettings {
|
|
var s PolicySettings
|
|
if v, ok := dictValue(d, "version"); ok {
|
|
s.Version = asString(v)
|
|
}
|
|
if v, ok := dictValue(d, "short"); ok {
|
|
s.Short = asString(v)
|
|
}
|
|
if v, ok := dictValue(d, "description"); ok {
|
|
s.Description = asString(v)
|
|
}
|
|
if v, ok := dictValue(d, "target"); ok {
|
|
s.Target = asString(v)
|
|
}
|
|
if v, ok := dictValue(d, "priority"); ok {
|
|
s.Priority = asInt32(v)
|
|
}
|
|
if v, ok := dictValue(d, "ingress_zones"); ok {
|
|
s.IngressZones = asStrings(v)
|
|
}
|
|
if v, ok := dictValue(d, "egress_zones"); ok {
|
|
s.EgressZones = asStrings(v)
|
|
}
|
|
if v, ok := dictValue(d, "services"); ok {
|
|
s.Services = asStrings(v)
|
|
}
|
|
if v, ok := dictValue(d, "ports"); ok {
|
|
s.Ports = asPorts(v)
|
|
}
|
|
if v, ok := dictValue(d, "source_ports"); ok {
|
|
s.SourcePorts = asPorts(v)
|
|
}
|
|
if v, ok := dictValue(d, "icmp_blocks"); ok {
|
|
s.ICMPBlocks = asStrings(v)
|
|
}
|
|
if v, ok := dictValue(d, "masquerade"); ok {
|
|
s.Masquerade = asBool(v)
|
|
}
|
|
if v, ok := dictValue(d, "forward_ports"); ok {
|
|
s.ForwardPorts = asForwardPorts(v)
|
|
}
|
|
if v, ok := dictValue(d, "rules_str"); ok {
|
|
s.RichRules = asStrings(v)
|
|
} else if v, ok := dictValue(d, "rich_rules"); ok {
|
|
s.RichRules = asStrings(v)
|
|
}
|
|
if v, ok := dictValue(d, "protocols"); ok {
|
|
s.Protocols = asStrings(v)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Policies lists the names of all policies known to the runtime. Returns
|
|
// ErrUnsupported on servers that predate policy support (firewalld < 0.9).
|
|
func (r *Runtime) Policies(ctx context.Context) ([]string, error) {
|
|
if !r.c.caps[Policies] {
|
|
return nil, ErrUnsupported
|
|
}
|
|
var names []string
|
|
err := r.c.call(ctx, basePath, ifacePolicy, "getPolicies", []any{&names})
|
|
return names, err
|
|
}
|
|
|
|
// ActivePolicies returns the policies that currently have zones bound.
|
|
func (r *Runtime) ActivePolicies(ctx context.Context) ([]string, error) {
|
|
if !r.c.caps[Policies] {
|
|
return nil, ErrUnsupported
|
|
}
|
|
var active map[string]map[string][]string
|
|
if err := r.c.call(ctx, basePath, ifacePolicy, "getActivePolicies", []any{&active}); err != nil {
|
|
return nil, err
|
|
}
|
|
names := make([]string, 0, len(active))
|
|
for name := range active {
|
|
names = append(names, name)
|
|
}
|
|
return names, nil
|
|
}
|
|
|
|
// PolicySettings reads a policy definition from the running firewall.
|
|
func (r *Runtime) PolicySettings(ctx context.Context, policy string) (PolicySettings, error) {
|
|
if !r.c.caps[Policies] {
|
|
return PolicySettings{}, ErrUnsupported
|
|
}
|
|
var d map[string]dbus.Variant
|
|
if err := r.c.call(ctx, basePath, ifacePolicy, "getPolicySettings", []any{&d}, policy); err != nil {
|
|
return PolicySettings{}, err
|
|
}
|
|
return policySettingsFromDict(d), nil
|
|
}
|
|
|
|
// SetPolicySettings replaces a policy's runtime settings wholesale.
|
|
func (r *Runtime) SetPolicySettings(ctx context.Context, policy string, settings PolicySettings) error {
|
|
if !r.c.caps[Policies] {
|
|
return ErrUnsupported
|
|
}
|
|
return r.c.call(ctx, basePath, ifacePolicy, "setPolicySettings", nil, policy, settings.toDict())
|
|
}
|
|
|
|
// PolicyNames lists the names of all permanent policies.
|
|
func (p *Permanent) PolicyNames(ctx context.Context) ([]string, error) {
|
|
if !p.c.caps[Policies] {
|
|
return nil, ErrUnsupported
|
|
}
|
|
var names []string
|
|
err := p.c.call(ctx, configPath, ifaceConfig, "getPolicyNames", []any{&names})
|
|
return names, err
|
|
}
|
|
|
|
// PolicyPaths lists the config object paths of all permanent policies.
|
|
func (p *Permanent) PolicyPaths(ctx context.Context) ([]dbus.ObjectPath, error) {
|
|
if !p.c.caps[Policies] {
|
|
return nil, ErrUnsupported
|
|
}
|
|
var paths []dbus.ObjectPath
|
|
err := p.c.call(ctx, configPath, ifaceConfig, "listPolicies", []any{&paths})
|
|
return paths, err
|
|
}
|
|
|
|
// AddPolicy creates a new permanent policy and returns its config object path.
|
|
func (p *Permanent) AddPolicy(ctx context.Context, name string, settings PolicySettings) (dbus.ObjectPath, error) {
|
|
if !p.c.caps[Policies] {
|
|
return "", ErrUnsupported
|
|
}
|
|
var path dbus.ObjectPath
|
|
err := p.c.call(ctx, configPath, ifaceConfig, "addPolicy", []any{&path}, name, settings.toDict())
|
|
return path, err
|
|
}
|
|
|
|
// Policy returns a handle for permanent operations on the named policy.
|
|
func (p *Permanent) Policy(name string) *PermPolicy {
|
|
return &PermPolicy{c: p.c, name: name}
|
|
}
|
|
|
|
// PermPolicy is a handle for permanent edits to a single policy, operating on the
|
|
// policy's config child object (/config/policy/N).
|
|
type PermPolicy struct {
|
|
c *Conn
|
|
name string
|
|
path dbus.ObjectPath
|
|
}
|
|
|
|
func (s *PermPolicy) resolve(ctx context.Context) (dbus.ObjectPath, error) {
|
|
if !s.c.caps[Policies] {
|
|
return "", ErrUnsupported
|
|
}
|
|
if s.path != "" {
|
|
return s.path, nil
|
|
}
|
|
var path dbus.ObjectPath
|
|
if err := s.c.call(ctx, configPath, ifaceConfig, "getPolicyByName", []any{&path}, s.name); err != nil {
|
|
return "", err
|
|
}
|
|
s.path = path
|
|
return path, nil
|
|
}
|
|
|
|
func (s *PermPolicy) callOn(ctx context.Context, method string, rets []any, args ...any) error {
|
|
path, err := s.resolve(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.c.call(ctx, path, ifaceConfigPolicy, method, rets, args...)
|
|
}
|
|
|
|
// Settings reads the policy's permanent settings.
|
|
func (s *PermPolicy) Settings(ctx context.Context) (PolicySettings, error) {
|
|
var d map[string]dbus.Variant
|
|
if err := s.callOn(ctx, "getSettings", []any{&d}); err != nil {
|
|
return PolicySettings{}, err
|
|
}
|
|
return policySettingsFromDict(d), nil
|
|
}
|
|
|
|
// Update replaces the policy's permanent settings wholesale.
|
|
func (s *PermPolicy) Update(ctx context.Context, settings PolicySettings) error {
|
|
return s.callOn(ctx, "update", nil, settings.toDict())
|
|
}
|
|
|
|
// Rename changes the policy's name and invalidates the cached path.
|
|
func (s *PermPolicy) Rename(ctx context.Context, newName string) error {
|
|
if err := s.callOn(ctx, "rename", nil, newName); err != nil {
|
|
return err
|
|
}
|
|
s.name = newName
|
|
s.path = ""
|
|
return nil
|
|
}
|
|
|
|
// Remove deletes the policy from the permanent configuration.
|
|
func (s *PermPolicy) Remove(ctx context.Context) error {
|
|
return s.callOn(ctx, "remove", nil)
|
|
}
|