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

azurerm_app_service, azurerm_app_service_slot, azurerm_function_app: Support for site_config.ip_restrictions.headers / site_config.scm_ip_restrictions.headers #11209

Merged
merged 7 commits into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
105 changes: 105 additions & 0 deletions azurerm/internal/services/web/app_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,55 @@ func schemaAppServiceIpRestriction() *schema.Schema {
"Deny",
}, false),
},

"headers": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"x_forwarded_host": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 8,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"x_forwarded_for": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 8,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsCIDR,
},
},
"x_azure_fdid": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 8,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsUUID,
},
},
"x_fd_health_probe": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
"1",
}, false),
},
},
},
},
},
},
},
}
Expand Down Expand Up @@ -1826,6 +1875,10 @@ func flattenAppServiceIpRestriction(input *[]web.IPSecurityRestriction) []interf
restriction["action"] = *action
}

if headers := v.Headers; headers != nil {
restriction["headers"] = flattenHeaders(headers)
}

restrictions = append(restrictions, restriction)
}

Expand Down Expand Up @@ -1946,9 +1999,61 @@ func expandAppServiceIpRestriction(input interface{}) ([]web.IPSecurityRestricti
if action != "" {
ipSecurityRestriction.Action = &action
}
if headers, ok := restriction["headers"]; ok {
ipSecurityRestriction.Headers = expandHeaders(headers.([]interface{}))
}

restrictions = append(restrictions, ipSecurityRestriction)
}

return restrictions, nil
}

func flattenHeaders(input map[string][]string) []interface{} {
output := make([]interface{}, 0)
headers := make(map[string]interface{})
if input == nil {
return output
}

if forwardedHost, ok := input["x-forwarded-host"]; ok && len(forwardedHost) > 0 {
headers["x_forwarded_host"] = forwardedHost
}
if forwardedFor, ok := input["x-forwarded-for"]; ok && len(forwardedFor) > 0 {
headers["x_forwarded_for"] = forwardedFor
}
if fdids, ok := input["x-azure-fdid"]; ok && len(fdids) > 0 {
headers["x_azure_fdid"] = fdids
}
if healthProbe, ok := input["x-fd-healthprobe"]; ok && len(healthProbe) > 0 {
headers["x_fd_health_probe"] = healthProbe
}

return append(output, headers)
}

func expandHeaders(input interface{}) map[string][]string {
output := make(map[string][]string)

for _, r := range input.([]interface{}) {
if r == nil {
continue
}

val := r.(map[string]interface{})
if raw := val["x_forwarded_host"].(*schema.Set).List(); len(raw) > 0 {
output["x-forwarded-host"] = *utils.ExpandStringSlice(raw)
}
if raw := val["x_forwarded_for"].(*schema.Set).List(); len(raw) > 0 {
output["x-forwarded-for"] = *utils.ExpandStringSlice(raw)
}
if raw := val["x_azure_fdid"].(*schema.Set).List(); len(raw) > 0 {
output["x-azure-fdid"] = *utils.ExpandStringSlice(raw)
}
if raw := val["x_fd_health_probe"].(*schema.Set).List(); len(raw) > 0 {
output["x-fd-healthprobe"] = *utils.ExpandStringSlice(raw)
}
}

return output
}
168 changes: 168 additions & 0 deletions azurerm/internal/services/web/app_service_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,15 @@ func TestAccAppService_completeIpRestriction(t *testing.T) {
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.name").HasValue("test-restriction"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.priority").HasValue("123"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.action").HasValue("Allow"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.#").HasValue("2"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.0").HasValue("2002::1234:abcd:ffff:c0a8:101/64"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.1").HasValue("9.9.9.9/32"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_azure_fdid.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_azure_fdid.0").HasValue("55ce4ed1-4b06-4bf1-b40e-4638452104da"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_fd_health_probe.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_fd_health_probe.0").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_host.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_host.0").HasValue("example.com"),
),
},
data.ImportStep(),
Expand Down Expand Up @@ -575,6 +584,75 @@ func TestAccAppService_zeroedIpRestriction(t *testing.T) {
})
}

