Azure / bicep-types-az

Bicep type definitions for ARM resources
MIT License
82 stars 26 forks source link

Role Assignment on Storage Account File share #1532

Open gummigroda opened 2 years ago

gummigroda commented 2 years ago

Bicep version Bicep CLI version 0.4.1008 (223b8d227a)

Describe the bug Creating a storage account file share and then setting role assignment on the file share (not the storage account) does not target correct "scope". Expected role assignment to scope to the file share

To Reproduce

Bicep file: fileshare-and-permissions.bicep

param storageAccount string
param fileshareName string
@allowed([
  'Cool'
  'Hot'
  'TransactionOptimized'
  'Premium'
])
param storageTier string
param quotaInGB int
param permissionRoleGuid string
param groupsOid array

resource stg 'Microsoft.Storage/storageAccounts@2021-08-01' existing = {
  name: storageAccount
}

resource fileshare 'Microsoft.Storage/storageAccounts/fileServices/shares@2021-08-01' = {
  name: '${stg.name}/default/${fileshareName}'
  properties: {
    accessTier: storageTier
    enabledProtocols:'SMB'
    shareQuota: quotaInGB
  }
}

resource roleDef 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = {
  scope: subscription()
  name: permissionRoleGuid
}

resource perms 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = [for group in groupsOid: {
  name: guid(subscription().id,resourceGroup().id, stg.name, fileshareName, group)
  scope: fileshare
  properties:{
    roleDefinitionId: roleDef.id
    principalId: group
  }
}]
$Props = @{
    Name               = 'File-Share'
    ResourceGroupName  = "my-rg"
    TemplateFile       = ".\fileshare-and-permissions.bicep"
    storageAccount     = "mystorageacc"
    fileshareName      = "test"
    storageTier        = "Hot"
    quotaInGB          = 5
    permissionRoleGuid = "0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb" # SMB Contributor
    groupsOid          = ("48477e2b-7ae7-46da-97c0-6690a03c2ce4")
}
New-AzResourceGroupDeployment @Props

Verifying the role assignment in the portal does not show the just added role/assignment.

Running pwsh:

Get-AzRoleAssignment | select displayname, scope | sort scope

Gives this scope: image

Where the green /fileshares/fxprofiles (created by another ARM template) works as expected. And the red /shares/test are created by the above bicep template and does not work.

alex-frankel commented 2 years ago

Hmm, your bicep code does look correct. Can you share the ARM template code that is creating the fileshare role assignment properly?

gummigroda commented 2 years ago

This is the ARM template.


{
            "type": "Microsoft.Authorization/roleAssignments",
            "apiVersion": "2021-04-01-preview",
            "name": "[guid(concat(parameters('companyAbbreviation'),'shareusr'))]",
            "dependsOn": [
                "[variables('storageName')]"
            ],
            "scope": "[concat('Microsoft.Storage/storageAccounts/', variables('storageName'),'/fileServices/default/fileshares/', variables('shareName'))]",
            "properties": {
                "roleDefinitionId": "[variables('SMBContributor')]",
                "principalId": "[parameters('WVDUserGroupID')]"
            }
        }

As I understand, the scope should be /fileshares/ and not only /shares/

brwilkinson commented 2 years ago

I am unable to repro your scenario with the following template, which i copied from your example:

param storageAccount string = 'acu1brwaoat5sadata2'
param fileshareName string = 'function2'
param permissionRoleGuid string = '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb'
param groupsOid array = [
  '3e83b36a-9bdd-4775-a30e-8e126a533f33'
]

resource stg 'Microsoft.Storage/storageAccounts@2021-08-01' existing = {
  name: storageAccount
}

resource fileshare 'Microsoft.Storage/storageAccounts/fileServices/shares@2021-08-01' = {
  name: '${stg.name}/default/${fileshareName}'
  properties: {
    enabledProtocols:'SMB'
  }
}

resource roleDef 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = {
  scope: subscription()
  name: permissionRoleGuid
}

resource perms 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = [for group in groupsOid: {
  name: guid(subscription().id,resourceGroup().id, stg.name, fileshareName, group)
  scope: fileshare
  properties:{
    roleDefinitionId: roleDef.id
    principalId: group
  }
}]

This gives me the following..

