diff --git a/docs/resources/access_application.md b/docs/resources/access_application.md
index 51ecf9a2423..f86241e53d7 100644
--- a/docs/resources/access_application.md
+++ b/docs/resources/access_application.md
@@ -90,7 +90,9 @@ resource "cloudflare_zero_trust_access_application" "infra-app-example" {
- `custom_deny_url` (String) Option that redirects to a custom URL when a user is denied access to the application via identity based rules.
- `custom_non_identity_deny_url` (String) Option that redirects to a custom URL when a user is denied access to the application via non identity rules.
- `custom_pages` (Set of String) The custom pages selected for the application.
+- `destinations` (Block List) A destination secured by Access. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`. Supersedes `self_hosted_domains` to allow for more flexibility in defining different types of destinations. Conflicts with `self_hosted_domains`. (see [below for nested schema](#nestedblock--destinations))
- `domain` (String) The primary hostname and path that Access will secure. If the app is visible in the App Launcher dashboard, this is the domain that will be displayed.
+- `domain_type` (String) The type of the primary domain. Available values: `public`, `private`.
- `enable_binding_cookie` (Boolean) Option to provide increased security against compromised authorization tokens and CSRF attacks by requiring an additional "binding" cookie on requests. Defaults to `false`.
- `footer_links` (Block Set) The footer links of the app launcher. (see [below for nested schema](#nestedblock--footer_links))
- `header_bg_color` (String) The background color of the header bar in the app launcher.
@@ -103,7 +105,7 @@ resource "cloudflare_zero_trust_access_application" "infra-app-example" {
- `saas_app` (Block List, Max: 1) SaaS configuration for the Access Application. (see [below for nested schema](#nestedblock--saas_app))
- `same_site_cookie_attribute` (String) Defines the same-site cookie setting for access tokens. Available values: `none`, `lax`, `strict`.
- `scim_config` (Block List, Max: 1) Configuration for provisioning to this application via SCIM. This is currently in closed beta. (see [below for nested schema](#nestedblock--scim_config))
-- `self_hosted_domains` (Set of String) List of domains that access will secure. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`.
+- `self_hosted_domains` (Set of String, Deprecated) List of public domains secured by Access. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`. Deprecated in favor of `destinations` and will be removed in the next major version. Conflicts with `destinations`.
- `service_auth_401_redirect` (Boolean) Option to return a 401 status code in service authentication rules on failed requests. Defaults to `false`.
- `session_duration` (String) How often a user will be forced to re-authorise. Must be in the format `48h` or `2h45m`. Defaults to `24h`.
- `skip_app_launcher_login_page` (Boolean) Option to skip the App Launcher landing page. Defaults to `false`.
@@ -133,6 +135,18 @@ Optional:
- `max_age` (Number) The maximum time a preflight request will be cached.
+
+### Nested Schema for `destinations`
+
+Required:
+
+- `uri` (String) 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:
+
+- `type` (String) The destination type. Available values: `public`, `private`. Defaults to `public`.
+
+
### Nested Schema for `footer_links`
diff --git a/docs/resources/zero_trust_access_application.md b/docs/resources/zero_trust_access_application.md
index 14e48e0773f..c2762f49c6d 100644
--- a/docs/resources/zero_trust_access_application.md
+++ b/docs/resources/zero_trust_access_application.md
@@ -71,7 +71,9 @@ resource "cloudflare_zero_trust_access_application" "staging_app" {
- `custom_deny_url` (String) Option that redirects to a custom URL when a user is denied access to the application via identity based rules.
- `custom_non_identity_deny_url` (String) Option that redirects to a custom URL when a user is denied access to the application via non identity rules.
- `custom_pages` (Set of String) The custom pages selected for the application.
+- `destinations` (Block List) A destination secured by Access. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`. Supersedes `self_hosted_domains` to allow for more flexibility in defining different types of destinations. Conflicts with `self_hosted_domains`. (see [below for nested schema](#nestedblock--destinations))
- `domain` (String) The primary hostname and path that Access will secure. If the app is visible in the App Launcher dashboard, this is the domain that will be displayed.
+- `domain_type` (String) The type of the primary domain. Available values: `public`, `private`.
- `enable_binding_cookie` (Boolean) Option to provide increased security against compromised authorization tokens and CSRF attacks by requiring an additional "binding" cookie on requests. Defaults to `false`.
- `footer_links` (Block Set) The footer links of the app launcher. (see [below for nested schema](#nestedblock--footer_links))
- `header_bg_color` (String) The background color of the header bar in the app launcher.
@@ -84,7 +86,7 @@ resource "cloudflare_zero_trust_access_application" "staging_app" {
- `saas_app` (Block List, Max: 1) SaaS configuration for the Access Application. (see [below for nested schema](#nestedblock--saas_app))
- `same_site_cookie_attribute` (String) Defines the same-site cookie setting for access tokens. Available values: `none`, `lax`, `strict`.
- `scim_config` (Block List, Max: 1) Configuration for provisioning to this application via SCIM. This is currently in closed beta. (see [below for nested schema](#nestedblock--scim_config))
-- `self_hosted_domains` (Set of String) List of domains that access will secure. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`.
+- `self_hosted_domains` (Set of String, Deprecated) List of public domains secured by Access. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`. Deprecated in favor of `destinations` and will be removed in the next major version. Conflicts with `destinations`.
- `service_auth_401_redirect` (Boolean) Option to return a 401 status code in service authentication rules on failed requests. Defaults to `false`.
- `session_duration` (String) How often a user will be forced to re-authorise. Must be in the format `48h` or `2h45m`. Defaults to `24h`.
- `skip_app_launcher_login_page` (Boolean) Option to skip the App Launcher landing page. Defaults to `false`.
@@ -114,6 +116,18 @@ Optional:
- `max_age` (Number) The maximum time a preflight request will be cached.
+
+### Nested Schema for `destinations`
+
+Required:
+
+- `uri` (String) 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:
+
+- `type` (String) The destination type. Available values: `public`, `private`. Defaults to `public`.
+
+
### Nested Schema for `footer_links`
diff --git a/go.mod b/go.mod
index bf7493566ea..53233cb9bd2 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.23.3
require (
github.com/agext/levenshtein v1.2.3 // indirect
- github.com/cloudflare/cloudflare-go v0.110.0
+ github.com/cloudflare/cloudflare-go v0.110.1-0.20241125012349-7c091bc8c0dd
github.com/fatih/color v1.16.0 // indirect
github.com/google/uuid v1.6.0
github.com/hashicorp/errwrap v1.1.0 // indirect
diff --git a/go.sum b/go.sum
index 258d630b28a..d9348e3d96c 100644
--- a/go.sum
+++ b/go.sum
@@ -47,6 +47,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.110.0 h1:aBKKUXwRWqErd4rITsnCLESOacxxset/BcpdXn23900=
github.com/cloudflare/cloudflare-go v0.110.0/go.mod h1:2ZZ+EkmThmd6pkZ56UKGXWpz2wsjeqoTg93P4+VSmMg=
+github.com/cloudflare/cloudflare-go v0.110.1-0.20241125012349-7c091bc8c0dd h1:XtqhEh/uj3YpDQlMVQPMm8HinUghbZcEoT24o586Tbw=
+github.com/cloudflare/cloudflare-go v0.110.1-0.20241125012349-7c091bc8c0dd/go.mod h1:DEgVq31NsC8LFxOQbz8QYBj5pvaAnLUcr9MMHeEUzSI=
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=
diff --git a/internal/consts/provider.go b/internal/consts/provider.go
index a92b4a14a2f..74f5acdb078 100644
--- a/internal/consts/provider.go
+++ b/internal/consts/provider.go
@@ -110,4 +110,10 @@ const (
// Schema description for all ID fields.
IDSchemaDescription = "The identifier of this resource."
+
+ // Schema key for access app destination
+ DestinationsSchemaKey = "destinations"
+
+ // Schema key for access app self_hosted_domains
+ SelfHostedDomainsSchemaKey = "self_hosted_domains"
)
diff --git a/internal/sdkv2provider/resource_cloudflare_access_application.go b/internal/sdkv2provider/resource_cloudflare_access_application.go
index 22c22d8eb12..f543ac191f4 100644
--- a/internal/sdkv2provider/resource_cloudflare_access_application.go
+++ b/internal/sdkv2provider/resource_cloudflare_access_application.go
@@ -88,7 +88,23 @@ func resourceCloudflareAccessApplicationCreate(ctx context.Context, d *schema.Re
}
if value, ok := d.GetOk("self_hosted_domains"); ok {
- newAccessApplication.SelfHostedDomains = expandInterfaceToStringList(value.(*schema.Set).List())
+ selfHostedDomains := expandInterfaceToStringList(value.(*schema.Set).List())
+ destinations := make([]cloudflare.AccessDestination, len(selfHostedDomains))
+ for i, uri := range selfHostedDomains {
+ destinations[i] = cloudflare.AccessDestination{
+ Type: cloudflare.AccessDestinationPublic,
+ URI: uri,
+ }
+ }
+ newAccessApplication.Destinations = destinations
+ }
+
+ if value, ok := d.GetOk("destinations"); ok {
+ destinations, err := convertDestinationsToStruct(value.([]interface{}))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ newAccessApplication.Destinations = destinations
}
if _, ok := d.GetOk("cors_headers"); ok {
@@ -258,7 +274,17 @@ func resourceCloudflareAccessApplicationRead(ctx context.Context, d *schema.Reso
}
if _, ok := d.GetOk("self_hosted_domains"); ok {
- d.Set("self_hosted_domains", accessApplication.SelfHostedDomains)
+ publicDomains := make([]string, 0, len(accessApplication.Destinations))
+ for _, dest := range accessApplication.Destinations {
+ if dest.Type == cloudflare.AccessDestinationPublic {
+ publicDomains = append(publicDomains, dest.URI)
+ }
+ }
+ d.Set("self_hosted_domains", publicDomains)
+ }
+
+ if _, ok := d.GetOk("destinations"); ok {
+ d.Set("destinations", convertDestinationsToSchema(accessApplication.Destinations))
}
scimConfig := convertScimConfigStructToSchema(accessApplication.SCIMConfig)
@@ -320,7 +346,23 @@ func resourceCloudflareAccessApplicationUpdate(ctx context.Context, d *schema.Re
}
if value, ok := d.GetOk("self_hosted_domains"); ok {
- updatedAccessApplication.SelfHostedDomains = expandInterfaceToStringList(value.(*schema.Set).List())
+ selfHostedDomains := expandInterfaceToStringList(value.(*schema.Set).List())
+ destinations := make([]cloudflare.AccessDestination, len(selfHostedDomains))
+ for i, uri := range selfHostedDomains {
+ destinations[i] = cloudflare.AccessDestination{
+ Type: cloudflare.AccessDestinationPublic,
+ URI: uri,
+ }
+ }
+ updatedAccessApplication.Destinations = destinations
+ }
+
+ if value, ok := d.GetOk("destinations"); ok {
+ destinations, err := convertDestinationsToStruct(value.([]interface{}))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ updatedAccessApplication.Destinations = destinations
}
if d.HasChange("policies") {
diff --git a/internal/sdkv2provider/schema_cloudflare_access_application.go b/internal/sdkv2provider/schema_cloudflare_access_application.go
index eae91df2e23..65a0b1376a4 100644
--- a/internal/sdkv2provider/schema_cloudflare_access_application.go
+++ b/internal/sdkv2provider/schema_cloudflare_access_application.go
@@ -54,13 +54,53 @@ func resourceCloudflareAccessApplicationSchema() map[string]*schema.Schema {
return oldValue == newValue
},
},
+ "domain_type": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ ValidateFunc: validation.StringInSlice([]string{"public", "private"}, 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)
+ // Suppress the diff if it's an app type that doesn't need a `domain` value.
+ if appType == "infrastructure" {
+ return true
+ }
+
+ return oldValue == newValue
+ },
+ },
+ "destinations": {
+ Type: schema.TypeList,
+ Optional: true,
+ ConflictsWith: []string{consts.SelfHostedDomainsSchemaKey},
+ Description: "A destination secured by Access. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`. Supersedes `self_hosted_domains` to allow for more flexibility in defining different types of destinations.",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "type": {
+ Type: schema.TypeString,
+ Default: "public",
+ Optional: true,
+ ValidateFunc: validation.StringInSlice([]string{"public", "private"}, false),
+ Description: fmt.Sprintf("The destination type. %s", renderAvailableDocumentationValuesStringSlice([]string{"public", "private"})),
+ },
+ "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.",
+ },
+ },
+ },
+ },
"self_hosted_domains": {
- Type: schema.TypeSet,
- Optional: true,
+ Type: schema.TypeSet,
+ Optional: true,
+ ConflictsWith: []string{consts.DestinationsSchemaKey},
Elem: &schema.Schema{
Type: schema.TypeString,
},
- Description: "List of domains that access will secure. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`",
+ Description: "List of public domains secured by Access. Only present for self_hosted, vnc, and ssh applications. Always includes the value set as `domain`. Deprecated in favor of `destinations` and will be removed in the next major version.",
+ Deprecated: "Use `destinations` instead",
},
"type": {
Type: schema.TypeString,
@@ -997,6 +1037,30 @@ func convertSaasSchemaToStruct(d *schema.ResourceData) *cloudflare.SaasApplicati
return &SaasConfig
}
+func convertDestinationsToStruct(destinationPayloads []interface{}) ([]cloudflare.AccessDestination, error) {
+ destinations := make([]cloudflare.AccessDestination, len(destinationPayloads))
+ for i, dp := range destinationPayloads {
+ dpMap := dp.(map[string]interface{})
+
+ if dType, ok := dpMap["type"].(string); ok {
+ switch dType {
+ case "public":
+ destinations[i].Type = cloudflare.AccessDestinationPublic
+ case "private":
+ destinations[i].Type = cloudflare.AccessDestinationPrivate
+ 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
+}
+
func convertTargetContextsToStruct(d *schema.ResourceData) (*[]cloudflare.AccessInfrastructureTargetContext, error) {
TargetContexts := []cloudflare.AccessInfrastructureTargetContext{}
if value, ok := d.GetOk("target_criteria"); ok {
@@ -1447,3 +1511,14 @@ func convertScimConfigMappingsStructsToSchema(mappingsData []*cloudflare.AccessA
return mappings
}
+
+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,
+ }
+ }
+ return schemas
+}