func TestAccAppService_zeroedIpRestrictionHeaders(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_app_service", "test")
r := AppServiceResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
// This configuration includes a single explicit ip_restriction with headers
Config: r.completeIpRestriction(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.#").HasValue("2"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.0").HasValue("2002::1234:abcd:ffff:c0a8:101/64"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.1").HasValue("9.9.9.9/32"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_azure_fdid.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_azure_fdid.0").HasValue("55ce4ed1-4b06-4bf1-b40e-4638452104da"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_fd_health_probe.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_fd_health_probe.0").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_host.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_host.0").HasValue("example.com"),
),
},
data.ImportStep(),
{
// This configuration includes a single explicit ip_restriction with one header
Config: r.completeIpRestrictionOneHeader(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.0").HasValue("9.9.9.9/32"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.1").DoesNotExist(),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_azure_fdid").DoesNotExist(),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_fd_health_probe").DoesNotExist(),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_host").DoesNotExist(),
),
},
data.ImportStep(),
{
// This configuration has no site_config blocks at all.
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.0").HasValue("9.9.9.9/32"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_for.1").DoesNotExist(),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_azure_fdid").DoesNotExist(),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_fd_health_probe").DoesNotExist(),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.0.x_forwarded_host").DoesNotExist(),
),
},
data.ImportStep(),
{
// This configuration explicitly sets ip_restriction.headers to [] using attribute syntax.
Config: r.zeroedIpRestrictionHeaders(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.#").HasValue("1"),
check.That(data.ResourceName).Key("site_config.0.ip_restriction.0.headers.#").HasValue("0"),
),
},
data.ImportStep(),
})
}

func TestAccAppService_manyIpRestrictions(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_app_service", "test")
r := AppServiceResource{}
Expand Down Expand Up @@ -2774,6 +2852,55 @@ resource "azurerm_app_service" "test" {
name = "test-restriction"
priority = 123
action = "Allow"
headers {
x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"]
x_fd_health_probe = ["1"]
x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"]
x_forwarded_host = ["example.com"]
}
}
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}

func (r AppServiceResource) completeIpRestrictionOneHeader(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_app_service_plan" "test" {
name = "acctestASP-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name

sku {
tier = "Standard"
size = "S1"
}
}

resource "azurerm_app_service" "test" {
name = "acctestAS-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
app_service_plan_id = azurerm_app_service_plan.test.id

site_config {
ip_restriction {
ip_address = "10.10.10.10/32"
name = "test-restriction"
priority = 123
action = "Allow"
headers {
x_forwarded_for = ["9.9.9.9/32"]
}
}
}
}
Expand Down Expand Up @@ -2974,6 +3101,47 @@ resource "azurerm_app_service" "test" {
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}

func (r AppServiceResource) zeroedIpRestrictionHeaders(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_app_service_plan" "test" {
name = "acctestASP-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name

sku {
tier = "Standard"
size = "S1"
}
}

resource "azurerm_app_service" "test" {
name = "acctestAS-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
app_service_plan_id = azurerm_app_service_plan.test.id

site_config {
ip_restriction {
ip_address = "10.10.10.10/32"
name = "test-restriction"
priority = 123
action = "Allow"
headers = []
}
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}

func (r AppServiceResource) scmUseMainIPRestriction(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
Expand Down
18 changes: 17 additions & 1 deletion website/docs/r/app_service.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ A `ip_restriction` block supports the following:

* `action` - (Optional) Does this restriction `Allow` or `Deny` access for this IP range. Defaults to `Allow`.

* `headers` - (Optional) The headers for this specific `ip_restriction` as defined below.

---

A `scm_ip_restriction` block supports the following:
Expand All @@ -355,7 +357,21 @@ A `scm_ip_restriction` block supports the following:

* `priority` - (Optional) The priority for this IP Restriction. Restrictions are enforced in priority order. By default, priority is set to 65000 if not specified.

* `action` - (Optional) Allow or Deny access for this IP range. Defaults to Allow.
* `action` - (Optional) Allow or Deny access for this IP range. Defaults to Allow.

* `headers` - (Optional) The headers for this specific `scm_ip_restriction` as defined below.

---

A `headers` block supports the following:

* `x_azure_fdid` - (Optional) A list of allowed Azure FrontDoor IDs.

* `x_fd_health_probe` - (Optional) A list to allow the Azure FrontDoor health probe header

* `x_forwarded_for` - (Optional) A list of allowed 'X-Forwarded-For' IPs.

* `x_forwarded_host` - (Optional) A list of allowed 'X-Forwarded-Host' domains.

---

Expand Down
15 changes: 15 additions & 0 deletions website/docs/r/app_service_slot.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,21 @@ A `ip_restriction` block supports the following:

* `action` - (Optional) Does this restriction `Allow` or `Deny` access for this IP range. Defaults to `Allow`.

* `headers` - (Optional) The headers for this specific `ip_restriction` as defined below.

---

A `headers` block supports the following:

* `x_azure_fdid` - (Optional) A list of allowed Azure FrontDoor IDs.

* `x_fd_health_probe` - (Optional) A list to allow the Azure FrontDoor health probe header

* `x_forwarded_for` - (Optional) A list of allowed 'X-Forwarded-For' IPs.

* `x_forwarded_host` - (Optional) A list of allowed 'X-Forwarded-Host' domains.


---

A `microsoft` block supports the following:
Expand Down
Loading