ACU1-BRW-AOA-T5-uaiStorageAccountFileContributor
/subscriptions/b8f402aa-20f7-4888-b45c-3cf086dad9c3/resourcegroups/ACU1-BRW-AOA-RG-T5/providers/Microsoft.Storage/storageAccounts/acu1brwaoat5sadata2/fileServices/default/shares/function2

which is identical to my own template that creates the file share and role assignment

ACU1-BRW-AOA-T5-uaiStorageAccountFileContributor
/subscriptions/b8f402aa-20f7-4888-b45c-3cf086dad9c3/resourcegroups/ACU1-BRW-AOA-RG-T5/providers/Microsoft.Storage/storageAccounts/acu1brwaoat5sadata2/fileServices/default/shares/function

gummigroda commented 2 years ago

Hi,

Thanks for the engagement, and excuse me for not being clear enough.

The template "works" and completes with a status as successful. But then trying to access the file share via SMB with a AD user that's a member of the assigned group does not give it access to the file share.

And if I click through to the bicep created file share in the portal, then IAM, the role assignment (Storage File Data SMB Share Contributor) does not show.

image

To my findings, this depends on the scope not being correct. Comparing a role assignment via the portal (IAM) on the file share, gives the scope path: xxx/Microsoft.Storage/storageAccounts/acu1brwaoat5sadata2/fileServices/default/FILESHARES/function2
and via the bicep template:
xxx/Microsoft.Storage/storageAccounts/acu1brwaoat5sadata2/fileServices/default/SHARES/function2

The only way of seeing it is via Powershell and Get-AzRoleAssignment | where scope -match 'storage/' | select displayname, scope | sort scope as the screenshot from the first post.

Does your file share function2 IAM setting show the Storage File Data SMB Share Contributor in the portal?

brwilkinson commented 2 years ago

Hi @gummigroda

Okay I see, this topic is specifically related to the File Share Roles for SMB.

I assume that you have domain joined your File Share so that it can support Kerberos authentication from your on-prem or traditional Active Directory ?

I don't have a domain joined file share right now, however once you confirm I will spin one up so that I can repro your scenario.

gummigroda commented 2 years ago

Hi,

Yes, the storage account is connected to AADDS in our Azure tenant. (Using the same storage account with the above ARM template does set the correct permissions)

brwilkinson commented 2 years ago

Thanks @gummigroda

(Using the same storage account with the above ARM template does set the correct permissions)

Can you just confirm what you meant by this?

gummigroda commented 2 years ago

Hi @brwilkinson,

I'm trying to migrate to bicep templates and when I'm using the "old" ARM template (see post3) with the same role assignment targeted to the same storage account, it works as expected.

Maybe I'm doing it wrong but the role permissions get set to different scopes with bicep than with ARM. In the ARM template the scope is set "manually".

brwilkinson commented 2 years ago

Yes, it's rather strange, thanks for confirming. So far I have not been able to repro, however I am still working on it.

Two things that I like to do, that you can try.

  1. Right click on the bicep file and select build that will compile it to the json arm template
  2. If you have a working ARM template use the bicep cli with the following to convert to to bicep.
bicep decompile D:\rbac-storage.json --outfile D:\rbac-storage.bicep

Then you can confirm any visible differences.

gummigroda commented 2 years ago

Build on bicep file:

{
      "copy": {
        "name": "perms",
        "count": "[length(parameters('groupsOid'))]"
      },
      "type": "Microsoft.Authorization/roleAssignments",
      "apiVersion": "2020-08-01-preview",
      "scope": "[format('Microsoft.Storage/storageAccounts/{0}/fileServices/{1}/shares/{2}', split(format('{0}/default/{1}', parameters('storageAccount'), parameters('fileshareName')), '/')[0], split(format('{0}/default/{1}', parameters('storageAccount'), parameters('fileshareName')), '/')[1], split(format('{0}/default/{1}', parameters('storageAccount'), parameters('fileshareName')), '/')[2])]",
      "name": "[guid(subscription().id, resourceGroup().id, parameters('storageAccount'), parameters('fileshareName'), parameters('groupsOid')[copyIndex()])]",
      "properties": {
        "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('permissionRoleGuid'))]",
        "principalId": "[parameters('groupsOid')[copyIndex()]]"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', split(format('{0}/default/{1}', parameters('storageAccount'), parameters('fileshareName')), '/')[0], split(format('{0}/default/{1}', parameters('storageAccount'), parameters('fileshareName')), '/')[1], split(format('{0}/default/{1}', parameters('storageAccount'), parameters('fileshareName')), '/')[2])]"
      ]
    }

