Skip to content

Commit

Permalink
feat(firewall/raw): Add new resource
Browse files Browse the repository at this point in the history
Closes #462
  • Loading branch information
vaerh committed May 28, 2024
1 parent 74223be commit 90eb2fa
Show file tree
Hide file tree
Showing 3 changed files with 391 additions and 0 deletions.
1 change: 1 addition & 0 deletions routeros/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func Provider() *schema.Provider {
"routeros_ip_firewall_filter": ResourceIPFirewallFilter(),
"routeros_ip_firewall_mangle": ResourceIPFirewallMangle(),
"routeros_ip_firewall_nat": ResourceIPFirewallNat(),
"routeros_firewall_raw": ResourceIPFirewallRaw(),
"routeros_ip_address": ResourceIPAddress(),
"routeros_ip_pool": ResourceIPPool(),
"routeros_ip_route": ResourceIPRoute(),
Expand Down
343 changes: 343 additions & 0 deletions routeros/resource_ip_firewall_raw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
package routeros

import (
"context"
"regexp"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

/*
{
".id": "*1",
"action": "accept",
"bytes": "53342",
"chain": "prerouting",
"comment": "1",
"disabled": "false",
"dynamic": "false",
"invalid": "false",
"log": "false",
"log-prefix": "",
"packets": "497"
}
*/
// ResourceIPFirewallRaw https://wiki.mikrotik.com/wiki/Manual:IP/Firewall/Raw
// https://help.mikrotik.com/docs/display/ROS/Common+Firewall+Matchers+and+Actions
func ResourceIPFirewallRaw() *schema.Resource {
resSchema := map[string]*schema.Schema{
MetaResourcePath: PropResourcePath("/ip/firewall/raw"),
MetaId: PropId(Id),
MetaSkipFields: PropSkipFields("bytes", "packets"),
MetaSetUnsetFields: PropSetUnsetFields("dst_address_list", "src_address_list", "in_interface_list",
"out_interface_list", "in_bridge_port_list", "out_bridge_port_list", "protocol"),

"action": {
Type: schema.TypeString,
Required: true,
Description: "Action to take if a packet is matched by the rule",
ValidateFunc: validation.StringInSlice([]string{
"accept", "add-dst-to-address-list", "add-src-to-address-list", "drop",
"jump", "log", "no-track", "passthrough", "return",
}, false),
},
"address_list": {
Type: schema.TypeString,
Optional: true,
Description: "Name of the address list used in 'add-dst-to-address-list' and 'add-src-to-address-list' actions.",
},
"address_list_timeout": {
Type: schema.TypeString,
Optional: true,
Description: "Time interval after which the address will be removed from the address list specified by " +
"address-list parameter. Used in conjunction with add-dst-to-address-list or add-src-to-address-list " +
"actions.",
DiffSuppressFunc: TimeEquall,
},
"chain": {
Type: schema.TypeString,
Required: true,
Description: "Specifies to which chain rule will be added. If the input does not match the name of an " +
"already defined chain, a new chain will be created.",
},
KeyComment: PropCommentRw,
"content": {
Type: schema.TypeString,
Optional: true,
Description: "Match packets that contain specified text.",
},
KeyDisabled: PropDisabledRw,
"dscp": {
Type: schema.TypeInt,
Optional: true,
Description: "Matches DSCP IP header field.",
ValidateFunc: validation.IntBetween(0, 63),
},
"dst_address": {
Type: schema.TypeString,
Optional: true,
Description: "Matches packets which destination is equal to specified IP or falls into specified IP range.",
},
"dst_address_list": {
Type: schema.TypeString,
Optional: true,
Description: "Matches destination address of a packet against user-defined address list.",
},
"dst_address_type": {
Type: schema.TypeString,
Optional: true,
Description: "Matches destination address type.",
ValidateDiagFunc: ValidationMultiValInSlice([]string{"unicast", "local", "broadcast", "multicast"}, false, true),
},
"dst_limit": {
Type: schema.TypeString,
Optional: true,
Description: "Matches packets until a given rate is exceeded.",
},
"dst_port": {
Type: schema.TypeString,
Optional: true,
Description: "List of destination port numbers or port number ranges.",
},
KeyDynamic: PropDynamicRo,
"fragment": {
Type: schema.TypeBool,
Optional: true,
Description: "Matches fragmented packets. First (starting) fragment does not count. If connection tracking " +
"is enabled there will be no fragments as system automatically assembles every packet",
},
"hotspot": {
Type: schema.TypeString,
Optional: true,
Description: "Matches packets received from HotSpot clients against various HotSpot matchers.",
ValidateDiagFunc: ValidationMultiValInSlice([]string{"auth", "from-client", "http", "local-dst", "to-client"}, false, true),
},
"icmp_options": {
Type: schema.TypeString,
Optional: true,
Description: "Matches ICMP type: code fields.",
},
"in_bridge_port": {
Type: schema.TypeString,
Optional: true,
Description: "Actual interface the packet has entered the router if the incoming interface is a bridge. " +
"Works only if use-ip-firewall is enabled in bridge settings.",
},
"in_bridge_port_list": {
Type: schema.TypeString,
Optional: true,
Description: "Set of interfaces defined in interface list. Works the same as in-bridge-port.",
},
"in_interface": {
Type: schema.TypeString,
Optional: true,
Description: "Interface the packet has entered the router.",
},
"in_interface_list": {
Type: schema.TypeString,
Optional: true,
Description: "Set of interfaces defined in interface list. Works the same as in-interface.",
},
"ingress_priority": {
Type: schema.TypeInt,
Optional: true,
Description: "Matches the priority of an ingress packet. Priority may be derived from VLAN, WMM, DSCP, " +
"or MPLS EXP bit.",
ValidateFunc: validation.IntBetween(0, 63),
},
"invalid": {
Type: schema.TypeBool,
Computed: true,
},
"ipsec_policy": {
Type: schema.TypeString,
Optional: true,
Description: "Matches the policy used by IPsec. Value is written in the following format: direction, policy.",
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(in|out)\s?,\s?(ipsec|none)$`),
"Value must be written in the following format: direction, policy."),
},
"ipv4_options": {
Type: schema.TypeString,
Optional: true,
Description: "Matches IPv4 header options.",
ValidateFunc: validation.StringInSlice([]string{
"any", "loose-source-routing", "no-record-route", "no-router-alert", "no-source-routing",
"no-timestamp", "none", "record-route", "router-alert", "strict-source-routing", "timestamp",
}, false),
},
"jump_target": {
Type: schema.TypeString,
Optional: true,
Description: "Name of the target chain to jump to. Applicable only if action=jump.",
},
"limit": {
Type: schema.TypeString,
Optional: true,
Description: "Matches packets up to a limited rate (packet rate or bit rate). A rule using this matcher " +
"will match until this limit is reached. Parameters are written in the following format: " +
"rate[/time],burst:mode.",
},
"log": {
Type: schema.TypeBool,
Optional: true,
Description: "Add a message to the system log.",
DiffSuppressFunc: AlwaysPresentNotUserProvided,
},
"log_prefix": {
Type: schema.TypeString,
Optional: true,
Description: "Adds specified text at the beginning of every log message. Applicable if action=log or " +
"log=yes configured.",
},
"nth": {
Type: schema.TypeString,
Optional: true,
Description: "Matches every nth packet: nth=2,1 rule will match every first packet of 2, hence, 50% of " +
"all the traffic that is matched by the rule",
},
"out_bridge_port": {
Type: schema.TypeString,
Optional: true,
Description: "Actual interface the packet is leaving the router if the outgoing interface is a bridge. " +
"Works only if use-ip-firewall is enabled in bridge settings.",
},
"out_bridge_port_list": {
Type: schema.TypeString,
Optional: true,
Description: "Set of interfaces defined in interface list. Works the same as out-bridge-port.",
},
"out_interface": {
Type: schema.TypeString,
Optional: true,
Description: "Interface the packet is leaving the router.",
},
"out_interface_list": {
Type: schema.TypeString,
Optional: true,
Description: "Set of interfaces defined in interface list. Works the same as out-interface.",
},
"packet_mark": {
Type: schema.TypeString,
Optional: true,
Description: "Matches packets marked via mangle facility with particular packet mark. If no-mark is set, " +
"the rule will match any unmarked packet.",
},
"packet_size": {
Type: schema.TypeString,
Optional: true,
Description: "Matches packets of specified size or size range in bytes.",
},
"per_connection_classifier": {
Type: schema.TypeString,
Optional: true,
Description: "PCC matcher allows dividing traffic into equal streams with the ability to keep packets " +
"with a specific set of options in one particular stream.",
},
KeyPlaceBefore: PropPlaceBefore,
"port": {
Type: schema.TypeString,
Optional: true,
Description: "Matches if any (source or destination) port matches the specified list of ports or port " +
"ranges. Applicable only if protocol is TCP or UDP",
},
"priority": {
Type: schema.TypeInt,
Optional: true,
Description: "Matches the packet's priority after a new priority has been set. Priority may be derived from " +
"VLAN, WMM, DSCP, MPLS EXP bit, or from the priority that has been set using the set-priority action.",
ValidateFunc: validation.IntBetween(0, 63),
},
"protocol": {
Type: schema.TypeString,
Optional: true,
Description: "Matches particular IP protocol specified by protocol name or number.",
},
"psd": {
Type: schema.TypeString,
Optional: true,
Description: "Attempts to detect TCP and UDP scans. Parameters are in the following format WeightThreshold, " +
"DelayThreshold, LowPortWeight, HighPortWeight.",
},
"random": {
Type: schema.TypeInt,
Optional: true,
Description: "Matches packets randomly with a given probability.",
ValidateFunc: validation.IntBetween(1, 99),
},
"src_address": {
Type: schema.TypeString,
Optional: true,
Description: "Matches packets which source is equal to specified IP or falls into a specified IP range.",
},
"src_address_list": {
Type: schema.TypeString,
Optional: true,
Description: "Matches source address of a packet against user-defined address list.",
},
"src_address_type": {
Type: schema.TypeString,
Optional: true,
Description: "Matches source address type.",
ValidateDiagFunc: ValidationMultiValInSlice([]string{"unicast", "local", "broadcast", "multicast",
"blackhole"}, false, true),
},
"src_port": {
Type: schema.TypeString,
Optional: true,
Description: "List of source ports and ranges of source ports. Applicable only if a protocol is TCP or UDP.",
},
"src_mac_address": {
Type: schema.TypeString,
Optional: true,
Description: "Matches source MAC address of the packet.",
ValidateFunc: ValidationMacAddress,
},
"tcp_flags": {
Type: schema.TypeString,
Optional: true,
Description: "Matches specified TCP flags.",
},
"tcp_mss": {
Type: schema.TypeString,
Optional: true,
Description: "Matches TCP MSS value of an IP packet.",
},
"time": {
Type: schema.TypeString,
Optional: true,
Description: "Allows to create a filter based on the packets' arrival time and date or, for locally " +
"generated packets, departure time and date.",
},
"tls_host": {
Type: schema.TypeString,
Optional: true,
Description: "Allows matching HTTPS traffic based on TLS SNI hostname.",
},
"ttl": {
Type: schema.TypeString,
Optional: true,
Description: "Matches packets TTL value.",
},
}
return &schema.Resource{
CreateContext: DefaultCreate(resSchema),
ReadContext: DefaultRead(resSchema),
UpdateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
skip := resSchema[MetaSkipFields].Default.(string)
resSchema[MetaSkipFields].Default = skip + `,"place_before"`
defer func() {
resSchema[MetaSkipFields].Default = skip
}()

return ResourceUpdate(ctx, resSchema, d, m)
},
DeleteContext: DefaultDelete(resSchema),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: resSchema,
}
}
47 changes: 47 additions & 0 deletions routeros/resource_ip_firewall_raw_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package routeros

import (
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

const testIPFirewallRawAddress = "routeros_firewall_raw.rule"

func TestAccIPFirewallRawTest_basic(t *testing.T) {
for _, name := range testNames {
t.Run(name, func(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
testSetTransportEnv(t, name)
},
ProviderFactories: testAccProviderFactories,
CheckDestroy: testCheckResourceDestroy("/ip/firewall/raw", "routeros_firewall_raw"),
Steps: []resource.TestStep{
{
Config: testAccIPFirewallRawConfig(),
Check: resource.ComposeTestCheckFunc(
testResourcePrimaryInstanceId(testIPFirewallRawAddress),
resource.TestCheckResourceAttr(testIPFirewallRawAddress, "action", "accept"),
),
},
},
})

})
}
}

func testAccIPFirewallRawConfig() string {
return providerConfig + `
resource "routeros_firewall_raw" "rule" {
action = "accept"
chain = "prerouting"
src_address = "10.0.0.1"
dst_address = "10.0.1.1"
dst_port = "443"
protocol = "tcp"
}
`
}

0 comments on commit 90eb2fa

Please sign in to comment.