Repository: cloudflare/cloudflare-go
Pull request #3250 (2024-09-25 20:41:22)

Add support for Access Applications with type `infrastructure`
## Description

Adds fields and test cases to Access Policies and for Access Applications with type `Infrastructure`.

## Has your change been tested?

I've tested these changes through acceptance tests.

## Screenshots (if appropriate):

## Types of changes

What sort of change does your code introduce/modify?

- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

- [X] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [X] I have added tests to cover my changes.
- [X] All new and existing tests passed.
- [ ] This change is using publicly documented in [cloudflare/api-schemas](https://github.com/cloudflare/api-schemas) 
      and relies on stable APIs.

[1]: https://help.github.com/articles/closing-issues-using-keywords/



Patch:
diff --git a/.changelog/3186.txt b/.changelog/3186.txt
new file mode 100644
index 000000000..c0d9da1db
--- /dev/null
+++ b/.changelog/3186.txt
@@ -0,0 +1,7 @@
+```release-note:enhancement
+access_application: added target contexts support for access application type infrastructure
+```
+
+```release-note:enhancement
+access_policy: added infrastructure connection rule support for access policy 
+```
diff --git a/access_application.go b/access_application.go
index def62c0b8..2944cf114 100644
--- a/access_application.go
+++ b/access_application.go
@@ -15,50 +15,52 @@ type AccessApplicationType string
 
 // These constants represent all valid application types.
 const (
-	SelfHosted  AccessApplicationType = "self_hosted"
-	SSH         AccessApplicationType = "ssh"
-	VNC         AccessApplicationType = "vnc"
-	Biso        AccessApplicationType = "biso"
-	AppLauncher AccessApplicationType = "app_launcher"
-	Warp        AccessApplicationType = "warp"
-	Bookmark    AccessApplicationType = "bookmark"
-	Saas        AccessApplicationType = "saas"
+	SelfHosted     AccessApplicationType = "self_hosted"
+	SSH            AccessApplicationType = "ssh"
+	VNC            AccessApplicationType = "vnc"
+	Biso           AccessApplicationType = "biso"
+	AppLauncher    AccessApplicationType = "app_launcher"
+	Warp           AccessApplicationType = "warp"
+	Bookmark       AccessApplicationType = "bookmark"
+	Saas           AccessApplicationType = "saas"
+	Infrastructure AccessApplicationType = "infrastructure"
 )
 
 // AccessApplication represents an Access application.
 type AccessApplication struct {
-	GatewayRules             []AccessApplicationGatewayRule `json:"gateway_rules,omitempty"`
-	AllowedIdps              []string                       `json:"allowed_idps,omitempty"`
-	CustomDenyMessage        string                         `json:"custom_deny_message,omitempty"`
-	LogoURL                  string                         `json:"logo_url,omitempty"`
-	AUD                      string                         `json:"aud,omitempty"`
-	Domain                   string                         `json:"domain"`
-	SelfHostedDomains        []string                       `json:"self_hosted_domains"`
-	Type                     AccessApplicationType          `json:"type,omitempty"`
-	SessionDuration          string                         `json:"session_duration,omitempty"`
-	SameSiteCookieAttribute  string                         `json:"same_site_cookie_attribute,omitempty"`
-	CustomDenyURL            string                         `json:"custom_deny_url,omitempty"`
-	CustomNonIdentityDenyURL string                         `json:"custom_non_identity_deny_url,omitempty"`
-	Name                     string                         `json:"name"`
-	ID                       string                         `json:"id,omitempty"`
-	PrivateAddress           string                         `json:"private_address"`
-	CorsHeaders              *AccessApplicationCorsHeaders  `json:"cors_headers,omitempty"`
-	CreatedAt                *time.Time                     `json:"created_at,omitempty"`
-	UpdatedAt                *time.Time                     `json:"updated_at,omitempty"`
-	SaasApplication          *SaasApplication               `json:"saas_app,omitempty"`
-	AutoRedirectToIdentity   *bool                          `json:"auto_redirect_to_identity,omitempty"`
-	SkipInterstitial         *bool                          `json:"skip_interstitial,omitempty"`
-	AppLauncherVisible       *bool                          `json:"app_launcher_visible,omitempty"`
-	EnableBindingCookie      *bool                          `json:"enable_binding_cookie,omitempty"`
-	HttpOnlyCookieAttribute  *bool                          `json:"http_only_cookie_attribute,omitempty"`
-	ServiceAuth401Redirect   *bool                          `json:"service_auth_401_redirect,omitempty"`
-	PathCookieAttribute      *bool                          `json:"path_cookie_attribute,omitempty"`
-	AllowAuthenticateViaWarp *bool                          `json:"allow_authenticate_via_warp,omitempty"`
-	OptionsPreflightBypass   *bool                          `json:"options_preflight_bypass,omitempty"`
-	CustomPages              []string                       `json:"custom_pages,omitempty"`
-	Tags                     []string                       `json:"tags,omitempty"`
-	SCIMConfig               *AccessApplicationSCIMConfig   `json:"scim_config,omitempty"`
-	Policies                 []AccessPolicy                 `json:"policies,omitempty"`
+	GatewayRules             []AccessApplicationGatewayRule       `json:"gateway_rules,omitempty"`
+	AllowedIdps              []string                             `json:"allowed_idps,omitempty"`
+	CustomDenyMessage        string                               `json:"custom_deny_message,omitempty"`
+	LogoURL                  string                               `json:"logo_url,omitempty"`
+	AUD                      string                               `json:"aud,omitempty"`
+	Domain                   string                               `json:"domain"`
+	SelfHostedDomains        []string                             `json:"self_hosted_domains"`
+	Type                     AccessApplicationType                `json:"type,omitempty"`
+	SessionDuration          string                               `json:"session_duration,omitempty"`
+	SameSiteCookieAttribute  string                               `json:"same_site_cookie_attribute,omitempty"`
+	CustomDenyURL            string                               `json:"custom_deny_url,omitempty"`
+	CustomNonIdentityDenyURL string                               `json:"custom_non_identity_deny_url,omitempty"`
+	Name                     string                               `json:"name"`
+	ID                       string                               `json:"id,omitempty"`
+	PrivateAddress           string                               `json:"private_address"`
+	CorsHeaders              *AccessApplicationCorsHeaders        `json:"cors_headers,omitempty"`
+	CreatedAt                *time.Time                           `json:"created_at,omitempty"`
+	UpdatedAt                *time.Time                           `json:"updated_at,omitempty"`
+	SaasApplication          *SaasApplication                     `json:"saas_app,omitempty"`
+	AutoRedirectToIdentity   *bool                                `json:"auto_redirect_to_identity,omitempty"`
+	SkipInterstitial         *bool                                `json:"skip_interstitial,omitempty"`
+	AppLauncherVisible       *bool                                `json:"app_launcher_visible,omitempty"`
+	EnableBindingCookie      *bool                                `json:"enable_binding_cookie,omitempty"`
+	HttpOnlyCookieAttribute  *bool                                `json:"http_only_cookie_attribute,omitempty"`
+	ServiceAuth401Redirect   *bool                                `json:"service_auth_401_redirect,omitempty"`
+	PathCookieAttribute      *bool                                `json:"path_cookie_attribute,omitempty"`
+	AllowAuthenticateViaWarp *bool                                `json:"allow_authenticate_via_warp,omitempty"`
+	OptionsPreflightBypass   *bool                                `json:"options_preflight_bypass,omitempty"`
+	CustomPages              []string                             `json:"custom_pages,omitempty"`
+	Tags                     []string                             `json:"tags,omitempty"`
+	SCIMConfig               *AccessApplicationSCIMConfig         `json:"scim_config,omitempty"`
+	Policies                 []AccessPolicy                       `json:"policies,omitempty"`
+	TargetContexts           *[]AccessInfrastructureTargetContext `json:"target_criteria,omitempty"`
 	AccessAppLauncherCustomization
 }
 
@@ -276,69 +278,71 @@ type ListAccessApplicationsParams struct {
 }
 
 type CreateAccessApplicationParams struct {
-	AllowedIdps              []string                       `json:"allowed_idps,omitempty"`
-	AppLauncherVisible       *bool                          `json:"app_launcher_visible,omitempty"`
-	AUD                      string                         `json:"aud,omitempty"`
-	AutoRedirectToIdentity   *bool                          `json:"auto_redirect_to_identity,omitempty"`
-	CorsHeaders              *AccessApplicationCorsHeaders  `json:"cors_headers,omitempty"`
-	CustomDenyMessage        string                         `json:"custom_deny_message,omitempty"`
-	CustomDenyURL            string                         `json:"custom_deny_url,omitempty"`
-	CustomNonIdentityDenyURL string                         `json:"custom_non_identity_deny_url,omitempty"`
-	Domain                   string                         `json:"domain"`
-	EnableBindingCookie      *bool                          `json:"enable_binding_cookie,omitempty"`
-	GatewayRules             []AccessApplicationGatewayRule `json:"gateway_rules,omitempty"`
-	HttpOnlyCookieAttribute  *bool                          `json:"http_only_cookie_attribute,omitempty"`
-	LogoURL                  string                         `json:"logo_url,omitempty"`
-	Name                     string                         `json:"name"`
-	PathCookieAttribute      *bool                          `json:"path_cookie_attribute,omitempty"`
-	PrivateAddress           string                         `json:"private_address"`
-	SaasApplication          *SaasApplication               `json:"saas_app,omitempty"`
-	SameSiteCookieAttribute  string                         `json:"same_site_cookie_attribute,omitempty"`
-	SelfHostedDomains        []string                       `json:"self_hosted_domains"`
-	ServiceAuth401Redirect   *bool                          `json:"service_auth_401_redirect,omitempty"`
-	SessionDuration          string                         `json:"session_duration,omitempty"`
-	SkipInterstitial         *bool                          `json:"skip_interstitial,omitempty"`
-	OptionsPreflightBypass   *bool                          `json:"options_preflight_bypass,omitempty"`
-	Type                     AccessApplicationType          `json:"type,omitempty"`
-	AllowAuthenticateViaWarp *bool                          `json:"allow_authenticate_via_warp,omitempty"`
-	CustomPages              []string                       `json:"custom_pages,omitempty"`
-	Tags                     []string                       `json:"tags,omitempty"`
-	SCIMConfig               *AccessApplicationSCIMConfig   `json:"scim_config,omitempty"`
+	AllowedIdps              []string                             `json:"allowed_idps,omitempty"`
+	AppLauncherVisible       *bool                                `json:"app_launcher_visible,omitempty"`
+	AUD                      string                               `json:"aud,omitempty"`
+	AutoRedirectToIdentity   *bool                                `json:"auto_redirect_to_identity,omitempty"`
+	CorsHeaders              *AccessApplicationCorsHeaders        `json:"cors_headers,omitempty"`
+	CustomDenyMessage        string                               `json:"custom_deny_message,omitempty"`
+	CustomDenyURL            string                               `json:"custom_deny_url,omitempty"`
+	CustomNonIdentityDenyURL string                               `json:"custom_non_identity_deny_url,omitempty"`
+	Domain                   string                               `json:"domain"`
+	EnableBindingCookie      *bool                                `json:"enable_binding_cookie,omitempty"`
+	GatewayRules             []AccessApplicationGatewayRule       `json:"gateway_rules,omitempty"`
+	HttpOnlyCookieAttribute  *bool                                `json:"http_only_cookie_attribute,omitempty"`
+	LogoURL                  string                               `json:"logo_url,omitempty"`
+	Name                     string                               `json:"name"`
+	PathCookieAttribute      *bool                                `json:"path_cookie_attribute,omitempty"`
+	PrivateAddress           string                               `json:"private_address"`
+	SaasApplication          *SaasApplication                     `json:"saas_app,omitempty"`
+	SameSiteCookieAttribute  string                               `json:"same_site_cookie_attribute,omitempty"`
+	SelfHostedDomains        []string                             `json:"self_hosted_domains"`
+	ServiceAuth401Redirect   *bool                                `json:"service_auth_401_redirect,omitempty"`
+	SessionDuration          string                               `json:"session_duration,omitempty"`
+	SkipInterstitial         *bool                                `json:"skip_interstitial,omitempty"`
+	OptionsPreflightBypass   *bool                                `json:"options_preflight_bypass,omitempty"`
+	Type                     AccessApplicationType                `json:"type,omitempty"`
+	AllowAuthenticateViaWarp *bool                                `json:"allow_authenticate_via_warp,omitempty"`
+	CustomPages              []string                             `json:"custom_pages,omitempty"`
+	Tags                     []string                             `json:"tags,omitempty"`
+	SCIMConfig               *AccessApplicationSCIMConfig         `json:"scim_config,omitempty"`
+	TargetContexts           *[]AccessInfrastructureTargetContext `json:"target_criteria,omitempty"`
 	// List of policy ids to link to this application in ascending order of precedence.
 	Policies []string `json:"policies,omitempty"`
 	AccessAppLauncherCustomization
 }
 
 type UpdateAccessApplicationParams struct {
-	ID                       string                         `json:"id,omitempty"`
-	AllowedIdps              []string                       `json:"allowed_idps,omitempty"`
-	AppLauncherVisible       *bool                          `json:"app_launcher_visible,omitempty"`
-	AUD                      string                         `json:"aud,omitempty"`
-	AutoRedirectToIdentity   *bool                          `json:"auto_redirect_to_identity,omitempty"`
-	CorsHeaders              *AccessApplicationCorsHeaders  `json:"cors_headers,omitempty"`
-	CustomDenyMessage        string                         `json:"custom_deny_message,omitempty"`
-	CustomDenyURL            string                         `json:"custom_deny_url,omitempty"`
-	CustomNonIdentityDenyURL string                         `json:"custom_non_identity_deny_url,omitempty"`
-	Domain                   string                         `json:"domain"`
-	EnableBindingCookie      *bool                          `json:"enable_binding_cookie,omitempty"`
-	GatewayRules             []AccessApplicationGatewayRule `json:"gateway_rules,omitempty"`
-	HttpOnlyCookieAttribute  *bool                          `json:"http_only_cookie_attribute,omitempty"`
-	LogoURL                  string                         `json:"logo_url,omitempty"`
-	Name                     string                         `json:"name"`
-	PathCookieAttribute      *bool                          `json:"path_cookie_attribute,omitempty"`
-	PrivateAddress           string                         `json:"private_address"`
-	SaasApplication          *SaasApplication               `json:"saas_app,omitempty"`
-	SameSiteCookieAttribute  string                         `json:"same_site_cookie_attribute,omitempty"`
-	SelfHostedDomains        []string                       `json:"self_hosted_domains"`
-	ServiceAuth401Redirect   *bool                          `json:"service_auth_401_redirect,omitempty"`
-	SessionDuration          string                         `json:"session_duration,omitempty"`
-	SkipInterstitial         *bool                          `json:"skip_interstitial,omitempty"`
-	Type                     AccessApplicationType          `json:"type,omitempty"`
-	AllowAuthenticateViaWarp *bool                          `json:"allow_authenticate_via_warp,omitempty"`
-	OptionsPreflightBypass   *bool                          `json:"options_preflight_bypass,omitempty"`
-	CustomPages              []string                       `json:"custom_pages,omitempty"`
-	Tags                     []string                       `json:"tags,omitempty"`
-	SCIMConfig               *AccessApplicationSCIMConfig   `json:"scim_config,omitempty"`
+	ID                       string                               `json:"id,omitempty"`
+	AllowedIdps              []string                             `json:"allowed_idps,omitempty"`
+	AppLauncherVisible       *bool                                `json:"app_launcher_visible,omitempty"`
+	AUD                      string                               `json:"aud,omitempty"`
+	AutoRedirectToIdentity   *bool                                `json:"auto_redirect_to_identity,omitempty"`
+	CorsHeaders              *AccessApplicationCorsHeaders        `json:"cors_headers,omitempty"`
+	CustomDenyMessage        string                               `json:"custom_deny_message,omitempty"`
+	CustomDenyURL            string                               `json:"custom_deny_url,omitempty"`
+	CustomNonIdentityDenyURL string                               `json:"custom_non_identity_deny_url,omitempty"`
+	Domain                   string                               `json:"domain"`
+	EnableBindingCookie      *bool                                `json:"enable_binding_cookie,omitempty"`
+	GatewayRules             []AccessApplicationGatewayRule       `json:"gateway_rules,omitempty"`
+	HttpOnlyCookieAttribute  *bool                                `json:"http_only_cookie_attribute,omitempty"`
+	LogoURL                  string                               `json:"logo_url,omitempty"`
+	Name                     string                               `json:"name"`
+	PathCookieAttribute      *bool                                `json:"path_cookie_attribute,omitempty"`
+	PrivateAddress           string                               `json:"private_address"`
+	SaasApplication          *SaasApplication                     `json:"saas_app,omitempty"`
+	SameSiteCookieAttribute  string                               `json:"same_site_cookie_attribute,omitempty"`
+	SelfHostedDomains        []string                             `json:"self_hosted_domains"`
+	ServiceAuth401Redirect   *bool                                `json:"service_auth_401_redirect,omitempty"`
+	SessionDuration          string                               `json:"session_duration,omitempty"`
+	SkipInterstitial         *bool                                `json:"skip_interstitial,omitempty"`
+	Type                     AccessApplicationType                `json:"type,omitempty"`
+	AllowAuthenticateViaWarp *bool                                `json:"allow_authenticate_via_warp,omitempty"`
+	OptionsPreflightBypass   *bool                                `json:"options_preflight_bypass,omitempty"`
+	CustomPages              []string                             `json:"custom_pages,omitempty"`
+	Tags                     []string                             `json:"tags,omitempty"`
+	SCIMConfig               *AccessApplicationSCIMConfig         `json:"scim_config,omitempty"`
+	TargetContexts           *[]AccessInfrastructureTargetContext `json:"target_criteria,omitempty"`
 	// List of policy ids to link to this application in ascending order of precedence.
 	// Can reference reusable policies and policies specific to this application.
 	// If this field is not provided, the existing policies will not be modified.
diff --git a/access_infrastructure_application.go b/access_infrastructure_application.go
new file mode 100644
index 000000000..3824400f2
--- /dev/null
+++ b/access_infrastructure_application.go
@@ -0,0 +1,22 @@
+package cloudflare
+
+type AccessInfrastructureProtocol string
+
+const (
+	AccessInfrastructureSSH AccessInfrastructureProtocol = "SSH"
+	AccessInfrastructureRDP AccessInfrastructureProtocol = "RDP"
+)
+
+type AccessInfrastructureTargetContext struct {
+	TargetAttributes map[string][]string          `json:"target_attributes"`
+	Port             int                          `json:"port"`
+	Protocol         AccessInfrastructureProtocol `json:"protocol"`
+}
+
+type AccessInfrastructureConnectionRulesSSH struct {
+	Usernames []string `json:"usernames"`
+}
+
+type AccessInfrastructureConnectionRules struct {
+	SSH *AccessInfrastructureConnectionRulesSSH `json:"ssh,omitempty"`
+}
diff --git a/access_policy.go b/access_policy.go
index a70ceede5..2b2ec812d 100644
--- a/access_policy.go
+++ b/access_policy.go
@@ -30,12 +30,13 @@ type AccessPolicy struct {
 	Reusable   *bool      `json:"reusable,omitempty"`
 	Name       string     `json:"name"`
 
-	IsolationRequired            *bool                 `json:"isolation_required,omitempty"`
-	SessionDuration              *string               `json:"session_duration,omitempty"`
-	PurposeJustificationRequired *bool                 `json:"purpose_justification_required,omitempty"`
-	PurposeJustificationPrompt   *string               `json:"purpose_justification_prompt,omitempty"`
-	ApprovalRequired             *bool                 `json:"approval_required,omitempty"`
-	ApprovalGroups               []AccessApprovalGroup `json:"approval_groups"`
+	IsolationRequired             *bool                                `json:"isolation_required,omitempty"`
+	SessionDuration               *string                              `json:"session_duration,omitempty"`
+	PurposeJustificationRequired  *bool                                `json:"purpose_justification_required,omitempty"`
+	PurposeJustificationPrompt    *string                              `json:"purpose_justification_prompt,omitempty"`
+	ApprovalRequired              *bool                                `json:"approval_required,omitempty"`
+	ApprovalGroups                []AccessApprovalGroup                `json:"approval_groups"`
+	InfrastructureConnectionRules *AccessInfrastructureConnectionRules `json:"connection_rules,omitempty"`
 
 	// The include policy works like an OR logical operator. The user must
 	// satisfy one of the rules.
@@ -94,12 +95,13 @@ type CreateAccessPolicyParams struct {
 	Decision   string `json:"decision"`
 	Name       string `json:"name"`
 
-	IsolationRequired            *bool                 `json:"isolation_required,omitempty"`
-	SessionDuration              *string               `json:"session_duration,omitempty"`
-	PurposeJustificationRequired *bool                 `json:"purpose_justification_required,omitempty"`
-	PurposeJustificationPrompt   *string               `json:"purpose_justification_prompt,omitempty"`
-	ApprovalRequired             *bool                 `json:"approval_required,omitempty"`
-	ApprovalGroups               []AccessApprovalGroup `json:"approval_groups"`
+	IsolationRequired             *bool                                `json:"isolation_required,omitempty"`
+	SessionDuration               *string                              `json:"session_duration,omitempty"`
+	PurposeJustificationRequired  *bool                                `json:"purpose_justification_required,omitempty"`
+	PurposeJustificationPrompt    *string                              `json:"purpose_justification_prompt,omitempty"`
+	ApprovalRequired              *bool                                `json:"approval_required,omitempty"`
+	ApprovalGroups                []AccessApprovalGroup                `json:"approval_groups"`
+	InfrastructureConnectionRules *AccessInfrastructureConnectionRules `json:"connection_rules,omitempty"`
 
 	// The include policy works like an OR logical operator. The user must
 	// satisfy one of the rules.
@@ -127,12 +129,13 @@ type UpdateAccessPolicyParams struct {
 	Decision   string `json:"decision"`
 	Name       string `json:"name"`
 
-	IsolationRequired            *bool                 `json:"isolation_required,omitempty"`
-	SessionDuration              *string               `json:"session_duration,omitempty"`
-	PurposeJustificationRequired *bool                 `json:"purpose_justification_required,omitempty"`
-	PurposeJustificationPrompt   *string               `json:"purpose_justification_prompt,omitempty"`
-	ApprovalRequired             *bool                 `json:"approval_required,omitempty"`
-	ApprovalGroups               []AccessApprovalGroup `json:"approval_groups"`
+	IsolationRequired             *bool                                `json:"isolation_required,omitempty"`
+	SessionDuration               *string                              `json:"session_duration,omitempty"`
+	PurposeJustificationRequired  *bool                                `json:"purpose_justification_required,omitempty"`
+	PurposeJustificationPrompt    *string                              `json:"purpose_justification_prompt,omitempty"`
+	ApprovalRequired              *bool                                `json:"approval_required,omitempty"`
+	ApprovalGroups                []AccessApprovalGroup                `json:"approval_groups"`
+	InfrastructureConnectionRules *AccessInfrastructureConnectionRules `json:"connection_rules,omitempty"`
 
 	// The include policy works like an OR logical operator. The user must
 	// satisfy one of the rules.


Test patch:
diff --git a/access_infrastructure_application_test.go b/access_infrastructure_application_test.go
new file mode 100644
index 000000000..9b992b596
--- /dev/null
+++ b/access_infrastructure_application_test.go
@@ -0,0 +1,358 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const testInfrastructureApplicationId = "f52d1f8f-f194-4ac1-845f-e47203bf4dbb"
+
+var (
+	accessPolicyCreatedOn, _              = time.Parse(time.RFC3339, "2024-09-24T17:58:44Z")
+	accessPolicyUpdatedOn, _              = time.Parse(time.RFC3339, "2024-09-24T17:58:44Z")
+	infrastructureApplicationCreatedOn, _ = time.Parse(time.RFC3339, "2024-09-14T05:13:43Z")
+	infrastructureApplicationUpdatedOn, _ = time.Parse(time.RFC3339, "2024-09-24T17:58:44Z")
+	expectedInfraAppPolicy                = AccessPolicy{
+		ID:         "8dd6af47-a80b-48cb-89c1-f39299ebf096",
+		Precedence: 1,
+		Reusable:   BoolPtr(false),
+		Decision:   "allow",
+		CreatedAt:  &accessPolicyCreatedOn,
+		UpdatedAt:  &accessPolicyUpdatedOn,
+		Name:       "my infra SSH policy",
+		Include: []any{
+			map[string]interface{}{"email": map[string]interface{}{"email": "devuser@cloudflare.com"}},
+		},
+		Exclude: []any{},
+		Require: []any{},
+		InfrastructureConnectionRules: &AccessInfrastructureConnectionRules{
+			SSH: &AccessInfrastructureConnectionRulesSSH{
+				Usernames: []string{"devuser"},
+			},
+		},
+	}
+	expectedInfrastructureApplication = AccessApplication{
+		ID:                       testInfrastructureApplicationId,
+		CreatedAt:                &infrastructureApplicationCreatedOn,
+		UpdatedAt:                &infrastructureApplicationUpdatedOn,
+		AUD:                      "6570722c13470d8c3e7aed619fc66cdf948002efbb06601166b8f2dad85fae34",
+		Name:                     "infrastructure_application_test",
+		Domain:                   "",
+		Type:                     "infrastructure",
+		SessionDuration:          "",
+		AllowedIdps:              nil,
+		AutoRedirectToIdentity:   nil,
+		EnableBindingCookie:      nil,
+		AppLauncherVisible:       nil,
+		ServiceAuth401Redirect:   nil,
+		CustomDenyMessage:        "",
+		CustomDenyURL:            "",
+		SameSiteCookieAttribute:  "",
+		HttpOnlyCookieAttribute:  nil,
+		LogoURL:                  "",
+		SkipInterstitial:         nil,
+		PathCookieAttribute:      nil,
+		CustomPages:              nil,
+		Tags:                     nil,
+		CustomNonIdentityDenyURL: "",
+		AllowAuthenticateViaWarp: nil,
+		OptionsPreflightBypass:   nil,
+		TargetContexts: &[]AccessInfrastructureTargetContext{
+			{
+				TargetAttributes: map[string][]string{
+					"hostname": {"cfgo-acc-tests"},
+				},
+				Port:     22,
+				Protocol: "SSH",
+			},
+		},
+		Policies: []AccessPolicy{
+			expectedInfraAppPolicy,
+		},
+	}
+
+	expectedInfrastructureApplicationUpdated = AccessApplication{
+		ID:                       testInfrastructureApplicationId,
+		CreatedAt:                &infrastructureApplicationCreatedOn,
+		UpdatedAt:                &infrastructureApplicationUpdatedOn,
+		AUD:                      "6570722c13470d8c3e7aed619fc66cdf948002efbb06601166b8f2dad85fae34",
+		Name:                     "infrastructure_application_test_updated",
+		Domain:                   "",
+		Type:                     "infrastructure",
+		SessionDuration:          "",
+		AllowedIdps:              nil,
+		AutoRedirectToIdentity:   nil,
+		EnableBindingCookie:      nil,
+		AppLauncherVisible:       nil,
+		ServiceAuth401Redirect:   nil,
+		CustomDenyMessage:        "",
+		CustomDenyURL:            "",
+		SameSiteCookieAttribute:  "",
+		HttpOnlyCookieAttribute:  nil,
+		LogoURL:                  "",
+		SkipInterstitial:         nil,
+		PathCookieAttribute:      nil,
+		CustomPages:              nil,
+		Tags:                     nil,
+		CustomNonIdentityDenyURL: "",
+		AllowAuthenticateViaWarp: nil,
+		OptionsPreflightBypass:   nil,
+		TargetContexts: &[]AccessInfrastructureTargetContext{
+			{
+				TargetAttributes: map[string][]string{
+					"hostname": {"cfgo-acc-tests", "cfgo-acc-tests-duplicate"},
+				},
+				Port:     22,
+				Protocol: "SSH",
+			},
+		},
+		Policies: []AccessPolicy{
+			expectedInfraAppPolicy,
+		},
+	}
+)
+
+func TestInfrastructureApplication_Create(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprint(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"id": "f52d1f8f-f194-4ac1-845f-e47203bf4dbb",
+				"type": "infrastructure",
+				"name": "infrastructure_application_test",
+				"aud": "6570722c13470d8c3e7aed619fc66cdf948002efbb06601166b8f2dad85fae34",
+				"created_at": "2024-09-14T05:13:43Z",
+				"updated_at": "2024-09-24T17:58:44Z",
+				"target_criteria": [
+					{
+						"target_attributes": {
+							"hostname": [ "cfgo-acc-tests" ]
+						},
+						"port": 22,
+						"protocol": "SSH"
+					}
+				],
+				"policies": [
+					{
+						"created_at": "2024-09-24T17:58:44Z",
+						"decision": "allow",
+						"exclude": [],
+						"id": "8dd6af47-a80b-48cb-89c1-f39299ebf096",
+						"include": [
+							{
+								"email": {
+									"email": "devuser@cloudflare.com"
+								}
+							}
+						],
+						"name": "my infra SSH policy",
+						"require": [],
+						"connection_rules": {
+							"ssh": {
+								"usernames": [ "devuser" ]
+							}
+						},
+						"uid": "8dd6af47-a80b-48cb-89c1-f39299ebf096",
+						"updated_at": "2024-09-24T17:58:44Z",
+						"reusable": false,
+						"precedence": 1
+					}
+				]
+			}
+		}`)
+	})
+
+	actual, err := client.CreateAccessApplication(context.Background(), AccountIdentifier(testAccountID), CreateAccessApplicationParams{
+		Name: "infrastructure_application_test",
+		Type: "infrastructure",
+		TargetContexts: &[]AccessInfrastructureTargetContext{
+			{
+				TargetAttributes: map[string][]string{
+					"hostname": {"cfgo-acc-tests"},
+				},
+				Port:     22,
+				Protocol: "SSH",
+			},
+		},
+		Policies: []string{
+			"8dd6af47-a80b-48cb-89c1-f39299ebf096",
+		},
+	})
+	if assert.NoError(t, err) {
+		assert.Equal(t, expectedInfrastructureApplication, actual, "create infrastructure_application structs not equal")
+	}
+}
+
+func TestInfrastructureApplication_Update(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprint(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"id": "f52d1f8f-f194-4ac1-845f-e47203bf4dbb",
+				"type": "infrastructure",
+				"name": "infrastructure_application_test_updated",
+				"aud": "6570722c13470d8c3e7aed619fc66cdf948002efbb06601166b8f2dad85fae34",
+				"created_at": "2024-09-14T05:13:43Z",
+				"updated_at": "2024-09-24T17:58:44Z",
+				"target_criteria": [
+					{
+						"target_attributes": {
+							"hostname": [ "cfgo-acc-tests", "cfgo-acc-tests-duplicate" ]
+						},
+						"port": 22,
+						"protocol": "SSH"
+					}
+				],
+				"policies": [
+					{
+						"created_at": "2024-09-24T17:58:44Z",
+						"decision": "allow",
+						"exclude": [],
+						"id": "8dd6af47-a80b-48cb-89c1-f39299ebf096",
+						"include": [
+							{
+								"email": {
+									"email": "devuser@cloudflare.com"
+								}
+							}
+						],
+						"name": "my infra SSH policy",
+						"require": [],
+						"connection_rules": {
+							"ssh": {
+								"usernames": [ "devuser" ]
+							}
+						},
+						"uid": "8dd6af47-a80b-48cb-89c1-f39299ebf096",
+						"updated_at": "2024-09-24T17:58:44Z",
+						"reusable": false,
+						"precedence": 1
+					}
+				]
+			}
+		}`)
+	})
+
+	actual, err := client.CreateAccessApplication(context.Background(), AccountIdentifier(testAccountID), CreateAccessApplicationParams{
+		Name: "infrastructure_application_test_updated",
+		Type: "infrastructure",
+		TargetContexts: &[]AccessInfrastructureTargetContext{
+			{
+				TargetAttributes: map[string][]string{
+					"hostname": {"cfgo-acc-tests", "cfgo-acc-tests-duplicate"},
+				},
+				Port:     22,
+				Protocol: "SSH",
+			},
+		},
+		Policies: []string{
+			"8dd6af47-a80b-48cb-89c1-f39299ebf096",
+		},
+	})
+	if assert.NoError(t, err) {
+		assert.Equal(t, expectedInfrastructureApplicationUpdated, actual, "create infrastructure_application structs not equal")
+	}
+}
+
+func TestInfrastructureApplication_Get(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps/f52d1f8f-f194-4ac1-845f-e47203bf4dbb", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprint(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"id": "f52d1f8f-f194-4ac1-845f-e47203bf4dbb",
+				"type": "infrastructure",
+				"name": "infrastructure_application_test",
+				"aud": "6570722c13470d8c3e7aed619fc66cdf948002efbb06601166b8f2dad85fae34",
+				"created_at": "2024-09-14T05:13:43Z",
+				"updated_at": "2024-09-24T17:58:44Z",
+				"target_criteria": [
+					{
+						"target_attributes": {
+							"hostname": [ "cfgo-acc-tests" ]
+						},
+						"port": 22,
+						"protocol": "SSH"
+					}
+				],
+				"policies": [
+					{
+						"created_at": "2024-09-24T17:58:44Z",
+						"decision": "allow",
+						"exclude": [],
+						"id": "8dd6af47-a80b-48cb-89c1-f39299ebf096",
+						"include": [
+							{
+								"email": {
+									"email": "devuser@cloudflare.com"
+								}
+							}
+						],
+						"name": "my infra SSH policy",
+						"require": [],
+						"connection_rules": {
+							"ssh": {
+								"usernames": [ "devuser" ]
+							}
+						},
+						"uid": "8dd6af47-a80b-48cb-89c1-f39299ebf096",
+						"updated_at": "2024-09-24T17:58:44Z",
+						"reusable": false,
+						"precedence": 1
+					}
+				]
+			}
+		}`)
+	})
+
+	actual, err := client.GetAccessApplication(context.Background(), AccountIdentifier(testAccountID), "f52d1f8f-f194-4ac1-845f-e47203bf4dbb")
+	if assert.NoError(t, err) {
+		assert.Equal(t, expectedInfrastructureApplication, actual, "get infrastructure_application structs not equal")
+	}
+}
+
+func TestInfrastructureApplication_Delete(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps/f52d1f8f-f194-4ac1-845f-e47203bf4dbb", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprint(w, `{
+      "success": true,
+      "errors": [],
+      "messages": [],
+      "result": {
+        "id": "f52d1f8f-f194-4ac1-845f-e47203bf4dbb"
+      }
+    }`)
+	})
+
+	err := client.DeleteAccessApplication(context.Background(), AccountIdentifier(testAccountID), "f52d1f8f-f194-4ac1-845f-e47203bf4dbb")
+	assert.NoError(t, err)
+}
diff --git a/access_policy_test.go b/access_policy_test.go
index 756421382..6ed033fda 100644
--- a/access_policy_test.go
+++ b/access_policy_test.go
@@ -55,6 +55,11 @@ var (
 				ApprovalsNeeded: 1,
 			},
 		},
+		InfrastructureConnectionRules: &AccessInfrastructureConnectionRules{
+			SSH: &AccessInfrastructureConnectionRulesSSH{
+				Usernames: []string{"root", "ec2-user"},
+			},
+		},
 	}
 )
 
@@ -115,7 +120,12 @@ func TestAccessPolicies(t *testing.T) {
 					],
 					"approvals_needed": 1
 				  }
-				]
+				],
+				"connection_rules": {
+					"ssh": {
+						"usernames": ["root", "ec2-user"]
+					}
+				}
 			  }
 			],
 			"result_info": {
@@ -214,7 +224,12 @@ func TestAccessPolicy(t *testing.T) {
 						"email_addresses": ["email1@example.com", "email2@example.com"],
 						"approvals_needed": 1
 					}
-				]
+				],
+				"connection_rules": {
+					"ssh": {
+						"usernames": ["root", "ec2-user"]
+					}
+				}
 			}
 		}
 		`)
