LibreDevOpsHelpers.AzureStorage/LibreDevOpsHelpers.AzureStorage.psm1

Set-StrictMode -Version Latest

# Remembers each storage account's original network configuration so it can be
# restored after a temporary access rule is removed. Keyed by account name.
$script:LdoStorageStateCache = @{ }

function Add-LdoStorageCurrentIpRule {
    <#
    .SYNOPSIS
        Temporarily grants the caller's public IP access to a storage account.

    .DESCRIPTION
        Caches the account's current network configuration on first use, enables public
        network access with a default Deny action (preserving any existing bypass), and adds
        a network rule for the caller's current public IP. Existing IP and VNet rules are
        left untouched. Use Remove-LdoStorageCurrentIpRule to revert.

        Requires the Azure CLI to be signed in.

    .PARAMETER ResourceGroup
        Resource group containing the storage account.

    .PARAMETER StorageAccountName
        Name of the storage account.

    .EXAMPLE
        Add-LdoStorageCurrentIpRule -ResourceGroup rg-prod -StorageAccountName saprod

    .OUTPUTS
        None
    #>

    [CmdletBinding()]
    [OutputType([void])]
    param(
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ResourceGroup,
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$StorageAccountName
    )

    $ip = Get-LdoPublicIpAddress
    Write-LdoLog -Level INFO -Message "Current public IP: $ip"

    if (-not $script:LdoStorageStateCache.ContainsKey($StorageAccountName)) {
        $account = az storage account show -g $ResourceGroup -n $StorageAccountName -o json | ConvertFrom-Json
        Assert-LdoLastExitCode -Operation "az storage account show ($StorageAccountName)"
        $script:LdoStorageStateCache[$StorageAccountName] = @{
            PublicNetworkAccess = $account.publicNetworkAccess
            DefaultAction = $account.networkRuleSet.defaultAction
            Bypass = $account.networkRuleSet.bypass
        }
        Write-LdoLog -Level DEBUG -Message "Captured original network state for $StorageAccountName."
    }

    $cached = $script:LdoStorageStateCache[$StorageAccountName]
    $update = @('storage', 'account', 'update', '-g', $ResourceGroup, '-n', $StorageAccountName,
        '--public-network-access', 'Enabled', '--default-action', 'Deny')
    if ($cached.Bypass) { $update += @('--bypass', $cached.Bypass) }
    az @update | Out-Null
    Assert-LdoLastExitCode -Operation "az storage account update ($StorageAccountName)"

    $existing = az storage account network-rule list -g $ResourceGroup -n $StorageAccountName --query "[?ipAddress=='$ip']" -o tsv
    if (-not $existing) {
        az storage account network-rule add -g $ResourceGroup -n $StorageAccountName --ip-address $ip | Out-Null
        Assert-LdoLastExitCode -Operation "az storage account network-rule add ($StorageAccountName)"
        Write-LdoLog -Level INFO -Message "Added temporary storage rule for $ip on $StorageAccountName."
    }
    else {
        Write-LdoLog -Level INFO -Message "Storage rule for $ip already present on $StorageAccountName; skipping add."
    }
}

function Remove-LdoStorageCurrentIpRule {
    <#
    .SYNOPSIS
        Removes the caller's temporary storage account access rule and restores settings.

    .DESCRIPTION
        Removes the network rule for the caller's current public IP and restores the
        account's network configuration captured by Add-LdoStorageCurrentIpRule. If no
        cached state exists, the account is left with public network access disabled and a
        default Deny.

        Requires the Azure CLI to be signed in.

    .PARAMETER ResourceGroup
        Resource group containing the storage account.

    .PARAMETER StorageAccountName
        Name of the storage account.

    .EXAMPLE
        Remove-LdoStorageCurrentIpRule -ResourceGroup rg-prod -StorageAccountName saprod

    .OUTPUTS
        None
    #>

    [CmdletBinding()]
    [OutputType([void])]
    param(
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ResourceGroup,
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$StorageAccountName
    )

    $ip = Get-LdoPublicIpAddress

    az storage account network-rule remove -g $ResourceGroup -n $StorageAccountName --ip-address $ip 2>$null | Out-Null
    Write-LdoLog -Level INFO -Message "Removed temporary storage rule for $ip from $StorageAccountName."

    if ($script:LdoStorageStateCache.ContainsKey($StorageAccountName)) {
        $cached = $script:LdoStorageStateCache[$StorageAccountName]
        $publicAccess = if ($cached.PublicNetworkAccess) { $cached.PublicNetworkAccess } else { 'Disabled' }
        $defaultAction = if ($cached.DefaultAction) { $cached.DefaultAction } else { 'Deny' }
    }
    else {
        $publicAccess = 'Disabled'
        $defaultAction = 'Deny'
        $cached = @{ Bypass = $null }
    }

    $restore = @('storage', 'account', 'update', '-g', $ResourceGroup, '-n', $StorageAccountName,
        '--public-network-access', $publicAccess, '--default-action', $defaultAction)
    if ($cached.Bypass) { $restore += @('--bypass', $cached.Bypass) }
    az @restore | Out-Null
    Assert-LdoLastExitCode -Operation "az storage account update ($StorageAccountName)"

    $script:LdoStorageStateCache.Remove($StorageAccountName) | Out-Null
    Write-LdoLog -Level INFO -Message "Restored network ACLs on $StorageAccountName."
}

Export-ModuleMember -Function `
    Add-LdoStorageCurrentIpRule, `
    Remove-LdoStorageCurrentIpRule