/{1}/shares/{2}/ instead of /{1}/fileshares/{2}/

Decompiled arm:

resource storageName_default_shareName 'Microsoft.Storage/storageAccounts/fileServices/shares@2019-06-01' = {
  name: '${storageName_var}/default/${shareName}'
  properties: {
    accessTier: 'Hot'
  }
  dependsOn: [
    storageName
  ]
}

resource companyAbbreviation_shareusr 'Microsoft.Authorization/roleAssignments@2021-04-01-preview' = {
  scope: 'Microsoft.Storage/storageAccounts/${storageName_var}/fileServices/default/fileshares/${shareName}'
  name: guid('${companyAbbreviation}shareusr')
  properties: {
    roleDefinitionId: SMBContributor
    principalId: WVDUserGroupID
  }
  dependsOn: [
    storageName
  ]
}

The scope seems to be correct. But I don't know if the reference to the fileshare resource is the same?

brwilkinson commented 2 years ago

okay I have been able to repro you scenario now.

https://docs.microsoft.com/en-us/azure/storage/files/storage-files-active-directory-overview

I will look into this some more and see what is the best way to assign these permissions.

brwilkinson commented 2 years ago

It seems we do not publish this in the Storage APIs.

https://docs.microsoft.com/en-us/azure/templates/microsoft.storage/allversions

The following is missing

Microsoft.Storage/storageAccounts/fileServices/fileshares

Until this is added, I can only think of 1 workaround (I actually use this for ALL resource scoped role assignments)

https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/bicep/x.RBAC-ALL-RA-Resource.bicep https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/bicep/loadTextContext/genericRoleAssignment.json

in this case you will be passing in:

resourceid: /subscriptions/b8f402aa-20f7-4888-b45c-3cf086dad9c3/resourceGroups/ACU1-BRW-AOA-RG-T5/providers/Microsoft.Storage/storageAccounts/acu1brwaoat5sadata2/fileServices/default/fileshares/function2

We can mark this issue as provider bug to inform the storage team about this.

gummigroda commented 2 years ago

Okey, thanks.

Anyone able to educate me on the logic of the /default/ part of the name for the share?

img

Tried to find it in the docs, but it just says 'string', And to my logic it should just be enough with '${stg.name}/${fileshareName}' and without the 'azure-quickstart-templates' I'll probably wouldn't have got it to work.

Any link describing why is much appreciated. T.I.A.

brwilkinson commented 2 years ago

when you have the format:

Provider/Type/Subtype

e.g. Microsoft.Storage/storageAccounts/blobServices e.g. Microsoft.Storage/storageAccounts/fileServices

The names are the parameters at each level:

e.g. StorageAccountName/default

The default is just the standard name for the blobService or fileService.

You can use these specifically for certain file or blob settings

E.g. https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/bicep/SA-Storage.bicep#L123

resource SABlobService 'Microsoft.Storage/storageAccounts/blobServices@2021-06-01' = if(!(contains(storageInfo, 'isHnsEnabled') && bool(storageInfo.isHnsEnabled))) {
  name: 'default'
  parent: SA
  properties: {
    isVersioningEnabled: contains(storageInfo, 'blobVersioning') ? storageInfo.blobVersioning : false
    changeFeed: {
      enabled: contains(storageInfo, 'changeFeed') ? storageInfo.changeFeed : false
    }
    deleteRetentionPolicy: contains(storageInfo, 'softDeletePolicy') ? storageInfo.softDeletePolicy : null
  }
}

// https://docs.microsoft.com/en-us/azure/storage/files/files-smb-protocol?tabs=azure-powershell
resource SAFileService 'Microsoft.Storage/storageAccounts/fileServices@2021-06-01' = {
  name: 'default'
  parent: SA
  properties: {
    shareDeleteRetentionPolicy: contains(storageInfo, 'softDeletePolicy') ? storageInfo.softDeletePolicy : null
    protocolSettings: {
      smb: {
        versions: 'SMB3.0;SMB3.1.1' // remove SMB2.1
        kerberosTicketEncryption: 'AES-256' // remove RC4-HMAC
        multichannel: !contains(storageInfo, 'multichannel') ? null : {
          enabled: bool(storageInfo.multichannel)
        }
      }
    }
  }
}

