Skip to content

Commit

Permalink
AUTH-6690 Add more fields to access application destinations
Browse files Browse the repository at this point in the history
  • Loading branch information
Eduardo Gomes committed Jan 15, 2025
1 parent 8a39731 commit 8146b10
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 17 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.23.3

require (
github.com/agext/levenshtein v1.2.3 // indirect
github.com/cloudflare/cloudflare-go v0.113.0
github.com/cloudflare/cloudflare-go v0.114.0
github.com/fatih/color v1.16.0 // indirect
github.com/google/uuid v1.6.0
github.com/hashicorp/errwrap v1.1.0 // indirect
Expand All @@ -28,7 +28,7 @@ require (
golang.org/x/net v0.34.0
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.8.0 // indirect
golang.org/x/time v0.9.0 // indirect
)

require (
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vc
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/cloudflare-go v0.113.0 h1:qnOXmA6RbgZ4rg5gNBK5QGk0Pzbv8pnUYV3C4+8CU6w=
github.com/cloudflare/cloudflare-go v0.113.0/go.mod h1:Dlm4BAnycHc0i8yLxQZb9b+OlMwYOAoDJsUOEFgpVvo=
github.com/cloudflare/cloudflare-go v0.114.0 h1:ucoti4/7Exo0XQ+rzpn1H+IfVVe++zgiM+tyKtf0HUA=
github.com/cloudflare/cloudflare-go v0.114.0/go.mod h1:O7fYfFfA6wKqKFn2QIR9lhj7FDw6VQCGOY6hd2TBtd0=
github.com/cloudflare/cloudflare-go/v2 v2.4.0 h1:gys/26GoVDklgfq8NYV39WgvOEwzK/XAqYObmnI6iFg=
github.com/cloudflare/cloudflare-go/v2 v2.4.0/go.mod h1:AoIzb05z/rvdJLztPct4tSa+3IqXJJ6c+pbUFMOlTr8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down Expand Up @@ -266,6 +268,8 @@ golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package sdkv2provider
import (
"context"
"fmt"
"github.com/google/uuid"
"log"
"os"
"regexp"
"strings"
"testing"

"github.com/cloudflare/cloudflare-go"
Expand Down Expand Up @@ -954,11 +956,18 @@ func TestAccCloudflareAccessApplication_WithDestinations(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "destinations.#", "2"),
resource.TestCheckResourceAttr(name, "destinations.#", "4"),
resource.TestCheckResourceAttr(name, "destinations.0.type", "public"),
resource.TestCheckResourceAttr(name, "destinations.0.uri", fmt.Sprintf("d1.%s.%s", rnd, domain)),
resource.TestCheckResourceAttr(name, "destinations.1.type", "public"),
resource.TestCheckResourceAttr(name, "destinations.1.uri", fmt.Sprintf("d2.%s.%s", rnd, domain)),
resource.TestCheckResourceAttr(name, "destinations.2.type", "private"),
resource.TestCheckResourceAttr(name, "destinations.2.hostname", fmt.Sprintf("d1.%s.%s.privatenetwork", rnd, domain)),
resource.TestCheckResourceAttr(name, "destinations.2.port_range", "443"),
resource.TestCheckResourceAttr(name, "destinations.2.l4_protocol", "udp"),
resource.TestCheckResourceAttr(name, "destinations.3.type", "private"),
resource.TestCheckResourceAttr(name, "destinations.3.cidr", "127.0.0.2/32"),
resource.TestCheckResourceAttrWith(name, "destinations.3.vnet_id", uuid.Validate),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "type", "self_hosted"),
resource.TestCheckResourceAttr(name, "session_duration", "24h"),
Expand Down Expand Up @@ -1248,7 +1257,7 @@ resource "cloudflare_zero_trust_access_application" "%[1]s" {
name = "rank"
}
}
hybrid_and_implicit_options {
return_id_token_from_authorization_endpoint = true
return_access_token_from_authorization_endpoint = true
Expand Down Expand Up @@ -1485,6 +1494,10 @@ resource "cloudflare_zero_trust_access_application" "%[1]s" {
}

func testAccCloudflareAccessApplicationWithDestinations(rnd string, domain string, identifier *cloudflare.ResourceContainer) string {
// make sure the seed string has at least 16 bytes to fill the UUID
vnetSeed := strings.Repeat(rnd, 16)
vnetID, _ := uuid.FromBytes([]byte(vnetSeed[:16]))

return fmt.Sprintf(`
resource "cloudflare_zero_trust_access_application" "%[1]s" {
%[3]s_id = "%[4]s"
Expand All @@ -1498,8 +1511,19 @@ resource "cloudflare_zero_trust_access_application" "%[1]s" {
destinations {
uri = "d2.%[1]s.%[2]s"
}
destinations {
type = "private"
hostname = "d1.%[1]s.%[2]s.privatenetwork"
port_range = "443"
l4_protocol = "udp"
}
destinations {
type = "private"
cidr = "127.0.0.2"
vnet_id = "%[5]s"
}
}
`, rnd, domain, identifier.Type, identifier.Identifier)
`, rnd, domain, identifier.Type, identifier.Identifier, vnetID)
}

func testAccCloudflareAccessApplicationWithDestinations2(rnd string, domain string, identifier *cloudflare.ResourceContainer) string {
Expand Down
119 changes: 107 additions & 12 deletions internal/sdkv2provider/schema_cloudflare_access_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sdkv2provider
import (
"fmt"
"regexp"
"strings"
"time"

"github.com/cloudflare/cloudflare-go"
Expand Down Expand Up @@ -63,7 +64,7 @@ func resourceCloudflareAccessApplicationSchema() map[string]*schema.Schema {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"public", "private"}, false),
ValidateFunc: validation.StringInSlice([]string{"public"}, false),
Description: fmt.Sprintf("The type of the primary domain. %s", renderAvailableDocumentationValuesStringSlice([]string{"public", "private"})),
DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool {
appType := d.Get("type").(string)
Expand Down Expand Up @@ -91,8 +92,57 @@ func resourceCloudflareAccessApplicationSchema() map[string]*schema.Schema {
},
"uri": {
Type: schema.TypeString,
Required: true,
Description: "The URI of the destination. Public destinations can include a domain and path with wildcards. Private destinations are an early access feature and gated behind a feature flag. Private destinations support private IPv4, IPv6, and Server Name Indications (SNI) with optional port ranges.",
Optional: true,
Description: "The public URI of the destination. Can include a domain and path with wildcards. Only valid when type=public",
},
"hostname": {
Type: schema.TypeString,
Optional: true,
Description: "The private hostname of the destination. Only valid when type=private. Private hostnames currently match only Server Name Indications (SNI). Private destinations are an early access feature and gated behind a feature flag.",
},
"cidr": {
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressOnRefresh: true,
DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool {
// /32 is the same as ommiting the mask for an IPV4
// And /128 for an ipv6
oldIsIpv4 := strings.Count(oldValue, ".") == 3
newIsIpv4 := strings.Count(newValue, ".") == 3

if oldIsIpv4 && newIsIpv4 {
return (strings.HasSuffix(oldValue, "/32") && !strings.Contains(newValue, "/")) ||
(strings.HasSuffix(newValue, "/32") && !strings.Contains(oldValue, "/"))
}

return (strings.HasSuffix(oldValue, "/128") && !strings.Contains(newValue, "/")) ||
(strings.HasSuffix(newValue, "/128") && !strings.Contains(oldValue, "/"))
},
Description: "The private CIDR of the destination. Only valid when type=private. IPs are computed as /32 cidr. Private destinations are an early access feature and gated behind a feature flag.",
},
"port_range": {
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressOnRefresh: true,
DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool {
// Passing a number is the same a range of length 1
// E.g., "443" == "443-443"
return newValue == fmt.Sprintf("%s-%s", oldValue, oldValue) || oldValue == fmt.Sprintf("%s-%s", newValue, newValue)
},
Description: "The port range of the destination. Only valid when type=private. Single ports are supported. Private destinations are an early access feature and gated behind a feature flag.",
},
"vnet_id": {
Type: schema.TypeString,
Optional: true,
Description: "The VNet ID of the destination. Only valid when type=private. Private destinations are an early access feature and gated behind a feature flag.",
},
"l4_protocol": {
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"tcp", "udp"}, false),
Optional: true,
Description: "The l4 protocol that matches this destination. Only valid when type=private. Private destinations are an early access feature and gated behind a feature flag.",
},
},
},
Expand Down Expand Up @@ -1030,6 +1080,38 @@ func convertSaasSchemaToStruct(d *schema.ResourceData) *cloudflare.SaasApplicati
}
}

func convertPublicDestinationStruct(payload map[string]any) cloudflare.AccessDestination {
dest := cloudflare.AccessDestination{
Type: cloudflare.AccessDestinationPublic,
}
if uri, ok := payload["uri"].(string); ok {
dest.URI = uri
}
return dest
}

func convertPrivateDestinationStruct(payload map[string]any) cloudflare.AccessDestination {
dest := cloudflare.AccessDestination{
Type: cloudflare.AccessDestinationPrivate,
}
if hostname, ok := payload["hostname"].(string); ok {
dest.Hostname = hostname
}
if ip, ok := payload["cidr"].(string); ok {
dest.CIDR = ip
}
if portRange, ok := payload["port_range"].(string); ok {
dest.PortRange = portRange
}
if l4Protocol, ok := payload["l4_protocol"].(string); ok {
dest.L4Protocol = l4Protocol
}
if vnetID, ok := payload["vnet_id"].(string); ok {
dest.VnetID = vnetID
}
return dest
}

func convertDestinationsToStruct(destinationPayloads []interface{}) ([]cloudflare.AccessDestination, error) {
destinations := make([]cloudflare.AccessDestination, len(destinationPayloads))
for i, dp := range destinationPayloads {
Expand All @@ -1038,17 +1120,13 @@ func convertDestinationsToStruct(destinationPayloads []interface{}) ([]cloudflar
if dType, ok := dpMap["type"].(string); ok {
switch dType {
case "public":
destinations[i].Type = cloudflare.AccessDestinationPublic
destinations[i] = convertPublicDestinationStruct(dpMap)
case "private":
destinations[i].Type = cloudflare.AccessDestinationPrivate
destinations[i] = convertPrivateDestinationStruct(dpMap)
default:
return nil, fmt.Errorf("failed to parse destination type: value must be one of public or private")
}
}

if uri, ok := dpMap["uri"].(string); ok {
destinations[i].URI = uri
}
}

return destinations, nil
Expand Down Expand Up @@ -1558,10 +1636,27 @@ func convertScimConfigMappingsStructsToSchema(mappingsData []*cloudflare.AccessA
func convertDestinationsToSchema(destinations []cloudflare.AccessDestination) []interface{} {
schemas := make([]interface{}, len(destinations))
for i, dest := range destinations {
schemas[i] = map[string]interface{}{
"type": string(dest.Type),
"uri": dest.URI,
resultDest := make(map[string]interface{})
resultDest["type"] = string(dest.Type)
if dest.URI != "" {
resultDest["uri"] = dest.URI
}
if dest.Hostname != "" {
resultDest["hostname"] = dest.Hostname
}
if dest.CIDR != "" {
resultDest["cidr"] = dest.CIDR
}
if dest.PortRange != "" {
resultDest["port_range"] = dest.PortRange
}
if dest.L4Protocol != "" {
resultDest["l4_protocol"] = dest.L4Protocol
}
if dest.VnetID != "" {
resultDest["vnet_id"] = dest.VnetID
}
schemas[i] = resultDest
}
return schemas
}

0 comments on commit 8146b10

Please sign in to comment.