# saif-resources / storage

Blob storage module. Wraps `iac-azure-modules` storageaccount with private endpoint and App Registration SP RBAC.

## Usage

```hcl
module "storage" {
  source  = "app.terraform.io/SAIFCorp/resources/saif//modules/storage"
  version = "~> 3.5.0"

  context  = module.environment.context
  identity = module.identity.identity

  resource_group_name     = module.resource_group.resource_group_name
  resource_group_location = module.resource_group.location

  containers             = ["documents", "uploads"]
  connection_string_name = "storage"
}

# Compose app_settings
module "api" {
  app_settings = merge(
    module.storage.app_settings,
    # ...
  )
}
```

## Inputs

| Name                       | Description                                  | Required |
| -------------------------- | -------------------------------------------- | -------- |
| `context`                  | Platform context from `environment` module   | yes      |
| `identity`                 | Identity bundle from `identity` module       | yes      |
| `resource_group_name`      | Resource group for the storage account       | yes      |
| `resource_group_location`  | Azure region                                 | yes      |
| `containers`               | List of blob container names to create       | no       |
| `connection_string_name`   | App settings key suffix (default: `storage`) | no       |
| `account_replication_type` | Replication type (default: `LRS`)            | no       |

## Outputs

| Name                    | Description                                  |
| ----------------------- | -------------------------------------------- |
| `account_name`          | Storage account name                         |
| `account_id`            | Storage account resource ID                  |
| `primary_blob_endpoint` | Primary blob endpoint URL                    |
| `container_names`       | Names of created containers                  |
| `app_settings`          | `{ "ConnectionStrings__{name}" = endpoint }` |

## What it creates

- Storage account (Standard LRS, shared key disabled, OAuth default)
- Blob containers (private access)
- Private endpoint (services subnet, blob sub-resource)
- RBAC: Storage Blob Data Contributor → App Registration SP (runtime data-plane blob access)

## RBAC notes

The App Registration service principal receives **Storage Blob Data Contributor** to enable data-plane operations (no connection strings, no SAS tokens). At runtime the platform pins `DefaultAzureCredential` to `EnvironmentCredential` (`AZURE_TOKEN_CREDENTIALS=environmentcredential`, `AZURE_CLIENT_ID` = app registration), so the SP — not the UAMI — is the identity making blob calls. The UAMI covers platform concerns only (ACR pull, Key Vault reference resolution). This matches the `cosmosdb` module, which grants its data-plane role to the SP.

Contributor rather than Owner follows least privilege and matches Aspire's default role assignment for `AddAzureStorage` (`StorageBlobDataContributor`); Owner only adds ADLS Gen2 POSIX ACL operations, which Forge apps don't use.

<!-- BEGIN_TF_DOCS -->
## Providers

No providers.

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_account_replication_type"></a> [account\_replication\_type](#input\_account\_replication\_type) | Storage account replication type (LRS, ZRS, GRS, etc.) | `string` | `"LRS"` | no |
| <a name="input_connection_string_name"></a> [connection\_string\_name](#input\_connection\_string\_name) | Key suffix for the app\_settings connection string: ConnectionStrings\_\_{name} | `string` | `"storage"` | no |
| <a name="input_containers"></a> [containers](#input\_containers) | List of blob container names to create | `list(string)` | `[]` | no |
| <a name="input_context"></a> [context](#input\_context) | Platform context from the environment module | `any` | n/a | yes |
| <a name="input_deployer_principal_ids"></a> [deployer\_principal\_ids](#input\_deployer\_principal\_ids) | Map of identity keys to principal IDs granted Blob Data Contributor for CI/CD deployments | `map(string)` | `{}` | no |
| <a name="input_enable_static_website"></a> [enable\_static\_website](#input\_enable\_static\_website) | Enable static website hosting on the storage account | `bool` | `false` | no |
| <a name="input_identity"></a> [identity](#input\_identity) | Identity bundle from the identity module. Required when enable\_static\_website is false (blob access via the App Registration SP). | `any` | `null` | no |
| <a name="input_name"></a> [name](#input\_name) | Override storage account name (bypasses namer). Use for migrations where the existing name differs from the namer convention. | `string` | `null` | no |
| <a name="input_network_rules_enabled"></a> [network\_rules\_enabled](#input\_network\_rules\_enabled) | Enable storage account network rules (deny by default) | `bool` | `true` | no |
| <a name="input_network_rules_ip_rules"></a> [network\_rules\_ip\_rules](#input\_network\_rules\_ip\_rules) | Additional public IP ranges to allow through the storage firewall. SAIF corporate/colocation ranges are always included. | `list(string)` | `[]` | no |
| <a name="input_private_endpoint_enabled"></a> [private\_endpoint\_enabled](#input\_private\_endpoint\_enabled) | Create a private endpoint for blob access. Disable for static websites where FD Private Link provides connectivity. | `bool` | `true` | no |
| <a name="input_resource_group_location"></a> [resource\_group\_location](#input\_resource\_group\_location) | The Azure region for the storage account | `string` | n/a | yes |
| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | The resource group for the storage account | `string` | n/a | yes |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_account_id"></a> [account\_id](#output\_account\_id) | The storage account resource ID |
| <a name="output_account_name"></a> [account\_name](#output\_account\_name) | The storage account name |
| <a name="output_app_settings"></a> [app\_settings](#output\_app\_settings) | App settings map for web app configuration |
| <a name="output_container_names"></a> [container\_names](#output\_container\_names) | Names of the created containers |
| <a name="output_primary_blob_endpoint"></a> [primary\_blob\_endpoint](#output\_primary\_blob\_endpoint) | The primary blob endpoint URL |
| <a name="output_static_website_host"></a> [static\_website\_host](#output\_static\_website\_host) | The primary web host for the static website (null when static website is disabled) |

## Resources
    
<!-- END_TF_DOCS -->

---

[View source on GitHub](https://github.com/saif-corp/forge/blob/main/src/terraform/saif-resources/modules/storage/README.md)