This becomes part of the resourceid and since the container/file share is a subtype of that blob/file service, it's included in the path.

As far as I am aware it always just has the name default.

r4hulp commented 2 years ago

We are facing a similar issue. We can not assign roles on the File Share level from a bicep template. Has anyone figured out an alternative?

gummigroda commented 2 years ago

Save @brwilkinson's template: https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/bicep/loadTextContext/genericRoleAssignment.json

Add this to the bicep template:

resource ResourceRoleAssignmentUsers 'Microsoft.Resources/deployments@2021-04-01' = {
  name: 'user-permissions_${now}'
  properties: {
    mode: 'Incremental'
    expressionEvaluationOptions: {
      scope: 'Outer'
    }
    template: json(loadTextContent('./genericRoleAssignment.json'))
    parameters: {
      scope: {
        value: replace(fileshare.id, '/shares/','/fileshares/')
      }
      name: {
        value: guid('${subscription().id},${resourceGroup().id},${stg.name},${fileshare.name},-users')
      }
      roleDefinitionId: {
        value: '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb' // Storage File Data SMB Share Contributor
      }
      principalId: {
        value: genSettings.AADGroup
      }
      principalType: {
        value: 'Group'
      }
    }
  }
}

Change the loadTextContent path to where you saved the arm template

r4hulp commented 2 years ago

Save @brwilkinson's template: https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/bicep/loadTextContext/genericRoleAssignment.json

Add this to the bicep template:

resource ResourceRoleAssignmentUsers 'Microsoft.Resources/deployments@2021-04-01' = {
  name: 'user-permissions_${now}'
  properties: {
    mode: 'Incremental'
    expressionEvaluationOptions: {
      scope: 'Outer'
    }
    template: json(loadTextContent('./genericRoleAssignment.json'))
    parameters: {
      scope: {
        value: replace(fileshare.id, '/shares/','/fileshares/')
      }
      name: {
        value: guid('${subscription().id},${resourceGroup().id},${stg.name},${fileshare.name},-users')
      }
      roleDefinitionId: {
        value: '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb' // Storage File Data SMB Share Contributor
      }
      principalId: {
        value: genSettings.AADGroup
      }
      principalType: {
        value: 'Group'
      }
    }
  }
}

Change the loadTextContent path to where you saved the arm template

Perfect, thank you :-)

brianstringfellow commented 1 year ago

It seems we do not publish this in the Storage APIs.

https://docs.microsoft.com/en-us/azure/templates/microsoft.storage/allversions

The following is missing

Microsoft.Storage/storageAccounts/fileServices/fileshares

Until this is added, I can only think of 1 workaround (I actually use this for ALL resource scoped role assignments)

  • This basically falls back to calling a generic ARM template, where you pass in the resourceid for the scope of the role assignment.

https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/bicep/x.RBAC-ALL-RA-Resource.bicep https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/bicep/loadTextContext/genericRoleAssignment.json

in this case you will be passing in:

resourceid: /subscriptions/b8f402aa-20f7-4888-b45c-3cf086dad9c3/resourceGroups/ACU1-BRW-AOA-RG-T5/providers/Microsoft.Storage/storageAccounts/acu1brwaoat5sadata2/fileServices/default/fileshares/function2

We can mark this issue as provider bug to inform the storage team about this.

Has there been a bug filed with the provider team? I have now encountered the problem and confusion about the scope to use (shares vs fileshares). I see that fileshares is the name that works but it certainly doesn't match the ARM provider type.

/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{accountName}/fileServices/default/shares?api-version=2022-05-01

mrkesu commented 1 year ago

Wow, spent a day troubleshooting this myself only to discover it has been reported a year ago 😆

Will there be a native fix for this, or is this a case of a new "permanent workaround"?

alex-frankel commented 1 year ago

If anyone here who has encountered the issue can open a support ticket to have this routed to the Storage Resource Provider team, that would be helpful.

AlexanderSehr commented 8 months ago

I just filed a support request as this also was raised in CARML and would have, if not handled, end up in AVM. @alex-frankel I'll implement the workaround proposed by @brwilkinson for now, but sincerely hope this is fixed by the Storage PG. Inventing an artificial resource type 'fileShares' appears to be a major oversight on their part 🔧

Mike-E-angelo commented 7 months ago