@@ -307,7 +322,12 @@ func TestCreateAccessPolicy(t *testing.T) {
 						"email_addresses": ["email1@example.com", "email2@example.com"],
 						"approvals_needed": 1
 					}
-				]
+				],
+				"connection_rules": {
+					"ssh": {
+						"usernames": ["root", "ec2-user"]
+					}
+				}
 			}
 		}
 		`)
@@ -345,6 +365,11 @@ func TestCreateAccessPolicy(t *testing.T) {
 				ApprovalsNeeded: 1,
 			},
 		},
+		InfrastructureConnectionRules: &AccessInfrastructureConnectionRules{
+			SSH: &AccessInfrastructureConnectionRulesSSH{
+				Usernames: []string{"root", "ec2-user"},
+			},
+		},
 	}
 
 	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps/"+accessApplicationID+"/policies", handler)
@@ -415,6 +440,11 @@ func TestCreateAccessPolicyAuthContextRule(t *testing.T) {
 				ApprovalsNeeded: 1,
 			},
 		},
+		InfrastructureConnectionRules: &AccessInfrastructureConnectionRules{
+			SSH: &AccessInfrastructureConnectionRulesSSH{
+				Usernames: []string{"root", "ec2-user"},
+			},
+		},
 	}
 
 	handler := func(w http.ResponseWriter, r *http.Request) {
@@ -462,7 +492,12 @@ func TestCreateAccessPolicyAuthContextRule(t *testing.T) {
 						"email_addresses": ["email1@example.com", "email2@example.com"],
 						"approvals_needed": 1
 					}
-				]
+				],
+				"connection_rules": {
+					"ssh": {
+						"usernames": ["root", "ec2-user"]
+					}
+				}
 			}
 		}
 		`)
