Skip to content

Commit ecda156

Browse files
authored
Merge pull request #16 from monte-carlo-data/mrostan/private-storage-azapi
Use azapi for private_storage example to avoid public IP workaround
2 parents 100823c + 8da4a0c commit ecda156

5 files changed

Lines changed: 68 additions & 47 deletions

File tree

examples/private_storage/README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@
33
This example deploys an Agent using a VNet and storage accounts using private access.
44
Private endpoints are created for the storage accounts and the agent is configured to use them.
55

6-
Please note that Storage Accounts are configured with public access enabled only from the IP
7-
address running Terraform.
8-
This is due to a limitation in the way the Azure provider for TF works, in order to create
9-
shares or containers it needs access to the storage account.
10-
You can remove this rule if you run TF from a subnet with access to the Storage Account or you
11-
can update this module to remove the creation of the storage accounts (and the corresponding
12-
share and container) and receive them as input variables.
6+
The Storage Accounts in this example are kept fully private (`public_network_access_enabled = false`):
7+
they are only reachable through the private endpoints. This is possible because the file share and
8+
blob container are created with the [`azapi`](https://registry.terraform.io/providers/Azure/azapi/latest)
9+
provider, which manages them through the Azure Resource Manager (ARM) management plane rather than the
10+
storage data plane. Management-plane calls are not subject to the storage account firewall, so the
11+
machine running Terraform does not need any network path to the storage accounts.
12+
13+
> The `azurerm_storage_share` / `azurerm_storage_container` resources operate over the storage data
14+
> plane (`*.file.core.windows.net` / `*.blob.core.windows.net`), so with a private account they
15+
> would require Terraform to run from a network with access to the account (e.g. a subnet/private
16+
> endpoint), or a temporary public firewall rule allowing the runner's IP. Using `azapi` avoids that
17+
> entirely. If you prefer the `azurerm`-only approach, you can instead run Terraform from a subnet
18+
> with access to the Storage Account, or remove the storage account creation from this example and
19+
> pass them in as input variables.
1320
1421
Note that there are additional requirements and limitations for using private storage accounts
1522
with Monte Carlo. Please review the docs [here](https://mc-d.io/LgZYUfJ) for further details.

examples/private_storage/main.tf

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,6 @@ resource "random_id" "unique_id" {
22
byte_length = 4
33
}
44

5-
# get the current IP address, used to allow access to the storage account from TF
6-
# to create the share
7-
data "http" "ip" {
8-
url = "https://checkip.amazonaws.com"
9-
}
10-
11-
locals {
12-
my_ip = chomp(data.http.ip.response_body)
13-
}
14-
155
locals {
166
suffix = random_id.unique_id.hex
177
resource_group_name = "mcd-agent-private-storage"
@@ -36,10 +26,10 @@ module "apollo" {
3626
existing_storage_accounts = {
3727
agent_durable_function_storage_account_name = azurerm_storage_account.durable_function_storage.name
3828
agent_durable_function_storage_account_access_key = azurerm_storage_account.durable_function_storage.primary_access_key
39-
agent_durable_function_storage_account_share_name = azurerm_storage_share.durable_function_storage.name
29+
agent_durable_function_storage_account_share_name = azapi_resource.durable_function_storage_share.name
4030

4131
agent_data_storage_account_name = azurerm_storage_account.mcd_agent_storage.name
42-
agent_data_storage_container_name = azurerm_storage_container.mcd_agent_storage_container.name
32+
agent_data_storage_container_name = azapi_resource.mcd_agent_storage_container.name
4333

4434
private_access = true
4535
}
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1+
terraform {
2+
required_providers {
3+
# azapi is used to create the blob container and file share via the ARM
4+
# management plane, which is not subject to the storage account firewall.
5+
# This lets us keep the storage accounts fully private (no public access).
6+
azapi = {
7+
source = "Azure/azapi"
8+
version = "~> 2.0"
9+
}
10+
}
11+
}
12+
113
provider "azurerm" {
214
features {
315
resource_group {
416
prevent_deletion_if_contains_resources = false
517
}
618
}
7-
}
19+
}
20+
21+
provider "azapi" {}

examples/private_storage/storage.tf

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,28 @@ resource "azurerm_storage_account" "durable_function_storage" {
1010
allow_nested_items_to_be_public = false
1111
infrastructure_encryption_enabled = true
1212

13-
# Using this approach to allow access from the IP address running Terraform,
14-
# this is required for the creation of the share below and run TF plan/apply
15-
# in the future.
16-
# You can manually disable public access completely after deploying this module,
17-
# just remember to restore this rule before executing TF again.
18-
public_network_access_enabled = true
13+
# The account is kept fully private: access is only possible through the
14+
# private endpoints below. The share is created via the azapi provider
15+
# (ARM management plane), so no public/data-plane access from the Terraform
16+
# runner is required.
17+
public_network_access_enabled = false
1918
network_rules {
2019
default_action = "Deny"
21-
ip_rules = [local.my_ip]
2220
}
2321
}
2422

25-
# Share to use for the Azure Function, for the WEBSITE_CONTENTSHARE setting
26-
resource "azurerm_storage_share" "durable_function_storage" {
27-
name = azurerm_storage_account.durable_function_storage.name
28-
storage_account_name = azurerm_storage_account.durable_function_storage.name
29-
quota = 50
23+
# Share to use for the Azure Function, for the WEBSITE_CONTENTSHARE setting.
24+
# Created through the ARM management plane (azapi) rather than the storage data
25+
# plane, so it works even with public_network_access_enabled = false.
26+
resource "azapi_resource" "durable_function_storage_share" {
27+
type = "Microsoft.Storage/storageAccounts/fileServices/shares@2023-01-01"
28+
name = azurerm_storage_account.durable_function_storage.name
29+
parent_id = "${azurerm_storage_account.durable_function_storage.id}/fileServices/default"
30+
body = {
31+
properties = {
32+
shareQuota = 50
33+
}
34+
}
3035
}
3136

3237
# Private endpoints for the Durable function storage account
@@ -114,23 +119,28 @@ resource "azurerm_storage_account" "mcd_agent_storage" {
114119
allow_nested_items_to_be_public = false
115120
infrastructure_encryption_enabled = true
116121

117-
# Using this approach to allow access from the IP address running Terraform,
118-
# this is required for the creation of the container below and run TF plan/apply
119-
# in the future.
120-
# You can manually disable public access completely after deploying this module,
121-
# just remember to restore this rule before executing TF again.
122-
public_network_access_enabled = true
122+
# The account is kept fully private: access is only possible through the
123+
# private endpoint below. The container is created via the azapi provider
124+
# (ARM management plane), so no public/data-plane access from the Terraform
125+
# runner is required.
126+
public_network_access_enabled = false
123127
network_rules {
124128
default_action = "Deny"
125-
ip_rules = [local.my_ip]
126129
}
127130
}
128131

129-
# Container used by the MC agent
130-
resource "azurerm_storage_container" "mcd_agent_storage_container" {
131-
name = local.agent_data_storage_container_name
132-
storage_account_name = azurerm_storage_account.mcd_agent_storage.name
133-
container_access_type = "private"
132+
# Container used by the MC agent.
133+
# Created through the ARM management plane (azapi) rather than the storage data
134+
# plane, so it works even with public_network_access_enabled = false.
135+
resource "azapi_resource" "mcd_agent_storage_container" {
136+
type = "Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01"
137+
name = local.agent_data_storage_container_name
138+
parent_id = "${azurerm_storage_account.mcd_agent_storage.id}/blobServices/default"
139+
body = {
140+
properties = {
141+
publicAccess = "None"
142+
}
143+
}
134144
}
135145

136146
# Private endpoint for the MC agent storage account
@@ -160,7 +170,7 @@ resource "azurerm_storage_management_policy" "mcd_agent_storage_lifecycle" {
160170
enabled = true
161171
filters {
162172
blob_types = ["blockBlob", "appendBlob"]
163-
prefix_match = ["${azurerm_storage_container.mcd_agent_storage_container.name}/${local.agent_data_store_data_prefix}"]
173+
prefix_match = ["${azapi_resource.mcd_agent_storage_container.name}/${local.agent_data_store_data_prefix}"]
164174
}
165175
actions {
166176
base_blob {
@@ -173,7 +183,7 @@ resource "azurerm_storage_management_policy" "mcd_agent_storage_lifecycle" {
173183
enabled = true
174184
filters {
175185
blob_types = ["blockBlob", "appendBlob"]
176-
prefix_match = ["${azurerm_storage_container.mcd_agent_storage_container.name}/${local.agent_data_store_data_prefix}/tmp"]
186+
prefix_match = ["${azapi_resource.mcd_agent_storage_container.name}/${local.agent_data_store_data_prefix}/tmp"]
177187
}
178188
actions {
179189
base_blob {

main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
locals {
22
# Wrapper metadata
3-
mcd_wrapper_version = "1.0.3"
3+
mcd_wrapper_version = "1.0.5"
44
mcd_agent_platform = "AZURE"
55
mcd_agent_deployment_type = "TERRAFORM"
66

0 commit comments

Comments
 (0)