I ran into this issue as well and see there was a PR by @AlexanderSehr 🙏 Has it made its way into Bicep or do I have something misunderstood (quite likely 😁)?

FWIW I did try the workaround but am not having any luck with it. It seems to execute but the Role Assignment is not listed in the share's assignments. In my case I am wanting to add a Managed Identity, and I noticed the workaround is for a group. I am curious if this is why this might not be working. 🤔

AlexanderSehr commented 7 months ago

I ran into this issue as well and see there was a PR by @AlexanderSehr 🙏 Has it made its way into Bicep or do I have something misunderstood (quite likely 😁)?

FWIW I did try the workaround but am not having any luck with it. It seems to execute but the Role Assignment is not listed in the share's assignments. In my case I am wanting to add a Managed Identity, and I noticed the workaround is for a group. I am curious if this is why this might not be working. 🤔

You wish :D No, it was not yet fixed by the Provider and my support ticket is also still open. However - I did implement the workaround both in the CARML, as well as the (to be) Azure-Verified-Modules module if you want to have a reference and hope to get rid of the workaround as soon as I hear back from the Product Group.

Mike-E-angelo commented 7 months ago

Thank you for the clarification @AlexanderSehr. FWIW I was able to get the workaround working after all. 😌 I had the wrong path/share used (lots of moving pieces over here), so I am set in the meantime.

Also, I did want to mention that in Visual Studio Code, using the above workaround does result in the following warning:

image

Interestingly enough the warning's URL resolves to here: https://www.bing.com/?ref=aka&shorturl=bicep/linter/no-deployments-resources

Not sure if this is a bug, but thought I would mention it. Please note that I am already in a module and wonder if this is contributing to this.

AlexanderSehr commented 7 months ago

Thank you for the clarification @AlexanderSehr. FWIW I was able to get the workaround working after all. 😌 I had the wrong path/share used (lots of moving pieces over here), so I am set in the meantime.

Also, I did want to mention that in Visual Studio Code, using the above workaround does result in the following warning:

image

Interestingly enough the warning's URL resolves to here: https://www.bing.com/?ref=aka&shorturl=bicep/linter/no-deployments-resources

Not sure if this is a bug, but thought I would mention it. Please note that I am already in a module and wonder if this is contributing to this.

Nah, it's by design. With Bicep, Microsoft.Resource/deployments is now 'hidden' behind the module myModule 'localPath/main.bicep' = { (...) } syntax. So if you use local references you'll see that in the rendered ARM template they're all just deployment objects that correspond in turn to deployment objects in the Azure Portal. However, in this case you're referencing a nested linked ARM template so you don't really get around it. You can supress it though.

anthony-c-martin commented 6 months ago

The link https://aka.ms/bicep/linter/no-deployments-resources has been fixed to correctly redirect to Microsoft Docs.

The linter rule is valid here - instead of:

resource ResourceRoleAssignmentUsers 'Microsoft.Resources/deployments@2021-04-01' = {
  name: 'user-permissions_${now}'
  properties: {
    mode: 'Incremental'
    expressionEvaluationOptions: {
      scope: 'Outer'
    }
    template: json(loadTextContent('./genericRoleAssignment.json'))
    parameters: {
      scope: {
        value: replace(fileshare.id, '/shares/','/fileshares/')
      }
      name: {
        value: guid('${subscription().id},${resourceGroup().id},${stg.name},${fileshare.name},-users')
      }
      roleDefinitionId: {
        value: '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb' // Storage File Data SMB Share Contributor
      }
      principalId: {
        value: genSettings.AADGroup
      }
      principalType: {
        value: 'Group'
      }
    }
  }
}

The recommendation is to use:

module ResourceRoleAssignmentUsers 'genericRoleAssignment.json' = {
  name: 'user-permissions_${now}'
  params: {
    scope: replace(fileshare.id, '/shares/','/fileshares/')
    name: guid('${subscription().id},${resourceGroup().id},${stg.name},${fileshare.name},-users')
    roleDefinitionId: '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb' // Storage File Data SMB Share Contributor
    principalId: genSettings.AADGroup
    principalType: 'Group'
  }
}
BronsonMagnan commented 2 months ago

Hello from June 2024, this is still an issue. and I spent an entire day of troubleshooting before I noticed /shares/ <> /fileshares/. The work around with calling the arm from inside bicep does the trick though.