Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

access_application: prevent bad CORS config with allowing all origins and credentials #1073

Merged
merged 1 commit into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cloudflare/resource_cloudflare_access_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,15 @@ func convertCORSSchemaToStruct(d *schema.ResourceData) (*cloudflare.AccessApplic
CORSConfig.AllowCredentials = d.Get("cors_headers.0.allow_credentials").(bool)
CORSConfig.MaxAge = d.Get("cors_headers.0.max_age").(int)

// Prevent misconfigurations of CORS when `Access-Control-Allow-Origin` is
// a wildcard (aka all origins) and using credentials.
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials
if CORSConfig.AllowCredentials {
if contains(CORSConfig.AllowedOrigins, "*") || CORSConfig.AllowAllOrigins {
return nil, errors.New("CORS credentials are not permitted when all origins are allowed")
}
}

// Ensure that should someone forget to set allowed methods (either
// individually or *), we throw an error to prevent getting into an
// unrecoverable state.
Expand Down
104 changes: 98 additions & 6 deletions cloudflare/resource_cloudflare_access_application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,11 @@ func TestAccCloudflareAccessApplicationWithADefinedIdps(t *testing.T) {
func testAccCloudflareAccessApplicationConfigBasic(rnd string, domain string, identifier AccessIdentifier) string {
return fmt.Sprintf(`
resource "cloudflare_access_application" "%[1]s" {
%[3]s_id = "%[4]s"
name = "%[1]s"
domain = "%[1]s.%[2]s"
session_duration = "24h"
auto_redirect_to_identity = false
%[3]s_id = "%[4]s"
name = "%[1]s"
domain = "%[1]s.%[2]s"
session_duration = "24h"
auto_redirect_to_identity = false
}
`, rnd, domain, identifier.Type, identifier.Value)
}
Expand Down Expand Up @@ -258,7 +258,7 @@ resource "cloudflare_access_application" "%[1]s" {
domain = "%[1]s.%[3]s"
session_duration = "24h"
custom_deny_message = "denied!"
custom_deny_url = "https://www.cloudflare.com"
custom_deny_url = "https://www.cloudflare.com"
}
`, rnd, zoneID, domain)
}
Expand Down Expand Up @@ -435,6 +435,66 @@ func TestAccCloudflareAccessApplicationWithInvalidSessionDuration(t *testing.T)
})
}

func TestAccessApplicationMisconfiguredCORSCredentialsAllowingAllOrigins(t *testing.T) {
// Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access
// service does not yet support the API tokens and it results in
// misleading state error messages.
if os.Getenv("CLOUDFLARE_API_TOKEN") != "" {
defer func(apiToken string) {
os.Setenv("CLOUDFLARE_API_TOKEN", apiToken)
}(os.Getenv("CLOUDFLARE_API_TOKEN"))
os.Setenv("CLOUDFLARE_API_TOKEN", "")
}

rnd := generateRandomResourceName()
zone := os.Getenv("CLOUDFLARE_DOMAIN")
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccessAccPreCheck(t)
testAccPreCheckAccount(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccessApplicationMisconfiguredCORSAllowAllOriginsWithCredentials(rnd, zone, zoneID),
ExpectError: regexp.MustCompile(regexp.QuoteMeta(`CORS credentials are not permitted when all origins are allowed`)),
},
},
})
}

func TestAccessApplicationMisconfiguredCORSCredentialsAllowingWildcardOrigins(t *testing.T) {
// Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access
// service does not yet support the API tokens and it results in
// misleading state error messages.
if os.Getenv("CLOUDFLARE_API_TOKEN") != "" {
defer func(apiToken string) {
os.Setenv("CLOUDFLARE_API_TOKEN", apiToken)
}(os.Getenv("CLOUDFLARE_API_TOKEN"))
os.Setenv("CLOUDFLARE_API_TOKEN", "")
}

rnd := generateRandomResourceName()
zone := os.Getenv("CLOUDFLARE_DOMAIN")
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccessAccPreCheck(t)
testAccPreCheckAccount(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccessApplicationMisconfiguredCORSAllowWildcardOriginWithCredentials(rnd, zone, zoneID),
ExpectError: regexp.MustCompile(regexp.QuoteMeta(`CORS credentials are not permitted when all origins are allowed`)),
},
},
})
}

func testAccessApplicationWithZoneID(resourceID, zone, zoneID string) string {
return fmt.Sprintf(`
resource "cloudflare_access_application" "%[1]s" {
Expand Down Expand Up @@ -493,3 +553,35 @@ func testAccessApplicationWithInvalidSessionDuration(resourceID, zone, zoneID st
}
`, resourceID, zone, zoneID)
}

func testAccessApplicationMisconfiguredCORSAllowAllOriginsWithCredentials(resourceID, zone, zoneID string) string {
return fmt.Sprintf(`
resource "cloudflare_access_application" "%[1]s" {
name = "%[1]s-updated"
zone_id = "%[3]s"
domain = "%[1]s.%[2]s"

cors_headers {
allowed_methods = ["GET"]
allow_all_origins = true
allow_credentials = true
}
}
`, resourceID, zone, zoneID)
}

func testAccessApplicationMisconfiguredCORSAllowWildcardOriginWithCredentials(resourceID, zone, zoneID string) string {
return fmt.Sprintf(`
resource "cloudflare_access_application" "%[1]s" {
name = "%[1]s-updated"
zone_id = "%[3]s"
domain = "%[1]s.%[2]s"

cors_headers {
allowed_methods = ["GET"]
allowed_origins = ["*"]
allow_credentials = true
}
}
`, resourceID, zone, zoneID)
}