diff --git a/docs/resources/wlan.md b/docs/resources/wlan.md index 94babbf..73c9051 100644 --- a/docs/resources/wlan.md +++ b/docs/resources/wlan.md @@ -39,6 +39,10 @@ resource "unifi_wlan" "wifi" { passphrase = "12345678" security = "wpapsk" + # enable WPA2/WPA3 support + wpa3_support = true + wpa3_transition = true + network_id = unifi_network.vlan.id ap_group_ids = [data.unifi_ap_group.default.id] user_group_id = data.unifi_user_group.default.id @@ -74,6 +78,8 @@ resource "unifi_wlan" "wifi" { - **vlan_id** (Number, Deprecated) VLAN ID for the network. Set network_id instead of vlan_id for controller version >= 6. - **wlan_band** (String) Radio band your WiFi network will use. - **wlan_group_id** (String, Deprecated) ID of the WLAN group to use for this network. Set ap_group_ids instead of wlan_group_id for controller version >= 6. +- **wpa3_support** (Boolean) Enable WPA 3 support (security must be `wpapsk`). +- **wpa3_transition** (Boolean) Enable WPA 3 and WPA 2 support (security must be `wpapsk` and `wpa3_support` must be true). ### Read-Only diff --git a/examples/resources/unifi_wlan/resource.tf b/examples/resources/unifi_wlan/resource.tf index df92bfc..c978169 100644 --- a/examples/resources/unifi_wlan/resource.tf +++ b/examples/resources/unifi_wlan/resource.tf @@ -24,6 +24,10 @@ resource "unifi_wlan" "wifi" { passphrase = "12345678" security = "wpapsk" + # enable WPA2/WPA3 support + wpa3_support = true + wpa3_transition = true + network_id = unifi_network.vlan.id ap_group_ids = [data.unifi_ap_group.default.id] user_group_id = data.unifi_user_group.default.id diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 895b9f0..2c22143 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -15,6 +15,9 @@ import ( var ( controllerV5 = version.Must(version.NewVersion("5.0.0")) controllerV6 = version.Must(version.NewVersion("6.0.0")) + + // https://community.ui.com/releases/UniFi-Network-Controller-6-1-61/62f1ad38-1ac5-430c-94b0-becbb8f71d7d + controllerVersionWPA3 = version.Must(version.NewVersion("6.1.61")) ) func init() { diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index e831a78..9316bb7 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -85,16 +85,20 @@ func preCheck(t *testing.T) { } } -func preCheckV6Only(t *testing.T) { +func preCheckMinVersion(t *testing.T, min *version.Version) { v, err := version.NewVersion(testClient.Version()) if err != nil { t.Fatalf("error parsing version: %s", err) } - if v.LessThan(controllerV6) { - t.Skipf("skipping test on controller version %q", v) + if v.LessThan(min) { + t.Skipf("skipping test on controller version %q (need at least %q)", v, min) } } +func preCheckV6Only(t *testing.T) { + preCheckMinVersion(t, controllerV6) +} + func preCheckV5Only(t *testing.T) { v, err := version.NewVersion(testClient.Version()) if err != nil { diff --git a/internal/provider/resource_wlan.go b/internal/provider/resource_wlan.go index f56793f..4f05a58 100644 --- a/internal/provider/resource_wlan.go +++ b/internal/provider/resource_wlan.go @@ -3,10 +3,10 @@ package provider import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "log" "strings" + "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" "github.com/paultyng/go-unifi/unifi" @@ -53,6 +53,16 @@ func resourceWLAN() *schema.Resource { Required: true, ValidateFunc: validation.StringInSlice([]string{"wpapsk", "wpaeap", "open"}, false), }, + "wpa3_support": { + Description: "Enable WPA 3 support (security must be `wpapsk`).", + Type: schema.TypeBool, + Optional: true, + }, + "wpa3_transition": { + Description: "Enable WPA 3 and WPA 2 support (security must be `wpapsk` and `wpa3_support` must be true).", + Type: schema.TypeBool, + Optional: true, + }, "passphrase": { Description: "The passphrase for the network, this is only required if `security` is not set to `open`.", Type: schema.TypeString, @@ -204,6 +214,22 @@ func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*uni passphrase = "" } + wpa3 := d.Get("wpa3_support").(bool) + wpa3Transition := d.Get("wpa3_transition").(bool) + switch security { + case "wpapsk": + // nothing + default: + if wpa3 || wpa3Transition { + return nil, fmt.Errorf("wpa3_support and wpa3_transition are only valid for security type wpapsk") + } + } + if v := c.ControllerVersion(); v.LessThanOrEqual(controllerVersionWPA3) { + if wpa3 || wpa3Transition { + return nil, fmt.Errorf("WPA 3 support is not available on controller version %q, you must be on %q or higher", v, controllerVersionWPA3) + } + } + macFilterEnabled := d.Get("mac_filter_enabled").(bool) macFilterList, err := setToStringSlice(d.Get("mac_filter_list").(*schema.Set)) if err != nil { @@ -259,6 +285,8 @@ func resourceWLANGetResourceData(d *schema.ResourceData, meta interface{}) (*uni ApGroupIDs: apGroupIDs, UserGroupID: d.Get("user_group_id").(string), Security: security, + WPA3Support: wpa3, + WPA3Transition: wpa3Transition, MulticastEnhanceEnabled: d.Get("multicast_enhance").(bool), MACFilterEnabled: macFilterEnabled, MACFilterList: macFilterList, @@ -321,9 +349,14 @@ func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData, meta security := resp.Security passphrase := resp.XPassphrase + wpa3 := false + wpa3Transition := false switch security { case "open": passphrase = "" + case "wpapsk": + wpa3 = resp.WPA3Support + wpa3Transition = resp.WPA3Transition } macFilterEnabled := resp.MACFilterEnabled @@ -349,6 +382,8 @@ func resourceWLANSetResourceData(resp *unifi.WLAN, d *schema.ResourceData, meta d.Set("hide_ssid", resp.HideSSID) d.Set("is_guest", resp.IsGuest) d.Set("security", security) + d.Set("wpa3_support", wpa3) + d.Set("wpa3_transition", wpa3Transition) d.Set("multicast_enhance", resp.MulticastEnhanceEnabled) d.Set("mac_filter_enabled", macFilterEnabled) d.Set("mac_filter_list", macFilterList) diff --git a/internal/provider/resource_wlan_test.go b/internal/provider/resource_wlan_test.go index 3df45ac..47a3fe2 100644 --- a/internal/provider/resource_wlan_test.go +++ b/internal/provider/resource_wlan_test.go @@ -285,6 +285,48 @@ func TestAccWLAN_uapsd(t *testing.T) { }) } +func TestAccWLAN_wpa3(t *testing.T) { + vlanID := getTestVLAN(t) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + preCheck(t) + preCheckV6Only(t) + preCheckMinVersion(t, controllerVersionWPA3) + wlanPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: func(*terraform.State) error { + // TODO: actual CheckDestroy + + <-wlanConcurrency + return nil + }, + Steps: []resource.TestStep{ + { + Config: testAccWLANConfig_wpa3(vlanID, false), + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + { + Config: testAccWLANConfig_wpa3(vlanID, true), + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + { + Config: testAccWLANConfig_wpa3(vlanID, false), + Check: resource.ComposeTestCheckFunc( + // testCheckNetworkExists(t, "name"), + ), + }, + importStep("unifi_wlan.test"), + }, + }) +} + func testAccWLANConfig_wpapsk(vlanID int) string { return fmt.Sprintf(` data "unifi_ap_group" "default" { @@ -527,3 +569,33 @@ resource "unifi_wlan" "test" { } `, vlanID) } + +func testAccWLANConfig_wpa3(vlanID int, wpa3Transition bool) string { + return fmt.Sprintf(` +data "unifi_ap_group" "default" { +} + +data "unifi_user_group" "default" { +} + +resource "unifi_network" "test" { + name = "tfacc" + purpose = "corporate" + + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) + vlan_id = %[1]d +} + +resource "unifi_wlan" "test" { + name = "tfacc-wpapsk" + network_id = unifi_network.test.id + passphrase = "12345678" + ap_group_ids = [data.unifi_ap_group.default.id] + user_group_id = data.unifi_user_group.default.id + security = "wpapsk" + + wpa3_support = true + wpa3_transition = %[2]t +} +`, vlanID, wpa3Transition) +}