diff --git a/examples/resources/routeros_ip_firewall_connection_tracking/resource.tf b/examples/resources/routeros_ip_firewall_connection_tracking/resource.tf new file mode 100644 index 00000000..3bd48371 --- /dev/null +++ b/examples/resources/routeros_ip_firewall_connection_tracking/resource.tf @@ -0,0 +1,19 @@ + +resource "routeros_ip_firewall_connection_tracking" "data" { + enabled = "yes" + generic_timeout = "3m" + icmp_timeout = "3m" + loose_tcp_tracking = "false" + tcp_close_timeout = "3m" + tcp_close_wait_timeout = "3m" + tcp_established_timeout = "3m" + tcp_fin_wait_timeout = "3m" + tcp_last_ack_timeout = "3m" + tcp_max_retrans_timeout = "3m" + tcp_syn_received_timeout = "3m" + tcp_syn_sent_timeout = "3m" + tcp_time_wait_timeout = "3m" + tcp_unacked_timeout = "3m" + udp_stream_timeout = "3m" + udp_timeout = "3m" +} \ No newline at end of file diff --git a/routeros/provider.go b/routeros/provider.go index 4e50e2b1..f93ad417 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -71,6 +71,7 @@ func Provider() *schema.Provider { "routeros_ip_dhcp_server_option": ResourceDhcpServerOption(), "routeros_ip_dhcp_server_option_set": ResourceDhcpServerOptionSet(), "routeros_ip_firewall_addr_list": ResourceIPFirewallAddrList(), + "routeros_ip_firewall_connection_tracking": ResourceIPConnectionTracking(), "routeros_ip_firewall_filter": ResourceIPFirewallFilter(), "routeros_ip_firewall_mangle": ResourceIPFirewallMangle(), "routeros_ip_firewall_nat": ResourceIPFirewallNat(), diff --git a/routeros/provider_schema_helpers.go b/routeros/provider_schema_helpers.go index 302dd8f6..7d35064f 100644 --- a/routeros/provider_schema_helpers.go +++ b/routeros/provider_schema_helpers.go @@ -354,6 +354,17 @@ var ( return iOld == iNew } + + // AlwaysPresentNotUserProvided is a SupressDiff function that prevents values not provided by users to get updated. + // This is necessary in some system-wide fields that are present regardless if the users provides any values. + // Prevents the need of hardcode values for default values, as those are harder to track over time/versions of + // routeros + AlwaysPresentNotUserProvided = func(k, old, new string, d *schema.ResourceData) bool { + if old != "" && new == "" { + return true + } + return false + } ) func buildReadFilter(m map[string]interface{}) []string { diff --git a/routeros/resource_ip_firewall_connection_tracking.go b/routeros/resource_ip_firewall_connection_tracking.go new file mode 100644 index 00000000..affbde5b --- /dev/null +++ b/routeros/resource_ip_firewall_connection_tracking.go @@ -0,0 +1,184 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +/* + { + "active-ipv4": "yes", + "active-ipv6": "yes", + "enabled": "yes", + "generic-timeout": "10m", + "icmp-timeout": "10s", + "loose-tcp-tracking": "true", + "max-entries": "1048576", + "tcp-close-timeout": "1m", + "tcp-close-wait-timeout": "1m", + "tcp-established-timeout": "1d", + "tcp-fin-wait-timeout": "1m", + "tcp-last-ack-timeout": "1m", + "tcp-max-retrans-timeout": "5m", + "tcp-syn-received-timeout": "5s", + "tcp-syn-sent-timeout": "5s", + "tcp-time-wait-timeout": "1m", + "tcp-unacked-timeout": "5m", + "total-entries": "87", + "udp-stream-timeout": "3m", + "udp-timeout": "10s" +} +*/ + +// ResourceIPConnectionTracking https://help.mikrotik.com/docs/display/ROS/Connection+tracking +func ResourceIPConnectionTracking() *schema.Resource { + + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/ip/firewall/connection/tracking"), + MetaId: PropId(Name), + "active_ipv4": { + Type: schema.TypeBool, + Computed: true, + Description: "documentation is missing", + }, + "active_ipv6": { + Type: schema.TypeBool, + Computed: true, + Description: "documentation is missing", + }, + "enabled": { + Type: schema.TypeString, + Optional: true, + Description: `Allows to disable or enable connection tracking. Disabling connection tracking will cause several firewall features to stop working. + See the list of affected features. Starting from v6.0rc2 default value is auto. This means that connection tracing is disabled until at least one firewall rule is added.`, + ValidateFunc: ValidationAutoYesNo, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "generic_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "Timeout for all other connection entries", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "icmp_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "ICMP connection timeout", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "loose_tcp_tracking": { + Type: schema.TypeString, + Optional: true, + Description: "Disable picking up already established connections", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "max_entries": { + Type: schema.TypeString, + Description: `Max amount of entries that the connection tracking table can hold. This value depends on the installed amount of RAM. + Note that the system does not create a maximum_size connection tracking table when it starts, it may increase if the situation demands it and the system still has free ram, but size will not exceed 1048576`, + Computed: true, + }, + "tcp_close_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "No documentation", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_close_wait_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "No documentation", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_established_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "Time when established TCP connection times out.", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_fin_wait_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "No documentation", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_last_ack_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "No documentation", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_max_retrans_timeout": { + Type: schema.TypeString, + Optional: true, + // Documentation did contain the default, I'm getting it from the docker image default (7.10) + Description: "No documentation", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_syn_received_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "TCP SYN timeout.", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_syn_sent_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "TCP SYN timeout.", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_time_wait_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "No documentation", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "tcp_unacked_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "No documentation", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "total_entries": { + Type: schema.TypeInt, + Computed: true, + Description: "Amount of connections that currently connection table holds.", + }, + "udp_stream_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the timeout of UDP connections that has seen packets in both directions", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "udp_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the timeout for UDP connections that have seen packets in one direction", + ValidateFunc: ValidationTime, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + } + return &schema.Resource{ + CreateContext: DefaultSystemCreate(resSchema), + ReadContext: DefaultSystemRead(resSchema), + UpdateContext: DefaultSystemUpdate(resSchema), + DeleteContext: DefaultSystemDelete(resSchema), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_ip_firewall_connection_tracking_test.go b/routeros/resource_ip_firewall_connection_tracking_test.go new file mode 100644 index 00000000..6a59a5b8 --- /dev/null +++ b/routeros/resource_ip_firewall_connection_tracking_test.go @@ -0,0 +1,132 @@ +package routeros + +import ( + "errors" + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const testIPConnectionTracking = "routeros_ip_firewall_connection_tracking.data" +const testMingIPConnTrackingVersion = "7.10" + +func TestAccIPConnectionTrackingTest_basic(t *testing.T) { + for _, name := range testNames { + if !testCheckMinVersion(t, testMingIPConnTrackingVersion) { + t.Logf("Test skipped, the minimum required version is %v", testMingIPConnTrackingVersion) + return + } + + t.Run(name, func(t *testing.T) { + resource.Test(t, resource.TestCase{ + + PreCheck: func() { + testAccPreCheck(t) + testSetTransportEnv(t, name) + }, + ProviderFactories: testAccProviderFactories, + Steps: []resource.TestStep{ + // we can set all fields to non default + { + Config: testAccIPConnectionTrackingFullConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testIPConnectionTracking, "active_ipv4", "true"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "active_ipv6", "true"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "enabled", "yes"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "generic_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "icmp_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "loose_tcp_tracking", "false"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "max_entries", "419840"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_close_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_close_wait_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_established_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_fin_wait_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_last_ack_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_max_retrans_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_syn_received_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_syn_sent_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_time_wait_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_unacked_timeout", "3m"), + resource.TestCheckResourceAttrWith(testIPConnectionTracking, "total_entries", connectionsIsInAcceptableRange), + resource.TestCheckResourceAttr(testIPConnectionTracking, "udp_stream_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "udp_timeout", "3m"), + ), + }, + + // Empty resource don't override the settings + { + Config: testAccIPConnectionTrackingEmptyConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testIPConnectionTracking, "active_ipv4", "true"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "active_ipv6", "true"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "enabled", "yes"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "generic_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "icmp_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "loose_tcp_tracking", "false"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "max_entries", "419840"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_close_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_close_wait_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_established_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_fin_wait_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_last_ack_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_max_retrans_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_syn_received_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_syn_sent_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_time_wait_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "tcp_unacked_timeout", "3m"), + resource.TestCheckResourceAttrWith(testIPConnectionTracking, "total_entries", connectionsIsInAcceptableRange), + resource.TestCheckResourceAttr(testIPConnectionTracking, "udp_stream_timeout", "3m"), + resource.TestCheckResourceAttr(testIPConnectionTracking, "udp_timeout", "3m"), + ), + }, + }, + }) + }) + } +} + +func testAccIPConnectionTrackingEmptyConfig() string { + return providerConfig + ` +resource "routeros_ip_firewall_connection_tracking" "data" { + +} + +` +} + +func testAccIPConnectionTrackingFullConfig() string { + return providerConfig + ` +resource "routeros_ip_firewall_connection_tracking" "data" { + enabled = "yes" + generic_timeout = "3m" + icmp_timeout = "3m" + loose_tcp_tracking = "false" + tcp_close_timeout = "3m" + tcp_close_wait_timeout = "3m" + tcp_established_timeout = "3m" + tcp_fin_wait_timeout = "3m" + tcp_last_ack_timeout = "3m" + tcp_max_retrans_timeout = "3m" + tcp_syn_received_timeout = "3m" + tcp_syn_sent_timeout = "3m" + tcp_time_wait_timeout = "3m" + tcp_unacked_timeout = "3m" + udp_stream_timeout = "3m" + udp_timeout = "3m" +} + +` +} + +func connectionsIsInAcceptableRange(value string) error { + nConn, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("the total_entries was not a number %q", err) + } + if nConn <= 0 || nConn >= 100 { + return errors.New("number of tcp connections (total_entries) does not seem correct") + } + return nil +}