Skip to content

Commit

Permalink
Fix: added Resource validation for wildcard address and enabled brows…
Browse files Browse the repository at this point in the history
…er shortcut (Twingate#418)

* wip

* added validation for wildcard address and enabled browser shortcut

* fix checking if attribute present in TF config

* added wildcard address validation
  • Loading branch information
vmanilo authored Oct 27, 2023
1 parent b902587 commit 6001536
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 4 deletions.
34 changes: 34 additions & 0 deletions twingate/internal/provider/resource/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,37 @@ func stringPtr(s string) *string {
func boolPtr(b bool) *bool {
return &b
}

func TestIsWildcardAddress(t *testing.T) {
cases := []struct {
address string
expected bool
}{
{
address: "hello.com",
expected: false,
},
{
address: "*.hello.com",
expected: true,
},
{
address: "redis-?-blah.internal",
expected: true,
},
{
address: "redis-*-blah.internal",
expected: true,
},
{
address: "10.0.0.0/16",
expected: true,
},
}

for n, c := range cases {
t.Run(fmt.Sprintf("case_%d", n), func(t *testing.T) {
assert.Equal(t, c.expected, isWildcardAddress(c.address))
})
}
}
27 changes: 23 additions & 4 deletions twingate/internal/provider/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"log"
"reflect"
"regexp"
"strings"

"github.com/Twingate/terraform-provider-twingate/twingate/internal/attr"
Expand All @@ -17,9 +18,10 @@ import (
)

var (
ErrPortsWithPolicyAllowAll = errors.New(model.PolicyAllowAll + " policy does not allow specifying ports.")
ErrPortsWithPolicyDenyAll = errors.New(model.PolicyDenyAll + " policy does not allow specifying ports.")
ErrPolicyRestrictedWithoutPorts = errors.New(model.PolicyRestricted + " policy requires specifying ports.")
ErrPortsWithPolicyAllowAll = errors.New(model.PolicyAllowAll + " policy does not allow specifying ports.")
ErrPortsWithPolicyDenyAll = errors.New(model.PolicyDenyAll + " policy does not allow specifying ports.")
ErrPolicyRestrictedWithoutPorts = errors.New(model.PolicyRestricted + " policy requires specifying ports.")
ErrWildcardAddressWithEnabledShortcut = errors.New("Resources with a CIDR range or wildcard can't have the browser shortcut enabled.")
)

func Resource() *schema.Resource { //nolint:funlen
Expand Down Expand Up @@ -497,13 +499,30 @@ func convertResource(data *schema.ResourceData) (*model.Resource, error) {
}

isBrowserShortcutEnabled, ok := data.GetOkExists(attr.IsBrowserShortcutEnabled) //nolint
if val := isBrowserShortcutEnabled.(bool); ok {
if val := isBrowserShortcutEnabled.(bool); ok && isAttrKnown(data, attr.IsBrowserShortcutEnabled) {
res.IsBrowserShortcutEnabled = &val
}

if res.IsBrowserShortcutEnabled != nil && *res.IsBrowserShortcutEnabled && isWildcardAddress(res.Address) {
return nil, ErrWildcardAddressWithEnabledShortcut
}

return res, nil
}

var cidrRgxp = regexp.MustCompile(`(\d{1,3}\.){3}\d{1,3}(/\d+)?`)

func isWildcardAddress(address string) bool {
return strings.ContainsAny(address, "*?") || cidrRgxp.MatchString(address)
}

func isAttrKnown(data *schema.ResourceData, attr string) bool {
cfg := data.GetRawConfig()
val := cfg.GetAttr(attr)

return !val.IsNull() && val.IsKnown()
}

func getOptionalString(data *schema.ResourceData, attr string) *string {
var result *string

Expand Down
121 changes: 121 additions & 0 deletions twingate/internal/test/acctests/resource/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2421,3 +2421,124 @@ func TestAccTwingateResourcePolicyTransitionAllowAllToDenyAll(t *testing.T) {
},
})
}

func TestAccTwingateResourceWithBrowserOption(t *testing.T) {
const terraformResourceName = "test40"
theResource := acctests.TerraformResource(terraformResourceName)
remoteNetworkName := test.RandomName()
resourceName := test.RandomResourceName()
wildcardAddress := "*.acc-test.com"

sdk.Test(t, sdk.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []sdk.TestStep{
{
Config: createResourceWithoutBrowserOption(terraformResourceName, remoteNetworkName, resourceName, wildcardAddress),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
),
},
{
Config: createResourceWithBrowserOption(terraformResourceName, remoteNetworkName, resourceName, wildcardAddress, false),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
),
},
{
Config: createResourceWithBrowserOption(terraformResourceName, remoteNetworkName, resourceName, wildcardAddress, true),
ExpectError: regexp.MustCompile(resource.ErrWildcardAddressWithEnabledShortcut.Error()),
},
},
})
}

func TestAccTwingateResourceWithBrowserOptionFailOnUpdate(t *testing.T) {
const terraformResourceName = "test41"
theResource := acctests.TerraformResource(terraformResourceName)
remoteNetworkName := test.RandomName()
resourceName := test.RandomResourceName()
wildcardAddress := "*.acc-test.com"
simpleAddress := "acc-test.com"

sdk.Test(t, sdk.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []sdk.TestStep{
{
Config: createResourceWithoutBrowserOption(terraformResourceName, remoteNetworkName, resourceName, simpleAddress),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
),
},
{
Config: createResourceWithBrowserOption(terraformResourceName, remoteNetworkName, resourceName, simpleAddress, true),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
),
},
{
Config: createResourceWithBrowserOption(terraformResourceName, remoteNetworkName, resourceName, wildcardAddress, true),
ExpectError: regexp.MustCompile(resource.ErrWildcardAddressWithEnabledShortcut.Error()),
},
},
})
}

func TestAccTwingateResourceWithBrowserOptionRecovered(t *testing.T) {
const terraformResourceName = "test42"
theResource := acctests.TerraformResource(terraformResourceName)
remoteNetworkName := test.RandomName()
resourceName := test.RandomResourceName()
wildcardAddress := "*.acc-test.com"
simpleAddress := "acc-test.com"

sdk.Test(t, sdk.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []sdk.TestStep{
{
Config: createResourceWithBrowserOption(terraformResourceName, remoteNetworkName, resourceName, simpleAddress, true),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
),
},
{
Config: createResourceWithoutBrowserOption(terraformResourceName, remoteNetworkName, resourceName, wildcardAddress),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
),
},
},
})
}

func createResourceWithoutBrowserOption(name, networkName, resourceName, address string) string {
return fmt.Sprintf(`
resource "twingate_remote_network" "%[1]s" {
name = "%[2]s"
}
resource "twingate_resource" "%[1]s" {
name = "%[3]s"
address = "%[4]s"
remote_network_id = twingate_remote_network.%[1]s.id
}
`, name, networkName, resourceName, address)
}

func createResourceWithBrowserOption(name, networkName, resourceName, address string, browserOption bool) string {
return fmt.Sprintf(`
resource "twingate_remote_network" "%[1]s" {
name = "%[2]s"
}
resource "twingate_resource" "%[1]s" {
name = "%[3]s"
address = "%[4]s"
remote_network_id = twingate_remote_network.%[1]s.id
is_browser_shortcut_enabled = %[5]v
}
`, name, networkName, resourceName, address, browserOption)
}

0 comments on commit 6001536

Please sign in to comment.