Skip to content
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

Firewall Rule includes more properties and create returns tracker id #41

Merged
merged 5 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion pfsenseapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"encoding/json"
"errors"
"fmt"
"golang.org/x/exp/slices"
"io"
"net/http"
"sync"
"time"

"golang.org/x/exp/slices"
)

var (
Expand All @@ -33,10 +35,12 @@ var (
type Client struct {
client *http.Client
Cfg Config
lock sync.Mutex

System *SystemService
Token *TokenService
DHCP *DHCPService
Unbound *UnboundService
Status *StatusService
Interface *InterfaceService
Routing *RoutingService
Expand Down Expand Up @@ -94,6 +98,7 @@ func NewClient(config Config) *Client {
newClient.Routing = &RoutingService{client: newClient}
newClient.Firewall = &FirewallService{client: newClient}
newClient.User = &UserService{client: newClient}
newClient.Unbound = &UnboundService{client: newClient}
return newClient
}

Expand Down Expand Up @@ -286,6 +291,10 @@ func (c *Client) get(ctx context.Context, endpoint string, queryMap map[string]s
}

func (c *Client) post(ctx context.Context, endpoint string, queryMap map[string]string, body []byte) ([]byte, error) {
// PFSense API cannot handle concurrent write requests
c.lock.Lock()
defer c.lock.Unlock()

res, err := c.do(ctx, http.MethodPost, endpoint, queryMap, body)
if err != nil {
return nil, err
Expand All @@ -312,6 +321,10 @@ func (c *Client) post(ctx context.Context, endpoint string, queryMap map[string]
}

func (c *Client) put(ctx context.Context, endpoint string, queryMap map[string]string, body []byte) ([]byte, error) {
// PFSense API cannot handle concurrent write requests
c.lock.Lock()
defer c.lock.Unlock()

res, err := c.do(ctx, http.MethodPut, endpoint, queryMap, body)
if err != nil {
return nil, err
Expand All @@ -338,6 +351,10 @@ func (c *Client) put(ctx context.Context, endpoint string, queryMap map[string]s
}

func (c *Client) delete(ctx context.Context, endpoint string, queryMap map[string]string) ([]byte, error) {
// PFSense API cannot handle concurrent write requests
c.lock.Lock()
defer c.lock.Unlock()

res, err := c.do(ctx, http.MethodDelete, endpoint, queryMap, nil)
if err != nil {
return nil, err
Expand Down
191 changes: 155 additions & 36 deletions pfsenseapi/dhcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import (
"context"
"encoding/json"
"fmt"
"strconv"
)

const (
leasesEndpoint = "api/v1/services/dhcpd/lease"
staticMappingEndpoint = "api/v1/services/dhcpd/static_mapping"
serverEndpoint = "api/v1/services/dhcpd"
)

// DHCPService provides DHCP API methods
Expand Down Expand Up @@ -41,33 +43,35 @@

// DHCPStaticMapping represents a single DHCP static reservation
type DHCPStaticMapping struct {
ID int `json:"id"`
Mac string `json:"mac"`
Cid string `json:"cid"`
IPaddr string `json:"ipaddr"`
Hostname string `json:"hostname"`
Descr string `json:"descr"`
Filename string `json:"filename"`
Rootpath string `json:"rootpath"`
DefaultLeaseTime string `json:"defaultleasetime"`
MaxLeaseTime string `json:"maxleasetime"`
Gateway string `json:"gateway"`
Domain string `json:"domain"`
DomainSearchList string `json:"domainsearchlist"`
DDNSDomain string `json:"ddnsdomain"`
DDNSDomainPrimary string `json:"ddnsdomainprimary"`
DDNSDomainSecondary string `json:"ddnsdomainsecondary"`
DDNSDomainkeyName string `json:"ddnsdomainkeyname"`
DDNSDomainkeyAlgorithm string `json:"ddnsdomainkeyalgorithm"`
DDNSDomainkey string `json:"ddnsdomainkey"`
TFTP string `json:"tftp"`
LDAP string `json:"ldap"`
NextServer string `json:"nextserver"`
Filename32 string `json:"filename32"`
Filename64 string `json:"filename64"`
Filename32Arm string `json:"filename32arm"`
Filename64Arm string `json:"filename64arm"`
NumberOptions string `json:"numberoptions"`
ID int `json:"id"`
Mac string `json:"mac"`
Cid string `json:"cid"`
IPaddr string `json:"ipaddr"`
Hostname string `json:"hostname"`
Descr string `json:"descr"`
Filename string `json:"filename"`
Rootpath string `json:"rootpath"`
DefaultLeaseTime string `json:"defaultleasetime"`
MaxLeaseTime string `json:"maxleasetime"`
Gateway string `json:"gateway"`
Domain string `json:"domain"`
DomainSearchList string `json:"domainsearchlist"`
DDNSDomain string `json:"ddnsdomain"`
DDNSDomainPrimary string `json:"ddnsdomainprimary"`
DDNSDomainSecondary string `json:"ddnsdomainsecondary"`
DDNSDomainkeyName string `json:"ddnsdomainkeyname"`
DDNSDomainkeyAlgorithm string `json:"ddnsdomainkeyalgorithm"`
DDNSDomainkey string `json:"ddnsdomainkey"`
DNSServers []string `json:"dnsserver"`
TFTP string `json:"tftp"`
LDAP string `json:"ldap"`
NextServer string `json:"nextserver"`
Filename32 string `json:"filename32"`
Filename64 string `json:"filename64"`
Filename32Arm string `json:"filename32arm"`
Filename64Arm string `json:"filename64arm"`
NumberOptions string `json:"numberoptions"`
ArpTableStaticEntry TrueIfPresent `json:"arp_table_static_entry"`
}

// DHCPStaticMappingRequest represents a single DHCP static reservation. This
Expand Down Expand Up @@ -151,15 +155,37 @@
Id int `json:"id"`
}

func (s DHCPService) getStaticMappingObjectId(ctx context.Context, mappingInterface string, macAddress string) (int, error) {
mappings, err := s.ListStaticMappings(ctx, mappingInterface)

if err != nil {
return 0, err

Check warning on line 162 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L162

Added line #L162 was not covered by tests
}

for i, mapping := range mappings {
if mapping.Mac == macAddress {
return i, nil
}
}

return 0, fmt.Errorf("Unable to find static mapping on interface %s with mac %s", mappingInterface, macAddress)

Check warning on line 171 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L171

Added line #L171 was not covered by tests
}

// UpdateStaticMapping modifies a DHCP static mapping.
func (s DHCPService) UpdateStaticMapping(
ctx context.Context,
idToUpdate int,
macAddress string,
mappingData DHCPStaticMappingRequest,
) (*DHCPStaticMapping, error) {
id, err := s.getStaticMappingObjectId(ctx, mappingData.Interface, macAddress)

Check warning on line 180 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L180

Added line #L180 was not covered by tests

if err != nil {
return nil, err

Check warning on line 183 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L182-L183

Added lines #L182 - L183 were not covered by tests
}

requestData := dhcpStaticMappingRequestUpdate{
DHCPStaticMappingRequest: mappingData,
Id: idToUpdate,
Id: id,

Check warning on line 188 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L188

Added line #L188 was not covered by tests
}

jsonData, err := json.Marshal(requestData)
Expand All @@ -179,21 +205,114 @@
}

// DeleteStaticMapping deletes a DHCP static mapping.
func (s DHCPService) DeleteStaticMapping(
ctx context.Context,
mappingInterface string,
idToDelete int,
) error {
_, err := s.client.delete(
func (s DHCPService) DeleteStaticMapping(ctx context.Context, mappingInterface string, macAddress string) error {
id, err := s.getStaticMappingObjectId(ctx, mappingInterface, macAddress)

if err != nil {
return err

Check warning on line 212 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L212

Added line #L212 was not covered by tests
}

_, err = s.client.delete(
ctx,
staticMappingEndpoint,
map[string]string{
"interface": mappingInterface,
"id": strconv.Itoa(idToDelete),
"id": strconv.Itoa(id),
},
)
if err != nil {
return err
}
return nil
}

// DHCPServerConfigurationRequest updates the current DHCP Server (dhcpd) configuration for a specified interface
type DHCPServerConfigurationRequest struct {
DefaultLeaseTime *int `json:"defaultleasetime"`
DenyUnknown bool `json:"denyunknown"`
DNSServer []string `json:"dnsserver,omitempty"`
Domain string `json:"domain,omitempty"`
DomainSearchList []string `json:"domainsearchlist,omitempty"`
Enable bool `json:"enable"`
Gateway string `json:"gateway,omitempty"`
IgnoreBootP bool `json:"ignorebootp,omitempty"`
Interface string `json:"interface"`
MacAllow []string `json:"mac_allow,omitempty"`
MacDeny []string `json:"mac_deny,omitempty"`
MaxLeaseTime *int `json:"maxleasetime,omitempty"`
NumberOptions []interface{} `json:"numberoptions,omitempty"`
RangeFrom string `json:"range_from,omitempty"`
RangeTo string `json:"range_to,omitempty"`
StaticARP bool `json:"staticarp"`
}

type DHCPRange struct {
From string `json:"from"`
To string `json:"to"`
}

// DHCPServerConfiguration describes the current DHCP Server (dhcpd) configuration for a specified interface
type DHCPServerConfiguration struct {
DefaultLeaseTime OptionalJSONInt `json:"defaultleasetime"`
DenyUnknown TrueIfPresent `json:"denyunknown"`
DNSServer []string `json:"dnsserver"`
Domain string `json:"domain"`
DomainSearchList string `json:"domainsearchlist"`
Enable TrueIfPresent `json:"enable"`
Gateway string `json:"gateway"`
IgnoreBootP bool `json:"ignorebootp"`
Interface string `json:"interface"`
MacAllow string `json:"mac_allow"`
MacDeny string `json:"mac_deny"`
MaxLeaseTime OptionalJSONInt `json:"maxleasetime"`
NumberOptions string `json:"numberoptions"`
Range *DHCPRange `json:"range"`
StaticARP TrueIfPresent `json:"staticarp"`
}

type dhcpServerResponse struct {
apiResponse
Data []*DHCPServerConfiguration `json:"data"`
}

// ListServerConfigurations lists all DHCP server configurations
func (s DHCPService) ListServerConfigurations(ctx context.Context) ([]*DHCPServerConfiguration, error) {
response, err := s.client.get(ctx, serverEndpoint, nil)
if err != nil {
return nil, err

Check warning on line 282 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L279-L282

Added lines #L279 - L282 were not covered by tests
}

resp := new(dhcpServerResponse)
if err = json.Unmarshal(response, resp); err != nil {
return nil, err

Check warning on line 287 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L285-L287

Added lines #L285 - L287 were not covered by tests
}
return resp.Data, nil

Check warning on line 289 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L289

Added line #L289 was not covered by tests
}

type dhcpServerUpdateResponse struct {
apiResponse
Data *DHCPServerConfiguration `json:"data"`
}

// UpdateServerConfiguration modifies a DHCP server configuration.
func (s DHCPService) UpdateServerConfiguration(
ctx context.Context,
dhcpConfigData DHCPServerConfigurationRequest,
) (*DHCPServerConfiguration, error) {
jsonData, err := json.Marshal(dhcpConfigData)
if err != nil {
return nil, err

Check warning on line 304 in pfsenseapi/dhcp.go

View check run for this annotation

Codecov / codecov/patch

pfsenseapi/dhcp.go#L304

Added line #L304 was not covered by tests
}
response, err := s.client.put(ctx, serverEndpoint, nil, jsonData)
if err != nil {
return nil, err
}

resp := new(dhcpServerUpdateResponse)
if err = json.Unmarshal(response, resp); err != nil {
return nil, err
}

resp.Data.Interface = dhcpConfigData.Interface
return resp.Data, nil
}
77 changes: 76 additions & 1 deletion pfsenseapi/dhcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,80 @@ func TestDHCPService_ListStaticMappings(t *testing.T) {
newClient := NewClientWithNoAuth(server.URL)
response, err := newClient.DHCP.ListStaticMappings(context.Background(), testInterface)
require.NoError(t, err)
require.Len(t, response, 1)
require.Len(t, response, 4)
}

func TestDHCPService_DeleteStaticMappings(t *testing.T) {
listResponse := mustReadFileString(t, "testdata/liststaticmappings.json")
deleteResponse := mustReadFileString(t, "testdata/deletestaticmapping.json")

testInterface := "IOT"
mappingId := "3"
mappingMac := "00:1d:93:aa:4c"

handler := func(w http.ResponseWriter, r *http.Request) {

query, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = fmt.Fprintf(w, "invalid request")
return
}

interfaceValue := query.Get("interface")

if interfaceValue != testInterface {
w.WriteHeader(http.StatusBadRequest)
_, _ = fmt.Fprintf(w, "invalid request")
return
}

if r.Method == http.MethodDelete {
id := query.Get("id")

if id != mappingId {
w.WriteHeader(http.StatusBadRequest)
_, _ = fmt.Fprintf(w, "invalid request")
} else {
w.WriteHeader(http.StatusOK)
_, _ = io.WriteString(w, deleteResponse)
}
} else {
w.WriteHeader(http.StatusOK)
_, _ = io.WriteString(w, listResponse)
}
}

server := httptest.NewServer(http.HandlerFunc(handler))
defer server.Close()

newClient := NewClientWithNoAuth(server.URL)
err := newClient.DHCP.DeleteStaticMapping(context.Background(), testInterface, mappingMac)
require.NoError(t, err)
}

func TestDHCPService_UpdateDHCPConfiguration(t *testing.T) {
data := makeResultList(t, mustReadFileString(t, "testdata/dhcpconfiguration.json"))

handler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(data.popStatus())
_, _ = io.WriteString(w, data.popResult())
}

server := httptest.NewServer(http.HandlerFunc(handler))
defer server.Close()

newClient := NewClientWithNoAuth(server.URL)
response, err := newClient.DHCP.UpdateServerConfiguration(context.Background(), DHCPServerConfigurationRequest{})
require.NotNil(t, response)
require.NoError(t, err)

response, err = newClient.DHCP.UpdateServerConfiguration(context.Background(), DHCPServerConfigurationRequest{})
require.Nil(t, response)
require.Error(t, err)

response, err = newClient.DHCP.UpdateServerConfiguration(context.Background(), DHCPServerConfigurationRequest{})
require.Nil(t, response)
require.Error(t, err)
}
Loading
Loading