@@ -554,6 +589,11 @@ func TestUpdateAccessPolicy(t *testing.T) {
 				ApprovalsNeeded: 1,
 			},
 		},
+		InfrastructureConnectionRules: &AccessInfrastructureConnectionRules{
+			SSH: &AccessInfrastructureConnectionRulesSSH{
+				Usernames: []string{"root", "ec2-user"},
+			},
+		},
 	}
 	handler := func(w http.ResponseWriter, r *http.Request) {
 		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
@@ -604,7 +644,12 @@ func TestUpdateAccessPolicy(t *testing.T) {
 						"email_addresses": ["email1@example.com", "email2@example.com"],
 						"approvals_needed": 1
 					}
-				]
+				],
+				"connection_rules": {
+					"ssh": {
+						"usernames": ["root", "ec2-user"]
+					}
+				}
 			}
 		}
 		`)
diff --git a/infrastructure_access_target_test.go b/infrastructure_access_target_test.go
index b80bcda76..dc1423ff0 100644
--- a/infrastructure_access_target_test.go
+++ b/infrastructure_access_target_test.go
@@ -150,7 +150,7 @@ func TestInfrastructureTarget_List(t *testing.T) {
 	if assert.NoError(t, err) {
 		assert.Equal(t, 1, len(out), "expected 1 challenge_widgets")
 		assert.Equal(t, 20, results.PerPage, "expected 20 per page")
-		assert.Equal(t, expectedInfrastructureAccessTarget, out[0], "list challenge_widgets structs not equal")
+		assert.Equal(t, expectedInfrastructureAccessTarget, out[0], "list infrastructure_access_target structs not equal")
 	}
 }
 
@@ -256,7 +256,7 @@ func TestInfrastructureTarget_Update(t *testing.T) {
 		},
 	})
 	if assert.NoError(t, err) {
-		assert.Equal(t, expectedInfrastructureModified, out, "update challenge_widgets structs not equal")
+		assert.Equal(t, expectedInfrastructureModified, out, "update infrastructure_access_target structs not equal")
 	}
 }