Родитель
d9edfdafe6
Коммит
abc1d99ac8
|
@ -11,5 +11,13 @@
|
|||
"[powershell]": {
|
||||
"editor.formatOnSave": false,
|
||||
"editor.tabSize": 4
|
||||
}
|
||||
},
|
||||
"cSpell.words": [
|
||||
"bicepparam",
|
||||
"contoso",
|
||||
"eastus"
|
||||
],
|
||||
"cSpell.enableFiletypes": [
|
||||
"bicep"
|
||||
]
|
||||
}
|
||||
|
|
22
README.md
22
README.md
|
@ -13,10 +13,10 @@ To learn more about PSRule for Azure, see <https://aka.ms/ps-rule-azure>.
|
|||
|
||||
This repository includes:
|
||||
|
||||
- **Azure Templates** — Starter Azure Resource Manager (ARM) templates and parameter files.
|
||||
- Use the files in the `template/` folder if you are using ARM templates to deploy resources.
|
||||
- **Azure Bicep** — Starter Azure Bicep deployments and test files.
|
||||
- Use the files in the `bicep/` folder if you are using Bicep deployments and modules to deploy resources.
|
||||
- **Azure Bicep deployment** — Starter Azure Bicep deployments.
|
||||
- Use the files in the `deployments/` folder if you are using Bicep to deploy resources.
|
||||
- **Azure Bicep modules** — Starter Azure Bicep modules.
|
||||
- Use the files in the `modules/` folder if you are using Bicep to create reusable modules with tests.
|
||||
- **GitHub Actions** — Starter workflow for checking Azure Infrastructure as Code (IaC).
|
||||
- Use the files in the `.github/workflows/` to check your Azure IaC with GitHub Actions.
|
||||
- The `ms-analyze.yaml` file can be ignore or removed as this will not execute outside this repository.
|
||||
|
@ -28,14 +28,21 @@ This repository includes:
|
|||
- PSRule options are configures within `ps-rule.yaml`.
|
||||
- Options include suppressing rules, configuring input/ output, and any rules modules.
|
||||
|
||||
> **ARM templates**
|
||||
> PSRule for Azure supports ARM templates in addition to Bicep code.
|
||||
> However going forward this repository will focus on Bicep deployments and modules.
|
||||
> Existing ARM templates samples are no longer maintained and have been archived.
|
||||
> To access these samples jump to the [archive/with-arm-templates][3] branch.
|
||||
|
||||
[3]: https://github.com/Azure/PSRule.Rules.Azure-quickstart/tree/archive/with-arm-templates
|
||||
|
||||
## What to expect?
|
||||
|
||||
This repository shows valid uses of PSRule for Azure, both pass and failure cases.
|
||||
Inspect the following files for instructions to test PSRule for Azure rules by creating a failure.
|
||||
|
||||
- [bicep/deployments/contoso/landing-zones/subscription-1/rg-app-001/dev.bicepparam](bicep/deployments/contoso/landing-zones/subscription-1/rg-app-001/dev.bicepparam)
|
||||
- [bicep/deployments/contoso/landing-zones/subscription-1/rg-app-002/deploy.bicep](bicep/deployments/contoso/landing-zones/subscription-1/rg-app-002/deploy.bicep)
|
||||
- [template/deployments/contoso/landing-zones/subscription-1/rg-app-001/sttemplateapp001.parameters.json](template/deployments/contoso/landing-zones/subscription-1/rg-app-001/sttemplateapp001.parameters.json)
|
||||
- [deployments/contoso/landing-zones/subscription-1/rg-app-001/dev.bicepparam](deployments/contoso/landing-zones/subscription-1/rg-app-001/dev.bicepparam)
|
||||
- [deployments/contoso/landing-zones/subscription-1/rg-app-002/deploy.bicep](deployments/contoso/landing-zones/subscription-1/rg-app-002/deploy.bicep)
|
||||
|
||||
## Support
|
||||
|
||||
|
@ -61,7 +68,6 @@ or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any addi
|
|||
## Maintainers
|
||||
|
||||
- [Bernie White](https://github.com/BernieWhite)
|
||||
- [Sam Bell](https://github.com/ms-sambell)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
targetScope = 'resourceGroup'
|
||||
|
||||
metadata name = 'Storage Account'
|
||||
metadata description = 'Create or update an Storage Account.'
|
||||
|
||||
@sys.description('The name of the Storage Account.')
|
||||
param name string
|
||||
|
||||
@metadata({
|
||||
strongType: 'location'
|
||||
})
|
||||
@sys.description('The Azure region to deploy to.')
|
||||
param location string = resourceGroup().location
|
||||
|
||||
@allowed([
|
||||
'Standard_GRS'
|
||||
'Standard_LRS'
|
||||
])
|
||||
@sys.description('Create the Storage Account as LRS or GRS.')
|
||||
param sku string = 'Standard_GRS'
|
||||
|
||||
@sys.description('Determines if any containers can be configured with the anonymous access types of blob or container.')
|
||||
param allowBlobPublicAccess bool = true
|
||||
|
||||
@metadata({
|
||||
example: {
|
||||
service: '<service_name>'
|
||||
env: 'prod'
|
||||
}
|
||||
})
|
||||
@sys.description('Tags to apply to the resource.')
|
||||
param tags object = resourceGroup().tags
|
||||
|
||||
@sys.description('Create or update an Storage Account.')
|
||||
resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = {
|
||||
name: name
|
||||
location: location
|
||||
sku: {
|
||||
name: sku
|
||||
}
|
||||
kind: 'StorageV2'
|
||||
properties: {
|
||||
networkAcls: {
|
||||
bypass: 'AzureServices'
|
||||
virtualNetworkRules: []
|
||||
ipRules: []
|
||||
defaultAction: 'Deny'
|
||||
}
|
||||
supportsHttpsTrafficOnly: true
|
||||
accessTier: 'Hot'
|
||||
allowBlobPublicAccess: allowBlobPublicAccess
|
||||
minimumTlsVersion: 'TLS1_2'
|
||||
}
|
||||
tags: tags
|
||||
}
|
||||
|
||||
@sys.description('Configure Blob Services for a Storage Account.')
|
||||
resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2019-06-01' = {
|
||||
parent: storageAccount
|
||||
name: 'default'
|
||||
properties: {
|
||||
cors: {
|
||||
corsRules: []
|
||||
}
|
||||
deleteRetentionPolicy: {
|
||||
enabled: true
|
||||
days: 7
|
||||
}
|
||||
containerDeleteRetentionPolicy: {
|
||||
enabled: true
|
||||
days: 7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@sys.description('Configure File Services for a Storage Account.')
|
||||
resource fileServices 'Microsoft.Storage/storageAccounts/fileServices@2019-06-01' = {
|
||||
parent: storageAccount
|
||||
name: 'default'
|
||||
properties: {
|
||||
shareDeleteRetentionPolicy: {
|
||||
enabled: true
|
||||
days: 7
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// Note:
|
||||
// This Azure Bicep parameter file demonstrates using parameters.
|
||||
|
||||
using 'main.bicep'
|
||||
|
||||
// The env tag must be test, dev, or prod.
|
||||
// Try setting this to 'demo' to fail the custom organization Org.Azure.Tags rule.
|
||||
// See .ps-rule/Org.Rule.yaml for details.
|
||||
param environment = 'dev'
|
||||
|
||||
param name = 'kv-example-001'
|
||||
|
||||
// Key Vault should only accept explicitly allowed traffic through the firewall.
|
|
@ -1,3 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// Note:
|
||||
// This Azure Bicep code demonstrates using resources directly.
|
||||
// Also see parameter file for configurable options.
|
||||
|
||||
targetScope = 'resourceGroup'
|
||||
|
||||
param name string
|
||||
|
@ -20,9 +27,12 @@ resource vault 'Microsoft.KeyVault/vaults@2023-02-01' = {
|
|||
name: 'standard'
|
||||
}
|
||||
tenantId: tenant().tenantId
|
||||
|
||||
// Try setting any of these to false to flag an issue.
|
||||
enableSoftDelete: true
|
||||
enablePurgeProtection: true
|
||||
enableRbacAuthorization: true
|
||||
|
||||
networkAcls: {
|
||||
defaultAction: defaultAction
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
// Note:
|
||||
// This Azure Bicep code demonistrates a deployment of one or more modules.
|
||||
// This Azure Bicep code demonstrates a deployment of one or more modules.
|
||||
// This file has multiple template errors to show validation.
|
||||
|
||||
@description('Configures the location to deploy the Azure resources.')
|
||||
|
@ -42,7 +42,8 @@ module keyvault '../../../../../modules/keyvault/v1/main.bicep' = {
|
|||
workspaceId: '/subscriptions/<subscription_id>/resourceGroups/rg-test/providers/Microsoft.OperationalInsights/workspaces/latest001'
|
||||
|
||||
// An env tag must be test, dev, or prod.
|
||||
// Try setting this to 'demo' to fail the Org.Azure.Tags rule.
|
||||
// Try setting this to 'demo' to fail the custom organization Org.Azure.Tags rule.
|
||||
// See .ps-rule/Org.Rule.yaml for details.
|
||||
tags: {
|
||||
env: 'dev'
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// Note:
|
||||
// This Azure Bicep code demonstrates using an AVM module.
|
||||
|
||||
module vault 'br/public:avm/res/key-vault/vault:0.3.5' = {
|
||||
// The name of the deployment.
|
||||
name: '${uniqueString(deployment().name)}-test-kvvwaf'
|
||||
params: {
|
||||
|
||||
// The name of the key vault.
|
||||
name: 'kvvwaf002'
|
||||
|
||||
// Try setting any of these to false to flag an issue.
|
||||
enablePurgeProtection: true
|
||||
enableRbacAuthorization: true
|
||||
|
||||
networkAcls: {
|
||||
bypass: 'AzureServices'
|
||||
|
||||
// Try setting the firewall to 'Allow' traffic by default to flag an issue.
|
||||
defaultAction: 'Deny'
|
||||
}
|
||||
|
||||
diagnosticSettings: [
|
||||
{
|
||||
workspaceResourceId: '<workspaceResourceId>'
|
||||
}
|
||||
]
|
||||
|
||||
softDeleteRetentionInDays: 7
|
||||
|
||||
// An env tag must be test, dev, or prod.
|
||||
// Try setting this to 'demo' to fail the custom organization Org.Azure.Tags rule.
|
||||
// See .ps-rule/Org.Rule.yaml for details.
|
||||
tags: {
|
||||
env: 'dev'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
targetScope = 'resourceGroup'
|
||||
|
||||
metadata name = 'Key Vault'
|
||||
metadata description = 'Create or update an Azure Key Vault.'
|
||||
metadata version = '1.0.0'
|
||||
|
||||
@sys.description('The name of the Key Vault.')
|
||||
param name string
|
|
@ -0,0 +1,105 @@
|
|||
// Create or update a Private Endpoint for the Storage Account.
|
||||
|
||||
// ----------
|
||||
// PARAMETERS
|
||||
// ----------
|
||||
|
||||
@description('The name of the Private Endpoint.')
|
||||
param name string
|
||||
|
||||
@metadata({
|
||||
strongType: 'location'
|
||||
example: 'eastus'
|
||||
})
|
||||
@description('The Azure region to deploy to.')
|
||||
param location string
|
||||
|
||||
@description('The unique resource identifer for the resource to expose through the Private Endpoint.')
|
||||
param resourceId string
|
||||
|
||||
@allowed([
|
||||
'blob'
|
||||
'file'
|
||||
'table'
|
||||
'queue'
|
||||
])
|
||||
@description('The sub-resources to register the Private Endpoint for.')
|
||||
param groupId string
|
||||
|
||||
@metadata({
|
||||
strongType: 'Microsoft.Network/virtualNetworks/subnets'
|
||||
})
|
||||
@description('The unique resource identifer for the subnet to join the private endpoint to.')
|
||||
param subnetId string
|
||||
|
||||
@metadata({
|
||||
strongType: 'Microsoft.Network/privateDnsZones'
|
||||
})
|
||||
@description('The private DNS zone to register the private endpoint within.')
|
||||
param privateDnsZoneId string = ''
|
||||
|
||||
@description('Tags to apply to the resource.')
|
||||
param tags object
|
||||
|
||||
// ---------
|
||||
// VARIABLES
|
||||
// ---------
|
||||
|
||||
// ---------
|
||||
// RESOURCES
|
||||
// ---------
|
||||
|
||||
@description('Create or update a Private Endpoint for a resource.')
|
||||
resource endpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = {
|
||||
location: location
|
||||
name: name
|
||||
properties: {
|
||||
subnet: {
|
||||
id: subnetId
|
||||
}
|
||||
privateLinkServiceConnections: [
|
||||
{
|
||||
name: name
|
||||
properties: {
|
||||
privateLinkServiceId: resourceId
|
||||
groupIds: [
|
||||
groupId
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
tags: tags
|
||||
}
|
||||
|
||||
@description('Configures DNS for the Private Endpoint.')
|
||||
resource endpointGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-04-01' = if (!empty(privateDnsZoneId)) {
|
||||
parent: endpoint
|
||||
name: 'default'
|
||||
properties: {
|
||||
privateDnsZoneConfigs: [
|
||||
{
|
||||
name: replace(last(split(privateDnsZoneId, '/')), '.', '-')
|
||||
properties: {
|
||||
privateDnsZoneId: privateDnsZoneId
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// -------
|
||||
// OUTPUTS
|
||||
// -------
|
||||
|
||||
@description('A unique identifier for the Private Endpoint.')
|
||||
output id string = endpoint.id
|
||||
|
||||
@description('The name of the associated Private DNS Zone.')
|
||||
output privateDnsZone string = last(split(privateDnsZoneId, '/'))
|
||||
|
||||
@description('The name of the Resource Group where the Private Endpoint is deployed.')
|
||||
output resourceGroupName string = resourceGroup().name
|
||||
|
||||
@description('The guid for the subscription where the Private Endpoint is deployed.')
|
||||
output subscriptionId string = subscription().subscriptionId
|
|
@ -0,0 +1,67 @@
|
|||
// Configure role assignments for the Storage Account
|
||||
|
||||
// ----------
|
||||
// PARAMETERS
|
||||
// ----------
|
||||
|
||||
@sys.description('The display name of the role to assign or the GUID.')
|
||||
param role string
|
||||
|
||||
@sys.description('The GUID of the identity object to assign.')
|
||||
param principalId string
|
||||
|
||||
@sys.description('A description of the assignment.')
|
||||
param description string = ''
|
||||
|
||||
@allowed([
|
||||
'ServicePrincipal'
|
||||
'Group'
|
||||
'User'
|
||||
'ForeignGroup'
|
||||
'Device'
|
||||
])
|
||||
@sys.description('The principal type to assign.')
|
||||
param principalType string = 'ServicePrincipal'
|
||||
|
||||
@sys.description('The name of the Storage Account.')
|
||||
param resourceName string
|
||||
|
||||
// ---------
|
||||
// VARIABLES
|
||||
// ---------
|
||||
|
||||
var roles = {
|
||||
Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
|
||||
Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
|
||||
Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
|
||||
'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')
|
||||
'Storage Account Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')
|
||||
'Storage Account Key Operator Service Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')
|
||||
'Storage Blob Data Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')
|
||||
'Storage Blob Data Owner': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')
|
||||
'Storage Blob Data Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')
|
||||
'Storage File Data SMB Share Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')
|
||||
'Storage File Data SMB Share Elevated Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')
|
||||
'Storage File Data SMB Share Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')
|
||||
}
|
||||
var roleDefinitionId = contains(roles, role) ? roles[role] : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', role)
|
||||
|
||||
// ---------
|
||||
// RESOURCES
|
||||
// ---------
|
||||
|
||||
resource r 'Microsoft.Storage/storageAccounts@2023-01-01' existing = {
|
||||
name: resourceName
|
||||
}
|
||||
|
||||
@sys.description('Assign permissions to an Azure AD principal.')
|
||||
resource rbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
|
||||
name: guid(r.id, principalId, roleDefinitionId)
|
||||
scope: r
|
||||
properties: {
|
||||
principalId: principalId
|
||||
roleDefinitionId: roleDefinitionId
|
||||
principalType: principalType
|
||||
description: description
|
||||
}
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
// Create or update a Storage Account
|
||||
targetScope = 'resourceGroup'
|
||||
metadata name = 'Storage Account'
|
||||
metadata description = 'Deploys and configures a Storage Account, optionally with a Private Endpoint. When Private Endpoints are enabled, access from the Internet is blocked.'
|
||||
metadata version = '1.0.0'
|
||||
|
||||
// ----------
|
||||
// PARAMETERS
|
||||
// ----------
|
||||
|
||||
@minLength(3)
|
||||
@maxLength(24)
|
||||
@sys.description('The name of the Storage Account.')
|
||||
#disable-next-line BCP334
|
||||
param name string = take(deployment().name, 24)
|
||||
|
||||
@metadata({
|
||||
strongType: 'location'
|
||||
example: 'eastus'
|
||||
ignore: true
|
||||
})
|
||||
@sys.description('The Azure region to deploy to.')
|
||||
param location string = resourceGroup().location
|
||||
|
||||
@allowed([
|
||||
'StorageV2'
|
||||
'FileStorage'
|
||||
])
|
||||
@sys.description('The type of storage to use.')
|
||||
param storageKind string = 'StorageV2'
|
||||
|
||||
@allowed([
|
||||
'Standard_LRS'
|
||||
'Standard_GRS'
|
||||
'Standard_ZRS'
|
||||
'Premium_LRS'
|
||||
'Premium_ZRS'
|
||||
])
|
||||
@sys.description('Create the Storage Account as LRS, GRS, or ZRS.')
|
||||
param sku string = 'Standard_GRS'
|
||||
|
||||
@metadata({
|
||||
example: [
|
||||
{
|
||||
name: 'CONTAINER_NAME'
|
||||
publicAccess: 'None'
|
||||
metadata: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
@sys.description('An list of storage containers to create on the storage account. [See docs](https://npccloud.com/docs)')
|
||||
param containers containerType[] = []
|
||||
|
||||
@metadata({
|
||||
example: {
|
||||
enabled: true
|
||||
name: 'RULE_NAME'
|
||||
type: 'Lifecycle'
|
||||
definition: {
|
||||
actions: {
|
||||
baseBlob: {
|
||||
delete: {
|
||||
daysAfterModificationGreaterThan: 7
|
||||
}
|
||||
}
|
||||
}
|
||||
filters: {
|
||||
blobTypes: [
|
||||
'blockBlob'
|
||||
]
|
||||
prefixMatch: [
|
||||
'logs/'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@sys.description('An array of lifecycle management policies for the Storage Account.')
|
||||
param lifecycleRules object[] = []
|
||||
|
||||
@minValue(0)
|
||||
@maxValue(365)
|
||||
@metadata({
|
||||
example: 7
|
||||
})
|
||||
@sys.description('The number of days to retain deleted blobs. When set to 0, soft delete is disabled.')
|
||||
param blobSoftDeleteDays int = 7
|
||||
|
||||
@minValue(0)
|
||||
@maxValue(365)
|
||||
@metadata({
|
||||
example: 7
|
||||
})
|
||||
@sys.description('The number of days to retain deleted containers. When set to 0, soft delete is disabled.')
|
||||
param containerSoftDeleteDays int = 7
|
||||
|
||||
@metadata({
|
||||
example: [
|
||||
{
|
||||
name: 'SHARE_NAME'
|
||||
shareQuota: 5
|
||||
metadata: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
@sys.description('An array of file shares to create on the Storage Account.')
|
||||
param shares object[] = []
|
||||
|
||||
@metadata({
|
||||
ignore: true
|
||||
})
|
||||
@sys.description('Determines if large file shares are enabled. This can not be disabled once enabled.')
|
||||
param useLargeFileShares bool = false
|
||||
|
||||
@minValue(0)
|
||||
@maxValue(365)
|
||||
@metadata({
|
||||
example: 7
|
||||
})
|
||||
@sys.description('The number of days to retain deleted shares. When set to 0, soft delete is disabled.')
|
||||
param shareSoftDeleteDays int = 7
|
||||
|
||||
@allowed([
|
||||
'Deny'
|
||||
'Allow'
|
||||
])
|
||||
@sys.description('Deny or allow network traffic unless explicitly allowed.')
|
||||
param defaultFirewallAction string = 'Deny'
|
||||
|
||||
@metadata({
|
||||
example: [
|
||||
'x.x.x.x'
|
||||
]
|
||||
})
|
||||
@sys.description('Firewall rules to permit specific IP addresses access to storage.')
|
||||
param firewallIPRules string[] = []
|
||||
|
||||
@metadata({
|
||||
example: [
|
||||
'/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Network/virtualNetworks/VNET_NAME/subnets/SUBNET_NAME'
|
||||
]
|
||||
})
|
||||
@sys.description('A list of resource IDs to subnets that are permitted access to storage. For each entry, a service endpoint firewall rule is created for the subnet.')
|
||||
param firewallVirtualNetworkRules string[] = []
|
||||
|
||||
@sys.description('Determines if any containers can be configured with the anonymous access types of blob or container. By default, anonymous access to blobs and containers is disabled (`false`).')
|
||||
param allowBlobPublicAccess bool = false
|
||||
|
||||
@sys.description('Determines if access keys and SAS tokens can be used to access storage. By default, access keys and SAS tokens are disabled (`false`).')
|
||||
param allowSharedKeyAccess bool = false
|
||||
|
||||
@sys.description('Determines if the Azure Portal defaults to OAuth.')
|
||||
param defaultToOAuthAuthentication bool = true
|
||||
|
||||
@sys.maxLength(0)
|
||||
@sys.maxLength(5)
|
||||
@sys.description('Configures any CORS rules to apply to blob requests.')
|
||||
param cors corsRuleType[] = []
|
||||
|
||||
@metadata({
|
||||
example: [
|
||||
{
|
||||
principalId: 'OBJECT_ID'
|
||||
description: 'DESCRIPTION'
|
||||
principalType: 'Group'
|
||||
role: 'Contributor'
|
||||
}
|
||||
]
|
||||
})
|
||||
@sys.description('A list of additional role assignments for the Storage Account.')
|
||||
param assignments assignmentType[] = []
|
||||
|
||||
@metadata({
|
||||
strongType: 'Microsoft.Network/virtualNetworks/subnets'
|
||||
})
|
||||
@sys.description('The subnet to connect a private endpoint.')
|
||||
param subnetId string = ''
|
||||
|
||||
@sys.description('Additional tags to apply to the resource. Tags from the resource group will automatically be applied.')
|
||||
param tags object = {}
|
||||
|
||||
// -----
|
||||
// TYPES
|
||||
// -----
|
||||
|
||||
type corsRuleType = {
|
||||
@sys.description('A list of headers allowed to be part of the cross-origin request.')
|
||||
allowedHeaders: string[]
|
||||
|
||||
@sys.description('A list of HTTP methods that are allowed to be executed by the origin.')
|
||||
allowedMethods: ('CONNECT' | 'DELETE' | 'GET' | 'HEAD' | 'MERGE' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'TRACE')[]
|
||||
|
||||
@sys.description('A list of origin domains that will be allowed via CORS, or `*` to allow all domains.')
|
||||
allowedOrigins: string[]
|
||||
|
||||
@sys.description('A list of response headers to expose to CORS clients.')
|
||||
exposedHeaders: string[]
|
||||
|
||||
@sys.description('The number of seconds that the client/ browser should cache a preflight response.')
|
||||
maxAgeInSeconds: int
|
||||
}
|
||||
|
||||
type assignmentType = {
|
||||
@sys.minLength(36)
|
||||
@sys.maxLength(36)
|
||||
@sys.description('The GUID of the principal to assign.')
|
||||
principalId: string
|
||||
|
||||
@sys.description('A description for the assignment.')
|
||||
description: string?
|
||||
|
||||
@sys.description('The type of principal.')
|
||||
principalType: 'ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device'
|
||||
|
||||
@sys.description('''
|
||||
The a name or GUID for the role to assign. Common role assignments include:
|
||||
|
||||
- `Owner`
|
||||
- `Contributor`
|
||||
- `Reader`
|
||||
- `User Access Administrator`
|
||||
- `Storage Account Contributor`
|
||||
- `Storage Account Key Operator Service Role`
|
||||
- `Storage Blob Data Contributor`
|
||||
- `Storage Blob Data Owner`
|
||||
- `Storage Blob Data Reader`
|
||||
- `Storage File Data SMB Share Contributor`
|
||||
- `Storage File Data SMB Share Elevated Contributor`
|
||||
- `Storage File Data SMB Share Reader`
|
||||
|
||||
''')
|
||||
role: string
|
||||
}
|
||||
|
||||
type containerType = {
|
||||
@sys.description('The name of the container.')
|
||||
name: string
|
||||
|
||||
@sys.description('Determines if the container is exposed without authentication.')
|
||||
publicAccess: 'Blob' | 'Container' | 'None' | null
|
||||
|
||||
@sys.description('Additional metadata to assign to the container.')
|
||||
metadata: object?
|
||||
}
|
||||
|
||||
// ---------
|
||||
// VARIABLES
|
||||
// ---------
|
||||
|
||||
// Calculate storage account name using existing complex naming rules
|
||||
var storageAccountName = toLower(name)
|
||||
|
||||
// Always use large file shares if using FileStorage
|
||||
var configureLargeFileShares = storageKind == 'FileStorage' ? true : useLargeFileShares
|
||||
var largeFileSharesState = configureLargeFileShares ? 'Enabled' : 'Disabled'
|
||||
|
||||
// Configure private endpoints based on blob or file
|
||||
var blobEndpoint = [
|
||||
'blob'
|
||||
]
|
||||
var fileEndpoint = [
|
||||
'file'
|
||||
]
|
||||
var isFileStorage = storageKind == 'FileStorage'
|
||||
var usePrivateEndpoint = !empty(subnetId)
|
||||
var endpoints = !usePrivateEndpoint ? [] : isFileStorage ? fileEndpoint : blobEndpoint
|
||||
|
||||
// Configure tags
|
||||
var allTags = union(resourceGroup().tags, tags)
|
||||
|
||||
// ---------
|
||||
// RESOURCES
|
||||
// ---------
|
||||
|
||||
@sys.description('Create or update a Storage Account.')
|
||||
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
|
||||
name: storageAccountName
|
||||
location: location
|
||||
sku: {
|
||||
name: sku
|
||||
}
|
||||
kind: storageKind
|
||||
properties: {
|
||||
networkAcls: {
|
||||
defaultAction: usePrivateEndpoint ? 'Deny' : defaultFirewallAction
|
||||
bypass: 'AzureServices'
|
||||
ipRules: [for item in firewallIPRules: {
|
||||
action: 'Allow'
|
||||
value: item
|
||||
}]
|
||||
virtualNetworkRules: [for item in firewallVirtualNetworkRules: {
|
||||
action: 'Allow'
|
||||
|
||||
#disable-next-line use-resource-id-functions
|
||||
id: item
|
||||
}]
|
||||
resourceAccessRules: [
|
||||
{
|
||||
tenantId: tenant().tenantId
|
||||
|
||||
#disable-next-line use-resource-id-functions
|
||||
resourceId: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Security/datascanners/StorageDataScanner'
|
||||
}
|
||||
]
|
||||
}
|
||||
supportsHttpsTrafficOnly: true
|
||||
encryption: {
|
||||
services: {
|
||||
file: {
|
||||
enabled: true
|
||||
}
|
||||
blob: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
keySource: 'Microsoft.Storage'
|
||||
}
|
||||
accessTier: 'Hot'
|
||||
largeFileSharesState: largeFileSharesState
|
||||
allowBlobPublicAccess: allowBlobPublicAccess
|
||||
allowSharedKeyAccess: allowSharedKeyAccess
|
||||
defaultToOAuthAuthentication: allowSharedKeyAccess ? true : defaultToOAuthAuthentication
|
||||
minimumTlsVersion: 'TLS1_2'
|
||||
publicNetworkAccess: usePrivateEndpoint ? 'Disabled' : 'Enabled'
|
||||
}
|
||||
tags: allTags
|
||||
}
|
||||
|
||||
@sys.description('Configure blob services for the Storage Account.')
|
||||
resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
|
||||
parent: storageAccount
|
||||
name: 'default'
|
||||
properties: {
|
||||
cors: {
|
||||
corsRules: cors ?? []
|
||||
}
|
||||
deleteRetentionPolicy: {
|
||||
enabled: blobSoftDeleteDays > 0
|
||||
|
||||
#disable-next-line BCP329
|
||||
days: blobSoftDeleteDays > 0 ? blobSoftDeleteDays : null
|
||||
}
|
||||
containerDeleteRetentionPolicy: {
|
||||
enabled: containerSoftDeleteDays > 0
|
||||
|
||||
#disable-next-line BCP329
|
||||
days: containerSoftDeleteDays > 0 ? containerSoftDeleteDays : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@sys.description('Configure file services for the Storage Account.')
|
||||
resource fileServices 'Microsoft.Storage/storageAccounts/fileServices@2023-01-01' = {
|
||||
parent: storageAccount
|
||||
name: 'default'
|
||||
properties: {
|
||||
shareDeleteRetentionPolicy: {
|
||||
enabled: shareSoftDeleteDays > 0
|
||||
|
||||
#disable-next-line BCP329
|
||||
days: shareSoftDeleteDays > 0 ? shareSoftDeleteDays : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@sys.description('Create or update blob containers for the Storage Account.')
|
||||
resource storageContainers 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = [for item in containers: if (!empty(containers)) {
|
||||
parent: blobServices
|
||||
name: item.name
|
||||
properties: {
|
||||
metadata: contains(item, 'metadata') ? item.metadata : {}
|
||||
publicAccess: contains(item, 'publicAccess') ? item.publicAccess : 'None'
|
||||
}
|
||||
}]
|
||||
|
||||
@sys.description('Create or update file shares for the Storage Account.')
|
||||
resource storageShares 'Microsoft.Storage/storageAccounts/fileServices/shares@2023-01-01' = [for item in shares: if (!empty(shares)) {
|
||||
parent: fileServices
|
||||
name: item.name
|
||||
properties: {
|
||||
metadata: contains(item, 'metadata') ? item.metadata : {}
|
||||
shareQuota: contains(item, 'shareQuota') ? item.shareQuota : 5120
|
||||
}
|
||||
}]
|
||||
|
||||
@sys.description('Configure policies for managing blob lifecycle for the Storage Account.')
|
||||
resource managementPolicies 'Microsoft.Storage/storageAccounts/managementPolicies@2023-01-01' = if (!empty(lifecycleRules)) {
|
||||
parent: storageAccount
|
||||
name: 'default'
|
||||
properties: {
|
||||
policy: {
|
||||
rules: lifecycleRules
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@sys.description('Create or update a Private Endpoint for the Storage Account.')
|
||||
module pe '.bicep/pe.bicep' = [for endpoint in endpoints: {
|
||||
name: 'pend-${storageAccountName}-${endpoint[0]}-001'
|
||||
params: {
|
||||
name: 'pend-${storageAccountName}-${endpoint[0]}-001'
|
||||
location: location
|
||||
resourceId: storageAccount.id
|
||||
groupId: endpoint
|
||||
subnetId: subnetId
|
||||
tags: tags
|
||||
}
|
||||
}]
|
||||
|
||||
@sys.description('Create or update role assignments for the Storage Account.')
|
||||
module rbac '.bicep/rbac.bicep' = [for assignment in assignments: {
|
||||
name: 'assignment-${uniqueString(storageAccount.id, assignment.principalId, assignment.role)}'
|
||||
params: {
|
||||
principalId: assignment.principalId
|
||||
description: contains(assignment, 'description') ? assignment.description : ''
|
||||
principalType: assignment.principalType
|
||||
role: assignment.role
|
||||
resourceName: storageAccount.name
|
||||
}
|
||||
}]
|
||||
|
||||
// -------
|
||||
// OUTPUTS
|
||||
// -------
|
||||
|
||||
@sys.description('A unique identifier for the Storage Account.')
|
||||
output id string = storageAccount.id
|
||||
|
||||
@sys.description('The name of the Storage Account.')
|
||||
output storageAccountName string = storageAccountName
|
||||
|
||||
@sys.description('The name of the Resource Group where the Storage Account is deployed.')
|
||||
output resourceGroupName string = resourceGroup().name
|
||||
|
||||
@sys.description('The guid for the subscription where the Storage Account is deployed.')
|
||||
output subscriptionId string = subscription().subscriptionId
|
||||
|
||||
@sys.description('The primary blob endpoint for the Storage Account.')
|
||||
output blobEndpoint string = isFileStorage ? '' : storageAccount.properties.primaryEndpoints.blob
|
14
ps-rule.yaml
14
ps-rule.yaml
|
@ -19,7 +19,7 @@ execution:
|
|||
# Require minimum versions of modules.
|
||||
requires:
|
||||
PSRule: '@pre >=2.9.0'
|
||||
PSRule.Rules.Azure: '@pre >=1.31.1'
|
||||
PSRule.Rules.Azure: '@pre >=1.34.2'
|
||||
|
||||
# Use PSRule for Azure.
|
||||
include:
|
||||
|
@ -36,12 +36,11 @@ input:
|
|||
- '**'
|
||||
|
||||
# Include deployments.
|
||||
- '!bicep/deployments/**/*.bicepparam'
|
||||
- '!bicep/deployments/**/deploy.bicep'
|
||||
- '!template/**/*.parameters.json'
|
||||
- '!deployments/**/*.bicepparam'
|
||||
- '!deployments/**/deploy.bicep'
|
||||
|
||||
# Include module tests.
|
||||
- '!bicep/modules/**/*.tests.bicep'
|
||||
- '!modules/**/*.tests.bicep'
|
||||
|
||||
configuration:
|
||||
# Enable automatic expansion of Azure parameter files.
|
||||
|
@ -58,7 +57,10 @@ configuration:
|
|||
AZURE_BICEP_CHECK_TOOL: true
|
||||
|
||||
# Configure the minimum version of the Bicep CLI.
|
||||
AZURE_BICEP_MINIMUM_VERSION: '0.19.5'
|
||||
AZURE_BICEP_MINIMUM_VERSION: '0.25.53'
|
||||
|
||||
AZURE_DEPLOYMENT_NONSENSITIVE_PARAMETER_NAMES:
|
||||
- keys
|
||||
|
||||
# Suppression ignores rules for a specific Azure resource by name.
|
||||
suppression:
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"metadata": {
|
||||
"template": "template/templates/policy-exemption/v1/template.json"
|
||||
},
|
||||
"parameters": {
|
||||
"exemptionNameSuffix": {
|
||||
"value": "awesome-app-exemption-001"
|
||||
},
|
||||
"assignmentId": {
|
||||
"value": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/SecurityCenterBuiltIn"
|
||||
},
|
||||
"resourceGroup": {
|
||||
"value": "rg-awesome-app-prod-eus-001"
|
||||
},
|
||||
"exemptionCategory": {
|
||||
"value": "Waiver"
|
||||
},
|
||||
"description": {
|
||||
"value": "Allow cognitive services to be public and called directly from mobile workforce. This is a temporary exemption until VPN is implemented."
|
||||
},
|
||||
"displayName": {
|
||||
"value": "001: Temporary public access for Awesome App cognitive services"
|
||||
},
|
||||
"requestedBy": {
|
||||
"value": "Awesome App Team"
|
||||
},
|
||||
"approvedBy": {
|
||||
"value": "Security vTeam"
|
||||
},
|
||||
"expiresOnDate": {
|
||||
"value": "2099-12-28T00:00:00+10:00"
|
||||
},
|
||||
"policyDefinitionReferenceIds": {
|
||||
"value": [
|
||||
"cognitiveServicesAccountsShouldEnableDataEncryptionWithACustomerManagedKeyMonitoringEffect"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"metadata": {
|
||||
"template": "template/templates/storage/v1/template.json"
|
||||
},
|
||||
"parameters": {
|
||||
"storageAccountName": {
|
||||
"value": "sttemplateapp001"
|
||||
},
|
||||
"sku": {
|
||||
// The storage account must use GRS storage.
|
||||
// Try setting this to 'Standard_LRS' to fail the Azure.Storage.UseReplication rule.
|
||||
"value": "Standard_GRS"
|
||||
},
|
||||
"blobSoftDeleteDays": {
|
||||
// The storage account must use soft delete on blobs.
|
||||
// Try setting this to 0 or removing this parameter entirely to fail the 'Azure.Storage.SoftDelete' rule.
|
||||
"value": 7
|
||||
},
|
||||
"tags": {
|
||||
"value": {
|
||||
// An env tag must be test, dev, or prod.
|
||||
// Try setting this to 'demo' to fail the Org.Azure.Tags rule.
|
||||
"env": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
|
||||
"contentVersion": "2.0.0.0",
|
||||
"metadata": {
|
||||
"name": "Key Vault",
|
||||
"description": "Create or update a Key Vault."
|
||||
},
|
||||
"parameters": {
|
||||
"vaultName": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The name of the Key Vault.",
|
||||
"example": "<name>"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"defaultValue": "[resourceGroup().location]",
|
||||
"metadata": {
|
||||
"description": "The Azure region to deploy to.",
|
||||
"strongType": "location",
|
||||
"example": "eastus"
|
||||
}
|
||||
},
|
||||
"accessPolicies": {
|
||||
"type": "array",
|
||||
"defaultValue": [],
|
||||
"metadata": {
|
||||
"description": "The access policies defined for this vault.",
|
||||
"example": [
|
||||
{
|
||||
"objectId": "<object_id>",
|
||||
"tenantId": "<tenant_id>",
|
||||
"permissions": {
|
||||
"secrets": [
|
||||
"Get",
|
||||
"List",
|
||||
"Set"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"useDeployment": {
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"metadata": {
|
||||
"description": "Determines if Azure can deploy certificates from this Key Vault."
|
||||
}
|
||||
},
|
||||
"useTemplate": {
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"metadata": {
|
||||
"description": "Determines if templates can reference secrets from this Key Vault."
|
||||
}
|
||||
},
|
||||
"useDiskEncryption": {
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"metadata": {
|
||||
"description": "Determines if this Key Vault can be used for Azure Disk Encryption."
|
||||
}
|
||||
},
|
||||
"useSoftDelete": {
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"metadata": {
|
||||
"description": "Determine if soft delete is enabled on this Key Vault."
|
||||
}
|
||||
},
|
||||
"usePurgeProtection": {
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"metadata": {
|
||||
"description": "Determine if purge protection is enabled on this Key Vault."
|
||||
}
|
||||
},
|
||||
"softDeleteDays": {
|
||||
"type": "int",
|
||||
"defaultValue": 90,
|
||||
"minValue": 7,
|
||||
"maxValue": 90,
|
||||
"metadata": {
|
||||
"description": "The number of days to retain soft deleted vaults and vault objects."
|
||||
}
|
||||
},
|
||||
"useRBAC": {
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"metadata": {
|
||||
"description": "Determines if access to the objects granted using RBAC. When true, access policies are ignored."
|
||||
}
|
||||
},
|
||||
"networkAcls": {
|
||||
"type": "object",
|
||||
"defaultValue": {
|
||||
"defaultAction": "Allow",
|
||||
"bypass": "AzureServices",
|
||||
"ipRules": [],
|
||||
"virtualNetworkRules": []
|
||||
},
|
||||
"metadata": {
|
||||
"description": "The network firewall defined for this vault."
|
||||
}
|
||||
},
|
||||
"workspaceId": {
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"metadata": {
|
||||
"description": "The workspace to store audit logs.",
|
||||
"strongType": "Microsoft.OperationalInsights/workspaces",
|
||||
"example": "/subscriptions/<subscription_id>/resourceGroups/<resource_group>/providers/Microsoft.OperationalInsights/workspaces/<workspace_name>"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"metadata": {
|
||||
"description": "Tags to apply to the resource.",
|
||||
"example": {
|
||||
"service": "<service_name>",
|
||||
"env": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"comments": "Create or update a Key Vault.",
|
||||
"type": "Microsoft.KeyVault/vaults",
|
||||
"name": "[parameters('vaultName')]",
|
||||
"apiVersion": "2019-09-01",
|
||||
"location": "[parameters('location')]",
|
||||
"properties": {
|
||||
"enabledForDeployment": "[parameters('useDeployment')]",
|
||||
"enabledForTemplateDeployment": "[parameters('useTemplate')]",
|
||||
"enabledForDiskEncryption": "[parameters('useDiskEncryption')]",
|
||||
"accessPolicies": "[parameters('accessPolicies')]",
|
||||
"tenantId": "[subscription().tenantId]",
|
||||
"sku": {
|
||||
"name": "Standard",
|
||||
"family": "A"
|
||||
},
|
||||
"networkAcls": "[parameters('networkAcls')]",
|
||||
"enableSoftDelete": "[parameters('useSoftDelete')]",
|
||||
"enablePurgeProtection": "[parameters('usePurgeProtection')]",
|
||||
"softDeleteRetentionInDays": "[parameters('softDeleteDays')]",
|
||||
"enableRbacAuthorization": "[parameters('useRBAC')]"
|
||||
},
|
||||
"tags": "[parameters('tags')]",
|
||||
"resources": [
|
||||
{
|
||||
"comments": "Forward Key Vault audit events to a Log Analytics workspace.",
|
||||
"condition": "[not(empty(parameters('workspaceId')))]",
|
||||
"type": "Microsoft.KeyVault/vaults/providers/diagnosticSettings",
|
||||
"name": "[concat(parameters('vaultName'), '/Microsoft.Insights/service')]",
|
||||
"apiVersion": "2016-09-01",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.KeyVault/vaults/', parameters('vaultName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"workspaceId": "[parameters('workspaceId')]",
|
||||
"logs": [
|
||||
{
|
||||
"category": "AuditEvent",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"resourceId": {
|
||||
"type": "string",
|
||||
"value": "[resourceId('Microsoft.KeyVault/vaults', parameters('vaultName'))]",
|
||||
"metadata": {
|
||||
"description": "A unique resource identifier for the Key Vault."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/subscriptionDeploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"metadata": {
|
||||
"name": "Policy Exemption",
|
||||
"description": "Create or update an Azure Policy exemption for a Resource Group."
|
||||
},
|
||||
"parameters": {
|
||||
"exemptionNameSuffix": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "This value will be added as a suffix to the exemption name.",
|
||||
"example": ""
|
||||
}
|
||||
},
|
||||
"assignmentId": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The resource identifier to the policy assignment that will be exempt."
|
||||
}
|
||||
},
|
||||
"resourceGroup": {
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"metadata": {
|
||||
"description": "The name of the Resource Group where the exemption will be scoped.",
|
||||
"example": "<resource_group_name>"
|
||||
}
|
||||
},
|
||||
"exemptionCategory": {
|
||||
"type": "string",
|
||||
"defaultValue": "Waiver",
|
||||
"allowedValues": [
|
||||
"Waiver",
|
||||
"Mitigated"
|
||||
],
|
||||
"metadata": {
|
||||
"description": "The type of exemption."
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "A description for the policy exemption.",
|
||||
"example": "<description>"
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The display name of the policy exemption.",
|
||||
"example": "<display_name>"
|
||||
}
|
||||
},
|
||||
"requestedBy": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The team that own the resource that the exemption is being created for.",
|
||||
"example": "<requested_team>"
|
||||
}
|
||||
},
|
||||
"approvedBy": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The team that approved the exemption.",
|
||||
"example": "<approval_team>"
|
||||
}
|
||||
},
|
||||
"expiresOnDate": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The expiration date and time (in UTC ISO 8601 format yyyy-MM-ddTHH:mm:ssZ) of the policy exemption.",
|
||||
"example": "2021-04-28T00:00:00+10:00"
|
||||
}
|
||||
},
|
||||
"policyDefinitionReferenceIds": {
|
||||
"type": "array",
|
||||
"metadata": {
|
||||
"description": "An array of definition references that this resource is exempt from."
|
||||
}
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"exemptionName": "[concat(subscription().subscriptionId, '-', parameters('exemptionNameSuffix'))]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"comments": "Create or update an Azure Policy exemption for a Resource Group.",
|
||||
"name": "[variables('exemptionName')]",
|
||||
"type": "Microsoft.Resources/deployments",
|
||||
"apiVersion": "2019-10-01",
|
||||
"subscriptionId": "[subscription().subscriptionId]",
|
||||
"resourceGroup": "[parameters('resourceGroup')]",
|
||||
"location": "[if(empty(parameters('resourceGroup')), deployment().location, '')]",
|
||||
"properties": {
|
||||
"expressionEvaluationOptions": {
|
||||
"scope": "outer"
|
||||
},
|
||||
"mode": "Incremental",
|
||||
"template": {
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"resources": [
|
||||
{
|
||||
"name": "[variables('exemptionName')]",
|
||||
"type": "Microsoft.Authorization/policyExemptions",
|
||||
"apiVersion": "2020-07-01-preview",
|
||||
"properties": {
|
||||
"policyAssignmentId": "[parameters('assignmentId')]",
|
||||
"policyDefinitionReferenceIds": "[parameters('policyDefinitionReferenceIds')]",
|
||||
"exemptionCategory": "[parameters('exemptionCategory')]",
|
||||
"expiresOn": "[parameters('expiresOnDate')]",
|
||||
"displayName": "[parameters('displayName')]",
|
||||
"description": "[parameters('description')]",
|
||||
"metadata": {
|
||||
"requestedBy": "[parameters('requestedBy')]",
|
||||
"approvedBy": "[parameters('approvedBy')]",
|
||||
"createdBy": "DevOps deployment"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,349 +0,0 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
|
||||
"contentVersion": "2.1.1.0",
|
||||
"metadata": {
|
||||
"name": "Storage Account",
|
||||
"description": "Create or update a Storage Account."
|
||||
},
|
||||
"parameters": {
|
||||
"storageAccountName": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The name of the Storage Account.",
|
||||
"example": "<name>"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"defaultValue": "[resourceGroup().location]",
|
||||
"metadata": {
|
||||
"description": "The Azure region to deploy to.",
|
||||
"strongType": "location",
|
||||
"example": "EastUS"
|
||||
}
|
||||
},
|
||||
"sku": {
|
||||
"type": "string",
|
||||
"defaultValue": "Standard_LRS",
|
||||
"allowedValues": [
|
||||
"Standard_LRS",
|
||||
"Standard_GRS"
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Create the Storage Account as LRS or GRS."
|
||||
}
|
||||
},
|
||||
"suffixLength": {
|
||||
"type": "int",
|
||||
"defaultValue": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 13,
|
||||
"metadata": {
|
||||
"description": "Determine how many additional characters are added to the storage account name as a suffix."
|
||||
}
|
||||
},
|
||||
"containers": {
|
||||
"type": "array",
|
||||
"defaultValue": [],
|
||||
"metadata": {
|
||||
"description": "An array of storage containers to create on the storage account.",
|
||||
"example": [
|
||||
{
|
||||
"name": "logs",
|
||||
"publicAccess": "None",
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lifecycleRules": {
|
||||
"type": "array",
|
||||
"defaultValue": [],
|
||||
"metadata": {
|
||||
"description": "An array of lifecycle management policies for the storage account.",
|
||||
"example": {
|
||||
"enabled": true,
|
||||
"name": "<rule_name>",
|
||||
"type": "Lifecycle",
|
||||
"definition": {
|
||||
"actions": {
|
||||
"baseBlob": {
|
||||
"delete": {
|
||||
"daysAfterModificationGreaterThan": 7
|
||||
}
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"blobTypes": [
|
||||
"blockBlob"
|
||||
],
|
||||
"prefixMatch": [
|
||||
"logs/"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"blobSoftDeleteDays": {
|
||||
"type": "int",
|
||||
"defaultValue": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 365,
|
||||
"metadata": {
|
||||
"description": "The number of days to retain deleted blobs. When set to 0, soft delete is disabled.",
|
||||
"example": 7
|
||||
}
|
||||
},
|
||||
"containerSoftDeleteDays": {
|
||||
"type": "int",
|
||||
"defaultValue": 7,
|
||||
"minValue": 0,
|
||||
"maxValue": 365,
|
||||
"metadata": {
|
||||
"description": "The number of days to retain deleted containers. When set to 0, soft delete is disabled.",
|
||||
"example": 7
|
||||
}
|
||||
},
|
||||
"shares": {
|
||||
"type": "array",
|
||||
"defaultValue": [],
|
||||
"metadata": {
|
||||
"description": "An array of file shares to create on the storage account.",
|
||||
"example": [
|
||||
{
|
||||
"name": "<share_name>",
|
||||
"shareQuota": 5,
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"useLargeFileShares": {
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"metadata": {
|
||||
"description": "Determines if large file shares are enabled. This can not be disabled once enabled."
|
||||
}
|
||||
},
|
||||
"shareSoftDeleteDays": {
|
||||
"type": "int",
|
||||
"defaultValue": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 365,
|
||||
"metadata": {
|
||||
"description": "The number of days to retain deleted shares. When set to 0, soft delete is disabled.",
|
||||
"example": 7
|
||||
}
|
||||
},
|
||||
"allowBlobPublicAccess": {
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"metadata": {
|
||||
"description": "Determines if any containers can be configured with the anonymous access types of blob or container."
|
||||
}
|
||||
},
|
||||
"keyVaultPrincipalId": {
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"metadata": {
|
||||
"description": "Set to the objectId of Azure Key Vault to delegated permission for use with Key Managed Storage Accounts."
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"metadata": {
|
||||
"description": "Tags to apply to the resource.",
|
||||
"example": {
|
||||
"service": "<service_name>",
|
||||
"env": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"storageAccountName": "[concat(parameters('storageAccountName'), if(greater(parameters('suffixLength'), 0), substring(uniqueString(resourceGroup().id), 0, parameters('suffixLength')), ''))]",
|
||||
"blobSoftDeleteLookup": {
|
||||
"true": {
|
||||
"enabled": true,
|
||||
"days": "[parameters('blobSoftDeleteDays')]"
|
||||
},
|
||||
"false": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"containerSoftDeleteLookup": {
|
||||
"true": {
|
||||
"enabled": true,
|
||||
"days": "[parameters('containerSoftDeleteDays')]"
|
||||
},
|
||||
"false": null
|
||||
},
|
||||
"shareSoftDeleteLookup": {
|
||||
"true": {
|
||||
"enabled": true,
|
||||
"days": "[parameters('shareSoftDeleteDays')]"
|
||||
},
|
||||
"false": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"largeFileSharesState": "[if(parameters('useLargeFileShares'), 'Enabled', 'Disabled')]",
|
||||
"storageAccountKeyOperatorRoleId": "[resourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"comments": "Storage Account",
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"apiVersion": "2019-06-01",
|
||||
"name": "[variables('storageAccountName')]",
|
||||
"location": "[parameters('location')]",
|
||||
"sku": {
|
||||
"name": "[parameters('sku')]",
|
||||
"tier": "Standard"
|
||||
},
|
||||
"kind": "StorageV2",
|
||||
"properties": {
|
||||
"networkAcls": {
|
||||
"bypass": "AzureServices",
|
||||
"virtualNetworkRules": [],
|
||||
"ipRules": [],
|
||||
"defaultAction": "Deny"
|
||||
},
|
||||
"supportsHttpsTrafficOnly": true,
|
||||
"encryption": {
|
||||
"services": {
|
||||
"file": {
|
||||
"enabled": true
|
||||
},
|
||||
"blob": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"keySource": "Microsoft.Storage"
|
||||
},
|
||||
"accessTier": "Hot",
|
||||
"largeFileSharesState": "[variables('largeFileSharesState')]",
|
||||
"allowBlobPublicAccess": "[parameters('allowBlobPublicAccess')]",
|
||||
"minimumTlsVersion": "TLS1_2"
|
||||
},
|
||||
"tags": "[parameters('tags')]",
|
||||
"resources": [
|
||||
{
|
||||
"comments": "Configure blob storage services",
|
||||
"type": "Microsoft.Storage/storageAccounts/blobServices",
|
||||
"apiVersion": "2019-06-01",
|
||||
"name": "[concat(variables('storageAccountName'), '/default')]",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
|
||||
],
|
||||
"sku": {
|
||||
"name": "[parameters('sku')]"
|
||||
},
|
||||
"properties": {
|
||||
"cors": {
|
||||
"corsRules": []
|
||||
},
|
||||
"deleteRetentionPolicy": "[variables('blobSoftDeleteLookup')[string(greater(parameters('blobSoftDeleteDays'), 0))]]",
|
||||
"containerDeleteRetentionPolicy": "[variables('containerSoftDeleteLookup')[string(greater(parameters('containerSoftDeleteDays'), 0))]]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comments": "Configure file storage services",
|
||||
"type": "Microsoft.Storage/storageAccounts/fileServices",
|
||||
"apiVersion": "2019-06-01",
|
||||
"name": "[concat(variables('storageAccountName'), '/default')]",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
|
||||
],
|
||||
"sku": {
|
||||
"name": "[parameters('sku')]"
|
||||
},
|
||||
"properties": {
|
||||
"shareDeleteRetentionPolicy": "[variables('shareSoftDeleteLookup')[string(greater(parameters('shareSoftDeleteDays'), 0))]]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"comments": "Create a blob container",
|
||||
"condition": "[not(equals(length(parameters('containers')), 0))]",
|
||||
"type": "Microsoft.Storage/storageAccounts/blobServices/containers",
|
||||
"apiVersion": "2019-06-01",
|
||||
"name": "[if(equals(length(parameters('containers')), 0), concat(variables('storageAccountName'), '/default/empty'), concat(variables('storageAccountName'), '/default/', parameters('containers')[copyIndex('containerIndex')].name))]",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('storageAccountName'), 'default')]",
|
||||
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
|
||||
],
|
||||
"copy": {
|
||||
"mode": "Parallel",
|
||||
"count": "[if(equals(length(parameters('containers')), 0), 1, length(parameters('containers')))]",
|
||||
"name": "containerIndex"
|
||||
},
|
||||
"properties": {
|
||||
"metadata": "[parameters('containers')[copyIndex('containerIndex')].metadata]",
|
||||
"publicAccess": "[parameters('containers')[copyIndex('containerIndex')].publicAccess]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comments": "Create blob lifecycle policy",
|
||||
"condition": "[not(empty(parameters('lifecycleRules')))]",
|
||||
"name": "[concat(variables('storageAccountName'), '/default')]",
|
||||
"type": "Microsoft.Storage/storageAccounts/managementPolicies",
|
||||
"apiVersion": "2019-06-01",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"policy": {
|
||||
"rules": "[parameters('lifecycleRules')]"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"comments": "Create a share",
|
||||
"condition": "[not(equals(length(parameters('shares')), 0))]",
|
||||
"type": "Microsoft.Storage/storageAccounts/fileServices/shares",
|
||||
"apiVersion": "2019-06-01",
|
||||
"name": "[if(equals(length(parameters('shares')), 0), concat(variables('storageAccountName'), '/default/empty'), concat(variables('storageAccountName'), '/default/', parameters('shares')[copyIndex('shareIndex')].name))]",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('storageAccountName'), 'default')]",
|
||||
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
|
||||
],
|
||||
"copy": {
|
||||
"mode": "Parallel",
|
||||
"count": "[if(equals(length(parameters('shares')), 0), 1, length(parameters('shares')))]",
|
||||
"name": "shareIndex"
|
||||
},
|
||||
"properties": {
|
||||
"metadata": "[parameters('shares')[copyIndex('shareIndex')].metadata]",
|
||||
"shareQuota": "[parameters('shares')[copyIndex('shareIndex')].shareQuota]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comments": "Delegate Key Vault permission to rotate keys",
|
||||
"condition": "[not(empty(parameters('keyVaultPrincipalId')))]",
|
||||
"type": "Microsoft.Storage/storageAccounts/providers/roleAssignments",
|
||||
"apiVersion": "2018-09-01-preview",
|
||||
"name": "[concat(variables('storageAccountName'), '/Microsoft.Authorization/', guid(parameters('keyVaultPrincipalId'), variables('storageAccountKeyOperatorRoleId')))]",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"roleDefinitionId": "[variables('storageAccountKeyOperatorRoleId')]",
|
||||
"principalId": "[parameters('keyVaultPrincipalId')]",
|
||||
"scope": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
|
||||
"principalType": "ServicePrincipal"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"resourceId": {
|
||||
"type": "string",
|
||||
"value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
|
||||
"metadata": {
|
||||
"description": "A unique resource identifier for the Storage Account."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче